diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 21ebc70c2..10bda8e20 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -25,6 +25,6 @@ jobs: steps: - uses: tibdex/backport@v2 with: - labels_template: "<%= JSON.stringify(labels) %>" + labels_template: "<%= JSON.stringify([...labels, 'X-Release-Blocker']) %>" # We can't use GITHUB_TOKEN here or CI won't run on the new PR github_token: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml new file mode 100644 index 000000000..512af0c15 --- /dev/null +++ b/.github/workflows/release-npm.yml @@ -0,0 +1,39 @@ +# Must only be called from `release#published` triggers +name: Publish to npm +on: + workflow_call: + secrets: + NPM_TOKEN: + required: true +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 }} diff --git a/.github/workflows/jsdoc.yml b/.github/workflows/release.yml similarity index 90% rename from .github/workflows/jsdoc.yml rename to .github/workflows/release.yml index 3763c7841..5b7621c49 100644 --- a/.github/workflows/jsdoc.yml +++ b/.github/workflows/release.yml @@ -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 }} diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 64bfd4b1b..10394ff79 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -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@v4.0.0 + 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 }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 00691cac0..3d86e0282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +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)). + * Add txn_id support to sliding sync ([\#2567](https://github.com/matrix-org/matrix-js-sdk/pull/2567)). + +## 🐛 Bug Fixes + * 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. + * fixed a sliding sync bug which could cause the `roomIndexToRoomId` map to be incorrect when a new room is added in the middle of the list or when an existing room is deleted from the middle of the list. ([\#2610](https://github.com/matrix-org/matrix-js-sdk/pull/2610)). + * Fix: Handle parsing of a beacon info event without asset ([\#2591](https://github.com/matrix-org/matrix-js-sdk/pull/2591)). Fixes vector-im/element-web#23078. Contributed by @kerryarchibald. + * Fix finding event read up to if stable private read receipts is missing ([\#2585](https://github.com/matrix-org/matrix-js-sdk/pull/2585)). Fixes vector-im/element-web#23027. + * fixed a sliding sync issue where history could be interpreted as live events. ([\#2583](https://github.com/matrix-org/matrix-js-sdk/pull/2583)). + Changes in [19.3.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v19.3.0) (2022-08-16) ================================================================================================== diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7df3845e3..7405ed23f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 -``` - -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 diff --git a/package.json b/package.json index 6babecc69..962781c00 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "19.3.0", + "version": "19.4.0", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=12.9.0" @@ -84,25 +84,25 @@ "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz", "@types/bs58": "^4.0.1", "@types/content-type": "^1.1.5", - "@types/jest": "^28.0.0", + "@types/jest": "^29.0.0", "@types/node": "16", "@types/request": "^2.48.5", "@typescript-eslint/eslint-plugin": "^5.6.0", "@typescript-eslint/parser": "^5.6.0", "allchange": "^1.0.6", - "babel-jest": "^28.0.0", + "babel-jest": "^29.0.0", "babelify": "^10.0.0", "better-docs": "^2.4.0-beta.9", "browserify": "^17.0.0", "docdash": "^1.2.0", - "eslint": "8.20.0", + "eslint": "8.23.0", "eslint-config-google": "^0.14.0", "eslint-plugin-import": "^2.25.4", - "eslint-plugin-matrix-org": "^0.5.0", + "eslint-plugin-matrix-org": "^0.6.0", "exorcist": "^2.0.0", "fake-indexeddb": "^4.0.0", - "jest": "^28.0.0", "jest-environment-jsdom": "^28.1.3", + "jest": "^29.0.0", "jest-localstorage-mock": "^2.4.6", "jest-sonar-reporter": "^2.0.0", "jsdoc": "^3.6.6", diff --git a/post-release.sh b/post-release.sh new file mode 100755 index 000000000..4e93f668a --- /dev/null +++ b/post-release.sh @@ -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 diff --git a/release.sh b/release.sh index 63f0765c1..e8a082d4d 100755 --- a/release.sh +++ b/release.sh @@ -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,19 +196,19 @@ 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 pkglock='' fi -git commit package.json $pkglock -m "$tag" +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 diff --git a/spec/MockStorageApi.js b/spec/MockStorageApi.ts similarity index 65% rename from spec/MockStorageApi.js rename to spec/MockStorageApi.ts index d985fe0cf..da002ed66 100644 --- a/spec/MockStorageApi.js +++ b/spec/MockStorageApi.ts @@ -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 = {}; + 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; - }, -}; - + } +} diff --git a/spec/TestClient.ts b/spec/TestClient.ts index a1e5b9111..0a6c4e0ee 100644 --- a/spec/TestClient.ts +++ b/spec/TestClient.ts @@ -50,7 +50,7 @@ export class TestClient { options?: Partial, ) { if (sessionStoreBackend === undefined) { - sessionStoreBackend = new MockStorageApi(); + sessionStoreBackend = new MockStorageApi() as unknown as Storage; } this.httpBackend = new MockHttpBackend(); diff --git a/spec/integ/matrix-client-syncing.spec.js b/spec/integ/matrix-client-syncing.spec.ts similarity index 74% rename from spec/integ/matrix-client-syncing.spec.js rename to spec/integ/matrix-client-syncing.spec.ts index 0c571707a..9f3fb9887 100644 --- a/spec/integ/matrix-client-syncing.spec.js +++ b/spec/integ/matrix-client-syncing.spec.ts @@ -14,14 +14,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventTimeline, MatrixEvent, RoomEvent, RoomStateEvent, RoomMemberEvent } from "../../src"; -import { UNSTABLE_MSC2716_MARKER } from "../../src/@types/event"; +import { Optional } from "matrix-events-sdk/lib/types"; +import HttpBackend from "matrix-mock-request"; + +import { + EventTimeline, + MatrixEvent, + RoomEvent, + RoomStateEvent, + RoomMemberEvent, + UNSTABLE_MSC2716_MARKER, + MatrixClient, +} from "../../src"; import * as utils from "../test-utils/test-utils"; import { TestClient } from "../TestClient"; -describe("MatrixClient syncing", function() { - let client = null; - let httpBackend = null; +describe("MatrixClient syncing", () => { + let client: Optional = null; + let httpBackend: Optional = null; const selfUserId = "@alice:localhost"; const selfAccessToken = "aseukfgwef"; const otherUserId = "@bob:localhost"; @@ -31,48 +41,47 @@ describe("MatrixClient syncing", function() { const roomOne = "!foo:localhost"; const roomTwo = "!bar:localhost"; - beforeEach(function() { + beforeEach(() => { const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken); httpBackend = testClient.httpBackend; client = testClient.client; - httpBackend.when("GET", "/versions").respond(200, {}); - httpBackend.when("GET", "/pushrules").respond(200, {}); - httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); + httpBackend!.when("GET", "/versions").respond(200, {}); + httpBackend!.when("GET", "/pushrules").respond(200, {}); + httpBackend!.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); }); - afterEach(function() { - httpBackend.verifyNoOutstandingExpectation(); - client.stopClient(); - return httpBackend.stop(); + afterEach(() => { + httpBackend!.verifyNoOutstandingExpectation(); + client!.stopClient(); + return httpBackend!.stop(); }); - describe("startClient", function() { + describe("startClient", () => { const syncData = { next_batch: "batch_token", rooms: {}, presence: {}, }; - it("should /sync after /pushrules and /filter.", function(done) { - httpBackend.when("GET", "/sync").respond(200, syncData); + it("should /sync after /pushrules and /filter.", (done) => { + httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); - httpBackend.flushAllExpected().then(function() { + httpBackend!.flushAllExpected().then(() => { done(); }); }); - it("should pass the 'next_batch' token from /sync to the since= param " + - " of the next /sync", function(done) { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").check(function(req) { + it("should pass the 'next_batch' token from /sync to the since= param of the next /sync", (done) => { + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").check((req) => { expect(req.queryParams.since).toEqual(syncData.next_batch); }).respond(200, syncData); - client.startClient(); + client!.startClient(); - httpBackend.flushAllExpected().then(function() { + httpBackend!.flushAllExpected().then(() => { done(); }); }); @@ -96,14 +105,14 @@ describe("MatrixClient syncing", function() { }, }, }; - httpBackend.when("GET", "/sync").respond(200, { + httpBackend!.when("GET", "/sync").respond(200, { ...syncData, rooms: inviteSyncRoomSection, }); // Second sync: a leave (reject of some kind) - httpBackend.when("POST", "/leave").respond(200, {}); - httpBackend.when("GET", "/sync").respond(200, { + httpBackend!.when("POST", "/leave").respond(200, {}); + httpBackend!.when("GET", "/sync").respond(200, { ...syncData, rooms: { leave: { @@ -143,28 +152,28 @@ describe("MatrixClient syncing", function() { }); // Third sync: another invite - httpBackend.when("GET", "/sync").respond(200, { + httpBackend!.when("GET", "/sync").respond(200, { ...syncData, rooms: inviteSyncRoomSection, }); // First fire: an initial invite let fires = 0; - client.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { // Room, string, string + client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { // Room, string, string fires++; expect(room.roomId).toBe(roomId); expect(membership).toBe("invite"); expect(oldMembership).toBeFalsy(); // Second fire: a leave - client.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { + client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { fires++; expect(room.roomId).toBe(roomId); expect(membership).toBe("leave"); expect(oldMembership).toBe("invite"); // Third/final fire: a second invite - client.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { + client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => { fires++; expect(room.roomId).toBe(roomId); expect(membership).toBe("invite"); @@ -173,18 +182,81 @@ describe("MatrixClient syncing", function() { }); // For maximum safety, "leave" the room after we register the handler - client.leave(roomId); + client!.leave(roomId); }); // noinspection ES6MissingAwait - client.startClient(); - await httpBackend.flushAllExpected(); + client!.startClient(); + await httpBackend!.flushAllExpected(); expect(fires).toBe(3); }); + + it("should honour lazyLoadMembers if user is not a guest", () => { + client!.doesServerSupportLazyLoading = jest.fn().mockResolvedValue(true); + + httpBackend!.when("GET", "/sync").check((req) => { + expect(JSON.parse(req.queryParams.filter).room.state.lazy_load_members).toBeTruthy(); + }).respond(200, syncData); + + client!.setGuest(false); + client!.startClient({ lazyLoadMembers: true }); + + return httpBackend!.flushAllExpected(); + }); + + it("should not honour lazyLoadMembers if user is a guest", () => { + httpBackend!.expectedRequests = []; + httpBackend!.when("GET", "/versions").respond(200, {}); + client!.doesServerSupportLazyLoading = jest.fn().mockResolvedValue(true); + + httpBackend!.when("GET", "/sync").check((req) => { + expect(JSON.parse(req.queryParams.filter).room?.state?.lazy_load_members).toBeFalsy(); + }).respond(200, syncData); + + client!.setGuest(true); + client!.startClient({ lazyLoadMembers: true }); + + return httpBackend!.flushAllExpected(); + }); }); - describe("resolving invites to profile info", function() { + describe("initial sync", () => { + const syncData = { + next_batch: "batch_token", + rooms: {}, + presence: {}, + }; + + it("should only apply initialSyncLimit to the initial sync", () => { + // 1st request + httpBackend!.when("GET", "/sync").check((req) => { + expect(JSON.parse(req.queryParams.filter).room.timeline.limit).toEqual(1); + }).respond(200, syncData); + // 2nd request + httpBackend!.when("GET", "/sync").check((req) => { + expect(req.queryParams.filter).toEqual("a filter id"); + }).respond(200, syncData); + + client!.startClient({ initialSyncLimit: 1 }); + + httpBackend!.flushSync(undefined); + return httpBackend!.flushAllExpected(); + }); + + it("should not apply initialSyncLimit to a first sync if we have a stored token", () => { + httpBackend!.when("GET", "/sync").check((req) => { + expect(req.queryParams.filter).toEqual("a filter id"); + }).respond(200, syncData); + + client!.store.getSavedSyncToken = jest.fn().mockResolvedValue("this-is-a-token"); + client!.startClient({ initialSyncLimit: 1 }); + + return httpBackend!.flushAllExpected(); + }); + }); + + describe("resolving invites to profile info", () => { const syncData = { next_batch: "s_5_3", presence: { @@ -197,7 +269,7 @@ describe("MatrixClient syncing", function() { }, }; - beforeEach(function() { + beforeEach(() => { syncData.presence.events = []; syncData.rooms.join[roomOne] = { timeline: { @@ -226,41 +298,43 @@ describe("MatrixClient syncing", function() { }; }); - it("should resolve incoming invites from /sync", function() { + it("should resolve incoming invites from /sync", () => { syncData.rooms.join[roomOne].state.events.push( utils.mkMembership({ room: roomOne, mship: "invite", user: userC, }), ); - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/profile/" + encodeURIComponent(userC)).respond( + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/profile/" + encodeURIComponent(userC)).respond( 200, { avatar_url: "mxc://flibble/wibble", displayname: "The Boss", }, ); - client.startClient({ + client!.startClient({ resolveInvitesToProfiles: true, }); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { - const member = client.getRoom(roomOne).getMember(userC); + ]).then(() => { + const member = client!.getRoom(roomOne).getMember(userC); expect(member.name).toEqual("The Boss"); expect( - member.getAvatarUrl("home.server.url", null, null, null, false), + member.getAvatarUrl("home.server.url", null, null, null, false, false), ).toBeTruthy(); }); }); - it("should use cached values from m.presence wherever possible", function() { + it("should use cached values from m.presence wherever possible", () => { syncData.presence.events = [ utils.mkPresence({ - user: userC, presence: "online", name: "The Ghost", + user: userC, + presence: "online", + name: "The Ghost", }), ]; syncData.rooms.join[roomOne].state.events.push( @@ -269,25 +343,27 @@ describe("MatrixClient syncing", function() { }), ); - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient({ + client!.startClient({ resolveInvitesToProfiles: true, }); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { - const member = client.getRoom(roomOne).getMember(userC); + ]).then(() => { + const member = client!.getRoom(roomOne).getMember(userC); expect(member.name).toEqual("The Ghost"); }); }); - it("should result in events on the room member firing", function() { + it("should result in events on the room member firing", () => { syncData.presence.events = [ utils.mkPresence({ - user: userC, presence: "online", name: "The Ghost", + user: userC, + presence: "online", + name: "The Ghost", }), ]; syncData.rooms.join[roomOne].state.events.push( @@ -296,83 +372,84 @@ describe("MatrixClient syncing", function() { }), ); - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); let latestFiredName = null; - client.on(RoomMemberEvent.Name, function(event, m) { + client!.on(RoomMemberEvent.Name, (event, m) => { if (m.userId === userC && m.roomId === roomOne) { latestFiredName = m.name; } }); - client.startClient({ + client!.startClient({ resolveInvitesToProfiles: true, }); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { + ]).then(() => { expect(latestFiredName).toEqual("The Ghost"); }); }); - it("should no-op if resolveInvitesToProfiles is not set", function() { + it("should no-op if resolveInvitesToProfiles is not set", () => { syncData.rooms.join[roomOne].state.events.push( utils.mkMembership({ room: roomOne, mship: "invite", user: userC, }), ); - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { - const member = client.getRoom(roomOne).getMember(userC); + ]).then(() => { + const member = client!.getRoom(roomOne).getMember(userC); expect(member.name).toEqual(userC); expect( - member.getAvatarUrl("home.server.url", null, null, null, false), + member.getAvatarUrl("home.server.url", null, null, null, false, false), ).toBe(null); }); }); }); - describe("users", function() { + describe("users", () => { const syncData = { next_batch: "nb", presence: { events: [ utils.mkPresence({ - user: userA, presence: "online", + user: userA, + presence: "online", }), utils.mkPresence({ - user: userB, presence: "unavailable", + user: userB, + presence: "unavailable", }), ], }, }; - it("should create users for presence events from /sync", - function() { - httpBackend.when("GET", "/sync").respond(200, syncData); + it("should create users for presence events from /sync", () => { + httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { - expect(client.getUser(userA).presence).toEqual("online"); - expect(client.getUser(userB).presence).toEqual("unavailable"); + ]).then(() => { + expect(client!.getUser(userA).presence).toEqual("online"); + expect(client!.getUser(userB).presence).toEqual("unavailable"); }); }); }); - describe("room state", function() { + describe("room state", () => { const msgText = "some text here"; const otherDisplayName = "Bob Smith"; @@ -478,17 +555,17 @@ describe("MatrixClient syncing", function() { }, }; - it("should continually recalculate the right room name.", function() { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + it("should continually recalculate the right room name.", () => { + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), - ]).then(function() { - const room = client.getRoom(roomOne); + ]).then(() => { + const room = client!.getRoom(roomOne); // should have clobbered the name to the one from /events expect(room.name).toEqual( nextSyncData.rooms.join[roomOne].state.events[0].content.name, @@ -496,49 +573,49 @@ describe("MatrixClient syncing", function() { }); }); - it("should store the right events in the timeline.", function() { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + it("should store the right events in the timeline.", () => { + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), - ]).then(function() { - const room = client.getRoom(roomTwo); + ]).then(() => { + const room = client!.getRoom(roomTwo); // should have added the message from /events expect(room.timeline.length).toEqual(2); expect(room.timeline[1].getContent().body).toEqual(msgText); }); }); - it("should set the right room name.", function() { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + it("should set the right room name.", () => { + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), - ]).then(function() { - const room = client.getRoom(roomTwo); + ]).then(() => { + const room = client!.getRoom(roomTwo); // should use the display name of the other person. expect(room.name).toEqual(otherDisplayName); }); }); - it("should set the right user's typing flag.", function() { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + it("should set the right user's typing flag.", () => { + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), - ]).then(function() { - const room = client.getRoom(roomTwo); + ]).then(() => { + const room = client!.getRoom(roomTwo); let member = room.getMember(otherUserId); expect(member).toBeTruthy(); expect(member.typing).toEqual(true); @@ -552,16 +629,16 @@ describe("MatrixClient syncing", function() { // events that arrive in the incremental sync as if they preceeded the // timeline events, however this breaks peeking, so it's disabled // (see sync.js) - xit("should correctly interpret state in incremental sync.", function() { - httpBackend.when("GET", "/sync").respond(200, syncData); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + xit("should correctly interpret state in incremental sync.", () => { + httpBackend!.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), - ]).then(function() { - const room = client.getRoom(roomOne); + ]).then(() => { + const room = client!.getRoom(roomOne); const stateAtStart = room.getLiveTimeline().getState( EventTimeline.BACKWARDS, ); @@ -576,11 +653,11 @@ describe("MatrixClient syncing", function() { }); }); - xit("should update power levels for users in a room", function() { + xit("should update power levels for users in a room", () => { }); - xit("should update the room topic", function() { + xit("should update the room topic", () => { }); @@ -650,16 +727,16 @@ describe("MatrixClient syncing", function() { expect(markerEvent.sender).toBeDefined(); expect(markerEvent.sender).not.toEqual(roomCreateEvent.sender); - httpBackend.when("GET", "/sync").respond(200, normalFirstSync); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + httpBackend!.when("GET", "/sync").respond(200, normalFirstSync); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]); - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room.getTimelineNeedsRefresh()).toEqual(false); }); @@ -721,15 +798,15 @@ describe("MatrixClient syncing", function() { }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room.getTimelineNeedsRefresh()).toEqual(false); }); @@ -751,15 +828,15 @@ describe("MatrixClient syncing", function() { }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room.getTimelineNeedsRefresh()).toEqual(false); }); @@ -784,15 +861,15 @@ describe("MatrixClient syncing", function() { }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room.getTimelineNeedsRefresh()).toEqual(false); }); @@ -818,27 +895,27 @@ describe("MatrixClient syncing", function() { const markerEventId = nextSyncData.rooms.join[roomOne].timeline.events[0].event_id; // Only do the first sync - httpBackend.when("GET", "/sync").respond(200, normalFirstSync); - client.startClient(); + httpBackend!.when("GET", "/sync").respond(200, normalFirstSync); + client!.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); // Get the room after the first sync so the room is created - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); let emitCount = 0; - room.on(RoomEvent.HistoryImportedWithinTimeline, function(markerEvent, room) { + room.on(RoomEvent.HistoryImportedWithinTimeline, (markerEvent, room) => { expect(markerEvent.getId()).toEqual(markerEventId); expect(room.roomId).toEqual(roomOne); emitCount += 1; }); // Now do a subsequent sync with the marker event - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); @@ -873,16 +950,16 @@ describe("MatrixClient syncing", function() { }, }; - httpBackend.when("GET", "/sync").respond(200, normalFirstSync); - httpBackend.when("GET", "/sync").respond(200, nextSyncData); + httpBackend!.when("GET", "/sync").respond(200, normalFirstSync); + httpBackend!.when("GET", "/sync").respond(200, nextSyncData); - client.startClient(); + client!.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(2), ]); - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room.getTimelineNeedsRefresh()).toEqual(true); }); }); @@ -929,19 +1006,19 @@ describe("MatrixClient syncing", function() { it("should be able to listen to state events even after " + "the timeline is reset during `limited` sync response", async () => { // Create a room from the sync - httpBackend.when("GET", "/sync").respond(200, syncData); - client.startClient(); + httpBackend!.when("GET", "/sync").respond(200, syncData); + client!.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); // Get the room after the first sync so the room is created - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room).toBeTruthy(); let stateEventEmitCount = 0; - client.on(RoomStateEvent.Update, () => { + client!.on(RoomStateEvent.Update, () => { stateEventEmitCount += 1; }); @@ -969,10 +1046,10 @@ describe("MatrixClient syncing", function() { prev_batch: "newerTok", }, }; - httpBackend.when("GET", "/sync").respond(200, limitedSyncData); + httpBackend!.when("GET", "/sync").respond(200, limitedSyncData); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); @@ -997,25 +1074,25 @@ describe("MatrixClient syncing", function() { { timelineSupport: true }, ); httpBackend = testClientWithTimelineSupport.httpBackend; - httpBackend.when("GET", "/versions").respond(200, {}); - httpBackend.when("GET", "/pushrules").respond(200, {}); - httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); + httpBackend!.when("GET", "/versions").respond(200, {}); + httpBackend!.when("GET", "/pushrules").respond(200, {}); + httpBackend!.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); client = testClientWithTimelineSupport.client; // Create a room from the sync - httpBackend.when("GET", "/sync").respond(200, syncData); - client.startClient(); + httpBackend!.when("GET", "/sync").respond(200, syncData); + client!.startClient(); await Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); // Get the room after the first sync so the room is created - const room = client.getRoom(roomOne); + const room = client!.getRoom(roomOne); expect(room).toBeTruthy(); let stateEventEmitCount = 0; - client.on(RoomStateEvent.Update, () => { + client!.on(RoomStateEvent.Update, () => { stateEventEmitCount += 1; }); @@ -1027,8 +1104,8 @@ describe("MatrixClient syncing", function() { const eventsInRoom = syncData.rooms.join[roomOne].timeline.events; const contextUrl = `/rooms/${encodeURIComponent(roomOne)}/context/` + `${encodeURIComponent(eventsInRoom[0].event_id)}`; - httpBackend.when("GET", contextUrl) - .respond(200, function() { + httpBackend!.when("GET", contextUrl) + .respond(200, () => { return { start: "start_token", events_before: [EVENTS[1], EVENTS[0]], @@ -1045,7 +1122,7 @@ describe("MatrixClient syncing", function() { // reference to change await Promise.all([ room.refreshLiveTimeline(), - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), ]); // Cause `RoomStateEvent.Update` to be fired @@ -1056,8 +1133,8 @@ describe("MatrixClient syncing", function() { }); }); - describe("timeline", function() { - beforeEach(function() { + describe("timeline", () => { + beforeEach(() => { const syncData = { next_batch: "batch_token", rooms: { @@ -1075,16 +1152,16 @@ describe("MatrixClient syncing", function() { }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), ]); }); - it("should set the back-pagination token on new rooms", function() { + it("should set the back-pagination token on new rooms", () => { const syncData = { next_batch: "batch_token", rooms: { @@ -1102,13 +1179,13 @@ describe("MatrixClient syncing", function() { }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { - const room = client.getRoom(roomTwo); + ]).then(() => { + const room = client!.getRoom(roomTwo); expect(room).toBeTruthy(); const tok = room.getLiveTimeline() .getPaginationToken(EventTimeline.BACKWARDS); @@ -1116,7 +1193,7 @@ describe("MatrixClient syncing", function() { }); }); - it("should set the back-pagination token on gappy syncs", function() { + it("should set the back-pagination token on gappy syncs", () => { const syncData = { next_batch: "batch_token", rooms: { @@ -1134,11 +1211,11 @@ describe("MatrixClient syncing", function() { prev_batch: "newerTok", }, }; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); let resetCallCount = 0; // the token should be set *before* timelineReset is emitted - client.on(RoomEvent.TimelineReset, function(room) { + client!.on(RoomEvent.TimelineReset, (room) => { resetCallCount++; const tl = room.getLiveTimeline(); @@ -1148,10 +1225,10 @@ describe("MatrixClient syncing", function() { }); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { - const room = client.getRoom(roomOne); + ]).then(() => { + const room = client!.getRoom(roomOne); const tl = room.getLiveTimeline(); expect(tl.getEvents().length).toEqual(1); expect(resetCallCount).toEqual(1); @@ -1159,7 +1236,7 @@ describe("MatrixClient syncing", function() { }); }); - describe("receipts", function() { + describe("receipts", () => { const syncData = { rooms: { join: { @@ -1202,13 +1279,13 @@ describe("MatrixClient syncing", function() { }, }; - beforeEach(function() { + beforeEach(() => { syncData.rooms.join[roomOne].ephemeral = { events: [], }; }); - it("should sync receipts from /sync.", function() { + it("should sync receipts from /sync.", () => { const ackEvent = syncData.rooms.join[roomOne].timeline.events[0]; const receipt = {}; receipt[ackEvent.event_id] = { @@ -1222,15 +1299,15 @@ describe("MatrixClient syncing", function() { room_id: roomOne, type: "m.receipt", }]; - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); - client.startClient(); + client!.startClient(); return Promise.all([ - httpBackend.flushAllExpected(), + httpBackend!.flushAllExpected(), awaitSyncEvent(), - ]).then(function() { - const room = client.getRoom(roomOne); + ]).then(() => { + const room = client!.getRoom(roomOne); expect(room.getReceiptsForEvent(new MatrixEvent(ackEvent))).toEqual([{ type: "m.read", userId: userC, @@ -1242,59 +1319,62 @@ describe("MatrixClient syncing", function() { }); }); - describe("of a room", function() { + describe("of a room", () => { xit("should sync when a join event (which changes state) for the user" + - " arrives down the event stream (e.g. join from another device)", function() { + " arrives down the event stream (e.g. join from another device)", () => { }); - xit("should sync when the user explicitly calls joinRoom", function() { + xit("should sync when the user explicitly calls joinRoom", () => { }); }); - describe("syncLeftRooms", function() { - beforeEach(function(done) { - client.startClient(); + describe("syncLeftRooms", () => { + beforeEach((done) => { + client!.startClient(); - httpBackend.flushAllExpected().then(function() { + httpBackend!.flushAllExpected().then(() => { // the /sync call from syncLeftRooms ends up in the request // queue behind the call from the running client; add a response // to flush the client's one out. - httpBackend.when("GET", "/sync").respond(200, {}); + httpBackend!.when("GET", "/sync").respond(200, {}); done(); }); }); - it("should create and use an appropriate filter", function() { - httpBackend.when("POST", "/filter").check(function(req) { + it("should create and use an appropriate filter", () => { + httpBackend!.when("POST", "/filter").check((req) => { expect(req.data).toEqual({ - room: { timeline: { limit: 1 }, - include_leave: true } }); + room: { + timeline: { limit: 1 }, + include_leave: true, + }, + }); }).respond(200, { filter_id: "another_id" }); - const prom = new Promise((resolve) => { - httpBackend.when("GET", "/sync").check(function(req) { + const prom = new Promise((resolve) => { + httpBackend!.when("GET", "/sync").check((req) => { expect(req.queryParams.filter).toEqual("another_id"); resolve(); }).respond(200, {}); }); - client.syncLeftRooms(); + client!.syncLeftRooms(); // first flush the filter request; this will make syncLeftRooms // make its /sync call return Promise.all([ - httpBackend.flush("/filter").then(function() { + httpBackend!.flush("/filter").then(() => { // flush the syncs - return httpBackend.flushAllExpected(); + return httpBackend!.flushAllExpected(); }), prom, ]); }); - it("should set the back-pagination token on left rooms", function() { + it("should set the back-pagination token on left rooms", () => { const syncData = { next_batch: "batch_token", rooms: { @@ -1313,15 +1393,15 @@ describe("MatrixClient syncing", function() { }, }; - httpBackend.when("POST", "/filter").respond(200, { + httpBackend!.when("POST", "/filter").respond(200, { filter_id: "another_id", }); - httpBackend.when("GET", "/sync").respond(200, syncData); + httpBackend!.when("GET", "/sync").respond(200, syncData); return Promise.all([ - client.syncLeftRooms().then(function() { - const room = client.getRoom(roomTwo); + client!.syncLeftRooms().then(() => { + const room = client!.getRoom(roomTwo); const tok = room.getLiveTimeline().getPaginationToken( EventTimeline.BACKWARDS); @@ -1329,8 +1409,8 @@ describe("MatrixClient syncing", function() { }), // first flush the filter request; this will make syncLeftRooms make its /sync call - httpBackend.flush("/filter").then(function() { - return httpBackend.flushAllExpected(); + httpBackend!.flush("/filter").then(() => { + return httpBackend!.flushAllExpected(); }), ]); }); @@ -1342,7 +1422,7 @@ describe("MatrixClient syncing", function() { * @param {Number?} numSyncs number of syncs to wait for * @returns {Promise} promise which resolves after the sync events have happened */ - function awaitSyncEvent(numSyncs) { + function awaitSyncEvent(numSyncs?: number) { return utils.syncPromise(client, numSyncs); } }); diff --git a/spec/integ/sliding-sync-sdk.spec.ts b/spec/integ/sliding-sync-sdk.spec.ts index 34d84bdda..f7dc68754 100644 --- a/spec/integ/sliding-sync-sdk.spec.ts +++ b/spec/integ/sliding-sync-sdk.spec.ts @@ -168,6 +168,7 @@ describe("SlidingSyncSdk", () => { const roomD = "!d_with_notif_count:localhost"; const roomE = "!e_with_invite:localhost"; const roomF = "!f_calc_room_name:localhost"; + const roomG = "!g_join_invite_counts:localhost"; const data: Record = { [roomA]: { name: "A", @@ -261,12 +262,25 @@ describe("SlidingSyncSdk", () => { ], initial: true, }, + [roomG]: { + name: "G", + required_state: [], + timeline: [ + mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""), + mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId), + mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""), + ], + joined_count: 5, + invited_count: 2, + initial: true, + }, }; it("can be created with required_state and timeline", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomA, data[roomA]); const gotRoom = client.getRoom(roomA); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect(gotRoom.name).toEqual(data[roomA].name); expect(gotRoom.getMyMembership()).toEqual("join"); assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-2), data[roomA].timeline); @@ -276,6 +290,7 @@ describe("SlidingSyncSdk", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomB, data[roomB]); const gotRoom = client.getRoom(roomB); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect(gotRoom.name).toEqual(data[roomB].name); expect(gotRoom.getMyMembership()).toEqual("join"); assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-5), data[roomB].timeline); @@ -285,6 +300,7 @@ describe("SlidingSyncSdk", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomC, data[roomC]); const gotRoom = client.getRoom(roomC); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect( gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight), ).toEqual(data[roomC].highlight_count); @@ -294,15 +310,26 @@ describe("SlidingSyncSdk", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomD, data[roomD]); const gotRoom = client.getRoom(roomD); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect( gotRoom.getUnreadNotificationCount(NotificationCountType.Total), ).toEqual(data[roomD].notification_count); }); + it("can be created with an invited/joined_count", () => { + mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomG, data[roomG]); + const gotRoom = client.getRoom(roomG); + expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } + expect(gotRoom.getInvitedMemberCount()).toEqual(data[roomG].invited_count); + expect(gotRoom.getJoinedMemberCount()).toEqual(data[roomG].joined_count); + }); + it("can be created with invite_state", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomE, data[roomE]); const gotRoom = client.getRoom(roomE); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect(gotRoom.getMyMembership()).toEqual("invite"); expect(gotRoom.currentState.getJoinRule()).toEqual(JoinRule.Invite); }); @@ -311,6 +338,7 @@ describe("SlidingSyncSdk", () => { mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomF, data[roomF]); const gotRoom = client.getRoom(roomF); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect( gotRoom.name, ).toEqual(data[roomF].name); @@ -326,6 +354,7 @@ describe("SlidingSyncSdk", () => { }); const gotRoom = client.getRoom(roomA); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } const newTimeline = data[roomA].timeline; newTimeline.push(newEvent); assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-3), newTimeline); @@ -333,6 +362,8 @@ describe("SlidingSyncSdk", () => { it("can update with a new required_state event", async () => { let gotRoom = client.getRoom(roomB); + expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect(gotRoom.getJoinRule()).toEqual(JoinRule.Invite); // default mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomB, { required_state: [ @@ -343,6 +374,7 @@ describe("SlidingSyncSdk", () => { }); gotRoom = client.getRoom(roomB); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect(gotRoom.getJoinRule()).toEqual(JoinRule.Restricted); }); @@ -355,6 +387,7 @@ describe("SlidingSyncSdk", () => { }); const gotRoom = client.getRoom(roomC); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect( gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight), ).toEqual(1); @@ -369,11 +402,25 @@ describe("SlidingSyncSdk", () => { }); const gotRoom = client.getRoom(roomD); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } expect( gotRoom.getUnreadNotificationCount(NotificationCountType.Total), ).toEqual(1); }); + it("can update with a new joined_count", () => { + mockSlidingSync.emit(SlidingSyncEvent.RoomData, roomG, { + name: data[roomD].name, + required_state: [], + timeline: [], + joined_count: 1, + }); + const gotRoom = client.getRoom(roomG); + expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } + expect(gotRoom.getJoinedMemberCount()).toEqual(1); + }); + // Regression test for a bug which caused the timeline entries to be out-of-order // when the same room appears twice with different timeline limits. E.g appears in // the list with timeline_limit:1 then appears again as a room subscription with @@ -394,6 +441,7 @@ describe("SlidingSyncSdk", () => { }); const gotRoom = client.getRoom(roomA); expect(gotRoom).toBeDefined(); + if (gotRoom == null) { return; } logger.log("want:", oldTimeline.map((e) => (e.type + " : " + (e.content || {}).body))); logger.log("got:", gotRoom.getLiveTimeline().getEvents().map( diff --git a/spec/integ/sliding-sync.spec.ts b/spec/integ/sliding-sync.spec.ts index 8c4a7ad12..4cfe39215 100644 --- a/spec/integ/sliding-sync.spec.ts +++ b/spec/integ/sliding-sync.spec.ts @@ -558,6 +558,153 @@ describe("SlidingSync", () => { await httpBackend.flushAllExpected(); await responseProcessed; await listPromise; + }); + + // this refers to a set of operations where the end result is no change. + it("should handle net zero operations correctly", async () => { + const indexToRoomId = { + 0: roomB, + 1: roomC, + }; + expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual(indexToRoomId); + httpBackend.when("POST", syncUrl).respond(200, { + pos: "f", + // currently the list is [B,C] so we will insert D then immediately delete it + lists: [{ + count: 500, + ops: [ + { + op: "DELETE", index: 2, + }, + { + op: "INSERT", index: 0, room_id: roomA, + }, + { + op: "DELETE", index: 0, + }, + ], + }, + { + count: 50, + }], + }); + const listPromise = listenUntil(slidingSync, "SlidingSync.List", + (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); + expect(joinedCount).toEqual(500); + expect(roomIndexToRoomId).toEqual(indexToRoomId); + return true; + }); + const responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => { + return state === SlidingSyncState.Complete; + }); + await httpBackend.flushAllExpected(); + await responseProcessed; + await listPromise; + }); + + it("should handle deletions correctly", async () => { + expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual({ + 0: roomB, + 1: roomC, + }); + httpBackend.when("POST", syncUrl).respond(200, { + pos: "g", + lists: [{ + count: 499, + ops: [ + { + op: "DELETE", index: 0, + }, + ], + }, + { + count: 50, + }], + }); + const listPromise = listenUntil(slidingSync, "SlidingSync.List", + (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); + expect(joinedCount).toEqual(499); + expect(roomIndexToRoomId).toEqual({ + 0: roomC, + }); + return true; + }); + const responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => { + return state === SlidingSyncState.Complete; + }); + await httpBackend.flushAllExpected(); + await responseProcessed; + await listPromise; + }); + + it("should handle insertions correctly", async () => { + expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual({ + 0: roomC, + }); + httpBackend.when("POST", syncUrl).respond(200, { + pos: "h", + lists: [{ + count: 500, + ops: [ + { + op: "INSERT", index: 1, room_id: roomA, + }, + ], + }, + { + count: 50, + }], + }); + let listPromise = listenUntil(slidingSync, "SlidingSync.List", + (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); + expect(joinedCount).toEqual(500); + expect(roomIndexToRoomId).toEqual({ + 0: roomC, + 1: roomA, + }); + return true; + }); + let responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => { + return state === SlidingSyncState.Complete; + }); + await httpBackend.flushAllExpected(); + await responseProcessed; + await listPromise; + + httpBackend.when("POST", syncUrl).respond(200, { + pos: "h", + lists: [{ + count: 501, + ops: [ + { + op: "INSERT", index: 1, room_id: roomB, + }, + ], + }, + { + count: 50, + }], + }); + listPromise = listenUntil(slidingSync, "SlidingSync.List", + (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); + expect(joinedCount).toEqual(501); + expect(roomIndexToRoomId).toEqual({ + 0: roomC, + 1: roomB, + 2: roomA, + }); + return true; + }); + responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => { + return state === SlidingSyncState.Complete; + }); + await httpBackend.flushAllExpected(); + await responseProcessed; + await listPromise; slidingSync.stop(); }); }); diff --git a/spec/olm-loader.js b/spec/olm-loader.ts similarity index 89% rename from spec/olm-loader.js rename to spec/olm-loader.ts index 505c08615..388220f0d 100644 --- a/spec/olm-loader.js +++ b/spec/olm-loader.ts @@ -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) { diff --git a/spec/test-utils/emitter.ts b/spec/test-utils/emitter.ts index 0e6971ada..b56ac2ebc 100644 --- a/spec/test-utils/emitter.ts +++ b/spec/test-utils/emitter.ts @@ -24,5 +24,5 @@ limitations under the License. * expect(beaconLivenessEmits.length).toBe(1); * ``` */ -export const filterEmitCallsByEventType = (eventType: string, spy: jest.SpyInstance) => +export const filterEmitCallsByEventType = (eventType: string, spy: jest.SpyInstance) => spy.mock.calls.filter((args) => args[0] === eventType); diff --git a/spec/test-utils/test-utils.ts b/spec/test-utils/test-utils.ts index abb328e0c..16d1cb565 100644 --- a/spec/test-utils/test-utils.ts +++ b/spec/test-utils/test-utils.ts @@ -147,9 +147,9 @@ export function mkEventCustom(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; } diff --git a/spec/unit/crypto.spec.ts b/spec/unit/crypto.spec.ts index 19217cdda..93a5461b3 100644 --- a/spec/unit/crypto.spec.ts +++ b/spec/unit/crypto.spec.ts @@ -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 diff --git a/spec/unit/crypto/algorithms/megolm.spec.ts b/spec/unit/crypto/algorithms/megolm.spec.ts index 9aa3c5c78..b9f16c742 100644 --- a/spec/unit/crypto/algorithms/megolm.spec.ts +++ b/spec/unit/crypto/algorithms/megolm.spec.ts @@ -32,8 +32,8 @@ import { ClientEvent, MatrixClient, RoomMember } from '../../../../src'; import { DeviceInfo, IDevice } from '../../../../src/crypto/deviceinfo'; import { DeviceTrustLevel } from '../../../../src/crypto/CrossSigning'; -const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; -const MegolmEncryption = algorithms.ENCRYPTION_CLASSES['m.megolm.v1.aes-sha2']; +const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2'); +const MegolmEncryption = algorithms.ENCRYPTION_CLASSES.get('m.megolm.v1.aes-sha2'); const ROOM_ID = '!ROOM:ID'; diff --git a/spec/unit/crypto/backup.spec.ts b/spec/unit/crypto/backup.spec.ts index 6759fe161..8e2647404 100644 --- a/spec/unit/crypto/backup.spec.ts +++ b/spec/unit/crypto/backup.spec.ts @@ -34,7 +34,7 @@ import { IAbortablePromise, MatrixScheduler } from '../../../src'; const Olm = global.Olm; -const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; +const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2'); const ROOM_ID = '!ROOM:ID'; diff --git a/spec/unit/crypto/outgoing-room-key-requests.spec.ts b/spec/unit/crypto/outgoing-room-key-requests.spec.ts index c572a63eb..1b1a4f57a 100644 --- a/spec/unit/crypto/outgoing-room-key-requests.spec.ts +++ b/spec/unit/crypto/outgoing-room-key-requests.spec.ts @@ -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 = diff --git a/spec/unit/event-timeline-set.spec.ts b/spec/unit/event-timeline-set.spec.ts index 42f4bca4d..e6c45fbd4 100644 --- a/spec/unit/event-timeline-set.spec.ts +++ b/spec/unit/event-timeline-set.spec.ts @@ -16,14 +16,15 @@ limitations under the License. import * as utils from "../test-utils/test-utils"; import { + DuplicateStrategy, EventTimeline, EventTimelineSet, EventType, + Filter, MatrixClient, MatrixEvent, MatrixEventEvent, Room, - DuplicateStrategy, } from '../../src'; import { Thread } from "../../src/models/thread"; import { ReEmitter } from "../../src/ReEmitter"; @@ -291,4 +292,34 @@ describe('EventTimelineSet', () => { expect(eventTimelineSet.canContain(event)).toBeTruthy(); }); }); + + describe("handleRemoteEcho", () => { + it("should add to liveTimeline only if the event matches the filter", () => { + const filter = new Filter(client.getUserId()!, "test_filter"); + filter.setDefinition({ + room: { + timeline: { + types: [EventType.RoomMessage], + }, + }, + }); + const eventTimelineSet = new EventTimelineSet(room, { filter }, client); + + const roomMessageEvent = new MatrixEvent({ + type: EventType.RoomMessage, + content: { body: "test" }, + event_id: "!test1:server", + }); + eventTimelineSet.handleRemoteEcho(roomMessageEvent, "~!local-event-id:server", roomMessageEvent.getId()); + expect(eventTimelineSet.getLiveTimeline().getEvents()).toContain(roomMessageEvent); + + const roomFilteredEvent = new MatrixEvent({ + type: "other_event_type", + content: { body: "test" }, + event_id: "!test2:server", + }); + eventTimelineSet.handleRemoteEcho(roomFilteredEvent, "~!local-event-id:server", roomFilteredEvent.getId()); + expect(eventTimelineSet.getLiveTimeline().getEvents()).not.toContain(roomFilteredEvent); + }); + }); }); diff --git a/spec/unit/matrix-client.spec.ts b/spec/unit/matrix-client.spec.ts index 702c22c05..2b8faf506 100644 --- a/spec/unit/matrix-client.spec.ts +++ b/spec/unit/matrix-client.spec.ts @@ -36,9 +36,14 @@ import { ReceiptType } from "../../src/@types/read_receipts"; import * as testUtils from "../test-utils/test-utils"; import { makeBeaconInfoContent } from "../../src/content-helpers"; import { M_BEACON_INFO } from "../../src/@types/beacon"; -import { ContentHelpers, Room } from "../../src"; +import { ContentHelpers, EventTimeline, Room } from "../../src"; import { supportsMatrixCall } from "../../src/webrtc/call"; import { makeBeaconEvent } from "../test-utils/beacon"; +import { + IGNORE_INVITES_ACCOUNT_EVENT_KEY, + POLICIES_ACCOUNT_EVENT_TYPE, + PolicyScope, +} from "../../src/models/invites-ignorer"; jest.useFakeTimers(); @@ -427,7 +432,7 @@ describe("MatrixClient", function() { } }); }); - await client.startClient(); + await client.startClient({ filter }); await syncPromise; }); @@ -1412,4 +1417,301 @@ describe("MatrixClient", function() { expect(client.crypto.encryptAndSendToDevices).toHaveBeenLastCalledWith(deviceInfos, payload); }); }); + + describe("support for ignoring invites", () => { + beforeEach(() => { + // Mockup `getAccountData`/`setAccountData`. + const dataStore = new Map(); + client.setAccountData = function(eventType, content) { + dataStore.set(eventType, content); + return Promise.resolve(); + }; + client.getAccountData = function(eventType) { + const data = dataStore.get(eventType); + return new MatrixEvent({ + content: data, + }); + }; + + // Mockup `createRoom`/`getRoom`/`joinRoom`, including state. + const rooms = new Map(); + client.createRoom = function(options = {}) { + const roomId = options["_roomId"] || `!room-${rooms.size}:example.org`; + const state = new Map(); + const room = { + roomId, + _options: options, + _state: state, + getUnfilteredTimelineSet: function() { + return { + getLiveTimeline: function() { + return { + getState: function(direction) { + expect(direction).toBe(EventTimeline.FORWARDS); + return { + getStateEvents: function(type) { + const store = state.get(type) || {}; + return Object.keys(store).map(key => store[key]); + }, + }; + }, + }; + }, + }; + }, + }; + rooms.set(roomId, room); + return Promise.resolve({ room_id: roomId }); + }; + client.getRoom = function(roomId) { + return rooms.get(roomId); + }; + client.joinRoom = function(roomId) { + return this.getRoom(roomId) || this.createRoom({ _roomId: roomId }); + }; + + // Mockup state events + client.sendStateEvent = function(roomId, type, content) { + const room = this.getRoom(roomId); + const state: Map = room._state; + let store = state.get(type); + if (!store) { + store = {}; + state.set(type, store); + } + const eventId = `$event-${Math.random()}:example.org`; + store[eventId] = { + getId: function() { + return eventId; + }, + getRoomId: function() { + return roomId; + }, + getContent: function() { + return content; + }, + }; + return { event_id: eventId }; + }; + client.redactEvent = function(roomId, eventId) { + const room = this.getRoom(roomId); + const state: Map = room._state; + for (const store of state.values()) { + delete store[eventId]; + } + }; + }); + + it("should initialize and return the same `target` consistently", async () => { + const target1 = await client.ignoredInvites.getOrCreateTargetRoom(); + const target2 = await client.ignoredInvites.getOrCreateTargetRoom(); + expect(target1).toBeTruthy(); + expect(target1).toBe(target2); + }); + + it("should initialize and return the same `sources` consistently", async () => { + const sources1 = await client.ignoredInvites.getOrCreateSourceRooms(); + const sources2 = await client.ignoredInvites.getOrCreateSourceRooms(); + expect(sources1).toBeTruthy(); + expect(sources1).toHaveLength(1); + expect(sources1).toEqual(sources2); + }); + + it("should initially not reject any invite", async () => { + const rule = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:example.org", + roomId: "!snafu:somewhere.org", + }); + expect(rule).toBeFalsy(); + }); + + it("should reject invites once we have added a matching rule in the target room (scope: user)", async () => { + await client.ignoredInvites.addRule(PolicyScope.User, "*:example.org", "just a test"); + + // We should reject this invite. + const ruleMatch = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:example.org", + roomId: "!snafu:somewhere.org", + }); + expect(ruleMatch).toBeTruthy(); + expect(ruleMatch.getContent()).toMatchObject({ + recommendation: "m.ban", + reason: "just a test", + }); + + // We should let these invites go through. + const ruleWrongServer = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:somewhere.org", + roomId: "!snafu:somewhere.org", + }); + expect(ruleWrongServer).toBeFalsy(); + + const ruleWrongServerRoom = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:somewhere.org", + roomId: "!snafu:example.org", + }); + expect(ruleWrongServerRoom).toBeFalsy(); + }); + + it("should reject invites once we have added a matching rule in the target room (scope: server)", async () => { + const REASON = `Just a test ${Math.random()}`; + await client.ignoredInvites.addRule(PolicyScope.Server, "example.org", REASON); + + // We should reject these invites. + const ruleSenderMatch = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:example.org", + roomId: "!snafu:somewhere.org", + }); + expect(ruleSenderMatch).toBeTruthy(); + expect(ruleSenderMatch.getContent()).toMatchObject({ + recommendation: "m.ban", + reason: REASON, + }); + + const ruleRoomMatch = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:somewhere.org", + roomId: "!snafu:example.org", + }); + expect(ruleRoomMatch).toBeTruthy(); + expect(ruleRoomMatch.getContent()).toMatchObject({ + recommendation: "m.ban", + reason: REASON, + }); + + // We should let these invites go through. + const ruleWrongServer = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:somewhere.org", + roomId: "!snafu:somewhere.org", + }); + expect(ruleWrongServer).toBeFalsy(); + }); + + it("should reject invites once we have added a matching rule in the target room (scope: room)", async () => { + const REASON = `Just a test ${Math.random()}`; + const BAD_ROOM_ID = "!bad:example.org"; + const GOOD_ROOM_ID = "!good:example.org"; + await client.ignoredInvites.addRule(PolicyScope.Room, BAD_ROOM_ID, REASON); + + // We should reject this invite. + const ruleSenderMatch = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:example.org", + roomId: BAD_ROOM_ID, + }); + expect(ruleSenderMatch).toBeTruthy(); + expect(ruleSenderMatch.getContent()).toMatchObject({ + recommendation: "m.ban", + reason: REASON, + }); + + // We should let these invites go through. + const ruleWrongRoom = await client.ignoredInvites.getRuleForInvite({ + sender: BAD_ROOM_ID, + roomId: GOOD_ROOM_ID, + }); + expect(ruleWrongRoom).toBeFalsy(); + }); + + it("should reject invites once we have added a matching rule in a non-target source room", async () => { + const NEW_SOURCE_ROOM_ID = "!another-source:example.org"; + + // Make sure that everything is initialized. + await client.ignoredInvites.getOrCreateSourceRooms(); + await client.joinRoom(NEW_SOURCE_ROOM_ID); + await client.ignoredInvites.addSource(NEW_SOURCE_ROOM_ID); + + // Add a rule in the new source room. + await client.sendStateEvent(NEW_SOURCE_ROOM_ID, PolicyScope.User, { + entity: "*:example.org", + reason: "just a test", + recommendation: "m.ban", + }); + + // We should reject this invite. + const ruleMatch = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:example.org", + roomId: "!snafu:somewhere.org", + }); + expect(ruleMatch).toBeTruthy(); + expect(ruleMatch.getContent()).toMatchObject({ + recommendation: "m.ban", + reason: "just a test", + }); + + // We should let these invites go through. + const ruleWrongServer = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:somewhere.org", + roomId: "!snafu:somewhere.org", + }); + expect(ruleWrongServer).toBeFalsy(); + + const ruleWrongServerRoom = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:somewhere.org", + roomId: "!snafu:example.org", + }); + expect(ruleWrongServerRoom).toBeFalsy(); + }); + + it("should not reject invites anymore once we have removed a rule", async () => { + await client.ignoredInvites.addRule(PolicyScope.User, "*:example.org", "just a test"); + + // We should reject this invite. + const ruleMatch = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:example.org", + roomId: "!snafu:somewhere.org", + }); + expect(ruleMatch).toBeTruthy(); + expect(ruleMatch.getContent()).toMatchObject({ + recommendation: "m.ban", + reason: "just a test", + }); + + // After removing the invite, we shouldn't reject it anymore. + await client.ignoredInvites.removeRule(ruleMatch); + const ruleMatch2 = await client.ignoredInvites.getRuleForInvite({ + sender: "@foobar:example.org", + roomId: "!snafu:somewhere.org", + }); + expect(ruleMatch2).toBeFalsy(); + }); + + it("should add new rules in the target room, rather than any other source room", async () => { + const NEW_SOURCE_ROOM_ID = "!another-source:example.org"; + + // Make sure that everything is initialized. + await client.ignoredInvites.getOrCreateSourceRooms(); + await client.joinRoom(NEW_SOURCE_ROOM_ID); + const newSourceRoom = client.getRoom(NEW_SOURCE_ROOM_ID); + + // Fetch the list of sources and check that we do not have the new room yet. + const policies = await client.getAccountData(POLICIES_ACCOUNT_EVENT_TYPE.name).getContent(); + expect(policies).toBeTruthy(); + const ignoreInvites = policies[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name]; + expect(ignoreInvites).toBeTruthy(); + expect(ignoreInvites.sources).toBeTruthy(); + expect(ignoreInvites.sources).not.toContain(NEW_SOURCE_ROOM_ID); + + // Add a source. + const added = await client.ignoredInvites.addSource(NEW_SOURCE_ROOM_ID); + expect(added).toBe(true); + const added2 = await client.ignoredInvites.addSource(NEW_SOURCE_ROOM_ID); + expect(added2).toBe(false); + + // Fetch the list of sources and check that we have added the new room. + const policies2 = await client.getAccountData(POLICIES_ACCOUNT_EVENT_TYPE.name).getContent(); + expect(policies2).toBeTruthy(); + const ignoreInvites2 = policies2[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name]; + expect(ignoreInvites2).toBeTruthy(); + expect(ignoreInvites2.sources).toBeTruthy(); + expect(ignoreInvites2.sources).toContain(NEW_SOURCE_ROOM_ID); + + // Add a rule. + const eventId = await client.ignoredInvites.addRule(PolicyScope.User, "*:example.org", "just a test"); + + // Check where it shows up. + const targetRoomId = ignoreInvites2.target; + const targetRoom = client.getRoom(targetRoomId); + expect(targetRoom._state.get(PolicyScope.User)[eventId]).toBeTruthy(); + expect(newSourceRoom._state.get(PolicyScope.User)?.[eventId]).toBeFalsy(); + }); + }); }); diff --git a/spec/unit/room-member.spec.js b/spec/unit/room-member.spec.ts similarity index 50% rename from spec/unit/room-member.spec.js rename to spec/unit/room-member.spec.ts index 89e98692e..e435cca22 100644 --- a/spec/unit/room-member.spec.js +++ b/spec/unit/room-member.spec.ts @@ -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. diff --git a/spec/unit/room-state.spec.js b/spec/unit/room-state.spec.ts similarity index 79% rename from spec/unit/room-state.spec.js rename to spec/unit/room-state.spec.ts index b54121431..858bed80c 100644 --- a/spec/unit/room-state.spec.js +++ b/spec/unit/room-state.spec.ts @@ -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; 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]); }); diff --git a/spec/unit/room.spec.ts b/spec/unit/room.spec.ts index 695bc1227..800415d2b 100644 --- a/spec/unit/room.spec.ts +++ b/spec/unit/room.spec.ts @@ -288,11 +288,11 @@ describe("Room", function() { room.addLiveEvents(events); expect(room.currentState.setStateEvents).toHaveBeenCalledWith( [events[0]], - { timelineWasEmpty: undefined }, + { timelineWasEmpty: false }, ); expect(room.currentState.setStateEvents).toHaveBeenCalledWith( [events[1]], - { timelineWasEmpty: undefined }, + { timelineWasEmpty: false }, ); expect(events[0].forwardLooking).toBe(true); expect(events[1].forwardLooking).toBe(true); @@ -426,6 +426,17 @@ describe("Room", function() { // but without the event ID matching we will still have the local event in pending events expect(room.getEventForTxnId(txnId)).toBeUndefined(); }); + + it("should correctly handle remote echoes from other devices", () => { + const remoteEvent = utils.mkMessage({ + room: roomId, user: userA, event: true, + }); + remoteEvent.event.unsigned = { transaction_id: "TXN_ID" }; + + // add the remoteEvent + room.addLiveEvents([remoteEvent]); + expect(room.timeline.length).toEqual(1); + }); }); describe('addEphemeralEvents', () => { diff --git a/spec/unit/utils.spec.ts b/spec/unit/utils.spec.ts index 36ad9e164..be30890ed 100644 --- a/spec/unit/utils.spec.ts +++ b/spec/unit/utils.spec.ts @@ -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 }, diff --git a/src/client.ts b/src/client.ts index 6a566ef9b..a07234192 100644 --- a/src/client.ts +++ b/src/client.ts @@ -201,6 +201,7 @@ import { Thread, THREAD_RELATION_TYPE } from "./models/thread"; import { MBeaconInfoEventContent, M_BEACON_INFO } from "./@types/beacon"; import { ToDeviceMessageQueue } from "./ToDeviceMessageQueue"; import { ToDeviceBatch } from "./models/ToDeviceMessage"; +import { IgnoredInvites } from "./models/invites-ignorer"; export type Store = IStore; @@ -406,8 +407,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; @@ -974,6 +974,9 @@ export class MatrixClient extends TypedEventEmitter} */ -export const ENCRYPTION_CLASSES: Record EncryptionAlgorithm> = {}; +export const ENCRYPTION_CLASSES = new Map EncryptionAlgorithm>(); type DecryptionClassParams = Omit; @@ -44,7 +44,7 @@ type DecryptionClassParams = Omit; * * @type {Object.} */ -export const DECRYPTION_CLASSES: Record DecryptionAlgorithm> = {}; +export const DECRYPTION_CLASSES = new Map DecryptionAlgorithm>(); export interface IParams { userId: string; @@ -297,6 +297,6 @@ export function registerAlgorithm( encryptor: new (params: IParams) => EncryptionAlgorithm, decryptor: new (params: Omit) => DecryptionAlgorithm, ): void { - ENCRYPTION_CLASSES[algorithm] = encryptor; - DECRYPTION_CLASSES[algorithm] = decryptor; + ENCRYPTION_CLASSES.set(algorithm, encryptor); + DECRYPTION_CLASSES.set(algorithm, decryptor); } diff --git a/src/crypto/algorithms/megolm.ts b/src/crypto/algorithms/megolm.ts index a0a7383e3..fb99b83e1 100644 --- a/src/crypto/algorithms/megolm.ts +++ b/src/crypto/algorithms/megolm.ts @@ -1185,7 +1185,7 @@ class MegolmEncryption extends EncryptionAlgorithm { class MegolmDecryption extends DecryptionAlgorithm { // events which we couldn't decrypt due to unknown sessions / indexes: map from // senderKey|sessionId to Set of MatrixEvents - private pendingEvents: Record>> = {}; + private pendingEvents = new Map>>(); // this gets stubbed out by the unit tests. private olmlib = olmlib; @@ -1337,10 +1337,10 @@ class MegolmDecryption extends DecryptionAlgorithm { const content = event.getWireContent(); const senderKey = content.sender_key; const sessionId = content.session_id; - if (!this.pendingEvents[senderKey]) { - this.pendingEvents[senderKey] = new Map(); + if (!this.pendingEvents.has(senderKey)) { + this.pendingEvents.set(senderKey, new Map>()); } - const senderPendingEvents = this.pendingEvents[senderKey]; + const senderPendingEvents = this.pendingEvents.get(senderKey); if (!senderPendingEvents.has(sessionId)) { senderPendingEvents.set(sessionId, new Set()); } @@ -1358,7 +1358,7 @@ class MegolmDecryption extends DecryptionAlgorithm { const content = event.getWireContent(); const senderKey = content.sender_key; const sessionId = content.session_id; - const senderPendingEvents = this.pendingEvents[senderKey]; + const senderPendingEvents = this.pendingEvents.get(senderKey); const pendingEvents = senderPendingEvents?.get(sessionId); if (!pendingEvents) { return; @@ -1369,7 +1369,7 @@ class MegolmDecryption extends DecryptionAlgorithm { senderPendingEvents.delete(sessionId); } if (senderPendingEvents.size === 0) { - delete this.pendingEvents[senderKey]; + this.pendingEvents.delete(senderKey); } } @@ -1710,7 +1710,7 @@ class MegolmDecryption extends DecryptionAlgorithm { * @return {Boolean} whether all messages were successfully decrypted */ private async retryDecryption(senderKey: string, sessionId: string): Promise { - const senderPendingEvents = this.pendingEvents[senderKey]; + const senderPendingEvents = this.pendingEvents.get(senderKey); if (!senderPendingEvents) { return true; } @@ -1731,16 +1731,16 @@ class MegolmDecryption extends DecryptionAlgorithm { })); // If decrypted successfully, they'll have been removed from pendingEvents - return !this.pendingEvents[senderKey]?.has(sessionId); + return !this.pendingEvents.get(senderKey)?.has(sessionId); } public async retryDecryptionFromSender(senderKey: string): Promise { - const senderPendingEvents = this.pendingEvents[senderKey]; + const senderPendingEvents = this.pendingEvents.get(senderKey); if (!senderPendingEvents) { return true; } - delete this.pendingEvents[senderKey]; + this.pendingEvents.delete(senderKey); await Promise.all([...senderPendingEvents].map(async ([_sessionId, pending]) => { await Promise.all([...pending].map(async (ev) => { @@ -1752,7 +1752,7 @@ class MegolmDecryption extends DecryptionAlgorithm { })); })); - return !this.pendingEvents[senderKey]; + return !this.pendingEvents.has(senderKey); } public async sendSharedHistoryInboundSessions(devicesByUser: Record): Promise { diff --git a/src/crypto/index.ts b/src/crypto/index.ts index e6d473786..64652d388 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -301,9 +301,9 @@ export class Crypto extends TypedEventEmitter = {}; + private roomEncryptors = new Map(); // map from algorithm to DecryptionAlgorithm instance, for each room - private roomDecryptors: Record> = {}; + private roomDecryptors = new Map>(); private deviceKeys: Record = {}; // type: key @@ -445,7 +445,7 @@ export class Crypto extends TypedEventEmitter { const trackMembers = async () => { // not an encrypted room - if (!this.roomEncryptors[roomId]) { + if (!this.roomEncryptors.has(roomId)) { return; } const room = this.clientStore.getRoom(roomId); @@ -2808,7 +2808,7 @@ export class Crypto extends TypedEventEmitter { // check for rooms with encryption enabled - const alg = this.roomEncryptors[room.roomId]; + const alg = this.roomEncryptors.get(room.roomId); if (!alg) { return false; } @@ -3556,7 +3556,7 @@ export class Crypto extends TypedEventEmitter; + let decryptors: Map; let alg: DecryptionAlgorithm; roomId = roomId || null; if (roomId) { - decryptors = this.roomDecryptors[roomId]; + decryptors = this.roomDecryptors.get(roomId); if (!decryptors) { - this.roomDecryptors[roomId] = decryptors = {}; + decryptors = new Map(); + this.roomDecryptors.set(roomId, decryptors); } - alg = decryptors[algorithm]; + alg = decryptors.get(algorithm); if (alg) { return alg; } } - const AlgClass = algorithms.DECRYPTION_CLASSES[algorithm]; + const AlgClass = algorithms.DECRYPTION_CLASSES.get(algorithm); if (!AlgClass) { throw new algorithms.DecryptionError( 'UNKNOWN_ENCRYPTION_ALGORITHM', @@ -3800,7 +3801,7 @@ export class Crypto extends TypedEventEmitter + recipient.userId === userId && recipient.deviceId === deviceId, + )) { results.push(keyReq); } cursor.continue(); diff --git a/src/crypto/store/memory-crypto-store.ts b/src/crypto/store/memory-crypto-store.ts index 2e441908e..f62f52250 100644 --- a/src/crypto/store/memory-crypto-store.ts +++ b/src/crypto/store/memory-crypto-store.ts @@ -191,11 +191,13 @@ export class MemoryCryptoStore implements CryptoStore { deviceId: string, wantedStates: number[], ): Promise { - 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); } } diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index 6341e4820..a951a399a 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -86,7 +86,7 @@ export class EventTimelineSet extends TypedEventEmitter; + private _eventIdToTimeline = new Map(); private filter?: Filter; /** @@ -138,7 +138,7 @@ export class EventTimelineSet extends TypedEventEmitter(); this.filter = opts.filter; @@ -210,7 +210,7 @@ export class EventTimelineSet extends TypedEventEmitter(); } else { this.timelines.push(newTimeline); } @@ -288,7 +288,7 @@ export class EventTimelineSet extends TypedEventEmitter = {}; -function intern(str: string): string { - if (!interns[str]) { - interns[str] = str; - } - return interns[str]; -} - /* eslint-disable camelcase */ export interface IContent { [key: string]: any; @@ -326,17 +318,17 @@ export class MatrixEvent extends TypedEventEmitter { if (typeof event[prop] !== "string") return; - event[prop] = intern(event[prop]); + event[prop] = internaliseString(event[prop]); }); ["membership", "avatar_url", "displayname"].forEach((prop) => { if (typeof event.content?.[prop] !== "string") return; - event.content[prop] = intern(event.content[prop]); + event.content[prop] = internaliseString(event.content[prop]); }); ["rel_type"].forEach((prop) => { if (typeof event.content?.["m.relates_to"]?.[prop] !== "string") return; - event.content["m.relates_to"][prop] = intern(event.content["m.relates_to"][prop]); + event.content["m.relates_to"][prop] = internaliseString(event.content["m.relates_to"][prop]); }); this.txnId = event.txn_id || null; @@ -796,6 +788,8 @@ export class MatrixEvent extends TypedEventEmitter { + const target = await this.getOrCreateTargetRoom(); + const response = await this.client.sendStateEvent(target.roomId, scope, { + entity, + reason, + recommendation: PolicyRecommendation.Ban, + }); + return response.event_id; + } + + /** + * Remove a rule. + */ + public async removeRule(event: MatrixEvent) { + await this.client.redactEvent(event.getRoomId()!, event.getId()!); + } + + /** + * Add a new room to the list of sources. If the user isn't a member of the + * room, attempt to join it. + * + * @param roomId A valid room id. If this room is already in the list + * of sources, it will not be duplicated. + * @return `true` if the source was added, `false` if it was already present. + * @throws If `roomId` isn't the id of a room that the current user is already + * member of or can join. + * + * # Safety + * + * This method will rewrite the `Policies` object in the user's account data. + * This rewrite is inherently racy and could overwrite or be overwritten by + * other concurrent rewrites of the same object. + */ + public async addSource(roomId: string): Promise { + // We attempt to join the room *before* calling + // `await this.getOrCreateSourceRooms()` to decrease the duration + // of the racy section. + await this.client.joinRoom(roomId); + // Race starts. + const sources = (await this.getOrCreateSourceRooms()) + .map(room => room.roomId); + if (sources.includes(roomId)) { + return false; + } + sources.push(roomId); + await this.withIgnoreInvitesPolicies(ignoreInvitesPolicies => { + ignoreInvitesPolicies.sources = sources; + }); + + // Race ends. + return true; + } + + /** + * Find out whether an invite should be ignored. + * + * @param sender The user id for the user who issued the invite. + * @param roomId The room to which the user is invited. + * @returns A rule matching the entity, if any was found, `null` otherwise. + */ + public async getRuleForInvite({ sender, roomId }: { + sender: string; + roomId: string; + }): Promise> { + // In this implementation, we perform a very naive lookup: + // - search in each policy room; + // - turn each (potentially glob) rule entity into a regexp. + // + // Real-world testing will tell us whether this is performant enough. + // In the (unfortunately likely) case it isn't, there are several manners + // in which we could optimize this: + // - match several entities per go; + // - pre-compile each rule entity into a regexp; + // - pre-compile entire rooms into a single regexp. + const policyRooms = await this.getOrCreateSourceRooms(); + const senderServer = sender.split(":")[1]; + const roomServer = roomId.split(":")[1]; + for (const room of policyRooms) { + const state = room.getUnfilteredTimelineSet().getLiveTimeline().getState(EventTimeline.FORWARDS); + + for (const { scope, entities } of [ + { scope: PolicyScope.Room, entities: [roomId] }, + { scope: PolicyScope.User, entities: [sender] }, + { scope: PolicyScope.Server, entities: [senderServer, roomServer] }, + ]) { + const events = state.getStateEvents(scope); + for (const event of events) { + const content = event.getContent(); + if (content?.recommendation != PolicyRecommendation.Ban) { + // Ignoring invites only looks at `m.ban` recommendations. + continue; + } + const glob = content?.entity; + if (!glob) { + // Invalid event. + continue; + } + let regexp: RegExp; + try { + regexp = new RegExp(globToRegexp(glob, false)); + } catch (ex) { + // Assume invalid event. + continue; + } + for (const entity of entities) { + if (entity && regexp.test(entity)) { + return event; + } + } + // No match. + } + } + } + return null; + } + + /** + * Get the target room, i.e. the room in which any new rule should be written. + * + * If there is no target room setup, a target room is created. + * + * Note: This method is public for testing reasons. Most clients should not need + * to call it directly. + * + * # Safety + * + * This method will rewrite the `Policies` object in the user's account data. + * This rewrite is inherently racy and could overwrite or be overwritten by + * other concurrent rewrites of the same object. + */ + public async getOrCreateTargetRoom(): Promise { + const ignoreInvitesPolicies = this.getIgnoreInvitesPolicies(); + let target = ignoreInvitesPolicies.target; + // Validate `target`. If it is invalid, trash out the current `target` + // and create a new room. + if (typeof target !== "string") { + target = null; + } + if (target) { + // Check that the room exists and is valid. + const room = this.client.getRoom(target); + if (room) { + return room; + } else { + target = null; + } + } + // We need to create our own policy room for ignoring invites. + target = (await this.client.createRoom({ + name: "Individual Policy Room", + preset: Preset.PrivateChat, + })).room_id; + await this.withIgnoreInvitesPolicies(ignoreInvitesPolicies => { + ignoreInvitesPolicies.target = target; + }); + + // Since we have just called `createRoom`, `getRoom` should not be `null`. + return this.client.getRoom(target)!; + } + + /** + * Get the list of source rooms, i.e. the rooms from which rules need to be read. + * + * If no source rooms are setup, the target room is used as sole source room. + * + * Note: This method is public for testing reasons. Most clients should not need + * to call it directly. + * + * # Safety + * + * This method will rewrite the `Policies` object in the user's account data. + * This rewrite is inherently racy and could overwrite or be overwritten by + * other concurrent rewrites of the same object. + */ + public async getOrCreateSourceRooms(): Promise { + const ignoreInvitesPolicies = this.getIgnoreInvitesPolicies(); + let sources = ignoreInvitesPolicies.sources; + + // Validate `sources`. If it is invalid, trash out the current `sources` + // and create a new list of sources from `target`. + let hasChanges = false; + if (!Array.isArray(sources)) { + // `sources` could not be an array. + hasChanges = true; + sources = []; + } + let sourceRooms: Room[] = sources + // `sources` could contain non-string / invalid room ids + .filter(roomId => typeof roomId === "string") + .map(roomId => this.client.getRoom(roomId)) + .filter(room => !!room); + if (sourceRooms.length != sources.length) { + hasChanges = true; + } + if (sourceRooms.length == 0) { + // `sources` could be empty (possibly because we've removed + // invalid content) + const target = await this.getOrCreateTargetRoom(); + hasChanges = true; + sourceRooms = [target]; + } + if (hasChanges) { + // Reload `policies`/`ignoreInvitesPolicies` in case it has been changed + // during or by our call to `this.getTargetRoom()`. + await this.withIgnoreInvitesPolicies(ignoreInvitesPolicies => { + ignoreInvitesPolicies.sources = sources; + }); + } + return sourceRooms; + } + + /** + * Fetch the `IGNORE_INVITES_POLICIES` object from account data. + * + * If both an unstable prefix version and a stable prefix version are available, + * it will return the stable prefix version preferentially. + * + * The result is *not* validated but is guaranteed to be a non-null object. + * + * @returns A non-null object. + */ + private getIgnoreInvitesPolicies(): {[key: string]: any} { + return this.getPoliciesAndIgnoreInvitesPolicies().ignoreInvitesPolicies; + } + + /** + * Modify in place the `IGNORE_INVITES_POLICIES` object from account data. + */ + private async withIgnoreInvitesPolicies(cb: (ignoreInvitesPolicies: {[key: string]: any}) => void) { + const { policies, ignoreInvitesPolicies } = this.getPoliciesAndIgnoreInvitesPolicies(); + cb(ignoreInvitesPolicies); + policies[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name] = ignoreInvitesPolicies; + await this.client.setAccountData(POLICIES_ACCOUNT_EVENT_TYPE.name, policies); + } + + /** + * As `getIgnoreInvitesPolicies` but also return the `POLICIES_ACCOUNT_EVENT_TYPE` + * object. + */ + private getPoliciesAndIgnoreInvitesPolicies(): + {policies: {[key: string]: any}, ignoreInvitesPolicies: {[key: string]: any}} { + let policies = {}; + for (const key of [POLICIES_ACCOUNT_EVENT_TYPE.name, POLICIES_ACCOUNT_EVENT_TYPE.altName]) { + if (!key) { + continue; + } + const value = this.client.getAccountData(key)?.getContent(); + if (value) { + policies = value; + break; + } + } + + let ignoreInvitesPolicies = {}; + let hasIgnoreInvitesPolicies = false; + for (const key of [IGNORE_INVITES_ACCOUNT_EVENT_KEY.name, IGNORE_INVITES_ACCOUNT_EVENT_KEY.altName]) { + if (!key) { + continue; + } + const value = policies[key]; + if (value && typeof value == "object") { + ignoreInvitesPolicies = value; + hasIgnoreInvitesPolicies = true; + break; + } + } + if (!hasIgnoreInvitesPolicies) { + policies[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name] = ignoreInvitesPolicies; + } + + return { policies, ignoreInvitesPolicies }; + } +} diff --git a/src/models/relations-container.ts b/src/models/relations-container.ts index 224375efc..9b2f255ee 100644 --- a/src/models/relations-container.ts +++ b/src/models/relations-container.ts @@ -23,14 +23,8 @@ import { Room } from "./room"; export class RelationsContainer { // A tree of objects to access a set of related children for an event, as in: - // this.relations[parentEventId][relationType][relationEventType] - private relations: { - [parentEventId: string]: { - [relationType: RelationType | string]: { - [eventType: EventType | string]: Relations; - }; - }; - } = {}; + // this.relations.get(parentEventId).get(relationType).get(relationEventType) + private relations = new Map>>(); constructor(private readonly client: MatrixClient, private readonly room?: Room) { } @@ -57,14 +51,15 @@ export class RelationsContainer { relationType: RelationType | string, eventType: EventType | string, ): Relations | undefined { - return this.relations[eventId]?.[relationType]?.[eventType]; + return this.relations.get(eventId)?.get(relationType)?.get(eventType); } public getAllChildEventsForEvent(parentEventId: string): MatrixEvent[] { - const relationsForEvent = this.relations[parentEventId] ?? {}; + const relationsForEvent = this.relations.get(parentEventId) + ?? new Map>(); const events: MatrixEvent[] = []; - for (const relationsRecord of Object.values(relationsForEvent)) { - for (const relations of Object.values(relationsRecord)) { + for (const relationsRecord of relationsForEvent.values()) { + for (const relations of relationsRecord.values()) { events.push(...relations.getRelations()); } } @@ -79,11 +74,11 @@ export class RelationsContainer { * @param {MatrixEvent} event The event to check as relation target. */ public aggregateParentEvent(event: MatrixEvent): void { - const relationsForEvent = this.relations[event.getId()]; + const relationsForEvent = this.relations.get(event.getId()); if (!relationsForEvent) return; - for (const relationsWithRelType of Object.values(relationsForEvent)) { - for (const relationsWithEventType of Object.values(relationsWithRelType)) { + for (const relationsWithRelType of relationsForEvent.values()) { + for (const relationsWithEventType of relationsWithRelType.values()) { relationsWithEventType.setTargetEvent(event); } } @@ -123,23 +118,26 @@ export class RelationsContainer { const { event_id: relatesToEventId, rel_type: relationType } = relation; const eventType = event.getType(); - let relationsForEvent = this.relations[relatesToEventId]; + let relationsForEvent = this.relations.get(relatesToEventId); if (!relationsForEvent) { - relationsForEvent = this.relations[relatesToEventId] = {}; + relationsForEvent = new Map>(); + this.relations.set(relatesToEventId, relationsForEvent); } - let relationsWithRelType = relationsForEvent[relationType]; + let relationsWithRelType = relationsForEvent.get(relationType); if (!relationsWithRelType) { - relationsWithRelType = relationsForEvent[relationType] = {}; + relationsWithRelType = new Map(); + relationsForEvent.set(relationType, relationsWithRelType); } - let relationsWithEventType = relationsWithRelType[eventType]; + let relationsWithEventType = relationsWithRelType.get(eventType); if (!relationsWithEventType) { - relationsWithEventType = relationsWithRelType[eventType] = new Relations( + relationsWithEventType = new Relations( relationType, eventType, this.client, ); + relationsWithRelType.set(eventType, relationsWithEventType); const room = this.room ?? timelineSet?.room; const relatesToEvent = timelineSet?.findEventById(relatesToEventId) diff --git a/src/models/room-state.ts b/src/models/room-state.ts index c7d3ac325..b0104cf70 100644 --- a/src/models/room-state.ts +++ b/src/models/room-state.ts @@ -79,7 +79,7 @@ export class RoomState extends TypedEventEmitter public readonly reEmitter = new TypedReEmitter(this); private sentinels: Record = {}; // userId: RoomMember // stores fuzzy matches to a list of userIDs (applies utils.removeHiddenChars to keys) - private displayNameToUserIds: Record = {}; + private displayNameToUserIds = new Map(); private userIdsToDisplayNames: Record = {}; private tokenToInvite: Record = {}; // 3pid invite state_key to m.room.member invite private joinedMemberCount: number = null; // cache of the number of joined members @@ -709,7 +709,7 @@ export class RoomState extends TypedEventEmitter * @return {string[]} An array of user IDs or an empty array. */ public getUserIdsWithDisplayName(displayName: string): string[] { - return this.displayNameToUserIds[utils.removeHiddenChars(displayName)] || []; + return this.displayNameToUserIds.get(utils.removeHiddenChars(displayName)) ?? []; } /** @@ -941,11 +941,11 @@ export class RoomState extends TypedEventEmitter // the lot. const strippedOldName = utils.removeHiddenChars(oldName); - const existingUserIds = this.displayNameToUserIds[strippedOldName]; + const existingUserIds = this.displayNameToUserIds.get(strippedOldName); if (existingUserIds) { // remove this user ID from this array const filteredUserIDs = existingUserIds.filter((id) => id !== userId); - this.displayNameToUserIds[strippedOldName] = filteredUserIDs; + this.displayNameToUserIds.set(strippedOldName, filteredUserIDs); } } @@ -954,10 +954,9 @@ export class RoomState extends TypedEventEmitter const strippedDisplayname = displayName && utils.removeHiddenChars(displayName); // an empty stripped displayname (undefined/'') will be set to MXID in room-member.js if (strippedDisplayname) { - if (!this.displayNameToUserIds[strippedDisplayname]) { - this.displayNameToUserIds[strippedDisplayname] = []; - } - this.displayNameToUserIds[strippedDisplayname].push(userId); + const arr = this.displayNameToUserIds.get(strippedDisplayname) ?? []; + arr.push(userId); + this.displayNameToUserIds.set(strippedDisplayname, arr); } } } diff --git a/src/models/room.ts b/src/models/room.ts index 46c623c5f..92af3fe65 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -1981,14 +1981,6 @@ export class Room extends TypedEventEmitter } } } - - if (event.getUnsigned().transaction_id) { - const existingEvent = this.txnToEvent[event.getUnsigned().transaction_id]; - if (existingEvent) { - // remote echo of an event we sent earlier - this.handleRemoteEcho(event, existingEvent); - } - } } /** @@ -1996,7 +1988,7 @@ export class Room extends TypedEventEmitter * "Room.timeline". * * @param {MatrixEvent} event Event to be added - * @param {IAddLiveEventOptions} options addLiveEvent options + * @param {IAddLiveEventOptions} addLiveEventOptions addLiveEvent options * @fires module:client~MatrixClient#event:"Room.timeline" * @private */ @@ -2344,7 +2336,7 @@ export class Room extends TypedEventEmitter fromCache = false, ): void { let duplicateStrategy = duplicateStrategyOrOpts as DuplicateStrategy; - let timelineWasEmpty: boolean; + let timelineWasEmpty = false; if (typeof (duplicateStrategyOrOpts) === 'object') { ({ duplicateStrategy, @@ -2383,10 +2375,25 @@ export class Room extends TypedEventEmitter const threadRoots = this.findThreadRoots(events); const eventsByThread: { [threadId: string]: MatrixEvent[] } = {}; + const options: IAddLiveEventOptions = { + duplicateStrategy, + fromCache, + timelineWasEmpty, + }; + for (const event of events) { // TODO: We should have a filter to say "only add state event types X Y Z to the timeline". this.processLiveEvent(event); + if (event.getUnsigned().transaction_id) { + const existingEvent = this.txnToEvent[event.getUnsigned().transaction_id!]; + if (existingEvent) { + // remote echo of an event we sent earlier + this.handleRemoteEcho(event, existingEvent); + continue; // we can skip adding the event to the timeline sets, it is already there + } + } + const { shouldLiveInRoom, shouldLiveInThread, @@ -2399,11 +2406,7 @@ export class Room extends TypedEventEmitter eventsByThread[threadId]?.push(event); if (shouldLiveInRoom) { - this.addLiveEvent(event, { - duplicateStrategy, - fromCache, - timelineWasEmpty, - }); + this.addLiveEvent(event, options); } } diff --git a/src/sliding-sync-sdk.ts b/src/sliding-sync-sdk.ts index 46f6c5f6c..8ac1b81b9 100644 --- a/src/sliding-sync-sdk.ts +++ b/src/sliding-sync-sdk.ts @@ -296,13 +296,16 @@ export class SlidingSyncSdk { this.processRoomData(this.client, room, roomData); } - private onLifecycle(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse, err?: Error): void { + private onLifecycle(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err: Error | null): void { if (err) { logger.debug("onLifecycle", state, err); } switch (state) { case SlidingSyncState.Complete: this.purgeNotifications(); + if (!resp) { + break; + } // Element won't stop showing the initial loading spinner unless we fire SyncState.Prepared if (!this.lastPos) { this.updateSyncState(SyncState.Prepared, { @@ -529,6 +532,13 @@ export class SlidingSyncSdk { } } + if (Number.isInteger(roomData.invited_count)) { + room.currentState.setInvitedMemberCount(roomData.invited_count!); + } + if (Number.isInteger(roomData.joined_count)) { + room.currentState.setJoinedMemberCount(roomData.joined_count!); + } + if (roomData.invite_state) { const inviteStateEvents = mapEvents(this.client, room.roomId, roomData.invite_state); this.injectRoomEvents(room, inviteStateEvents); @@ -609,6 +619,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); @@ -632,8 +646,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 diff --git a/src/sliding-sync.ts b/src/sliding-sync.ts index 28026b3a9..da6419c96 100644 --- a/src/sliding-sync.ts +++ b/src/sliding-sync.ts @@ -47,6 +47,8 @@ export interface MSC3575Filter { room_types?: string[]; not_room_types?: string[]; spaces?: string[]; + tags?: string[]; + not_tags?: string[]; } /** @@ -82,6 +84,8 @@ export interface MSC3575RoomData { timeline: (IRoomEvent | IStateEvent)[]; notification_count?: number; highlight_count?: number; + joined_count?: number; + invited_count?: number; invite_state?: IStateEvent[]; initial?: boolean; limited?: boolean; @@ -318,7 +322,9 @@ export enum SlidingSyncEvent { export type SlidingSyncEventHandlerMap = { [SlidingSyncEvent.RoomData]: (roomId: string, roomData: MSC3575RoomData) => void; - [SlidingSyncEvent.Lifecycle]: (state: SlidingSyncState, resp: MSC3575SlidingSyncResponse, err: Error) => void; + [SlidingSyncEvent.Lifecycle]: ( + state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err: Error | null, + ) => void; [SlidingSyncEvent.List]: ( listIndex: number, joinedCount: number, roomIndexToRoomId: Record, ) => void; @@ -530,6 +536,65 @@ export class SlidingSync extends TypedEventEmitter low; i--) { + if (this.lists[listIndex].isIndexInRange(i)) { + this.lists[listIndex].roomIndexToRoomId[i] = + this.lists[listIndex].roomIndexToRoomId[ + i - 1 + ]; + } + } + } + + private shiftLeft(listIndex: number, hi: number, low: number) { + // l h + // 0,1,2,3,4 <- before + // 0,1,3,4,4 <- after, low is deleted and hi is duplicated + for (let i = low; i < hi; i++) { + if (this.lists[listIndex].isIndexInRange(i)) { + this.lists[listIndex].roomIndexToRoomId[i] = + this.lists[listIndex].roomIndexToRoomId[ + i + 1 + ]; + } + } + } + + private removeEntry(listIndex: number, index: number) { + // work out the max index + let max = -1; + for (const n in this.lists[listIndex].roomIndexToRoomId) { + if (Number(n) > max) { + max = Number(n); + } + } + if (max < 0 || index > max) { + return; + } + // Everything higher than the gap needs to be shifted left. + this.shiftLeft(listIndex, max, index); + delete this.lists[listIndex].roomIndexToRoomId[max]; + } + + private addEntry(listIndex: number, index: number) { + // work out the max index + let max = -1; + for (const n in this.lists[listIndex].roomIndexToRoomId) { + if (Number(n) > max) { + max = Number(n); + } + } + if (max < 0 || index > max) { + return; + } + // Everything higher than the gap needs to be shifted right, +1 so we don't delete the highest element + this.shiftRight(listIndex, max+1, index); + } + private processListOps(list: ListResponse, listIndex: number): void { let gapIndex = -1; list.ops.forEach((op: Operation) => { @@ -537,6 +602,10 @@ export class SlidingSync extends TypedEventEmitter op.index) { + // we haven't been told where to shift from, so make way for a new room entry. + this.addEntry(listIndex, op.index); + } else if (gapIndex > op.index) { // the gap is further down the list, shift every element to the right // starting at the gap so we can just shift each element in turn: // [A,B,C,_] gapIndex=3, op.index=0 @@ -572,26 +630,13 @@ export class SlidingSync extends TypedEventEmitter op.index; i--) { - if (this.lists[listIndex].isIndexInRange(i)) { - this.lists[listIndex].roomIndexToRoomId[i] = - this.lists[listIndex].roomIndexToRoomId[ - i - 1 - ]; - } - } + this.shiftRight(listIndex, gapIndex, op.index); } else if (gapIndex < op.index) { // the gap is further up the list, shift every element to the left // starting at the gap so we can just shift each element in turn - for (let i = gapIndex; i < op.index; i++) { - if (this.lists[listIndex].isIndexInRange(i)) { - this.lists[listIndex].roomIndexToRoomId[i] = - this.lists[listIndex].roomIndexToRoomId[ - i + 1 - ]; - } - } + this.shiftLeft(listIndex, op.index, gapIndex); } + gapIndex = -1; // forget the gap, we don't need it anymore. } this.lists[listIndex].roomIndexToRoomId[op.index] = op.room_id; break; @@ -631,6 +676,11 @@ export class SlidingSync extends TypedEventEmitter = T & { * updating presence. */ export class SyncApi { - private _peekRoom: Room = null; - private currentSyncRequest: IAbortablePromise = null; - private syncState: SyncState = null; - private syncStateData: ISyncStateData = null; // additional data (eg. error object for failed sync) + private _peekRoom: Optional = null; + private currentSyncRequest: Optional> = null; + private syncState: Optional = null; + private syncStateData: Optional = null; // additional data (eg. error object for failed sync) private catchingUp = false; private running = false; - private keepAliveTimer: ReturnType = null; - private connectionReturnedDefer: IDeferred = null; + private keepAliveTimer: Optional> = null; + private connectionReturnedDefer: Optional> = 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; + /** * Main entry point */ - public sync(): void { - const client = this.client; - + public async sync(): Promise { 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 { - 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 { @@ -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 { 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; diff --git a/src/utils.ts b/src/utils.ts index 9cad330d1..cda91c126 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -29,6 +29,30 @@ import { MatrixClient } from "."; import { M_TIMESTAMP } from "./@types/location"; import { ReceiptType } from "./@types/read_receipts"; +const interns = new Map(); + +/** + * Internalises a string, reusing a known pointer or storing the pointer + * if needed for future strings. + * @param str The string to internalise. + * @returns The internalised string. + */ +export function internaliseString(str: string): string { + // Unwrap strings before entering the map, if we somehow got a wrapped + // string as our input. This should only happen from tests. + if ((str as unknown) instanceof String) { + str = str.toString(); + } + + // Check the map to see if we can store the value + if (!interns.has(str)) { + interns.set(str, str); + } + + // Return any cached string reference + return interns.get(str); +} + /** * Encode a dictionary of query parameters. * Omits any undefined/null values. @@ -75,8 +99,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 { +export function encodeUri(pathTemplate: string, variables: Record): string { for (const key in variables) { if (!variables.hasOwnProperty(key)) { continue; @@ -216,33 +239,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 @@ -328,7 +342,7 @@ export function escapeRegExp(string: string): string { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } -export function globToRegexp(glob: string, extended?: any): string { +export function globToRegexp(glob: string, extended = false): string { // From // https://github.com/matrix-org/synapse/blob/abbee6b29be80a77e05730707602f3bbfc3f38cb/synapse/push/__init__.py#L132 // Because micromatch is about 130KB with dependencies, @@ -336,7 +350,7 @@ export function globToRegexp(glob: string, extended?: any): string { const replacements: ([RegExp, string | ((substring: string, ...args: any[]) => string) ])[] = [ [/\\\*/g, '.*'], [/\?/g, '.'], - extended !== false && [ + !extended && [ /\\\[(!|)(.*)\\]/g, (_match: string, neg: string, pat: string) => [ '[', diff --git a/tsconfig.json b/tsconfig.json index caf28e263..69c3f0196 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,6 @@ }, "include": [ "./src/**/*.ts", - "./spec/**/*.ts", + "./spec/**/*.ts" ] } diff --git a/yarn.lock b/yarn.lock index 77527cdf2..729e2eb10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -58,26 +58,26 @@ dependencies: "@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== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86" + integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== "@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.19.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3" + integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.10" - "@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/generator" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helpers" "^7.19.0" + "@babel/parser" "^7.19.0" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.18.10" - "@babel/types" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -100,12 +100,21 @@ 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.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" + +"@babel/generator@^7.18.13", "@babel/generator@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a" + integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== + dependencies: + "@babel/types" "^7.19.0" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -124,20 +133,33 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf" - integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0" + integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== dependencies: - "@babel/compat-data" "^7.18.8" + "@babel/compat-data" "^7.19.0" "@babel/helper-validator-option" "^7.18.6" browserslist "^4.20.2" 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== +"@babel/helper-create-class-features-plugin@^7.18.6": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" + integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + +"@babel/helper-create-class-features-plugin@^7.18.9": + 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" @@ -147,10 +169,10 @@ "@babel/helper-replace-supers" "^7.18.9" "@babel/helper-split-export-declaration" "^7.18.6" -"@babel/helper-create-regexp-features-plugin@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz#3e35f4e04acbbf25f1b3534a657610a000543d3c" - integrity sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A== +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" + integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.1.0" @@ -179,13 +201,13 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz#940e6084a55dee867d33b4e487da2676365e86b0" - integrity sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A== +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== dependencies: - "@babel/template" "^7.18.6" - "@babel/types" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" @@ -208,19 +230,19 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz#5a1079c005135ed627442df31a42887e80fcb712" - integrity sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g== +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" + integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" "@babel/helper-simple-access" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.18.6" - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -229,10 +251,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f" - integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== "@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" @@ -292,23 +314,23 @@ integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== "@babel/helper-wrap-function@^7.18.9": - version "7.18.11" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz#bff23ace436e3f6aefb61f85ffae2291c80ed1fb" - integrity sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz#89f18335cff1152373222f76a4b37799636ae8b1" + integrity sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg== dependencies: - "@babel/helper-function-name" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.18.11" - "@babel/types" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" -"@babel/helpers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.9.tgz#4bef3b893f253a1eced04516824ede94dcfe7ff9" - integrity sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ== +"@babel/helpers@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" + integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== dependencies: - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" "@babel/highlight@^7.18.6": version "7.18.6" @@ -319,10 +341,15 @@ 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.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/parser@^7.18.10", "@babel/parser@^7.18.13", "@babel/parser@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" + integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -340,13 +367,13 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-proposal-optional-chaining" "^7.18.9" -"@babel/plugin-proposal-async-generator-functions@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz#85ea478c98b0095c3e4102bff3b67d306ed24952" - integrity sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew== +"@babel/plugin-proposal-async-generator-functions@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.0.tgz#cf5740194f170467df20581712400487efc79ff1" + integrity sha512-nhEByMUTx3uZueJ/QkJuSlCfN4FGg+xy+vRsfGQGzSauq5ks2Deid2+05Q3KhfaUjvec1IGhw/Zm3cFm8JigTQ== dependencies: "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -532,6 +559,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -625,16 +659,17 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-classes@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz#90818efc5b9746879b869d5ce83eb2aa48bbc3da" - integrity sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g== +"@babel/plugin-transform-classes@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" + integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.19.0" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-replace-supers" "^7.18.9" "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" @@ -646,10 +681,10 @@ dependencies: "@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== +"@babel/plugin-transform-destructuring@^7.18.13": + 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" @@ -725,14 +760,14 @@ "@babel/helper-simple-access" "^7.18.6" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz#545df284a7ac6a05125e3e405e536c5853099a06" - integrity sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A== +"@babel/plugin-transform-modules-systemjs@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz#5f20b471284430f02d9c5059d9b9a16d4b085a1f" + integrity sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A== dependencies: "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-validator-identifier" "^7.18.6" babel-plugin-dynamic-import-node "^2.3.3" @@ -744,13 +779,13 @@ "@babel/helper-module-transforms" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz#c89bfbc7cc6805d692f3a49bc5fc1b630007246d" - integrity sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg== +"@babel/plugin-transform-named-capturing-groups-regex@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.0.tgz#58c52422e4f91a381727faed7d513c89d7f41ada" + integrity sha512-HDSuqOQzkU//kfGdiHBt71/hkDTApw4U/cMVgKgX7PqfB3LOaK+2GtCEsBu1dL9CkswDm0Gwehht1dCr421ULQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-regexp-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-new-target@^7.18.6": version "7.18.6" @@ -815,12 +850,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-spread@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz#6ea7a6297740f381c540ac56caf75b05b74fb664" - integrity sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA== +"@babel/plugin-transform-spread@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" + integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-transform-sticky-regex@^7.18.6": @@ -869,17 +904,17 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/preset-env@^7.12.11": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.10.tgz#83b8dfe70d7eea1aae5a10635ab0a5fe60dfc0f4" - integrity sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.0.tgz#fd18caf499a67d6411b9ded68dc70d01ed1e5da7" + integrity sha512-1YUju1TAFuzjIQqNM9WsF4U6VbD/8t3wEAlw3LFYuuEr+ywqLRcSXxFKz4DCEj+sN94l/XTDiUXYRrsvMpz9WQ== dependencies: - "@babel/compat-data" "^7.18.8" - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/compat-data" "^7.19.0" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-validator-option" "^7.18.6" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.18.10" + "@babel/plugin-proposal-async-generator-functions" "^7.19.0" "@babel/plugin-proposal-class-properties" "^7.18.6" "@babel/plugin-proposal-class-static-block" "^7.18.6" "@babel/plugin-proposal-dynamic-import" "^7.18.6" @@ -913,9 +948,9 @@ "@babel/plugin-transform-async-to-generator" "^7.18.6" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" "@babel/plugin-transform-block-scoping" "^7.18.9" - "@babel/plugin-transform-classes" "^7.18.9" + "@babel/plugin-transform-classes" "^7.19.0" "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.18.13" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" @@ -925,9 +960,9 @@ "@babel/plugin-transform-member-expression-literals" "^7.18.6" "@babel/plugin-transform-modules-amd" "^7.18.6" "@babel/plugin-transform-modules-commonjs" "^7.18.6" - "@babel/plugin-transform-modules-systemjs" "^7.18.9" + "@babel/plugin-transform-modules-systemjs" "^7.19.0" "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.0" "@babel/plugin-transform-new-target" "^7.18.6" "@babel/plugin-transform-object-super" "^7.18.6" "@babel/plugin-transform-parameters" "^7.18.8" @@ -935,14 +970,14 @@ "@babel/plugin-transform-regenerator" "^7.18.6" "@babel/plugin-transform-reserved-words" "^7.18.6" "@babel/plugin-transform-shorthand-properties" "^7.18.6" - "@babel/plugin-transform-spread" "^7.18.9" + "@babel/plugin-transform-spread" "^7.19.0" "@babel/plugin-transform-sticky-regex" "^7.18.6" "@babel/plugin-transform-template-literals" "^7.18.9" "@babel/plugin-transform-typeof-symbol" "^7.18.9" "@babel/plugin-transform-unicode-escapes" "^7.18.10" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.18.10" + "@babel/types" "^7.19.0" babel-plugin-polyfill-corejs2 "^0.3.2" babel-plugin-polyfill-corejs3 "^0.5.3" babel-plugin-polyfill-regenerator "^0.4.0" @@ -981,13 +1016,13 @@ source-map-support "^0.5.16" "@babel/runtime@^7.12.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" - integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" + integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.18.10", "@babel/template@^7.18.6", "@babel/template@^7.3.3": +"@babel/template@^7.18.10", "@babel/template@^7.3.3": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== @@ -996,26 +1031,51 @@ "@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.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/traverse@^7.18.9", "@babel/traverse@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed" + integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.19.0" + "@babel/types" "^7.19.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + 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" + to-fast-properties "^2.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.19.0", "@babel/types@^7.4.4": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" + integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== dependencies: "@babel/helper-string-parser" "^7.18.10" "@babel/helper-validator-identifier" "^7.18.6" @@ -1026,14 +1086,14 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" 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== +"@eslint/eslintrc@^1.3.1": + 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" @@ -1041,15 +1101,25 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.9.2": - version "0.9.5" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" - integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== +"@humanwhocodes/config-array@^0.10.4": + version "0.10.4" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.4.tgz#01e7366e57d2ad104feea63e72248f22015c520c" + integrity sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" minimatch "^3.0.4" +"@humanwhocodes/gitignore-to-minimatch@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz#316b0a63b91c10e53f242efb4ace5c3b34e8728d" + integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA== + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" @@ -1071,62 +1141,61 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.3.tgz#2030606ec03a18c31803b8a36382762e447655df" - integrity sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw== +"@jest/console@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.0.2.tgz#3a02dccad4dd37c25fd30013df67ec50998402ce" + integrity sha512-Fv02ijyhF4D/Wb3DvZO3iBJQz5DnzpJEIDBDbvje8Em099N889tNMUnBw7SalmSuOI+NflNG40RA1iK71kImPw== dependencies: - "@jest/types" "^28.1.3" + "@jest/types" "^29.0.2" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^28.1.3" - jest-util "^28.1.3" + jest-message-util "^29.0.2" + jest-util "^29.0.2" slash "^3.0.0" -"@jest/core@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.3.tgz#0ebf2bd39840f1233cd5f2d1e6fc8b71bd5a1ac7" - integrity sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA== +"@jest/core@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.0.2.tgz#7bf47ff6cd882678c47fbdea562bdf1ff03b6d33" + integrity sha512-imP5M6cdpHEOkmcuFYZuM5cTG1DAF7ZlVNCq1+F7kbqme2Jcl+Kh4M78hihM76DJHNkurbv4UVOnejGxBKEmww== dependencies: - "@jest/console" "^28.1.3" - "@jest/reporters" "^28.1.3" - "@jest/test-result" "^28.1.3" - "@jest/transform" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/console" "^29.0.2" + "@jest/reporters" "^29.0.2" + "@jest/test-result" "^29.0.2" + "@jest/transform" "^29.0.2" + "@jest/types" "^29.0.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^28.1.3" - jest-config "^28.1.3" - jest-haste-map "^28.1.3" - jest-message-util "^28.1.3" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.3" - jest-resolve-dependencies "^28.1.3" - jest-runner "^28.1.3" - jest-runtime "^28.1.3" - jest-snapshot "^28.1.3" - jest-util "^28.1.3" - jest-validate "^28.1.3" - jest-watcher "^28.1.3" + jest-changed-files "^29.0.0" + jest-config "^29.0.2" + jest-haste-map "^29.0.2" + jest-message-util "^29.0.2" + jest-regex-util "^29.0.0" + jest-resolve "^29.0.2" + jest-resolve-dependencies "^29.0.2" + jest-runner "^29.0.2" + jest-runtime "^29.0.2" + jest-snapshot "^29.0.2" + jest-util "^29.0.2" + jest-validate "^29.0.2" + jest-watcher "^29.0.2" micromatch "^4.0.4" - pretty-format "^28.1.3" - rimraf "^3.0.0" + pretty-format "^29.0.2" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.3.tgz#abed43a6b040a4c24fdcb69eab1f97589b2d663e" - integrity sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA== +"@jest/environment@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.0.2.tgz#9e4b6d4c9bce5bfced6f63945d8c8e571394f572" + integrity sha512-Yf+EYaLOrVCgts/aTS5nGznU4prZUPa5k9S63Yct8YSOKj2jkdS17hHSUKhk5jxDFMyCy1PXknypDw7vfgc/mA== dependencies: - "@jest/fake-timers" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/fake-timers" "^29.0.2" + "@jest/types" "^29.0.2" "@types/node" "*" - jest-mock "^28.1.3" + jest-mock "^29.0.2" "@jest/expect-utils@^28.1.3": version "28.1.3" @@ -1135,46 +1204,54 @@ dependencies: jest-get-type "^28.0.2" -"@jest/expect@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.3.tgz#9ac57e1d4491baca550f6bdbd232487177ad6a72" - integrity sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw== +"@jest/expect-utils@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.0.2.tgz#00dfcb9e6fe99160c326ba39f7734b984543dea8" + integrity sha512-+wcQF9khXKvAEi8VwROnCWWmHfsJYCZAs5dmuMlJBKk57S6ZN2/FQMIlo01F29fJyT8kV/xblE7g3vkIdTLOjw== dependencies: - expect "^28.1.3" - jest-snapshot "^28.1.3" + jest-get-type "^29.0.0" -"@jest/fake-timers@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.3.tgz#230255b3ad0a3d4978f1d06f70685baea91c640e" - integrity sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw== +"@jest/expect@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.0.2.tgz#641d151e1062ceb976c5ad1c23eba3bb1e188896" + integrity sha512-y/3geZ92p2/zovBm/F+ZjXUJ3thvT9IRzD6igqaWskFE2aR0idD+N/p5Lj/ZautEox/9RwEc6nqergebeh72uQ== dependencies: - "@jest/types" "^28.1.3" + expect "^29.0.2" + jest-snapshot "^29.0.2" + +"@jest/fake-timers@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.0.2.tgz#6f15f4d8eb1089d445e3f73473ddc434faa2f798" + integrity sha512-2JhQeWU28fvmM5r33lxg6BxxkTKaVXs6KMaJ6eXSM8ml/MaWkt2BvbIO8G9KWAJFMdBXWbn+2h9OK1/s5urKZA== + dependencies: + "@jest/types" "^29.0.2" "@sinonjs/fake-timers" "^9.1.2" "@types/node" "*" - jest-message-util "^28.1.3" - jest-mock "^28.1.3" - jest-util "^28.1.3" + jest-message-util "^29.0.2" + jest-mock "^29.0.2" + jest-util "^29.0.2" -"@jest/globals@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.3.tgz#a601d78ddc5fdef542728309894895b4a42dc333" - integrity sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA== +"@jest/globals@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.0.2.tgz#605d3389ad0c6bfe17ad3e1359b5bc39aefd8b65" + integrity sha512-4hcooSNJCVXuTu07/VJwCWW6HTnjLtQdqlcGisK6JST7z2ixa8emw4SkYsOk7j36WRc2ZUEydlUePnOIOTCNXg== dependencies: - "@jest/environment" "^28.1.3" - "@jest/expect" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/environment" "^29.0.2" + "@jest/expect" "^29.0.2" + "@jest/types" "^29.0.2" + jest-mock "^29.0.2" -"@jest/reporters@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.3.tgz#9adf6d265edafc5fc4a434cfb31e2df5a67a369a" - integrity sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg== +"@jest/reporters@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.0.2.tgz#5f927646b6f01029525c05ac108324eac7d7ad5c" + integrity sha512-Kr41qejRQHHkCgWHC9YwSe7D5xivqP4XML+PvgwsnRFaykKdNflDUb4+xLXySOU+O/bPkVdFpGzUpVNSJChCrw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^28.1.3" - "@jest/test-result" "^28.1.3" - "@jest/transform" "^28.1.3" - "@jest/types" "^28.1.3" - "@jridgewell/trace-mapping" "^0.3.13" + "@jest/console" "^29.0.2" + "@jest/test-result" "^29.0.2" + "@jest/transform" "^29.0.2" + "@jest/types" "^29.0.2" + "@jridgewell/trace-mapping" "^0.3.15" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" @@ -1186,9 +1263,9 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^28.1.3" - jest-util "^28.1.3" - jest-worker "^28.1.3" + jest-message-util "^29.0.2" + jest-util "^29.0.2" + jest-worker "^29.0.2" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" @@ -1202,51 +1279,58 @@ dependencies: "@sinclair/typebox" "^0.24.1" -"@jest/source-map@^28.1.2": - version "28.1.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.1.2.tgz#7fe832b172b497d6663cdff6c13b0a920e139e24" - integrity sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww== +"@jest/schemas@^29.0.0": + version "29.0.0" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a" + integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA== dependencies: - "@jridgewell/trace-mapping" "^0.3.13" + "@sinclair/typebox" "^0.24.1" + +"@jest/source-map@^29.0.0": + version "29.0.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.0.0.tgz#f8d1518298089f8ae624e442bbb6eb870ee7783c" + integrity sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ== + dependencies: + "@jridgewell/trace-mapping" "^0.3.15" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.3.tgz#5eae945fd9f4b8fcfce74d239e6f725b6bf076c5" - integrity sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg== +"@jest/test-result@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.0.2.tgz#dde4922e6234dd311c85ddf1ec2b7f600a90295d" + integrity sha512-b5rDc0lLL6Kx73LyCx6370k9uZ8o5UKdCpMS6Za3ke7H9y8PtAU305y6TeghpBmf2In8p/qqi3GpftgzijSsNw== dependencies: - "@jest/console" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/console" "^29.0.2" + "@jest/types" "^29.0.2" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz#9d0c283d906ac599c74bde464bc0d7e6a82886c3" - integrity sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw== +"@jest/test-sequencer@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.0.2.tgz#ae9b2d2c1694c7aa1a407713100e14dbfa79293e" + integrity sha512-fsyZqHBlXNMv5ZqjQwCuYa2pskXCO0DVxh5aaVCuAtwzHuYEGrhordyEncBLQNuCGQSYgElrEEmS+7wwFnnMKw== dependencies: - "@jest/test-result" "^28.1.3" + "@jest/test-result" "^29.0.2" graceful-fs "^4.2.9" - jest-haste-map "^28.1.3" + jest-haste-map "^29.0.2" slash "^3.0.0" -"@jest/transform@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.3.tgz#59d8098e50ab07950e0f2fc0fc7ec462371281b0" - integrity sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA== +"@jest/transform@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.0.2.tgz#eef90ebd939b68bf2c2508d9e914377871869146" + integrity sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^28.1.3" - "@jridgewell/trace-mapping" "^0.3.13" + "@jest/types" "^29.0.2" + "@jridgewell/trace-mapping" "^0.3.15" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" + fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^28.1.3" - jest-regex-util "^28.0.2" - jest-util "^28.1.3" + jest-haste-map "^29.0.2" + jest-regex-util "^29.0.0" + jest-util "^29.0.2" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" @@ -1264,6 +1348,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.0.2": + version "29.0.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.0.2.tgz#5a5391fa7f7f41bf4b201d6d2da30e874f95b6c1" + integrity sha512-5WNMesBLmlkt1+fVkoCjHa0X3i3q8zc4QLTDkdHgCa2gyPZc7rdlZBWgVLqwS1860ZW5xJuCDwAzqbGaXIr/ew== + dependencies: + "@jest/schemas" "^29.0.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -1304,7 +1400,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.8", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.8", "@jridgewell/trace-mapping@^0.3.9": version "0.3.15" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== @@ -1314,7 +1410,6 @@ "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz": version "3.2.12" - uid "0bce3c86f9d36a4984d3c3e07df1c3fb4c679bd9" resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz#0bce3c86f9d36a4984d3c3e07df1c3fb4c679bd9" "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": @@ -1561,13 +1656,13 @@ dependencies: "@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== +"@types/jest@^29.0.0": + version "29.0.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.0.0.tgz#bc66835bf6b09d6a47e22c21d7f5b82692e60e72" + integrity sha512-X6Zjz3WO4cT39Gkl0lZ2baFRaEMqJl5NC1OjElkwtNzAlbkr2K/WJXkBkH5VP0zx4Hgsd2TZYdOEfvp2Dxia+Q== dependencies: - expect "^28.0.0" - pretty-format "^28.0.0" + expect "^29.0.0" + pretty-format "^29.0.0" "@types/jsdom@^16.2.4": version "16.2.15" @@ -1607,14 +1702,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.49" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.49.tgz#560b1ea774b61e19a89c3fc72d2dcaa3863f38b2" - integrity sha512-Abq9fBviLV93OiXMu+f6r0elxCzRwc0RC5f99cU892uBITL44pTvgvEqlRlPRi8EGcO1z7Cp8A4d0s/p3J/+Nw== + version "16.11.57" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.57.tgz#786f74cef16acf2c5eb11795b6c3f7ae93596662" + integrity sha512-diBb5AE2V8h9Fs9zEDtBwSeLvIACng/aAkdZ3ujMV+cGuIQ9Nc/V+wQqurk9HJp8ni5roBxQHW21z/ZYbGDivg== "@types/parse5@^6.0.3": version "6.0.3" @@ -1669,13 +1764,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.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz#6df092a20e0f9ec748b27f293a12cb39d0c1fe4d" + integrity sha512-OwwR8LRwSnI98tdc2z7mJYgY60gf7I9ZfGjN5EjCwwns9bdTuQfAXcsjSB2wSQ/TVNYSGKf4kzVXbNGaZvwiXw== 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.36.2" + "@typescript-eslint/type-utils" "5.36.2" + "@typescript-eslint/utils" "5.36.2" debug "^4.3.4" functional-red-black-tree "^1.0.1" ignore "^5.2.0" @@ -1684,68 +1779,69 @@ 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.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.36.2.tgz#3ddf323d3ac85a25295a55fcb9c7a49ab4680ddd" + integrity sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA== 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.36.2" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/typescript-estree" "5.36.2" 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.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz#a75eb588a3879ae659514780831370642505d1cd" + integrity sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw== dependencies: - "@typescript-eslint/types" "5.33.1" - "@typescript-eslint/visitor-keys" "5.33.1" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/visitor-keys" "5.36.2" -"@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.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.36.2.tgz#752373f4babf05e993adf2cd543a763632826391" + integrity sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw== dependencies: - "@typescript-eslint/utils" "5.33.1" + "@typescript-eslint/typescript-estree" "5.36.2" + "@typescript-eslint/utils" "5.36.2" 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.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.36.2.tgz#a5066e500ebcfcee36694186ccc57b955c05faf9" + integrity sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ== -"@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.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz#0c93418b36c53ba0bc34c61fe9405c4d1d8fe560" + integrity sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w== dependencies: - "@typescript-eslint/types" "5.33.1" - "@typescript-eslint/visitor-keys" "5.33.1" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/visitor-keys" "5.36.2" 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.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.36.2.tgz#b01a76f0ab244404c7aefc340c5015d5ce6da74c" + integrity sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg== 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.36.2" + "@typescript-eslint/types" "5.36.2" + "@typescript-eslint/typescript-estree" "5.36.2" 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.36.2": + version "5.36.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz#2f8f78da0a3bad3320d2ac24965791ac39dace5a" + integrity sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A== dependencies: - "@typescript-eslint/types" "5.33.1" + "@typescript-eslint/types" "5.36.2" eslint-visitor-keys "^3.3.0" JSONStream@^1.0.3: @@ -1847,9 +1943,9 @@ align-text@^0.1.1, align-text@^0.1.3: repeat-string "^1.5.2" allchange@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.0.6.tgz#f905918255541dc92d6a1f5cdf758db4597f569c" - integrity sha512-37a4J55oSxhLmlS/DeBOKjKn5dbjkyR4qMJ9is8+CKLPTe7NybcWBYvrPLr9kVLBa6aigWrdovRHrQj/4v6k4w== + version "1.1.0" + resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.1.0.tgz#f8fa129e4b40c0b0a2c072c530f2324c6590e208" + integrity sha512-brDWf2feuL3FRyivSyC6AKOgpX+bYgs1Z7+ZmLti6PnBdZgIjRSnKvlc68N8+1UX2rCISx2I+XuUvE3/GJNG2A== dependencies: "@actions/core" "^1.4.0" "@actions/github" "^5.0.0" @@ -2014,15 +2110,15 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -babel-jest@^28.0.0, babel-jest@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.3.tgz#c1187258197c099072156a0a121c11ee1e3917d5" - integrity sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q== +babel-jest@^29.0.0, babel-jest@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.0.2.tgz#7efde496c07607949e9be499bf277aa1543ded95" + integrity sha512-yTu4/WSi/HzarjQtrJSwV+/0maoNt+iP0DmpvFJdv9yY+5BuNle8TbheHzzcSWj5gIHfuhpbLYHWRDYhWKyeKQ== dependencies: - "@jest/transform" "^28.1.3" + "@jest/transform" "^29.0.2" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^28.1.3" + babel-preset-jest "^29.0.2" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -2045,10 +2141,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz#1952c4d0ea50f2d6d794353762278d1d8cca3fbe" - integrity sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q== +babel-plugin-jest-hoist@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.2.tgz#ae61483a829a021b146c016c6ad39b8bcc37c2c8" + integrity sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -2097,12 +2193,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz#5dfc20b99abed5db994406c2b9ab94c73aaa419d" - integrity sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A== +babel-preset-jest@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.0.2.tgz#e14a7124e22b161551818d89e5bdcfb3b2b0eac7" + integrity sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA== dependencies: - babel-plugin-jest-hoist "^28.1.3" + babel-plugin-jest-hoist "^29.0.2" babel-preset-current-node-syntax "^1.0.0" babel-runtime@^6.26.0: @@ -2474,9 +2570,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.30001390" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz#158a43011e7068ef7fc73590e9fd91a7cece5e7f" + integrity sha512-sS4CaUM+/+vqQUlCvCJ2WtDlV81aWtHhqeEVkLokVJJa3ViN4zDxAGfq9R8i1m90uGHxo99cy10Od+lvn3hf0g== caseless@~0.12.0: version "0.12.0" @@ -2724,9 +2820,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" @@ -2965,6 +3061,11 @@ diff-sequences@^28.1.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== +diff-sequences@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.0.0.tgz#bae49972ef3933556bcb0800b72e8579d19d9e4f" + integrity sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -3040,9 +3141,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.242" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.242.tgz#51284820b0e6f6ce6c60d3945a3c4f9e4bd88f5f" + integrity sha512-nPdgMWtjjWGCtreW/2adkrB2jyHjClo9PtVhR6rW+oxa4E4Wom642Tn+5LslHP3XPL5MCpkn5/UEY60EXylNeQ== elliptic@^6.5.3: version "6.5.4" @@ -3231,10 +3332,10 @@ eslint-plugin-import@^2.25.4: resolve "^1.22.0" tsconfig-paths "^3.14.1" -eslint-plugin-matrix-org@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-0.5.2.tgz#eb355b1a81906ea814235d0b224e8162db7cbbf4" - integrity sha512-qJbyxp9cOi35Qpn3WCBqohCJaMSVp3ntOJ3WbjpREbCQdyrFze6MJAayl7GNidbNsdP7ejHTi0PtZzyKLcfLzQ== +eslint-plugin-matrix-org@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-0.6.1.tgz#deab0636a1fe999d9c2a42929c2b486334ec8ead" + integrity sha512-kq7fCbOdj6OvPF50gJtTVSgg6TbQCOxwwZktyIGQJfZyGNWhew77ptTnmaxgxq+RIQ+rzNcWrcMGO5eQC9fZAg== eslint-rule-composer@^0.3.0: version "0.3.0" @@ -3274,13 +3375,15 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@8.20.0: - version "8.20.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.20.0.tgz#048ac56aa18529967da8354a478be4ec0a2bc81b" - integrity sha512-d4ixhz5SKCa1D6SCPrivP7yYVi7nyD6A4vs6HIAul9ujBzcEmZVM3/0NN/yu5nKhmO1wjp5xQ46iRfmDGlOviA== +eslint@8.23.0: + version "8.23.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.23.0.tgz#a184918d288820179c6041bb3ddcc99ce6eea040" + integrity sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA== dependencies: - "@eslint/eslintrc" "^1.3.0" - "@humanwhocodes/config-array" "^0.9.2" + "@eslint/eslintrc" "^1.3.1" + "@humanwhocodes/config-array" "^0.10.4" + "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" + "@humanwhocodes/module-importer" "^1.0.1" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -3290,14 +3393,17 @@ eslint@8.20.0: eslint-scope "^7.1.1" eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" - espree "^9.3.2" + espree "^9.4.0" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" + find-up "^5.0.0" functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" globals "^13.15.0" + globby "^11.1.0" + grapheme-splitter "^1.0.4" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -3313,12 +3419,11 @@ eslint@8.20.0: strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" - v8-compile-cache "^2.0.3" -espree@^9.3.2: - 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.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" @@ -3418,7 +3523,7 @@ exorcist@^2.0.0: mkdirp "^1.0.4" mold-source-map "^0.4.0" -expect@^28.0.0, expect@^28.1.0, expect@^28.1.3: +expect@^28.1.0: version "28.1.3" resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.3.tgz#90a7c1a124f1824133dd4533cce2d2bdcb6603ec" integrity sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g== @@ -3429,12 +3534,23 @@ expect@^28.0.0, expect@^28.1.0, expect@^28.1.3: jest-message-util "^28.1.3" jest-util "^28.1.3" -ext@^1.1.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz#3871d50641e874cc172e2b53f919842d19db4c52" - integrity sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg== +expect@^29.0.0, expect@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.0.2.tgz#22c7132400f60444b427211f1d6bb604a9ab2420" + integrity sha512-JeJlAiLKn4aApT4pzUXBVxl3NaZidWIOdg//smaIlP9ZMBDkHZGFd9ubphUZP9pUyDEo7bC6M0IIZR51o75qQw== dependencies: - type "^2.5.0" + "@jest/expect-utils" "^29.0.2" + jest-get-type "^29.0.0" + jest-matcher-utils "^29.0.2" + jest-message-util "^29.0.2" + jest-util "^29.0.2" + +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" extend@~3.0.2: version "3.0.2" @@ -3474,7 +3590,7 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -3558,9 +3674,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" @@ -3753,6 +3869,11 @@ graceful-fs@^4.1.9, graceful-fs@^4.2.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -4243,82 +4364,82 @@ istanbul-reports@^3.1.3, istanbul-reports@^3.1.4: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.1.3.tgz#d9aeee6792be3686c47cb988a8eaf82ff4238831" - integrity sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA== +jest-changed-files@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.0.0.tgz#aa238eae42d9372a413dd9a8dadc91ca1806dce0" + integrity sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ== dependencies: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.3.tgz#d14bd11cf8ee1a03d69902dc47b6bd4634ee00e4" - integrity sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow== +jest-circus@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.0.2.tgz#7dda94888a8d47edb58e85a8e5f688f9da6657a3" + integrity sha512-YTPEsoE1P1X0bcyDQi3QIkpt2Wl9om9k2DQRuLFdS5x8VvAKSdYAVJufgvudhnKgM8WHvvAzhBE+1DRQB8x1CQ== dependencies: - "@jest/environment" "^28.1.3" - "@jest/expect" "^28.1.3" - "@jest/test-result" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/environment" "^29.0.2" + "@jest/expect" "^29.0.2" + "@jest/test-result" "^29.0.2" + "@jest/types" "^29.0.2" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^28.1.3" - jest-matcher-utils "^28.1.3" - jest-message-util "^28.1.3" - jest-runtime "^28.1.3" - jest-snapshot "^28.1.3" - jest-util "^28.1.3" + jest-each "^29.0.2" + jest-matcher-utils "^29.0.2" + jest-message-util "^29.0.2" + jest-runtime "^29.0.2" + jest-snapshot "^29.0.2" + jest-util "^29.0.2" p-limit "^3.1.0" - pretty-format "^28.1.3" + pretty-format "^29.0.2" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.3.tgz#558b33c577d06de55087b8448d373b9f654e46b2" - integrity sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ== +jest-cli@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.0.2.tgz#adf341ee3a4fd6ad1f23e3c0eb4e466847407021" + integrity sha512-tlf8b+4KcUbBGr25cywIi3+rbZ4+G+SiG8SvY552m9sRZbXPafdmQRyeVE/C/R8K+TiBAMrTIUmV2SlStRJ40g== dependencies: - "@jest/core" "^28.1.3" - "@jest/test-result" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/core" "^29.0.2" + "@jest/test-result" "^29.0.2" + "@jest/types" "^29.0.2" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^28.1.3" - jest-util "^28.1.3" - jest-validate "^28.1.3" + jest-config "^29.0.2" + jest-util "^29.0.2" + jest-validate "^29.0.2" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.3.tgz#e315e1f73df3cac31447eed8b8740a477392ec60" - integrity sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ== +jest-config@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.0.2.tgz#0ce168e1f74ca46c27285a7182ecb06c2d8ce7d9" + integrity sha512-RU4gzeUNZAFktYVzDGimDxeYoaiTnH100jkYYZgldqFamaZukF0IqmFx8+QrzVeEWccYg10EEJT3ox1Dq5b74w== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^28.1.3" - "@jest/types" "^28.1.3" - babel-jest "^28.1.3" + "@jest/test-sequencer" "^29.0.2" + "@jest/types" "^29.0.2" + babel-jest "^29.0.2" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^28.1.3" - jest-environment-node "^28.1.3" - jest-get-type "^28.0.2" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.3" - jest-runner "^28.1.3" - jest-util "^28.1.3" - jest-validate "^28.1.3" + jest-circus "^29.0.2" + jest-environment-node "^29.0.2" + jest-get-type "^29.0.0" + jest-regex-util "^29.0.0" + jest-resolve "^29.0.2" + jest-runner "^29.0.2" + jest-util "^29.0.2" + jest-validate "^29.0.2" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^28.1.3" + pretty-format "^29.0.2" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -4332,24 +4453,23 @@ jest-diff@^28.1.3: jest-get-type "^28.0.2" pretty-format "^28.1.3" -jest-docblock@^28.1.1: - version "28.1.1" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8" - integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== +jest-diff@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.0.2.tgz#1a99419efda66f9ee72f91e580e774df95de5ddc" + integrity sha512-b9l9970sa1rMXH1owp2Woprmy42qIwwll/htsw4Gf7+WuSp5bZxNhkKHDuCGKL+HoHn1KhcC+tNEeAPYBkD2Jg== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.0.0" + jest-get-type "^29.0.0" + pretty-format "^29.0.2" + +jest-docblock@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.0.0.tgz#3151bcc45ed7f5a8af4884dcc049aee699b4ceae" + integrity sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw== dependencies: detect-newline "^3.0.0" -jest-each@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.3.tgz#bdd1516edbe2b1f3569cfdad9acd543040028f81" - integrity sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g== - dependencies: - "@jest/types" "^28.1.3" - chalk "^4.0.0" - jest-get-type "^28.0.2" - jest-util "^28.1.3" - pretty-format "^28.1.3" - jest-environment-jsdom@^28.1.3: version "28.1.3" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-28.1.3.tgz#2d4e5d61b7f1d94c3bddfbb21f0308ee506c09fb" @@ -4368,45 +4488,66 @@ jest-environment-node@^28.1.3: version "28.1.3" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.3.tgz#7e74fe40eb645b9d56c0c4b70ca4357faa349be5" integrity sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A== + +jest-each@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.0.2.tgz#f98375a79a37761137e11d458502dfe1f00ba5b0" + integrity sha512-+sA9YjrJl35iCg0W0VCrgCVj+wGhDrrKQ+YAqJ/DHBC4gcDFAeePtRRhpJnX9gvOZ63G7gt52pwp2PesuSEx0Q== dependencies: - "@jest/environment" "^28.1.3" - "@jest/fake-timers" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/types" "^29.0.2" + chalk "^4.0.0" + jest-get-type "^29.0.0" + jest-util "^29.0.2" + pretty-format "^29.0.2" + +jest-environment-node@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.0.2.tgz#8196268c9f740f1d2e7ecccf212b4c1c5b0167e4" + integrity sha512-4Fv8GXVCToRlMzDO94gvA8iOzKxQ7rhAbs8L+j8GPyTxGuUiYkV+63LecGeVdVhsL2KXih1sKnoqmH6tp89J7Q== + dependencies: + "@jest/environment" "^29.0.2" + "@jest/fake-timers" "^29.0.2" + "@jest/types" "^29.0.2" "@types/node" "*" - jest-mock "^28.1.3" - jest-util "^28.1.3" + jest-mock "^29.0.2" + jest-util "^29.0.2" jest-get-type@^28.0.2: version "28.0.2" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== -jest-haste-map@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.3.tgz#abd5451129a38d9841049644f34b034308944e2b" - integrity sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA== +jest-get-type@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.0.0.tgz#843f6c50a1b778f7325df1129a0fd7aa713aef80" + integrity sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw== + +jest-haste-map@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.0.2.tgz#cac403a595e6e43982c9776b5c4dae63e38b22c5" + integrity sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A== dependencies: - "@jest/types" "^28.1.3" + "@jest/types" "^29.0.2" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^28.0.2" - jest-util "^28.1.3" - jest-worker "^28.1.3" + jest-regex-util "^29.0.0" + jest-util "^29.0.2" + jest-worker "^29.0.2" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz#a6685d9b074be99e3adee816ce84fd30795e654d" - integrity sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA== +jest-leak-detector@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.0.2.tgz#f88fd08e352b5fad3d33e48ecab39e97077ed8a8" + integrity sha512-5f0493qDeAxjUldkBSQg5D1cLadRgZVyWpTQvfJeQwQUpHQInE21AyVHVv64M7P2Ue8Z5EZ4BAcoDS/dSPPgMw== dependencies: - jest-get-type "^28.0.2" - pretty-format "^28.1.3" + jest-get-type "^29.0.0" + pretty-format "^29.0.2" jest-localstorage-mock@^2.4.6: version "2.4.22" @@ -4423,6 +4564,16 @@ jest-matcher-utils@^28.1.3: jest-get-type "^28.0.2" pretty-format "^28.1.3" +jest-matcher-utils@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.0.2.tgz#0ffdcaec340a9810caee6c73ff90fb029b446e10" + integrity sha512-s62YkHFBfAx0JLA2QX1BlnCRFwHRobwAv2KP1+YhjzF6ZCbCVrf1sG8UJyn62ZUsDaQKpoo86XMTjkUyO5aWmQ== + dependencies: + chalk "^4.0.0" + jest-diff "^29.0.2" + jest-get-type "^29.0.0" + pretty-format "^29.0.2" + jest-message-util@^28.1.3: version "28.1.3" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.3.tgz#232def7f2e333f1eecc90649b5b94b0055e7c43d" @@ -4438,12 +4589,27 @@ jest-message-util@^28.1.3: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.3.tgz#d4e9b1fc838bea595c77ab73672ebf513ab249da" - integrity sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA== +jest-message-util@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.0.2.tgz#b2781dfb6a2d1c63830d9684c5148ae3155c6154" + integrity sha512-kcJAgms3ckJV0wUoLsAM40xAhY+pb9FVSZwicjFU9PFkaTNmqh9xd99/CzKse48wPM1ANUQKmp03/DpkY+lGrA== dependencies: - "@jest/types" "^28.1.3" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.0.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.0.2" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.0.2.tgz#d7810966a6338aca6a440c3cd9f19276477840ad" + integrity sha512-giWXOIT23UCxHCN2VUfUJ0Q7SmiqQwfSFXlCaIhW5anITpNQ+3vuLPQdKt5wkuwM37GrbFyHIClce8AAK9ft9g== + dependencies: + "@jest/types" "^29.0.2" "@types/node" "*" jest-pnp-resolver@^1.2.2: @@ -4451,116 +4617,117 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== -jest-regex-util@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" - integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== +jest-regex-util@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.0.0.tgz#b442987f688289df8eb6c16fa8df488b4cd007de" + integrity sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug== -jest-resolve-dependencies@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz#8c65d7583460df7275c6ea2791901fa975c1fe66" - integrity sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA== +jest-resolve-dependencies@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.0.2.tgz#2d30199ed0059ff97712f4fa6320c590bfcd2061" + integrity sha512-fSAu6eIG7wtGdnPJUkVVdILGzYAP9Dj/4+zvC8BrGe8msaUMJ9JeygU0Hf9+Uor6/icbuuzQn5See1uajLnAqg== dependencies: - jest-regex-util "^28.0.2" - jest-snapshot "^28.1.3" + jest-regex-util "^29.0.0" + jest-snapshot "^29.0.2" -jest-resolve@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.3.tgz#cfb36100341ddbb061ec781426b3c31eb51aa0a8" - integrity sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ== +jest-resolve@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.0.2.tgz#dd097e1c8020fbed4a8c1e1889ccb56022288697" + integrity sha512-V3uLjSA+EHxLtjIDKTBXnY71hyx+8lusCqPXvqzkFO1uCGvVpjBfuOyp+KOLBNSuY61kM2jhepiMwt4eiJS+Vw== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^28.1.3" + jest-haste-map "^29.0.2" jest-pnp-resolver "^1.2.2" - jest-util "^28.1.3" - jest-validate "^28.1.3" + jest-util "^29.0.2" + jest-validate "^29.0.2" resolve "^1.20.0" resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.3.tgz#5eee25febd730b4713a2cdfd76bdd5557840f9a1" - integrity sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA== +jest-runner@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.0.2.tgz#64e4e6c88f74387307687b73a4688f93369d8d99" + integrity sha512-+D82iPZejI8t+SfduOO1deahC/QgLFf8aJBO++Znz3l2ETtOMdM7K4ATsGWzCFnTGio5yHaRifg1Su5Ybza5Nw== dependencies: - "@jest/console" "^28.1.3" - "@jest/environment" "^28.1.3" - "@jest/test-result" "^28.1.3" - "@jest/transform" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/console" "^29.0.2" + "@jest/environment" "^29.0.2" + "@jest/test-result" "^29.0.2" + "@jest/transform" "^29.0.2" + "@jest/types" "^29.0.2" "@types/node" "*" chalk "^4.0.0" emittery "^0.10.2" graceful-fs "^4.2.9" - jest-docblock "^28.1.1" - jest-environment-node "^28.1.3" - jest-haste-map "^28.1.3" - jest-leak-detector "^28.1.3" - jest-message-util "^28.1.3" - jest-resolve "^28.1.3" - jest-runtime "^28.1.3" - jest-util "^28.1.3" - jest-watcher "^28.1.3" - jest-worker "^28.1.3" + jest-docblock "^29.0.0" + jest-environment-node "^29.0.2" + jest-haste-map "^29.0.2" + jest-leak-detector "^29.0.2" + jest-message-util "^29.0.2" + jest-resolve "^29.0.2" + jest-runtime "^29.0.2" + jest-util "^29.0.2" + jest-watcher "^29.0.2" + jest-worker "^29.0.2" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.3.tgz#a57643458235aa53e8ec7821949e728960d0605f" - integrity sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw== +jest-runtime@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.0.2.tgz#dc3de788b8d75af346ae163d59c585027a9d809c" + integrity sha512-DO6F81LX4okOgjJLkLySv10E5YcV5NHUbY1ZqAUtofxdQE+q4hjH0P2gNsY8x3z3sqgw7O/+919SU4r18Fcuig== dependencies: - "@jest/environment" "^28.1.3" - "@jest/fake-timers" "^28.1.3" - "@jest/globals" "^28.1.3" - "@jest/source-map" "^28.1.2" - "@jest/test-result" "^28.1.3" - "@jest/transform" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/environment" "^29.0.2" + "@jest/fake-timers" "^29.0.2" + "@jest/globals" "^29.0.2" + "@jest/source-map" "^29.0.0" + "@jest/test-result" "^29.0.2" + "@jest/transform" "^29.0.2" + "@jest/types" "^29.0.2" + "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" - execa "^5.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^28.1.3" - jest-message-util "^28.1.3" - jest-mock "^28.1.3" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.3" - jest-snapshot "^28.1.3" - jest-util "^28.1.3" + jest-haste-map "^29.0.2" + jest-message-util "^29.0.2" + jest-mock "^29.0.2" + jest-regex-util "^29.0.0" + jest-resolve "^29.0.2" + jest-snapshot "^29.0.2" + jest-util "^29.0.2" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.3.tgz#17467b3ab8ddb81e2f605db05583d69388fc0668" - integrity sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg== +jest-snapshot@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.0.2.tgz#5017d54db8369f01900d11e179513fa5839fb5ac" + integrity sha512-26C4PzGKaX5gkoKg8UzYGVy2HPVcTaROSkf0gwnHu3lGeTB7bAIJBovvVPZoiJ20IximJELQs/r8WSDRCuGX2A== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^28.1.3" - "@jest/transform" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/expect-utils" "^29.0.2" + "@jest/transform" "^29.0.2" + "@jest/types" "^29.0.2" "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^28.1.3" + expect "^29.0.2" graceful-fs "^4.2.9" - jest-diff "^28.1.3" - jest-get-type "^28.0.2" - jest-haste-map "^28.1.3" - jest-matcher-utils "^28.1.3" - jest-message-util "^28.1.3" - jest-util "^28.1.3" + jest-diff "^29.0.2" + jest-get-type "^29.0.0" + jest-haste-map "^29.0.2" + jest-matcher-utils "^29.0.2" + jest-message-util "^29.0.2" + jest-util "^29.0.2" natural-compare "^1.4.0" - pretty-format "^28.1.3" + pretty-format "^29.0.2" semver "^7.3.5" jest-sonar-reporter@^2.0.0: @@ -4582,50 +4749,62 @@ jest-util@^28.1.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.3.tgz#e322267fd5e7c64cea4629612c357bbda96229df" - integrity sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA== +jest-util@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.0.2.tgz#c75c5cab7f3b410782f9570a60c5558b5dfb6e3a" + integrity sha512-ozk8ruEEEACxqpz0hN9UOgtPZS0aN+NffwQduR5dVlhN+eN47vxurtvgZkYZYMpYrsmlAEx1XabkB3BnN0GfKQ== dependencies: - "@jest/types" "^28.1.3" + "@jest/types" "^29.0.2" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.0.2.tgz#ad86e157cc1735a3a3ea88995a611ebf8544bd67" + integrity sha512-AeRKm7cEucSy7tr54r3LhiGIXYvOILUwBM1S7jQkKs6YelwAlWKsmZGVrQR7uwsd31rBTnR5NQkODi1Z+6TKIQ== + dependencies: + "@jest/types" "^29.0.2" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^28.0.2" + jest-get-type "^29.0.0" leven "^3.1.0" - pretty-format "^28.1.3" + pretty-format "^29.0.2" -jest-watcher@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.3.tgz#c6023a59ba2255e3b4c57179fc94164b3e73abd4" - integrity sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g== +jest-watcher@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.0.2.tgz#093c044e0d7462e691ec64ca6d977014272c9bca" + integrity sha512-ds2bV0oyUdYoyrUTv4Ga5uptz4cEvmmP/JzqDyzZZanvrIn8ipxg5l3SDOAIiyuAx1VdHd2FBzeXPFO5KPH8vQ== dependencies: - "@jest/test-result" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/test-result" "^29.0.2" + "@jest/types" "^29.0.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.10.2" - jest-util "^28.1.3" + jest-util "^29.0.2" string-length "^4.0.1" -jest-worker@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98" - integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== +jest-worker@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.0.2.tgz#46c9f2cb9a19663d22babbacf998e4b5d7c46574" + integrity sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^28.0.0: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.3.tgz#e9c6a7eecdebe3548ca2b18894a50f45b36dfc6b" - integrity sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA== +jest@^29.0.0: + version "29.0.2" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.0.2.tgz#16e20003dbf8fb9ed7e6ab801579a77084e13fba" + integrity sha512-enziNbNUmXTcTaTP/Uq5rV91r0Yqy2UKzLUIabxMpGm9YHz8qpbJhiRnNVNvm6vzWfzt/0o97NEHH8/3udoClA== dependencies: - "@jest/core" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/core" "^29.0.2" + "@jest/types" "^29.0.2" import-local "^3.0.2" - jest-cli "^28.1.3" + jest-cli "^29.0.2" js-stringify@^1.0.1: version "1.0.2" @@ -4988,9 +5167,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" @@ -5502,7 +5681,7 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== -pretty-format@^28.0.0, pretty-format@^28.1.3: +pretty-format@^28.1.3: version "28.1.3" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.3.tgz#c9fba8cedf99ce50963a11b27d982a9ae90970d5" integrity sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q== @@ -5512,6 +5691,15 @@ pretty-format@^28.0.0, pretty-format@^28.1.3: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.0.0, pretty-format@^29.0.2: + version "29.0.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.0.2.tgz#7f7666a7bf05ba2bcacde61be81c6db64f6f3be6" + integrity sha512-wp3CdtUa3cSJVFn3Miu5a1+pxc1iPIQTenOAn+x5erXeN1+ryTcLesV5pbK/rlW5EKwp27x38MoYfNGaNXDDhg== + dependencies: + "@jest/schemas" "^29.0.0" + ansi-styles "^5.0.0" + react-is "^18.0.0" + private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -5990,7 +6178,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -6383,9 +6571,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" @@ -6612,7 +6800,7 @@ type@^1.0.1: resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== -type@^2.5.0: +type@^2.7.2: version "2.7.2" resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== @@ -6628,9 +6816,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" @@ -6736,9 +6924,9 @@ universalify@^0.1.2: integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== update-browserslist-db@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" - integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q== + version "1.0.7" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz#16279639cff1d0f800b14792de43d97df2d11b7d" + integrity sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -6792,11 +6980,6 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-compile-cache@^2.0.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== - v8-to-istanbul@^9.0.0, v8-to-istanbul@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4" @@ -6842,9 +7025,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"