+ Connection lost +
++ You were disconnected from the call. +
+diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 730d31f9..5d9ca1f2 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,7 +1,7 @@ const COPYRIGHT_HEADER = `/* Copyright %%CURRENT_YEAR%% New Vector Ltd. -SPDX-License-Identifier: AGPL-3.0-only +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ @@ -44,7 +44,7 @@ module.exports = { ], // To encourage good usage of RxJS: "rxjs/no-exposed-subjects": "error", - "rxjs/finnish": "error", + "rxjs/finnish": ["error", { names: { "^this$": false } }], "import/no-restricted-imports": [ "error", { diff --git a/.githooks/post-commit b/.githooks/post-commit new file mode 100755 index 00000000..467799bd --- /dev/null +++ b/.githooks/post-commit @@ -0,0 +1,11 @@ +#!/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 new file mode 100755 index 00000000..435d75f1 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,11 @@ +#!/usr/bin/sh + +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." + exit 1 +fi diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000..6cfbe249 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,30 @@ +changelog: + categories: + - title: 🛠 Breaking Changes + labels: + - PR-Breaking-Change + - title: ✨ Features + labels: + - PR-Feature + - title: 🙌 Improvements + labels: + - PR-Improvement + - title: 📄 Documentation + labels: + - PR-Documentation + - title: 🐛 Bugfixes + labels: + - PR-Bug-Fix + - title: 💾 Developer Experience + labels: + - PR-Developer-Experience + - title: Others + labels: + - "*" + exclude: + labels: + - PR-Task + - dependencies + - title: 👒 Dependencies + labels: + - dependencies diff --git a/.github/workflows/blocked.yaml b/.github/workflows/blocked.yaml new file mode 100644 index 00000000..f3c99b3e --- /dev/null +++ b/.github/workflows/blocked.yaml @@ -0,0 +1,17 @@ +name: Prevent blocked +on: + pull_request_target: + types: [opened, labeled, unlabeled, synchronize] +jobs: + prevent-blocked: + name: Prevent blocked + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - name: Add notice + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + if: contains(github.event.pull_request.labels.*.name, 'X-Blocked') + with: + script: | + core.setFailed("PR has been labeled with X-Blocked; it cannot be merged."); diff --git a/.github/workflows/docker.yaml b/.github/workflows/build-and-publish-docker.yaml similarity index 69% rename from .github/workflows/docker.yaml rename to .github/workflows/build-and-publish-docker.yaml index f0f52291..3ebb594a 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/build-and-publish-docker.yaml @@ -1,4 +1,4 @@ -name: Docker - Deploy +name: Build and publish docker image on: workflow_call: inputs: @@ -26,15 +26,15 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: 📥 Download artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ inputs.artifact_run_id }} - name: build-output + name: build-output-full path: dist - name: Log in to container registry - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -42,16 +42,18 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: ${{ inputs.docker_tags}} + labels: | + org.opencontainers.image.licenses=AGPL-3.0-only OR LicenseRef-Element-Commercial - name: Set up Docker Buildx - uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Build and push Docker image - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/element-call.yaml b/.github/workflows/build-element-call.yaml similarity index 56% rename from .github/workflows/element-call.yaml rename to .github/workflows/build-element-call.yaml index 1ecb4823..49542e5d 100644 --- a/.github/workflows/element-call.yaml +++ b/.github/workflows/build-element-call.yaml @@ -1,10 +1,19 @@ -name: Element Call - Build +name: Build Element Call on: workflow_call: inputs: vite_app_version: required: true type: string + package: + type: string # This would ideally be a `choice` type, but that isn't supported yet + description: The package type to be built. Must be one of 'full' or 'embedded' + required: true + build_mode: + type: string # This would ideally be a `choice` type, but that isn't supported yet + description: The build mode for vite. Must be either 'development' or 'production' + required: false + default: production secrets: SENTRY_ORG: required: true @@ -24,15 +33,17 @@ jobs: steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Enable Corepack + run: corepack enable - name: Yarn cache - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: cache: "yarn" node-version-file: ".node-version" - name: Install dependencies - run: "yarn install" - - name: Build - run: "yarn run build" + run: "yarn install --immutable" + - name: Build Element Call + run: ${{ format('yarn run build:{0}:{1}', inputs.package, inputs.build_mode) }} env: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} @@ -42,9 +53,9 @@ jobs: VITE_APP_VERSION: ${{ inputs.vite_app_version }} NODE_OPTIONS: "--max-old-space-size=4096" - name: Upload Artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: build-output + name: build-output-${{ inputs.package }} path: dist # We'll only use this in a triggered job, then we're done with it retention-days: 1 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 98002b6e..6aa5fae6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -5,19 +5,64 @@ on: - synchronize - opened - labeled - paths-ignore: - - ".github/**" - - "docs/**" push: branches: [livekit, full-mesh] - paths-ignore: - - ".github/**" - - "docs/**" jobs: - build_element_call: - uses: ./.github/workflows/element-call.yaml + build_full_element_call: + # Use the full package vite build + uses: ./.github/workflows/build-element-call.yaml with: + package: full vite_app_version: ${{ github.event.release.tag_name || github.sha }} + build_mode: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'development build') && 'development' || 'production' }} + secrets: + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + SENTRY_URL: ${{ secrets.SENTRY_URL }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + deploy_develop: + # Deploy livekit branch to call.element.dev after build completes + if: github.ref == 'refs/heads/livekit' + needs: build_full_element_call + runs-on: ubuntu-latest + steps: + - name: Deploy to call.element.dev + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6 + with: + github-token: ${{ secrets.DEVELOP_DEPLOYMENT_TOKEN }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: 'element-hq', + repo: 'element-call-webapp-deployments', + workflow_id: 'deploy.yml', + ref: 'main', + inputs: { + target: 'call.element.dev', + version: '${{ github.sha }}' + } + }) + docker_for_develop: + # Build docker and publish docker for livekit branch after build completes + if: github.ref == 'refs/heads/livekit' + needs: build_full_element_call + permissions: + contents: write + packages: write + uses: ./.github/workflows/build-and-publish-docker.yaml + with: + artifact_run_id: ${{ github.run_id }} + docker_tags: | + type=sha,format=short,event=branch + type=raw,value=latest-ci + type=raw,value=latest-ci_{{date 'X' }} + build_embedded_element_call: + # Use the embedded package vite build + uses: ./.github/workflows/build-element-call.yaml + with: + package: embedded + vite_app_version: ${{ github.event.release.tag_name || github.sha }} + build_mode: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'development build') && 'development' || 'production' }} secrets: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} diff --git a/.github/workflows/changelog-label.yml b/.github/workflows/changelog-label.yml new file mode 100644 index 00000000..8d9acbc2 --- /dev/null +++ b/.github/workflows/changelog-label.yml @@ -0,0 +1,14 @@ +name: PR changelog label + +on: + pull_request_target: + types: [labeled, unlabeled, opened] +jobs: + pr-changelog-label: + runs-on: ubuntu-latest + steps: + - uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2 + with: + REQUIRED_LABELS_ANY: "PR-Bug-Fix,PR-Documentation,PR-Task,PR-Feature,PR-Improvement,PR-Developer-Experience,dependencies,PR-Breaking-Change" + REQUIRED_LABELS_ANY_DESCRIPTION: "Select at least one 'PR-' label" + BANNED_LABELS: "banned" diff --git a/.github/workflows/netlify.yaml b/.github/workflows/deploy-to-netlify.yaml similarity index 95% rename from .github/workflows/netlify.yaml rename to .github/workflows/deploy-to-netlify.yaml index 9c7b7d09..388192e4 100644 --- a/.github/workflows/netlify.yaml +++ b/.github/workflows/deploy-to-netlify.yaml @@ -1,4 +1,4 @@ -name: Netlify - Deploy +name: Deploy to Netlify on: workflow_call: inputs: @@ -46,11 +46,11 @@ jobs: Exercise caution. Use test accounts. - name: 📥 Download artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} run-id: ${{ inputs.artifact_run_id }} - name: build-output + name: build-output-full path: webapp - name: Add redirects file diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index ff347636..00000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Run E2E tests -on: - workflow_run: - workflows: ["deploy"] - types: - - completed - branches-ignore: - - "livekit" -jobs: - e2e: - name: E2E tests runs on Element Call - runs-on: ubuntu-latest - steps: - - name: Check out test private repo - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - repository: element-hq/static-call-participant - ref: refs/heads/main - path: static-call-participant - token: ${{ secrets.GH_E2E_TEST_TOKEN }} - - name: Build E2E Image - run: "cd static-call-participant && docker build --no-cache --tag matrixdotorg/chrome-node-static-call-participant:latest ." - - name: Run E2E tests in container - run: "docker run --rm -v '${{ github.workspace }}/static-call-participant/callemshost-users.txt:/opt/app/callemshost-users.txt' matrixdotorg/chrome-node-static-call-participant:latest ./e2e.sh" diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d9367626..0efbcf5a 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -8,13 +8,15 @@ jobs: steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Enable Corepack + run: corepack enable - name: Yarn cache - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: cache: "yarn" node-version-file: ".node-version" - name: Install dependencies - run: "yarn install" + run: "yarn install --immutable" - name: Prettier run: "yarn run prettier:check" - name: i18n diff --git a/.github/workflows/pr-deploy.yaml b/.github/workflows/pr-deploy.yaml index 262ce09b..7b128352 100644 --- a/.github/workflows/pr-deploy.yaml +++ b/.github/workflows/pr-deploy.yaml @@ -1,4 +1,4 @@ -name: PR Preview Deployments +name: Deploy previews for PRs on: workflow_run: workflows: ["Build"] @@ -24,7 +24,7 @@ jobs: needs: prdetails permissions: deployments: write - uses: ./.github/workflows/netlify.yaml + uses: ./.github/workflows/deploy-to-netlify.yaml with: artifact_run_id: ${{ github.event.workflow_run.id || github.run_id }} pr_number: ${{ needs.prdetails.outputs.pr_number }} @@ -42,7 +42,7 @@ jobs: permissions: contents: write packages: write - uses: ./.github/workflows/docker.yaml + uses: ./.github/workflows/build-and-publish-docker.yaml with: artifact_run_id: ${{ github.event.workflow_run.id || github.run_id }} docker_tags: | diff --git a/.github/workflows/publish-embedded-packages.yaml b/.github/workflows/publish-embedded-packages.yaml new file mode 100644 index 00000000..c309c91c --- /dev/null +++ b/.github/workflows/publish-embedded-packages.yaml @@ -0,0 +1,291 @@ +name: Build & publish embedded packages for releases + +on: + release: + types: [published] + pull_request: + types: + - synchronize + - opened + - labeled + push: + branches: [livekit] + +jobs: + versioning: + name: Versioning + runs-on: ubuntu-latest + outputs: + DRY_RUN: ${{ steps.dry_run.outputs.DRY_RUN }} + PREFIXED_VERSION: ${{ steps.prefixed_version.outputs.PREFIXED_VERSION }} + UNPREFIXED_VERSION: ${{ steps.unprefixed_version.outputs.UNPREFIXED_VERSION }} + TAG: ${{ steps.tag.outputs.TAG }} + steps: + - name: Calculate VERSION + # We should only use the hard coded test value for a dry run + run: echo "VERSION=${{ github.event_name == 'release' && github.event.release.tag_name || 'v0.0.0-pre.0' }}" >> "$GITHUB_ENV" + - id: dry_run + name: Set DRY_RUN + # We perform a dry run for all events except releases. + # This is to help make sure that we notice if the packaging process has become + # broken ahead of a release. + run: echo "DRY_RUN=${{ github.event_name != 'release' }}" >> "$GITHUB_OUTPUT" + - id: prefixed_version + name: Set PREFIXED_VERSION + run: echo "PREFIXED_VERSION=${VERSION}" >> "$GITHUB_OUTPUT" + - id: unprefixed_version + name: Set UNPREFIXED_VERSION + # This just strips the leading character + run: echo "UNPREFIXED_VERSION=${VERSION:1}" >> "$GITHUB_OUTPUT" + - id: tag + # latest = a proper release + # other = anything else + name: Set tag + run: | + if [[ "${VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "TAG=latest" >> "$GITHUB_OUTPUT" + else + echo "TAG=other" >> "$GITHUB_OUTPUT" + fi + + build_element_call: + needs: versioning + uses: ./.github/workflows/build-element-call.yaml + with: + vite_app_version: embedded-${{ needs.versioning.outputs.PREFIXED_VERSION }} + package: embedded + secrets: + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + SENTRY_URL: ${{ secrets.SENTRY_URL }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + publish_tarball: + needs: [build_element_call, versioning] + if: always() + name: Publish tarball + runs-on: ubuntu-latest + permissions: + contents: write # required to upload release asset + steps: + - name: Determine filename + run: echo "FILENAME_PREFIX=element-call-embedded-${{ needs.versioning.outputs.UNPREFIXED_VERSION }}" >> "$GITHUB_ENV" + - name: 📥 Download built element-call artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id || github.run_id }} + name: build-output-embedded + path: ${{ env.FILENAME_PREFIX}} + - name: Create Tarball + run: tar --numeric-owner -cvzf ${{ env.FILENAME_PREFIX }}.tar.gz ${{ env.FILENAME_PREFIX }} + - name: Create Checksum + run: find ${{ env.FILENAME_PREFIX }} -type f -print0 | sort -z | xargs -0 sha256sum | tee ${{ env.FILENAME_PREFIX }}.sha256 + - name: Upload + if: ${{ needs.versioning.outputs.DRY_RUN == 'false' }} + uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2 + with: + files: | + ${{ env.FILENAME_PREFIX }}.tar.gz + ${{ env.FILENAME_PREFIX }}.sha256 + + publish_npm: + needs: [build_element_call, versioning] + if: always() + name: Publish NPM + runs-on: ubuntu-latest + outputs: + ARTIFACT_VERSION: ${{ steps.artifact_version.outputs.ARTIFACT_VERSION }} + permissions: + contents: read + id-token: write # required for the provenance flag on npm publish + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: 📥 Download built element-call artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id || github.run_id }} + name: build-output-embedded + path: embedded/web/dist + + # n.b. We don't enable corepack here because we are using plain npm + - name: Setup node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: .node-version + registry-url: "https://registry.npmjs.org" + + - name: Publish npm + working-directory: embedded/web + run: | + npm version ${{ needs.versioning.outputs.PREFIXED_VERSION }} --no-git-tag-version + echo "ARTIFACT_VERSION=$(jq '.version' --raw-output package.json)" >> "$GITHUB_ENV" + npm publish --provenance --access public --tag ${{ needs.versioning.outputs.TAG }} ${{ needs.versioning.outputs.DRY_RUN == 'true' && '--dry-run' || '' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_RELEASE_TOKEN }} + + - id: artifact_version + name: Output artifact version + run: echo "ARTIFACT_VERSION=${{env.ARTIFACT_VERSION}}" >> "$GITHUB_OUTPUT" + + publish_android: + needs: [build_element_call, versioning] + if: always() + name: Publish Android AAR + runs-on: ubuntu-latest + outputs: + ARTIFACT_VERSION: ${{ steps.artifact_version.outputs.ARTIFACT_VERSION }} + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - name: 📥 Download built element-call artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id || github.run_id }} + name: build-output-embedded + path: embedded/android/lib/src/main/assets/element-call + + - name: ☕️ Setup Java + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4 + with: + distribution: "temurin" + java-version: "17" + + - name: Get artifact version + # Anything that is not a final release will be tagged as a snapshot + run: | + if [[ "${{ needs.versioning.outputs.TAG }}" == "latest" ]]; then + echo "ARTIFACT_VERSION=${{ needs.versioning.outputs.UNPREFIXED_VERSION }}" >> "$GITHUB_ENV" + else + echo "ARTIFACT_VERSION=${{ needs.versioning.outputs.UNPREFIXED_VERSION }}-SNAPSHOT" >> "$GITHUB_ENV" + fi + + - name: Set version string + run: sed -i "s/0.0.0/${{ env.ARTIFACT_VERSION }}/g" embedded/android/lib/src/main/kotlin/io/element/android/call/embedded/Version.kt + + - name: Publish AAR + working-directory: embedded/android + env: + EC_VERSION: ${{ env.ARTIFACT_VERSION }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_RELEASE_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_RELEASE_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_SIGNING_KEY_PASSWORD }} + run: ./gradlew publishToMavenCentral --no-daemon ${{ needs.versioning.outputs.DRY_RUN == 'true' && '--dry-run' || '' }} + + - id: artifact_version + name: Output artifact version + run: echo "ARTIFACT_VERSION=${{env.ARTIFACT_VERSION}}" >> "$GITHUB_OUTPUT" + + publish_ios: + needs: [build_element_call, versioning] + if: always() + name: Publish SwiftPM Library + runs-on: ubuntu-latest + outputs: + ARTIFACT_VERSION: ${{ steps.artifact_version.outputs.ARTIFACT_VERSION }} + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + path: element-call + + - name: 📥 Download built element-call artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id || github.run_id }} + name: build-output-embedded + path: element-call/embedded/ios/Sources/dist + + - name: Checkout element-call-swift + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + repository: element-hq/element-call-swift + path: element-call-swift + token: ${{ secrets.SWIFT_RELEASE_TOKEN }} + + - name: Copy files + run: rsync -a --delete --exclude .git element-call/embedded/ios/ element-call-swift + + - name: Get artifact version + run: echo "ARTIFACT_VERSION=${{ needs.versioning.outputs.UNPREFIXED_VERSION }}" >> "$GITHUB_ENV" + + - name: Set version string + run: sed -i "s/0.0.0/${{ env.ARTIFACT_VERSION }}/g" element-call-swift/Sources/EmbeddedElementCall/EmbeddedElementCall.swift + + - name: Test build + working-directory: element-call-swift + run: swift build + + - name: Commit and tag + working-directory: element-call-swift + run: | + git config --global user.email "ci@element.io" + git config --global user.name "Element CI" + git add -A + git commit -am "Release ${{ needs.versioning.outputs.PREFIXED_VERSION }}" + git tag -a ${{ env.ARTIFACT_VERSION }} -m "${{ github.event.release.html_url }}" + + - name: Push + working-directory: element-call-swift + run: | + git push --tags ${{ needs.versioning.outputs.DRY_RUN == 'true' && '--dry-run' || '' }} + + - id: artifact_version + name: Output artifact version + run: echo "ARTIFACT_VERSION=${{env.ARTIFACT_VERSION}}" >> "$GITHUB_OUTPUT" + + release_notes: + needs: [versioning, publish_npm, publish_android, publish_ios] + if: always() + name: Update release notes + runs-on: ubuntu-latest + permissions: + contents: write # to update release notes + steps: + - name: Log versions + run: | + echo "NPM: ${{ needs.publish_npm.outputs.ARTIFACT_VERSION }}" + echo "Android: ${{ needs.publish_android.outputs.ARTIFACT_VERSION }}" + echo "iOS: ${{ needs.publish_ios.outputs.ARTIFACT_VERSION }}" + - name: Add release notes + if: ${{ needs.versioning.outputs.DRY_RUN == 'false' }} + uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2 + with: + append_body: true + body: | + + ## Embedded packages + + This release includes the following embedded packages that allow Element Call to be used as an embedded widget + within another application. + + ### NPM + + ``` + npm install @element-hq/element-call-embedded@${{ needs.publish_npm.outputs.ARTIFACT_VERSION }} + ``` + + ### Android AAR + + ``` + dependencies { + implementation 'io.element.android:element-call-embedded:${{ needs.publish_android.outputs.ARTIFACT_VERSION }}' + } + ``` + + ### SwiftPM + + ``` + .package(url: "https://github.com/element-hq/element-call-swift.git", from: "${{ needs.publish_ios.outputs.ARTIFACT_VERSION }}") + ``` diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index a433e3dc..86169e16 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -1,72 +1,82 @@ -name: Build & publish images to the package registry for tags +name: Build & publish full packages for releases on: release: types: [published] - workflow_run: - workflows: ["Build"] - branches: [livekit] - types: - - completed env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} + VERSION: ${{ github.event.release.tag_name }} jobs: build_element_call: - if: ${{ github.event_name == 'release' }} - uses: ./.github/workflows/element-call.yaml + uses: ./.github/workflows/build-element-call.yaml with: - vite_app_version: ${{ github.event.release.tag_name || github.sha }} + package: full + vite_app_version: ${{ github.event.release.tag_name }} # Using ${{ env.VERSION }} here doesn't work secrets: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} SENTRY_URL: ${{ secrets.SENTRY_URL }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + publish_tarball: needs: build_element_call if: always() name: Publish tarball runs-on: ubuntu-latest - outputs: - unix_time: ${{steps.current-time.outputs.unix_time}} permissions: contents: write # required to upload release asset packages: write steps: - - name: Get current time - id: current-time - run: echo "unix_time=$(date +'%s')" >> $GITHUB_OUTPUT - - name: 📥 Download artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 + - name: Determine filename + run: echo "FILENAME_PREFIX=element-call-${VERSION:1}" >> "$GITHUB_ENV" + - name: 📥 Download built element-call artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id || github.run_id }} - name: build-output - path: dist + name: build-output-full + path: ${{ env.FILENAME_PREFIX }} - name: Create Tarball - env: - TARBALL_VERSION: ${{ github.event.release.tag_name || github.sha }} - run: | - tar --numeric-owner --transform "s/dist/element-call-${TARBALL_VERSION}/" -cvzf element-call-${TARBALL_VERSION}.tar.gz dist + run: tar --numeric-owner --transform "s/dist/${{ env.FILENAME_PREFIX }}/" -cvzf ${{ env.FILENAME_PREFIX }}.tar.gz ${{ env.FILENAME_PREFIX }} + - name: Create Checksum + run: find ${{ env.FILENAME_PREFIX }} -type f -print0 | sort -z | xargs -0 sha256sum | tee ${{ env.FILENAME_PREFIX }}.sha256 - name: Upload - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 - env: - GITHUB_TOKEN: ${{ github.token }} + uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2 with: - path: "./element-call-*.tar.gz" + files: | + ${{ env.FILENAME_PREFIX }}.tar.gz + ${{ env.FILENAME_PREFIX }}.sha256 + publish_docker: - needs: publish_tarball + needs: build_element_call if: always() + name: Publish docker permissions: contents: write packages: write - uses: ./.github/workflows/docker.yaml + uses: ./.github/workflows/build-and-publish-docker.yaml with: artifact_run_id: ${{ github.event.workflow_run.id || github.run_id }} docker_tags: | type=sha,format=short,event=branch - type=semver,pattern=v{{version}} - type=raw,value=latest-ci,enable={{is_default_branch}} - type=raw,value=latest-ci_${{needs.publish_tarball.outputs.unix_time}},enable={{is_default_branch}} + type=raw,value=${{ github.event.release.tag_name }} + # Like before, using ${{ env.VERSION }} above doesn't work + add_docker_release_note: + needs: publish_docker + name: Add docker release note + runs-on: ubuntu-latest + steps: + - name: Add release note + uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2 + with: + append_body: true + body: | + ## Docker full package + Element Call is available as a Docker image from the [GitHub Container Registry](https://github.com/element-hq/element-call/pkgs/container/element-call). + + The image provides a full build of Element Call that can be used both in standalone and as a widget (on a remote URL). + + ``` + docker pull ghcr.io/element-hq/element-call:${{ env.VERSION }} + ``` diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a0579e4a..f532cda6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,28 +1,61 @@ -name: Run unit tests +name: Test on: pull_request: {} push: branches: [livekit, full-mesh] jobs: vitest: - name: Run vitest tests + name: Run unit tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Enable Corepack + run: corepack enable - name: Yarn cache - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: cache: "yarn" node-version-file: ".node-version" - name: Install dependencies - run: "yarn install" + run: "yarn install --immutable" - name: Vitest run: "yarn run test:coverage" - name: Upload to codecov - uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5 + uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: flags: unittests fail_ci_if_error: true + playwright: + name: Run end-to-end tests + timeout-minutes: 30 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Enable Corepack + run: corepack enable + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + cache: "yarn" + node-version-file: ".node-version" + - name: Install dependencies + run: yarn install --immutable + - name: Install Playwright Browsers + run: yarn 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 + docker compose -f playwright-backend-docker-compose.yml -f playwright-backend-docker-compose.override.yml up -d + docker ps + - name: Run Playwright tests + env: + USE_DOCKER: 1 + run: yarn playwright test + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 3 diff --git a/.github/workflows/translations-download.yaml b/.github/workflows/translations-download.yaml index 4b5e19d0..fc4fbf40 100644 --- a/.github/workflows/translations-download.yaml +++ b/.github/workflows/translations-download.yaml @@ -15,13 +15,16 @@ jobs: - name: Checkout the code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 + - name: Enable Corepack + run: corepack enable + + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: cache: "yarn" node-version-file: ".node-version" - name: Install Deps - run: "yarn install --frozen-lockfile" + run: "yarn install --immutable" - name: Prune i18n run: "rm -R locales" @@ -39,7 +42,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6 + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: token: ${{ secrets.ELEMENT_BOT_TOKEN }} branch: actions/localazy-download diff --git a/.gitignore b/.gitignore index 8d306b2c..3e9016a6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,28 @@ node_modules dist dist-ssr *.local +*.bkp .idea/ public/config.json backend/synapse_tmp/* /coverage +config.json + +# Yarn yarn-error.log +/.pnp.* +/.yarn/* +!/.yarn/patches +!/.yarn/plugins +!/.yarn/releases +!/.yarn/sdks +!/.yarn/versions +/.links.yaml +/.links.disabled.yaml +/.links.temp-disabled.yaml + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ \ No newline at end of file diff --git a/.yarn/plugins/linker.cjs b/.yarn/plugins/linker.cjs new file mode 100644 index 00000000..cf7181f9 --- /dev/null +++ b/.yarn/plugins/linker.cjs @@ -0,0 +1,91 @@ +/* +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 new file mode 100644 index 00000000..538de0e7 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1,3 @@ +nodeLinker: node-modules +plugins: + - .yarn/plugins/linker.cjs diff --git a/Dockerfile b/Dockerfile index 275ab153..bf34c6c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,13 @@ -FROM nginxinc/nginx-unprivileged:alpine +FROM alpine AS builder + +COPY ./dist /dist + +# Compress assets to work with nginx-gzip-static-module +WORKDIR /dist/assets +RUN gzip -k ../index.html *.js *.map *.css *.wasm *-app-*.json + +FROM nginxinc/nginx-unprivileged:alpine-slim + +COPY --from=builder ./dist /app -COPY ./dist /app COPY config/nginx.conf /etc/nginx/conf.d/default.conf diff --git a/LICENSE b/LICENSE-AGPL-3.0 similarity index 100% rename from LICENSE rename to LICENSE-AGPL-3.0 diff --git a/LICENSE-COMMERCIAL b/LICENSE-COMMERCIAL new file mode 100644 index 00000000..173e03e0 --- /dev/null +++ b/LICENSE-COMMERCIAL @@ -0,0 +1,6 @@ +Licensees holding a valid commercial license with Element may use this +software in accordance with the terms contained in a written agreement +between you and Element. + +To purchase a commercial license please contact our sales team at +licensing@element.io diff --git a/README.md b/README.md index ffd73d5e..510b7c76 100644 --- a/README.md +++ b/README.md @@ -2,214 +2,308 @@ [](https://matrix.to/#/#webrtc:matrix.org) [](https://localazy.com/p/element-call) +[](LICENSE-AGPL-3.0) -Group calls with WebRTC that leverage [Matrix](https://matrix.org) and an -open-source WebRTC toolkit from [LiveKit](https://livekit.io/). +[🎬 Live Demo 🎬](https://call.element.io) -For prior version of the Element Call that relied solely on full-mesh logic, -check [`full-mesh`](https://github.com/element-hq/element-call/tree/full-mesh) -branch. +The world's first 🌐 decentralized and 🤝 federated video conferencing solution +powered by **the Matrix protocol**. - +## 📌 Overview -To try it out, visit our hosted version at -[call.element.io](https://call.element.io). You can also find the latest -development version continuously deployed to +**Element Call** is a native Matrix video conferencing application developed by +[Element](https://element.io/), designed for **secure**, **scalable**, +**privacy-respecting**, and **decentralized** video and voice calls over the +Matrix protocol. Built on **MatrixRTC** +([MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)), it +utilizes +**[MSC4195](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)** +with **[LiveKit](https://livekit.io/)** as its backend. + + + +You can find the latest development version continuously deployed to [call.element.dev](https://call.element.dev/). -## Host it yourself +> [!NOTE] +> For prior version of the Element Call that relied solely on full-mesh logic, +> check [`full-mesh`](https://github.com/element-hq/element-call/tree/full-mesh) +> branch. -Until prebuilt tarballs are available, you'll need to build Element Call from -source. First, clone and install the package: +## ✨ Key Features -``` -git clone https://github.com/element-hq/element-call.git -cd element-call -yarn -yarn build -``` +✅ **Decentralized & Federated** – No central authority; works across Matrix +homeservers. +✅ **End-to-End Encrypted** – Secure and private calls. +✅ **Standalone & Widget Mode** – Use as an independent app or embed in Matrix +clients. +✅ **WebRTC-based** – No additional software required. +✅ **Scalable with LiveKit** – Supports large meetings via SFU +([MSC4195: MatrixRTC using LiveKit backend](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)). +✅ **Raise Hand** – Participants can signal when they want to speak, helping to +organize the flow of the meeting. +✅ **Emoji Reactions** – Users can react with emojis 👍️ 🎉 👏 🤘, adding +engagement and interactivity to the conversation. -If all went well, you can now find the build output under `dist` as a series of -static files. These can be hosted using any web server that can be configured -with custom routes (see below). +## 🚀 Deployment & Packaging Options -You may also wish to add a configuration file (Element Call uses the domain it's -hosted on as a Homeserver URL by default, but you can change this in the config -file). This goes in `public/config.json` - you can use the sample as a starting -point: +Element Call is developed using the +[Matrix js-sdk](https://github.com/matrix-org/matrix-js-sdk) with Matroska mode. +This allows the app to run either as a Standalone App directly connected to a +homeserver with login interfaces or it can be used as a widget within a Matrix +client. -``` -cp config/config.sample.json public/config.json -# edit public/config.json -``` +### 🖥️ Standalone Mode -Because Element Call uses client-side routing, your server must be able to route -any requests to non-existing paths back to `/index.html`. For example, in Nginx -you can achieve this with the `try_files` directive: +
+
+
+
+
+
+
+
+
+
+
+
+
This is the content.
+- {error instanceof TranslatedError - ? error.translatedMessage - : error.message} -
-Submitting debug logs will help us track down the problem.
-{t("error.generic_description")}
++ {t("error.open_elsewhere_description", { + brand: import.meta.env.VITE_PRODUCT_NAME || "Element Call", + })} +
++ This is the content. +
+
+ {error.localisedMessage ?? (
+ ]}
+ values={{ errorCode: error.code }}
+ />
+ )}
+
+ That link doesn't appear to belong to any existing call. + Check that you have the right link, or{" "} + create a new one. +
+{groupCallState.error.messageBody}
+ {groupCallState.error.reason && ( ++ {t("group_call_loader.reason", { + reason: groupCallState.error.reason, + })} +
+ )} ++ You were disconnected from the call. +
++ The server is not configured to work with Element Call. Please contact your server admin (Domain: example.com, Error Code: MISSING_MATRIX_RTC_FOCUS). +
++ The server is not configured to work with Element Call. Please contact your server admin (Domain: example.com, Error Code: MISSING_MATRIX_RTC_FOCUS). +
++ The server is not configured to work with Element Call. Please contact your server admin (Domain: example.com, Error Code: MISSING_MATRIX_RTC_FOCUS). +
++ You were disconnected from the call. +
++ Your web browser does not support encrypted calls. Supported browsers include Chrome, Safari, and Firefox 117+. +
++ The server has reached its maximum capacity and you cannot join the call at this time. Try again later, or contact your server admin if the problem persists. +
+
@@ -122,6 +155,82 @@ export const DeveloperSettingsTab: FC
+ {t("developer_mode.livekit_sfu", {
+ url: sfuUrl?.href || "unknown",
+ })}
+ {t("developer_mode.livekit_server_info")} {t("developer_mode.environment_variables")} {t("developer_mode.url_params")}
+ {livekitRoom.serverInfo
+ ? JSON.stringify(livekitRoom.serverInfo, null, 2)
+ : "undefined"}
+ {livekitRoom.metadata}
+
+ >
+ ) : null}
+ {JSON.stringify(import.meta.env, null, 2)}
+ {JSON.stringify(urlParams, null, 2)}
>
);
};
diff --git a/src/settings/DeviceSelection.tsx b/src/settings/DeviceSelection.tsx
index aebe0aac..197046c3 100644
--- a/src/settings/DeviceSelection.tsx
+++ b/src/settings/DeviceSelection.tsx
@@ -1,7 +1,7 @@
/*
Copyright 2024 New Vector Ltd.
-SPDX-License-Identifier: AGPL-3.0-only
+SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
@@ -21,31 +21,40 @@ import {
Separator,
} from "@vector-im/compound-web";
import { Trans, useTranslation } from "react-i18next";
+import { useObservableEagerState } from "observable-hooks";
-import { type MediaDevice } from "../livekit/MediaDevicesContext";
+import {
+ type AudioOutputDeviceLabel,
+ type DeviceLabel,
+ type SelectedDevice,
+ type MediaDevice,
+} from "../state/MediaDevices";
import styles from "./DeviceSelection.module.css";
interface Props {
- devices: MediaDevice;
+ device: MediaDevice
@@ -66,20 +67,27 @@ export const FeedbackSettingsTab: FC{t("common.analytics")}
- {t("common.analytics")}
+ {t("settings.feedback_tab_h4")}
{t("settings.background_blur_header")}
+
+