mirror of
https://github.com/element-hq/element-web.git
synced 2025-12-11 01:40:42 +00:00
Compare commits
115 Commits
renovate/d
...
t3chguy/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99553210be | ||
|
|
668450325c | ||
|
|
b4aa375619 | ||
|
|
be92e64458 | ||
|
|
4d0c740ad2 | ||
|
|
438d07bc75 | ||
|
|
b4d343053d | ||
|
|
88d507f4f9 | ||
|
|
4ffc77dfef | ||
|
|
e4d138f4d1 | ||
|
|
746c20bdba | ||
|
|
cd2bd81268 | ||
|
|
561868f12a | ||
|
|
d1f0cbea27 | ||
|
|
e80ab738ce | ||
|
|
d60768daec | ||
|
|
a7ace95eac | ||
|
|
cdb65233b0 | ||
|
|
83186c0403 | ||
|
|
8776371ed4 | ||
|
|
5f9637b14e | ||
|
|
c924f59f41 | ||
|
|
c6134b1849 | ||
|
|
818ec7529c | ||
|
|
7c7cbe655a | ||
|
|
d379103352 | ||
|
|
844a318eb2 | ||
|
|
205f0f9f30 | ||
|
|
703ad190f8 | ||
|
|
1237fbf74c | ||
|
|
137b20b784 | ||
|
|
5de07e091f | ||
|
|
f3237fb0b0 | ||
|
|
e41b476be3 | ||
|
|
4506988ad6 | ||
|
|
734e698c7e | ||
|
|
ba9f962570 | ||
|
|
1dca6b60c3 | ||
|
|
4e47b75f0f | ||
|
|
55634304b0 | ||
|
|
eb2ff67e7b | ||
|
|
c5d04b3649 | ||
|
|
0845c515c4 | ||
|
|
76416ec7b9 | ||
|
|
e3aee58a6d | ||
|
|
ea158ed63f | ||
|
|
38c0bf3b62 | ||
|
|
6821a35444 | ||
|
|
9f9cd6ff08 | ||
|
|
f8b3be682a | ||
|
|
ab5ff85bff | ||
|
|
aa9851dc4e | ||
|
|
437b5241b9 | ||
|
|
42943f3ffc | ||
|
|
2fd4e474cb | ||
|
|
cf1eddf0ea | ||
|
|
cee4a754d7 | ||
|
|
72354161e2 | ||
|
|
fa1d2c7dfb | ||
|
|
42c0c19556 | ||
|
|
d3a0925e42 | ||
|
|
351a31c9ee | ||
|
|
2614cab64b | ||
|
|
15db387425 | ||
|
|
53cc1c78b1 | ||
|
|
be6528de26 | ||
|
|
087f1bc948 | ||
|
|
4360f5a63e | ||
|
|
65a87f1a53 | ||
|
|
b0c04834b6 | ||
|
|
b61d5b4475 | ||
|
|
6c3817fa34 | ||
|
|
e151d9ab53 | ||
|
|
f15b188297 | ||
|
|
ea16928d35 | ||
|
|
4651bf4eb4 | ||
|
|
5442d4050d | ||
|
|
695cc1b28b | ||
|
|
c94e3f00ed | ||
|
|
79e0661be3 | ||
|
|
6cf0809064 | ||
|
|
a0eb94704e | ||
|
|
e1e0d37478 | ||
|
|
9039e70990 | ||
|
|
596f297094 | ||
|
|
4f92927a3d | ||
|
|
b77218e65a | ||
|
|
c21e9f6383 | ||
|
|
4f8a8a2543 | ||
|
|
d615e69a86 | ||
|
|
db1d440e3b | ||
|
|
24fafe2588 | ||
|
|
1e06e3083f | ||
|
|
8e9f3a786c | ||
|
|
95f134a7c5 | ||
|
|
50f5c15027 | ||
|
|
65b43d8dc7 | ||
|
|
8dad4f5f10 | ||
|
|
a3a436ad64 | ||
|
|
ffb69d4c47 | ||
|
|
369ae1208e | ||
|
|
050dae7a9f | ||
|
|
a9cd567fc9 | ||
|
|
82073aea3e | ||
|
|
9cf7b8b6f6 | ||
|
|
fa5bd19368 | ||
|
|
fb72597fcf | ||
|
|
3ce0d78961 | ||
|
|
361636a3d1 | ||
|
|
0b49a7724b | ||
|
|
2db2cf2b1b | ||
|
|
c38505598f | ||
|
|
b98289c660 | ||
|
|
85dbaaa5ff | ||
|
|
29c176b521 |
@@ -88,6 +88,7 @@ module.exports = {
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
3
.github/labels.yml
vendored
3
.github/labels.yml
vendored
@@ -261,3 +261,6 @@
|
||||
color: "ededed"
|
||||
- name: "Z-t3chguy"
|
||||
color: "ededed"
|
||||
- name: "Z-Flaky-Test-Disabled"
|
||||
description: "The flaking test has been disabled"
|
||||
color: "ededed"
|
||||
|
||||
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -37,6 +37,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
# Workaround for yarn install timeouts, especially on Windows
|
||||
- run: yarn config set network-timeout 300000
|
||||
|
||||
3
.github/workflows/build_develop.yml
vendored
3
.github/workflows/build_develop.yml
vendored
@@ -26,6 +26,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: "./scripts/layered.sh"
|
||||
@@ -98,7 +99,7 @@ jobs:
|
||||
running-workflow-name: "Build & Deploy develop.element.io"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare).)*$
|
||||
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages).)*$
|
||||
|
||||
# We keep the latest develop.tar.gz on R2 instead of relying on the github artifact uploaded earlier
|
||||
# as the expires after 24h and requires auth to download.
|
||||
|
||||
2
.github/workflows/dockerhub.yaml
vendored
2
.github/workflows/dockerhub.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
- name: Build and push
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5
|
||||
uses: docker/build-push-action@31159d49c0d4756269a0940a750801a1ea5d7003 # v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
@@ -20,7 +20,6 @@ jobs:
|
||||
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
with:
|
||||
final: ${{ inputs.mode == 'final' }}
|
||||
include-changes: matrix-react-sdk
|
||||
gpg-fingerprint: ${{ vars.GPG_FINGERPRINT }}
|
||||
asset-path: dist/*.tar.gz
|
||||
expected-asset-count: 3
|
||||
@@ -31,11 +30,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify element-desktop repo that element-web release has completed to re-trigger release-drafter
|
||||
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3
|
||||
uses: benc-uk/workflow-dispatch@25b02cc069be46d637e8fe2f1e8484008e9e9609 # v1
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
repository: element-hq/element-desktop
|
||||
event-type: upstream-release-notify
|
||||
workflow: release-drafter.yml
|
||||
repo: element-hq/element-desktop
|
||||
ref: staging
|
||||
# Required when using the `repo` option. Either a PAT or a token generated from the GitHub app or CLI
|
||||
token: "${{ secrets.ELEMENT_BOT_TOKEN }}"
|
||||
|
||||
check:
|
||||
name: Post release checks
|
||||
|
||||
5
.github/workflows/static_analysis.yaml
vendored
5
.github/workflows/static_analysis.yaml
vendored
@@ -21,6 +21,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: "./scripts/layered.sh"
|
||||
@@ -43,6 +44,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
@@ -60,6 +62,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
# Needs branch matching as it inherits .stylelintrc.js from matrix-react-sdk
|
||||
- name: Install Dependencies
|
||||
@@ -77,6 +80,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
@@ -94,6 +98,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Deps
|
||||
run: "scripts/layered.sh"
|
||||
|
||||
1
.github/workflows/tests.yaml
vendored
1
.github/workflows/tests.yaml
vendored
@@ -23,6 +23,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: "./scripts/layered.sh"
|
||||
|
||||
2
.github/workflows/triage-labelled.yml
vendored
2
.github/workflows/triage-labelled.yml
vendored
@@ -148,7 +148,7 @@ jobs:
|
||||
contains(github.event.issue.labels.*.name, 'A-Element-R')
|
||||
steps:
|
||||
- id: add_to_project
|
||||
uses: actions/add-to-project@v1.0.1
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: ${{ env.PROJECT_URL }}
|
||||
github-token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
3
.github/workflows/update-jitsi.yml
vendored
3
.github/workflows/update-jitsi.yml
vendored
@@ -13,6 +13,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --frozen-lockfile"
|
||||
@@ -21,7 +22,7 @@ jobs:
|
||||
run: "yarn update:jitsi"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6
|
||||
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
branch: actions/jitsi-update
|
||||
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -1,3 +1,26 @@
|
||||
Changes in [1.11.69](https://github.com/element-hq/element-web/releases/tag/v1.11.69) (2024-06-18)
|
||||
==================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
* Change avatar setting component to use a menu ([#12585](https://github.com/matrix-org/matrix-react-sdk/pull/12585)). Contributed by @dbkr.
|
||||
* New user profile UI in User Settings ([#12548](https://github.com/matrix-org/matrix-react-sdk/pull/12548)). Contributed by @dbkr.
|
||||
* MSC4108 support OIDC QR code login ([#12370](https://github.com/matrix-org/matrix-react-sdk/pull/12370)). Contributed by @t3chguy.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix image upload preview size ([#12612](https://github.com/matrix-org/matrix-react-sdk/pull/12612)). Contributed by @RiotRobot.
|
||||
* Fix screen sharing in recent Chrome (https://github.com/matrix-org/matrix-js-sdk/pull/4243).
|
||||
* Fix roving tab index crash `compareDocumentPosition` ([#12594](https://github.com/matrix-org/matrix-react-sdk/pull/12594)). Contributed by @t3chguy.
|
||||
* Keep dialog glass border on narrow screens ([#12591](https://github.com/matrix-org/matrix-react-sdk/pull/12591)). Contributed by @dbkr.
|
||||
* Add missing a11y label to dismiss onboarding button in room list ([#12587](https://github.com/matrix-org/matrix-react-sdk/pull/12587)). Contributed by @t3chguy.
|
||||
* Add hover / active state on avatar setting upload button ([#12590](https://github.com/matrix-org/matrix-react-sdk/pull/12590)). Contributed by @dbkr.
|
||||
* Fix EditInPlace button styles ([#12589](https://github.com/matrix-org/matrix-react-sdk/pull/12589)). Contributed by @dbkr.
|
||||
* Fix incorrect assumptions about required fields in /search response ([#12575](https://github.com/matrix-org/matrix-react-sdk/pull/12575)). Contributed by @t3chguy.
|
||||
* Fix display of no avatar in avatar setting controls ([#12558](https://github.com/matrix-org/matrix-react-sdk/pull/12558)). Contributed by @dbkr.
|
||||
* Element-R: pass pickleKey in as raw key for indexeddb encryption ([#12543](https://github.com/matrix-org/matrix-react-sdk/pull/12543)). Contributed by @richvdh.
|
||||
|
||||
|
||||
|
||||
Changes in [1.11.68](https://github.com/element-hq/element-web/releases/tag/v1.11.68) (2024-06-04)
|
||||
==================================================================================================
|
||||
## ✨ Features
|
||||
|
||||
@@ -17,17 +17,17 @@ module.exports = {
|
||||
],
|
||||
plugins: [
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
"@babel/plugin-proposal-numeric-separator",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
"@babel/plugin-transform-numeric-separator",
|
||||
"@babel/plugin-transform-class-properties",
|
||||
"@babel/plugin-transform-object-rest-spread",
|
||||
"@babel/plugin-transform-optional-chaining",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator",
|
||||
|
||||
// transform logical assignment (??=, ||=, &&=). preset-env doesn't
|
||||
// normally bother with these (presumably because all the target
|
||||
// browsers support it natively), but they make our webpack version (or
|
||||
// something downstream of babel, at least) fall over.
|
||||
"@babel/plugin-proposal-logical-assignment-operators",
|
||||
"@babel/plugin-transform-logical-assignment-operators",
|
||||
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-transform-runtime",
|
||||
|
||||
@@ -250,12 +250,14 @@ When Element is deployed alongside a homeserver with SSO-only login, some option
|
||||
user can be sent to in order to log them out of that system too, making logout symmetric between Element and the SSO system.
|
||||
2. `sso_redirect_options`: Options to define how to handle unauthenticated users. If the object contains `"immediate": true`, then
|
||||
all unauthenticated users will be automatically redirected to the SSO system to start their login. If instead you'd only like to
|
||||
have users which land on the welcome page to be redirected, use `"on_welcome_page": true`. As an example:
|
||||
have users which land on the welcome page to be redirected, use `"on_welcome_page": true`. Additionally, there is an option to
|
||||
redirect anyone landing on the login page, by using `"on_login_page": true`. As an example:
|
||||
```json
|
||||
{
|
||||
"sso_redirect_options": {
|
||||
"immediate": false,
|
||||
"on_welcome_page": true
|
||||
"on_welcome_page": true,
|
||||
"on_login_page": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
23
docs/labs.md
23
docs/labs.md
@@ -110,29 +110,6 @@ This is useful while we experiment with encryption and to make calling compatibl
|
||||
|
||||
Enables rendering of MD / HTML in room topics.
|
||||
|
||||
## Use the Rust cryptography implementation (`feature_rust_crypto`) [In Development]
|
||||
|
||||
Configures Element to use a new cryptography implementation based on the [matrix-rust-sdk](https://github.com/matrix-org/matrix-rust-sdk).
|
||||
|
||||
This setting is (currently) _sticky_ to a user's session: it only takes effect when the user logs in to a new session. Likewise, even after disabling the setting in `config.json`, the Rust implementation will remain in use until users log out.
|
||||
|
||||
This configuration value is now set to `true` by default. This means that without any additional configuration
|
||||
every new login will use the new cryptography implementation.
|
||||
|
||||
For administrators looking to transition existing users to the new stack, the `RustCrypto.staged_rollout_percent` configuration is available.
|
||||
This configuration allows for a phased migration of users, represented as an integer percentage (0 to 100). By default, this value is set to `0`,
|
||||
which means no existing users will be migrated to the new stack. If you wish to migrate all users, you can adjust this value to `100`.
|
||||
|
||||
This configuration should be placed under the `setting_defaults` section as shown:
|
||||
|
||||
```
|
||||
"setting_defaults": {
|
||||
"RustCrypto.staged_rollout_percent": 20
|
||||
},
|
||||
```
|
||||
|
||||
By adjusting the `RustCrypto.staged_rollout_percent` value, you can control the migration process according to your deployment strategy.
|
||||
|
||||
## New room header & details (`feature_new_room_decoration_ui`) [In Development]
|
||||
|
||||
Refactors visually the room header and room sidebar
|
||||
|
||||
31
package.json
31
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "element-web",
|
||||
"version": "1.11.68",
|
||||
"version": "1.11.69",
|
||||
"description": "A feature-rich client for Matrix.org",
|
||||
"author": "New Vector Ltd.",
|
||||
"repository": {
|
||||
@@ -71,9 +71,7 @@
|
||||
"@types/react": "17.0.80"
|
||||
},
|
||||
"dependencies": {
|
||||
"@matrix-org/olm": "3.2.15",
|
||||
"@matrix-org/react-sdk-module-api": "^2.3.0",
|
||||
"gfm.css": "^1.1.2",
|
||||
"jsrsasign": "^11.0.0",
|
||||
"katex": "^0.16.0",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -90,14 +88,14 @@
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/eslint-parser": "^7.12.10",
|
||||
"@babel/eslint-plugin": "^7.12.10",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.12.1",
|
||||
"@babel/plugin-proposal-logical-assignment-operators": "^7.20.7",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.12.7",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.12.7",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-class-properties": "^7.12.1",
|
||||
"@babel/plugin-transform-logical-assignment-operators": "^7.20.7",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.12.1",
|
||||
"@babel/plugin-transform-numeric-separator": "^7.12.7",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.12.1",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.12.7",
|
||||
"@babel/plugin-transform-runtime": "^7.12.10",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/preset-react": "^7.12.10",
|
||||
@@ -136,7 +134,7 @@
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/tar-js": "^0.3.5",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"babel-jest": "^29.0.0",
|
||||
@@ -153,13 +151,13 @@
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-deprecate": "0.8.4",
|
||||
"eslint-plugin-deprecate": "0.8.5",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-matrix-org": "^1.0.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-unicorn": "^53.0.0",
|
||||
"fake-indexeddb": "^5.0.0",
|
||||
"eslint-plugin-unicorn": "^54.0.0",
|
||||
"fake-indexeddb": "^6.0.0",
|
||||
"fetch-mock": "9.11.0",
|
||||
"fetch-mock-jest": "^1.5.1",
|
||||
"file-loader": "^6.0.0",
|
||||
@@ -183,15 +181,14 @@
|
||||
"postcss-loader": "8.1.0",
|
||||
"postcss-mixins": "^10.0.0",
|
||||
"postcss-nested": "^6.0.0",
|
||||
"postcss-preset-env": "^9.3.0",
|
||||
"postcss-preset-env": "^9.5.14",
|
||||
"postcss-scss": "^4.0.4",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"prettier": "3.2.5",
|
||||
"prettier": "3.3.2",
|
||||
"process": "^0.11.10",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^5.0.0",
|
||||
"semver": "^7.5.2",
|
||||
"setimmediate": "^1.0.5",
|
||||
"string-replace-loader": "3",
|
||||
"style-loader": "4",
|
||||
"stylelint": "^16.1.0",
|
||||
@@ -200,7 +197,7 @@
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
"ts-node": "^10.9.1",
|
||||
"ts-prune": "^0.10.3",
|
||||
"typescript": "5.4.5",
|
||||
"typescript": "5.5.2",
|
||||
"util": "^0.12.5",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-bundle-analyzer": "^4.8.0",
|
||||
|
||||
@@ -38,7 +38,7 @@ module.exports.pitch = function pitch(request) {
|
||||
return cb(err);
|
||||
}
|
||||
if (entries[0]) {
|
||||
return cb(null, `module.exports = __webpack_public_path__ + ${JSON.stringify(entries[0].files[0])};`);
|
||||
return cb(null, `module.exports = __webpack_public_path__ + ${JSON.stringify([...entries[0].files][0])};`);
|
||||
}
|
||||
return cb(null, null);
|
||||
});
|
||||
|
||||
3
res/jitsi_external_api.min.js
vendored
3
res/jitsi_external_api.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -194,9 +194,9 @@ export default class Favicon {
|
||||
}
|
||||
|
||||
private setIcon(canvas: HTMLCanvasElement): void {
|
||||
setImmediate(() => {
|
||||
setTimeout(() => {
|
||||
this.setIconSrc(canvas.toDataURL("image/png"));
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private setIconSrc(url: string): void {
|
||||
|
||||
@@ -20,7 +20,7 @@ import { buildAndEncodePickleKey } from "matrix-react-sdk/src/utils/tokens/pickl
|
||||
|
||||
const serverSupportMap: {
|
||||
[serverUrl: string]: {
|
||||
supportsMSC3916: boolean;
|
||||
supportsAuthedMedia: boolean;
|
||||
cacheExpiryTimeMs: number;
|
||||
};
|
||||
} = {};
|
||||
@@ -79,10 +79,8 @@ self.addEventListener("fetch", (event: FetchEvent) => {
|
||||
await tryUpdateServerSupportMap(csApi, accessToken);
|
||||
|
||||
// If we have server support (and a means of authentication), rewrite the URL to use MSC3916 endpoints.
|
||||
if (serverSupportMap[csApi].supportsMSC3916 && accessToken) {
|
||||
// Currently unstable only.
|
||||
// TODO: Support stable endpoints when available.
|
||||
url = url.replace(/\/media\/v3\/(.*)\//, "/client/unstable/org.matrix.msc3916/media/$1/");
|
||||
if (serverSupportMap[csApi].supportsAuthedMedia && accessToken) {
|
||||
url = url.replace(/\/media\/v3\/(.*)\//, "/client/v1/media/$1/");
|
||||
} // else by default we make no changes
|
||||
} catch (err) {
|
||||
console.error("SW: Error in request rewrite.", err);
|
||||
@@ -106,7 +104,7 @@ async function tryUpdateServerSupportMap(clientApiUrl: string, accessToken?: str
|
||||
const versions = await (await fetch(`${clientApiUrl}/_matrix/client/versions`, config)).json();
|
||||
|
||||
serverSupportMap[clientApiUrl] = {
|
||||
supportsMSC3916: Boolean(versions?.unstable_features?.["org.matrix.msc3916"]),
|
||||
supportsAuthedMedia: Boolean(versions?.versions?.includes("v1.11")),
|
||||
cacheExpiryTimeMs: new Date().getTime() + 2 * 60 * 60 * 1000, // 2 hours from now
|
||||
};
|
||||
}
|
||||
|
||||
@@ -89,9 +89,14 @@ export async function loadApp(fragParams: {}, matrixChatRef: React.Ref<MatrixCha
|
||||
// XXX: This path matching is a bit brittle, but better to do it early instead of in the app code.
|
||||
const isWelcomeOrLanding =
|
||||
window.location.hash === "#/welcome" || window.location.hash === "#" || window.location.hash === "";
|
||||
const isLoginPage = window.location.hash === "#/login";
|
||||
|
||||
if (!autoRedirect && ssoRedirects.on_welcome_page && isWelcomeOrLanding) {
|
||||
autoRedirect = true;
|
||||
}
|
||||
if (!autoRedirect && ssoRedirects.on_login_page && isLoginPage) {
|
||||
autoRedirect = true;
|
||||
}
|
||||
if (!hasPossibleToken && !isReturningFromSso && autoRedirect) {
|
||||
logger.log("Bypassing app load to redirect to SSO");
|
||||
const tempCli = createClient({
|
||||
|
||||
@@ -81,31 +81,6 @@
|
||||
<img src="<%= require('matrix-react-sdk/res/img/format/quote.svg').default %>" aria-hidden alt="" width="25" height="22" style="visibility: hidden; position: absolute; top: 0px; left: 0px;"/>
|
||||
<img src="<%= require('matrix-react-sdk/res/img/format/strikethrough.svg').default %>" aria-hidden alt="" width="25" height="22" style="visibility: hidden; position: absolute; top: 0px; left: 0px;"/>
|
||||
|
||||
<audio id="messageAudio">
|
||||
<source src="media/message.ogg" type="audio/ogg" />
|
||||
<source src="media/message.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="ringAudio" loop>
|
||||
<source src="media/ring.ogg" type="audio/ogg" />
|
||||
<source src="media/ring.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="ringbackAudio" loop>
|
||||
<source src="media/ringback.ogg" type="audio/ogg" />
|
||||
<source src="media/ringback.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="callendAudio">
|
||||
<source src="media/callend.ogg" type="audio/ogg" />
|
||||
<source src="media/callend.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="busyAudio">
|
||||
<source src="media/busy.ogg" type="audio/ogg" />
|
||||
<source src="media/busy.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="errorAudio">
|
||||
<source src="media/error.ogg" type="audio/ogg" />
|
||||
<source src="media/error.mp3" type="audio/mpeg" />
|
||||
</audio>
|
||||
<audio id="remoteAudio"></audio>
|
||||
<!-- let CSS themes pass constants to the app -->
|
||||
<div id="mx_theme_accentColor"></div><div id="mx_theme_secondaryAccentColor"></div><div id="mx_theme_tertiaryAccentColor"></div>
|
||||
|
||||
|
||||
@@ -19,19 +19,14 @@ 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";
|
||||
import "./modernizr";
|
||||
|
||||
// Make setImmediate available in bundle
|
||||
import "setimmediate";
|
||||
|
||||
// Require common CSS here; this will make webpack process it into bundle.css.
|
||||
// Our own CSS (which is themed) is imported via separate webpack entry points
|
||||
// in webpack.config.js
|
||||
require("gfm.css/gfm.css");
|
||||
require("katex/dist/katex.css");
|
||||
|
||||
/**
|
||||
@@ -60,8 +55,8 @@ function checkBrowserFeatures(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Custom checks atop Modernizr because it doesn't have ES2018/ES2019 checks
|
||||
// in it for some features we depend on.
|
||||
// Custom checks atop Modernizr because it doesn't have checks in it for
|
||||
// some features we depend on.
|
||||
// Modernizr requires rules to be lowercase with no punctuation.
|
||||
// ES2018: http://262.ecma-international.org/9.0/#sec-promise.prototype.finally
|
||||
window.Modernizr.addTest("promiseprototypefinally", () => typeof window.Promise?.prototype?.finally === "function");
|
||||
@@ -74,6 +69,13 @@ function checkBrowserFeatures(): boolean {
|
||||
);
|
||||
// ES2019: http://262.ecma-international.org/10.0/#sec-object.fromentries
|
||||
window.Modernizr.addTest("objectfromentries", () => typeof window.Object?.fromEntries === "function");
|
||||
// ES2024: https://tc39.es/ecma262/2024/#sec-get-regexp.prototype.unicodesets
|
||||
window.Modernizr.addTest(
|
||||
"regexpunicodesets",
|
||||
() => window.RegExp?.prototype && "unicodeSets" in window.RegExp.prototype,
|
||||
);
|
||||
// ES2024: https://402.ecma-international.org/9.0/#sec-intl.segmenter
|
||||
window.Modernizr.addTest("intlsegmenter", () => typeof window.Intl?.Segmenter === "function");
|
||||
|
||||
const featureList = Object.keys(window.Modernizr) as Array<keyof ModernizrStatic>;
|
||||
|
||||
@@ -109,7 +111,6 @@ async function start(): Promise<void> {
|
||||
rageshakePromise,
|
||||
setupLogStorage,
|
||||
preparePlatform,
|
||||
loadOlm,
|
||||
loadConfig,
|
||||
loadLanguage,
|
||||
loadTheme,
|
||||
@@ -118,6 +119,7 @@ async function start(): Promise<void> {
|
||||
showError,
|
||||
showIncompatibleBrowser,
|
||||
_t,
|
||||
extractErrorMessageFromError,
|
||||
} = await import(
|
||||
/* webpackChunkName: "init" */
|
||||
/* webpackPreload: true */
|
||||
@@ -147,7 +149,6 @@ async function start(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
const loadOlmPromise = loadOlm();
|
||||
// set the platform for react sdk
|
||||
preparePlatform();
|
||||
// load config requires the platform to be ready
|
||||
@@ -180,7 +181,7 @@ async function start(): Promise<void> {
|
||||
// error handling begins here
|
||||
// ##########################
|
||||
if (!acceptBrowser) {
|
||||
await new Promise<void>((resolve) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
logger.error("Browser is missing required features.");
|
||||
// take to a different landing page to AWOOOOOGA at the user
|
||||
showIncompatibleBrowser(() => {
|
||||
@@ -189,7 +190,7 @@ async function start(): Promise<void> {
|
||||
}
|
||||
logger.log("User accepts the compatibility risks.");
|
||||
resolve();
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -214,7 +215,6 @@ async function start(): Promise<void> {
|
||||
// app load critical path starts here
|
||||
// assert things started successfully
|
||||
// ##################################
|
||||
await loadOlmPromise;
|
||||
await loadModulesPromise;
|
||||
await loadThemePromise;
|
||||
await loadLanguagePromise;
|
||||
|
||||
@@ -17,10 +17,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import olmWasmPath from "@matrix-org/olm/olm.wasm";
|
||||
import Olm from "@matrix-org/olm";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import * as React from "react";
|
||||
import * as languageHandler from "matrix-react-sdk/src/languageHandler";
|
||||
@@ -76,48 +72,6 @@ export async function loadConfig(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export function loadOlm(): Promise<void> {
|
||||
/* Load Olm. We try the WebAssembly version first, and then the legacy,
|
||||
* asm.js version if that fails. For this reason we need to wait for this
|
||||
* to finish before continuing to load the rest of the app. In future
|
||||
* we could somehow pass a promise down to react-sdk and have it wait on
|
||||
* that so olm can be loading in parallel with the rest of the app.
|
||||
*
|
||||
* We also need to tell the Olm js to look for its wasm file at the same
|
||||
* level as index.html. It really should be in the same place as the js,
|
||||
* ie. in the bundle directory, but as far as I can tell this is
|
||||
* completely impossible with webpack. We do, however, use a hashed
|
||||
* filename to avoid caching issues.
|
||||
*/
|
||||
return Olm.init({
|
||||
locateFile: () => olmWasmPath,
|
||||
})
|
||||
.then(() => {
|
||||
logger.log("Using WebAssembly Olm");
|
||||
})
|
||||
.catch((wasmLoadError) => {
|
||||
logger.log("Failed to load Olm: trying legacy version", wasmLoadError);
|
||||
return new Promise((resolve, reject) => {
|
||||
const s = document.createElement("script");
|
||||
s.src = "olm_legacy.js"; // XXX: This should be cache-busted too
|
||||
s.onload = resolve;
|
||||
s.onerror = reject;
|
||||
document.body.appendChild(s);
|
||||
})
|
||||
.then(() => {
|
||||
// Init window.Olm, ie. the one just loaded by the script tag,
|
||||
// not 'Olm' which is still the failed wasm version.
|
||||
return window.Olm.init();
|
||||
})
|
||||
.then(() => {
|
||||
logger.log("Using legacy Olm");
|
||||
})
|
||||
.catch((legacyLoadError) => {
|
||||
logger.log("Both WebAssembly and asm.js Olm failed!", legacyLoadError);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadLanguage(): Promise<void> {
|
||||
const prefLang = SettingsStore.getValue("language", null, /*excludeDefault=*/ true);
|
||||
let langs: string[] = [];
|
||||
@@ -138,7 +92,7 @@ export async function loadLanguage(): Promise<void> {
|
||||
}
|
||||
|
||||
export async function loadTheme(): Promise<void> {
|
||||
setTheme();
|
||||
return setTheme();
|
||||
}
|
||||
|
||||
export async function loadApp(fragParams: {}): Promise<void> {
|
||||
@@ -189,3 +143,5 @@ export async function loadModules(): Promise<void> {
|
||||
}
|
||||
|
||||
export { _t } from "../languageHandler";
|
||||
|
||||
export { extractErrorMessageFromError } from "matrix-react-sdk/src/components/views/dialogs/ErrorDialog";
|
||||
|
||||
@@ -177,17 +177,17 @@ const setupCompleted = (async (): Promise<string | void> => {
|
||||
}
|
||||
}
|
||||
|
||||
await widgetApi!.transport.reply(ev.detail, response);
|
||||
widgetApi!.transport.reply(ev.detail, response);
|
||||
});
|
||||
};
|
||||
|
||||
handleAction(ElementWidgetActions.JoinCall, async ({ audioInput, videoInput }) => {
|
||||
joinConference(audioInput as string | null, videoInput as string | null);
|
||||
void joinConference(audioInput as string | null, videoInput as string | null);
|
||||
});
|
||||
handleAction(ElementWidgetActions.HangupCall, async ({ force }) => {
|
||||
if (force === true) {
|
||||
meetApi?.dispose();
|
||||
notifyHangup();
|
||||
void notifyHangup();
|
||||
meetApi = undefined;
|
||||
closeConference();
|
||||
} else {
|
||||
@@ -297,7 +297,7 @@ function toggleConferenceVisibility(inConference: boolean): void {
|
||||
|
||||
function skipToJitsiSplashScreen(): void {
|
||||
// really just a function alias for self-documenting code
|
||||
joinConference();
|
||||
void joinConference();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -500,8 +500,8 @@ const onVideoConferenceJoined = (): void => {
|
||||
if (widgetApi) {
|
||||
// ignored promise because we don't care if it works
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
widgetApi.setAlwaysOnScreen(true);
|
||||
widgetApi.transport.send(ElementWidgetActions.JoinCall, {});
|
||||
void widgetApi.setAlwaysOnScreen(true);
|
||||
void widgetApi.transport.send(ElementWidgetActions.JoinCall, {});
|
||||
}
|
||||
|
||||
// Video rooms should start in tile mode
|
||||
@@ -509,7 +509,7 @@ const onVideoConferenceJoined = (): void => {
|
||||
};
|
||||
|
||||
const onVideoConferenceLeft = (): void => {
|
||||
notifyHangup();
|
||||
void notifyHangup();
|
||||
meetApi = undefined;
|
||||
};
|
||||
|
||||
@@ -517,7 +517,7 @@ const onErrorOccurred = ({ error }: Parameters<ExternalAPIEventCallbacks["errorO
|
||||
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);
|
||||
void notifyHangup(error.message);
|
||||
meetApi = undefined;
|
||||
closeConference();
|
||||
}
|
||||
@@ -525,7 +525,7 @@ const onErrorOccurred = ({ error }: Parameters<ExternalAPIEventCallbacks["errorO
|
||||
|
||||
const onAudioMuteStatusChanged = ({ muted }: AudioMuteStatusChangedEvent): void => {
|
||||
const action = muted ? ElementWidgetActions.MuteAudio : ElementWidgetActions.UnmuteAudio;
|
||||
widgetApi?.transport.send(action, {});
|
||||
void widgetApi?.transport.send(action, {});
|
||||
};
|
||||
|
||||
const onVideoMuteStatusChanged = ({ muted }: VideoMuteStatusChangedEvent): void => {
|
||||
@@ -535,15 +535,15 @@ const onVideoMuteStatusChanged = ({ muted }: VideoMuteStatusChangedEvent): void
|
||||
// otherwise the React SDK will mistakenly think the user turned off
|
||||
// their video by hand
|
||||
setTimeout(() => {
|
||||
if (meetApi) widgetApi?.transport.send(ElementWidgetActions.MuteVideo, {});
|
||||
if (meetApi) void widgetApi?.transport.send(ElementWidgetActions.MuteVideo, {});
|
||||
}, 200);
|
||||
} else {
|
||||
widgetApi?.transport.send(ElementWidgetActions.UnmuteVideo, {});
|
||||
void widgetApi?.transport.send(ElementWidgetActions.UnmuteVideo, {});
|
||||
}
|
||||
};
|
||||
|
||||
const updateParticipants = (): void => {
|
||||
widgetApi?.transport.send(ElementWidgetActions.CallParticipants, {
|
||||
void widgetApi?.transport.send(ElementWidgetActions.CallParticipants, {
|
||||
participants: meetApi?.getParticipantsInfo(),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -120,4 +120,4 @@ async function initPage(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
initPage();
|
||||
void initPage();
|
||||
|
||||
@@ -167,15 +167,14 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||
});
|
||||
});
|
||||
|
||||
window.electron.on("openDesktopCapturerSourcePicker", () => {
|
||||
window.electron.on("openDesktopCapturerSourcePicker", async () => {
|
||||
const { finished } = Modal.createDialog(DesktopCapturerSourcePicker);
|
||||
finished.then(([source]) => {
|
||||
// getDisplayMedia promise does not return if no dummy is passed here as source
|
||||
this.ipc.call("callDisplayMediaCallback", source ?? { id: "", name: "", thumbnailURL: "" });
|
||||
});
|
||||
const [source] = await finished;
|
||||
// getDisplayMedia promise does not return if no dummy is passed here as source
|
||||
await this.ipc.call("callDisplayMediaCallback", source ?? { id: "", name: "", thumbnailURL: "" });
|
||||
});
|
||||
|
||||
this.ipc.call("startSSOFlow", this.ssoID);
|
||||
void this.ipc.call("startSSOFlow", this.ssoID);
|
||||
|
||||
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
}
|
||||
@@ -195,7 +194,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||
),
|
||||
initial: getInitialLetter(r.name),
|
||||
}));
|
||||
this.ipc.call("breadcrumbs", rooms);
|
||||
void this.ipc.call("breadcrumbs", rooms);
|
||||
};
|
||||
|
||||
private onUpdateDownloaded = async (ev: Event, { releaseNotes, releaseName }: SquirrelUpdate): Promise<void> => {
|
||||
@@ -261,7 +260,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||
const handler = notification.onclick as Function;
|
||||
notification.onclick = (): void => {
|
||||
handler?.();
|
||||
this.ipc.call("focusWindow");
|
||||
void this.ipc.call("focusWindow");
|
||||
};
|
||||
|
||||
return notification;
|
||||
@@ -399,7 +398,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
|
||||
}
|
||||
|
||||
public navigateForwardBack(back: boolean): void {
|
||||
this.ipc.call(back ? "navigateBack" : "navigateForward");
|
||||
void this.ipc.call(back ? "navigateBack" : "navigateForward");
|
||||
}
|
||||
|
||||
public overrideBrowserShortcuts(): boolean {
|
||||
|
||||
@@ -113,10 +113,10 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||
// annoyingly, the latest spec says this returns a
|
||||
// promise, but this is only supported in Chrome 46
|
||||
// and Firefox 47, so adapt the callback API.
|
||||
return new Promise(function (resolve) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
window.Notification.requestPermission((result) => {
|
||||
resolve(result);
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -148,7 +148,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(WebPlatform.VERSION));
|
||||
this.pollForUpdate((version: string, newVersion: string) => {
|
||||
void this.pollForUpdate((version: string, newVersion: string) => {
|
||||
const query = parseQs(location);
|
||||
if (query.updated) {
|
||||
console.log("Update reloaded but still on an old version, stopping");
|
||||
@@ -207,7 +207,7 @@ export default class WebPlatform extends VectorBasePlatform {
|
||||
|
||||
public startUpdateCheck(): void {
|
||||
super.startUpdateCheck();
|
||||
this.pollForUpdate(showUpdateToast, hideUpdateToast).then((updateState) => {
|
||||
void this.pollForUpdate(showUpdateToast, hideUpdateToast).then((updateState) => {
|
||||
dis.dispatch<CheckUpdatesPayload>({
|
||||
action: Action.CheckUpdates,
|
||||
...updateState,
|
||||
|
||||
@@ -35,7 +35,7 @@ export function initRageshake(): Promise<void> {
|
||||
// we manually check persistence for rageshakes ourselves
|
||||
const prom = rageshake.init(/*setUpPersistence=*/ false);
|
||||
prom.then(
|
||||
() => {
|
||||
async () => {
|
||||
logger.log("Initialised rageshake.");
|
||||
logger.log(
|
||||
"To fix line numbers in Chrome: " +
|
||||
@@ -48,7 +48,7 @@ export function initRageshake(): Promise<void> {
|
||||
rageshake.flush();
|
||||
});
|
||||
|
||||
rageshake.cleanup();
|
||||
await rageshake.cleanup();
|
||||
},
|
||||
(err) => {
|
||||
logger.error("Failed to initialise rageshake: " + err);
|
||||
|
||||
@@ -1,692 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2020 New Vector Ltd
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/* loading.js: test the myriad paths we have for loading the application */
|
||||
|
||||
import "fake-indexeddb/auto";
|
||||
import React from "react";
|
||||
import { render, screen, fireEvent, waitFor, RenderResult, waitForElementToBeRemoved } from "@testing-library/react";
|
||||
import PlatformPeg from "matrix-react-sdk/src/PlatformPeg";
|
||||
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 { 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";
|
||||
import { parseQs, parseQsFromFragment } from "../../src/vector/url_utils";
|
||||
import { cleanLocalstorage, deleteIndexedDB, waitForLoadingSpinner, waitForWelcomeComponent } from "../test-utils";
|
||||
|
||||
const DEFAULT_HS_URL = "http://my_server";
|
||||
const DEFAULT_IS_URL = "http://my_is";
|
||||
|
||||
/** The matrix versions our mock server claims to support */
|
||||
const SERVER_SUPPORTED_MATRIX_VERSIONS = ["v1.1", "v1.5", "v1.6", "v1.8", "v1.9"];
|
||||
|
||||
describe("loading:", function () {
|
||||
let httpBackend: MockHttpBackend;
|
||||
|
||||
// an Object simulating the window.location
|
||||
let windowLocation: Location | undefined;
|
||||
|
||||
// the mounted MatrixChat
|
||||
let matrixChat: RenderResult | undefined;
|
||||
|
||||
// a promise which resolves when the MatrixChat calls onTokenLoginCompleted
|
||||
let tokenLoginCompletePromise: Promise<void> | undefined;
|
||||
|
||||
beforeEach(function () {
|
||||
httpBackend = new MockHttpBackend();
|
||||
// @ts-ignore
|
||||
window.fetch = httpBackend.fetchFn;
|
||||
|
||||
windowLocation = undefined;
|
||||
matrixChat = undefined;
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
console.log(`${Date.now()}: loading: afterEach`);
|
||||
matrixChat?.unmount();
|
||||
// unmounting should have cleared the MatrixClientPeg
|
||||
expect(MatrixClientPeg.get()).toBe(null);
|
||||
|
||||
// clear the indexeddbs so we can start from a clean slate next time.
|
||||
await Promise.all([deleteIndexedDB("matrix-js-sdk:crypto"), deleteIndexedDB("matrix-js-sdk:riot-web-sync")]);
|
||||
cleanLocalstorage();
|
||||
console.log(`${Date.now()}: loading: afterEach complete`);
|
||||
});
|
||||
|
||||
/* simulate the load process done by index.js
|
||||
*
|
||||
* 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: {
|
||||
queryString?: string;
|
||||
uriFragment?: string;
|
||||
config?: IConfigOptions;
|
||||
} = {},
|
||||
): void {
|
||||
const queryString = opts.queryString || "";
|
||||
const uriFragment = opts.uriFragment || "";
|
||||
|
||||
windowLocation = {
|
||||
search: queryString,
|
||||
hash: uriFragment,
|
||||
toString: function (): string {
|
||||
return this.search + this.hash;
|
||||
},
|
||||
} as Location;
|
||||
|
||||
function onNewScreen(screen: string): void {
|
||||
console.log(Date.now() + " newscreen " + screen);
|
||||
const hash = "#/" + screen;
|
||||
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: Location): { screen: string; params: QueryDict } {
|
||||
const fragparts = parseQsFromFragment(location);
|
||||
return {
|
||||
screen: fragparts.location.substring(1),
|
||||
params: fragparts.params,
|
||||
};
|
||||
}
|
||||
|
||||
const fragParts = parseQsFromFragment(windowLocation);
|
||||
|
||||
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==",
|
||||
},
|
||||
features: {
|
||||
feature_rust_crypto: false,
|
||||
},
|
||||
...(opts.config ?? {}),
|
||||
} as IConfigOptions;
|
||||
|
||||
PlatformPeg.set(new WebPlatform());
|
||||
|
||||
const params = parseQs(windowLocation);
|
||||
|
||||
tokenLoginCompletePromise = new Promise<void>((resolve) => {
|
||||
matrixChat = render(
|
||||
<MatrixChat
|
||||
onNewScreen={onNewScreen}
|
||||
config={config!}
|
||||
realQueryParams={params}
|
||||
startingFragmentQueryParams={fragParts.params}
|
||||
enableGuest={true}
|
||||
onTokenLoginCompleted={resolve}
|
||||
initialScreenAfterLogin={getScreenFromLocation(windowLocation!)}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// set an expectation that we will get a call to /sync, then flush
|
||||
// http requests until we do.
|
||||
//
|
||||
// returns a promise resolving to the received request
|
||||
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: SERVER_SUPPORTED_MATRIX_VERSIONS,
|
||||
unstable_features: {},
|
||||
});
|
||||
const isGuest = opts?.isGuest;
|
||||
if (!isGuest) {
|
||||
// the call to create the LL filter
|
||||
httpBackend.when("POST", "/filter").respond(200, { filter_id: "llfid" });
|
||||
httpBackend.when("GET", "/pushrules").respond(200, {});
|
||||
}
|
||||
httpBackend
|
||||
.when("GET", "/sync")
|
||||
.check((r) => {
|
||||
syncRequest = r;
|
||||
})
|
||||
.respond(200, {});
|
||||
|
||||
for (let attempts = 10; attempts > 0; attempts--) {
|
||||
console.log(Date.now() + " waiting for /sync");
|
||||
if (syncRequest) {
|
||||
return syncRequest;
|
||||
}
|
||||
await httpBackend.flush(undefined);
|
||||
}
|
||||
throw new Error("Gave up waiting for /sync");
|
||||
}
|
||||
|
||||
describe("Clean load with no stored credentials:", function () {
|
||||
it("gives a welcome page by default", function () {
|
||||
loadApp();
|
||||
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// at this point, we're trying to do a guest registration;
|
||||
// we expect a spinner
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(403, "Guest access is disabled");
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return waitForWelcomeComponent(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
return waitFor(() => expect(windowLocation?.hash).toEqual("#/welcome"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should follow the original link after successful login", function () {
|
||||
loadApp({
|
||||
uriFragment: "#/room/!room:id",
|
||||
});
|
||||
|
||||
// Pass the liveliness checks
|
||||
httpBackend.when("GET", "/versions").respond(200, { versions: SERVER_SUPPORTED_MATRIX_VERSIONS });
|
||||
httpBackend.when("GET", "/_matrix/identity/v2").respond(200, {});
|
||||
httpBackend
|
||||
.when("GET", "/_matrix/client/unstable/org.matrix.msc2965/auth_issuer")
|
||||
.respond(404, { errcode: "M_UNRECOGNIZED", error: "Unrecognized request" });
|
||||
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// at this point, we're trying to do a guest registration;
|
||||
// we expect a spinner
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(403, "Guest access is disabled");
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return sleep(10);
|
||||
})
|
||||
.then(() => {
|
||||
return moveFromWelcomeToLogin(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
return completeLogin(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
// once the sync completes, we should have a room view
|
||||
return awaitRoomView(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
expect(windowLocation?.hash).toEqual("#/room/!room:id");
|
||||
|
||||
// and the localstorage should have been updated
|
||||
expect(localStorage.getItem("mx_user_id")).toEqual("@user:id");
|
||||
expect(localStorage.getItem("mx_access_token")).toEqual("access_token");
|
||||
expect(localStorage.getItem("mx_hs_url")).toEqual(DEFAULT_HS_URL);
|
||||
expect(localStorage.getItem("mx_is_url")).toEqual(DEFAULT_IS_URL);
|
||||
});
|
||||
});
|
||||
|
||||
it.skip("should not register as a guest when using a #/login link", function () {
|
||||
loadApp({
|
||||
uriFragment: "#/login",
|
||||
});
|
||||
|
||||
// Pass the liveliness checks
|
||||
httpBackend.when("GET", "/versions").respond(200, { versions: SERVER_SUPPORTED_MATRIX_VERSIONS });
|
||||
httpBackend.when("GET", "/_matrix/identity/v2").respond(200, {});
|
||||
|
||||
return awaitLoginComponent(matrixChat)
|
||||
.then(async () => {
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
|
||||
// we expect a single <Login> component
|
||||
await screen.findByRole("main");
|
||||
screen.getAllByText("Sign in");
|
||||
|
||||
// the only outstanding request should be a GET /login
|
||||
// (in particular there should be no /register request for
|
||||
// guest registration).
|
||||
const allowedRequests = ["/_matrix/client/v3/login", "/versions", "/_matrix/identity/v2"];
|
||||
for (const req of httpBackend.requests) {
|
||||
if (req.method === "GET" && allowedRequests.find((p) => req.path.endsWith(p))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected HTTP request to ${req}`);
|
||||
}
|
||||
return completeLogin(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
expect(matrixChat?.container.querySelector(".mx_HomePage")).toBeTruthy();
|
||||
expect(windowLocation?.hash).toEqual("#/home");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("MatrixClient rehydrated from stored credentials:", function () {
|
||||
beforeEach(async function () {
|
||||
localStorage.setItem("mx_hs_url", "http://localhost");
|
||||
localStorage.setItem("mx_is_url", "http://localhost");
|
||||
localStorage.setItem("mx_access_token", "access_token");
|
||||
localStorage.setItem("mx_user_id", "@me:localhost");
|
||||
localStorage.setItem("mx_device_id", "QWERTYUIOP");
|
||||
localStorage.setItem("mx_last_room_id", "!last_room:id");
|
||||
|
||||
// Create a crypto store as well to satisfy storage consistency checks
|
||||
const cryptoStore = new IndexedDBCryptoStore(indexedDB, "matrix-js-sdk:crypto");
|
||||
await cryptoStore.startup();
|
||||
});
|
||||
|
||||
it("shows the last known room by default", function () {
|
||||
loadApp();
|
||||
|
||||
return awaitLoggedIn(matrixChat!)
|
||||
.then(() => {
|
||||
// we are logged in - let the sync complete
|
||||
return expectAndAwaitSync();
|
||||
})
|
||||
.then(() => {
|
||||
// once the sync completes, we should have a room view
|
||||
return awaitRoomView(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
expect(windowLocation?.hash).toEqual("#/room/!last_room:id");
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a home page by default if we have no joined rooms", function () {
|
||||
localStorage.removeItem("mx_last_room_id");
|
||||
|
||||
loadApp();
|
||||
|
||||
return awaitLoggedIn(matrixChat!)
|
||||
.then(() => {
|
||||
// we are logged in - let the sync complete
|
||||
return expectAndAwaitSync();
|
||||
})
|
||||
.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");
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a room view if we followed a room link", function () {
|
||||
loadApp({
|
||||
uriFragment: "#/room/!room:id",
|
||||
});
|
||||
|
||||
return awaitLoggedIn(matrixChat!)
|
||||
.then(() => {
|
||||
// we are logged in - let the sync complete
|
||||
return expectAndAwaitSync();
|
||||
})
|
||||
.then(() => {
|
||||
// once the sync completes, we should have a room view
|
||||
return awaitRoomView(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
expect(windowLocation?.hash).toEqual("#/room/!room:id");
|
||||
});
|
||||
});
|
||||
|
||||
describe("/#/login link:", function () {
|
||||
beforeEach(function () {
|
||||
loadApp({
|
||||
uriFragment: "#/login",
|
||||
});
|
||||
|
||||
// give the UI a chance to display
|
||||
return expectAndAwaitSync();
|
||||
});
|
||||
|
||||
it("does not show a login view", async function () {
|
||||
await awaitRoomView(matrixChat);
|
||||
|
||||
await screen.getByRole("tree", { name: "Spaces" });
|
||||
expect(screen.queryAllByText("Sign in")).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Guest auto-registration:", function () {
|
||||
it("shows a welcome page by default", function () {
|
||||
loadApp();
|
||||
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// at this point, we're trying to do a guest registration;
|
||||
// we expect a spinner
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@guest:localhost",
|
||||
device_id: "QWERTYUIOP",
|
||||
access_token: "secret_token",
|
||||
});
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
return awaitLoggedIn(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
// we are logged in - let the sync complete
|
||||
return expectAndAwaitSync({ isGuest: true });
|
||||
})
|
||||
.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");
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the default homeserver to register with", function () {
|
||||
loadApp();
|
||||
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// at this point, we're trying to do a guest registration;
|
||||
// we expect a spinner
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.path.startsWith(DEFAULT_HS_URL)).toBe(true);
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@guest:localhost",
|
||||
access_token: "secret_token",
|
||||
});
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
return awaitLoggedIn(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
return expectAndAwaitSync({ isGuest: true });
|
||||
})
|
||||
.then((req) => {
|
||||
expect(req.path.startsWith(DEFAULT_HS_URL)).toBe(true);
|
||||
|
||||
// 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(MatrixClientPeg.safeGet().baseUrl).toEqual(DEFAULT_HS_URL);
|
||||
expect(MatrixClientPeg.safeGet().idBaseUrl).toEqual(DEFAULT_IS_URL);
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a room view if we followed a room link", function () {
|
||||
loadApp({
|
||||
uriFragment: "#/room/!room:id",
|
||||
});
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// at this point, we're trying to do a guest registration;
|
||||
// we expect a spinner
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@guest:localhost",
|
||||
access_token: "secret_token",
|
||||
});
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
return awaitLoggedIn(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
return expectAndAwaitSync({ isGuest: true });
|
||||
})
|
||||
.then(() => {
|
||||
// once the sync completes, we should have a room view
|
||||
return awaitRoomView(matrixChat);
|
||||
})
|
||||
.then(() => {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
expect(windowLocation?.hash).toEqual("#/room/!room:id");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Login as user", function () {
|
||||
beforeEach(function () {
|
||||
// first we have to load the homepage
|
||||
loadApp();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/register")
|
||||
.check(function (req) {
|
||||
expect(req.queryParams?.kind).toEqual("guest");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@guest:localhost",
|
||||
access_token: "secret_token",
|
||||
});
|
||||
|
||||
return httpBackend
|
||||
.flush(undefined)
|
||||
.then(() => {
|
||||
return awaitLoggedIn(matrixChat!);
|
||||
})
|
||||
.then(() => {
|
||||
// we got a sync spinner - let the sync complete
|
||||
return expectAndAwaitSync();
|
||||
})
|
||||
.then(async () => {
|
||||
// once the sync completes, we should have a home page
|
||||
await waitFor(() => matrixChat?.container.querySelector(".mx_HomePage"));
|
||||
|
||||
// we simulate a click on the 'login' button by firing off
|
||||
// the relevant dispatch.
|
||||
//
|
||||
// XXX: is it an anti-pattern to access the react-sdk's
|
||||
// dispatcher in this way? Is it better to find the login
|
||||
// button and simulate a click? (we might have to arrange
|
||||
// for it to be shown - it's not always, due to the
|
||||
// collapsing left panel
|
||||
|
||||
dis.dispatch({ action: "start_login" });
|
||||
|
||||
return awaitLoginComponent(matrixChat);
|
||||
});
|
||||
});
|
||||
|
||||
it("should give us a login page", async function () {
|
||||
// we expect a single <Login> component
|
||||
await screen.findByRole("main");
|
||||
screen.getAllByText("Sign in");
|
||||
|
||||
expect(windowLocation?.hash).toEqual("#/login");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Token login:", function () {
|
||||
it("logs in successfully", function () {
|
||||
localStorage.setItem("mx_sso_hs_url", "https://homeserver");
|
||||
localStorage.setItem("mx_sso_is_url", "https://idserver");
|
||||
loadApp({
|
||||
queryString: "?loginToken=secretToken",
|
||||
});
|
||||
|
||||
return sleep(1)
|
||||
.then(async () => {
|
||||
// we expect a spinner while we're logging in
|
||||
await waitForLoadingSpinner();
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/login")
|
||||
.check(function (req) {
|
||||
expect(req.path).toMatch(new RegExp("^https://homeserver/"));
|
||||
expect(req.data.type).toEqual("m.login.token");
|
||||
expect(req.data.token).toEqual("secretToken");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@user:localhost",
|
||||
device_id: "DEVICE_ID",
|
||||
access_token: "access_token",
|
||||
});
|
||||
|
||||
return httpBackend.flush(undefined);
|
||||
})
|
||||
.then(() => {
|
||||
// at this point, MatrixChat should fire onTokenLoginCompleted, which
|
||||
// makes index.js reload the app. We're not going to attempt to
|
||||
// simulate the reload - just check that things are left in the
|
||||
// right state for the reloaded app.
|
||||
|
||||
return tokenLoginCompletePromise;
|
||||
})
|
||||
.then(() => {
|
||||
return expectAndAwaitSync().catch((e) => {
|
||||
throw new Error("Never got /sync after login: did the client start?");
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
// check that the localstorage has been set up in such a way that
|
||||
// the reloaded app can pick up where we leave off.
|
||||
expect(localStorage.getItem("mx_user_id")).toEqual("@user:localhost");
|
||||
expect(localStorage.getItem("mx_access_token")).toEqual("access_token");
|
||||
expect(localStorage.getItem("mx_hs_url")).toEqual("https://homeserver");
|
||||
expect(localStorage.getItem("mx_is_url")).toEqual("https://idserver");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// check that we have a Login component, send a 'user:pass' login,
|
||||
// and await the HTTP requests.
|
||||
async function completeLogin(matrixChat: RenderResult): Promise<void> {
|
||||
// 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(undefined); // We already would have tried the GET /login request
|
||||
|
||||
httpBackend
|
||||
.when("POST", "/login")
|
||||
.check(function (req) {
|
||||
expect(req.data.type).toEqual("m.login.password");
|
||||
expect(req.data.identifier.type).toEqual("m.id.user");
|
||||
expect(req.data.identifier.user).toEqual("user");
|
||||
expect(req.data.password).toEqual("pass");
|
||||
})
|
||||
.respond(200, {
|
||||
user_id: "@user:id",
|
||||
device_id: "DEVICE_ID",
|
||||
access_token: "access_token",
|
||||
});
|
||||
|
||||
// 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(undefined)
|
||||
.then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return sleep(1);
|
||||
})
|
||||
.then(() => {
|
||||
return expectAndAwaitSync().catch((e) => {
|
||||
throw new Error("Never got /sync after login: did the client start?");
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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 }: ActionPayload): void => {
|
||||
if (action !== "on_logged_in") {
|
||||
return;
|
||||
}
|
||||
console.log(Date.now() + ": Received on_logged_in action");
|
||||
dis.unregister(dispatcherRef);
|
||||
resolve(sleep(1));
|
||||
};
|
||||
const dispatcherRef = dis.register(onAction);
|
||||
console.log(Date.now() + ": Waiting for on_logged_in action");
|
||||
});
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
function moveFromWelcomeToLogin(matrixChat?: RenderResult): Promise<void> {
|
||||
dis.dispatch({ action: "start_login" });
|
||||
return awaitLoginComponent(matrixChat);
|
||||
}
|
||||
@@ -16,40 +16,6 @@ limitations under the License.
|
||||
|
||||
import { RenderResult, screen, waitFor } from "@testing-library/react";
|
||||
|
||||
export function cleanLocalstorage(): void {
|
||||
window.localStorage.clear();
|
||||
}
|
||||
|
||||
export function deleteIndexedDB(dbName: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!window.indexedDB) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
console.log(`${startTime}: Removing indexeddb instance: ${dbName}`);
|
||||
const req = window.indexedDB.deleteDatabase(dbName);
|
||||
|
||||
req.onblocked = (): void => {
|
||||
console.log(`${Date.now()}: can't yet delete indexeddb ${dbName} because it is open elsewhere`);
|
||||
};
|
||||
|
||||
req.onerror = (ev): void => {
|
||||
reject(new Error(`${Date.now()}: unable to delete indexeddb ${dbName}: ${req.error?.message}`));
|
||||
};
|
||||
|
||||
req.onsuccess = (): void => {
|
||||
const now = Date.now();
|
||||
console.log(`${now}: Removed indexeddb instance: ${dbName} in ${now - startTime} ms`);
|
||||
resolve();
|
||||
};
|
||||
}).catch((e) => {
|
||||
console.error(`${Date.now()}: Error removing indexeddb instance ${dbName}: ${e}`);
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
// wait for loading page
|
||||
export async function waitForLoadingSpinner(): Promise<void> {
|
||||
await screen.findByRole("progressbar");
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
"esModuleInterop": true,
|
||||
"module": "es2022",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2016",
|
||||
"target": "es2018",
|
||||
"noUnusedLocals": true,
|
||||
"sourceMap": false,
|
||||
"outDir": "./lib",
|
||||
"declaration": true,
|
||||
"jsx": "react",
|
||||
"lib": ["es2021", "dom", "dom.iterable"],
|
||||
"lib": ["es2022", "dom", "dom.iterable"],
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
|
||||
@@ -274,10 +274,6 @@ module.exports = (env, argv) => {
|
||||
// there is no need for webpack to parse them - they can just be
|
||||
// included as-is.
|
||||
/highlight\.js[\\/]lib[\\/]languages/,
|
||||
|
||||
// olm takes ages for webpack to process, and it's already heavily
|
||||
// optimised, so there is little to gain by us uglifying it.
|
||||
/olm[\\/](javascript[\\/])?olm\.js$/,
|
||||
],
|
||||
rules: [
|
||||
useHMR && {
|
||||
@@ -443,20 +439,6 @@ module.exports = (env, argv) => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// the olm library wants to load its own wasm, rather than have webpack do it.
|
||||
// We therefore use the `file-loader` to tell webpack to dump the contents to
|
||||
// a separate file and return the name, and override the default `type` for `.wasm` files
|
||||
// (which is `webassembly/experimental` under webpack 4) to stop webpack trying to interpret
|
||||
// the filename as webassembly. (see also https://github.com/webpack/webpack/issues/6725)
|
||||
test: /olm\.wasm$/,
|
||||
loader: "file-loader",
|
||||
type: "javascript/auto",
|
||||
options: {
|
||||
name: "[name].[hash:7].[ext]",
|
||||
outputPath: ".",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Fix up the name of the opus-recorder worker (react-sdk dependency).
|
||||
// We more or less just want it to be clear it's for opus and not something else.
|
||||
@@ -498,8 +480,11 @@ module.exports = (env, argv) => {
|
||||
},
|
||||
},
|
||||
{
|
||||
// Same deal as olm.wasm: the decoderWorker wants to load the wasm artifact
|
||||
// itself.
|
||||
// The decoderWorker wants to load its own wasm, rather than have webpack do it.
|
||||
// We therefore use the `file-loader` to tell webpack to dump the contents to
|
||||
// a separate file and return the name, and override the default `type` for `.wasm` files
|
||||
// (which is `webassembly/experimental` under webpack 4) to stop webpack trying to interpret
|
||||
// the filename as webassembly. (see also https://github.com/webpack/webpack/issues/6725)
|
||||
test: /decoderWorker\.min\.wasm$/,
|
||||
loader: "file-loader",
|
||||
type: "javascript/auto",
|
||||
@@ -657,8 +642,8 @@ module.exports = (env, argv) => {
|
||||
|
||||
// This exports our CSS using the splitChunks and loaders above.
|
||||
new MiniCssExtractPlugin({
|
||||
filename: useHMR ? "bundles/[name].css" : "bundles/[hash]/[name].css",
|
||||
chunkFilename: useHMR ? "bundles/[name].css" : "bundles/[hash]/[name].css",
|
||||
filename: useHMR ? "bundles/[name].css" : "bundles/[fullhash]/[name].css",
|
||||
chunkFilename: useHMR ? "bundles/[name].css" : "bundles/[fullhash]/[name].css",
|
||||
ignoreOrder: false, // Enable to remove warnings about conflicting order
|
||||
}),
|
||||
|
||||
@@ -750,7 +735,6 @@ module.exports = (env, argv) => {
|
||||
{ from: "vector-icons/**", context: path.resolve(__dirname, "res") },
|
||||
{ from: "decoder-ring/**", context: path.resolve(__dirname, "res") },
|
||||
{ from: "media/**", context: path.resolve(__dirname, "node_modules/matrix-react-sdk/res/") },
|
||||
"node_modules/@matrix-org/olm/olm_legacy.js",
|
||||
{ from: "config.json", noErrorOnMissing: true },
|
||||
"contribute.json",
|
||||
],
|
||||
@@ -774,9 +758,9 @@ module.exports = (env, argv) => {
|
||||
// directory and symlink it into place - this allows users who loaded
|
||||
// an older version of the application to continue to access webpack
|
||||
// chunks even after the app is redeployed.
|
||||
filename: "bundles/[hash]/[name].js",
|
||||
chunkFilename: "bundles/[hash]/[name].js",
|
||||
webassemblyModuleFilename: "bundles/[hash]/[modulehash].wasm",
|
||||
filename: "bundles/[fullhash]/[name].js",
|
||||
chunkFilename: "bundles/[fullhash]/[name].js",
|
||||
webassemblyModuleFilename: "bundles/[fullhash]/[modulehash].wasm",
|
||||
},
|
||||
|
||||
// configuration for the webpack-dev-server
|
||||
|
||||
Reference in New Issue
Block a user