Compare commits
21 Commits
v19.4.0
...
v19.5.0-rc.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ba30bc4ef | |||
| 6e28634819 | |||
| b1e70c5404 | |||
| b11c502a40 | |||
| 167f51c8cd | |||
| 5d2753241e | |||
| 274fe447fd | |||
| c0f1849a83 | |||
| 7b2b618d26 | |||
| 37187ef347 | |||
| e87ce873b0 | |||
| 207171efd6 | |||
| 8cc5efdf46 | |||
| cbcf47d5c0 | |||
| bbaa0e6536 | |||
| 1fbd8983ed | |||
| 1c77816dbd | |||
| c1160f40c2 | |||
| b789cc5933 | |||
| 5e4474b959 | |||
| 8e646ea584 |
@@ -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 }}
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
Executable
+37
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
+290
-210
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) {
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -12,6 +12,6 @@
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"./spec/**/*.ts",
|
||||
"./spec/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user