diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 164afa07e2..908981c52e 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -15,6 +15,7 @@ module.exports = { "plugin:matrix-org/typescript", "prettier", "plugin:rxjs/recommended", + "plugin:storybook/recommended", ], parserOptions: { ecmaVersion: "latest", diff --git a/.githooks/post-commit b/.githooks/post-commit deleted file mode 100755 index 467799bd81..0000000000 --- a/.githooks/post-commit +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/sh - -FILE=.links.temp-disabled.yaml -if test -f "$FILE"; then - # Only do the post-commit hook if the file was temp-disabled by the pre-commit hook. - # Otherwise linking was actively (`yarn links:disable`) disabled and this hook should noop. - mv .links.temp-disabled.yaml .links.yaml - yarnLog=$(yarn) - echo "[yarn-linker] The post-commit hook has re-enabled .links.yaml." - exit 1 -fi diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 435d75f131..2656c9b939 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,11 +1,9 @@ -#!/usr/bin/sh +#!/usr/bin/env bash -FILE=".links.yaml" -if test -f "$FILE"; then - mv .links.yaml .links.temp-disabled.yaml - # echo "running yarn" - x=$(yarn) - y=$(git add yarn.lock) - echo "[yarn-linker] The pre-commit hook has disabled .links.yaml and MODIFIED the yarn.lock file. Review the staged changes (the hook added yarn.lock, was this desired?) and run \`git commit \` again if they look okay. The post-commit hook will re-enable your links." +# Checks if there currently is linking configured. Informs the user to disable linking before committing. + +PNPMFILE=.pnpmfile.cjs +if test -f "$PNPMFILE"; then + echo "[pnpm-linker] The pre-commit hook detected $PNPMFILE which implies you have linked packages in your pnpm-lock.yaml. Run pnpm links:off and commit again. See also linking.md." exit 1 fi diff --git a/.github/workflows/build-element-call.yaml b/.github/workflows/build-element-call.yaml index 7adc9903b7..878816c3ac 100644 --- a/.github/workflows/build-element-call.yaml +++ b/.github/workflows/build-element-call.yaml @@ -37,15 +37,16 @@ jobs: persist-credentials: false - name: Enable Corepack run: corepack enable - - name: Yarn cache + - name: pnpm cache uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - cache: "yarn" + cache: "pnpm" node-version-file: ".node-version" - name: Install dependencies - run: "yarn install --immutable" + # ignore-pnpmfile should never be commited. Make CI crash if it happened (`pnpmfileChecksum` is present) + run: "pnpm install --frozen-lockfile --ignore-pnpmfile" - name: Build Element Call - run: yarn run build:"$PACKAGE":"$BUILD_MODE" + run: pnpm run build:"$PACKAGE":"$BUILD_MODE" env: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 763d2eacb8..0638eca6d5 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -12,20 +12,21 @@ jobs: persist-credentials: false - name: Enable Corepack run: corepack enable - - name: Yarn cache + - name: pnpm cache uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - cache: "yarn" + cache: "pnpm" node-version-file: ".node-version" - name: Install dependencies - run: "yarn install --immutable" + # ignore-pnpmfile should never be commited. Make CI crash if it happened (`pnpmfileChecksum` is present) + run: "pnpm install --frozen-lockfile --ignore-pnpmfile" - name: Prettier - run: "yarn run prettier:check" + run: "pnpm run prettier:check" - name: i18n - run: "yarn run i18n:check" + run: "pnpm run i18n:check" - name: ESLint - run: "yarn run lint:eslint" + run: "pnpm run lint:eslint" - name: Type check - run: "yarn run lint:types" + run: "pnpm run lint:types" - name: Dead code analysis - run: "yarn run lint:knip" + run: "pnpm run lint:knip" diff --git a/.github/workflows/publish-embedded-packages.yaml b/.github/workflows/publish-embedded-packages.yaml index bfbf9feb1e..ea90a34c67 100644 --- a/.github/workflows/publish-embedded-packages.yaml +++ b/.github/workflows/publish-embedded-packages.yaml @@ -97,7 +97,7 @@ jobs: run: find ${FILENAME_PREFIX} -type f -print0 | sort -z | xargs -0 sha256sum | tee ${FILENAME_PREFIX}.sha256 - name: Upload if: ${{ needs.versioning.outputs.DRY_RUN == 'false' }} - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: files: | ${{ env.FILENAME_PREFIX }}.tar.gz @@ -164,7 +164,7 @@ jobs: NEEDS_PUBLISH_NPM_OUTPUTS_ARTIFACT_VERSION: ${{ needs.publish_npm.outputs.ARTIFACT_VERSION }} - name: Add release notes if: ${{ needs.versioning.outputs.DRY_RUN == 'false' }} - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: append_body: true body: | diff --git a/.github/workflows/test-netlify.yaml b/.github/workflows/test-netlify.yaml new file mode 100644 index 0000000000..bccde44507 --- /dev/null +++ b/.github/workflows/test-netlify.yaml @@ -0,0 +1,48 @@ +# Triggers after the playwright tests have finished, +# taking the artifact and uploading it to Netlify for easier viewing +name: Upload End to End Test report to Netlify +on: + # Privilege escalation necessary to publish to Netlify + # 🚨 We must not execute any checked out code here. + workflow_run: # zizmor: ignore[dangerous-triggers] + workflows: ["Test"] + types: + - completed + +concurrency: + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.run_id }} + cancel-in-progress: ${{ github.event.workflow_run.event == 'pull_request' }} + +permissions: {} + +jobs: + report: + if: github.event.workflow_run.conclusion != 'cancelled' + name: Report results + runs-on: ubuntu-24.04 + environment: Netlify + permissions: + statuses: write + deployments: write + actions: read + steps: + - name: Download HTML report + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + name: html-report + path: playwright-report + + - name: 📤 Deploy to Netlify + uses: matrix-org/netlify-pr-preview@9805cd123fc9a7e421e35340a05e1ebc5dee46b5 # v3 + with: + path: playwright-report + owner: ${{ github.event.workflow_run.head_repository.owner.login }} + branch: ${{ github.event.workflow_run.head_branch }} + revision: ${{ github.event.workflow_run.head_sha }} + token: ${{ secrets.NETLIFY_AUTH_TOKEN }} + site_id: ${{ secrets.NETLIFY_SITE_ID }} + desc: Playwright Report + deployment_env: EndToEndTests + prefix: "e2e-" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index cd1c94c56c..2cec64f562 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,11 +2,15 @@ name: Test on: pull_request: {} push: - branches: [livekit, full-mesh] + branches: [livekit] jobs: vitest: name: Run unit tests runs-on: ubuntu-latest + container: + # Make sure to grab the latest version of the Playwright image + # https://playwright.dev/docs/docker#pull-the-image + image: mcr.microsoft.com/playwright:v1.59.1-noble steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -14,17 +18,18 @@ jobs: persist-credentials: false - name: Enable Corepack run: corepack enable - - name: Yarn cache + - name: pnpm cache uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - cache: "yarn" + cache: "pnpm" node-version-file: ".node-version" - name: Install dependencies - run: "yarn install --immutable" + # ignore-pnpmfile should never be commited. Make CI crash if it happened (`pnpmfileChecksum` is present) + run: "pnpm install --frozen-lockfile --ignore-pnpmfile" - name: Vitest - run: "yarn run test:coverage" + run: "pnpm run test:coverage" - name: Upload to codecov - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 + uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: @@ -42,12 +47,13 @@ jobs: run: corepack enable - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - cache: "yarn" + cache: "pnpm" node-version-file: ".node-version" - name: Install dependencies - run: yarn install --immutable + # ignore-pnpmfile should never be commited. Make CI crash if it happened (`pnpmfileChecksum` is present) + run: pnpm install --frozen-lockfile --ignore-pnpmfile - name: Install Playwright Browsers - run: yarn playwright install --with-deps + run: pnpm exec playwright install --with-deps - name: Run backend components run: | docker compose -f playwright-backend-docker-compose.yml -f playwright-backend-docker-compose.override.yml pull @@ -56,10 +62,11 @@ jobs: - name: Run Playwright tests env: USE_DOCKER: 1 - run: yarn playwright test - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + run: pnpm exec playwright test + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: ${{ !cancelled() }} with: - name: playwright-report - path: playwright-report/ - retention-days: 3 + name: html-report + path: playwright-report + if-no-files-found: error + retention-days: 4 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index d3b6e9698f..104e073ef7 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -20,4 +20,4 @@ jobs: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 diff --git a/.gitignore b/.gitignore index 5751844a7d..e9225072dd 100644 --- a/.gitignore +++ b/.gitignore @@ -21,12 +21,20 @@ yarn-error.log !/.yarn/releases !/.yarn/sdks !/.yarn/versions +# old yarn based linking /.links.yaml /.links.disabled.yaml /.links.temp-disabled.yaml +# pnpm based linking +/.links.cjs +/.links.disabled.cjs +/.links.temp-disabled.cjs # Playwright /test-results/ /playwright-report/ /blob-report/ /playwright/.cache/ + +*storybook.log +storybook-static diff --git a/.prettierignore b/.prettierignore index f06235c460..31e6cd83b0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ +pnpm-lock.yaml node_modules dist diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 0000000000..e227ef765b --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,36 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import type { StorybookConfig } from "@storybook/react-vite"; + +const config: StorybookConfig = { + stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + addons: ["@storybook/addon-docs", "@storybook/addon-vitest"], + framework: "@storybook/react-vite", + // THIS IS IMPORTANT + // vitest runs without Vite's normal dependency optimization, so we need to manually include the polyfills for the stories to work. + // otherwise we will get: new dependencies optimized: ... + // and + // ``` + // [vitest] Vite unexpectedly reloaded a test. This may cause tests to fail, lead to flaky behaviour or duplicated test runs. + // For a stable experience, please add mentioned dependencies to your config's `optimizeDeps.include` field manually. + // ``` + // which breaks the storybook ci on the first and only run. + viteFinal(config) { + config.optimizeDeps = { + ...config.optimizeDeps, + include: [ + ...(config.optimizeDeps?.include ?? []), + "vite-plugin-node-polyfills/shims/buffer", + "vite-plugin-node-polyfills/shims/global", + "vite-plugin-node-polyfills/shims/process", + ], + }; + return config; + }, +}; +export default config; diff --git a/.storybook/manager.ts b/.storybook/manager.ts new file mode 100644 index 0000000000..1177be2fda --- /dev/null +++ b/.storybook/manager.ts @@ -0,0 +1,31 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { create } from "storybook/theming"; +import { addons } from "storybook/manager-api"; + +addons.setConfig({ + theme: create({ + base: "light", + colorPrimary: "#1b1d22", + colorSecondary: "#0467dd", + + // Typography + fontBase: '"Inter", sans-serif', + fontCode: '"Inconsolata", monospace', + + // Text colors + textColor: "#1b1d22", + appBg: "#ffffff", + barBg: "#ffffff", + + brandTitle: "Element Call", + brandUrl: "https://element.io/", + brandImage: "/src/icons/Logo.svg", + brandTarget: "_self", + }), +}); diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 0000000000..757c1f8a74 --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,56 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import type { Preview } from "@storybook/react-vite"; +import { TooltipProvider } from "@vector-im/compound-web"; +import i18n from "i18next"; +import { logger } from "matrix-js-sdk/lib/logger"; + +import EN from "../locales/en/app.json"; +import { initReactI18next } from "react-i18next"; +import "../src/index.css"; + +// Bare-minimum i18n config +i18n + .use(initReactI18next) + .init({ + lng: "en", + fallbackLng: "en", + supportedLngs: ["en"], + // We embed the translations, so that it never needs to fetch + resources: { + en: { + translation: EN, + }, + }, + interpolation: { + escapeValue: false, // React has built-in XSS protections + }, + }) + .catch((e) => logger.warn("Failed to init i18n for stories", e)); + +const preview: Preview = { + parameters: { + layout: "centered", + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + tags: ["autodocs"], + decorators: [ + (Story) => ( + + + + ), + ], +}; + +export default preview; diff --git a/.yarn/plugins/linker.cjs b/.yarn/plugins/linker.cjs deleted file mode 100644 index cf7181f9ad..0000000000 --- a/.yarn/plugins/linker.cjs +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright 2025 New Vector Ltd. - -SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE in the repository root for full details. -*/ - -module.exports = { - name: "linker", - factory: (require) => ({ - hooks: { - // Yarn's plugin system is very light on documentation. The best we have - // for this hook is simply the type definition in - // https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-core/sources/Plugin.ts - registerPackageExtensions: async (config, registerPackageExtension) => { - const { structUtils } = require("@yarnpkg/core"); - const { parseSyml } = require("@yarnpkg/parsers"); - const path = require("path"); - const fs = require("fs"); - const process = require("process"); - - // Create a descriptor that we can use to target our direct dependencies - const projectPath = config.projectCwd - .replace(/\\/g, "/") - .replace("/C:/", "C:/"); - const manifestPath = path.join(projectPath, "package.json"); - const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); - const selfDescriptor = structUtils.parseDescriptor( - `${manifest.name}@*`, - true, - ); - - // Load the list of linked packages - const linksPath = path.join(projectPath, ".links.yaml"); - let linksFile; - try { - linksFile = fs.readFileSync(linksPath, "utf8"); - } catch (e) { - return; // File doesn't exist, there's nothing to link - } - let links; - try { - links = parseSyml(linksFile); - } catch (e) { - console.error(".links.yaml has invalid syntax", e); - process.exit(1); - } - - // Resolve paths and turn them into a Yarn package extension - const overrides = Object.fromEntries( - Object.entries(links).map(([name, link]) => [ - name, - `portal:${path.resolve(config.projectCwd, link)}`, - ]), - ); - const overrideIdentHashes = new Set(); - for (const name of Object.keys(overrides)) - overrideIdentHashes.add( - structUtils.parseDescriptor(`${name}@*`, true).identHash, - ); - - // Extend our own package's dependencies with these local overrides - registerPackageExtension(selfDescriptor, { dependencies: overrides }); - - // Filter out the original dependencies from the package spec so Yarn - // actually respects the overrides - const filterDependencies = (original) => { - const pkg = structUtils.copyPackage(original); - pkg.dependencies = new Map( - Array.from(pkg.dependencies.entries()).filter( - ([, value]) => !overrideIdentHashes.has(value.identHash), - ), - ); - return pkg; - }; - - // Patch Yarn's own normalizePackage method to use the above filter - const originalNormalizePackage = config.normalizePackage; - config.normalizePackage = function (pkg, extensions) { - return originalNormalizePackage.call( - this, - pkg.identHash === selfDescriptor.identHash - ? filterDependencies(pkg) - : pkg, - extensions, - ); - }; - }, - }, - }), -}; diff --git a/.yarnrc.yml b/.yarnrc.yml deleted file mode 100644 index 538de0e70b..0000000000 --- a/.yarnrc.yml +++ /dev/null @@ -1,3 +0,0 @@ -nodeLinker: node-modules -plugins: - - .yarn/plugins/linker.cjs diff --git a/README.md b/README.md index 688a7a7f8f..5df2c42ee1 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ requiring a separate Matrix client. ### 📲 In-App Calling (Widget Mode in Messenger Apps) -When used as a widget 🧩, Element Call is solely responsible on the core calling +When used as a widget 🧩, Element Call is solely responsible for the core calling functionality (MatrixRTC). Authentication, event handling, and room state updates (via the Client-Server API) are handled by the hosting client. Communication between Element Call and the client is managed through the widget @@ -108,18 +108,18 @@ recommended method for embedding Element Call.

For more details on the packages, see the -[Embedded vs. Standalone Guide](./docs/embedded-standalone.md). +[Embedded vs. Standalone Guide](./docs/embedded_standalone.md). ## 🛠️ Self-Hosting For operating and deploying Element Call on your own server, refer to the -[**Self-Hosting Guide**](./docs/self-hosting.md). +[**Self-Hosting Guide**](./docs/self_hosting.md). ## 🧭 MatrixRTC Backend Discovery and Selection For proper Element Call operation each site deployment needs a MatrixRTC backend -setup as outlined in the [Self-Hosting](#self-hosting). A typical federated site -deployment for three different sites A, B and C is depicted below. +setup as outlined in the [Self-Hosting Guide](./docs/self_hosting.md). A typical +federated site deployment for three different sites A, B and C is depicted below.

Element Call federated setup @@ -127,7 +127,7 @@ deployment for three different sites A, B and C is depicted below. ### Backend Discovery -MatrixRTC backend (according to +The MatrixRTC backend (according to [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)) is announced by the Matrix site's `.well-known/matrix/client` file and discovered via the `org.matrix.msc4143.rtc_foci` key, e.g.: @@ -151,11 +151,10 @@ via `livekit_service_url`. - Each call participant proposes their discovered MatrixRTC backend from `org.matrix.msc4143.rtc_foci` in their `org.matrix.msc3401.call.member` state event. -- For **LiveKit** MatrixRTC backend +- For the **LiveKit** MatrixRTC backend ([MSC4195](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)), - the **first participant who joined the call** defines via the `foci_preferred` - key in their `org.matrix.msc3401.call.member` which actual MatrixRTC backend - will be used for this call. + the **first participant who joined the call** defines which backend will be used for this call via + the `foci_preferred` key in their `org.matrix.msc3401.call.member` state event. - During the actual call join flow, the **[MatrixRTC Authorization Service](https://github.com/element-hq/lk-jwt-service)** provides the client with the **LiveKit SFU WebSocket URL** and an **access JWT token** in order to exchange media via WebRTC. @@ -178,6 +177,13 @@ discuss and coordinate translation efforts. ## 🛠️ Development +### Dependencies + +- Node.js (e.g. via [nvm](https://github.com/nvm-sh/nvm)) +- [Corepack](https://github.com/nodejs/corepack) (not bundled with Node.js anymore starting from 25.0.0) +- Docker client and runtime + Docker Compose (for the backend) + - On macOS you can install everything with `brew install colima docker docker-compose` + ### Frontend To get started clone and set up this project: @@ -186,7 +192,7 @@ To get started clone and set up this project: git clone https://github.com/element-hq/element-call.git cd element-call corepack enable -yarn +pnpm install ``` To use it, create a local config by, e.g., @@ -197,12 +203,12 @@ environment as outlined in the next section out of box. You're now ready to launch the development server: ```sh -yarn dev +pnpm dev ``` See also: -- [Developing with linked packages](./linking.md) +- [Developing with linked packages](./docs/linking.md) ### Backend @@ -210,28 +216,29 @@ A docker compose file `dev-backend-docker-compose.yml` is provided to start the whole stack of components which is required for a local development environment including federation: -- Minimum Synapse Setup (servernameis: `synapse.m.localhost`, `synapse.othersite.m.localhost`) -- MatrixRTC Authorization Service (Note requires Federation API and hence a TLS reverse proxy) +- Minimum Synapse Setup (servernames: `synapse.m.localhost`, `synapse.othersite.m.localhost`) +- MatrixRTC Authorization Service (Note: requires Federation API and hence a TLS reverse proxy) - Minimum LiveKit SFU setup using dev defaults for config - Minimum `localhost` Certificate Authority (CA) for Transport Layer Security (TLS) - Hostnames: `m.localhost`, `*.m.localhost`, `*.othersite.m.localhost` - - Add [./backend/dev_tls_local-ca.crt](./backend/dev_tls_local-ca.crt) to your web browsers trusted + - Add [./backend/dev_tls_local-ca.crt](./backend/dev_tls_local-ca.crt) to your web browser's trusted certificates - Minimum TLS reverse proxy for - Synapse homeserver: `synapse.m.localhost` and `synapse.othersite.m.localhost` - MatrixRTC backend: `matrix-rtc.m.localhost` and `matrix-rtc.othersite.m.localhost` - - Local Element Call development `call.m.localhost` via `yarn dev --host ` + - Local Element Call development `call.m.localhost` via `pnpm dev --host ` - Element Web `app.m.localhost` and `app.othersite.m.localhost` - Note certificates will expire on Thr, 20 September 2035 14:27:35 CEST These use a test 'secret' published in this repository, so this must be used only for local development and **_never be exposed to the public Internet._** -Run backend components: +Make sure your Docker runtime is running (e.g. via `colima start`) and then start +the backend components: ```sh -yarn backend -# or for podman-compose +pnpm backend +# or for podman-compose: # podman-compose -f dev-backend-docker-compose.yml up ``` @@ -242,7 +249,7 @@ yarn backend > `https://synapse.m.localhost/.well-known/matrix/client`. This can be either > done by adding the minimum localhost CA > ([./backend/dev_tls_local-ca.crt](./backend/dev_tls_local-ca.crt)) to your web -> browsers trusted certificates or by simply copying and pasting each URL into +> browser's trusted certificates or by simply copying and pasting each URL into > your browser’s address bar and follow the prompts to add the exception. ### Playwright tests @@ -260,13 +267,13 @@ on https://localhost:3000 (this is configured in `playwright.config.ts`) - this is what will be tested. The local backend environment should be running for the test to work: -`yarn backend` +`pnpm backend` There are a few different ways to run the tests yourself. The simplest is to run: ```shell -yarn run test:playwright +pnpm run test:playwright ``` This will run the Playwright tests once, non-interactively. @@ -274,7 +281,7 @@ This will run the Playwright tests once, non-interactively. There is a more user-friendly way to run the tests in interactive mode: ```shell -yarn run test:playwright:open +pnpm run test:playwright:open ``` The easiest way to develop new test is to use the codegen feature of Playwright: @@ -316,7 +323,7 @@ To add a new translation key you can do these steps: 1. Add the new key entry to the code where the new key is used: `t("some_new_key")` -1. Run `yarn i18n` to extract the new key and update the translation files. This +1. Run `pnpm i18n` to extract the new key and update the translation files. This will add a skeleton entry to the `locales/en/app.json` file: ```jsonc diff --git a/WIDGET_TEST.md b/WIDGET_TEST.md index 53e26a29da..fbad026a48 100644 --- a/WIDGET_TEST.md +++ b/WIDGET_TEST.md @@ -1,6 +1,6 @@ # Testing Element-Call in widget mode -When running `yarn backend` the latest element-web develop will be deployed and served on `http://localhost:8081`. +When running `pnpm backend` the latest element-web develop will be deployed and served on `http://localhost:8081`. In a development environment, you might prefer to just use the `element-web` repo directly, but this setup is useful for CI/CD testing. ## Setup @@ -18,7 +18,7 @@ that uses It is part of the existing backend setup. To start the backend, run: ```sh -yarn backend +pnpm backend ``` Then open `http://localhost:8081` in your browser. diff --git a/backend/dev_homeserver-othersite.yaml b/backend/dev_homeserver-othersite.yaml index 81e775cab7..7eb8f294a9 100644 --- a/backend/dev_homeserver-othersite.yaml +++ b/backend/dev_homeserver-othersite.yaml @@ -50,6 +50,9 @@ max_event_delay_duration: 24h enable_registration: true enable_registration_without_verification: true +# Shared secret for admin user registration via API (for testing only!) +registration_shared_secret: "test_shared_secret_for_local_dev_only" + report_stats: false serve_server_wellknown: true diff --git a/backend/dev_homeserver.yaml b/backend/dev_homeserver.yaml index dc7b42c84d..0aea2ece25 100644 --- a/backend/dev_homeserver.yaml +++ b/backend/dev_homeserver.yaml @@ -50,6 +50,9 @@ max_event_delay_duration: 24h enable_registration: true enable_registration_without_verification: true +# Shared secret for admin user registration via API (for testing only!) +registration_shared_secret: "test_shared_secret_for_local_dev_only" + report_stats: false serve_server_wellknown: true diff --git a/backend/dev_livekit-othersite.yaml b/backend/dev_livekit-othersite.yaml index 0ae98c2404..53fc9ce94a 100644 --- a/backend/dev_livekit-othersite.yaml +++ b/backend/dev_livekit-othersite.yaml @@ -18,3 +18,7 @@ keys: devkey: secret room: auto_create: false +webhook: + api_key: devkey + urls: + - https://matrix-rtc.othersite.m.localhost/livekit/jwt/sfu_webhook diff --git a/backend/dev_livekit.yaml b/backend/dev_livekit.yaml index 157e4d04c4..6cef4241a8 100644 --- a/backend/dev_livekit.yaml +++ b/backend/dev_livekit.yaml @@ -18,3 +18,7 @@ keys: devkey: secret room: auto_create: false +webhook: + api_key: devkey + urls: + - https://matrix-rtc.m.localhost/livekit/jwt/sfu_webhook diff --git a/backend/dev_nginx.conf b/backend/dev_nginx.conf index d3ddbc531d..6ec0d7010d 100644 --- a/backend/dev_nginx.conf +++ b/backend/dev_nginx.conf @@ -28,14 +28,19 @@ server { # Reason: the lk-jwt-service uses the federation API for the openid token # verification, which requires TLS location ~ ^(/_matrix|/_synapse/client) { - proxy_pass "http://homeserver:8008"; + proxy_pass "http://homeserver:8008"; proxy_http_version 1.1; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $host; + proxy_set_header Host $host; + } + location ~ ^(/_matrix|/_synapse/admin) { + proxy_pass "http://homeserver:8008"; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; } - - error_page 500 502 503 504 /50x.html; } @@ -73,10 +78,16 @@ server { proxy_http_version 1.1; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $host; + proxy_set_header Host $host; } - error_page 500 502 503 504 /50x.html; + location ~ ^(/_matrix|/_synapse/admin) { + proxy_pass "http://homeserver-1:18008"; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + } } @@ -108,7 +119,7 @@ server { # JWT Service running at port 6080 proxy_pass http://jwt-auth-services/; - + } location ^~ /livekit/sfu/ { @@ -128,8 +139,6 @@ server { # LiveKit SFU websocket connection running at port 7880 proxy_pass http://livekit-sfu:7880/; } - - error_page 500 502 503 504 /50x.html; } @@ -156,7 +165,7 @@ server { # JWT Service running at port 16080 proxy_pass http://auth-service-1:16080/; - + } location ^~ /livekit/sfu/ { @@ -176,14 +185,12 @@ server { # LiveKit SFU websocket connection running at port 17880 proxy_pass http://livekit-sfu-1:17880/; } - - error_page 500 502 503 504 /50x.html; } # Convenience reverse proxy for the call.m.localhost domain to element call # running on the host either via -# - yarn dev --host or +# - pnpm dev --host or # - falling back to http (the element call docker container) server { listen 80; @@ -228,7 +235,6 @@ server { proxy_pass http://host.docker.internal:8080; } - error_page 500 502 503 504 /50x.html; } @@ -260,8 +266,6 @@ server { proxy_ssl_verify off; } - - error_page 500 502 503 504 /50x.html; } @@ -293,7 +297,5 @@ server { proxy_ssl_verify off; } - - error_page 500 502 503 504 /50x.html; } diff --git a/backend/playwright_homeserver-othersite.yaml b/backend/playwright_homeserver-othersite.yaml index 35640ae9a7..86c77b35f9 100644 --- a/backend/playwright_homeserver-othersite.yaml +++ b/backend/playwright_homeserver-othersite.yaml @@ -50,6 +50,9 @@ max_event_delay_duration: 24h enable_registration: true enable_registration_without_verification: true +# Shared secret for admin user registration via API (for testing only!) +registration_shared_secret: "test_shared_secret_for_local_dev_only" + report_stats: false serve_server_wellknown: true diff --git a/backend/playwright_homeserver.yaml b/backend/playwright_homeserver.yaml index a83247cd90..8f4375241c 100644 --- a/backend/playwright_homeserver.yaml +++ b/backend/playwright_homeserver.yaml @@ -50,6 +50,9 @@ max_event_delay_duration: 24h enable_registration: true enable_registration_without_verification: true +# Shared secret for admin user registration via API (for testing only!) +registration_shared_secret: "test_shared_secret_for_local_dev_only" + report_stats: false serve_server_wellknown: true diff --git a/dev-backend-docker-compose.yml b/dev-backend-docker-compose.yml index 8d885399d4..702aef395f 100644 --- a/dev-backend-docker-compose.yml +++ b/dev-backend-docker-compose.yml @@ -3,7 +3,7 @@ networks: services: auth-service: - image: ghcr.io/element-hq/lk-jwt-service:sha-f8ddd00 + image: ghcr.io/element-hq/lk-jwt-service:0.4.4 pull_policy: always hostname: auth-server environment: @@ -25,7 +25,7 @@ services: - ecbackend auth-service-1: - image: ghcr.io/element-hq/lk-jwt-service:sha-f8ddd00 + image: ghcr.io/element-hq/lk-jwt-service:0.4.4 pull_policy: always hostname: auth-server-1 environment: @@ -47,7 +47,7 @@ services: - ecbackend livekit: - image: livekit/livekit-server:v1.9.11 + image: livekit/livekit-server:v1.10.1 pull_policy: always hostname: livekit-sfu command: --dev --config /etc/livekit.yaml @@ -62,12 +62,15 @@ services: - 7882:7882/tcp - 50100-50200:50100-50200/udp volumes: + - ./backend/dev_tls_m.localhost.crt:/local_cert.pem:Z - ./backend/dev_livekit.yaml:/etc/livekit.yaml:Z + environment: + - SSL_CERT_FILE=/local_cert.pem networks: - ecbackend livekit-1: - image: livekit/livekit-server:v1.9.11 + image: livekit/livekit-server:v1.10.1 pull_policy: always hostname: livekit-sfu-1 command: --dev --config /etc/livekit.yaml @@ -82,7 +85,10 @@ services: - 17882:17882/tcp - 50300-50400:50300-50400/udp volumes: + - ./backend/dev_tls_m.localhost.crt:/local_cert.pem:Z - ./backend/dev_livekit-othersite.yaml:/etc/livekit.yaml:Z + environment: + - SSL_CERT_FILE=/local_cert.pem networks: - ecbackend @@ -164,6 +170,8 @@ services: - "8448:8448" extra_hosts: - "host.docker.internal:host-gateway" + - "auth-server:127.0.0.1" + - "auth-server-1:127.0.0.1" depends_on: - synapse networks: diff --git a/docs/README.md b/docs/README.md index d97e8d5687..e5a5d08a39 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,8 +2,8 @@ This folder contains documentation for setup, usage, and development of Element Call. -- [Embedded vs standalone mode](./embedded-standalone.md) -- [Url format and parameters](./url-params.md) +- [Embedded vs standalone mode](./embedded_standalone.md) +- [Url format and parameters](./url_params.md) - [Global JS controls](./controls.md) -- [Self-Hosting](./self-hosting.md) +- [Self-Hosting](./self_hosting.md) - [Developing with linked packages](./linking.md) diff --git a/docs/controls.md b/docs/controls.md index e5e0746d95..b97fe795d8 100644 --- a/docs/controls.md +++ b/docs/controls.md @@ -12,7 +12,7 @@ A few aspects of Element Call's interface can be controlled through a global API On mobile platforms (iOS, Android), web views do not reliably support selecting audio output devices such as the main speaker, earpiece, or headset. To address this limitation, the following functions allow the hosting application (e.g., Element Web, Element X) to manage audio devices via exposed JavaScript interfaces. These functions must be enabled using the URL parameter `controlledAudioDevices` to take effect. -- `controls.setAvailableAudioDevices(devices: { id: string, name: string, forEarpiece?: boolean, isEarpiece?: boolean isSpeaker?: boolean, isExternalHeadset?, boolean; }[]): void` Sets the list of available audio outputs. `forEarpiece` is used on iOS only. +- `controls.setAvailableAudioDevices(devices: { id: string, name: string, forEarpiece?: boolean, isEarpiece?: boolean isSpeaker?: boolean, isExternalHeadset?: boolean }[]): void` Sets the list of available audio outputs. `forEarpiece` is used on iOS only. It flags the device that should be used if the user selects earpiece mode. This should be the main stereo loudspeaker of the device. - `controls.onAudioDeviceSelect: ((id: string) => void) | undefined` Callback called whenever the user or application selects a new audio output. - `controls.setAudioDevice(id: string): void` Sets the selected audio device in Element Call's menu. This should be used if the OS decides to automatically switch to Bluetooth, for example. diff --git a/docs/embedded-standalone.md b/docs/embedded_standalone.md similarity index 88% rename from docs/embedded-standalone.md rename to docs/embedded_standalone.md index 440dfac0d3..456ce120ae 100644 --- a/docs/embedded-standalone.md +++ b/docs/embedded_standalone.md @@ -14,7 +14,7 @@ The table below provides a comparison of the two packages: | **Release artifacts** | Docker Image, Tarball | Tarball, NPM for Web, Android AAR, SwiftPM for iOS | | **Recommended for** | Standalone/guest access usage | Embedding within messenger apps | | **Responsibility for regulatory compliance** | The administrator that is deploying the app is responsible for compliance with any applicable regulations (e.g. privacy) | The developer of the messenger app is responsible for compliance | -| **Analytics consent** | Element Call will show a consent UI. | Element Call will not show a consent UI. The messenger app should only provide the embedded Element Call with the [analytics URL parameters](./url-params.md#embedded-only-parameters) if consent has been granted. | +| **Analytics consent** | Element Call will show a consent UI. | Element Call will not show a consent UI. The messenger app should only provide the embedded Element Call with the [analytics URL parameters](./url_params.md#embedded-only-parameters) if consent has been granted. | | **Analytics data** | Element Call will send data to the Posthog, Sentry and Open Telemetry targets specified by the administrator in the `config.json` | Element Call will send data to the Posthog and Sentry targets specified in the URL parameters by the messenger app | ### Using the embedded package within a messenger app @@ -25,8 +25,8 @@ The basics are: 1. Add the appropriate platform dependency as given for a [release](https://github.com/element-hq/element-call/releases), or use the embedded tarball. e.g. `npm install @element-hq/element-call-embedded@0.9.0` 2. Include the assets from the platform dependency in the build process. e.g. copy the assets during a [Webpack](https://github.com/element-hq/element-web/blob/247cd8d56d832d006d7dfb919d1042529d712b59/webpack.config.js#L677-L682) build. -3. Use the `index.html` entrypointof the imported assets when you are constructing the WebView or iframe. e.g. using a [relative path in a webapp](https://github.com/element-hq/element-web/blob/247cd8d56d832d006d7dfb919d1042529d712b59/src/models/Call.ts#L680), or on the the Android [WebViewAssetLoader](https://github.com/element-hq/element-x-android/blob/fe5aab6588ecdcf9354a3bfbd9e97c1b31175a8f/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt#L20) -4. Set any of the [embedded-only URL parameters](./url-params.md#embedded-only-parameters) that you need. +3. Use the `index.html` entrypoint of the imported assets when you are constructing the WebView or iframe. e.g. using a [relative path in a webapp](https://github.com/element-hq/element-web/blob/247cd8d56d832d006d7dfb919d1042529d712b59/src/models/Call.ts#L680), or on the the Android [WebViewAssetLoader](https://github.com/element-hq/element-x-android/blob/fe5aab6588ecdcf9354a3bfbd9e97c1b31175a8f/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt#L20) +4. Set any of the [embedded-only URL parameters](./url_params.md#embedded-only-parameters) that you need. ## Widget vs standalone mode @@ -35,5 +35,5 @@ Element Call is developed using the [js-sdk](https://github.com/matrix-org/matri As a widget, the app only uses the core calling (MatrixRTC) parts. The rest (authentication, sending events, getting room state updates about calls) is done by the hosting client. Element Call and the hosting client are connected via the widget API. -Element Call detects that it is run as a widget if a widgetId is defined in the url parameters. If `widgetId` is present then Element Call will try to connect to the client via the widget postMessage API using the parameters provided in [Url Format and parameters -](./url-params.md). +Element Call detects that it is run as a widget if `widgetId` is defined in the url parameters. If `widgetId` is present then Element Call will try to connect to the client via the widget postMessage API using the parameters provided in [Url Format and parameters +](./url_params.md). diff --git a/docs/linking.md b/docs/linking.md index 0abbc73ec1..3a18844d40 100644 --- a/docs/linking.md +++ b/docs/linking.md @@ -1,39 +1,65 @@ +## Quickstart guide + +Run: + +```bash +./scripts/setup-linking.sh +``` + +Read the script output: + +``` +Setup complete. +Update: .links.cjs to your liking +Run: 'pnpm links:on' to test your .links.cjs +Run: 'git commit' with links enabled to test the git pre-commit hook. +Run: 'pnpm links:off' to be able to commit again +Run: 'git config --local core.hooksPath ""' to allow committing with linking (not recommended) +Run: 'rm links.cjs' & 'git config --local core.hooksPath ""' to fully revert what this script did +``` + # Developing with linked packages -If you want to make changes to a package that Element Call depends on and see those changes applied in real time, you can create a link to a local copy of the package. Yarn has a command for this (`yarn link`), but it's not recommended to use it as it ends up modifying package.json with details specific to your development environment. +If you want to make changes to a package that Element Call depends on and see those changes applied in real time, you can create a link to a local copy of the package. `pnpm` has a command for this (`pnpm link`), but it's not recommended to use it as it ends up modifying package.json with details specific to your development environment. -Instead, you can use our little 'linker' plugin. Create a file named `.links.yaml` in the Element Call project directory, listing the names and paths of any dependencies you want to link. For example: +Instead, create a file named `.links.cjs` in the Element Call project directory (or run `./scripts/setup-linking.sh` to create a template), listing the names and paths of any dependencies you want to link. For example: -```yaml -matrix-js-sdk: ../path/to/matrix-js-sdk -"@vector-im/compound-web": /home/alice/path/to/compound-web +```cjs +// Packages to link to local checkouts +module.exports = { + "matrix-js-sdk": "../your/path/matrix-js-sdk", + "matrix-widget-api": "../your/path/matrix-widget-api", +}; ``` -Then run `yarn install`. +Then run `pnpm links:on`. (this will activate the pnpm file + run `pnpm install` to setup the linking) ## Hooks -Changes in `.links.yaml` will also update `yarn.lock` when `yarn` is executed. The lockfile will then contain the local +Changes in `.links.cjs` will also update `pnpm-lock.yaml` when `pnpm install` is executed. The lockfile will then contain the local version of the package which would not work on others dev setups or the github CI. -One always needs to run: + +One always needs to remove the pnpm `readPackage` script (the `.pnpmfile.cjs`) and run: ```bash -mv .links.yaml .links.disabled.yaml -yarn +pnpm install ``` before committing a change. -To make it more convenient to work with this linking system we added git hooks for your conviniece. -A `pre-commit` hook will run `mv .links.yaml .links.disabled.yaml`, `yarn` and `git add yarn.lock` if it detects -a `.links.yaml` file and abort the commit. -You will than need to check if the resulting changes are appropriate and commit again. - -A `post-commit` hook will setup the linking as it was -before if a `.links.disabled.yaml` is present. It runs `mv .links.disabled.yaml .links.yaml` and `yarn`. +To make this less of a foot gun we added a git hook. +A `pre-commit` hook will check if linking is currently used. If it detects +a `.pnpmfile.cjs` file it will abort the commit with an explanatory message. +You will then need to run `pnpm links:off` and commit again. -To activate the hooks automatically configure git with +To activate the hooks configure git with (when using the setup script (`./scripts/setup-linking.sh`) this is already done): ```bash -git config --local core.hooksPath .githooks/ +git config --local core.hooksPath .githooks ``` + +This will add the hook path for this repository only to .gihooks. which is a tracked (by git) folder containing the pre-commit hook. + +## Background + +Information, why this approach is used can be found in the [linking concept reasoning](./linking_concept_reasoning.md) document. diff --git a/docs/linking_concept_reasoning.md b/docs/linking_concept_reasoning.md new file mode 100644 index 0000000000..d065ba0b78 --- /dev/null +++ b/docs/linking_concept_reasoning.md @@ -0,0 +1,30 @@ +### Why do we not enable .pnpmfile.cjs by default + +Background: The presence of the `.pnpmfile.cjs` adds a field to the `pnpm-lock.yaml` called: `pnpmfileChecksum`. This field is a checksum of the content of the `.pnpmfile.cjs` file. +`pnpm install --frozen-lockfile` **fails** if there is a `.pnpmfile.cjs` but no `pnpmfileChecksum` or vice versa (or on mismatch). + +_TLDR: running with `--ignore-pnpmfile` will fail if `pnpmfileChecksum` is present._ + +#### `pnpmfileChecksum` + renovate bot + +When the renovate bot creates a PR it runs `pnpm install --ignore-pnpmfile`. This means that the `pnpmfileChecksum` in the lockfile will be **empty**. +This breaks builds that **don't** ignore the `.pnpmfile.cjs`-file. (CI that runs on the renovate PR) +From here we have two possible paths: + +- ignore `.pnpmfile.cjs` in all CI builds (CI will also fail if we accidently add it locally). +- fixup the `pnpm-lock.yaml` in the renovate PR to contain the correct `pnpmfileChecksum`. + +Ignoring in all CI builds means that CI will always fail if we enable the linking system. +This is annoying but can be worked around with the git hook we provide that at least lets us know that we are +commiting with enabled linking. +Only if we remember setting it back/disbale linking (or let ourselves remember by the git hook) the CI will work. + +#### Summary + +- We will always run into conflicts with the `pnpmfileChecksum` because in renovate prs it will be empty (`--ignore-pnpmfile`) +- To keep it simple we set `--ignore-pnpmfile` in all of our CI builds to see issues immediately. +- The only solution is to never have a `.pnpmfile.cjs` in the repository when pushing. + - This way there will never be a commit with `pnpmfileChecksum` in the lockfile. + - renovate (which uses `--ignore-pnpmfile` which we cannot disable) and other CI will work. +- We are able to use the linking system locally if we `cp` this file from the scripts folder into `./` on demand. +- `pnpm links:on` and `pnpm links:off` + `./scripts/setup-linking.sh` will help us with this. diff --git a/docs/self-hosting.md b/docs/self_hosting.md similarity index 97% rename from docs/self-hosting.md rename to docs/self_hosting.md index d6d4642179..e8ea2f6d88 100644 --- a/docs/self-hosting.md +++ b/docs/self_hosting.md @@ -58,7 +58,7 @@ rc_message: rc_delayed_event_mgmt: # This needs to match at least the heart-beat frequency plus a bit of headroom - # Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s + # Currently the heart-beat is every 5 seconds which translates into a rate of 0.2Hz per_second: 1 burst_count: 20 ``` @@ -70,7 +70,7 @@ make sure that your Synapse server has either a `federation` or `openid` ### MatrixRTC Backend -In order to **guarantee smooth operation** of Element Call MatrixRTC backend is +In order to **guarantee smooth operation** of Element Call, a MatrixRTC backend is required for each site deployment. ![MSC4195 compatible setup](MSC4195_setup.drawio.png) @@ -190,8 +190,8 @@ backend mxrtc_auth_backend > [!IMPORTANT] > As defined in -> [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) -> MatrixRTC backend must be announced to the client via your **Matrix site's +> [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143), +> the MatrixRTC backend(s) must be announced to the client via your **Matrix site's > `.well-known/matrix/client`** file (e.g. > `example.com/.well-known/matrix/client` matching the site deployment example > from above). The configuration is a list of Foci configs: @@ -222,7 +222,7 @@ Access-Control-Allow-Headers: X-Requested-With, Content-Type, Authorization > [!NOTE] > Most `org.matrix.msc4143.rtc_foci` configurations will only have one entry in -> the array +> the array. ## Building Element Call @@ -237,8 +237,8 @@ source. First, clone and install the package: git clone https://github.com/element-hq/element-call.git cd element-call corepack enable -yarn -yarn build +pnpm install +pnpm build ``` If all went well, you can now find the build output under `dist` as a series of @@ -291,7 +291,7 @@ be able to handle those yet and it may behave unreliably. Therefore, to use a self-hosted homeserver, this is recommended to be a new server where any user account created has not joined any normal rooms anywhere -in the Matrix federated network. The homeserver used can be setup to disable +in the Matrix federated network. The homeserver used can be set up to disable federation, so as to prevent spam registrations (if you keep registrations open) and to ensure Element Call continues to work in case any user decides to log in to their Element Call account using the standard Element app and joins normal diff --git a/docs/url-params.md b/docs/url_params.md similarity index 60% rename from docs/url-params.md rename to docs/url_params.md index e24e9823f7..4d567e8452 100644 --- a/docs/url-params.md +++ b/docs/url_params.md @@ -4,7 +4,7 @@ There are two formats for Element Call URLs. ## Link for sharing -Requires Element Call to be deployed in [standalone](./embedded-standalone.md) mode. +Requires Element Call to be deployed in [standalone](./embedded_standalone.md) mode. ```text https://element_call.domain/room/# @@ -12,7 +12,7 @@ https://element_call.domain/room/# ``` The URL is split into two sections. The `https://element_call.domain/room/#` -contains the app and the intend that the link brings you into a specific room +contains the app and the intent that the link brings you into a specific room (`https://call.element.io/#` would be the homepage). The fragment is used for query parameters to make sure they never get sent to the element_call.domain server. Here we have the actual Matrix room ID and the password which are used @@ -36,61 +36,60 @@ possible to support encryption. | Package | Deployment | URL | | ------------------------------------ | ----------------------------- | ----------------------------------------------------------------------------- | -| [Full](./embedded-standalone.md) | All | `https://element_call.domain/room` | -| [Embedded](./embedded-standalone.md) | Remote URL | `https://element_call.domain/` n.b. no `/room` part | -| [Embedded](./embedded-standalone.md) | Embedded within messenger app | Platform dependent, but you load the `index.html` file without a `/room` part | +| [Full](./embedded_standalone.md) | All | `https://element_call.domain/room` | +| [Embedded](./embedded_standalone.md) | Remote URL | `https://element_call.domain/` n.b. no `/room` part | +| [Embedded](./embedded_standalone.md) | Embedded within messenger app | Platform dependent, but you load the `index.html` file without a `/room` part | ## Parameters ### Common Parameters -These parameters are relevant to both [widget](./embedded-standalone.md) and [standalone](./embedded-standalone.md) modes: - -| Name | Values | Required for widget | Required for SPA | Description | -| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `intent` | `start_call`, `join_existing`, `start_call_dm`, `join_existing_dm. | No, defaults to `start_call` | No, defaults to `start_call` | The intent is a special url parameter that defines the defaults for all the other parameters. In most cases it should be enough to only set the intent to setup element-call. | -| `allowIceFallback` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Allows use of fallback STUN servers for ICE if the user's homeserver doesn’t provide any. | -| `posthogUserId` | Posthog analytics ID | No | No | Available only with user's consent for sharing telemetry in Element Web. | -| `appPrompt` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Prompts the user to launch the native mobile app upon entering a room, applicable only on Android and iOS, and must be enabled in config. | -| `confineToRoom` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Keeps the user confined to the current call/room. | -| `displayName` | | No | No | Display name used for auto-registration. | -| `enableE2EE` (deprecated) | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Legacy flag to enable end-to-end encryption, not used in the `livekit` branch. | -| `fontScale` | A decimal number such as `0.9` | No, defaults to `1.0` | No, defaults to `1.0` | Factor by which to scale the interface's font size. | -| `fonts` | | No | No | Defines the font(s) used by the interface. Multiple font parameters can be specified: `?font=font-one&font=font-two...`. | -| `header` | `none`, `standard` or `app_bar` | No, defaults to `standard` | No, defaults to `standard` | The style of headers to show. `standard` is the default arrangement, `none` hides the header entirely, and `app_bar` produces a header with a back button like you might see in mobile apps. The callback for the back button is `window.controls.onBackButtonPressed`. | -| `hideScreensharing` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Hides the screen-sharing button. | -| `homeserver` | | Not applicable | No | Homeserver for registering a new (guest) user, configures non-default guest user server when creating a spa link. | -| `lang` | [BCP 47](https://www.rfc-editor.org/info/bcp47) code | No | No | The language the app should use. | -| `password` | | No | No | E2EE password when using a shared secret. (For individual sender keys in embedded mode this is not required.) | -| `perParticipantE2EE` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Enables per participant encryption with Keys exchanged over encrypted matrix room messages. | -| `controlledAudioDevices` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Whether the [global JS controls for audio devices](./controls.md#audio-devices) should be enabled, allowing the list of audio devices to be controlled by the app hosting Element Call. | -| `roomId` | [Matrix Room ID](https://spec.matrix.org/v1.12/appendices/#room-ids) | Yes | No | Anything about what room we're pointed to should be from useRoomIdentifier which parses the path and resolves alias with respect to the default server name, however roomId is an exception as we need the room ID in embedded widget mode, and not the room alias (or even the via params because we are not trying to join it). This is also not validated, where it is in `useRoomIdentifier()`. | -| `showControls` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Displays controls like mute, screen-share, invite, and hangup buttons during a call. | -| `skipLobby` (deprecated: use `intent` instead) | `true` or `false` | No. If `intent` is explicitly `start_call` then defaults to `true`. Otherwise defaults to `false` | No, defaults to `false` | Skips the lobby to join a call directly, can be combined with preload in widget. When `true` the audio and video inputs will be muted by default. (This means there currently is no way to start without muted video if one wants to skip the lobby. Also not in widget mode.) | -| `theme` | One of: `light`, `dark`, `light-high-contrast`, `dark-high-contrast` | No, defaults to `dark` | No, defaults to `dark` | UI theme to use. | -| `viaServers` | Comma separated list of [Matrix Server Names](https://spec.matrix.org/v1.12/appendices/#server-name) | Not applicable | No | Homeserver for joining a room, non-empty value required for rooms not on the user’s default homeserver. | -| `sendNotificationType` | `ring` or `notification` | No | No | Will send a "ring" or "notification" `m.rtc.notification` event if the user is the first one in the call. | -| `autoLeaveWhenOthersLeft` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Whether the app should automatically leave the call when there is no one left in the call. | -| `waitForCallPickup` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | When sending a notification, show UI that the app is awaiting an answer, play a dial tone, and (in widget mode) auto-close the widget once the notification expires. | +These parameters are relevant to both [widget](./embedded_standalone.md) and [standalone](./embedded_standalone.md) modes: + +| Name | Values | Required for widget | Required for SPA | Description | +| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `intent` | `start_call`, `join_existing`, `start_call_voice`, `join_existing_voice`, `start_call_dm`, `join_existing_dm`, `start_call_dm_voice`, or `join_existing_dm_voice`. | No, defaults to `start_call` | No, defaults to `start_call` | The intent is a special url parameter that defines the defaults for all the other parameters. In most cases it should be enough to only set the intent to setup element-call. | +| `allowIceFallback` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Allows use of fallback STUN servers for ICE if the user's homeserver doesn’t provide any. | +| `posthogUserId` | Posthog analytics ID | No | No | Available only with user's consent for sharing telemetry in Element Web. | +| `confineToRoom` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Keeps the user confined to the current call/room. | +| `displayName` | | No | No | Display name used for auto-registration. | +| `enableE2EE` (deprecated) | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Legacy flag to enable end-to-end encryption, not used in the `livekit` branch. | +| `fontScale` | A decimal number such as `0.9` | No, defaults to `1.0` | No, defaults to `1.0` | Factor by which to scale the interface's font size. | +| `fonts` | | No | No | Defines the font(s) used by the interface. Multiple font parameters can be specified: `?font=font-one&font=font-two...`. | +| `header` | `none`, `standard` or `app_bar` | No, defaults to `standard` | No, defaults to `standard` | The style of headers to show. `standard` is the default arrangement, `none` hides the header entirely, and `app_bar` produces a header with a back button like you might see in mobile apps. The callback for the back button is `window.controls.onBackButtonPressed`. | +| `hideScreensharing` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Hides the screen-sharing button. | +| `homeserver` | | Not applicable | No | Homeserver for registering a new (guest) user, configures non-default guest user server when creating a spa link. | +| `lang` | [BCP 47](https://www.rfc-editor.org/info/bcp47) code | No | No | The language the app should use. | +| `password` | | No | No | E2EE password when using a shared secret. (For individual sender keys in embedded mode this is not required.) | +| `perParticipantE2EE` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Enables per participant encryption with Keys exchanged over encrypted matrix room messages. | +| `controlledAudioDevices` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Whether the [global JS controls for audio devices](./controls.md#audio-devices) should be enabled, allowing the list of audio devices to be controlled by the app hosting Element Call. | +| `roomId` | [Matrix Room ID](https://spec.matrix.org/v1.12/appendices/#room-ids) | Yes | No | Anything about what room we're pointed to should be from useRoomIdentifier which parses the path and resolves alias with respect to the default server name, however roomId is an exception as we need the room ID in embedded widget mode, and not the room alias (or even the via params because we are not trying to join it). This is also not validated, where it is in `useRoomIdentifier()`. | +| `showControls` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Displays controls like mute, screen-share, invite, and hangup buttons during a call. | +| `skipLobby` (deprecated: use `intent` instead) | `true` or `false` | No. If `intent` is explicitly `start_call` then defaults to `true`. Otherwise defaults to `false` | No, defaults to `false` | Skips the lobby to join a call directly, can be combined with preload in widget. When `true` the audio and video inputs will be muted by default. (This means there currently is no way to start without muted video if one wants to skip the lobby. Also not in widget mode.) | +| `theme` | One of: `light`, `dark`, `light-high-contrast`, `dark-high-contrast` | No, defaults to `dark` | No, defaults to `dark` | UI theme to use. | +| `viaServers` | Comma separated list of [Matrix Server Names](https://spec.matrix.org/v1.12/appendices/#server-name) | Not applicable | No | Homeserver for joining a room, non-empty value required for rooms not on the user’s default homeserver. | +| `sendNotificationType` | `ring` or `notification` | No | No | Will send a "ring" or "notification" `m.rtc.notification` event if the user is the first one in the call. | +| `autoLeaveWhenOthersLeft` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Whether the app should automatically leave the call when there is no one left in the call. | +| `waitForCallPickup` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | When sending a notification, show UI that the app is awaiting an answer, play a dial tone, and (in widget mode) auto-close the widget once the notification expires. | ### Widget-only parameters -These parameters are only supported in [widget](./embedded-standalone.md) mode. +These parameters are only supported in [widget](./embedded_standalone.md) mode. -| Name | Values | Required | Description | -| --------------- | ----------------------------------------------------------------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `baseUrl` | | Yes | The base URL of the homeserver to use for media lookups. | -| `deviceId` | Matrix device ID | Yes | The Matrix device ID for the widget host. | -| `parentUrl` | | Yes | The url used to send widget action postMessages. This should be the domain of the client or the webview the widget is hosted in. (in case the widget is not in an Iframe but in a dedicated webview we send the postMessages same WebView the widget lives in. Filtering is done in the widget so it ignores the messages it receives from itself) | -| `posthogUserId` | Posthog user identifier | No | This replaces the `analyticsID` parameter | -| `preload` | `true` or `false` | No, defaults to `false` | Pauses app before joining a call until an `io.element.join` widget action is seen, allowing preloading. | -| `returnToLobby` | `true` or `false` | No, defaults to `false` | Displays the lobby in widget mode after leaving a call; shows a blank page if set to `false`. Useful for video rooms. | -| `userId` | [Matrix User Identifier](https://spec.matrix.org/v1.12/appendices/#user-identifiers) | Yes | The Matrix user ID. | -| `widgetId` | [MSC2774](https://github.com/matrix-org/matrix-spec-proposals/pull/2774) format widget ID | Yes | The id used by the widget. The presence of this parameter implies that element call will not connect to a homeserver directly and instead tries to establish postMessage communication via the `parentUrl`. | +| Name | Values | Required | Description | +| --------------- | ----------------------------------------------------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `baseUrl` | | Yes | The base URL of the homeserver to use for media lookups. | +| `deviceId` | Matrix device ID | Yes | The Matrix device ID for the widget host. | +| `parentUrl` | | Yes | The url used to send widget action postMessages. This should be the domain of the client or the webview the widget is hosted in. (In case the widget is not in an Iframe but in a dedicated webview, we send the postMessages in the same WebView the widget lives in. Filtering is done in the widget so it ignores the messages it receives from itself.) | +| `posthogUserId` | Posthog user identifier | No | This replaces the `analyticsID` parameter | +| `preload` | `true` or `false` | No, defaults to `false` | Pauses app before joining a call until an `io.element.join` widget action is seen, allowing preloading. | +| `returnToLobby` | `true` or `false` | No, defaults to `false` | Displays the lobby in widget mode after leaving a call; shows a blank page if set to `false`. Useful for video rooms. | +| `userId` | [Matrix User Identifier](https://spec.matrix.org/v1.12/appendices/#user-identifiers) | Yes | The Matrix user ID. | +| `widgetId` | [MSC2774](https://github.com/matrix-org/matrix-spec-proposals/pull/2774) format widget ID | Yes | The id used by the widget. The presence of this parameter implies that element call will not connect to a homeserver directly and instead tries to establish postMessage communication via the `parentUrl`. | ### Embedded-only parameters -These parameters are only supported in the [embedded](./embedded-standalone.md) package of Element Call and will be ignored in the [full](./embedded-standalone.md) package. +These parameters are only supported in the [embedded](./embedded_standalone.md) package of Element Call and will be ignored in the [full](./embedded_standalone.md) package. | Name | Values | Required | Description | | -------------------- | -------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- | diff --git a/embedded/android/publish_android_package.sh b/embedded/android/publish_android_package.sh index 8c310c9baa..3169331798 100755 --- a/embedded/android/publish_android_package.sh +++ b/embedded/android/publish_android_package.sh @@ -11,7 +11,7 @@ pushd $CURRENT_DIR > /dev/null function build_assets() { echo "Generating Element Call assets..." pushd ../.. > /dev/null - yarn build + pnpm build popd > /dev/null } @@ -26,7 +26,7 @@ function copy_assets() { } getopts :sh opt -case $opt in +case $opt in s) SKIP=1 ;; @@ -41,7 +41,7 @@ if [ ! $SKIP ]; then echo "" if [[ $REPLY =~ ^[Yy]$ ]]; then build_assets - else + else echo "Using existing assets from ../../dist" fi copy_assets @@ -56,4 +56,4 @@ echo "Publishing the Android project" ./gradlew publishAndReleaseToMavenCentral --no-daemon -popd > /dev/null \ No newline at end of file +popd > /dev/null diff --git a/index.html b/index.html index f17c73c0b5..f3177a4870 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,7 @@ <%- brand %>