Merge branch 'develop' of https://github.com/vector-im/element-web into t3chguy/react18/context

This commit is contained in:
Michael Telatynski 2023-05-09 14:09:00 +01:00
commit 7bb4d91e5c
No known key found for this signature in database
GPG Key ID: A2B008A5F49F5D0D
59 changed files with 2011 additions and 1716 deletions

View File

@ -87,6 +87,7 @@ module.exports = {
// We don't need super strict typing in test utilities
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/ban-ts-comment": "off",
},
},
],

View File

@ -23,7 +23,7 @@ jobs:
)
)
steps:
- uses: tibdex/backport@v2
- uses: tibdex/backport@2e217641d82d02ba0603f46b1aeedefb258890ac # v2
with:
labels_template: "<%= JSON.stringify([...labels, 'X-Release-Blocker']) %>"
# We can't use GITHUB_TOKEN here or CI won't run on the new PR

View File

@ -18,7 +18,7 @@ jobs:
environment: develop
env:
R2_BUCKET: "element-web-develop"
R2_URL: ${{ secrets.CF_R2_S3_API }}
R2_URL: ${{ vars.CF_R2_S3_API }}
R2_PUBLIC_URL: "https://element-web-develop.element.io"
steps:
- uses: actions/checkout@v3
@ -68,20 +68,25 @@ jobs:
run: cp .github/cfp_headers _deploy/_headers
# Redirect requests for the develop tarball and the historical bundles to R2
# We find the latest 100 bundle.css files and add their bundles to the redirects file
# S3 has no sane way to get the age of a directory as they don't really exist
- name: Populate _redirects
run: |
{
echo "/develop.tar.gz $R2_PUBLIC_URL/develop.tar.gz 301"
for bundle in $(aws s3 ls s3://$R2_BUCKET/bundles/ --endpoint-url $R2_URL --region=auto | awk '{print $2}'); do
echo "/bundles/${bundle}* $R2_PUBLIC_URL/bundles/${bundle}:splat 301"
done
echo "/develop.tar.gz $R2_PUBLIC_URL/develop.tar.gz 301"
RESULTS=$(aws s3api --region auto --endpoint-url $R2_URL list-objects-v2 --bucket $R2_BUCKET \
--query "sort_by(Contents[?ends_with(Key, '/bundle.css')], &LastModified)[-100:].Key"
--prefix "bundles/" | jq -r '.[]' | grep -oE '[^\"].*\/\s*')
for bundle in "$RESULTS"; do
echo "/${path}* $R2_PUBLIC_URL/${path}:splat 301"
done
} | tee _deploy/_redirects
env:
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_TOKEN }}
- name: Wait for other steps to succeed
uses: lewagon/wait-on-check-action@v1.3.1
uses: t3chguy/wait-on-check-action@05861d3a448898eb33dfce34153bd1ecb9422fb9 # fork
with:
ref: ${{ github.sha }}
running-workflow-name: "Build & Deploy develop.element.io"
@ -102,7 +107,7 @@ jobs:
- name: Deploy to Cloudflare Pages
id: cfp
uses: cloudflare/pages-action@1
uses: cloudflare/pages-action@61eafe73baad0195ab582cb447b2c6e15a0df9ce # v1
with:
apiToken: ${{ secrets.CF_PAGES_TOKEN }}
accountId: ${{ secrets.CF_PAGES_ACCOUNT_ID }}

View File

@ -6,7 +6,7 @@ on:
schedule:
# This job can take a while, and we have usage limits, so just publish develop only twice a day
- cron: "0 7/12 * * *"
concurrency: ${{ github.ref_name }}
concurrency: ${{ github.workflow }}-${{ github.ref_name }}
jobs:
buildx:
name: Docker Buildx
@ -18,22 +18,22 @@ jobs:
fetch-depth: 0 # needed for docker-package to be able to calculate the version
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2
with:
install: true
- name: Login to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@c4ee3adeed93b1fa6a762f209fb01608c1a22f1e # v4
with:
images: |
vectorim/element-web
@ -44,7 +44,7 @@ jobs:
latest=${{ contains(github.ref_name, '-rc.') && 'false' || 'auto' }}
- name: Build and push
uses: docker/build-push-action@v4
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 # v4
with:
context: .
push: true
@ -53,7 +53,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
- name: Update repo description
uses: peter-evans/dockerhub-description@v3
uses: peter-evans/dockerhub-description@202973a37c8a723405c0c5f0a71b6d99db470dae # v3
continue-on-error: true
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}

View File

@ -2,7 +2,6 @@ name: Pull Request
on:
pull_request_target:
types: [opened, edited, labeled, unlabeled, synchronize]
concurrency: ${{ github.workflow }}-${{ github.event.pull_request.head.ref }}
jobs:
action:
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop

View File

@ -41,13 +41,13 @@ jobs:
- name: Get diff lines
id: diff
uses: Equip-Collaboration/diff-line-numbers@v1.0.0
uses: Equip-Collaboration/diff-line-numbers@df70b4b83e05105c15f20dc6cc61f1463411b2a6 # v1.0.0
with:
include: '["\\.tsx?$"]'
- name: Detecting files changed
id: files
uses: futuratrepadeira/changed-files@v4.0.0
uses: futuratrepadeira/changed-files@0239328a3a6268aad16af7c3e4efc78e32d6c0f0 # v4.0.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pattern: '^.*\.tsx?$'
@ -80,7 +80,7 @@ jobs:
# Does not need branch matching as only analyses this layer
- name: Install Deps
run: "yarn install --pure-lockfile"
run: "yarn install --frozen-lockfile"
- name: Run Linter
run: "yarn run lint:js"

View File

@ -27,10 +27,10 @@ jobs:
- name: Get number of CPU cores
id: cpu-cores
uses: SimenB/github-actions-cpu-cores@v1
uses: SimenB/github-actions-cpu-cores@410541432439795d30db6501fb1d8178eb41e502 # v1
- name: Run tests with coverage
run: "yarn coverage --ci --reporters github-actions --max-workers ${{ steps.cpu-cores.outputs.count }}"
run: "yarn coverage --ci --max-workers ${{ steps.cpu-cores.outputs.count }}"
- name: Upload Artifact
uses: actions/upload-artifact@v3

View File

@ -12,20 +12,7 @@ jobs:
contains(github.event.issue.assignees.*.login, 'andybalaam') ||
contains(github.event.issue.assignees.*.login, 'justjanne')
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_project
- uses: actions/add-to-project@main
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc4AKjJS"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
project-url: https://github.com/orgs/vector-im/projects/67
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}

View File

@ -72,23 +72,10 @@ jobs:
contains(github.event.issue.labels.*.name, 'O-Frequent') ||
contains(github.event.issue.labels.*.name, 'A11y'))
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_project
- uses: actions/add-to-project@main
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc0sUA"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
project-url: https://github.com/orgs/vector-im/projects/18
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
add_product_issues:
name: X-Needs-Product to product project board
@ -96,23 +83,10 @@ jobs:
if: >
contains(github.event.issue.labels.*.name, 'X-Needs-Product')
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_project
- uses: actions/add-to-project@main
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc4AAg6N"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
project-url: https://github.com/orgs/vector-im/projects/28
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
Search_issues_to_board:
name: Search issues to project board
@ -120,114 +94,10 @@ jobs:
if: >
contains(github.event.issue.labels.*.name, 'A-New-Search-Experience')
steps:
- uses: octokit/graphql-action@v2.x
- uses: actions/add-to-project@main
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc4ADtaO"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_voice-message_issues:
name: A-Voice Messages to voice message board
runs-on: ubuntu-latest
if: >
contains(github.event.issue.labels.*.name, 'A-Voice Messages')
steps:
- uses: octokit/graphql-action@v2.x
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc2KCw"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_message_bubbles_issues:
name: A-Message-Bubbles to Message bubbles board
runs-on: ubuntu-latest
if: >
contains(github.event.issue.labels.*.name, 'A-Message-Bubbles')
steps:
- uses: octokit/graphql-action@v2.x
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc3m-g"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_ftue_issues:
name: Z-FTUE issues to the FTUE project board
runs-on: ubuntu-latest
if: >
contains(github.event.issue.labels.*.name, 'Z-FTUE')
steps:
- uses: octokit/graphql-action@v2.x
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc4AAqVx"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_WTF_issues:
name: Z-WTF issues to the WTF project board
runs-on: ubuntu-latest
if: >
contains(github.event.issue.labels.*.name, 'Z-WTF')
steps:
- uses: octokit/graphql-action@v2.x
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc4AArk0"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
project-url: https://github.com/orgs/vector-im/projects/48
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
ps_features1:
name: Add labelled issues to PS features team 1
@ -240,23 +110,10 @@ jobs:
(contains(github.event.issue.labels.*.name, 'A-Session-Mgmt') &&
contains(github.event.issue.labels.*.name, 'A-User-Settings'))
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_project
- uses: actions/add-to-project@main
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc4AHJKF"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
project-url: https://github.com/orgs/vector-im/projects/56
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
ps_features2:
name: Add labelled issues to PS features team 2
@ -265,23 +122,10 @@ jobs:
contains(github.event.issue.labels.*.name, 'A-DM-Start') ||
contains(github.event.issue.labels.*.name, 'A-Broadcast')
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_project
- uses: actions/add-to-project@main
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc4AHJKd"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
project-url: https://github.com/orgs/vector-im/projects/58
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
ps_features3:
name: Add labelled issues to PS features team 3
@ -289,23 +133,10 @@ jobs:
if: >
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor')
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_project
- uses: actions/add-to-project@main
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc4AHJKW"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
project-url: https://github.com/orgs/vector-im/projects/57
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
voip:
name: Add labelled issues to VoIP project board
@ -313,23 +144,10 @@ jobs:
if: >
contains(github.event.issue.labels.*.name, 'Team: VoIP')
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_project
- uses: actions/add-to-project@main
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc4ABMIk"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
project-url: https://github.com/orgs/vector-im/projects/41
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
verticals_feature:
name: Add labelled issues to Verticals Feature project
@ -337,20 +155,7 @@ jobs:
if: >
contains(github.event.issue.labels.*.name, 'Team: Verticals Feature')
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_project
- uses: actions/add-to-project@main
with:
headers: '{"GraphQL-Features": "projects_next_graphql"}'
query: |
mutation add_to_project($projectid:ID!,$contentid:ID!) {
addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) {
item {
id
}
}
}
projectid: ${{ env.PROJECT_ID }}
contentid: ${{ github.event.issue.node_id }}
env:
PROJECT_ID: "PVT_kwDOAM0swc4AHJKW"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
project-url: https://github.com/orgs/vector-im/projects/57
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}

View File

@ -43,11 +43,11 @@ jobs:
# If either a reviewer matches a team member, or a team matches our team, say "true"
if [ $(join /tmp/team_members.txt /tmp/reviewers.txt | wc -l) != 0 ]; then
echo "::set-output name=match::true"
echo "match=true" >> $GITHUB_OUTPUT
elif [ $(join /tmp/team.txt /tmp/team_reviewers.txt | wc -l) != 0 ]; then
echo "::set-output name=match::true"
echo "match=true" >> $GITHUB_OUTPUT
else
echo "::set-output name=match::false"
echo "match=false" >> $GITHUB_OUTPUT
fi
env:
TEAM: "design"
@ -110,11 +110,11 @@ jobs:
# If either a reviewer matches a team member, or a team matches our team, say "true"
if [ $(join /tmp/team_members.txt /tmp/reviewers.txt | wc -l) != 0 ]; then
echo "::set-output name=match::true"
echo "match=true" >> $GITHUB_OUTPUT
elif [ $(join /tmp/team.txt /tmp/team_reviewers.txt | wc -l) != 0 ]; then
echo "::set-output name=match::true"
echo "match=true" >> $GITHUB_OUTPUT
else
echo "::set-output name=match::false"
echo "match=false" >> $GITHUB_OUTPUT
fi
env:
TEAM: "product"

View File

View File

@ -1,4 +1,4 @@
module.exports = {
...require("matrix-react-sdk/.stylelintrc.js"),
extends: ["stylelint-config-standard", "stylelint-config-prettier"],
extends: ["stylelint-config-standard"],
};

View File

