Compare commits

...

21 Commits

Author SHA1 Message Date
RiotRobot 8ba30bc4ef v19.5.0-rc.2 2022-09-06 12:21:02 +01:00
RiotRobot 6e28634819 Prepare changelog for v19.5.0-rc.2 2022-09-06 12:21:01 +01:00
RiotRobot b1e70c5404 Fix release.sh 2022-09-06 12:20:41 +01:00
RiotRobot b11c502a40 Merge branch 'develop' into staging 2022-09-06 12:18:10 +01:00
Michael Telatynski 167f51c8cd Fix release script for layers without a release_config.yaml file (#2642) 2022-09-06 12:17:40 +01:00
RiotRobot 5d2753241e Merge branch 'develop' into staging 2022-09-06 12:11:27 +01:00
Michael Telatynski 274fe447fd Simplify releases: move npm publishing to gha, consolidate scripts (#2616)
* Remove stale comment re dependency

* Move npm publishing from release.sh to GHA

* Extract js-sdk & react-sdk post release steps

* Consolidate release subproject upgrade management
2022-09-06 12:10:26 +01:00
RiotRobot c0f1849a83 v19.5.0-rc.1 2022-09-06 11:28:19 +01:00
RiotRobot 7b2b618d26 Prepare changelog for v19.5.0-rc.1 2022-09-06 11:28:19 +01:00
Kerry 37187ef347 Test typescriptification - room-member and room-state (#2601)
* renamed:    spec/MockStorageApi.js -> spec/MockStorageApi.ts

* renamed:    spec/olm-loader.js -> spec/olm-loader.t

* renamed:    spec/unit/room-state.spec.js -> spec/unit/room-state.spec.ts

* ts fixes in room-state.spec

* renamed:    spec/unit/room-member.spec.js -> spec/unit/room-member.spec.ts

* ts fixes in room-member.spec

* strict mode fixes for MockStorageApi

* strict ts fixes in room-state

* strict errors
2022-09-05 10:38:05 +02:00
3nprob e87ce873b0 utils: Fix bug in deepCompare which would incorrectly return objects with disjoint keys as equal (#2586)
* utils: Fix bug in deepCompare which would incorrectly return objects with disjoint keys as equal

* Fix bugs in sync test

This test wrongly asserted that `initialSyncLimit` would be used to make a filter
It is used only for the initial sync inline filter, and not in POST /filter

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2022-09-01 21:36:24 +00:00
Michael Weimann 207171efd6 Link contributing to Element Web (#2621) 2022-09-01 13:49:47 +02:00
Travis Ralston 8cc5efdf46 Update CHANGELOG.md 2022-08-31 11:28:56 -06:00
RiotRobot cbcf47d5c0 Resetting package fields for development 2022-08-31 16:26:39 +01:00
RiotRobot bbaa0e6536 Merge branch 'master' into develop 2022-08-31 16:26:39 +01:00
renovate[bot] 1fbd8983ed Lock file maintenance (#2625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-29 00:00:48 -04:00
Faye Duxovni 1c77816dbd Use deep equality comparisons when searching for outgoing key requests by target (#2623) 2022-08-27 03:13:08 +00:00
Michael Telatynski c1160f40c2 Tweak tsc-strict config (#2620) 2022-08-25 09:49:58 +01:00
Michael Telatynski b789cc5933 Refactor Sync and fix initialSyncLimit (#2587)
* Small tidy-up to sync.ts

* Convert doSync into a while loop

* Apply `initialSyncLimit` only to initial syncs

* Convert matrix-client-syncing spec to TS

* Add tests around initial sync filtering

* Switch confusing filterId field for `filter`

* Tweak doSync error control flow

* Fix error control flow intricacies

* use includes

* Add tests

* Fix some strict mode errors

* Fix more strict mode errors

* Fix some strict mode errors
2022-08-23 16:25:54 +01:00
Jonathan Otto 5e4474b959 Fix room membership race with PREPARED event (#2613)
* Fix room membership race with PREPARED event

See the call site of the original triggering event of this function: https://github.com/matrix-org/matrix-js-sdk/blob/b265d795a427c6d30ccdf279a09f7836509df863/src/sliding-sync.ts#L789-L806

I think the bug is current code assumes downstream event listeners of `SlidingSyncEvent.RoomData` have synchronous execution so that by the time it emits `SlidingSyncState.Complete`, and eventually `SyncState.Prepared` the room state is correct. But since SlidingSyncSdk's `processRoomData` is async, and the membership field was being set after the async, it looks like `SlidingSyncState.Complete` was being fired before the membership field was set.

* Rm whitespace
2022-08-23 15:01:33 +00:00
Michael Telatynski 8e646ea584 Add static analysis for tsc --strict (#2615)
* Initial attempt at CI to annotate new TSC errors

* Make tsconfig file valid

* enable debug

* Specify commit

* Fix commit specification

* Switch back to main

* Tweak permissions

* Add strict mode failure

* Attempt number two

* Fix ts-extra-args

* Add static analysis for tsc --strict
2022-08-23 14:02:50 +01:00
28 changed files with 1335 additions and 1349 deletions
+40
View File
@@ -0,0 +1,40 @@
# Must only be called from `release#published` triggers
name: Publish to npm
on:
workflow_call:
secrets:
NPM_TOKEN:
required: true
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
npm:
name: Publish to npm
runs-on: ubuntu-latest
steps:
- name: 🧮 Checkout code
uses: actions/checkout@v3
- name: 🔧 Yarn cache
uses: actions/setup-node@v3
with:
cache: "yarn"
- name: 🔨 Install dependencies
run: "yarn install --pure-lockfile"
- name: 🚀 Publish to npm
id: npm-publish
uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}
access: public
tag: next
- name: 🎖️ Add `latest` dist-tag to final releases
if: github.event.release.prerelease == false
run: |
package=$(cat package.json | jq -er .name)
npm dist-tag add "$package@$release" latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
release: ${{ steps.npm-publish.outputs.version }}
@@ -24,7 +24,6 @@ jobs:
- name: 📋 Copy to temp
run: |
ls -lah
tag="${{ github.ref_name }}"
version="${tag#v}"
echo "VERSION=$version" >> $GITHUB_ENV
@@ -51,3 +50,9 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
keep_files: true
publish_dir: .
npm:
name: Publish
uses: matrix-org/matrix-js-sdk/.github/workflows/release-npm.yml@develop
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+35
View File
@@ -54,3 +54,38 @@ jobs:
- name: Generate Docs
run: "yarn run gendoc"
tsc-strict:
name: Typescript Strict Error Checker
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
pull-requests: read
checks: write
steps:
- uses: actions/checkout@v3
- name: Get diff lines
id: diff
uses: Equip-Collaboration/diff-line-numbers@v1.0.0
with:
include: '["\\.tsx?$"]'
- name: Detecting files changed
id: files
uses: futuratrepadeira/changed-files@v3.2.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pattern: '^.*\.tsx?$'
- uses: t3chguy/typescript-check-action@main
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
use-check: false
check-fail-mode: added
output-behaviour: annotate
ts-extra-args: '--strict'
files-changed: ${{ steps.files.outputs.files_updated }}
files-added: ${{ steps.files.outputs.files_created }}
files-deleted: ${{ steps.files.outputs.files_deleted }}
line-numbers: ${{ steps.diff.outputs.lineNumbers }}
+17
View File
@@ -1,6 +1,23 @@
Changes in [19.5.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v19.5.0-rc.2) (2022-09-06)
============================================================================================================
Changes in [19.5.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v19.5.0-rc.1) (2022-09-06)
============================================================================================================
## 🐛 Bug Fixes
* Fix bug in deepCompare which would incorrectly return objects with disjoint keys as equal ([\#2586](https://github.com/matrix-org/matrix-js-sdk/pull/2586)). Contributed by @3nprob.
* Refactor Sync and fix `initialSyncLimit` ([\#2587](https://github.com/matrix-org/matrix-js-sdk/pull/2587)).
* Use deep equality comparisons when searching for outgoing key requests by target ([\#2623](https://github.com/matrix-org/matrix-js-sdk/pull/2623)). Contributed by @duxovni.
* Fix room membership race with PREPARED event ([\#2613](https://github.com/matrix-org/matrix-js-sdk/pull/2613)). Contributed by @jotto.
Changes in [19.4.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v19.4.0) (2022-08-31)
==================================================================================================
## 🔒 Security
* Fix for [CVE-2022-36059](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=CVE%2D2022%2D36059)
Find more details at https://matrix.org/blog/2022/08/31/security-releases-matrix-js-sdk-19-4-0-and-matrix-react-sdk-3-53-0
## ✨ Features
* Re-emit room state events on rooms ([\#2607](https://github.com/matrix-org/matrix-js-sdk/pull/2607)).
* Add ability to override built in room name generator for an i18n'able one ([\#2609](https://github.com/matrix-org/matrix-js-sdk/pull/2609)).
+1 -280
View File
@@ -1,284 +1,5 @@
Contributing code to matrix-js-sdk
==================================
Everyone is welcome to contribute code to matrix-js-sdk, provided that they are
willing to license their contributions under the same license as the project
itself. We follow a simple 'inbound=outbound' model for contributions: the act
of submitting an 'inbound' contribution means that the contributor agrees to
license the code under the same terms as the project's overall 'outbound'
license - in this case, Apache Software License v2 (see
[LICENSE](LICENSE)).
matrix-js-sdk follows the same pattern as https://github.com/vector-im/element-web/blob/develop/CONTRIBUTING.md
How to contribute
-----------------
The preferred and easiest way to contribute changes to the project is to fork
it on github, and then create a pull request to ask us to pull your changes
into our repo (https://help.github.com/articles/using-pull-requests/)
We use GitHub's pull request workflow to review the contribution, and either
ask you to make any refinements needed or merge it and make them ourselves.
Things that should go into your PR description:
* A changelog entry in the `Notes` section (see below)
* References to any bugs fixed by the change (in GitHub's `Fixes` notation)
* Describe the why and what is changing in the PR description so it's easy for
onlookers and reviewers to onboard and context switch. This information is
also helpful when we come back to look at this in 6 months and ask "why did
we do it like that?" we have a chance of finding out.
* Why didn't it work before? Why does it work now? What use cases does it
unlock?
* If you find yourself adding information on how the code works or why you
chose to do it the way you did, make sure this information is instead
written as comments in the code itself.
* Sometimes a PR can change considerably as it is developed. In this case,
the description should be updated to reflect the most recent state of
the PR. (It can be helpful to retain the old content under a suitable
heading, for additional context.)
* Include both **before** and **after** screenshots to easily compare and discuss
what's changing.
* Include a step-by-step testing strategy so that a reviewer can check out the
code locally and easily get to the point of testing your change.
* Add comments to the diff for the reviewer that might help them to understand
why the change is necessary or how they might better understand and review it.
We rely on information in pull request to populate the information that goes
into the changelogs our users see, both for the JS SDK itself and also for some
projects based on it. This is picked up from both labels on the pull request and
the `Notes:` annotation in the description. By default, the PR title will be
used for the changelog entry, but you can specify more options, as follows.
To add a longer, more detailed description of the change for the changelog:
*Fix llama herding bug*
```
Notes: Fix a bug (https://github.com/matrix-org/notaproject/issues/123) where the 'Herd' button would not herd more than 8 Llamas if the moon was in the waxing gibbous phase
```
For some PRs, it's not useful to have an entry in the user-facing changelog (this is
the default for PRs labelled with `T-Task`):
*Remove outdated comment from `Ungulates.ts`*
```
Notes: none
```
Sometimes, you're fixing a bug in a downstream project, in which case you want
an entry in that project's changelog. You can do that too:
*Fix another herding bug*
```
Notes: Fix a bug where the `herd()` function would only work on Tuesdays
element-web notes: Fix a bug where the 'Herd' button only worked on Tuesdays
```
This example is for Element Web. You can specify:
* matrix-react-sdk
* element-web
* element-desktop
If your PR introduces a breaking change, use the `Notes` section in the same
way, additionally adding the `X-Breaking-Change` label (see below). There's no need
to specify in the notes that it's a breaking change - this will be added
automatically based on the label - but remember to tell the developer how to
migrate:
*Remove legacy class*
```
Notes: Remove legacy `Camelopard` class. `Giraffe` should be used instead.
```
Other metadata can be added using labels.
* `X-Breaking-Change`: A breaking change - adding this label will mean the change causes a *major* version bump.
* `T-Enhancement`: A new feature - adding this label will mean the change causes a *minor* version bump.
* `T-Defect`: A bug fix (in either code or docs).
* `T-Task`: No user-facing changes, eg. code comments, CI fixes, refactors or tests. Won't have a changelog entry unless you specify one.
If you don't have permission to add labels, your PR reviewer(s) can work with you
to add them: ask in the PR description or comments.
We use continuous integration, and all pull requests get automatically tested:
if your change breaks the build, then the PR will show that there are failed
checks, so please check back after a few minutes.
Tests
-----
Your PR should include tests.
For new user facing features in `matrix-react-sdk` or `element-web`, you
must include:
1. Comprehensive unit tests written in Jest. These are located in `/test`.
2. "happy path" end-to-end tests.
These are located in `/test/end-to-end-tests` in `matrix-react-sdk`, and
are run using `element-web`. Ideally, you would also include tests for edge
and error cases.
Unit tests are expected even when the feature is in labs. It's good practice
to write tests alongside the code as it ensures the code is testable from
the start, and gives you a fast feedback loop while you're developing the
functionality. End-to-end tests should be added prior to the feature
leaving labs, but don't have to be present from the start (although it might
be beneficial to have some running early, so you can test things faster).
For bugs in those repos, your change must include at least one unit test or
end-to-end test; which is best depends on what sort of test most concisely
exercises the area.
Changes to `matrix-js-sdk` must be accompanied by unit tests written in Jest.
These are located in `/spec/`.
When writing unit tests, please aim for a high level of test coverage
for new code - 80% or greater. If you cannot achieve that, please document
why it's not possible in your PR.
Some sections of code are not sensible to add coverage for, such as those
which explicitly inhibit noisy logging for tests. Which can be hidden using
an istanbul magic comment as [documented here][1]. See example:
```javascript
/* istanbul ignore if */
if (process.env.NODE_ENV !== "test") {
logger.error("Log line that is noisy enough in tests to want to skip");
}
```
Tests validate that your change works as intended and also document
concisely what is being changed. Ideally, your new tests fail
prior to your change, and succeed once it has been applied. You may
find this simpler to achieve if you write the tests first.
If you're spiking some code that's experimental and not being used to support
production features, exceptions can be made to requirements for tests.
Note that tests will still be required in order to ship the feature, and it's
strongly encouraged to think about tests early in the process, as adding
tests later will become progressively more difficult.
If you're not sure how to approach writing tests for your change, ask for help
in [#element-dev](https://matrix.to/#/#element-dev:matrix.org).
Code style
----------
The js-sdk aims to target TypeScript/ES6. All new files should be written in
TypeScript and existing files should use ES6 principles where possible.
Members should not be exported as a default export in general - it causes problems
with the architecture of the SDK (index file becomes less clear) and could
introduce naming problems (as default exports get aliased upon import). In
general, avoid using `export default`.
The remaining code-style for matrix-js-sdk is not formally documented, but
contributors are encouraged to read the
[code style document for matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md)
and follow the principles set out there.
Please ensure your changes match the cosmetic style of the existing project,
and ***never*** mix cosmetic and functional changes in the same commit, as it
makes it horribly hard to review otherwise.
Attribution
-----------
Everyone who contributes anything to Matrix is welcome to be listed in the
AUTHORS.rst file for the project in question. Please feel free to include a
change to AUTHORS.rst in your pull request to list yourself and a short
description of the area(s) you've worked on. Also, we sometimes have swag to
give away to contributors - if you feel that Matrix-branded apparel is missing
from your life, please mail us your shipping address to matrix at matrix.org
and we'll try to fix it :)
Sign off
--------
In order to have a concrete record that your contribution is intentional
and you agree to license it under the same terms as the project's license, we've
adopted the same lightweight approach that the Linux Kernel
(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker
(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
projects use: the DCO (Developer Certificate of Origin:
http://developercertificate.org/). This is a simple declaration that you wrote
the contribution or otherwise have the right to contribute it to Matrix:
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
If you agree to this for your contribution, then all that's needed is to
include the line in your commit or pull request comment:
```
Signed-off-by: Your Name <your@email.example.org>
```
We accept contributions under a legally identifiable name, such as your name on
government documentation or common-law names (names claimed by legitimate usage
or repute). Unfortunately, we cannot accept anonymous contributions at this
time.
Git allows you to add this signoff automatically when using the `-s` flag to
`git commit`, which uses the name and email set in your `user.name` and
`user.email` git configs.
If you forgot to sign off your commits before making your pull request and are
on Git 2.17+ you can mass signoff using rebase:
```
git rebase --signoff origin/develop
```
Review expectations
===================
See https://github.com/vector-im/element-meta/wiki/Review-process
Merge Strategy
==============
The preferred method for merging pull requests is squash merging to keep the
commit history trim, but it is up to the discretion of the team member merging
the change. We do not support rebase merges due to `allchange` being unable to
handle them. When merging make sure to leave the default commit title, or
at least leave the PR number at the end in brackets like by default.
When stacking pull requests, you may wish to do the following:
1. Branch from develop to your branch (branch1), push commits onto it and open a pull request
2. Branch from your base branch (branch1) to your work branch (branch2), push commits and open a pull request configuring the base to be branch1, saying in the description that it is based on your other PR.
3. Merge the first PR using a merge commit otherwise your stacked PR will need a rebase. Github will automatically adjust the base branch of your other PR to be develop.
[1]: https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "19.4.0",
"version": "19.5.0-rc.2",
"description": "Matrix Client-Server SDK for Javascript",
"engines": {
"node": ">=12.9.0"
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
#
# Script to perform a post-release steps of matrix-js-sdk.
#
# Requires:
# jq; install from your distribution's package manager (https://stedolan.github.io/jq/)
set -e
jq --version > /dev/null || (echo "jq is required: please install it"; kill $$)
if [ "$(git branch -lr | grep origin/develop -c)" -ge 1 ]; then
# When merging to develop, we need revert the `main` and `typings` fields if we adjusted them previously.
for i in main typings
do
# If a `lib` prefixed value is present, it means we adjusted the field
# earlier at publish time, so we should revert it now.
if [ "$(jq -r ".matrix_lib_$i" package.json)" != "null" ]; then
# If there's a `src` prefixed value, use that, otherwise delete.
# This is used to delete the `typings` field and reset `main` back
# to the TypeScript source.
src_value=$(jq -r ".matrix_src_$i" package.json)
if [ "$src_value" != "null" ]; then
jq ".$i = .matrix_src_$i" package.json > package.json.new && mv package.json.new package.json
else
jq "del(.$i)" package.json > package.json.new && mv package.json.new package.json
fi
fi
done
if [ -n "$(git ls-files --modified package.json)" ]; then
echo "Committing develop package.json"
git commit package.json -m "Resetting package fields for development"
fi
git push origin develop
fi
+70 -78
View File
@@ -3,19 +3,16 @@
# Script to perform a release of matrix-js-sdk and downstream projects.
#
# Requires:
# github-changelog-generator; install via:
# pip install git+https://github.com/matrix-org/github-changelog-generator.git
# jq; install from your distribution's package manager (https://stedolan.github.io/jq/)
# hub; install via brew (macOS) or source/pre-compiled binaries (debian) (https://github.com/github/hub) - Tested on v2.2.9
# npm; typically installed by Node.js
# yarn; install via brew (macOS) or similar (https://yarnpkg.com/docs/install/)
#
# Note: this script is also used to release matrix-react-sdk and element-web.
# Note: this script is also used to release matrix-react-sdk, element-web, and element-desktop.
set -e
jq --version > /dev/null || (echo "jq is required: please install it"; kill $$)
if [[ `command -v hub` ]] && [[ `hub --version` =~ hub[[:space:]]version[[:space:]]([0-9]*).([0-9]*) ]]; then
if [[ $(command -v hub) ]] && [[ $(hub --version) =~ hub[[:space:]]version[[:space:]]([0-9]*).([0-9]*) ]]; then
HUB_VERSION_MAJOR=${BASH_REMATCH[1]}
HUB_VERSION_MINOR=${BASH_REMATCH[2]}
if [[ $HUB_VERSION_MAJOR -lt 2 ]] || [[ $HUB_VERSION_MAJOR -eq 2 && $HUB_VERSION_MINOR -lt 5 ]]; then
@@ -26,7 +23,6 @@ else
echo "hub is required: please install it"
exit
fi
npm --version > /dev/null || (echo "npm is required: please install it"; kill $$)
yarn --version > /dev/null || (echo "yarn is required: please install it"; kill $$)
USAGE="$0 [-x] [-c changelog_file] vX.Y.Z"
@@ -37,7 +33,6 @@ $USAGE
-c changelog_file: specify name of file containing changelog
-x: skip updating the changelog
-n: skip publish to NPM
EOF
}
@@ -59,10 +54,8 @@ if ! git diff-files --quiet; then
fi
skip_changelog=
skip_npm=
changelog_file="CHANGELOG.md"
expected_npm_user="matrixdotorg"
while getopts hc:u:xzn f; do
while getopts hc:x f; do
case $f in
h)
help
@@ -74,21 +67,58 @@ while getopts hc:u:xzn f; do
x)
skip_changelog=1
;;
n)
skip_npm=1
;;
u)
expected_npm_user="$OPTARG"
;;
esac
done
shift `expr $OPTIND - 1`
shift $(expr $OPTIND - 1)
if [ $# -ne 1 ]; then
echo "Usage: $USAGE" >&2
exit 1
fi
function check_dependency {
echo "Checking version of $1..."
local depver=$(cat package.json | jq -r .dependencies[\"$1\"])
local latestver=$(yarn info -s "$1" dist-tags.next)
if [ "$depver" != "$latestver" ]
then
echo "The latest version of $1 is $latestver but package.json depends on $depver."
echo -n "Type 'u' to auto-upgrade, 'c' to continue anyway, or 'a' to abort:"
read resp
if [ "$resp" != "u" ] && [ "$resp" != "c" ]
then
echo "Aborting."
exit 1
fi
if [ "$resp" == "u" ]
then
echo "Upgrading $1 to $latestver..."
yarn add -E "$1@$latestver"
git add -u
git commit -m "Upgrade $1 to $latestver"
fi
fi
}
function reset_dependency {
echo "Resetting $1 to develop branch..."
yarn add "github:matrix-org/$1#develop"
git add -u
git commit -m "Reset $1 back to develop branch"
}
has_subprojects=0
if [ -f release_config.yaml ]; then
subprojects=$(cat release_config.yaml | python -c "import yaml; import sys; print(' '.join(list(yaml.load(sys.stdin)['subprojects'].keys())))" 2> /dev/null)
if [ "$?" -eq 0 ]; then
has_subprojects=1
echo "Checking subprojects for upgrades"
for proj in $subprojects; do
check_dependency "$proj"
done
fi
fi
# We use Git branch / commit dependencies for some packages, and Yarn seems
# to have a hard time getting that right. See also
# https://github.com/yarnpkg/yarn/issues/4734. As a workaround, we clean the
@@ -97,16 +127,6 @@ yarn cache clean
# Ensure all dependencies are updated
yarn install --ignore-scripts --pure-lockfile
# Login and publish continues to use `npm`, as it seems to have more clearly
# defined options and semantics than `yarn` for writing to the registry.
if [ -z "$skip_npm" ]; then
actual_npm_user=`npm whoami`;
if [ $expected_npm_user != $actual_npm_user ]; then
echo "you need to be logged into npm as $expected_npm_user, but you are logged in as $actual_npm_user" >&2
exit 1
fi
fi
# ignore leading v on release
release="${1#v}"
tag="v${release}"
@@ -117,7 +137,7 @@ prerelease=0
# see if the version has a hyphen in it. Crude,
# but semver doesn't support postreleases so anything
# with a hyphen is a prerelease.
echo $release | grep -q '-' && prerelease=1
echo "$release" | grep -q '-' && prerelease=1
if [ $prerelease -eq 1 ]; then
echo Making a PRE-RELEASE
@@ -143,13 +163,13 @@ if [ -z "$skip_changelog" ]; then
yarn run allchange "$release"
read -p "Edit $changelog_file manually, or press enter to continue " REPLY
if [ -n "$(git ls-files --modified $changelog_file)" ]; then
if [ -n "$(git ls-files --modified "$changelog_file")" ]; then
echo "Committing updated changelog"
git commit "$changelog_file" -m "Prepare changelog for $tag"
fi
fi
latest_changes=`mktemp`
cat "${changelog_file}" | `dirname $0`/scripts/changelog_head.py > "${latest_changes}"
latest_changes=$(mktemp)
cat "${changelog_file}" | "$(dirname "$0")/scripts/changelog_head.py" > "${latest_changes}"
set -x
@@ -176,7 +196,7 @@ do
done
# commit yarn.lock if it exists, is versioned, and is modified
if [[ -f yarn.lock && `git status --porcelain yarn.lock | grep '^ M'` ]];
if [[ -f yarn.lock && $(git status --porcelain yarn.lock | grep '^ M') ]];
then
pkglock='yarn.lock'
else
@@ -188,7 +208,7 @@ git commit package.json $pkglock -m "$tag"
# figure out if we should be signing this release
signing_id=
if [ -f release_config.yaml ]; then
result=`cat release_config.yaml | python -c "import yaml; import sys; print yaml.load(sys.stdin)['signing_id']" 2> /dev/null || true`
result=$(cat release_config.yaml | python -c "import yaml; import sys; print(yaml.load(sys.stdin)['signing_id'])" 2> /dev/null || true)
if [ "$?" -eq 0 ]; then
signing_id=$result
fi
@@ -206,8 +226,8 @@ assets=''
dodist=0
jq -e .scripts.dist package.json 2> /dev/null || dodist=$?
if [ $dodist -eq 0 ]; then
projdir=`pwd`
builddir=`mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir'`
projdir=$(pwd)
builddir=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir')
echo "Building distribution copy in $builddir"
pushd "$builddir"
git clone "$projdir" .
@@ -232,7 +252,7 @@ fi
if [ -n "$signing_id" ]; then
# make a signed tag
# gnupg seems to fail to get the right tty device unless we set it here
GIT_COMMITTER_EMAIL="$signing_id" GPG_TTY=`tty` git tag -u "$signing_id" -F "${latest_changes}" "$tag"
GIT_COMMITTER_EMAIL="$signing_id" GPG_TTY=$(tty) git tag -u "$signing_id" -F "${latest_changes}" "$tag"
else
git tag -a -F "${latest_changes}" "$tag"
fi
@@ -270,7 +290,7 @@ if [ -n "$signing_id" ]; then
curl -L "${gh_project_url}/archive/${tarfile}" -o "${tarfile}"
# unzip it and compare it with the tar we would generate
if ! cmp --silent <(gunzip -c $tarfile) \
if ! cmp --silent <(gunzip -c "$tarfile") \
<(git archive --format tar --prefix="${project_name}-${release}/" "$tag"); then
# we don't bail out here, because really it's more likely that our comparison
@@ -298,11 +318,11 @@ if [ $prerelease -eq 1 ]; then
hubflags='-p'
fi
release_text=`mktemp`
release_text=$(mktemp)
echo "$tag" > "${release_text}"
echo >> "${release_text}"
cat "${latest_changes}" >> "${release_text}"
hub release create $hubflags $assets -F "${release_text}" "$tag"
hub release create $hubflags "$assets" -F "${release_text}" "$tag"
if [ $dodist -eq 0 ]; then
rm -rf "$builddir"
@@ -310,19 +330,6 @@ fi
rm "${release_text}"
rm "${latest_changes}"
# Login and publish continues to use `npm`, as it seems to have more clearly
# defined options and semantics than `yarn` for writing to the registry.
# Tag both releases and prereleases as `next` so the last stable release remains
# the default.
if [ -z "$skip_npm" ]; then
npm publish --tag next
if [ $prerelease -eq 0 ]; then
# For a release, also add the default `latest` tag.
package=$(cat package.json | jq -er .name)
npm dist-tag add "$package@$release" latest
fi
fi
# if it is a pre-release, leave it on the release branch for now.
if [ $prerelease -eq 1 ]; then
git checkout "$rel_branch"
@@ -339,34 +346,19 @@ git merge "$rel_branch" --no-edit
git push origin master
# finally, merge master back onto develop (if it exists)
if [ $(git branch -lr | grep origin/develop -c) -ge 1 ]; then
if [ "$(git branch -lr | grep origin/develop -c)" -ge 1 ]; then
git checkout develop
git pull
git merge master --no-edit
# When merging to develop, we need revert the `main` and `typings` fields if
# we adjusted them previously.
for i in main typings
do
# If a `lib` prefixed value is present, it means we adjusted the field
# earlier at publish time, so we should revert it now.
if [ "$(jq -r ".matrix_lib_$i" package.json)" != "null" ]; then
# If there's a `src` prefixed value, use that, otherwise delete.
# This is used to delete the `typings` field and reset `main` back
# to the TypeScript source.
src_value=$(jq -r ".matrix_src_$i" package.json)
if [ "$src_value" != "null" ]; then
jq ".$i = .matrix_src_$i" package.json > package.json.new && mv package.json.new package.json
else
jq "del(.$i)" package.json > package.json.new && mv package.json.new package.json
fi
fi
done
if [ -n "$(git ls-files --modified package.json)" ]; then
echo "Committing develop package.json"
git commit package.json -m "Resetting package fields for development"
fi
git push origin develop
fi
[ -x ./post-release.sh ] && ./post-release.sh
if [ $has_subprojects -eq 1 ] && [ $prerelease -eq 0 ]; then
echo "Resetting subprojects to develop"
for proj in $subprojects; do
reset_dependency "$proj"
done
git push origin develop
fi
@@ -1,6 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019, 2022 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.
@@ -17,31 +17,32 @@ limitations under the License.
/**
* A mock implementation of the webstorage api
* @constructor
*/
export function MockStorageApi() {
this.data = {};
this.keys = [];
this.length = 0;
}
export class MockStorageApi {
public data: Record<string, string> = {};
public keys: string[] = [];
public length = 0;
MockStorageApi.prototype = {
setItem: function(k, v) {
public setItem(k: string, v: string): void {
this.data[k] = v;
this._recalc();
},
getItem: function(k) {
this.recalc();
}
public getItem(k: string): string | null {
return this.data[k] || null;
},
removeItem: function(k) {
}
public removeItem(k: string): void {
delete this.data[k];
this._recalc();
},
key: function(index) {
this.recalc();
}
public key(index: number): string {
return this.keys[index];
},
_recalc: function() {
const keys = [];
}
private recalc(): void {
const keys: string[] = [];
for (const k in this.data) {
if (!this.data.hasOwnProperty(k)) {
continue;
@@ -50,6 +51,5 @@ MockStorageApi.prototype = {
}
this.keys = keys;
this.length = keys.length;
},
};
}
}
+1 -1
View File
@@ -50,7 +50,7 @@ export class TestClient {
options?: Partial<ICreateClientOpts>,
) {
if (sessionStoreBackend === undefined) {
sessionStoreBackend = new MockStorageApi();
sessionStoreBackend = new MockStorageApi() as unknown as Storage;
}
this.httpBackend = new MockHttpBackend();
File diff suppressed because it is too large Load Diff
@@ -20,6 +20,7 @@ import * as utils from "../src/utils";
// try to load the olm library.
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
global.Olm = require('@matrix-org/olm');
logger.log('loaded libolm');
} catch (e) {
@@ -28,6 +29,7 @@ try {
// also try to set node crypto
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const crypto = require('crypto');
utils.setCrypto(crypto);
} catch (err) {
+1 -1
View File
@@ -24,5 +24,5 @@ limitations under the License.
* expect(beaconLivenessEmits.length).toBe(1);
* ```
*/
export const filterEmitCallsByEventType = (eventType: string, spy: jest.SpyInstance<any, unknown[]>) =>
export const filterEmitCallsByEventType = (eventType: string, spy: jest.SpyInstance<any, any[]>) =>
spy.mock.calls.filter((args) => args[0] === eventType);
+3 -3
View File
@@ -147,9 +147,9 @@ export function mkEventCustom<T>(base: T): T & GeneratedMetadata {
interface IPresenceOpts {
user?: string;
sender?: string;
url: string;
name: string;
ago: number;
url?: string;
name?: string;
ago?: number;
presence?: string;
event?: boolean;
}
+7 -6
View File
@@ -15,6 +15,7 @@ import { CRYPTO_ENABLED } from "../../src/client";
import { DeviceInfo } from "../../src/crypto/deviceinfo";
import { logger } from '../../src/logger';
import { MemoryStore } from "../../src";
import { IStore } from '../../src/store';
const Olm = global.Olm;
@@ -158,8 +159,8 @@ describe("Crypto", function() {
let fakeEmitter;
beforeEach(async function() {
const mockStorage = new MockStorageApi();
const clientStore = new MemoryStore({ localStorage: mockStorage });
const mockStorage = new MockStorageApi() as unknown as Storage;
const clientStore = new MemoryStore({ localStorage: mockStorage }) as unknown as IStore;
const cryptoStore = new MemoryCryptoStore();
cryptoStore.storeEndToEndDeviceData({
@@ -469,12 +470,12 @@ describe("Crypto", function() {
jest.setTimeout(10000);
const client = (new TestClient("@a:example.com", "dev")).client;
await client.initCrypto();
client.crypto.getSecretStorageKey = async () => null;
client.crypto.getSecretStorageKey = jest.fn().mockResolvedValue(null);
client.crypto.isCrossSigningReady = async () => false;
client.crypto.baseApis.uploadDeviceSigningKeys = jest.fn().mockResolvedValue(null);
client.crypto.baseApis.setAccountData = () => null;
client.crypto.baseApis.uploadKeySignatures = () => null;
client.crypto.baseApis.http.authedRequest = () => null;
client.crypto.baseApis.setAccountData = jest.fn().mockResolvedValue(null);
client.crypto.baseApis.uploadKeySignatures = jest.fn();
client.crypto.baseApis.http.authedRequest = jest.fn();
const createSecretStorageKey = async () => {
return {
keyInfo: undefined, // Returning undefined here used to cause a crash
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {
IndexedDBCryptoStore,
} from '../../../src/crypto/store/indexeddb-crypto-store';
import { CryptoStore } from '../../../src/crypto/store/base';
import { IndexedDBCryptoStore } from '../../../src/crypto/store/indexeddb-crypto-store';
import { LocalStorageCryptoStore } from '../../../src/crypto/store/localStorage-crypto-store';
import { MemoryCryptoStore } from '../../../src/crypto/store/memory-crypto-store';
import { RoomKeyRequestState } from '../../../src/crypto/OutgoingRoomKeyRequestManager';
@@ -26,36 +26,39 @@ import 'jest-localstorage-mock';
const requests = [
{
requestId: "A",
requestBody: { session_id: "A", room_id: "A" },
requestBody: { session_id: "A", room_id: "A", sender_key: "A", algorithm: "m.megolm.v1.aes-sha2" },
state: RoomKeyRequestState.Sent,
recipients: [
{ userId: "@alice:example.com", deviceId: "*" },
{ userId: "@becca:example.com", deviceId: "foobarbaz" },
],
},
{
requestId: "B",
requestBody: { session_id: "B", room_id: "B" },
requestBody: { session_id: "B", room_id: "B", sender_key: "B", algorithm: "m.megolm.v1.aes-sha2" },
state: RoomKeyRequestState.Sent,
recipients: [
{ userId: "@alice:example.com", deviceId: "*" },
{ userId: "@carrie:example.com", deviceId: "barbazquux" },
],
},
{
requestId: "C",
requestBody: { session_id: "C", room_id: "C" },
requestBody: { session_id: "C", room_id: "C", sender_key: "B", algorithm: "m.megolm.v1.aes-sha2" },
state: RoomKeyRequestState.Unsent,
recipients: [
{ userId: "@becca:example.com", deviceId: "foobarbaz" },
],
},
];
describe.each([
["IndexedDBCryptoStore",
() => new IndexedDBCryptoStore(global.indexedDB, "tests")],
["LocalStorageCryptoStore",
() => new IndexedDBCryptoStore(undefined, "tests")],
["MemoryCryptoStore", () => {
const store = new IndexedDBCryptoStore(undefined, "tests");
// @ts-ignore set private properties
store.backend = new MemoryCryptoStore();
// @ts-ignore
store.backendPromise = Promise.resolve(store.backend);
return store;
}],
["LocalStorageCryptoStore", () => new LocalStorageCryptoStore(localStorage)],
["MemoryCryptoStore", () => new MemoryCryptoStore()],
])("Outgoing room key requests [%s]", function(name, dbFactory) {
let store;
let store: CryptoStore;
beforeAll(async () => {
store = dbFactory();
@@ -75,6 +78,15 @@ describe.each([
});
});
it("getOutgoingRoomKeyRequestsByTarget retrieves all entries with a given target",
async () => {
const r = await store.getOutgoingRoomKeyRequestsByTarget(
"@becca:example.com", "foobarbaz", [RoomKeyRequestState.Sent],
);
expect(r).toHaveLength(1);
expect(r[0]).toEqual(requests[0]);
});
test("getOutgoingRoomKeyRequestByState retrieves any entry in a given state",
async () => {
const r =
+1 -1
View File
@@ -427,7 +427,7 @@ describe("MatrixClient", function() {
}
});
});
await client.startClient();
await client.startClient({ filter });
await syncPromise;
});
@@ -1,12 +1,29 @@
/*
Copyright 2022 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 * as utils from "../test-utils/test-utils";
import { RoomMember } from "../../src/models/room-member";
import { RoomMember, RoomMemberEvent } from "../../src/models/room-member";
import { RoomState } from "../../src";
describe("RoomMember", function() {
const roomId = "!foo:bar";
const userA = "@alice:bar";
const userB = "@bertha:bar";
const userC = "@clarissa:bar";
let member;
let member = new RoomMember(roomId, userA);
beforeEach(function() {
member = new RoomMember(roomId, userA);
@@ -27,17 +44,17 @@ describe("RoomMember", function() {
avatar_url: "mxc://flibble/wibble",
},
});
const url = member.getAvatarUrl(hsUrl);
const url = member.getAvatarUrl(hsUrl, 1, 1, '', false, false);
// we don't care about how the mxc->http conversion is done, other
// than it contains the mxc body.
expect(url.indexOf("flibble/wibble")).not.toEqual(-1);
expect(url?.indexOf("flibble/wibble")).not.toEqual(-1);
});
it("should return nothing if there is no m.room.member and allowDefault=false",
function() {
const url = member.getAvatarUrl(hsUrl, 64, 64, "crop", false);
expect(url).toEqual(null);
});
function() {
const url = member.getAvatarUrl(hsUrl, 64, 64, "crop", false, false);
expect(url).toEqual(null);
});
});
describe("setPowerLevelEvent", function() {
@@ -66,92 +83,92 @@ describe("RoomMember", function() {
});
it("should emit 'RoomMember.powerLevel' if the power level changes.",
function() {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@bertha:bar": 200,
"@invalid:user": 10, // shouldn't barf on this.
function() {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@bertha:bar": 200,
"@invalid:user": 10, // shouldn't barf on this.
},
},
},
event: true,
});
let emitCount = 0;
event: true,
});
let emitCount = 0;
member.on("RoomMember.powerLevel", function(emitEvent, emitMember) {
emitCount += 1;
expect(emitMember).toEqual(member);
expect(emitEvent).toEqual(event);
});
member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
emitCount += 1;
expect(emitMember).toEqual(member);
expect(emitEvent).toEqual(event);
});
member.setPowerLevelEvent(event);
expect(emitCount).toEqual(1);
member.setPowerLevelEvent(event); // no-op
expect(emitCount).toEqual(1);
});
member.setPowerLevelEvent(event);
expect(emitCount).toEqual(1);
member.setPowerLevelEvent(event); // no-op
expect(emitCount).toEqual(1);
});
it("should honour power levels of zero.",
function() {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": 0,
function() {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": 0,
},
},
},
event: true,
});
let emitCount = 0;
event: true,
});
let emitCount = 0;
// set the power level to something other than zero or we
// won't get an event
member.powerLevel = 1;
member.on("RoomMember.powerLevel", function(emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual('@alice:bar');
expect(emitMember.powerLevel).toEqual(0);
expect(emitEvent).toEqual(event);
});
// set the power level to something other than zero or we
// won't get an event
member.powerLevel = 1;
member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual('@alice:bar');
expect(emitMember.powerLevel).toEqual(0);
expect(emitEvent).toEqual(event);
});
member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(0);
expect(emitCount).toEqual(1);
});
member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(0);
expect(emitCount).toEqual(1);
});
it("should not honor string power levels.",
function() {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
function() {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
},
},
event: true,
});
let emitCount = 0;
event: true,
});
let emitCount = 0;
member.on("RoomMember.powerLevel", function(emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual('@alice:bar');
expect(emitMember.powerLevel).toEqual(20);
expect(emitEvent).toEqual(event);
});
member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual('@alice:bar');
expect(emitMember.powerLevel).toEqual(20);
expect(emitEvent).toEqual(event);
});
member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(20);
expect(emitCount).toEqual(1);
});
member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(20);
expect(emitCount).toEqual(1);
});
});
describe("setTypingEvent", function() {
@@ -183,34 +200,34 @@ describe("RoomMember", function() {
});
it("should emit 'RoomMember.typing' if the typing state changes",
function() {
const event = utils.mkEvent({
type: "m.typing",
room: roomId,
content: {
user_ids: [
userA, userC,
],
},
event: true,
function() {
const event = utils.mkEvent({
type: "m.typing",
room: roomId,
content: {
user_ids: [
userA, userC,
],
},
event: true,
});
let emitCount = 0;
member.on(RoomMemberEvent.Typing, function(ev, mem) {
expect(mem).toEqual(member);
expect(ev).toEqual(event);
emitCount += 1;
});
member.typing = false;
member.setTypingEvent(event);
expect(emitCount).toEqual(1);
member.setTypingEvent(event); // no-op
expect(emitCount).toEqual(1);
});
let emitCount = 0;
member.on("RoomMember.typing", function(ev, mem) {
expect(mem).toEqual(member);
expect(ev).toEqual(event);
emitCount += 1;
});
member.typing = false;
member.setTypingEvent(event);
expect(emitCount).toEqual(1);
member.setTypingEvent(event); // no-op
expect(emitCount).toEqual(1);
});
});
describe("isOutOfBand", function() {
it("should be set by markOutOfBand", function() {
const member = new RoomMember();
const member = new RoomMember(roomId, userA);
expect(member.isOutOfBand()).toEqual(false);
member.markOutOfBand();
expect(member.isOutOfBand()).toEqual(true);
@@ -235,50 +252,50 @@ describe("RoomMember", function() {
});
it("should set 'membership' and assign the event to 'events.member'.",
function() {
member.setMembershipEvent(inviteEvent);
expect(member.membership).toEqual("invite");
expect(member.events.member).toEqual(inviteEvent);
member.setMembershipEvent(joinEvent);
expect(member.membership).toEqual("join");
expect(member.events.member).toEqual(joinEvent);
});
function() {
member.setMembershipEvent(inviteEvent);
expect(member.membership).toEqual("invite");
expect(member.events.member).toEqual(inviteEvent);
member.setMembershipEvent(joinEvent);
expect(member.membership).toEqual("join");
expect(member.events.member).toEqual(joinEvent);
});
it("should set 'name' based on user_id, displayname and room state",
function() {
const roomState = {
getStateEvents: function(type) {
if (type !== "m.room.member") {
return [];
}
return [
utils.mkMembership({
event: true, mship: "join", room: roomId,
user: userB,
}),
utils.mkMembership({
event: true, mship: "join", room: roomId,
user: userC, name: "Alice",
}),
joinEvent,
];
},
getUserIdsWithDisplayName: function(displayName) {
return [userA, userC];
},
};
expect(member.name).toEqual(userA); // default = user_id
member.setMembershipEvent(joinEvent);
expect(member.name).toEqual("Alice"); // prefer displayname
member.setMembershipEvent(joinEvent, roomState);
expect(member.name).not.toEqual("Alice"); // it should disambig.
// user_id should be there somewhere
expect(member.name.indexOf(userA)).not.toEqual(-1);
});
function() {
const roomState = {
getStateEvents: function(type) {
if (type !== "m.room.member") {
return [];
}
return [
utils.mkMembership({
event: true, mship: "join", room: roomId,
user: userB,
}),
utils.mkMembership({
event: true, mship: "join", room: roomId,
user: userC, name: "Alice",
}),
joinEvent,
];
},
getUserIdsWithDisplayName: function(displayName) {
return [userA, userC];
},
} as unknown as RoomState;
expect(member.name).toEqual(userA); // default = user_id
member.setMembershipEvent(joinEvent);
expect(member.name).toEqual("Alice"); // prefer displayname
member.setMembershipEvent(joinEvent, roomState);
expect(member.name).not.toEqual("Alice"); // it should disambig.
// user_id should be there somewhere
expect(member.name.indexOf(userA)).not.toEqual(-1);
});
it("should emit 'RoomMember.membership' if the membership changes", function() {
let emitCount = 0;
member.on("RoomMember.membership", function(ev, mem) {
member.on(RoomMemberEvent.Membership, function(ev, mem) {
emitCount += 1;
expect(mem).toEqual(member);
expect(ev).toEqual(inviteEvent);
@@ -291,7 +308,7 @@ describe("RoomMember", function() {
it("should emit 'RoomMember.name' if the name changes", function() {
let emitCount = 0;
member.on("RoomMember.name", function(ev, mem) {
member.on(RoomMemberEvent.Name, function(ev, mem) {
emitCount += 1;
expect(mem).toEqual(member);
expect(ev).toEqual(joinEvent);
@@ -341,7 +358,7 @@ describe("RoomMember", function() {
getUserIdsWithDisplayName: function(displayName) {
return [userA, userC];
},
};
} as unknown as RoomState;
expect(member.name).toEqual(userA); // default = user_id
member.setMembershipEvent(joinEvent, roomState);
expect(member.name).not.toEqual("Alíce"); // it should disambig.
@@ -1,14 +1,37 @@
/*
Copyright 2022 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 { MockedObject } from 'jest-mock';
import * as utils from "../test-utils/test-utils";
import { makeBeaconEvent, makeBeaconInfoEvent } from "../test-utils/beacon";
import { filterEmitCallsByEventType } from "../test-utils/emitter";
import { RoomState, RoomStateEvent } from "../../src/models/room-state";
import { BeaconEvent, getBeaconInfoIdentifier } from "../../src/models/beacon";
import {
Beacon,
BeaconEvent,
getBeaconInfoIdentifier,
} from "../../src/models/beacon";
import { EventType, RelationType, UNSTABLE_MSC2716_MARKER } from "../../src/@types/event";
import {
MatrixEvent,
MatrixEventEvent,
} from "../../src/models/event";
import { M_BEACON } from "../../src/@types/beacon";
import { MatrixClient } from "../../src/client";
describe("RoomState", function() {
const roomId = "!foo:bar";
@@ -17,7 +40,7 @@ describe("RoomState", function() {
const userC = "@cleo:bar";
const userLazy = "@lazy:bar";
let state;
let state = new RoomState(roomId);
beforeEach(function() {
state = new RoomState(roomId);
@@ -67,8 +90,8 @@ describe("RoomState", function() {
it("should return a member which changes as state changes", function() {
const member = state.getMember(userB);
expect(member.membership).toEqual("join");
expect(member.name).toEqual(userB);
expect(member?.membership).toEqual("join");
expect(member?.name).toEqual(userB);
state.setStateEvents([
utils.mkMembership({
@@ -77,40 +100,40 @@ describe("RoomState", function() {
}),
]);
expect(member.membership).toEqual("leave");
expect(member.name).toEqual("BobGone");
expect(member?.membership).toEqual("leave");
expect(member?.name).toEqual("BobGone");
});
});
describe("getSentinelMember", function() {
it("should return a member with the user id as name", function() {
expect(state.getSentinelMember("@no-one:here").name).toEqual("@no-one:here");
expect(state.getSentinelMember("@no-one:here")?.name).toEqual("@no-one:here");
});
it("should return a member which doesn't change when the state is updated",
function() {
const preLeaveUser = state.getSentinelMember(userA);
state.setStateEvents([
utils.mkMembership({
room: roomId, user: userA, mship: "leave", event: true,
name: "AliceIsGone",
}),
]);
const postLeaveUser = state.getSentinelMember(userA);
function() {
const preLeaveUser = state.getSentinelMember(userA);
state.setStateEvents([
utils.mkMembership({
room: roomId, user: userA, mship: "leave", event: true,
name: "AliceIsGone",
}),
]);
const postLeaveUser = state.getSentinelMember(userA);
expect(preLeaveUser.membership).toEqual("join");
expect(preLeaveUser.name).toEqual(userA);
expect(preLeaveUser?.membership).toEqual("join");
expect(preLeaveUser?.name).toEqual(userA);
expect(postLeaveUser.membership).toEqual("leave");
expect(postLeaveUser.name).toEqual("AliceIsGone");
});
expect(postLeaveUser?.membership).toEqual("leave");
expect(postLeaveUser?.name).toEqual("AliceIsGone");
});
});
describe("getStateEvents", function() {
it("should return null if a state_key was specified and there was no match",
function() {
expect(state.getStateEvents("foo.bar.baz", "keyname")).toEqual(null);
});
function() {
expect(state.getStateEvents("foo.bar.baz", "keyname")).toEqual(null);
});
it("should return an empty list if a state_key was not specified and there" +
" was no match", function() {
@@ -118,21 +141,21 @@ describe("RoomState", function() {
});
it("should return a list of matching events if no state_key was specified",
function() {
const events = state.getStateEvents("m.room.member");
expect(events.length).toEqual(2);
// ordering unimportant
expect([userA, userB].indexOf(events[0].getStateKey())).not.toEqual(-1);
expect([userA, userB].indexOf(events[1].getStateKey())).not.toEqual(-1);
});
function() {
const events = state.getStateEvents("m.room.member");
expect(events.length).toEqual(2);
// ordering unimportant
expect([userA, userB].indexOf(events[0].getStateKey() as string)).not.toEqual(-1);
expect([userA, userB].indexOf(events[1].getStateKey() as string)).not.toEqual(-1);
});
it("should return a single MatrixEvent if a state_key was specified",
function() {
const event = state.getStateEvents("m.room.member", userA);
expect(event.getContent()).toMatchObject({
membership: "join",
function() {
const event = state.getStateEvents("m.room.member", userA);
expect(event.getContent()).toMatchObject({
membership: "join",
});
});
});
});
describe("setStateEvents", function() {
@@ -146,7 +169,7 @@ describe("RoomState", function() {
}),
];
let emitCount = 0;
state.on("RoomState.members", function(ev, st, mem) {
state.on(RoomStateEvent.Members, function(ev, st, mem) {
expect(ev).toEqual(memberEvents[emitCount]);
expect(st).toEqual(state);
expect(mem).toEqual(state.getMember(ev.getSender()));
@@ -166,7 +189,7 @@ describe("RoomState", function() {
}),
];
let emitCount = 0;
state.on("RoomState.newMember", function(ev, st, mem) {
state.on(RoomStateEvent.NewMember, function(ev, st, mem) {
expect(state.getMember(mem.userId)).toEqual(mem);
expect(mem.userId).toEqual(memberEvents[emitCount].getSender());
expect(mem.membership).toBeFalsy(); // not defined yet
@@ -192,7 +215,7 @@ describe("RoomState", function() {
}),
];
let emitCount = 0;
state.on("RoomState.events", function(ev, st) {
state.on(RoomStateEvent.Events, function(ev, st) {
expect(ev).toEqual(events[emitCount]);
expect(st).toEqual(state);
emitCount += 1;
@@ -272,7 +295,7 @@ describe("RoomState", function() {
}),
];
let emitCount = 0;
state.on("RoomState.Marker", function(markerEvent, markerFoundOptions) {
state.on(RoomStateEvent.Marker, function(markerEvent, markerFoundOptions) {
expect(markerEvent).toEqual(events[emitCount]);
expect(markerFoundOptions).toEqual({ timelineWasEmpty: true });
emitCount += 1;
@@ -296,7 +319,7 @@ describe("RoomState", function() {
it('does not add redacted beacon info events to state', () => {
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
const redactionEvent = { event: { type: 'm.room.redaction' } };
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction' });
redactedBeaconEvent.makeRedacted(redactionEvent);
const emitSpy = jest.spyOn(state, 'emit');
@@ -316,27 +339,27 @@ describe("RoomState", function() {
state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
expect(beaconInstance.isLive).toEqual(true);
expect(beaconInstance?.isLive).toEqual(true);
state.setStateEvents([updatedBeaconEvent]);
// same Beacon
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance);
// updated liveness
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent)).isLive).toEqual(false);
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))?.isLive).toEqual(false);
});
it('destroys and removes redacted beacon events', () => {
const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactionEvent = { event: { type: 'm.room.redaction', redacts: beaconEvent.getId() } };
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction', redacts: beaconEvent.getId() });
redactedBeaconEvent.makeRedacted(redactionEvent);
state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
const destroySpy = jest.spyOn(beaconInstance, 'destroy');
expect(beaconInstance.isLive).toEqual(true);
const destroySpy = jest.spyOn(beaconInstance as Beacon, 'destroy');
expect(beaconInstance?.isLive).toEqual(true);
state.setStateEvents([redactedBeaconEvent]);
@@ -357,7 +380,7 @@ describe("RoomState", function() {
// live beacon is now not live
const updatedLiveBeaconEvent = makeBeaconInfoEvent(
userA, roomId, { isLive: false }, liveBeaconEvent.getId(), '$beacon1',
userA, roomId, { isLive: false }, liveBeaconEvent.getId(),
);
state.setStateEvents([updatedLiveBeaconEvent]);
@@ -377,8 +400,8 @@ describe("RoomState", function() {
state.markOutOfBandMembersStarted();
state.setOutOfBandMembers([oobMemberEvent]);
const member = state.getMember(userLazy);
expect(member.userId).toEqual(userLazy);
expect(member.isOutOfBand()).toEqual(true);
expect(member?.userId).toEqual(userLazy);
expect(member?.isOutOfBand()).toEqual(true);
});
it("should have no effect when not in correct status", function() {
@@ -394,7 +417,7 @@ describe("RoomState", function() {
user: userLazy, mship: "join", room: roomId, event: true,
});
let eventReceived = false;
state.once('RoomState.newMember', (_, __, member) => {
state.once(RoomStateEvent.NewMember, (_event, _state, member) => {
expect(member.userId).toEqual(userLazy);
eventReceived = true;
});
@@ -410,8 +433,8 @@ describe("RoomState", function() {
state.markOutOfBandMembersStarted();
state.setOutOfBandMembers([oobMemberEvent]);
const memberA = state.getMember(userA);
expect(memberA.events.member.getId()).not.toEqual(oobMemberEvent.getId());
expect(memberA.isOutOfBand()).toEqual(false);
expect(memberA?.events?.member?.getId()).not.toEqual(oobMemberEvent.getId());
expect(memberA?.isOutOfBand()).toEqual(false);
});
it("should emit members when updating a member", function() {
@@ -420,7 +443,7 @@ describe("RoomState", function() {
user: doesntExistYetUserId, mship: "join", room: roomId, event: true,
});
let eventReceived = false;
state.once('RoomState.members', (_, __, member) => {
state.once(RoomStateEvent.Members, (_event, _state, member) => {
expect(member.userId).toEqual(doesntExistYetUserId);
eventReceived = true;
});
@@ -443,8 +466,8 @@ describe("RoomState", function() {
[userA, userB, userLazy].forEach((userId) => {
const member = state.getMember(userId);
const memberCopy = copy.getMember(userId);
expect(member.name).toEqual(memberCopy.name);
expect(member.isOutOfBand()).toEqual(memberCopy.isOutOfBand());
expect(member?.name).toEqual(memberCopy?.name);
expect(member?.isOutOfBand()).toEqual(memberCopy?.isOutOfBand());
});
// check member keys
expect(Object.keys(state.members)).toEqual(Object.keys(copy.members));
@@ -496,78 +519,80 @@ describe("RoomState", function() {
describe("maySendStateEvent", function() {
it("should say any member may send state with no power level event",
function() {
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
});
function() {
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
});
it("should say members with power >=50 may send state with power level event " +
"but no state default",
function() {
const powerLevelEvent = {
type: "m.room.power_levels", room: roomId, user: userA, event: true,
const powerLevelEvent = new MatrixEvent({
type: "m.room.power_levels", room_id: roomId, sender: userA,
state_key: "",
content: {
users_default: 10,
// state_default: 50, "intentionally left blank"
events_default: 25,
users: {
[userA]: 50,
},
},
};
powerLevelEvent.content.users[userA] = 50;
});
state.setStateEvents([utils.mkEvent(powerLevelEvent)]);
state.setStateEvents([powerLevelEvent]);
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
expect(state.maySendStateEvent('m.room.name', userB)).toEqual(false);
});
it("should obey state_default",
function() {
const powerLevelEvent = {
type: "m.room.power_levels", room: roomId, user: userA, event: true,
content: {
users_default: 10,
state_default: 30,
events_default: 25,
users: {
function() {
const powerLevelEvent = new MatrixEvent({
type: "m.room.power_levels", room_id: roomId, sender: userA,
state_key: "",
content: {
users_default: 10,
state_default: 30,
events_default: 25,
users: {
[userA]: 30,
[userB]: 29,
},
},
},
};
powerLevelEvent.content.users[userA] = 30;
powerLevelEvent.content.users[userB] = 29;
});
state.setStateEvents([utils.mkEvent(powerLevelEvent)]);
state.setStateEvents([powerLevelEvent]);
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
expect(state.maySendStateEvent('m.room.name', userB)).toEqual(false);
});
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
expect(state.maySendStateEvent('m.room.name', userB)).toEqual(false);
});
it("should honour explicit event power levels in the power_levels event",
function() {
const powerLevelEvent = {
type: "m.room.power_levels", room: roomId, user: userA, event: true,
content: {
events: {
"m.room.other_thing": 76,
function() {
const powerLevelEvent = new MatrixEvent({
type: "m.room.power_levels", room_id: roomId, sender: userA,
state_key: "", content: {
events: {
"m.room.other_thing": 76,
},
users_default: 10,
state_default: 50,
events_default: 25,
users: {
[userA]: 80,
[userB]: 50,
},
},
users_default: 10,
state_default: 50,
events_default: 25,
users: {
},
},
};
powerLevelEvent.content.users[userA] = 80;
powerLevelEvent.content.users[userB] = 50;
});
state.setStateEvents([utils.mkEvent(powerLevelEvent)]);
state.setStateEvents([powerLevelEvent]);
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
expect(state.maySendStateEvent('m.room.name', userB)).toEqual(true);
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
expect(state.maySendStateEvent('m.room.name', userB)).toEqual(true);
expect(state.maySendStateEvent('m.room.other_thing', userA)).toEqual(true);
expect(state.maySendStateEvent('m.room.other_thing', userB)).toEqual(false);
});
expect(state.maySendStateEvent('m.room.other_thing', userA)).toEqual(true);
expect(state.maySendStateEvent('m.room.other_thing', userB)).toEqual(false);
});
});
describe("getJoinedMemberCount", function() {
@@ -682,71 +707,73 @@ describe("RoomState", function() {
describe("maySendEvent", function() {
it("should say any member may send events with no power level event",
function() {
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
expect(state.maySendMessage(userA)).toEqual(true);
});
function() {
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
expect(state.maySendMessage(userA)).toEqual(true);
});
it("should obey events_default",
function() {
const powerLevelEvent = {
type: "m.room.power_levels", room: roomId, user: userA, event: true,
content: {
users_default: 10,
state_default: 30,
events_default: 25,
users: {
function() {
const powerLevelEvent = new MatrixEvent({
type: "m.room.power_levels", room_id: roomId, sender: userA,
state_key: "",
content: {
users_default: 10,
state_default: 30,
events_default: 25,
users: {
[userA]: 26,
[userB]: 24,
},
},
},
};
powerLevelEvent.content.users[userA] = 26;
powerLevelEvent.content.users[userB] = 24;
});
state.setStateEvents([utils.mkEvent(powerLevelEvent)]);
state.setStateEvents([powerLevelEvent]);
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
expect(state.maySendEvent('m.room.message', userB)).toEqual(false);
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
expect(state.maySendEvent('m.room.message', userB)).toEqual(false);
expect(state.maySendMessage(userA)).toEqual(true);
expect(state.maySendMessage(userB)).toEqual(false);
});
expect(state.maySendMessage(userA)).toEqual(true);
expect(state.maySendMessage(userB)).toEqual(false);
});
it("should honour explicit event power levels in the power_levels event",
function() {
const powerLevelEvent = {
type: "m.room.power_levels", room: roomId, user: userA, event: true,
content: {
events: {
"m.room.other_thing": 33,
function() {
const powerLevelEvent = new MatrixEvent({
type: "m.room.power_levels", room_id: roomId, sender: userA,
state_key: "",
content: {
events: {
"m.room.other_thing": 33,
},
users_default: 10,
state_default: 50,
events_default: 25,
users: {
[userA]: 40,
[userB]: 30,
},
},
users_default: 10,
state_default: 50,
events_default: 25,
users: {
},
},
};
powerLevelEvent.content.users[userA] = 40;
powerLevelEvent.content.users[userB] = 30;
});
state.setStateEvents([utils.mkEvent(powerLevelEvent)]);
state.setStateEvents([powerLevelEvent]);
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
expect(state.maySendEvent('m.room.message', userB)).toEqual(true);
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
expect(state.maySendEvent('m.room.message', userB)).toEqual(true);
expect(state.maySendMessage(userA)).toEqual(true);
expect(state.maySendMessage(userB)).toEqual(true);
expect(state.maySendMessage(userA)).toEqual(true);
expect(state.maySendMessage(userB)).toEqual(true);
expect(state.maySendEvent('m.room.other_thing', userA)).toEqual(true);
expect(state.maySendEvent('m.room.other_thing', userB)).toEqual(false);
});
expect(state.maySendEvent('m.room.other_thing', userA)).toEqual(true);
expect(state.maySendEvent('m.room.other_thing', userB)).toEqual(false);
});
});
describe('processBeaconEvents', () => {
const beacon1 = makeBeaconInfoEvent(userA, roomId, {}, '$beacon1', '$beacon1');
const beacon2 = makeBeaconInfoEvent(userB, roomId, {}, '$beacon2', '$beacon2');
const beacon1 = makeBeaconInfoEvent(userA, roomId, {}, '$beacon1');
const beacon2 = makeBeaconInfoEvent(userB, roomId, {}, '$beacon2');
const mockClient = { decryptEventIfNeeded: jest.fn() };
const mockClient = { decryptEventIfNeeded: jest.fn() } as unknown as MockedObject<MatrixClient>;
beforeEach(() => {
mockClient.decryptEventIfNeeded.mockClear();
@@ -816,11 +843,11 @@ describe("RoomState", function() {
beaconInfoId: 'some-other-beacon',
});
state.setStateEvents([beacon1, beacon2], mockClient);
state.setStateEvents([beacon1, beacon2]);
expect(state.beacons.size).toEqual(2);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beacon1));
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
const addLocationsSpy = jest.spyOn(beaconInstance, 'addLocations');
state.processBeaconEvents([location1, location2, location3], mockClient);
@@ -885,7 +912,7 @@ describe("RoomState", function() {
});
state.setStateEvents([beacon1, beacon2]);
const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1));
const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear();
state.processBeaconEvents([location, otherRelatedEvent], mockClient);
expect(addLocationsSpy).not.toHaveBeenCalled();
@@ -945,13 +972,13 @@ describe("RoomState", function() {
});
jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true);
state.setStateEvents([beacon1, beacon2]);
const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1));
const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear();
state.processBeaconEvents([decryptingRelatedEvent], mockClient);
// this event is a message after decryption
decryptingRelatedEvent.type = EventType.RoomMessage;
decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted);
decryptingRelatedEvent.event.type = EventType.RoomMessage;
decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted, decryptingRelatedEvent);
expect(addLocationsSpy).not.toHaveBeenCalled();
});
@@ -967,14 +994,14 @@ describe("RoomState", function() {
});
jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true);
state.setStateEvents([beacon1, beacon2]);
const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1));
const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear();
state.processBeaconEvents([decryptingRelatedEvent], mockClient);
// update type after '''decryption'''
decryptingRelatedEvent.event.type = M_BEACON.name;
decryptingRelatedEvent.event.content = locationEvent.content;
decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted);
decryptingRelatedEvent.event.content = locationEvent.event.content;
decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted, decryptingRelatedEvent);
expect(addLocationsSpy).toHaveBeenCalledWith([decryptingRelatedEvent]);
});
+3
View File
@@ -152,6 +152,9 @@ describe("utils", function() {
assert.isTrue(utils.deepCompare({ a: 1, b: 2 }, { a: 1, b: 2 }));
assert.isTrue(utils.deepCompare({ a: 1, b: 2 }, { b: 2, a: 1 }));
assert.isFalse(utils.deepCompare({ a: 1, b: 2 }, { a: 1, b: 3 }));
assert.isFalse(utils.deepCompare({ a: 1, b: 2 }, { a: 1 }));
assert.isFalse(utils.deepCompare({ a: 1 }, { a: 1, b: 2 }));
assert.isFalse(utils.deepCompare({ a: 1 }, { b: 1 }));
assert.isTrue(utils.deepCompare({
1: { name: "mhc", age: 28 },
+1 -2
View File
@@ -396,8 +396,7 @@ export interface IStartClientOpts {
pollTimeout?: number;
/**
* The filter to apply to /sync calls. This will override the opts.initialSyncLimit, which would
* normally result in a timeline limit filter.
* The filter to apply to /sync calls.
*/
filter?: Filter;
@@ -26,7 +26,7 @@ import {
Mode,
OutgoingRoomKeyRequest,
} from "./base";
import { IRoomKeyRequestBody } from "../index";
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index";
import { ICrossSigningKey } from "../../client";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
@@ -261,7 +261,9 @@ export class Backend implements CryptoStore {
const cursor = this.result;
if (cursor) {
const keyReq = cursor.value;
if (keyReq.recipients.includes({ userId, deviceId })) {
if (keyReq.recipients.some((recipient: IRoomKeyRequestRecipient) =>
recipient.userId === userId && recipient.deviceId === deviceId,
)) {
results.push(keyReq);
}
cursor.continue();
+4 -2
View File
@@ -191,11 +191,13 @@ export class MemoryCryptoStore implements CryptoStore {
deviceId: string,
wantedStates: number[],
): Promise<OutgoingRoomKeyRequest[]> {
const results = [];
const results: OutgoingRoomKeyRequest[] = [];
for (const req of this.outgoingRoomKeyRequests) {
for (const state of wantedStates) {
if (req.state === state && req.recipients.includes({ userId, deviceId })) {
if (req.state === state && req.recipients.some(
(recipient) => recipient.userId === userId && recipient.deviceId === deviceId,
)) {
results.push(req);
}
}
+4 -2
View File
@@ -551,6 +551,10 @@ export class SlidingSyncSdk {
// we deliberately don't add ephemeral events to the timeline
room.addEphemeralEvents(ephemeralEvents);
// local fields must be set before any async calls because call site assumes
// synchronous execution prior to emitting SlidingSyncState.Complete
room.updateMyMembership("join");
room.recalculate();
if (roomData.initial) {
client.store.storeRoom(room);
@@ -574,8 +578,6 @@ export class SlidingSyncSdk {
client.emit(ClientEvent.Event, e);
});
room.updateMyMembership("join");
// Decrypt only the last message in all rooms to make sure we can generate a preview
// And decrypt all events after the recorded read receipt to ensure an accurate
// notification count
+281 -279
View File
@@ -23,6 +23,8 @@ limitations under the License.
* for HTTP and WS at some point.
*/
import { Optional } from "matrix-events-sdk";
import { User, UserEvent } from "./models/user";
import { NotificationCountType, Room, RoomEvent } from "./models/room";
import * as utils from "./utils";
@@ -100,18 +102,16 @@ const MSC2716_ROOM_VERSIONS = [
function getFilterName(userId: string, suffix?: string): string {
// scope this on the user ID because people may login on many accounts
// and they all need to be stored!
return "FILTER_SYNC_" + userId + (suffix ? "_" + suffix : "");
return `FILTER_SYNC_${userId}` + (suffix ? "_" + suffix : "");
}
function debuglog(...params) {
if (!DEBUG) {
return;
}
if (!DEBUG) return;
logger.log(...params);
}
interface ISyncOptions {
filterId?: string;
filter?: string;
hasSyncedBefore?: boolean;
}
@@ -161,14 +161,14 @@ type WrappedRoom<T> = T & {
* updating presence.
*/
export class SyncApi {
private _peekRoom: Room = null;
private currentSyncRequest: IAbortablePromise<ISyncResponse> = null;
private syncState: SyncState = null;
private syncStateData: ISyncStateData = null; // additional data (eg. error object for failed sync)
private _peekRoom: Optional<Room> = null;
private currentSyncRequest: Optional<IAbortablePromise<ISyncResponse>> = null;
private syncState: Optional<SyncState> = null;
private syncStateData: Optional<ISyncStateData> = null; // additional data (eg. error object for failed sync)
private catchingUp = false;
private running = false;
private keepAliveTimer: ReturnType<typeof setTimeout> = null;
private connectionReturnedDefer: IDeferred<boolean> = null;
private keepAliveTimer: Optional<ReturnType<typeof setTimeout>> = null;
private connectionReturnedDefer: Optional<IDeferred<boolean>> = null;
private notifEvents: MatrixEvent[] = []; // accumulator of sync events in the current sync response
private failedSyncCount = 0; // Number of consecutive failed /sync requests
private storeIsInvalid = false; // flag set if the store needs to be cleared before we can start
@@ -214,7 +214,7 @@ export class SyncApi {
* historical messages are shown when we paginate `/messages` again.
* @param {Room} room The room where the marker event was sent
* @param {MatrixEvent} markerEvent The new marker event
* @param {ISetStateOptions} setStateOptions When `timelineWasEmpty` is set
* @param {IMarkerFoundOptions} setStateOptions When `timelineWasEmpty` is set
* as `true`, the given marker event will be ignored
*/
private onMarkerStateEvent(
@@ -367,7 +367,7 @@ export class SyncApi {
// XXX: copypasted from /sync until we kill off this minging v1 API stuff)
// handle presence events (User objects)
if (response.presence && Array.isArray(response.presence)) {
if (Array.isArray(response.presence)) {
response.presence.map(client.getEventMapper()).forEach(
function(presenceEvent) {
let user = client.store.getUser(presenceEvent.getContent().user_id);
@@ -542,20 +542,135 @@ export class SyncApi {
return false;
}
private getPushRules = async () => {
try {
debuglog("Getting push rules...");
const result = await this.client.getPushRules();
debuglog("Got push rules");
this.client.pushRules = result;
} catch (err) {
logger.error("Getting push rules failed", err);
if (this.shouldAbortSync(err)) return;
// wait for saved sync to complete before doing anything else,
// otherwise the sync state will end up being incorrect
debuglog("Waiting for saved sync before retrying push rules...");
await this.recoverFromSyncStartupError(this.savedSyncPromise, err);
return this.getPushRules(); // try again
}
};
private buildDefaultFilter = () => {
return new Filter(this.client.credentials.userId);
};
private checkLazyLoadStatus = async () => {
debuglog("Checking lazy load status...");
if (this.opts.lazyLoadMembers && this.client.isGuest()) {
this.opts.lazyLoadMembers = false;
}
if (this.opts.lazyLoadMembers) {
debuglog("Checking server lazy load support...");
const supported = await this.client.doesServerSupportLazyLoading();
if (supported) {
debuglog("Enabling lazy load on sync filter...");
if (!this.opts.filter) {
this.opts.filter = this.buildDefaultFilter();
}
this.opts.filter.setLazyLoadMembers(true);
} else {
debuglog("LL: lazy loading requested but not supported " +
"by server, so disabling");
this.opts.lazyLoadMembers = false;
}
}
// need to vape the store when enabling LL and wasn't enabled before
debuglog("Checking whether lazy loading has changed in store...");
const shouldClear = await this.wasLazyLoadingToggled(this.opts.lazyLoadMembers);
if (shouldClear) {
this.storeIsInvalid = true;
const reason = InvalidStoreError.TOGGLED_LAZY_LOADING;
const error = new InvalidStoreError(reason, !!this.opts.lazyLoadMembers);
this.updateSyncState(SyncState.Error, { error });
// bail out of the sync loop now: the app needs to respond to this error.
// we leave the state as 'ERROR' which isn't great since this normally means
// we're retrying. The client must be stopped before clearing the stores anyway
// so the app should stop the client, clear the store and start it again.
logger.warn("InvalidStoreError: store is not usable: stopping sync.");
return;
}
if (this.opts.lazyLoadMembers) {
this.opts.crypto?.enableLazyLoading();
}
try {
debuglog("Storing client options...");
await this.client.storeClientOptions();
debuglog("Stored client options");
} catch (err) {
logger.error("Storing client options failed", err);
throw err;
}
};
private getFilter = async (): Promise<{
filterId?: string;
filter?: Filter;
}> => {
debuglog("Getting filter...");
let filter: Filter;
if (this.opts.filter) {
filter = this.opts.filter;
} else {
filter = this.buildDefaultFilter();
}
let filterId: string;
try {
filterId = await this.client.getOrCreateFilter(getFilterName(this.client.credentials.userId), filter);
} catch (err) {
logger.error("Getting filter failed", err);
if (this.shouldAbortSync(err)) return {};
// wait for saved sync to complete before doing anything else,
// otherwise the sync state will end up being incorrect
debuglog("Waiting for saved sync before retrying filter...");
await this.recoverFromSyncStartupError(this.savedSyncPromise, err);
return this.getFilter(); // try again
}
return { filter, filterId };
};
private savedSyncPromise: Promise<void>;
/**
* Main entry point
*/
public sync(): void {
const client = this.client;
public async sync(): Promise<void> {
this.running = true;
if (global.window && global.window.addEventListener) {
global.window.addEventListener("online", this.onOnline, false);
global.window?.addEventListener?.("online", this.onOnline, false);
if (this.client.isGuest()) {
// no push rules for guests, no access to POST filter for guests.
return this.doSync({});
}
let savedSyncPromise = Promise.resolve();
let savedSyncToken = null;
// Pull the saved sync token out first, before the worker starts sending
// all the sync data which could take a while. This will let us send our
// first incremental sync request before we've processed our saved data.
debuglog("Getting saved sync token...");
const savedSyncTokenPromise = this.client.store.getSavedSyncToken().then(tok => {
debuglog("Got saved sync token");
return tok;
});
this.savedSyncPromise = this.client.store.getSavedSync().then((savedSync) => {
debuglog(`Got reply from saved sync, exists? ${!!savedSync}`);
if (savedSync) {
return this.syncFromCache(savedSync);
}
}).catch(err => {
logger.error("Getting saved sync failed", err);
});
// We need to do one-off checks before we can begin the /sync loop.
// These are:
@@ -565,149 +680,45 @@ export class SyncApi {
// 3) We need to check the lazy loading option matches what was used in the
// stored sync. If it doesn't, we can't use the stored sync.
const getPushRules = async () => {
try {
debuglog("Getting push rules...");
const result = await client.getPushRules();
debuglog("Got push rules");
// Now start the first incremental sync request: this can also
// take a while so if we set it going now, we can wait for it
// to finish while we process our saved sync data.
await this.getPushRules();
await this.checkLazyLoadStatus();
const { filterId, filter } = await this.getFilter();
if (!filter) return; // bail, getFilter failed
client.pushRules = result;
} catch (err) {
logger.error("Getting push rules failed", err);
if (this.shouldAbortSync(err)) return;
// wait for saved sync to complete before doing anything else,
// otherwise the sync state will end up being incorrect
debuglog("Waiting for saved sync before retrying push rules...");
await this.recoverFromSyncStartupError(savedSyncPromise, err);
getPushRules();
return;
}
checkLazyLoadStatus(); // advance to the next stage
};
// reset the notifications timeline to prepare it to paginate from
// the current point in time.
// The right solution would be to tie /sync pagination tokens into
// /notifications API somehow.
this.client.resetNotifTimelineSet();
const buildDefaultFilter = () => {
const filter = new Filter(client.credentials.userId);
filter.setTimelineLimit(this.opts.initialSyncLimit);
return filter;
};
if (this.currentSyncRequest === null) {
let firstSyncFilter = filterId;
const savedSyncToken = await savedSyncTokenPromise;
const checkLazyLoadStatus = async () => {
debuglog("Checking lazy load status...");
if (this.opts.lazyLoadMembers && client.isGuest()) {
this.opts.lazyLoadMembers = false;
}
if (this.opts.lazyLoadMembers) {
debuglog("Checking server lazy load support...");
const supported = await client.doesServerSupportLazyLoading();
if (supported) {
debuglog("Enabling lazy load on sync filter...");
if (!this.opts.filter) {
this.opts.filter = buildDefaultFilter();
}
this.opts.filter.setLazyLoadMembers(true);
} else {
debuglog("LL: lazy loading requested but not supported " +
"by server, so disabling");
this.opts.lazyLoadMembers = false;
}
}
// need to vape the store when enabling LL and wasn't enabled before
debuglog("Checking whether lazy loading has changed in store...");
const shouldClear = await this.wasLazyLoadingToggled(this.opts.lazyLoadMembers);
if (shouldClear) {
this.storeIsInvalid = true;
const reason = InvalidStoreError.TOGGLED_LAZY_LOADING;
const error = new InvalidStoreError(reason, !!this.opts.lazyLoadMembers);
this.updateSyncState(SyncState.Error, { error });
// bail out of the sync loop now: the app needs to respond to this error.
// we leave the state as 'ERROR' which isn't great since this normally means
// we're retrying. The client must be stopped before clearing the stores anyway
// so the app should stop the client, clear the store and start it again.
logger.warn("InvalidStoreError: store is not usable: stopping sync.");
return;
}
if (this.opts.lazyLoadMembers && this.opts.crypto) {
this.opts.crypto.enableLazyLoading();
}
try {
debuglog("Storing client options...");
await this.client.storeClientOptions();
debuglog("Stored client options");
} catch (err) {
logger.error("Storing client options failed", err);
throw err;
}
getFilter(); // Now get the filter and start syncing
};
const getFilter = async () => {
debuglog("Getting filter...");
let filter;
if (this.opts.filter) {
filter = this.opts.filter;
} else {
filter = buildDefaultFilter();
}
let filterId;
try {
filterId = await client.getOrCreateFilter(getFilterName(client.credentials.userId), filter);
} catch (err) {
logger.error("Getting filter failed", err);
if (this.shouldAbortSync(err)) return;
// wait for saved sync to complete before doing anything else,
// otherwise the sync state will end up being incorrect
debuglog("Waiting for saved sync before retrying filter...");
await this.recoverFromSyncStartupError(savedSyncPromise, err);
getFilter();
return;
}
// reset the notifications timeline to prepare it to paginate from
// the current point in time.
// The right solution would be to tie /sync pagination tokens into
// /notifications API somehow.
client.resetNotifTimelineSet();
if (this.currentSyncRequest === null) {
// Send this first sync request here so we can then wait for the saved
// sync data to finish processing before we process the results of this one.
if (savedSyncToken) {
debuglog("Sending first sync request...");
this.currentSyncRequest = this.doSyncRequest({ filterId }, savedSyncToken);
} else {
debuglog("Sending initial sync request...");
const initialFilter = this.buildDefaultFilter();
initialFilter.setDefinition(filter.getDefinition());
initialFilter.setTimelineLimit(this.opts.initialSyncLimit);
// Use an inline filter, no point uploading it for a single usage
firstSyncFilter = JSON.stringify(initialFilter.getDefinition());
}
// Now wait for the saved sync to finish...
debuglog("Waiting for saved sync before starting sync processing...");
await savedSyncPromise;
this.doSync({ filterId });
};
if (client.isGuest()) {
// no push rules for guests, no access to POST filter for guests.
this.doSync({});
} else {
// Pull the saved sync token out first, before the worker starts sending
// all the sync data which could take a while. This will let us send our
// first incremental sync request before we've processed our saved data.
debuglog("Getting saved sync token...");
savedSyncPromise = client.store.getSavedSyncToken().then((tok) => {
debuglog("Got saved sync token");
savedSyncToken = tok;
debuglog("Getting saved sync...");
return client.store.getSavedSync();
}).then((savedSync) => {
debuglog(`Got reply from saved sync, exists? ${!!savedSync}`);
if (savedSync) {
return this.syncFromCache(savedSync);
}
}).catch(err => {
logger.error("Getting saved sync failed", err);
});
// Now start the first incremental sync request: this can also
// take a while so if we set it going now, we can wait for it
// to finish while we process our saved sync data.
getPushRules();
// Send this first sync request here so we can then wait for the saved
// sync data to finish processing before we process the results of this one.
this.currentSyncRequest = this.doSyncRequest({ filter: firstSyncFilter }, savedSyncToken);
}
// Now wait for the saved sync to finish...
debuglog("Waiting for saved sync before starting sync processing...");
await this.savedSyncPromise;
// process the first sync request and continue syncing with the normal filterId
return this.doSync({ filter: filterId });
}
/**
@@ -719,9 +730,7 @@ export class SyncApi {
// global.window AND global.window.removeEventListener.
// Some platforms (e.g. React Native) register global.window,
// but do not have global.window.removeEventListener.
if (global.window && global.window.removeEventListener) {
global.window.removeEventListener("online", this.onOnline, false);
}
global.window?.removeEventListener?.("online", this.onOnline, false);
this.running = false;
this.currentSyncRequest?.abort();
if (this.keepAliveTimer) {
@@ -756,8 +765,7 @@ export class SyncApi {
this.client.store.setSyncToken(nextSyncToken);
// No previous sync, set old token to null
const syncEventData = {
oldSyncToken: null,
const syncEventData: ISyncStateData = {
nextSyncToken,
catchingUp: false,
fromCache: true,
@@ -792,7 +800,91 @@ export class SyncApi {
* @param {boolean} syncOptions.hasSyncedBefore
*/
private async doSync(syncOptions: ISyncOptions): Promise<void> {
const client = this.client;
while (this.running) {
const syncToken = this.client.store.getSyncToken();
let data: ISyncResponse;
try {
//debuglog('Starting sync since=' + syncToken);
if (this.currentSyncRequest === null) {
this.currentSyncRequest = this.doSyncRequest(syncOptions, syncToken);
}
data = await this.currentSyncRequest;
} catch (e) {
const abort = await this.onSyncError(e);
if (abort) return;
continue;
} finally {
this.currentSyncRequest = null;
}
//debuglog('Completed sync, next_batch=' + data.next_batch);
// set the sync token NOW *before* processing the events. We do this so
// if something barfs on an event we can skip it rather than constantly
// polling with the same token.
this.client.store.setSyncToken(data.next_batch);
// Reset after a successful sync
this.failedSyncCount = 0;
await this.client.store.setSyncData(data);
const syncEventData = {
oldSyncToken: syncToken,
nextSyncToken: data.next_batch,
catchingUp: this.catchingUp,
};
if (this.opts.crypto) {
// tell the crypto module we're about to process a sync
// response
await this.opts.crypto.onSyncWillProcess(syncEventData);
}
try {
await this.processSyncResponse(syncEventData, data);
} catch (e) {
// log the exception with stack if we have it, else fall back
// to the plain description
logger.error("Caught /sync error", e);
// Emit the exception for client handling
this.client.emit(ClientEvent.SyncUnexpectedError, e);
}
// update this as it may have changed
syncEventData.catchingUp = this.catchingUp;
// emit synced events
if (!syncOptions.hasSyncedBefore) {
this.updateSyncState(SyncState.Prepared, syncEventData);
syncOptions.hasSyncedBefore = true;
}
// tell the crypto module to do its processing. It may block (to do a
// /keys/changes request).
if (this.opts.crypto) {
await this.opts.crypto.onSyncCompleted(syncEventData);
}
// keep emitting SYNCING -> SYNCING for clients who want to do bulk updates
this.updateSyncState(SyncState.Syncing, syncEventData);
if (this.client.store.wantsSave()) {
// We always save the device list (if it's dirty) before saving the sync data:
// this means we know the saved device list data is at least as fresh as the
// stored sync data which means we don't have to worry that we may have missed
// device changes. We can also skip the delay since we're not calling this very
// frequently (and we don't really want to delay the sync for it).
if (this.opts.crypto) {
await this.opts.crypto.saveDeviceList(0);
}
// tell databases that everything is now in a consistent state and can be saved.
this.client.store.save();
}
}
if (!this.running) {
debuglog("Sync no longer running: exiting.");
@@ -801,94 +893,7 @@ export class SyncApi {
this.connectionReturnedDefer = null;
}
this.updateSyncState(SyncState.Stopped);
return;
}
const syncToken = client.store.getSyncToken();
let data;
try {
//debuglog('Starting sync since=' + syncToken);
if (this.currentSyncRequest === null) {
this.currentSyncRequest = this.doSyncRequest(syncOptions, syncToken);
}
data = await this.currentSyncRequest;
} catch (e) {
this.onSyncError(e, syncOptions);
return;
} finally {
this.currentSyncRequest = null;
}
//debuglog('Completed sync, next_batch=' + data.next_batch);
// set the sync token NOW *before* processing the events. We do this so
// if something barfs on an event we can skip it rather than constantly
// polling with the same token.
client.store.setSyncToken(data.next_batch);
// Reset after a successful sync
this.failedSyncCount = 0;
await client.store.setSyncData(data);
const syncEventData = {
oldSyncToken: syncToken,
nextSyncToken: data.next_batch,
catchingUp: this.catchingUp,
};
if (this.opts.crypto) {
// tell the crypto module we're about to process a sync
// response
await this.opts.crypto.onSyncWillProcess(syncEventData);
}
try {
await this.processSyncResponse(syncEventData, data);
} catch (e) {
// log the exception with stack if we have it, else fall back
// to the plain description
logger.error("Caught /sync error", e);
// Emit the exception for client handling
this.client.emit(ClientEvent.SyncUnexpectedError, e);
}
// update this as it may have changed
syncEventData.catchingUp = this.catchingUp;
// emit synced events
if (!syncOptions.hasSyncedBefore) {
this.updateSyncState(SyncState.Prepared, syncEventData);
syncOptions.hasSyncedBefore = true;
}
// tell the crypto module to do its processing. It may block (to do a
// /keys/changes request).
if (this.opts.crypto) {
await this.opts.crypto.onSyncCompleted(syncEventData);
}
// keep emitting SYNCING -> SYNCING for clients who want to do bulk updates
this.updateSyncState(SyncState.Syncing, syncEventData);
if (client.store.wantsSave()) {
// We always save the device list (if it's dirty) before saving the sync data:
// this means we know the saved device list data is at least as fresh as the
// stored sync data which means we don't have to worry that we may have missed
// device changes. We can also skip the delay since we're not calling this very
// frequently (and we don't really want to delay the sync for it).
if (this.opts.crypto) {
await this.opts.crypto.saveDeviceList(0);
}
// tell databases that everything is now in a consistent state and can be saved.
client.store.save();
}
// Begin next sync
this.doSync(syncOptions);
}
private doSyncRequest(syncOptions: ISyncOptions, syncToken: string): IAbortablePromise<ISyncResponse> {
@@ -902,7 +907,7 @@ export class SyncApi {
private getSyncParams(syncOptions: ISyncOptions, syncToken: string): ISyncParams {
let pollTimeout = this.opts.pollTimeout;
if (this.getSyncState() !== 'SYNCING' || this.catchingUp) {
if (this.getSyncState() !== SyncState.Syncing || this.catchingUp) {
// unless we are happily syncing already, we want the server to return
// as quickly as possible, even if there are no events queued. This
// serves two purposes:
@@ -918,13 +923,13 @@ export class SyncApi {
pollTimeout = 0;
}
let filterId = syncOptions.filterId;
if (this.client.isGuest() && !filterId) {
filterId = this.getGuestFilter();
let filter = syncOptions.filter;
if (this.client.isGuest() && !filter) {
filter = this.getGuestFilter();
}
const qps: ISyncParams = {
filter: filterId,
filter,
timeout: pollTimeout,
};
@@ -941,7 +946,7 @@ export class SyncApi {
qps._cacheBuster = Date.now();
}
if (this.getSyncState() == 'ERROR' || this.getSyncState() == 'RECONNECTING') {
if ([SyncState.Reconnecting, SyncState.Error].includes(this.getSyncState())) {
// we think the connection is dead. If it comes back up, we won't know
// about it till /sync returns. If the timeout= is high, this could
// be a long time. Set it to 0 when doing retries so we don't have to wait
@@ -952,7 +957,7 @@ export class SyncApi {
return qps;
}
private onSyncError(err: MatrixError, syncOptions: ISyncOptions): void {
private async onSyncError(err: MatrixError): Promise<boolean> {
if (!this.running) {
debuglog("Sync no longer running: exiting");
if (this.connectionReturnedDefer) {
@@ -960,14 +965,13 @@ export class SyncApi {
this.connectionReturnedDefer = null;
}
this.updateSyncState(SyncState.Stopped);
return;
return true; // abort
}
logger.error("/sync error %s", err);
logger.error(err);
if (this.shouldAbortSync(err)) {
return;
return true; // abort
}
this.failedSyncCount++;
@@ -981,20 +985,7 @@ export class SyncApi {
// erroneous. We set the state to 'reconnecting'
// instead, so that clients can observe this state
// if they wish.
this.startKeepAlives().then((connDidFail) => {
// Only emit CATCHUP if we detected a connectivity error: if we didn't,
// it's quite likely the sync will fail again for the same reason and we
// want to stay in ERROR rather than keep flip-flopping between ERROR
// and CATCHUP.
if (connDidFail && this.getSyncState() === SyncState.Error) {
this.updateSyncState(SyncState.Catchup, {
oldSyncToken: null,
nextSyncToken: null,
catchingUp: true,
});
}
this.doSync(syncOptions);
});
const keepAlivePromise = this.startKeepAlives();
this.currentSyncRequest = null;
// Transition from RECONNECTING to ERROR after a given number of failed syncs
@@ -1003,6 +994,19 @@ export class SyncApi {
SyncState.Error : SyncState.Reconnecting,
{ error: err },
);
const connDidFail = await keepAlivePromise;
// Only emit CATCHUP if we detected a connectivity error: if we didn't,
// it's quite likely the sync will fail again for the same reason and we
// want to stay in ERROR rather than keep flip-flopping between ERROR
// and CATCHUP.
if (connDidFail && this.getSyncState() === SyncState.Error) {
this.updateSyncState(SyncState.Catchup, {
catchingUp: true,
});
}
return false;
}
/**
@@ -1061,7 +1065,7 @@ export class SyncApi {
// - The isBrandNewRoom boilerplate is boilerplatey.
// handle presence events (User objects)
if (data.presence && Array.isArray(data.presence.events)) {
if (Array.isArray(data.presence?.events)) {
data.presence.events.map(client.getEventMapper()).forEach(
function(presenceEvent) {
let user = client.store.getUser(presenceEvent.getSender());
@@ -1077,7 +1081,7 @@ export class SyncApi {
}
// handle non-room account_data
if (data.account_data && Array.isArray(data.account_data.events)) {
if (Array.isArray(data.account_data?.events)) {
const events = data.account_data.events.map(client.getEventMapper());
const prevEventsMap = events.reduce((m, c) => {
m[c.getId()] = client.store.getAccountData(c.getType());
@@ -1218,8 +1222,7 @@ export class SyncApi {
// bother setting it here. We trust our calculations better than the
// server's for this case, and therefore will assume that our non-zero
// count is accurate.
if (!encrypted
|| (encrypted && room.getUnreadNotificationCount(NotificationCountType.Highlight) <= 0)) {
if (!encrypted || room.getUnreadNotificationCount(NotificationCountType.Highlight) <= 0) {
room.setUnreadNotificationCount(
NotificationCountType.Highlight,
joinObj.unread_notifications.highlight_count,
@@ -1232,8 +1235,7 @@ export class SyncApi {
if (joinObj.isBrandNewRoom) {
// set the back-pagination token. Do this *before* adding any
// events so that clients can start back-paginating.
room.getLiveTimeline().setPaginationToken(
joinObj.timeline.prev_batch, EventTimeline.BACKWARDS);
room.getLiveTimeline().setPaginationToken(joinObj.timeline.prev_batch, EventTimeline.BACKWARDS);
} else if (joinObj.timeline.limited) {
let limited = true;
+5 -15
View File
@@ -98,8 +98,7 @@ export function decodeParams(query: string): QueryDict {
* variables with. E.g. { "$bar": "baz" }.
* @return {string} The result of replacing all template variables e.g. '/foo/baz'.
*/
export function encodeUri(pathTemplate: string,
variables: Record<string, string>): string {
export function encodeUri(pathTemplate: string, variables: Record<string, string>): string {
for (const key in variables) {
if (!variables.hasOwnProperty(key)) {
continue;
@@ -239,33 +238,24 @@ export function deepCompare(x: any, y: any): boolean {
}
}
} else {
// disable jshint "The body of a for in should be wrapped in an if
// statement"
/* jshint -W089 */
// check that all of y's direct keys are in x
let p;
for (p in y) {
for (const p in y) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
}
}
// finally, compare each of x's keys with y
for (p in y) { // eslint-disable-line guard-for-in
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
}
if (!deepCompare(x[p], y[p])) {
for (const p in x) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p) || !deepCompare(x[p], y[p])) {
return false;
}
}
}
/* jshint +W089 */
return true;
}
// Dev note: This returns a tuple, but jsdoc doesn't like that. https://github.com/jsdoc/jsdoc/issues/1703
// Dev note: This returns an array of tuples, but jsdoc doesn't like that. https://github.com/jsdoc/jsdoc/issues/1703
/**
* Creates an array of object properties/values (entries) then
* sorts the result by key, recursively. The input object must
+1 -1
View File
@@ -12,6 +12,6 @@
},
"include": [
"./src/**/*.ts",
"./spec/**/*.ts",
"./spec/**/*.ts"
]
}
+122 -122
View File
@@ -59,25 +59,25 @@
"@babel/highlight" "^7.18.6"
"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==
version "7.18.13"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.13.tgz#6aff7b350a1e8c3e40b029e46cbe78e24a913483"
integrity sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==
"@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.7.5":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8"
integrity sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==
version "7.18.13"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.13.tgz#9be8c44512751b05094a4d3ab05fc53a47ce00ac"
integrity sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==
dependencies:
"@ampproject/remapping" "^2.1.0"
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.18.10"
"@babel/generator" "^7.18.13"
"@babel/helper-compilation-targets" "^7.18.9"
"@babel/helper-module-transforms" "^7.18.9"
"@babel/helpers" "^7.18.9"
"@babel/parser" "^7.18.10"
"@babel/parser" "^7.18.13"
"@babel/template" "^7.18.10"
"@babel/traverse" "^7.18.10"
"@babel/types" "^7.18.10"
"@babel/traverse" "^7.18.13"
"@babel/types" "^7.18.13"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
@@ -100,12 +100,12 @@
dependencies:
eslint-rule-composer "^0.3.0"
"@babel/generator@^7.12.11", "@babel/generator@^7.18.10", "@babel/generator@^7.7.2":
version "7.18.12"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.12.tgz#fa58daa303757bd6f5e4bbca91b342040463d9f4"
integrity sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==
"@babel/generator@^7.12.11", "@babel/generator@^7.18.13", "@babel/generator@^7.7.2":
version "7.18.13"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.13.tgz#59550cbb9ae79b8def15587bdfbaa388c4abf212"
integrity sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==
dependencies:
"@babel/types" "^7.18.10"
"@babel/types" "^7.18.13"
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
@@ -135,9 +135,9 @@
semver "^6.3.0"
"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz#d802ee16a64a9e824fcbf0a2ffc92f19d58550ce"
integrity sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==
version "7.18.13"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.13.tgz#63e771187bd06d234f95fdf8bd5f8b6429de6298"
integrity sha512-hDvXp+QYxSRL+23mpAlSGxHMDyIGChm0/AwTfTAAK5Ufe40nCsyNdaYCGuK91phn/fVu9kqayImRDkvNAgdrsA==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
"@babel/helper-environment-visitor" "^7.18.9"
@@ -319,10 +319,10 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.2.3", "@babel/parser@^7.9.4":
version "7.18.11"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9"
integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.18.13", "@babel/parser@^7.2.3", "@babel/parser@^7.9.4":
version "7.18.13"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.13.tgz#5b2dd21cae4a2c5145f1fbd8ca103f9313d3b7e4"
integrity sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
@@ -647,9 +647,9 @@
"@babel/helper-plugin-utils" "^7.18.9"
"@babel/plugin-transform-destructuring@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz#68906549c021cb231bee1db21d3b5b095f8ee292"
integrity sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==
version "7.18.13"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz#9e03bc4a94475d62b7f4114938e6c5c33372cbf5"
integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==
dependencies:
"@babel/helper-plugin-utils" "^7.18.9"
@@ -996,26 +996,26 @@
"@babel/parser" "^7.18.10"
"@babel/types" "^7.18.10"
"@babel/traverse@^7.1.6", "@babel/traverse@^7.18.10", "@babel/traverse@^7.18.11", "@babel/traverse@^7.18.9", "@babel/traverse@^7.7.2":
version "7.18.11"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.11.tgz#3d51f2afbd83ecf9912bcbb5c4d94e3d2ddaa16f"
integrity sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==
"@babel/traverse@^7.1.6", "@babel/traverse@^7.18.11", "@babel/traverse@^7.18.13", "@babel/traverse@^7.18.9", "@babel/traverse@^7.7.2":
version "7.18.13"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.13.tgz#5ab59ef51a997b3f10c4587d648b9696b6cb1a68"
integrity sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.18.10"
"@babel/generator" "^7.18.13"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.18.9"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.18.11"
"@babel/types" "^7.18.10"
"@babel/parser" "^7.18.13"
"@babel/types" "^7.18.13"
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.10.tgz#4908e81b6b339ca7c6b7a555a5fc29446f26dde6"
integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==
"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.13", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.18.13"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.13.tgz#30aeb9e514f4100f7c1cb6e5ba472b30e48f519a"
integrity sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==
dependencies:
"@babel/helper-string-parser" "^7.18.10"
"@babel/helper-validator-identifier" "^7.18.6"
@@ -1027,13 +1027,13 @@
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@eslint/eslintrc@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f"
integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==
version "1.3.1"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.1.tgz#de0807bfeffc37b964a7d0400e0c348ce5a2543d"
integrity sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^9.3.2"
espree "^9.4.0"
globals "^13.15.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
@@ -1557,9 +1557,9 @@
"@types/istanbul-lib-report" "*"
"@types/jest@^28.0.0":
version "28.1.7"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.7.tgz#a680c5d05b69634c2d54a63cb106d7fb1adaba16"
integrity sha512-acDN4VHD40V24tgu0iC44jchXavRNVFXQ/E6Z5XNsswgoSO/4NgsXoEYmPUGookKldlZQyIpmrEXsHI9cA3ZTA==
version "28.1.8"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.8.tgz#6936409f3c9724ea431efd412ea0238a0f03b09b"
integrity sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==
dependencies:
expect "^28.0.0"
pretty-format "^28.0.0"
@@ -1593,14 +1593,14 @@
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
"@types/node@*":
version "18.7.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.6.tgz#31743bc5772b6ac223845e18c3fc26f042713c83"
integrity sha512-EdxgKRXgYsNITy5mjjXjVE/CS8YENSdhiagGrLqjG0pvA2owgJ6i4l7wy/PFZGC0B1/H20lWKN7ONVDNYDZm7A==
version "18.7.13"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.13.tgz#23e6c5168333480d454243378b69e861ab5c011a"
integrity sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==
"@types/node@16":
version "16.11.54"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.54.tgz#1bc17ff09bf340d9350c32200adab22f22753376"
integrity sha512-ryOpwe15+BtTUxKFfzABjaI/EtXLPBSBEW4B6D5ygWNcORLVKG/1/FC3WwAr5d7t6lCnlVPRsCY0NH680QT+Pg==
version "16.11.56"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.56.tgz#dcbb617669481e158e0f1c6204d1c768cd675901"
integrity sha512-aFcUkv7EddxxOa/9f74DINReQ/celqH8DiB3fRYgVDM2Xm5QJL8sl80QKuAnGvwAsMn+H3IFA6WCrQh1CY7m1A==
"@types/prettier@^2.1.5":
version "2.7.0"
@@ -1645,13 +1645,13 @@
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^5.6.0":
version "5.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.1.tgz#c0a480d05211660221eda963cc844732fe9b1714"
integrity sha512-S1iZIxrTvKkU3+m63YUOxYPKaP+yWDQrdhxTglVDVEVBf+aCSw85+BmJnyUaQQsk5TXFG/LpBu9fa+LrAQ91fQ==
version "5.35.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.35.1.tgz#0d822bfea7469904dfc1bb8f13cabd362b967c93"
integrity sha512-RBZZXZlI4XCY4Wzgy64vB+0slT9+yAPQRjj/HSaRwUot33xbDjF1oN9BLwOLTewoOI0jothIltZRe9uJCHf8gg==
dependencies:
"@typescript-eslint/scope-manager" "5.33.1"
"@typescript-eslint/type-utils" "5.33.1"
"@typescript-eslint/utils" "5.33.1"
"@typescript-eslint/scope-manager" "5.35.1"
"@typescript-eslint/type-utils" "5.35.1"
"@typescript-eslint/utils" "5.35.1"
debug "^4.3.4"
functional-red-black-tree "^1.0.1"
ignore "^5.2.0"
@@ -1660,68 +1660,68 @@
tsutils "^3.21.0"
"@typescript-eslint/parser@^5.6.0":
version "5.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.33.1.tgz#e4b253105b4d2a4362cfaa4e184e2d226c440ff3"
integrity sha512-IgLLtW7FOzoDlmaMoXdxG8HOCByTBXrB1V2ZQYSEV1ggMmJfAkMWTwUjjzagS6OkfpySyhKFkBw7A9jYmcHpZA==
version "5.35.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.35.1.tgz#bf2ee2ebeaa0a0567213748243fb4eec2857f04f"
integrity sha512-XL2TBTSrh3yWAsMYpKseBYTVpvudNf69rPOWXWVBI08My2JVT5jR66eTt4IgQFHA/giiKJW5dUD4x/ZviCKyGg==
dependencies:
"@typescript-eslint/scope-manager" "5.33.1"
"@typescript-eslint/types" "5.33.1"
"@typescript-eslint/typescript-estree" "5.33.1"
"@typescript-eslint/scope-manager" "5.35.1"
"@typescript-eslint/types" "5.35.1"
"@typescript-eslint/typescript-estree" "5.35.1"
debug "^4.3.4"
"@typescript-eslint/scope-manager@5.33.1":
version "5.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.33.1.tgz#8d31553e1b874210018ca069b3d192c6d23bc493"
integrity sha512-8ibcZSqy4c5m69QpzJn8XQq9NnqAToC8OdH/W6IXPXv83vRyEDPYLdjAlUx8h/rbusq6MkW4YdQzURGOqsn3CA==
"@typescript-eslint/scope-manager@5.35.1":
version "5.35.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.35.1.tgz#ccb69d54b7fd0f2d0226a11a75a8f311f525ff9e"
integrity sha512-kCYRSAzIW9ByEIzmzGHE50NGAvAP3wFTaZevgWva7GpquDyFPFcmvVkFJGWJJktg/hLwmys/FZwqM9EKr2u24Q==
dependencies:
"@typescript-eslint/types" "5.33.1"
"@typescript-eslint/visitor-keys" "5.33.1"
"@typescript-eslint/types" "5.35.1"
"@typescript-eslint/visitor-keys" "5.35.1"
"@typescript-eslint/type-utils@5.33.1":
version "5.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.33.1.tgz#1a14e94650a0ae39f6e3b77478baff002cec4367"
integrity sha512-X3pGsJsD8OiqhNa5fim41YtlnyiWMF/eKsEZGsHID2HcDqeSC5yr/uLOeph8rNF2/utwuI0IQoAK3fpoxcLl2g==
"@typescript-eslint/type-utils@5.35.1":
version "5.35.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.35.1.tgz#d50903b56758c5c8fc3be52b3be40569f27f9c4a"
integrity sha512-8xT8ljvo43Mp7BiTn1vxLXkjpw8wS4oAc00hMSB4L1/jIiYbjjnc3Qp2GAUOG/v8zsNCd1qwcqfCQ0BuishHkw==
dependencies:
"@typescript-eslint/utils" "5.33.1"
"@typescript-eslint/utils" "5.35.1"
debug "^4.3.4"
tsutils "^3.21.0"
"@typescript-eslint/types@5.33.1":
version "5.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.33.1.tgz#3faef41793d527a519e19ab2747c12d6f3741ff7"
integrity sha512-7K6MoQPQh6WVEkMrMW5QOA5FO+BOwzHSNd0j3+BlBwd6vtzfZceJ8xJ7Um2XDi/O3umS8/qDX6jdy2i7CijkwQ==
"@typescript-eslint/types@5.35.1":
version "5.35.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.35.1.tgz#af355fe52a0cc88301e889bc4ada72f279b63d61"
integrity sha512-FDaujtsH07VHzG0gQ6NDkVVhi1+rhq0qEvzHdJAQjysN+LHDCKDKCBRlZFFE0ec0jKxiv0hN63SNfExy0KrbQQ==
"@typescript-eslint/typescript-estree@5.33.1":
version "5.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.1.tgz#a573bd360790afdcba80844e962d8b2031984f34"
integrity sha512-JOAzJ4pJ+tHzA2pgsWQi4804XisPHOtbvwUyqsuuq8+y5B5GMZs7lI1xDWs6V2d7gE/Ez5bTGojSK12+IIPtXA==
"@typescript-eslint/typescript-estree@5.35.1":
version "5.35.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.35.1.tgz#db878a39a0dbdc9bb133f11cdad451770bfba211"
integrity sha512-JUqE1+VRTGyoXlDWWjm6MdfpBYVq+hixytrv1oyjYIBEOZhBCwtpp5ZSvBt4wIA1MKWlnaC2UXl2XmYGC3BoQA==
dependencies:
"@typescript-eslint/types" "5.33.1"
"@typescript-eslint/visitor-keys" "5.33.1"
"@typescript-eslint/types" "5.35.1"
"@typescript-eslint/visitor-keys" "5.35.1"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.33.1":
version "5.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.33.1.tgz#171725f924fe1fe82bb776522bb85bc034e88575"
integrity sha512-uphZjkMaZ4fE8CR4dU7BquOV6u0doeQAr8n6cQenl/poMaIyJtBu8eys5uk6u5HiDH01Mj5lzbJ5SfeDz7oqMQ==
"@typescript-eslint/utils@5.35.1":
version "5.35.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.35.1.tgz#ae1399afbfd6aa7d0ed1b7d941e9758d950250eb"
integrity sha512-v6F8JNXgeBWI4pzZn36hT2HXXzoBBBJuOYvoQiaQaEEjdi5STzux3Yj8v7ODIpx36i/5s8TdzuQ54TPc5AITQQ==
dependencies:
"@types/json-schema" "^7.0.9"
"@typescript-eslint/scope-manager" "5.33.1"
"@typescript-eslint/types" "5.33.1"
"@typescript-eslint/typescript-estree" "5.33.1"
"@typescript-eslint/scope-manager" "5.35.1"
"@typescript-eslint/types" "5.35.1"
"@typescript-eslint/typescript-estree" "5.35.1"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/visitor-keys@5.33.1":
version "5.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.1.tgz#0155c7571c8cd08956580b880aea327d5c34a18b"
integrity sha512-nwIxOK8Z2MPWltLKMLOEZwmfBZReqUdbEoHQXeCpa+sRVARe5twpJGHCB4dk9903Yaf0nMAlGbQfaAH92F60eg==
"@typescript-eslint/visitor-keys@5.35.1":
version "5.35.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.35.1.tgz#285e9e34aed7c876f16ff646a3984010035898e6"
integrity sha512-cEB1DvBVo1bxbW/S5axbGPE6b7FIMAbo3w+AGq6zNDA7+NYJOIkKj/sInfTv4edxd4PxJSgdN4t6/pbvgA+n5g==
dependencies:
"@typescript-eslint/types" "5.33.1"
"@typescript-eslint/types" "5.35.1"
eslint-visitor-keys "^3.3.0"
JSONStream@^1.0.3:
@@ -2425,9 +2425,9 @@ camelcase@^6.2.0:
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
caniuse-lite@^1.0.30001370:
version "1.0.30001378"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001378.tgz#3d2159bf5a8f9ca093275b0d3ecc717b00f27b67"
integrity sha512-JVQnfoO7FK7WvU4ZkBRbPjaot4+YqxogSDosHv0Hv5mWpUESmN+UubMU6L/hGz8QlQ2aY5U0vR6MOs6j/CXpNA==
version "1.0.30001384"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001384.tgz#029527c2d781a3cfef13fa63b3a78a6088e35973"
integrity sha512-BBWt57kqWbc0GYZXb47wTXpmAgqr5LSibPzNjk/AWMdmJMQhLqOl3c/Kd4OAU/tu4NLfYkMx8Tlq3RVBkOBolQ==
caseless@~0.12.0:
version "0.12.0"
@@ -2675,9 +2675,9 @@ convert-source-map@~1.1.0:
integrity sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==
core-js-compat@^3.21.0, core-js-compat@^3.22.1:
version "3.24.1"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.24.1.tgz#d1af84a17e18dfdd401ee39da9996f9a7ba887de"
integrity sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw==
version "3.25.0"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.0.tgz#489affbfbf9cb3fa56192fe2dd9ebaee985a66c5"
integrity sha512-extKQM0g8/3GjFx9US12FAgx8KJawB7RCQ5y8ipYLbmfzEzmFRWdDjIlxDx82g7ygcNG85qMVUSRyABouELdow==
dependencies:
browserslist "^4.21.3"
semver "7.0.0"
@@ -2953,9 +2953,9 @@ ecc-jsbn@~0.1.1:
safer-buffer "^2.1.0"
electron-to-chromium@^1.4.202:
version "1.4.225"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.225.tgz#3e27bdd157cbaf19768141f2e0f0f45071e52338"
integrity sha512-ICHvGaCIQR3P88uK8aRtx8gmejbVJyC6bB4LEC3anzBrIzdzC7aiZHY4iFfXhN4st6I7lMO0x4sgBHf/7kBvRw==
version "1.4.233"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.233.tgz#aa142e45468bda111b88abc9cc59d573b75d6a60"
integrity sha512-ejwIKXTg1wqbmkcRJh9Ur3hFGHFDZDw1POzdsVrB2WZjgRuRMHIQQKNpe64N/qh3ZtH2otEoRoS+s6arAAuAAw==
elliptic@^6.5.3:
version "6.5.4"
@@ -3220,10 +3220,10 @@ eslint@8.22.0:
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
espree@^9.3.2, espree@^9.3.3:
version "9.3.3"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.3.tgz#2dd37c4162bb05f433ad3c1a52ddf8a49dc08e9d"
integrity sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==
espree@^9.3.3, espree@^9.4.0:
version "9.4.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a"
integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==
dependencies:
acorn "^8.8.0"
acorn-jsx "^5.3.2"
@@ -3463,9 +3463,9 @@ flat-cache@^3.0.4:
rimraf "^3.0.2"
flatted@^3.1.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2"
integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==
version "3.2.7"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
for-each@^0.3.3:
version "0.3.3"
@@ -4798,9 +4798,9 @@ markdown-it@^12.3.2:
uc.micro "^1.0.5"
marked@^4.0.10:
version "4.0.18"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.18.tgz#cd0ac54b2e5610cfb90e8fd46ccaa8292c9ed569"
integrity sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw==
version "4.0.19"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.19.tgz#d36198d1ac1255525153c351c68c75bc1d7aee46"
integrity sha512-rgQF/OxOiLcvgUAj1Q1tAf4Bgxn5h5JZTp04Fx4XUkVhs7B+7YA9JEWJhJpoO8eJt8MkZMwqLCNeNqj1bCREZQ==
matrix-events-sdk@^0.0.1-beta.7:
version "0.0.1-beta.7"
@@ -6141,9 +6141,9 @@ terminal-link@^2.0.0:
supports-hyperlinks "^2.0.0"
terser@^5.5.1:
version "5.14.2"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
version "5.15.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.15.0.tgz#e16967894eeba6e1091509ec83f0c60e179f2425"
integrity sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==
dependencies:
"@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"
@@ -6363,9 +6363,9 @@ typescript@^3.2.2:
integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==
typescript@^4.5.3, typescript@^4.5.4:
version "4.7.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
version "4.8.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
typeson-registry@^1.0.0-alpha.20:
version "1.0.0-alpha.39"
@@ -6572,9 +6572,9 @@ vue-docgen-api@^3.26.0:
vue-template-compiler "^2.0.0"
vue-template-compiler@^2.0.0:
version "2.7.9"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.9.tgz#ffbeb1769ae6af65cd405a6513df6b1e20e33616"
integrity sha512-NPJxt6OjVlzmkixYg0SdIN2Lw/rMryQskObY89uAMcM9flS/HrmLK5LaN1ReBTuhBgnYuagZZEkSS6FESATQUQ==
version "2.7.10"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.10.tgz#9e20f35b2fdccacacf732dd7dedb49bf65f4556b"
integrity sha512-QO+8R9YRq1Gudm8ZMdo/lImZLJVUIAM8c07Vp84ojdDAf8HmPJc7XB556PcXV218k2AkKznsRz6xB5uOjAC4EQ==
dependencies:
de-indent "^1.0.2"
he "^1.2.0"