@ -1,3 +1,189 @@
Changes in [1.11.30](https://github.com/vector-im/element-web/releases/tag/v1.11.30) (2023-04-25)
=================================================================================================
## 🔒 Security
* Fixes for [CVE-2023-30609](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=CVE-2023-30609) / GHSA-xv83-x443-7rmw
## ✨ Features
* Pick sensible default option for phone country dropdown ([\#10627](https://github.com/matrix-org/matrix-react-sdk/pull/10627)). Fixes #3528.
* Relate field validation tooltip via aria-describedby ([\#10522](https://github.com/matrix-org/matrix-react-sdk/pull/10522)). Fixes #24963.
* Handle more completion types in rte autocomplete ([\#10560](https://github.com/matrix-org/matrix-react-sdk/pull/10560)). Contributed by @alunturner.
* Show a tile for an unloaded predecessor room if it has via_servers ([\#10483](https://github.com/matrix-org/matrix-react-sdk/pull/10483)). Contributed by @andybalaam.
* Exclude message timestamps from aria live region ([\#10584](https://github.com/matrix-org/matrix-react-sdk/pull/10584)). Fixes #5696.
* Make composer format bar an aria toolbar ([\#10583](https://github.com/matrix-org/matrix-react-sdk/pull/10583)). Fixes #11283.
* Improve accessibility of font slider ([\#10473](https://github.com/matrix-org/matrix-react-sdk/pull/10473)). Fixes #20168 and #24962.
* fix file size display from kB to KB ([\#10561](https://github.com/matrix-org/matrix-react-sdk/pull/10561)). Fixes #24866. Contributed by @NSV1991.
* Handle /me in rte ([\#10558](https://github.com/matrix-org/matrix-react-sdk/pull/10558)). Contributed by @alunturner.
* bind html with switch for manage extension setting option ([\#10553](https://github.com/matrix-org/matrix-react-sdk/pull/10553)). Contributed by @NSV1991.
* Handle command completions in RTE ([\#10521](https://github.com/matrix-org/matrix-react-sdk/pull/10521)). Contributed by @alunturner.
* Add room and user avatars to rte ([\#10497](https://github.com/matrix-org/matrix-react-sdk/pull/10497)). Contributed by @alunturner.
* Support for MSC3882 revision 1 ([\#10443](https://github.com/matrix-org/matrix-react-sdk/pull/10443)). Contributed by @hughns.
* Check profiles before starting a DM ([\#10472](https://github.com/matrix-org/matrix-react-sdk/pull/10472)). Fixes #24830.
* Quick settings: Change the copy / labels on the options ([\#10427](https://github.com/matrix-org/matrix-react-sdk/pull/10427)). Fixes #24522. Contributed by @justjanne.
* Update rte autocomplete styling ([\#10503](https://github.com/matrix-org/matrix-react-sdk/pull/10503)). Contributed by @alunturner.
## 🐛 Bug Fixes
* Fix create subspace dialog not working ([\#10652](https://github.com/matrix-org/matrix-react-sdk/pull/10652)). Fixes vector-im/element-web#24882
* Fix multiple accessibility defects identified by AXE ([\#10606](https://github.com/matrix-org/matrix-react-sdk/pull/10606)).
* Fix view source from edit history dialog always showing latest event ([\#10626](https://github.com/matrix-org/matrix-react-sdk/pull/10626)). Fixes #21859.
* #21451 Fix WebGL disabled error message ([\#10589](https://github.com/matrix-org/matrix-react-sdk/pull/10589)). Contributed by @rashmitpankhania.
* Properly translate errors in `AddThreepid.ts` so they show up translated to the user but not in our logs ([\#10432](https://github.com/matrix-org/matrix-react-sdk/pull/10432)). Contributed by @MadLittleMods.
* Fix overflow on auth pages ([\#10605](https://github.com/matrix-org/matrix-react-sdk/pull/10605)). Fixes #19548.
* Fix incorrect avatar background colour when using a custom theme ([\#10598](https://github.com/matrix-org/matrix-react-sdk/pull/10598)). Contributed by @jdauphant.
* Remove dependency on `org.matrix.e2e_cross_signing` unstable feature ([\#10593](https://github.com/matrix-org/matrix-react-sdk/pull/10593)).
* Update setting description to match reality ([\#10600](https://github.com/matrix-org/matrix-react-sdk/pull/10600)). Fixes #25106.
* Fix no identity server in help & about settings ([\#10563](https://github.com/matrix-org/matrix-react-sdk/pull/10563)). Fixes #25077.
* Fix: Images no longer reserve their space in the timeline correctly ([\#10571](https://github.com/matrix-org/matrix-react-sdk/pull/10571)). Fixes #25082. Contributed by @kerryarchibald.
* Fix issues with inhibited accessible focus outlines ([\#10579](https://github.com/matrix-org/matrix-react-sdk/pull/10579)). Fixes #19742.
* Fix read receipts falling from sky ([\#10576](https://github.com/matrix-org/matrix-react-sdk/pull/10576)). Fixes #25081.
* Fix avatar text issue in rte ([\#10559](https://github.com/matrix-org/matrix-react-sdk/pull/10559)). Contributed by @alunturner.
* fix resizer only work with left mouse click ([\#10546](https://github.com/matrix-org/matrix-react-sdk/pull/10546)). Contributed by @NSV1991.
* Fix send two join requests when joining a room from spotlight search ([\#10534](https://github.com/matrix-org/matrix-react-sdk/pull/10534)). Fixes #25054.
* Highlight event when any version triggered a highlight ([\#10502](https://github.com/matrix-org/matrix-react-sdk/pull/10502)). Fixes #24923 and #24970. Contributed by @kerryarchibald.
* Fix spacing of headings of integration manager on General settings tab ([\#10232](https://github.com/matrix-org/matrix-react-sdk/pull/10232)). Fixes #24085. Contributed by @luixxiul.
Changes in [1.11.29](https://github.com/vector-im/element-web/releases/tag/v1.11.29) (2023-04-11)
=================================================================================================
## ✨ Features
* Allow desktop app to expose recent rooms in UI integrations ([\#16940](https://github.com/vector-im/element-web/pull/16940)).
* Add API params to mute audio and/or video in Jitsi calls by default ([\#24820](https://github.com/vector-im/element-web/pull/24820)). Contributed by @dhenneke.
* Style mentions as pills in rich text editor ([\#10448](https://github.com/matrix-org/matrix-react-sdk/pull/10448)). Contributed by @alunturner.
* Show room create icon if "UIComponent.roomCreation" is enabled ([\#10364](https://github.com/matrix-org/matrix-react-sdk/pull/10364)). Contributed by @maheichyk.
* Mentions as links rte ([\#10463](https://github.com/matrix-org/matrix-react-sdk/pull/10463)). Contributed by @alunturner.
* Better error handling in jump to date ([\#10405](https://github.com/matrix-org/matrix-react-sdk/pull/10405)). Contributed by @MadLittleMods.
* Show "Invite" menu option if "UIComponent.sendInvites" is enabled. ([\#10363](https://github.com/matrix-org/matrix-react-sdk/pull/10363)). Contributed by @maheichyk.
* Added `UserProfilesStore`, `LruCache` and user permalink profile caching ([\#10425](https://github.com/matrix-org/matrix-react-sdk/pull/10425)). Fixes #10559.
* Mentions as links rte ([\#10422](https://github.com/matrix-org/matrix-react-sdk/pull/10422)). Contributed by @alunturner.
* Implement MSC3952: intentional mentions ([\#9983](https://github.com/matrix-org/matrix-react-sdk/pull/9983)).
* Implement MSC3973: Search users in the user directory with the Widget API ([\#10269](https://github.com/matrix-org/matrix-react-sdk/pull/10269)). Contributed by @dhenneke.
* Permalinks to message are now displayed as pills ([\#10392](https://github.com/matrix-org/matrix-react-sdk/pull/10392)). Fixes #24751 and #24706.
* Show search,dial,explore in filterContainer if "UIComponent.filterContainer" is enabled ([\#10381](https://github.com/matrix-org/matrix-react-sdk/pull/10381)). Contributed by @maheichyk.
* Increase space panel collapse clickable area ([\#6084](https://github.com/matrix-org/matrix-react-sdk/pull/6084)). Fixes #17379. Contributed by @jaiwanth-v.
* Add fallback for replies to Polls ([\#10380](https://github.com/matrix-org/matrix-react-sdk/pull/10380)). Fixes #24197. Contributed by @kerryarchibald.
* Permalinks to rooms and users are now pillified ([\#10388](https://github.com/matrix-org/matrix-react-sdk/pull/10388)). Fixes #24825.
* Poll history - access poll history from room settings ([\#10356](https://github.com/matrix-org/matrix-react-sdk/pull/10356)). Contributed by @kerryarchibald.
* Add API params to mute audio and/or video in Jitsi calls by default ([\#10376](https://github.com/matrix-org/matrix-react-sdk/pull/10376)). Contributed by @dhenneke.
* Notifications: inline error message on notifications saving error ([\#10288](https://github.com/matrix-org/matrix-react-sdk/pull/10288)). Contributed by @kerryarchibald.
* Support dynamic room predecessor in SpaceProvider ([\#10348](https://github.com/matrix-org/matrix-react-sdk/pull/10348)). Contributed by @andybalaam.
* Support dynamic room predecessors for RoomProvider ([\#10346](https://github.com/matrix-org/matrix-react-sdk/pull/10346)). Contributed by @andybalaam.
* Support dynamic room predecessors in OwnBeaconStore ([\#10339](https://github.com/matrix-org/matrix-react-sdk/pull/10339)). Contributed by @andybalaam.
* Support dynamic room predecessors in ForwardDialog ([\#10344](https://github.com/matrix-org/matrix-react-sdk/pull/10344)). Contributed by @andybalaam.
* Support dynamic room predecessors in SpaceHierarchy ([\#10341](https://github.com/matrix-org/matrix-react-sdk/pull/10341)). Contributed by @andybalaam.
* Support dynamic room predecessors in AddExistingToSpaceDialog ([\#10342](https://github.com/matrix-org/matrix-react-sdk/pull/10342)). Contributed by @andybalaam.
* Support dynamic room predecessors in leave-behaviour ([\#10340](https://github.com/matrix-org/matrix-react-sdk/pull/10340)). Contributed by @andybalaam.
* Support dynamic room predecessors in StopGapWidgetDriver ([\#10338](https://github.com/matrix-org/matrix-react-sdk/pull/10338)). Contributed by @andybalaam.
* Support dynamic room predecessors in WidgetLayoutStore ([\#10326](https://github.com/matrix-org/matrix-react-sdk/pull/10326)). Contributed by @andybalaam.
* Support dynamic room predecessors in SpaceStore ([\#10332](https://github.com/matrix-org/matrix-react-sdk/pull/10332)). Contributed by @andybalaam.
* Sync polls push rules on changes to account_data ([\#10287](https://github.com/matrix-org/matrix-react-sdk/pull/10287)). Contributed by @kerryarchibald.
* Support dynamic room predecessors in BreadcrumbsStore ([\#10295](https://github.com/matrix-org/matrix-react-sdk/pull/10295)). Contributed by @andybalaam.
* Improved a11y for Field feedback and Secure Phrase input ([\#10320](https://github.com/matrix-org/matrix-react-sdk/pull/10320)). Contributed by @Sebbones.
* Support dynamic room predecessors in RoomNotificationStateStore ([\#10297](https://github.com/matrix-org/matrix-react-sdk/pull/10297)). Contributed by @andybalaam.
## 🐛 Bug Fixes
* Use a newly generated access_token while joining Jitsi ([\#24646](https://github.com/vector-im/element-web/pull/24646)). Fixes #24687. Contributed by @emrahcom.
* Fix cloudflare action pointing at commit hash instead of tag ([\#24777](https://github.com/vector-im/element-web/pull/24777)). Contributed by @justjanne.
* Allow editing with RTE to overflow for autocomplete visibility ([\#10499](https://github.com/matrix-org/matrix-react-sdk/pull/10499)). Contributed by @alunturner.
* Added auto focus to Github URL on opening of debug logs modal ([\#10479](https://github.com/matrix-org/matrix-react-sdk/pull/10479)). Contributed by @ShivamSpm.
* Fix detection of encryption for all users in a room ([\#10487](https://github.com/matrix-org/matrix-react-sdk/pull/10487)). Fixes #24995.
* Properly generate mentions when editing a reply with MSC3952 ([\#10486](https://github.com/matrix-org/matrix-react-sdk/pull/10486)). Fixes #24924. Contributed by @kerryarchibald.
* Improve performance of rendering a room with many hidden events ([\#10131](https://github.com/matrix-org/matrix-react-sdk/pull/10131)). Contributed by @andybalaam.
* Prevent future date selection in jump to date ([\#10419](https://github.com/matrix-org/matrix-react-sdk/pull/10419)). Fixes #20800. Contributed by @MadLittleMods.
* Add aria labels to message search bar to improve accessibility ([\#10476](https://github.com/matrix-org/matrix-react-sdk/pull/10476)). Fixes #24921.
* Fix decryption failure bar covering the timeline ([\#10360](https://github.com/matrix-org/matrix-react-sdk/pull/10360)). Fixes #24780 #24074 and #24183. Contributed by @luixxiul.
* Improve profile picture settings accessibility ([\#10470](https://github.com/matrix-org/matrix-react-sdk/pull/10470)). Fixes #24919.
* Handle group call redaction ([\#10465](https://github.com/matrix-org/matrix-react-sdk/pull/10465)).
* Display relative timestamp for threads on the same calendar day ([\#10399](https://github.com/matrix-org/matrix-react-sdk/pull/10399)). Fixes #24841. Contributed by @kerryarchibald.
* Fix timeline list and paragraph display issues ([\#10424](https://github.com/matrix-org/matrix-react-sdk/pull/10424)). Fixes #24602. Contributed by @alunturner.
* Use unique keys for voice broadcast pips ([\#10457](https://github.com/matrix-org/matrix-react-sdk/pull/10457)). Fixes #24959.
* Fix "show read receipts sent by other users" not applying to threads ([\#10445](https://github.com/matrix-org/matrix-react-sdk/pull/10445)). Fixes #24910.
* Fix joining public rooms without aliases in search dialog ([\#10437](https://github.com/matrix-org/matrix-react-sdk/pull/10437)). Fixes #23937.
* Add input validation for `m.direct` in `DMRoomMap` ([\#10436](https://github.com/matrix-org/matrix-react-sdk/pull/10436)). Fixes #24909.
* Reduce height reserved for "collapse" button's line on IRC layout ([\#10211](https://github.com/matrix-org/matrix-react-sdk/pull/10211)). Fixes #24605. Contributed by @luixxiul.
* Fix `creatorUserId is required` error when opening sticker picker ([\#10423](https://github.com/matrix-org/matrix-react-sdk/pull/10423)).
* Fix block/inline Element descendants error noise in `NewRoomIntro.tsx` ([\#10412](https://github.com/matrix-org/matrix-react-sdk/pull/10412)). Contributed by @MadLittleMods.
* Fix profile resizer to make first character of a line selectable in IRC layout ([\#10396](https://github.com/matrix-org/matrix-react-sdk/pull/10396)). Fixes #14764. Contributed by @luixxiul.
* Ensure space between wrapped lines of room name on IRC layout ([\#10188](https://github.com/matrix-org/matrix-react-sdk/pull/10188)). Fixes #24742. Contributed by @luixxiul.
* Remove unreadable alt attribute from the room status bar warning icon (nonsense to screenreaders) ([\#10402](https://github.com/matrix-org/matrix-react-sdk/pull/10402)). Contributed by @MadLittleMods.
* Fix big date separators when jump to date is enabled ([\#10404](https://github.com/matrix-org/matrix-react-sdk/pull/10404)). Fixes #22969. Contributed by @MadLittleMods.
* Fixes user authentication when registering via the module API ([\#10257](https://github.com/matrix-org/matrix-react-sdk/pull/10257)). Contributed by @maheichyk.
* Handle more edge cases in Space Hierarchy ([\#10280](https://github.com/matrix-org/matrix-react-sdk/pull/10280)). Contributed by @justjanne.
* Further improve performance with lots of hidden events ([\#10353](https://github.com/matrix-org/matrix-react-sdk/pull/10353)). Fixes #24480. Contributed by @andybalaam.
* Respect user cancelling upload flow by dismissing spinner ([\#10373](https://github.com/matrix-org/matrix-react-sdk/pull/10373)). Fixes #24667.
* When starting a DM, the end-to-end encryption status icon does now only appear if the DM can be encrypted ([\#10394](https://github.com/matrix-org/matrix-react-sdk/pull/10394)). Fixes #24397.
* Fix `[object Object]` in feedback metadata ([\#10390](https://github.com/matrix-org/matrix-react-sdk/pull/10390)).
* Fix pinned messages card saying nothing pinned while loading ([\#10385](https://github.com/matrix-org/matrix-react-sdk/pull/10385)). Fixes #24615.
* Fix import e2e key dialog staying disabled after paste ([\#10375](https://github.com/matrix-org/matrix-react-sdk/pull/10375)). Fixes #24818.
* Show all labs even if incompatible, with appropriate tooltip explaining requirements ([\#10369](https://github.com/matrix-org/matrix-react-sdk/pull/10369)). Fixes #24813.
* Fix UIFeature.Registration not applying to all paths ([\#10371](https://github.com/matrix-org/matrix-react-sdk/pull/10371)). Fixes #24814.
* Clicking on a user pill does now only open the profile in the right panel and no longer navigates to the home view. ([\#10359](https://github.com/matrix-org/matrix-react-sdk/pull/10359)). Fixes #24797.
* Fix start DM with pending third party invite ([\#10347](https://github.com/matrix-org/matrix-react-sdk/pull/10347)). Fixes #24781.
* Fix long display name overflowing reply tile on IRC layout ([\#10343](https://github.com/matrix-org/matrix-react-sdk/pull/10343)). Fixes #24738. Contributed by @luixxiul.
* Display redacted body on ThreadView in the same way as normal messages ([\#9016](https://github.com/matrix-org/matrix-react-sdk/pull/9016)). Fixes #24729. Contributed by @luixxiul.
* Handle more edge cases in ACL updates ([\#10279](https://github.com/matrix-org/matrix-react-sdk/pull/10279)). Contributed by @justjanne.
* Allow parsing png files to fail if thumbnailing is successful ([\#10308](https://github.com/matrix-org/matrix-react-sdk/pull/10308)).
Changes in [1.11.28](https://github.com/vector-im/element-web/releases/tag/v1.11.28) (2023-03-31)
=================================================================================================
## 🐛 Bug Fixes
* (No changes, version bumped to sync with element-desktop.)
Changes in [1.11.27](https://github.com/vector-im/element-web/releases/tag/v1.11.27) (2023-03-31)
=================================================================================================
## 🐛 Bug Fixes
* Fix detection of encryption for all users in a room ([\#10487](https://github.com/matrix-org/matrix-react-sdk/pull/10487)). Fixes #24995.
Changes in [1.11.26](https://github.com/vector-im/element-web/releases/tag/v1.11.26) (2023-03-28)
=================================================================================================
## 🔒 Security
* Fixes for [CVE-2023-28427](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=CVE-2023-28427) / GHSA-mwq8-fjpf-c2gr
* Fixes for [CVE-2023-28103](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=CVE-2023-28103) / GHSA-6g43-88cp-w5gv
Changes in [1.11.25](https://github.com/vector-im/element-web/releases/tag/v1.11.25) (2023-03-15)
=================================================================================================
## ✨ Features
* Remove experimental PWA support for Firefox and Safari ([\#24630](https://github.com/vector-im/element-web/pull/24630)).
* Only allow to start a DM with one email if encryption by default is enabled ([\#10253](https://github.com/matrix-org/matrix-react-sdk/pull/10253)). Fixes #23133.
* DM rooms are now encrypted if encryption by default is enabled and only inviting a single email address. Any action in the result DM room will be blocked until the other has joined. ([\#10229](https://github.com/matrix-org/matrix-react-sdk/pull/10229)).
* Reduce bottom margin of ReplyChain on compact modern layout ([\#8972](https://github.com/matrix-org/matrix-react-sdk/pull/8972)). Fixes #22748. Contributed by @luixxiul.
* Support for v2 of MSC3903 ([\#10165](https://github.com/matrix-org/matrix-react-sdk/pull/10165)). Contributed by @hughns.
* When starting a DM, existing rooms with pending third-party invites will be reused. ([\#10256](https://github.com/matrix-org/matrix-react-sdk/pull/10256)). Fixes #23139.
* Polls push rules: synchronise poll rules with message rules ([\#10263](https://github.com/matrix-org/matrix-react-sdk/pull/10263)). Contributed by @kerryarchibald.
* New verification request toast button labels ([\#10259](https://github.com/matrix-org/matrix-react-sdk/pull/10259)).
* Remove padding around integration manager iframe ([\#10148](https://github.com/matrix-org/matrix-react-sdk/pull/10148)).
* Fix block code styling in rich text editor ([\#10246](https://github.com/matrix-org/matrix-react-sdk/pull/10246)). Contributed by @alunturner.
* Poll history: fetch more poll history ([\#10235](https://github.com/matrix-org/matrix-react-sdk/pull/10235)). Contributed by @kerryarchibald.
* Sort short/exact emoji matches before longer incomplete matches ([\#10212](https://github.com/matrix-org/matrix-react-sdk/pull/10212)). Fixes #23210. Contributed by @grimhilt.
* Poll history: detail screen ([\#10172](https://github.com/matrix-org/matrix-react-sdk/pull/10172)). Contributed by @kerryarchibald.
* Provide a more detailed error message than "No known servers" ([\#6048](https://github.com/matrix-org/matrix-react-sdk/pull/6048)). Fixes #13247. Contributed by @aaronraimist.
* Say when a call was answered from a different device ([\#10224](https://github.com/matrix-org/matrix-react-sdk/pull/10224)).
* Widget permissions customizations using module api ([\#10121](https://github.com/matrix-org/matrix-react-sdk/pull/10121)). Contributed by @maheichyk.
* Fix copy button icon overlapping with copyable text ([\#10227](https://github.com/matrix-org/matrix-react-sdk/pull/10227)). Contributed by @Adesh-Pandey.
* Support joining non-peekable rooms via the module API ([\#10154](https://github.com/matrix-org/matrix-react-sdk/pull/10154)). Contributed by @maheichyk.
* The "new login" toast does now display the same device information as in the settings. "No" does now open the device settings. "Yes, it was me" dismisses the toast. ([\#10200](https://github.com/matrix-org/matrix-react-sdk/pull/10200)).
* Do not prompt for a password when doing a „reset all“ after login ([\#10208](https://github.com/matrix-org/matrix-react-sdk/pull/10208)).
## 🐛 Bug Fixes
* Fix incorrect copy in space creation flow ([\#10296](https://github.com/matrix-org/matrix-react-sdk/pull/10296)). Fixes #24741.
* Fix space settings dialog having rogue title tooltip ([\#10293](https://github.com/matrix-org/matrix-react-sdk/pull/10293)). Fixes #24740.
* Show spinner when starting a DM from the user profile (right panel) ([\#10290](https://github.com/matrix-org/matrix-react-sdk/pull/10290)).
* Reduce height of toggle on expanded view source event ([\#10283](https://github.com/matrix-org/matrix-react-sdk/pull/10283)). Fixes #22873. Contributed by @luixxiul.
* Pillify http and non-prefixed matrix.to links ([\#10277](https://github.com/matrix-org/matrix-react-sdk/pull/10277)). Fixes #20844.
* Fix some features not being configurable via `features` ([\#10276](https://github.com/matrix-org/matrix-react-sdk/pull/10276)).
* Fix starting a DM from the right panel in some cases ([\#10278](https://github.com/matrix-org/matrix-react-sdk/pull/10278)). Fixes #24722.
* Align info EventTile and normal EventTile on IRC layout ([\#10197](https://github.com/matrix-org/matrix-react-sdk/pull/10197)). Fixes #22782. Contributed by @luixxiul.
* Fix blowout of waveform of the voice message player on narrow UI ([\#8861](https://github.com/matrix-org/matrix-react-sdk/pull/8861)). Fixes #22604. Contributed by @luixxiul.
* Fix the hidden view source toggle on IRC layout ([\#10266](https://github.com/matrix-org/matrix-react-sdk/pull/10266)). Fixes #22872. Contributed by @luixxiul.
* Fix buttons on the room header being compressed due to long room name ([\#10155](https://github.com/matrix-org/matrix-react-sdk/pull/10155)). Contributed by @luixxiul.
* Use the room avatar as a placeholder in calls ([\#10231](https://github.com/matrix-org/matrix-react-sdk/pull/10231)).
* Fix calls showing as 'connecting' after hangup ([\#10223](https://github.com/matrix-org/matrix-react-sdk/pull/10223)).
* Prevent multiple Jitsi calls started at the same time ([\#10183](https://github.com/matrix-org/matrix-react-sdk/pull/10183)). Fixes #23009.
* Make localization keys compatible with agglutinative and/or SOV type languages ([\#10159](https://github.com/matrix-org/matrix-react-sdk/pull/10159)). Contributed by @luixxiul.
Changes in [1.11.24](https://github.com/vector-im/element-web/releases/tag/v1.11.24) (2023-02-28)
=================================================================================================

View File

@ -1,5 +1,5 @@
# Builder
FROM --platform=$BUILDPLATFORM node:16-buster as builder
FROM --platform=$BUILDPLATFORM node:20-bullseye as builder
# Support custom branches of the react-sdk and js-sdk. This also helps us build
# images of element-web develop.
@ -23,7 +23,7 @@ RUN dos2unix /src/scripts/docker-package.sh && bash /src/scripts/docker-package.
RUN cp /src/config.sample.json /src/webapp/config.json
# App
FROM nginx:alpine
FROM nginx:alpine-slim
COPY --from=builder /src/webapp /app

View File

@ -18,7 +18,8 @@ Element has several tiers of support for different environments:
- Supported
- Definition: Issues **actively triaged**, regressions **block** the release
- Last 2 major versions of Chrome, Firefox, Safari, and Edge on desktop OSes
- Last 2 major versions of Chrome, Firefox, and Edge on desktop OSes
- Last 2 versions of Safari
- Latest release of official Element Desktop app on desktop OSes
- Desktop OSes means macOS, Windows, and Linux versions for desktop devices
that are actively supported by the OS vendor and receive security updates
@ -85,7 +86,7 @@ your web server configuration when hosting Element Web:
- The `X-Frame-Options: SAMEORIGIN` header, to prevent Element Web from being
framed and protect from [clickjacking][owasp-clickjacking].
- The `frame-ancestors 'none'` directive to your `Content-Security-Policy`
- The `frame-ancestors 'self'` directive to your `Content-Security-Policy`
header, as the modern replacement for `X-Frame-Options` (though both should be
included since not all browsers support it yet, see
[this][owasp-clickjacking-csp]).
@ -113,7 +114,7 @@ For Apache, the configuration looks like:
Header set X-Frame-Options SAMEORIGIN
Header set X-Content-Type-Options nosniff
Header set X-XSS-Protection "1; mode=block"
Header set Content-Security-Policy "frame-ancestors 'none'"
Header set Content-Security-Policy "frame-ancestors 'self'"
```
Note: In case you are already setting a `Content-Security-Policy` header

View File

@ -59,20 +59,21 @@ Unless otherwise specified, the following applies to all code:
6. Lines are trimmed of all excess whitespace, including blank lines.
7. Long lines are broken up for readability.
## TypeScript / JavaScript {#typescript-javascript}
## TypeScript / JavaScript
1. Write TypeScript. Turn JavaScript into TypeScript when working in the area.
2. Use named exports.
3. Use semicolons for block/line termination.
2. Use [TSDoc](https://tsdoc.org/) to document your code. See [Comments](#comments) below.
3. Use named exports.
4. Use semicolons for block/line termination.
1. Except when defining interfaces, classes, and non-arrow functions specifically.
4. When a statement's body is a single line, it must be written without curly braces, so long as the body is placed on
5. When a statement's body is a single line, it must be written without curly braces, so long as the body is placed on
the same line as the statement.
```typescript
if (x) doThing();
```
5. Blocks for `if`, `for`, `switch` and so on must have a space surrounding the condition, but not
6. Blocks for `if`, `for`, `switch` and so on must have a space surrounding the condition, but not
within the condition.
```typescript
@ -81,17 +82,17 @@ Unless otherwise specified, the following applies to all code:
}
```
6. lowerCamelCase is used for function and variable naming.
7. UpperCamelCase is used for general naming.
8. Interface names should not be marked with an uppercase `I`.
9. One variable declaration per line.
10. If a variable is not receiving a value on declaration, its type must be defined.
7. lowerCamelCase is used for function and variable naming.
8. UpperCamelCase is used for general naming.
9. Interface names should not be marked with an uppercase `I`.
10. One variable declaration per line.
11. If a variable is not receiving a value on declaration, its type must be defined.
```typescript
let errorMessage: Optional<string>;
```
11. Objects can use shorthand declarations, including mixing of types.
12. Objects can use shorthand declarations, including mixing of types.
```typescript
{
@ -102,7 +103,7 @@ Unless otherwise specified, the following applies to all code:
{ room, prop: this.prop }
```
12. Object keys should always be non-strings when possible.
13. Object keys should always be non-strings when possible.
```typescript
{
@ -112,23 +113,28 @@ Unless otherwise specified, the following applies to all code:
}
```
13. Explicitly cast to a boolean.
14. Explicitly cast to a boolean, rather than relying on implicit truthiness of non-boolean values:
```typescript
!!stringVar || Boolean(stringVar);
const isRealUser = !!userId && ...;
// ... or ...
const isRealUser = Boolean(userId) && ...;
// but *not*:
const isRealUser = userId && ...; // invalid implicit cast
```
14. Use `switch` statements when checking against more than a few enum-like values.
15. Use `const` for constants, `let` for mutability.
16. Describe types exhaustively (ensure noImplictAny would pass).
15. Use `switch` statements when checking against more than a few enum-like values.
16. Use `const` for constants, `let` for mutability.
17. Describe types exhaustively (ensure noImplictAny would pass).
1. Notable exceptions are arrow functions used as parameters, when a void return type is
obvious, and when declaring and assigning a variable in the same line.
17. Declare member visibility (public/private/protected).
18. Private members are private and not prefixed unless required for naming conflicts.
18. Declare member visibility (public/private/protected).
19. Private members are private and not prefixed unless required for naming conflicts.
1. Convention is to use an underscore or the word "internal" to denote conflicted member names.
2. "Conflicted" typically refers to a getter which wants the same name as the underlying variable.
19. Prefer readonly members over getters backed by a variable, unless an internal setter is required.
20. Prefer Interfaces for object definitions, and types for parameter-value-only declarations.
20. Prefer readonly members over getters backed by a variable, unless an internal setter is required.
21. Prefer Interfaces for object definitions, and types for parameter-value-only declarations.
1. Note that an explicit type is optional if not expected to be used outside of the function call,
unlike in this example:
@ -145,9 +151,9 @@ Unless otherwise specified, the following applies to all code:
}
```
21. Variables/properties which are `public static` should also be `readonly` when possible.
22. Interface and type properties are terminated with semicolons, not commas.
23. Prefer arrow formatting when declaring functions for interfaces/types:
22. Variables/properties which are `public static` should also be `readonly` when possible.
23. Interface and type properties are terminated with semicolons, not commas.
24. Prefer arrow formatting when declaring functions for interfaces/types:
```typescript
interface Test {
@ -155,13 +161,13 @@ Unless otherwise specified, the following applies to all code:
}
```
24. Prefer a type definition over an inline type. For example, define an interface.
25. Always prefer to add types or declare a type over the use of `any`. Prefer inferred types
25. Prefer a type definition over an inline type. For example, define an interface.
26. Always prefer to add types or declare a type over the use of `any`. Prefer inferred types
when they are not `any`.
1. When using `any`, a comment explaining why must be present.
26. `import` should be used instead of `require`, as `require` does not have types.
27. Export only what can be reused.
28. Prefer a type like `Optional<X>` (`type Optional<T> = T | null | undefined`) instead
27. `import` should be used instead of `require`, as `require` does not have types.
28. Export only what can be reused.
29. Prefer a type like `Optional<X>` (`type Optional<T> = T | null | undefined`) instead
of truly optional parameters.
1. A notable exception is when the likelihood of a bug is minimal, such as when a function
@ -179,12 +185,12 @@ Unless otherwise specified, the following applies to all code:
}
```
29. There should be approximately one interface, class, or enum per file unless the file is named
30. There should be approximately one interface, class, or enum per file unless the file is named
"types.ts", "global.d.ts", or ends with "-types.ts".
1. The file name should match the interface, class, or enum name.
30. Bulk functions can be declared in a single file, though named as "foo-utils.ts" or "utils/foo.ts".
31. Imports are grouped by external module imports first, then by internal imports.
32. File ordering is not strict, but should generally follow this sequence:
31. Bulk functions can be declared in a single file, though named as "foo-utils.ts" or "utils/foo.ts".
32. Imports are grouped by external module imports first, then by internal imports.
33. File ordering is not strict, but should generally follow this sequence:
1. Licence header
2. Imports
3. Constants
@ -199,16 +205,16 @@ Unless otherwise specified, the following applies to all code:
5. Protected and abstract functions
6. Public/private functions
7. Public/protected/private static functions
33. Variable names should be noticeably unique from their types. For example, "str: string" instead
34. Variable names should be noticeably unique from their types. For example, "str: string" instead
of "string: string".
34. Use double quotes to enclose strings. You may use single quotes if the string contains double quotes.
35. Use double quotes to enclose strings. You may use single quotes if the string contains double quotes.
```typescript
const example1 = "simple string";
const example2 = 'string containing "double quotes"';
```
35. Prefer async-await to promise-chaining
36. Prefer async-await to promise-chaining
```typescript
async function () {
@ -255,7 +261,7 @@ Inheriting all the rules of TypeScript, the following additionally apply:
if at all possible.
11. A component should only use CSS class names in line with the component name.
1. When knowingly using a class name from another component, document it.
1. When knowingly using a class name from another component, document it with a [comment](#comments).
12. Curly braces within JSX should be padded with a space, however properties on those components should not.
See above code example.
@ -275,11 +281,15 @@ Inheriting all the rules of TypeScript, the following additionally apply:
Note: We use PostCSS + some plugins to process our styles. It looks like SCSS, but actually it is not.
1. Class names must be prefixed with "mx\_".
2. Class names should denote the component which defines them, followed by any context:
1. mx_MyFoo
2. mx_MyFoo_avatar
3. mx_MyFoo_avatar--user
3. Use the `$font` and `$spacing` variables instead of manual values.
2. Class names must denote the component which defines them, followed by any context.
The context is not further specified here in terms of meaning or syntax.
Use whatever is appropriate for your implementation use case.
Some examples:
1. `mx_MyFoo`
2. `mx_MyFoo_avatar`
3. `mx_MyFoo_avatarUser`
4. `mx_MyFoo_avatar--user`
3. Use the `$font` variables instead of manual values.
4. Keep indentation/nesting to a minimum. Maximum suggested nesting is 5 layers.
5. Use the whole class name instead of shortcuts:
@ -304,7 +314,7 @@ Note: We use PostCSS + some plugins to process our styles. It looks like SCSS, b
7. Non-shared variables should use $lowerCamelCase. Shared variables use $dashed-naming.
8. Overrides to Z indexes, adjustments of dimensions/padding with pixels, and so on should all be
documented for what the values mean:
[documented](#comments) for what the values mean:
```scss
.mx_MyFoo {
@ -314,7 +324,7 @@ Note: We use PostCSS + some plugins to process our styles. It looks like SCSS, b
}
```
9. Avoid the use of `!important`. If necessary, add a comment.
9. Avoid the use of `!important`. If `!important` is necessary, add a [comment](#comments) explaining why.
## Tests
@ -358,3 +368,38 @@ Note: We use PostCSS + some plugins to process our styles. It looks like SCSS, b
});
});
```
## Comments
1. As a general principle: be liberal with comments. This applies to all files: stylesheets as well as
JavaScript/TypeScript.
Good comments not only help future readers understand and maintain the code; they can also encourage good design
by clearly setting out how different parts of the codebase interact where that would otherwise be implicit and
subject to interpretation.
2. Aim to document all types, methods, class properties, functions, etc, with [TSDoc](https://tsdoc.org/) doc comments.
This is _especially_ important for public interfaces in `matrix-js-sdk`, but is good practice in general.
Even very simple interfaces can often benefit from a doc-comment, both as a matter of consistency, and because simple
interfaces have a habit of becoming more complex over time.
3. React components should be documented in the same way as other classes or functions. The documentation should give
a brief description of how the component should be used, and, especially for more complex components, each of its
properties should be clearly documented.
4. Inside a function, there is no need to comment every line, but consider:
- before a particular multiline section of code within the function, give an overview of what it does,
to make it easier for a reader to follow the flow through the function as a whole.
- if it is anything less than obvious, explain _why_ we are doing a particular operation, with particular emphasis
on how this function interacts with other parts of the codebase.
5. When making changes to existing code, authors are expected to read existing comments and make any necessary changes
to ensure they remain accurate.
6. Reviewers are encouraged to consider whether more comments would be useful, and to ask the author to add them.
It is natural for an author to feel that the code they have just written is "obvious" and that comments would be
redundant, whereas in reality it would take some time for reader unfamiliar with the code to understand it. A
reviewer is well-placed to make a more objective judgement.

View File

@ -508,6 +508,7 @@ Currently, the following UI feature flags are supported:
timeline for recent messages. When false day dates will be used.
- `UIFeature.BulkUnverifiedSessionsReminder` - Display popup reminders to verify or remove unverified sessions. Defaults
to true.
- `UIFeature.locationSharing` - Whether or not location sharing menus will be shown.
## Undocumented / developer options

View File

@ -100,12 +100,6 @@ theme definition.
For some sample themes, check out [aaronraimist/element-themes](https://github.com/aaronraimist/element-themes).
## Message preview tweaks
To enable message previews in the left panel for reactions in all rooms, enable `feature_roomlist_preview_reactions_all`.
To enable message previews for reactions in DMs only, enable `feature_roomlist_preview_reactions_dms`. This is ignored when it is enabled for all rooms.
## Dehydrated devices (`feature_dehydration`)
Allows users to receive encrypted messages by creating a device that is stored

57
jest.config.ts Normal file
View File

@ -0,0 +1,57 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { env } from "process";
import type { Config } from "jest";
const config: Config = {
testEnvironment: "jsdom",
testEnvironmentOptions: {
url: "http://localhost/",
},
testMatch: ["<rootDir>/test/**/*-test.[tj]s?(x)"],
setupFiles: ["jest-canvas-mock"],
setupFilesAfterEnv: ["<rootDir>/node_modules/matrix-react-sdk/test/setupTests.js"],
moduleNameMapper: {
"\\.(css|scss|pcss)$": "<rootDir>/__mocks__/cssMock.js",
"\\.(gif|png|ttf|woff2)$": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/imageMock.js",
"\\.svg$": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/svg.js",
"\\$webapp/i18n/languages.json": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/languages.json",
"^react$": "<rootDir>/node_modules/react",
"^react-dom$": "<rootDir>/node_modules/react-dom",
"^matrix-js-sdk$": "<rootDir>/node_modules/matrix-js-sdk/src",
"^matrix-react-sdk$": "<rootDir>/node_modules/matrix-react-sdk/src",
"decoderWorker\\.min\\.js": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/empty.js",
"decoderWorker\\.min\\.wasm": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/empty.js",
"waveWorker\\.min\\.js": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/empty.js",
"context-filter-polyfill": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/empty.js",
"FontManager.ts": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/FontManager.js",
"workers/(.+)\\.worker\\.ts": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/workerMock.js",
"^!!raw-loader!.*": "jest-raw-loader",
"RecorderWorklet": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/empty.js",
},
transformIgnorePatterns: ["/node_modules/(?!matrix-js-sdk).+$", "/node_modules/(?!matrix-react-sdk).+$"],
coverageReporters: ["text-summary", "lcov"],
testResultsProcessor: "@casualbot/jest-sonar-reporter",
};
// if we're running under GHA, enable the GHA reporter
if (env["GITHUB_ACTIONS"] !== undefined) {
config.reporters = [["github-actions", { silent: false }], "summary"];
}
export default config;

View File

@ -1,6 +1,6 @@
{
"name": "element-web",
"version": "1.11.24",
"version": "1.11.30",
"description": "A feature-rich client for Matrix.org",
"author": "New Vector Ltd.",
"repository": {
@ -26,6 +26,9 @@
"contribute.json"
],
"style": "bundle.css",
"matrix_i18n_extra_translation_funcs": [
"UserFriendlyError"
],
"scripts": {
"i18n": "matrix-gen-i18n",
"prunei18n": "matrix-prune-i18n",
@ -62,17 +65,17 @@
},
"resolutions": {
"@types/react-dom": "17.0.19",
"@types/react": "17.0.53"
"@types/react": "17.0.58"
},
"dependencies": {
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz",
"@matrix-org/react-sdk-module-api": "^0.0.4",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
"@matrix-org/react-sdk-module-api": "^0.0.5",
"gfm.css": "^1.1.2",
"jsrsasign": "^10.5.25",
"katex": "^0.16.0",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
"matrix-react-sdk": "github:matrix-org/matrix-react-sdk#develop",
"matrix-widget-api": "^1.1.1",
"matrix-widget-api": "^1.3.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"sanitize-html": "^2.3.2",
@ -100,12 +103,12 @@
"@sentry/webpack-plugin": "^1.18.1",
"@svgr/webpack": "^5.5.0",
"@testing-library/react": "^12.1.5",
"@types/flux": "^3.1.9",
"@types/jest": "^29.0.0",
"@types/jitsi-meet": "^2.0.2",
"@types/jsrsasign": "^10.5.4",
"@types/modernizr": "^3.5.3",
"@types/node": "^16",
"@types/react": "17.0.53",
"@types/react": "17.0.58",
"@types/react-dom": "17.0.19",
"@types/sanitize-html": "^2.3.1",
"@types/ua-parser-js": "^0.7.36",
@ -115,11 +118,11 @@
"babel-jest": "^29.0.0",
"babel-loader": "^8.2.2",
"chokidar": "^3.5.1",
"concurrently": "^7.0.0",
"concurrently": "^8.0.0",
"cpx": "^1.5.0",
"css-loader": "^4",
"dotenv": "^16.0.2",
"eslint": "8.35.0",
"eslint": "8.39.0",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-deprecate": "^0.7.0",
@ -127,7 +130,7 @@
"eslint-plugin-matrix-org": "^1.0.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-unicorn": "^45.0.0",
"eslint-plugin-unicorn": "^46.0.0",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"fake-indexeddb": "^4.0.0",
"fetch-mock-jest": "^1.5.1",
@ -135,17 +138,17 @@
"fs-extra": "^11.0.0",
"html-webpack-plugin": "^4.5.2",
"jest": "^29.0.0",
"jest-canvas-mock": "^2.3.0",
"jest-canvas-mock": "2.4.0",
"jest-environment-jsdom": "^29.0.0",
"jest-mock": "^29.0.0",
"jest-raw-loader": "^1.0.1",
"json-loader": "^0.5.7",
"loader-utils": "^3.0.0",
"matrix-mock-request": "^2.5.0",
"matrix-web-i18n": "^1.3.0",
"matrix-web-i18n": "^1.4.0",
"mini-css-extract-plugin": "^1",
"minimist": "^1.2.6",
"mkdirp": "^2.0.0",
"mkdirp": "^3.0.0",
"modernizr": "^3.12.0",
"node-fetch": "^2.6.7",
"optimize-css-assets-webpack-plugin": "^6.0.0",
@ -159,20 +162,20 @@
"postcss-preset-env": "^6.7.0",
"postcss-scss": "^4.0.4",
"postcss-simple-vars": "^5.0.2",
"prettier": "2.8.4",
"prettier": "2.8.8",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"rimraf": "^5.0.0",
"semver": "^7.3.7",
"simple-proxy-agent": "^1.1.0",
"string-replace-loader": "3",
"style-loader": "2",
"stylelint": "^15.0.0",
"stylelint-config-prettier": "^9.0.4",
"stylelint-config-standard": "^30.0.0",
"stylelint-scss": "^4.2.0",
"stylelint": "^15.3.0",
"stylelint-config-standard": "^33.0.0",
"stylelint-scss": "^5.0.0",
"terser-webpack-plugin": "^4.0.0",
"ts-node": "^10.9.1",
"ts-prune": "^0.10.3",
"typescript": "4.9.5",
"typescript": "5.0.4",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2",
@ -180,48 +183,6 @@
"worklet-loader": "^2.0.0",
"yaml": "^2.0.1"
},
"jest": {
"testEnvironment": "jsdom",
"testEnvironmentOptions": {
"url": "http://localhost/"
},
"testMatch": [
"<rootDir>/test/**/*-test.[tj]s?(x)"
],
"setupFiles": [
"jest-canvas-mock"
],
"setupFilesAfterEnv": [
"<rootDir>/node_modules/matrix-react-sdk/test/setupTests.js"
],
"moduleNameMapper": {
"\\.(css|scss|pcss)$": "<rootDir>/__mocks__/cssMock.js",
"\\.(gif|png|ttf|woff2)$": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/imageMock.js",
"\\.svg$": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/svg.js",
"\\$webapp/i18n/languages.json": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/languages.json",
"^react$": "<rootDir>/node_modules/react",
"^react-dom$": "<rootDir>/node_modules/react-dom",
"^matrix-js-sdk$": "<rootDir>/node_modules/matrix-js-sdk/src",
"^matrix-react-sdk$": "<rootDir>/node_modules/matrix-react-sdk/src",
"decoderWorker\\.min\\.js": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/empty.js",
"decoderWorker\\.min\\.wasm": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/empty.js",
"waveWorker\\.min\\.js": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/empty.js",
"context-filter-polyfill": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/empty.js",
"FontManager.ts": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/FontManager.js",
"workers/(.+)\\.worker\\.ts": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/workerMock.js",
"^!!raw-loader!.*": "jest-raw-loader",
"RecorderWorklet": "<rootDir>/node_modules/matrix-react-sdk/__mocks__/empty.js"
},
"transformIgnorePatterns": [
"/node_modules/(?!matrix-js-sdk).+$",
"/node_modules/(?!matrix-react-sdk).+$"
],
"coverageReporters": [
"text-summary",
"lcov"
],
"testResultsProcessor": "@casualbot/jest-sonar-reporter"
},
"@casualbot/jest-sonar-reporter": {
"outputDirectory": "coverage",
"outputName": "jest-sonar-report.xml",

View File

@ -3,7 +3,12 @@
"apps": [],
"details": [
{
"appID": "7J4U792NQT.im.vector.app",
"appIDs":[
"7J4U792NQT.im.vector.app",
"7J4U792NQT.io.element.elementx",
"7J4U792NQT.io.element.elementx.nightly",
"7J4U792NQT.io.element.elementx.pr"
],
"paths": [
"*"
]
@ -12,7 +17,10 @@
},
"webcredentials": {
"apps": [
"7J4U792NQT.im.vector.app"
"7J4U792NQT.im.vector.app",
"7J4U792NQT.io.element.elementx",
"7J4U792NQT.io.element.elementx.nightly",
"7J4U792NQT.io.element.elementx.pr"
]
}
}

View File

@ -5,12 +5,12 @@
const fs = require("fs");
const path = require("path");
const mkdirp = require("mkdirp");
const { mkdirpSync } = require("mkdirp");
const fetch = require("node-fetch");
const ProxyAgent = require("simple-proxy-agent");
console.log("Making webapp directory");
mkdirp.sync("webapp");
mkdirpSync("webapp");
// curl -s https://meet.element.io/libs/external_api.min.js > ./webapp/jitsi_external_api.min.js
console.log("Downloading Jitsi script");

View File

@ -77,7 +77,7 @@ dodep matrix-org matrix-js-sdk
pushd matrix-js-sdk
yarn link
yarn install --pure-lockfile
yarn install --frozen-lockfile
popd
yarn link matrix-js-sdk
@ -91,7 +91,7 @@ dodep matrix-org matrix-react-sdk
pushd matrix-react-sdk
yarn link
yarn link matrix-js-sdk
yarn install --pure-lockfile
yarn install --frozen-lockfile
popd
yarn link matrix-react-sdk

View File

@ -14,7 +14,7 @@ set -ex
# for the primary repo (element-web in this case).
# Install dependencies, as we'll be using fetchdep.sh from matrix-react-sdk
yarn install --pure-lockfile
yarn install --frozen-lockfile
# Pass appropriate repo to fetchdep.sh
export PR_ORG=vector-im
@ -24,7 +24,7 @@ export PR_REPO=element-web
node_modules/matrix-react-sdk/scripts/fetchdep.sh matrix-org matrix-js-sdk
pushd matrix-js-sdk
yarn link
yarn install --pure-lockfile
yarn install --frozen-lockfile
popd
# Also set up matrix-analytics-events so we get the latest from
@ -32,7 +32,7 @@ popd
node_modules/matrix-react-sdk/scripts/fetchdep.sh matrix-org matrix-analytics-events main
pushd matrix-analytics-events
yarn link
yarn install --pure-lockfile
yarn install --frozen-lockfile
yarn build:ts
popd
@ -42,7 +42,7 @@ pushd matrix-react-sdk
yarn link
yarn link matrix-js-sdk
yarn link @matrix-org/analytics-events
yarn install --pure-lockfile
yarn install --frozen-lockfile
popd
# Link the layers into element-web

29
src/@types/jitsi-meet.d.ts vendored Normal file
View File

@ -0,0 +1,29 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import "jitsi-meet";
declare module "jitsi-meet" {
interface ExternalAPIEventCallbacks {
errorOccurred: (e: { error: Error & { isFatal?: boolean } }) => void;
}
interface JitsiMeetExternalAPI {
executeCommand(command: "setTileView", value: boolean): void;
}
}
export as namespace Jitsi;

View File

@ -17,10 +17,10 @@ limitations under the License.
import * as React from "react";
import { _t } from "matrix-react-sdk/src/languageHandler";
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
// directly import the style here as this layer does not support rethemedex at this time so no matrix-react-sdk
// PostCSS variables will be accessible.
import "../../../res/css/structures/ErrorView.pcss";
import { ReactNode } from "react";
interface IProps {
onAccept(): void;
@ -30,7 +30,7 @@ const CompatibilityView: React.FC<IProps> = ({ onAccept }) => {
const brand = SdkConfig.get("brand");
const mobileBuilds = SdkConfig.get("mobile_builds");
let ios = null;
let ios: JSX.Element | undefined;
const iosCustomUrl = mobileBuilds?.ios;
if (iosCustomUrl !== null) {
// could be undefined or a string
@ -42,6 +42,7 @@ const CompatibilityView: React.FC<IProps> = ({ onAccept }) => {
<a
href={iosCustomUrl || "https://apps.apple.com/app/vector/id1083446067"}
target="_blank"
rel="noreferrer noopener"
className="mx_ClearDecoration"
>
<img height="48" src="themes/element/img/download/apple.svg" alt="Apple App Store" />
@ -63,6 +64,7 @@ const CompatibilityView: React.FC<IProps> = ({ onAccept }) => {
<a
href={andCustomUrl || "https://play.google.com/store/apps/details?id=im.vector.app"}
target="_blank"
rel="noreferrer noopener"
className="mx_ClearDecoration"
key="android"
>
@ -76,6 +78,7 @@ const CompatibilityView: React.FC<IProps> = ({ onAccept }) => {
<a
href={fdroidCustomUrl || "https://f-droid.org/repository/browse/?fdid=im.vector.app"}
target="_blank"
rel="noreferrer noopener"
className="mx_ClearDecoration"
key="fdroid"
>
@ -88,7 +91,7 @@ const CompatibilityView: React.FC<IProps> = ({ onAccept }) => {
android = [];
}
let mobileHeader = <h2 id="step2_heading">{_t("Use %(brand)s on mobile", { brand })}</h2>;
let mobileHeader: ReactNode = <h2 id="step2_heading">{_t("Use %(brand)s on mobile", { brand })}</h2>;
if (!android.length && !ios) {
mobileHeader = null;
}

View File

@ -27,7 +27,7 @@ const VectorAuthFooter = (): ReactElement => {
{ text: "GitHub", url: "https://github.com/vector-im/element-web" },
];
const authFooterLinks = [];
const authFooterLinks: JSX.Element[] = [];
for (const linkEntry of links) {
authFooterLinks.push(
<a href={linkEntry.url} key={linkEntry.text} target="_blank" rel="noreferrer noopener">

View File

@ -20,7 +20,7 @@ import SdkConfig from "matrix-react-sdk/src/SdkConfig";
import VectorAuthFooter from "./VectorAuthFooter";
export default class VectorAuthPage extends React.PureComponent {
private static welcomeBackgroundUrl;
private static welcomeBackgroundUrl?: string;
// cache the url as a static to prevent it changing without refreshing
private static getWelcomeBackgroundUrl(): string {

View File

@ -49,7 +49,7 @@ export default class Favicon {
private readonly params: IParams;
private readonly canvas: HTMLCanvasElement;
private readonly baseImage: HTMLImageElement;
private context: CanvasRenderingContext2D;
private context!: CanvasRenderingContext2D;
private icons: HTMLLinkElement[];
private isReady = false;
@ -72,14 +72,14 @@ export default class Favicon {
// get height and width of the favicon
this.canvas.height = this.baseImage.height > 0 ? this.baseImage.height : 32;
this.canvas.width = this.baseImage.width > 0 ? this.baseImage.width : 32;
this.context = this.canvas.getContext("2d");
this.context = this.canvas.getContext("2d")!;
this.ready();
};
this.baseImage.setAttribute("src", lastIcon.getAttribute("href"));
this.baseImage.setAttribute("src", lastIcon.getAttribute("href")!);
} else {
this.canvas.height = this.baseImage.height = 32;
this.canvas.width = this.baseImage.width = 32;
this.context = this.canvas.getContext("2d");
this.context = this.canvas.getContext("2d")!;
this.ready();
}
}
@ -152,7 +152,7 @@ export default class Favicon {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.context.drawImage(this.baseImage, 0, 0, this.canvas.width, this.canvas.height);
this.context.beginPath();
const fontSize = Math.floor(opt.h * (opt.n > 99 ? 0.85 : 1)) + "px";
const fontSize = Math.floor(opt.h * (typeof opt.n === "number" && opt.n > 99 ? 0.85 : 1)) + "px";
this.context.font = `${params.fontWeight} ${fontSize} ${params.fontFamily}`;
this.context.textAlign = "center";
@ -239,7 +239,7 @@ export default class Favicon {
const icons: HTMLLinkElement[] = [];
const links = window.document.getElementsByTagName("head")[0].getElementsByTagName("link");
for (const link of links) {
if (/(^|\s)icon(\s|$)/i.test(link.getAttribute("rel"))) {
if (link.hasAttribute("rel") && /(^|\s)icon(\s|$)/i.test(link.getAttribute("rel")!)) {
icons.push(link);
}
}

3
src/i18n/strings/my.json Normal file
View File

@ -0,0 +1,3 @@
{
"Invalid configuration: no default server specified.": "ဖွဲ့စည်းပုံ မမှန်ပါ။ default ဆာဗာကို သတ်မှတ်ထားခြင်း မရှိပါ။"
}

View File

@ -25,5 +25,8 @@
"Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.": "Vennligst installer <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, eller <safariLink>Safari</safariLink> for den beste opplevelsen.",
"%(brand)s uses advanced browser features which aren't supported by your current browser.": "%(brand)s bruker avanserte nettleserfunksjoner som ikke støttes av din nåværende nettleser.",
"Open": "Åpne",
"Use %(brand)s on mobile": "Bruk %(brand)s på mobil"
"Use %(brand)s on mobile": "Bruk %(brand)s på mobil",
"Decentralised, encrypted chat &amp; collaboration powered by $matrixLogo": "Desentralisert, kryptert chat & samhandling basert på $matrixLogo",
"%(appName)s: %(browserName)s on %(osName)s": "%(appName)s: %(browserName)s på %(osName)s",
"%(brand)s Desktop: %(platformName)s": "%(brand)s Desktop: %(platformName)s"
}

View File

@ -25,5 +25,8 @@
"Your Element is misconfigured": "Element er feilkonfigurert",
"Failed to start": "Klarte ikkje å starta",
"Open": "Opna",
"Download Completed": "Nedlasting Fullført"
"Download Completed": "Nedlasting Fullført",
"Decentralised, encrypted chat &amp; collaboration powered by $matrixLogo": "Desentralisertd kryptert chatt &amp; samarbeid som vert drive av $matrixLogo",
"%(appName)s: %(browserName)s on %(osName)s": "%(appName)s: %(browserName)s på %(osName)s",
"%(brand)s Desktop: %(platformName)s": "%(brand)s Skrivebord: %(platformName)s"
}

View File

@ -26,5 +26,7 @@
"Your Element is misconfigured": "Twój Element jest nieprawidłowo skonfigurowany",
"Powered by Matrix": "Zasilane przez Matrix",
"Use %(brand)s on mobile": "Użyj %(brand)s w telefonie",
"Decentralised, encrypted chat &amp; collaboration powered by $matrixLogo": "Zdecentralizowany, szyfrowany czat i współpraca oparte na $matrixLogo"
"Decentralised, encrypted chat &amp; collaboration powered by $matrixLogo": "Zdecentralizowany, szyfrowany czat i współpraca oparte na $matrixLogo",
"%(appName)s: %(browserName)s on %(osName)s": "%(appName)s: %(browserName)s na %(osName)s",
"%(brand)s Desktop: %(platformName)s": "Komputer %(brand)s: %(platformName)s"
}

View File

@ -25,5 +25,6 @@
"Failed to start": "Erro ao iniciar",
"%(brand)s uses advanced browser features which aren't supported by your current browser.": "%(brand)s utiliza funções avançadas que não são suportadas pelo teu atual browser.",
"Your browser can't run %(brand)s": "O teu browser não consegue executar %(brand)s",
"Use %(brand)s on mobile": "Usar %(brand)s no telemóvel"
"Use %(brand)s on mobile": "Usar %(brand)s no telemóvel",
"Decentralised, encrypted chat &amp; collaboration powered by $matrixLogo": "Chat descentralizado e encriptado &amp; colaboração alimentada por $matrixLogo"
}

View File

@ -1,7 +1,7 @@
{
"Dismiss": "關閉",
"Unknown device": "未知裝置",
"Welcome to Element": "歡迎來到 Element",
"Welcome to Element": "歡迎使用 Element",
"Sign In": "登入",
"Create Account": "建立帳號",
"Explore rooms": "探索聊天室",
@ -20,13 +20,13 @@
"Failed to start": "啟動失敗",
"Download Completed": "下載完成",
"Open": "開啟",
"Your Element is misconfigured": "您的 Element 配置錯誤",
"Your Element configuration contains invalid JSON. Please correct the problem and reload the page.": "您的 Element 的配置中包含無效 JSON請更正錯誤並重新加載網頁。",
"Your Element is misconfigured": "您的 Element 設定錯誤",
"Your Element configuration contains invalid JSON. Please correct the problem and reload the page.": "您的 Element 設定中包含無效 JSON請修正後重新載入網頁。",
"Your browser can't run %(brand)s": "您的瀏覽器無法執行 %(brand)s",
"%(brand)s uses advanced browser features which aren't supported by your current browser.": "%(brand)s 使用了您目前瀏覽器不支援的進階功能。",
"Powered by Matrix": "由 Matrix 驅動",
"Powered by Matrix": "Powered by Matrix",
"Use %(brand)s on mobile": "在行動裝置上使用 %(brand)s",
"Decentralised, encrypted chat &amp; collaboration powered by $matrixLogo": "去中心化、加密的聊天與協作,由 $matrixLogo 驅動",
"%(appName)s: %(browserName)s on %(osName)s": "%(appName)s%(browserName)s 位於 %(osName)s",
"%(brand)s Desktop: %(platformName)s": "%(brand)s 桌面%(platformName)s"
"Decentralised, encrypted chat &amp; collaboration powered by $matrixLogo": "由 $matrixLogo 驅動的去中心化、加密的聊天與協作工具",
"%(appName)s: %(browserName)s on %(osName)s": "%(appName)s%(osName)s 的 %(browserName)s",
"%(brand)s Desktop: %(platformName)s": "%(brand)s 桌面%(platformName)s"
}

View File

@ -23,9 +23,9 @@ import "matrix-js-sdk/src/browser-index";
import React, { ReactElement } from "react";
import PlatformPeg from "matrix-react-sdk/src/PlatformPeg";
import { _td, newTranslatableError } from "matrix-react-sdk/src/languageHandler";
import { UserFriendlyError } from "matrix-react-sdk/src/languageHandler";
import AutoDiscoveryUtils from "matrix-react-sdk/src/utils/AutoDiscoveryUtils";
import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery";
import { AutoDiscovery, ClientConfig } from "matrix-js-sdk/src/autodiscovery";
import * as Lifecycle from "matrix-react-sdk/src/Lifecycle";
import SdkConfig, { parseSsoRedirectOptions } from "matrix-react-sdk/src/SdkConfig";
import { IConfigOptions } from "matrix-react-sdk/src/IConfigOptions";
@ -33,6 +33,8 @@ import { logger } from "matrix-js-sdk/src/logger";
import { createClient } from "matrix-js-sdk/src/matrix";
import { SnakedObject } from "matrix-react-sdk/src/utils/SnakedObject";
import MatrixChat from "matrix-react-sdk/src/components/structures/MatrixChat";
import { ValidatedServerConfig } from "matrix-react-sdk/src/utils/ValidatedServerConfig";
import { QueryDict, encodeParams } from "matrix-js-sdk/src/utils";
import { parseQs } from "./url_utils";
import VectorBasePlatform from "./platform/VectorBasePlatform";
@ -55,24 +57,19 @@ window.matrixLogger = logger;
// If we're in electron, we should never pass through a file:// URL otherwise
// the identity server will try to 302 the browser to it, which breaks horribly.
// so in that instance, hardcode to use app.element.io for now instead.
function makeRegistrationUrl(params: object): string {
let url;
function makeRegistrationUrl(params: QueryDict): string {
let url: string;
if (window.location.protocol === "vector:") {
url = "https://app.element.io/#/register";
} else {
url = window.location.protocol + "//" + window.location.host + window.location.pathname + "#/register";
}
const keys = Object.keys(params);
for (let i = 0; i < keys.length; ++i) {
if (i === 0) {
url += "?";
} else {
url += "&";
}
const k = keys[i];
url += k + "=" + encodeURIComponent(params[k]);
const encodedParams = encodeParams(params);
if (encodedParams) {
url += "?" + encodedParams;
}
return url;
}
@ -117,18 +114,19 @@ export async function loadApp(fragParams: {}): Promise<ReactElement> {
if (!hasPossibleToken && !isReturningFromSso && autoRedirect) {
logger.log("Bypassing app load to redirect to SSO");
const tempCli = createClient({
baseUrl: config.validated_server_config.hsUrl,
idBaseUrl: config.validated_server_config.isUrl,
baseUrl: config.validated_server_config!.hsUrl,
idBaseUrl: config.validated_server_config!.isUrl,
});
PlatformPeg.get().startSingleSignOn(tempCli, "sso", `/${getScreenFromLocation(window.location).screen}`);
PlatformPeg.get()!.startSingleSignOn(tempCli, "sso", `/${getScreenFromLocation(window.location).screen}`);
// We return here because startSingleSignOn() will asynchronously redirect us. We don't
// care to wait for it, and don't want to show any UI while we wait (not even half a welcome
// page). As such, just don't even bother loading the MatrixChat component.
return;
return <React.Fragment />;
}
const defaultDeviceName = snakedConfig.get("default_device_display_name") ?? platform.getDefaultDeviceDisplayName();
const defaultDeviceName =
snakedConfig.get("default_device_display_name") ?? platform?.getDefaultDeviceDisplayName();
return (
<MatrixChat
@ -146,7 +144,7 @@ export async function loadApp(fragParams: {}): Promise<ReactElement> {
}
async function verifyServerConfig(): Promise<IConfigOptions> {
let validatedConfig;
let validatedConfig: ValidatedServerConfig;
try {
logger.log("Verifying homeserver configuration");
@ -168,16 +166,14 @@ async function verifyServerConfig(): Promise<IConfigOptions> {
const incompatibleOptions = [wkConfig, serverName, hsUrl].filter((i) => !!i);
if (incompatibleOptions.length > 1) {
// noinspection ExceptionCaughtLocallyJS
throw newTranslatableError(
_td(
"Invalid configuration: can only specify one of default_server_config, default_server_name, " +
"or default_hs_url.",
),
throw new UserFriendlyError(
"Invalid configuration: can only specify one of default_server_config, default_server_name, " +
"or default_hs_url.",
);
}
if (incompatibleOptions.length < 1) {
// noinspection ExceptionCaughtLocallyJS
throw newTranslatableError(_td("Invalid configuration: no default server specified."));
throw new UserFriendlyError("Invalid configuration: no default server specified.");
}
if (hsUrl) {
@ -199,7 +195,7 @@ async function verifyServerConfig(): Promise<IConfigOptions> {
}
}
let discoveryResult = null;
let discoveryResult: ClientConfig | undefined;
if (wkConfig) {
logger.log("Config uses a default_server_config - validating object");
discoveryResult = await AutoDiscovery.fromDiscoveryConfig(wkConfig);

View File

@ -18,16 +18,16 @@ import type { IConfigOptions } from "matrix-react-sdk/src/IConfigOptions";
// Load the config file. First try to load up a domain-specific config of the
// form "config.$domain.json" and if that fails, fall back to config.json.
export async function getVectorConfig(relativeLocation = ""): Promise<IConfigOptions> {
export async function getVectorConfig(relativeLocation = ""): Promise<IConfigOptions | undefined> {
if (relativeLocation !== "" && !relativeLocation.endsWith("/")) relativeLocation += "/";
const specificConfigPromise = getConfig(`${relativeLocation}config.${document.domain}.json`);
const specificConfigPromise = getConfig(`${relativeLocation}config.${window.location.hostname}.json`);
const generalConfigPromise = getConfig(relativeLocation + "config.json");
try {
const configJson = await specificConfigPromise;
// 404s succeed with an empty json config, so check that there are keys
if (Object.keys(configJson).length === 0) {
if (!configJson || Object.keys(configJson).length === 0) {
throw new Error(); // throw to enter the catch
}
return configJson;
@ -36,7 +36,7 @@ export async function getVectorConfig(relativeLocation = ""): Promise<IConfigOpt
}
}
async function getConfig(configJsonFilename: string): Promise<IConfigOptions> {
async function getConfig(configJsonFilename: string): Promise<IConfigOptions | undefined> {
const url = new URL(configJsonFilename, window.location.href);
url.searchParams.set("cachebuster", Date.now().toString());
const res = await fetch(url, {

View File

@ -26,7 +26,7 @@
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
style-src 'self' 'unsafe-inline' <%= csp_extra_source %>;
script-src 'self' 'unsafe-eval' https://www.recaptcha.net https://www.gstatic.com <%= csp_extra_source %>;
script-src 'self' 'wasm-unsafe-eval' https://www.recaptcha.net/recaptcha/ https://www.gstatic.com/recaptcha/ <%= csp_extra_source %>;
img-src * blob: data:;
connect-src *;
font-src 'self' data: <%= csp_extra_source %>;

View File

@ -19,6 +19,7 @@ limitations under the License.
*/
import { logger } from "matrix-js-sdk/src/logger";
import { extractErrorMessageFromError } from "matrix-react-sdk/src/components/views/dialogs/ErrorDialog";
// These are things that can run before the skin loads - be careful not to reference the react-sdk though.
import { parseQsFromFragment } from "./url_utils";
@ -71,7 +72,7 @@ function checkBrowserFeatures(): boolean {
// ES2019: http://262.ecma-international.org/10.0/#sec-object.fromentries
window.Modernizr.addTest("objectfromentries", () => typeof window.Object?.fromEntries === "function");
const featureList = Object.keys(window.Modernizr);
const featureList = Object.keys(window.Modernizr) as Array<keyof ModernizrStatic>;
let featureComplete = true;
for (const feature of featureList) {
@ -194,7 +195,7 @@ async function start(): Promise<void> {
await loadConfigPromise;
} catch (error) {
// Now that we've loaded the theme (CSS), display the config syntax error if needed.
if (error.err && error.err instanceof SyntaxError) {
if (error instanceof SyntaxError) {
// This uses the default brand since the app config is unavailable.
return showError(_t("Your Element is misconfigured"), [
_t(
@ -202,7 +203,7 @@ async function start(): Promise<void> {
"Please correct the problem and reload the page.",
),
_t("The message from the parser is: %(message)s", {
message: error.err.message || _t("Invalid JSON"),
message: error.message || _t("Invalid JSON"),
}),
]);
}
@ -231,7 +232,7 @@ async function start(): Promise<void> {
// Like the compatibility page, AWOOOOOGA at the user
// This uses the default brand since the app config is unavailable.
await showError(_t("Your Element is misconfigured"), [
err.translatedMessage || _t("Unexpected error preparing the app. See console for details."),
extractErrorMessageFromError(err, _t("Unexpected error preparing the app. See console for details.")),
]);
}
}
@ -240,7 +241,7 @@ start().catch((err) => {
logger.error(err);
// show the static error in an iframe to not lose any context / console data
// with some basic styling to make the iframe full page
delete document.body.style.height;
document.body.style.removeProperty("height");
const iframe = document.createElement("iframe");
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - typescript seems to only like the IE syntax for iframe sandboxing
@ -254,5 +255,5 @@ start().catch((err) => {
iframe.style.right = "0";
iframe.style.bottom = "0";
iframe.style.border = "0";
document.getElementById("matrixchat").appendChild(iframe);
document.getElementById("matrixchat")?.appendChild(iframe);
});

View File

@ -67,11 +67,11 @@ export async function loadConfig(): Promise<void> {
// granular settings are loaded correctly and to avoid duplicating the override logic for the theme.
//
// Note: this isn't called twice for some wrappers, like the Jitsi wrapper.
const platformConfig = await PlatformPeg.get().getConfig();
const platformConfig = await PlatformPeg.get()?.getConfig();
if (platformConfig) {
SdkConfig.put(platformConfig);
} else {
SdkConfig.unset(); // clears the config (sets to empty object)
SdkConfig.reset();
}
}
@ -119,7 +119,7 @@ export function loadOlm(): Promise<void> {
export async function loadLanguage(): Promise<void> {
const prefLang = SettingsStore.getValue("language", null, /*excludeDefault=*/ true);
let langs = [];
let langs: string[] = [];
if (!prefLang) {
languageHandler.getLanguagesFromBrowser().forEach((l) => {
@ -163,7 +163,7 @@ export async function showError(title: string, messages?: string[]): Promise<voi
);
}
export async function showIncompatibleBrowser(onAccept): Promise<void> {
export async function showIncompatibleBrowser(onAccept: () => void): Promise<void> {
const CompatibilityView = (
await import(
/* webpackChunkName: "compatibility-view" */

View File

@ -30,6 +30,14 @@ import { IConfigOptions } from "matrix-react-sdk/src/IConfigOptions";
import { SnakedObject } from "matrix-react-sdk/src/utils/SnakedObject";
import { ElementWidgetCapabilities } from "matrix-react-sdk/src/stores/widgets/ElementWidgetCapabilities";
import type {
JitsiMeetExternalAPIConstructor,
ExternalAPIEventCallbacks,
JitsiMeetExternalAPI as _JitsiMeetExternalAPI,
AudioMuteStatusChangedEvent,
LogEvent,
VideoMuteStatusChangedEvent,
} from "jitsi-meet";
import { getVectorConfig } from "../getconfig";
// We have to trick webpack into loading our CSS for us.
@ -40,7 +48,7 @@ const JITSI_OPENIDTOKEN_JWT_AUTH = "openidtoken-jwt";
// Dev note: we use raw JS without many dependencies to reduce bundle size.
// We do not need all of React to render a Jitsi conference.
declare let JitsiMeetExternalAPI: any;
declare let JitsiMeetExternalAPI: JitsiMeetExternalAPIConstructor;
let inConference = false;
@ -52,15 +60,16 @@ let avatarUrl: string;
let userId: string;
let jitsiAuth: string;
let roomId: string;
let openIdToken: IOpenIDCredentials;
let roomName: string;
let startAudioOnly: boolean;
let startWithAudioMuted: boolean | undefined;
let startWithVideoMuted: boolean | undefined;
let isVideoChannel: boolean;
let supportsScreensharing: boolean;
let language: string;
let widgetApi: WidgetApi;
let meetApi: any; // JitsiMeetExternalAPI
let widgetApi: WidgetApi | undefined;
let meetApi: _JitsiMeetExternalAPI | undefined;
let skipOurWelcomeScreen = false;
const setupCompleted = (async (): Promise<string | void> => {
@ -80,6 +89,13 @@ const setupCompleted = (async (): Promise<string | void> => {
}
return vals[0];
};
const parseBooleanOrUndefined = (value: string | undefined): boolean | undefined => {
if (value === undefined) {
return undefined;
}
return value === "true";
};
// If we have these params, expect a widget API to be available (ie. to be in an iframe
// inside a matrix client). Otherwise, assume we're on our own, eg. have been popped
@ -94,12 +110,12 @@ const setupCompleted = (async (): Promise<string | void> => {
}
// Set this up as early as possible because Element will be hitting it almost immediately.
let widgetApiReady: Promise<void>;
let widgetApiReady: Promise<void> | undefined;
if (parentUrl && widgetId) {
const parentOrigin = new URL(qsParam("parentUrl")).origin;
widgetApi = new WidgetApi(qsParam("widgetId"), parentOrigin);
widgetApiReady = new Promise<void>((resolve) => widgetApi.once("ready", resolve));
widgetApiReady = new Promise<void>((resolve) => widgetApi!.once("ready", resolve));
widgetApi.requestCapabilities(VideoConferenceCapabilities);
// jitsi cannot work in a popup if auth token is provided because widgetApi is not available there
@ -114,7 +130,7 @@ const setupCompleted = (async (): Promise<string | void> => {
action: WidgetApiAction,
handler: (request: IWidgetApiRequestData) => Promise<void>,
): void => {
widgetApi.on(`action:${action}`, async (ev: CustomEvent<IWidgetApiRequest>) => {
widgetApi!.on(`action:${action}`, async (ev: CustomEvent<IWidgetApiRequest>) => {
ev.preventDefault();
await setupCompleted;
@ -130,7 +146,7 @@ const setupCompleted = (async (): Promise<string | void> => {
}
}
await widgetApi.transport.reply(ev.detail, response);
await widgetApi!.transport.reply(ev.detail, response);
});
};
@ -141,7 +157,7 @@ const setupCompleted = (async (): Promise<string | void> => {
if (force === true) {
meetApi?.dispose();
notifyHangup();
meetApi = null;
meetApi = undefined;
closeConference();
} else {
meetApi?.executeCommand("hangup");
@ -197,14 +213,17 @@ const setupCompleted = (async (): Promise<string | void> => {
roomId = qsParam("roomId", true);
roomName = qsParam("roomName", true);
startAudioOnly = qsParam("isAudioOnly", true) === "true";
startWithAudioMuted = parseBooleanOrUndefined(qsParam("startWithAudioMuted", true));
startWithVideoMuted = parseBooleanOrUndefined(qsParam("startWithVideoMuted", true));
isVideoChannel = qsParam("isVideoChannel", true) === "true";
supportsScreensharing = qsParam("supportsScreensharing", true) === "true";
// We've reached the point where we have to wait for the config, so do that then parse it.
const instanceConfig = new SnakedObject<IConfigOptions>((await configPromise) ?? <IConfigOptions>{});
const jitsiConfig = instanceConfig.get("jitsi_widget") ?? {};
skipOurWelcomeScreen =
new SnakedObject<IConfigOptions["jitsi_widget"]>(jitsiConfig).get("skip_built_in_welcome_screen") ?? false;
const jitsiConfig = instanceConfig.get("jitsi_widget");
if (jitsiConfig) {
skipOurWelcomeScreen = new SnakedObject(jitsiConfig).get("skip_built_in_welcome_screen") ?? false;
}
// Either reveal the prejoin screen, or skip straight to Jitsi depending on the config.
// We don't set up the call yet though as this might lead to failure without the widget API.
@ -212,13 +231,6 @@ const setupCompleted = (async (): Promise<string | void> => {
if (widgetApi) {
await widgetApiReady;
// See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
if (jitsiAuth === JITSI_OPENIDTOKEN_JWT_AUTH) {
// Request credentials, give callback to continue when received
openIdToken = await widgetApi.requestOpenIDConnectToken();
logger.log("Got OpenID Connect token");
}
}
// Now that everything should be set up, skip to the Jitsi splash screen if needed
@ -229,12 +241,12 @@ const setupCompleted = (async (): Promise<string | void> => {
enableJoinButton(); // always enable the button
} catch (e) {
logger.error("Error setting up Jitsi widget", e);
document.getElementById("widgetActionContainer").innerText = "Failed to load Jitsi widget";
document.getElementById("widgetActionContainer")!.innerText = "Failed to load Jitsi widget";
}
})();
function enableJoinButton(): void {
document.getElementById("joinButton").onclick = (): void => joinConference();
document.getElementById("joinButton")!.onclick = (): Promise<void> => joinConference();
}
function switchVisibleContainers(): void {
@ -248,9 +260,9 @@ function switchVisibleContainers(): void {
}
function toggleConferenceVisibility(inConference: boolean): void {
document.getElementById("jitsiContainer").style.visibility = inConference ? "unset" : "hidden";
document.getElementById("jitsiContainer")!.style.visibility = inConference ? "unset" : "hidden";
// Video rooms have a separate UI for joining, so they should never show our join button
document.getElementById("joinButtonContainer").style.visibility =
document.getElementById("joinButtonContainer")!.style.visibility =
inConference || isVideoChannel ? "hidden" : "unset";
}
@ -260,11 +272,11 @@ function skipToJitsiSplashScreen(): void {
}
/**
* Create a JWT token fot jitsi openidtoken-jwt auth
* Create a JWT token for jitsi openidtoken-jwt auth
*
* See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
*/
function createJWTToken(): string {
function createJWTToken(openIdToken: IOpenIDCredentials): string {
// Header
const header = { alg: "HS256", typ: "JWT" };
// Payload
@ -308,7 +320,7 @@ async function notifyHangup(errorMessage?: string): Promise<void> {
function closeConference(): void {
switchVisibleContainers();
document.getElementById("jitsiContainer").innerHTML = "";
document.getElementById("jitsiContainer")!.innerHTML = "";
if (skipOurWelcomeScreen) {
skipToJitsiSplashScreen();
@ -345,17 +357,21 @@ function mapLanguage(language: string): string {
// audio input it can find, while an input of null instructs it to start muted,
// and a non-nullish input specifies the label of a specific device to use.
// Same for video inputs.
function joinConference(audioInput?: string | null, videoInput?: string | null): void {
let jwt;
async function joinConference(audioInput?: string | null, videoInput?: string | null): Promise<void> {
let jwt: string | undefined;
if (jitsiAuth === JITSI_OPENIDTOKEN_JWT_AUTH) {
// See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
const openIdToken = await widgetApi?.requestOpenIDConnectToken();
logger.log("Got OpenID Connect token");
if (!openIdToken?.access_token) {
// eslint-disable-line camelcase
// We've failing to get a token, don't try to init conference
logger.warn("Expected to have an OpenID credential, cannot initialize widget.");
document.getElementById("widgetActionContainer").innerText = "Failed to load Jitsi widget";
document.getElementById("widgetActionContainer")!.innerText = "Failed to load Jitsi widget";
return;
}
jwt = createJWTToken();
jwt = createJWTToken(openIdToken);
}
switchVisibleContainers();
@ -369,7 +385,7 @@ function joinConference(audioInput?: string | null, videoInput?: string | null):
const options = {
width: "100%",
height: "100%",
parentNode: document.querySelector("#jitsiContainer"),
parentNode: document.querySelector("#jitsiContainer") ?? undefined,
roomName: conferenceId,
devices: {
audioInput,
@ -388,8 +404,8 @@ function joinConference(audioInput?: string | null, videoInput?: string | null):
configOverwrite: {
subject: roomName,
startAudioOnly,
startWithAudioMuted: audioInput === null,
startWithVideoMuted: videoInput === null,
startWithAudioMuted: audioInput === null ? true : startWithAudioMuted,
startWithVideoMuted: videoInput === null ? true : startWithVideoMuted,
// Request some log levels for inclusion in rageshakes
// Ideally we would capture all possible log levels, but this can
// cause Jitsi Meet to try to post various circular data structures
@ -427,13 +443,13 @@ function joinConference(audioInput?: string | null, videoInput?: string | null):
// (regardless of video on or off)
meetApi.on("videoConferenceJoined", onVideoConferenceJoined);
meetApi.on("videoConferenceLeft", onVideoConferenceLeft);
meetApi.on("readyToClose", closeConference);
meetApi.on("readyToClose", closeConference as ExternalAPIEventCallbacks["readyToClose"]);
meetApi.on("errorOccurred", onErrorOccurred);
meetApi.on("audioMuteStatusChanged", onAudioMuteStatusChanged);
meetApi.on("videoMuteStatusChanged", onVideoMuteStatusChanged);
["videoConferenceJoined", "participantJoined", "participantLeft"].forEach((event) => {
meetApi.on(event, updateParticipants);
(["videoConferenceJoined", "participantJoined", "participantLeft"] as const).forEach((event) => {
meetApi!.on(event, updateParticipants);
});
// Patch logs into rageshakes
@ -449,9 +465,9 @@ const onVideoConferenceJoined = (): void => {
// We can't just use these commands immediately after creating the
// iframe, because there's *another* bug where they can crash Jitsi by
// racing with its startup process.
if (displayName) meetApi.executeCommand("displayName", displayName);
if (displayName) meetApi?.executeCommand("displayName", displayName);
// This doesn't have a userInfo equivalent, so has to be set via commands
if (avatarUrl) meetApi.executeCommand("avatarUrl", avatarUrl);
if (avatarUrl) meetApi?.executeCommand("avatarUrl", avatarUrl);
if (widgetApi) {
// ignored promise because we don't care if it works
@ -461,30 +477,30 @@ const onVideoConferenceJoined = (): void => {
}
// Video rooms should start in tile mode
if (isVideoChannel) meetApi.executeCommand("setTileView", true);
if (isVideoChannel) meetApi?.executeCommand("setTileView", true);
};
const onVideoConferenceLeft = (): void => {
notifyHangup();
meetApi = null;
meetApi = undefined;
};
const onErrorOccurred = ({ error }): void => {
const onErrorOccurred = ({ error }: Parameters<ExternalAPIEventCallbacks["errorOccurred"]>[0]): void => {
if (error.isFatal) {
// We got disconnected. Since Jitsi Meet might send us back to the
// prejoin screen, we're forced to act as if we hung up entirely.
notifyHangup(error.message);
meetApi = null;
meetApi = undefined;
closeConference();
}
};
const onAudioMuteStatusChanged = ({ muted }): void => {
const onAudioMuteStatusChanged = ({ muted }: AudioMuteStatusChangedEvent): void => {
const action = muted ? ElementWidgetActions.MuteAudio : ElementWidgetActions.UnmuteAudio;
widgetApi?.transport.send(action, {});
};
const onVideoMuteStatusChanged = ({ muted }): void => {
const onVideoMuteStatusChanged = ({ muted }: VideoMuteStatusChangedEvent): void => {
if (muted) {
// Jitsi Meet always sends a "video muted" event directly before
// hanging up, which we need to ignore by padding the timeout here,
@ -500,8 +516,9 @@ const onVideoMuteStatusChanged = ({ muted }): void => {
const updateParticipants = (): void => {
widgetApi?.transport.send(ElementWidgetActions.CallParticipants, {
participants: meetApi.getParticipantsInfo(),
participants: meetApi?.getParticipantsInfo(),
});
};
const onLog = ({ logLevel, args }): void => (parent as unknown as typeof global).mx_rage_logger?.log(logLevel, ...args);
const onLog = ({ logLevel, args }: LogEvent): void =>
(parent as unknown as typeof global).mx_rage_logger?.log(logLevel, ...args);

View File

@ -31,17 +31,17 @@ function renderConfigError(message: string): void {
}
async function initPage(): Promise<void> {
document.getElementById("back_to_element_button").onclick = onBackToElementClick;
document.getElementById("back_to_element_button")!.onclick = onBackToElementClick;
const config = await getVectorConfig("..");
// We manually parse the config similar to how validateServerConfig works because
// calling that function pulls in roughly 4mb of JS we don't use.
const wkConfig = config["default_server_config"]; // overwritten later under some conditions
const serverName = config["default_server_name"];
const defaultHsUrl = config["default_hs_url"];
const defaultIsUrl = config["default_is_url"];
const wkConfig = config?.["default_server_config"]; // overwritten later under some conditions
const serverName = config?.["default_server_name"];
const defaultHsUrl = config?.["default_hs_url"];
const defaultIsUrl = config?.["default_is_url"];
const incompatibleOptions = [wkConfig, serverName, defaultHsUrl].filter((i) => !!i);
if (incompatibleOptions.length > 1) {
@ -54,13 +54,13 @@ async function initPage(): Promise<void> {
return renderConfigError("Invalid configuration: no default server specified.");
}
let hsUrl = "";
let isUrl = "";
let hsUrl: string | undefined;
let isUrl: string | undefined;
if (wkConfig && wkConfig["m.homeserver"]) {
if (typeof wkConfig?.["m.homeserver"]?.["base_url"] === "string") {
hsUrl = wkConfig["m.homeserver"]["base_url"];
if (wkConfig["m.identity_server"]) {
if (typeof wkConfig["m.identity_server"]?.["base_url"] === "string") {
isUrl = wkConfig["m.identity_server"]["base_url"];
}
}
@ -96,17 +96,19 @@ async function initPage(): Promise<void> {
if (isUrl && !isUrl.endsWith("/")) isUrl += "/";
if (hsUrl !== "https://matrix.org/") {
(document.getElementById("configure_element_button") as HTMLAnchorElement).href =
"https://mobile.element.io?hs_url=" + encodeURIComponent(hsUrl) + "&is_url=" + encodeURIComponent(isUrl);
document.getElementById("step1_heading").innerHTML = "1: Install the app";
document.getElementById("step2_container").style.display = "block";
document.getElementById("hs_url").innerText = hsUrl;
let url = "https://mobile.element.io?hs_url=" + encodeURIComponent(hsUrl);
if (isUrl) {
document.getElementById("custom_is").style.display = "block";
document.getElementById("is_url").style.display = "block";
document.getElementById("is_url").innerText = isUrl;
document.getElementById("custom_is")!.style.display = "block";
document.getElementById("is_url")!.style.display = "block";
document.getElementById("is_url")!.innerText = isUrl;
url += "&is_url=" + encodeURIComponent(isUrl ?? "");
}
(document.getElementById("configure_element_button") as HTMLAnchorElement).href = url;
document.getElementById("step1_heading")!.innerHTML = "1: Install the app";
document.getElementById("step2_container")!.style.display = "block";
document.getElementById("hs_url")!.innerText = hsUrl;
}
}

View File

@ -40,11 +40,21 @@ import ToastStore from "matrix-react-sdk/src/stores/ToastStore";
import GenericExpiringToast from "matrix-react-sdk/src/components/views/toasts/GenericExpiringToast";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { BreadcrumbsStore } from "matrix-react-sdk/src/stores/BreadcrumbsStore";
import { UPDATE_EVENT } from "matrix-react-sdk/src/stores/AsyncStore";
import { avatarUrlForRoom, getInitialLetter } from "matrix-react-sdk/src/Avatar";
import VectorBasePlatform from "./VectorBasePlatform";
import { SeshatIndexManager } from "./SeshatIndexManager";
import { IPCManager } from "./IPCManager";
interface SquirrelUpdate {
releaseNotes: string;
releaseName: string;
releaseDate: Date;
updateURL: string;
}
const isMac = navigator.platform.toUpperCase().includes("MAC");
function platformFriendlyName(): string {
@ -69,7 +79,7 @@ function platformFriendlyName(): string {
function onAction(payload: ActionPayload): void {
// Whitelist payload actions, no point sending most across
if (["call_state"].includes(payload.action)) {
window.electron.send("app_onAction", payload);
window.electron!.send("app_onAction", payload);
}
}
@ -95,6 +105,10 @@ export default class ElectronPlatform extends VectorBasePlatform {
public constructor() {
super();
if (!window.electron) {
throw new Error("Cannot instantiate ElectronPlatform, window.electron is not set");
}
dis.register(onAction);
/*
IPC Call `check_updates` returns:
@ -125,12 +139,12 @@ export default class ElectronPlatform extends VectorBasePlatform {
const key = `DOWNLOAD_TOAST_${id}`;
const onAccept = (): void => {
window.electron.send("userDownloadAction", { id, open: true });
window.electron!.send("userDownloadAction", { id, open: true });
ToastStore.sharedInstance().dismissToast(key);
};
const onDismiss = (): void => {
window.electron.send("userDownloadAction", { id });
window.electron!.send("userDownloadAction", { id });
};
ToastStore.sharedInstance().addOrReplaceToast({
@ -150,13 +164,29 @@ export default class ElectronPlatform extends VectorBasePlatform {
});
this.ipc.call("startSSOFlow", this.ssoID);
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
}
public async getConfig(): Promise<IConfigOptions> {
public async getConfig(): Promise<IConfigOptions | undefined> {
return this.ipc.call("getConfig");
}
private onUpdateDownloaded = async (ev, { releaseNotes, releaseName }): Promise<void> => {
private onBreadcrumbsUpdate = (): void => {
const rooms = BreadcrumbsStore.instance.rooms.slice(0, 7).map((r) => ({
roomId: r.roomId,
avatarUrl: avatarUrlForRoom(
r,
Math.floor(60 * window.devicePixelRatio),
Math.floor(60 * window.devicePixelRatio),
"crop",
),
initial: getInitialLetter(r.name),
}));
this.ipc.call("breadcrumbs", rooms);
};
private onUpdateDownloaded = async (ev: Event, { releaseNotes, releaseName }: SquirrelUpdate): Promise<void> => {
dis.dispatch<CheckUpdatesPayload>({
action: Action.CheckUpdates,
status: UpdateCheckStatus.Ready,
@ -186,7 +216,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
if (this.notificationCount === count) return;
super.setNotificationCount(count);
window.electron.send("setBadgeCount", count);
window.electron!.send("setBadgeCount", count);
}
public supportsNotifications(): boolean {
@ -226,7 +256,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
}
public loudNotification(ev: MatrixEvent, room: Room): void {
window.electron.send("loudNotification");
window.electron!.send("loudNotification");
}
public needsUrlTooltips(): boolean {
@ -262,14 +292,14 @@ export default class ElectronPlatform extends VectorBasePlatform {
public startUpdateCheck(): void {
super.startUpdateCheck();
window.electron.send("check_updates");
window.electron!.send("check_updates");
}
public installUpdate(): void {
// IPC to the main process to install the update, since quitAndInstall
// doesn't fire the before-quit event so the main process needs to know
// it should exit.
window.electron.send("install_update");
window.electron!.send("install_update");
}
public getDefaultDeviceDisplayName(): string {
@ -389,4 +419,11 @@ export default class ElectronPlatform extends VectorBasePlatform {
await this.ipc.call("destroyPickleKey", userId, deviceId);
} catch (e) {}
}
public async clearStorage(): Promise<void> {
try {
await super.clearStorage();
await this.ipc.call("clearStorage");
} catch (e) {}
}
}

View File

@ -33,6 +33,9 @@ export class IPCManager {
private readonly sendChannel: ElectronChannel = "ipcCall",
private readonly recvChannel: ElectronChannel = "ipcReply",
) {
if (!window.electron) {
throw new Error("Cannot instantiate ElectronPlatform, window.electron is not set");
}
window.electron.on(this.recvChannel, this.onIpcReply);
}
@ -42,7 +45,7 @@ export class IPCManager {
const deferred = defer<any>();
this.pendingIpcCalls[ipcCallId] = deferred;
// Maybe add a timeout to these? Probably not necessary.
window.electron.send(this.sendChannel, { id: ipcCallId, name, args });
window.electron!.send(this.sendChannel, { id: ipcCallId, name, args });
return deferred.promise;
}

View File

@ -19,6 +19,7 @@ import BaseEventIndexManager, {
IEventAndProfile,
IIndexStats,
ISearchArgs,
ILoadArgs,
} from "matrix-react-sdk/src/indexing/BaseEventIndexManager";
import { IMatrixProfile, IEventWithRoomId as IMatrixEvent, IResultRoomEvents } from "matrix-js-sdk/src/@types/search";
@ -75,7 +76,7 @@ export class SeshatIndexManager extends BaseEventIndexManager {
return this.ipc.call("removeCrawlerCheckpoint", checkpoint);
}
public async loadFileEvents(args): Promise<IEventAndProfile[]> {
public async loadFileEvents(args: ILoadArgs): Promise<IEventAndProfile[]> {
return this.ipc.call("loadFileEvents", args);
}

View File

@ -28,9 +28,9 @@ import Favicon from "../../favicon";
* Vector-specific extensions to the BasePlatform template
*/
export default abstract class VectorBasePlatform extends BasePlatform {
protected _favicon: Favicon;
protected _favicon?: Favicon;
public async getConfig(): Promise<IConfigOptions> {
public async getConfig(): Promise<IConfigOptions | undefined> {
return getVectorConfig();
}

View File

@ -40,6 +40,8 @@ function getNormalizedAppVersion(version: string): string {
}
export default class WebPlatform extends VectorBasePlatform {
private static readonly VERSION = process.env.VERSION!; // baked in by Webpack
public constructor() {
super();
// Register service worker if available on this platform
@ -101,7 +103,7 @@ export default class WebPlatform extends VectorBasePlatform {
}
public getAppVersion(): Promise<string> {
return Promise.resolve(getNormalizedAppVersion(process.env.VERSION));
return Promise.resolve(getNormalizedAppVersion(WebPlatform.VERSION));
}
public startUpdater(): void {
@ -113,7 +115,7 @@ export default class WebPlatform extends VectorBasePlatform {
//
// Ideally, loading an old copy would be impossible with the
// cache-control: nocache HTTP header set, but Firefox doesn't always obey it :/
console.log("startUpdater, current version is " + getNormalizedAppVersion(process.env.VERSION));
console.log("startUpdater, current version is " + getNormalizedAppVersion(WebPlatform.VERSION));
this.pollForUpdate((version: string, newVersion: string) => {
const query = parseQs(location);
if (query.updated) {
@ -144,7 +146,7 @@ export default class WebPlatform extends VectorBasePlatform {
): Promise<UpdateStatus> => {
return this.getMostRecentVersion().then(
(mostRecentVersion) => {
const currentVersion = getNormalizedAppVersion(process.env.VERSION);
const currentVersion = getNormalizedAppVersion(WebPlatform.VERSION);
if (currentVersion !== mostRecentVersion) {
if (this.shouldShowUpdate(mostRecentVersion)) {

View File

@ -22,7 +22,7 @@ import MatrixChatType from "matrix-react-sdk/src/components/structures/MatrixCha
import { parseQsFromFragment } from "./url_utils";
let lastLocationHashSet: string = null;
let lastLocationHashSet: string | null = null;
export function getScreenFromLocation(location: Location): { screen: string; params: QueryDict } {
const fragparts = parseQsFromFragment(location);

View File

@ -25,10 +25,11 @@ import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
import MatrixChat from "matrix-react-sdk/src/components/structures/MatrixChat";
import dis from "matrix-react-sdk/src/dispatcher/dispatcher";
import MockHttpBackend from "matrix-mock-request";
import { makeType } from "matrix-react-sdk/src/utils/TypeUtils";
import { ValidatedServerConfig } from "matrix-react-sdk/src/utils/ValidatedServerConfig";
import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
import { QueryDict, sleep } from "matrix-js-sdk/src/utils";
import { IConfigOptions } from "matrix-react-sdk/src/IConfigOptions";
import { ActionPayload } from "matrix-react-sdk/src/dispatcher/payloads";
import "../jest-mocks";
import WebPlatform from "../../src/vector/platform/WebPlatform";
@ -39,29 +40,24 @@ const DEFAULT_HS_URL = "http://my_server";
const DEFAULT_IS_URL = "http://my_is";
describe("loading:", function () {
let parentDiv;
let httpBackend;
let httpBackend: MockHttpBackend;
// an Object simulating the window.location
let windowLocation;
let windowLocation: Location | undefined;
// the mounted MatrixChat
let matrixChat: RenderResult;
let matrixChat: RenderResult | undefined;
// a promise which resolves when the MatrixChat calls onTokenLoginCompleted
let tokenLoginCompletePromise;
let tokenLoginCompletePromise: Promise<void> | undefined;
beforeEach(function () {
httpBackend = new MockHttpBackend();
// @ts-ignore
window.fetch = httpBackend.fetchFn;
parentDiv = document.createElement("div");
// uncomment this to actually add the div to the UI, to help with
// debugging (but slow things down)
// document.body.appendChild(parentDiv);
windowLocation = null;
matrixChat = null;
windowLocation = undefined;
matrixChat = undefined;
});
afterEach(async function () {
@ -81,8 +77,13 @@ describe("loading:", function () {
* TODO: it would be nice to factor some of this stuff out of index.js so
* that we can test it rather than our own implementation of it.
*/
function loadApp(opts?): void {
opts = opts || {};
function loadApp(
opts: {
queryString?: string;
uriFragment?: string;
config?: IConfigOptions;
} = {},
): void {
const queryString = opts.queryString || "";
const uriFragment = opts.uriFragment || "";
@ -92,18 +93,18 @@ describe("loading:", function () {
toString: function (): string {
return this.search + this.hash;
},
};
} as Location;
function onNewScreen(screen): void {
function onNewScreen(screen: string): void {
console.log(Date.now() + " newscreen " + screen);
const hash = "#/" + screen;
windowLocation.hash = hash;
windowLocation!.hash = hash;
console.log(Date.now() + " browser URI now " + windowLocation);
}
// Parse the given window.location and return parameters that can be used when calling
// MatrixChat.showScreen(screen, params)
function getScreenFromLocation(location): { screen: string; params: QueryDict } {
function getScreenFromLocation(location: Location): { screen: string; params: QueryDict } {
const fragparts = parseQsFromFragment(location);
return {
screen: fragparts.location.substring(1),
@ -113,22 +114,20 @@ describe("loading:", function () {
const fragParts = parseQsFromFragment(windowLocation);
const config = Object.assign(
{
default_hs_url: DEFAULT_HS_URL,
default_is_url: DEFAULT_IS_URL,
validated_server_config: makeType(ValidatedServerConfig, {
hsUrl: DEFAULT_HS_URL,
hsName: "TEST_ENVIRONMENT",
hsNameIsDifferent: false, // yes, we lie
isUrl: DEFAULT_IS_URL,
}),
embeddedPages: {
homeUrl: "data:text/html;charset=utf-8;base64,PGh0bWw+PC9odG1sPg==",
},
const config = {
default_hs_url: DEFAULT_HS_URL,
default_is_url: DEFAULT_IS_URL,
validated_server_config: {
hsUrl: DEFAULT_HS_URL,
hsName: "TEST_ENVIRONMENT",
hsNameIsDifferent: false, // yes, we lie
isUrl: DEFAULT_IS_URL,
} as ValidatedServerConfig,
embedded_pages: {
home_url: "data:text/html;charset=utf-8;base64,PGh0bWw+PC9odG1sPg==",
},
opts.config || {},
);
...(opts.config ?? {}),
} as IConfigOptions;
PlatformPeg.set(new WebPlatform());
@ -138,18 +137,16 @@ describe("loading:", function () {
matrixChat = render(
<MatrixChat
onNewScreen={onNewScreen}
config={config}
serverConfig={config.validated_server_config}
config={config!}
realQueryParams={params}
startingFragmentQueryParams={fragParts.params}
enableGuest={true}
onTokenLoginCompleted={resolve}
initialScreenAfterLogin={getScreenFromLocation(windowLocation)}
initialScreenAfterLogin={getScreenFromLocation(windowLocation!)}
makeRegistrationUrl={(): string => {
throw new Error("Not implemented");
}}
/>,
parentDiv,
);
});
}
@ -158,15 +155,15 @@ describe("loading:", function () {
// http requests until we do.
//
// returns a promise resolving to the received request
async function expectAndAwaitSync(opts?): Promise<any> {
let syncRequest = null;
async function expectAndAwaitSync(opts?: { isGuest?: boolean }): Promise<any> {
let syncRequest: (typeof MockHttpBackend.prototype.requests)[number] | null = null;
httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
versions: ["r0.3.0"],
unstable_features: {
"m.lazy_load_members": true,
},
});
const isGuest = opts && opts.isGuest;
const isGuest = opts?.isGuest;
if (!isGuest) {
// the call to create the LL filter
httpBackend.when("POST", "/filter").respond(200, { filter_id: "llfid" });
@ -184,7 +181,7 @@ describe("loading:", function () {
if (syncRequest) {
return syncRequest;
}
await httpBackend.flush();
await httpBackend.flush(undefined);
}
throw new Error("Gave up waiting for /sync");
}
@ -202,18 +199,18 @@ describe("loading:", function () {
httpBackend
.when("POST", "/register")
.check(function (req) {
expect(req.queryParams.kind).toEqual("guest");
expect(req.queryParams?.kind).toEqual("guest");
})
.respond(403, "Guest access is disabled");
return httpBackend.flush();
return httpBackend.flush(undefined);
})
.then(() => {
// Wait for another trip around the event loop for the UI to update
return awaitWelcomeComponent(matrixChat);
})
.then(() => {
return waitFor(() => expect(windowLocation.hash).toEqual("#/welcome"));
return waitFor(() => expect(windowLocation?.hash).toEqual("#/welcome"));
});
});
@ -235,11 +232,11 @@ describe("loading:", function () {
httpBackend
.when("POST", "/register")
.check(function (req) {
expect(req.queryParams.kind).toEqual("guest");
expect(req.queryParams?.kind).toEqual("guest");
})
.respond(403, "Guest access is disabled");
return httpBackend.flush();
return httpBackend.flush(undefined);
})
.then(() => {
// Wait for another trip around the event loop for the UI to update
@ -249,7 +246,7 @@ describe("loading:", function () {
return moveFromWelcomeToLogin(matrixChat);
})
.then(() => {
return completeLogin(matrixChat);
return completeLogin(matrixChat!);
})
.then(() => {
// once the sync completes, we should have a room view
@ -257,7 +254,7 @@ describe("loading:", function () {
})
.then(() => {
httpBackend.verifyNoOutstandingExpectation();
expect(windowLocation.hash).toEqual("#/room/!room:id");
expect(windowLocation?.hash).toEqual("#/room/!room:id");
// and the localstorage should have been updated
expect(localStorage.getItem("mx_user_id")).toEqual("@user:id");
@ -294,11 +291,11 @@ describe("loading:", function () {
throw new Error(`Unexpected HTTP request to ${req}`);
}
return completeLogin(matrixChat);
return completeLogin(matrixChat!);
})
.then(() => {
expect(matrixChat.container.querySelector(".mx_HomePage")).toBeTruthy();
expect(windowLocation.hash).toEqual("#/home");
expect(matrixChat?.container.querySelector(".mx_HomePage")).toBeTruthy();
expect(windowLocation?.hash).toEqual("#/home");
});
});
});
@ -319,7 +316,7 @@ describe("loading:", function () {
it("shows the last known room by default", function () {
loadApp();
return awaitLoggedIn(matrixChat)
return awaitLoggedIn(matrixChat!)
.then(() => {
// we are logged in - let the sync complete
return expectAndAwaitSync();
@ -330,7 +327,7 @@ describe("loading:", function () {
})
.then(() => {
httpBackend.verifyNoOutstandingExpectation();
expect(windowLocation.hash).toEqual("#/room/!last_room:id");
expect(windowLocation?.hash).toEqual("#/room/!last_room:id");
});
});
@ -339,7 +336,7 @@ describe("loading:", function () {
loadApp();
return awaitLoggedIn(matrixChat)
return awaitLoggedIn(matrixChat!)
.then(() => {
// we are logged in - let the sync complete
return expectAndAwaitSync();
@ -347,8 +344,8 @@ describe("loading:", function () {
.then(() => {
// once the sync completes, we should have a home page
httpBackend.verifyNoOutstandingExpectation();
expect(matrixChat.container.querySelector(".mx_HomePage")).toBeTruthy();
expect(windowLocation.hash).toEqual("#/home");
expect(matrixChat?.container.querySelector(".mx_HomePage")).toBeTruthy();
expect(windowLocation?.hash).toEqual("#/home");
});
});
@ -357,7 +354,7 @@ describe("loading:", function () {
uriFragment: "#/room/!room:id",
});
return awaitLoggedIn(matrixChat)
return awaitLoggedIn(matrixChat!)
.then(() => {
// we are logged in - let the sync complete
return expectAndAwaitSync();
@ -368,7 +365,7 @@ describe("loading:", function () {
})
.then(() => {
httpBackend.verifyNoOutstandingExpectation();
expect(windowLocation.hash).toEqual("#/room/!room:id");
expect(windowLocation?.hash).toEqual("#/room/!room:id");
});
});
@ -404,17 +401,17 @@ describe("loading:", function () {
httpBackend
.when("POST", "/register")
.check(function (req) {
expect(req.queryParams.kind).toEqual("guest");
expect(req.queryParams?.kind).toEqual("guest");
})
.respond(200, {
user_id: "@guest:localhost",
access_token: "secret_token",
});
return httpBackend.flush();
return httpBackend.flush(undefined);
})
.then(() => {
return awaitLoggedIn(matrixChat);
return awaitLoggedIn(matrixChat!);
})
.then(() => {
// we are logged in - let the sync complete
@ -423,8 +420,8 @@ describe("loading:", function () {
.then(() => {
// once the sync completes, we should have a welcome page
httpBackend.verifyNoOutstandingExpectation();
expect(matrixChat.container.querySelector(".mx_Welcome")).toBeTruthy();
expect(windowLocation.hash).toEqual("#/welcome");
expect(matrixChat?.container.querySelector(".mx_Welcome")).toBeTruthy();
expect(windowLocation?.hash).toEqual("#/welcome");
});
});
@ -441,17 +438,17 @@ describe("loading:", function () {
.when("POST", "/register")
.check(function (req) {
expect(req.path.startsWith(DEFAULT_HS_URL)).toBe(true);
expect(req.queryParams.kind).toEqual("guest");
expect(req.queryParams?.kind).toEqual("guest");
})
.respond(200, {
user_id: "@guest:localhost",
access_token: "secret_token",
});
return httpBackend.flush();
return httpBackend.flush(undefined);
})
.then(() => {
return awaitLoggedIn(matrixChat);
return awaitLoggedIn(matrixChat!);
})
.then(() => {
return expectAndAwaitSync({ isGuest: true });
@ -461,8 +458,8 @@ describe("loading:", function () {
// once the sync completes, we should have a welcome page
httpBackend.verifyNoOutstandingExpectation();
expect(matrixChat.container.querySelector(".mx_Welcome")).toBeTruthy();
expect(windowLocation.hash).toEqual("#/welcome");
expect(matrixChat?.container.querySelector(".mx_Welcome")).toBeTruthy();
expect(windowLocation?.hash).toEqual("#/welcome");
expect(MatrixClientPeg.get().baseUrl).toEqual(DEFAULT_HS_URL);
expect(MatrixClientPeg.get().idBaseUrl).toEqual(DEFAULT_IS_URL);
});
@ -481,17 +478,17 @@ describe("loading:", function () {
httpBackend
.when("POST", "/register")
.check(function (req) {
expect(req.queryParams.kind).toEqual("guest");
expect(req.queryParams?.kind).toEqual("guest");
})
.respond(200, {
user_id: "@guest:localhost",
access_token: "secret_token",
});
return httpBackend.flush();
return httpBackend.flush(undefined);
})
.then(() => {
return awaitLoggedIn(matrixChat);
return awaitLoggedIn(matrixChat!);
})
.then(() => {
return expectAndAwaitSync({ isGuest: true });
@ -502,7 +499,7 @@ describe("loading:", function () {
})
.then(() => {
httpBackend.verifyNoOutstandingExpectation();
expect(windowLocation.hash).toEqual("#/room/!room:id");
expect(windowLocation?.hash).toEqual("#/room/!room:id");
});
});
@ -514,7 +511,7 @@ describe("loading:", function () {
httpBackend
.when("POST", "/register")
.check(function (req) {
expect(req.queryParams.kind).toEqual("guest");
expect(req.queryParams?.kind).toEqual("guest");
})
.respond(200, {
user_id: "@guest:localhost",
@ -522,9 +519,9 @@ describe("loading:", function () {
});
return httpBackend
.flush()
.flush(undefined)
.then(() => {
return awaitLoggedIn(matrixChat);
return awaitLoggedIn(matrixChat!);
})
.then(() => {
// we got a sync spinner - let the sync complete
@ -532,7 +529,7 @@ describe("loading:", function () {
})
.then(async () => {
// once the sync completes, we should have a home page
await waitFor(() => matrixChat.container.querySelector(".mx_HomePage"));
await waitFor(() => matrixChat?.container.querySelector(".mx_HomePage"));
// we simulate a click on the 'login' button by firing off
// the relevant dispatch.
@ -554,7 +551,7 @@ describe("loading:", function () {
await screen.findByRole("main");
screen.getAllByText("Sign in");
expect(windowLocation.hash).toEqual("#/login");
expect(windowLocation?.hash).toEqual("#/login");
});
});
});
@ -585,7 +582,7 @@ describe("loading:", function () {
access_token: "access_token",
});
return httpBackend.flush();
return httpBackend.flush(undefined);
})
.then(() => {
// at this point, MatrixChat should fire onTokenLoginCompleted, which
@ -612,11 +609,7 @@ describe("loading:", function () {
// When we switch to the login component, it'll hit the login endpoint
// for proof of life and to get flows. We'll only give it one option.
httpBackend.when("GET", "/login").respond(200, { flows: [{ type: "m.login.password" }] });
httpBackend.flush(); // We already would have tried the GET /login request
// Give the component some time to finish processing the login flows before
// continuing.
await sleep(100);
httpBackend.flush(undefined); // We already would have tried the GET /login request
httpBackend
.when("POST", "/login")
@ -631,12 +624,17 @@ describe("loading:", function () {
device_id: "DEVICE_ID",
access_token: "access_token",
});
fireEvent.change(matrixChat.container.querySelector("#mx_LoginForm_username"), { target: { value: "user" } });
fireEvent.change(matrixChat.container.querySelector("#mx_LoginForm_password"), { target: { value: "pass" } });
// Give the component some time to finish processing the login flows before continuing.
await waitFor(() => expect(matrixChat?.container.querySelector("#mx_LoginForm_username")).toBeTruthy());
// Enter login details
fireEvent.change(matrixChat.container.querySelector("#mx_LoginForm_username")!, { target: { value: "user" } });
fireEvent.change(matrixChat.container.querySelector("#mx_LoginForm_password")!, { target: { value: "pass" } });
fireEvent.click(screen.getByText("Sign in", { selector: ".mx_Login_submit" }));
return httpBackend
.flush()
.flush(undefined)
.then(() => {
// Wait for another trip around the event loop for the UI to update
return sleep(1);
@ -661,7 +659,7 @@ async function awaitLoggedIn(matrixChat: RenderResult): Promise<void> {
if (matrixChat.container.querySelector(".mx_MatrixChat_wrapper")) return; // already logged in
return new Promise((resolve) => {
const onAction = ({ action }): void => {
const onAction = ({ action }: ActionPayload): void => {
if (action !== "on_logged_in") {
return;
}
@ -674,19 +672,19 @@ async function awaitLoggedIn(matrixChat: RenderResult): Promise<void> {
});
}
async function awaitRoomView(matrixChat: RenderResult): Promise<void> {
await waitFor(() => matrixChat.container.querySelector(".mx_RoomView"));
async function awaitRoomView(matrixChat?: RenderResult): Promise<void> {
await waitFor(() => matrixChat?.container.querySelector(".mx_RoomView"));
}
async function awaitLoginComponent(matrixChat: RenderResult): Promise<void> {
await waitFor(() => matrixChat.container.querySelector(".mx_AuthPage"));
async function awaitLoginComponent(matrixChat?: RenderResult): Promise<void> {
await waitFor(() => matrixChat?.container.querySelector(".mx_AuthPage"));
}
async function awaitWelcomeComponent(matrixChat: RenderResult): Promise<void> {
await waitFor(() => matrixChat.container.querySelector(".mx_Welcome"));
async function awaitWelcomeComponent(matrixChat?: RenderResult): Promise<void> {
await waitFor(() => matrixChat?.container.querySelector(".mx_Welcome"));
}
function moveFromWelcomeToLogin(matrixChat: RenderResult): Promise<void> {
function moveFromWelcomeToLogin(matrixChat?: RenderResult): Promise<void> {
dis.dispatch({ action: "start_login" });
return awaitLoginComponent(matrixChat);
}

View File

@ -29,7 +29,7 @@ describe("Favicon", () => {
it("should create a link element if one doesn't yet exist", () => {
const favicon = new Favicon();
expect(favicon).toBeTruthy();
const link = window.document.querySelector("link");
const link = window.document.querySelector("link")!;
expect(link.rel).toContain("icon");
});

View File

@ -21,7 +21,6 @@ import { getVectorConfig } from "../../../src/vector/getconfig";
fetchMock.config.overwriteRoutes = true;
describe("getVectorConfig()", () => {
const prevDocumentDomain = document.domain;
const elementDomain = "app.element.io";
const now = 1234567890;
const specificConfig = {
@ -32,7 +31,10 @@ describe("getVectorConfig()", () => {
};
beforeEach(() => {
document.domain = elementDomain;
Object.defineProperty(window, "location", {
value: { href: `https://${elementDomain}`, hostname: elementDomain },
writable: true,
});
// stable value for cachebuster
jest.spyOn(Date, "now").mockReturnValue(now);
@ -41,7 +43,6 @@ describe("getVectorConfig()", () => {
});
afterAll(() => {
document.domain = prevDocumentDomain;
jest.spyOn(Date, "now").mockRestore();
});
@ -107,6 +108,6 @@ describe("getVectorConfig()", () => {
// We can't assert it'll be a SyntaxError as node-fetch behaves differently
// https://github.com/wheresrhys/fetch-mock/issues/270
await expect(getVectorConfig()).rejects.toThrow("Unexpected token } in JSON at position 19");
await expect(getVectorConfig()).rejects.toThrow("in JSON at position 19");
});
});

View File

@ -20,6 +20,7 @@ import { UpdateCheckStatus } from "matrix-react-sdk/src/BasePlatform";
import { Action } from "matrix-react-sdk/src/dispatcher/actions";
import dispatcher from "matrix-react-sdk/src/dispatcher/dispatcher";
import * as rageshake from "matrix-react-sdk/src/rageshake/rageshake";
import { BreadcrumbsStore } from "matrix-react-sdk/src/stores/BreadcrumbsStore";
import ElectronPlatform from "../../../../src/vector/platform/ElectronPlatform";
@ -43,12 +44,10 @@ describe("ElectronPlatform", () => {
const userId = "@alice:server.org";
const deviceId = "device-id";
window.electron = mockElectron;
beforeEach(() => {
window.electron = mockElectron;
jest.clearAllMocks();
delete window.navigator;
window.navigator = { userAgent: defaultUserAgent } as unknown as Navigator;
Object.defineProperty(window, "navigator", { value: { userAgent: defaultUserAgent }, writable: true });
});
const getElectronEventHandlerCall = (eventType: string): [type: string, handler: Function] | undefined =>
@ -56,7 +55,7 @@ describe("ElectronPlatform", () => {
it("flushes rageshake before quitting", () => {
new ElectronPlatform();
const [event, handler] = getElectronEventHandlerCall("before-quit");
const [event, handler] = getElectronEventHandlerCall("before-quit")!;
// correct event bound
expect(event).toBeTruthy();
@ -68,7 +67,7 @@ describe("ElectronPlatform", () => {
it("dispatches view settings action on preferences event", () => {
new ElectronPlatform();
const [event, handler] = getElectronEventHandlerCall("preferences");
const [event, handler] = getElectronEventHandlerCall("preferences")!;
// correct event bound
expect(event).toBeTruthy();
@ -80,7 +79,7 @@ describe("ElectronPlatform", () => {
describe("updates", () => {
it("dispatches on check updates action", () => {
new ElectronPlatform();
const [event, handler] = getElectronEventHandlerCall("check_updates");
const [event, handler] = getElectronEventHandlerCall("check_updates")!;
// correct event bound
expect(event).toBeTruthy();
@ -93,7 +92,7 @@ describe("ElectronPlatform", () => {
it("dispatches on check updates action when update not available", () => {
new ElectronPlatform();
const [, handler] = getElectronEventHandlerCall("check_updates");
const [, handler] = getElectronEventHandlerCall("check_updates")!;
handler({}, false);
expect(dispatchSpy).toHaveBeenCalledWith({
@ -138,8 +137,7 @@ describe("ElectronPlatform", () => {
["Mozilla/5.0 (X11; SunOS i686; rv:21.0) Gecko/20100101 Firefox/21.0", "Element Desktop: SunOS"],
["custom user agent", "Element Desktop: Unknown"],
])("%s = %s", (userAgent, result) => {
delete window.navigator;
window.navigator = { userAgent } as unknown as Navigator;
Object.defineProperty(window, "navigator", { value: { userAgent }, writable: true });
const platform = new ElectronPlatform();
expect(platform.getDefaultDeviceDisplayName()).toEqual(result);
});
@ -260,4 +258,20 @@ describe("ElectronPlatform", () => {
expect(mockElectron.send).toHaveBeenCalledWith("install_update");
});
});
describe("breacrumbs", () => {
it("should send breadcrumb updates over the IPC", () => {
const spy = jest.spyOn(BreadcrumbsStore.instance, "on");
new ElectronPlatform();
const cb = spy.mock.calls[0][1];
cb();
expect(mockElectron.send).toHaveBeenCalledWith(
"ipcCall",
expect.objectContaining({
name: "breadcrumbs",
}),
);
});
});
});

View File

@ -33,7 +33,6 @@ describe("WebPlatform", () => {
});
it("registers service worker", () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - mocking readonly object
navigator.serviceWorker = { register: jest.fn() };
new WebPlatform();
@ -41,10 +40,7 @@ describe("WebPlatform", () => {
});
it("should call reload on window location object", () => {
delete window.location;
window.location = {
reload: jest.fn(),
} as unknown as Location;
Object.defineProperty(window, "location", { value: { reload: jest.fn() }, writable: true });
const platform = new WebPlatform();
expect(window.location.reload).not.toHaveBeenCalled();
@ -53,10 +49,7 @@ describe("WebPlatform", () => {
});
it("should call reload to install update", () => {
delete window.location;
window.location = {
reload: jest.fn(),
} as unknown as Location;
Object.defineProperty(window, "location", { value: { reload: jest.fn() }, writable: true });
const platform = new WebPlatform();
expect(window.location.reload).not.toHaveBeenCalled();
@ -73,10 +66,8 @@ describe("WebPlatform", () => {
"develop.element.io: Chrome on macOS",
],
])("%s & %s = %s", (url, userAgent, result) => {
delete window.navigator;
window.navigator = { userAgent } as unknown as Navigator;
delete window.location;
window.location = { href: url } as unknown as Location;
Object.defineProperty(window, "navigator", { value: { userAgent }, writable: true });
Object.defineProperty(window, "location", { value: { href: url }, writable: true });
const platform = new WebPlatform();
expect(platform.getDefaultDeviceDisplayName()).toEqual(result);
});
@ -88,14 +79,12 @@ describe("WebPlatform", () => {
permission: "notGranted",
};
beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.Notification = mockNotification;
mockNotification.permission = "notGranted";
});
it("supportsNotifications returns false when platform does not support notifications", () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.Notification = undefined;
expect(new WebPlatform().supportsNotifications()).toBe(false);
@ -132,7 +121,8 @@ describe("WebPlatform", () => {
});
afterAll(() => {
process.env.VERSION = envVersion;
// @ts-ignore
WebPlatform.VERSION = envVersion;
});
it("should return true from canSelfUpdate()", async () => {
@ -142,18 +132,21 @@ describe("WebPlatform", () => {
});
it("getAppVersion returns normalized app version", async () => {
process.env.VERSION = prodVersion;
// @ts-ignore
WebPlatform.VERSION = prodVersion;
const platform = new WebPlatform();
const version = await platform.getAppVersion();
expect(version).toEqual(prodVersion);
process.env.VERSION = `v${prodVersion}`;
// @ts-ignore
WebPlatform.VERSION = `v${prodVersion}`;
const version2 = await platform.getAppVersion();
// v prefix removed
expect(version2).toEqual(prodVersion);
process.env.VERSION = `version not like semver`;
// @ts-ignore
WebPlatform.VERSION = `version not like semver`;
const notSemverVersion = await platform.getAppVersion();
expect(notSemverVersion).toEqual(`version not like semver`);
});
@ -163,7 +156,8 @@ describe("WebPlatform", () => {
"should return not available and call showNoUpdate when current version " +
"matches most recent version",
async () => {
process.env.VERSION = prodVersion;
// @ts-ignore
WebPlatform.VERSION = prodVersion;
fetchMock.getOnce("/version", prodVersion);
const platform = new WebPlatform();
@ -178,7 +172,8 @@ describe("WebPlatform", () => {
);
it("should strip v prefix from versions before comparing", async () => {
process.env.VERSION = prodVersion;
// @ts-ignore
WebPlatform.VERSION = prodVersion;
fetchMock.getOnce("/version", `v${prodVersion}`);
const platform = new WebPlatform();
@ -195,7 +190,8 @@ describe("WebPlatform", () => {
it(
"should return ready and call showUpdate when current version " + "differs from most recent version",
async () => {
process.env.VERSION = "0.0.0"; // old version
// @ts-ignore
WebPlatform.VERSION = "0.0.0"; // old version
fetchMock.getOnce("/version", prodVersion);
const platform = new WebPlatform();
@ -210,7 +206,8 @@ describe("WebPlatform", () => {
);
it("should return ready without showing update when user registered in last 24", async () => {
process.env.VERSION = "0.0.0"; // old version
// @ts-ignore
WebPlatform.VERSION = "0.0.0"; // old version
jest.spyOn(MatrixClientPeg, "userRegisteredWithinLastHours").mockReturnValue(true);
fetchMock.getOnce("/version", prodVersion);
const platform = new WebPlatform();

View File

@ -18,24 +18,28 @@ import { onNewScreen } from "../../../src/vector/routing";
describe("onNewScreen", () => {
it("should replace history if stripping via fields", () => {
delete window.location;
window.location = {
hash: "#/room/!room:server?via=abc",
replace: jest.fn(),
assign: jest.fn(),
} as unknown as Location;
Object.defineProperty(window, "location", {
value: {
hash: "#/room/!room:server?via=abc",
replace: jest.fn(),
assign: jest.fn(),
},
writable: true,
});
onNewScreen("room/!room:server");
expect(window.location.assign).not.toHaveBeenCalled();
expect(window.location.replace).toHaveBeenCalled();
});
it("should not replace history if changing rooms", () => {
delete window.location;
window.location = {
hash: "#/room/!room1:server?via=abc",
replace: jest.fn(),
assign: jest.fn(),
} as unknown as Location;
Object.defineProperty(window, "location", {
value: {
hash: "#/room/!room1:server?via=abc",
replace: jest.fn(),
assign: jest.fn(),
},
writable: true,
});
onNewScreen("room/!room2:server");
expect(window.location.assign).toHaveBeenCalled();
expect(window.location.replace).not.toHaveBeenCalled();

View File

@ -17,7 +17,6 @@ limitations under the License.
import { parseQsFromFragment, parseQs } from "../../../src/vector/url_utils";
describe("url_utils.ts", function () {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const location: Location = {
hash: "",

View File

@ -14,7 +14,20 @@
"outDir": "./lib",
"declaration": true,
"jsx": "react",
"lib": ["es2019", "dom", "dom.iterable"]
"lib": ["es2020", "dom", "dom.iterable"],
"alwaysStrict": true,
"strictBindCallApply": true,
"noImplicitThis": true
},
"include": ["./src/**/*.ts", "./src/**/*.tsx", "./test/**/*.ts", "./test/**/*.tsx"]
"include": [
"./node_modules/matrix-js-sdk/src/@types/*.d.ts",
"./node_modules/matrix-react-sdk/src/@types/diff-dom.d.ts",
"./node_modules/matrix-react-sdk/src/@types/opus-recorder.d.ts",
"./node_modules/matrix-react-sdk/src/@types/png-chunks-extract.d.ts",
"./node_modules/matrix-react-sdk/src/@types/sanitize-html.d.ts",
"./src/**/*.ts",
"./src/**/*.tsx",
"./test/**/*.ts",
"./test/**/*.tsx"
]
}

View File

@ -4,7 +4,7 @@
"jsx": "preserve",
"declaration": false,
"outDir": "./lib/module_system",
"lib": ["es2019"],
"lib": ["es2020"],
"types": ["node"]
},
"include": ["./module_system/**/*.ts"]

View File

@ -99,8 +99,8 @@ module.exports = (env, argv) => {
const development = {};
if (devMode) {
// High quality, embedded source maps for dev builds
development["devtool"] = "eval-source-map";
// Embedded source maps for dev builds, can't use eval-source-map due to CSP
development["devtool"] = "inline-source-map";
} else {
if (process.env.CI_PACKAGE) {
// High quality source maps in separate .map files which include the source. This doesn't bulk up the .js

2182
yarn.lock

File diff suppressed because it is too large Load Diff