Compare commits

...

756 Commits

Author SHA1 Message Date
RiotRobot 467b75f2dc v12.0.0 2021-06-21 16:20:26 +01:00
RiotRobot 1f728cab92 Prepare changelog for v12.0.0 2021-06-21 16:20:25 +01:00
RiotRobot 68c1171294 v12.0.0-rc.1 2021-06-15 16:10:48 +01:00
RiotRobot f4f01913fe Prepare changelog for v12.0.0-rc.1 2021-06-15 16:10:48 +01:00
Travis Ralston 9f6ed4fb33 Merge pull request #1730 from SimonBrandner/show-username
Rework how disambiguation is handled
2021-06-15 00:24:06 -06:00
Michael Telatynski 434117c771 Merge pull request #1735 from matrix-org/t3chguy/fix/17282 2021-06-14 22:15:12 +01:00
Michael Telatynski cd95b8724a Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17282 2021-06-14 21:46:31 +01:00
Michael Telatynski ec2a4d473e Iterate algorithm, base it on new js-sdk string lib 2021-06-14 21:28:33 +01:00
Travis Ralston 47eaf3cd58 Merge pull request #1734 from matrix-org/travis/fix-types
Move various types from the react-sdk to the js-sdk
2021-06-14 13:48:07 -06:00
Travis Ralston d7b23a8634 liiiinttteeerrrr 2021-06-14 13:37:19 -06:00
Travis Ralston 6db7972f04 preset 2021-06-14 13:35:20 -06:00
Travis Ralston 6840ee077c Appease the linter forever 2021-06-14 13:34:37 -06:00
Travis Ralston 3c85bcc3c9 Move various types from the react-sdk to the js-sdk 2021-06-14 13:32:28 -06:00
Travis Ralston 759eca6479 Merge pull request #1732 from matrix-org/travis/file-trees
Unstable implementation of MSC3089: File Trees
2021-06-14 12:04:09 -06:00
Travis Ralston 4488f174aa const 2021-06-14 12:00:59 -06:00
Travis Ralston 991a255041 Fix average on .5 2021-06-14 11:55:41 -06:00
Travis Ralston cfef635e1b Appease the linter 2021-06-14 10:41:32 -06:00
Travis Ralston d3027e1fe8 Offset the alphabet by 1 2021-06-14 10:28:26 -06:00
Travis Ralston d99ea1c6b4 Update src/utils.ts
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-06-14 08:21:09 -06:00
Travis Ralston 9af214007e APPEASE. THE. LINTER. 2021-06-11 11:37:44 -06:00
Travis Ralston 0541b7f3c5 Remove a layer of indirection 2021-06-11 11:36:52 -06:00
Travis Ralston 63fa774af7 Another round of appeasement 2021-06-11 11:34:37 -06:00
Travis Ralston 4b19b36de1 Crude JS->TS conversion on utils test because of linter and BigInt 2021-06-11 11:27:46 -06:00
Travis Ralston 5715df6b18 BigInt, rollover, and developer lint 2021-06-11 11:18:18 -06:00
Michael Telatynski 22bcacc715 Merge pull request #1729 from matrix-org/t3chguy/fix/17282
Add MSC3230 event type to enum
2021-06-11 12:40:23 +01:00
Travis Ralston f1e270ca9d appease the linter 2021-06-10 15:54:56 -06:00
Travis Ralston f535e7535c Update string averaging utils 2021-06-10 15:38:49 -06:00
David Baker 912c5e13e1 Merge pull request #1731 from matrix-org/dbkr/transferred_reason_code
Add separate reason code for transferred calls
2021-06-10 13:21:41 +01:00
Travis Ralston 4eb44ee2ea de-lint 2 2021-06-09 22:14:11 -06:00
Travis Ralston e41a2beb65 de-lint 2021-06-09 22:09:26 -06:00
Travis Ralston 1f6ba31a3f Use a sane padStart instead 2021-06-09 21:56:46 -06:00
Travis Ralston bcccc909c5 Pre-lint format 2021-06-09 21:55:06 -06:00
Travis Ralston b3a11030f2 Early file management APIs 2021-06-09 21:54:17 -06:00
Travis Ralston baaf76668f Early directory management 2021-06-09 21:54:17 -06:00
David Baker 36e2533164 Add separate reason code for transferred calls
'Replaced' is special cased so the media isn't torn down, but we
were passing Replaced for calls we transferred off elsewhere which
meant we never closed the media capture. Calls transferred elsewhere
aren't really replaced: they may as well have their own code.
2021-06-09 16:52:58 +01:00
Travis Ralston 9084b4e7aa Early implementation of MSC3089 (file trees)
MSC: https://github.com/matrix-org/matrix-doc/pull/3089
Includes part of MSC3088 (room subtyping): https://github.com/matrix-org/matrix-doc/pull/3088

The NamespacedValue stuff is borrowed from the Extensible Events implementation PR in the react-sdk as a useful thing to put here. When/if the MSCs become stable, we'd convert the values to enums and drop the constants (or keep them for migration purposes, but switch to stable). 

This flags the whole thing as unstable because it's highly subject to change.
2021-06-08 14:43:20 -06:00
Šimon Brandner f724da7e84 Merge remote-tracking branch 'upstream/develop' into disambiguate-prop 2021-06-08 11:30:50 +02:00
David Baker 4b8f47e2b4 Merge pull request #1728 from matrix-org/dbkr/hold_with_sendonly
Use sendonly for call hold
2021-06-08 09:26:57 +01:00
RiotRobot 35ecbed29d Resetting package fields for development 2021-06-07 17:00:30 +01:00
RiotRobot 30ed153ad1 Merge branch 'master' into develop 2021-06-07 17:00:30 +01:00
RiotRobot a1098989ff v11.2.0 2021-06-07 16:57:42 +01:00
RiotRobot 42bb63e07d Prepare changelog for v11.2.0 2021-06-07 16:57:42 +01:00
Michael Telatynski 683aae5ed5 Add MSC3230 event type to enum 2021-06-07 09:00:08 +01:00
David Baker 49ef274a83 Use sendonly for call hold
Instead of inactive. Remove the logic for trying to remember who
had who on hold, which is now unnecesasary. Appears to work fine
with element android & ios too.
2021-06-04 17:22:14 +01:00
J. Ryan Stinnett af16bba1ec Merge pull request #1727 from matrix-org/t3chguy/arg
Stop breeding sync listeners
2021-06-04 14:09:45 +01:00
Michael Telatynski 781086608e Stop breeding sync listeners 2021-06-04 13:58:19 +01:00
J. Ryan Stinnett e9c98b03b0 Merge pull request #1724 from matrix-org/jryans/semi-linting
Fix semicolons in TS files
2021-06-04 10:31:56 +01:00
J. Ryan Stinnett 75370f7855 Reduce allowable warning count 2021-06-04 10:24:55 +01:00
J. Ryan Stinnett 4a79e13410 Auto-fix more errors 2021-06-04 10:24:09 +01:00
J. Ryan Stinnett 220061f022 Merge remote-tracking branch 'origin/develop' into jryans/semi-linting 2021-06-04 10:22:06 +01:00
Travis Ralston 6c317b7f28 Fix types on checkOwnCrossSigningTrust 2021-06-04 00:10:33 -06:00
Travis Ralston 3788fbf607 Merge pull request #1718 from matrix-org/travis/ts-mtxcli
[BREAKING] Convert MatrixClient to TypeScript
2021-06-03 19:09:23 -06:00
Travis Ralston 382854c04c Appease linter 2021-06-03 19:04:49 -06:00
Travis Ralston c2fae3bad8 Fix missed conversion fallout 2021-06-03 19:02:46 -06:00
Travis Ralston 92ebd39391 Reincorporate crypto changes
https://github.com/matrix-org/matrix-js-sdk/pull/1697
2021-06-03 18:59:01 -06:00
Travis Ralston f53a32a6b4 Merge branch 'develop' into travis/ts-mtxcli 2021-06-03 18:49:08 -06:00
Hubert Chathi 6c882e6605 Merge pull request #1697 from uhoreg/backup_refactor
Factor out backup management to a separate module
2021-06-03 18:56:19 -04:00
Hubert Chathi ca85dfc6ff re-lint 2021-06-03 18:52:06 -04:00
Hubert Chathi e22ecc6b6d Merge branch 'develop' into backup_refactor 2021-06-03 18:43:46 -04:00
Hubert Chathi 2608dd2d64 mark members as public 2021-06-03 18:15:05 -04:00
J. Ryan Stinnett 7d20d24249 Convert small blocks to inline expressions 2021-06-03 15:51:33 +01:00
Travis Ralston a3ac3692af private->protected 2021-06-02 22:05:53 -06:00
Travis Ralston 0070c8f843 Merge pull request #1723 from schmop/power-levels-unknown-state-key
Ignore power_levels events with unknown state_key on room-state initialization
2021-06-02 19:04:13 -06:00
Travis Ralston b360fc8308 Fix test failure 2021-06-02 18:42:52 -06:00
Travis Ralston bf9ba65ac4 Fix olmVersion types 2021-06-02 13:42:20 -06:00
Travis Ralston 43abfbc537 Upstream build pass 2 2021-06-02 13:42:20 -06:00
Travis Ralston 2700f0acf6 Upstream build pass 1 2021-06-02 13:42:20 -06:00
Travis Ralston 9156bed961 Tests pass 2 2021-06-02 13:42:20 -06:00
Travis Ralston 71dc0bac56 Tests pass 1 2021-06-02 13:42:20 -06:00
Travis Ralston 40f55b2964 Lint pass 4 2021-06-02 13:42:20 -06:00
Travis Ralston 9307f9f345 Build pass 2 2021-06-02 13:42:20 -06:00
Travis Ralston dfb918adc3 Lint pass 3 2021-06-02 13:42:20 -06:00
Travis Ralston 2f87a4859e Lint pass 2 2021-06-02 13:42:20 -06:00
Travis Ralston 191f73e0d0 Appease typescript 2021-06-02 13:42:20 -06:00
Travis Ralston 263e55f25d Build pass 1 2021-06-02 13:42:20 -06:00
Travis Ralston 5c55dce13e Lint pass 1 2021-06-02 13:42:20 -06:00
Travis Ralston a1a6ec6dfa Fix remaining hot paths 2021-06-02 13:42:20 -06:00
Travis Ralston e1edd84700 Early pass to fix runtime/build errors 2021-06-02 13:42:20 -06:00
Travis Ralston 07ee256756 Incorporate https://github.com/matrix-org/matrix-js-sdk/pull/1720 2021-06-02 13:42:20 -06:00
Travis Ralston 48888e530e Defer types 2021-06-02 13:42:20 -06:00
Travis Ralston 4ef50bef55 define this.identityServer 2021-06-02 13:42:20 -06:00
Travis Ralston 486369e97c Clean up "base-apis" find&replace 2021-06-02 13:42:20 -06:00
Travis Ralston 67994f7a53 Move new MatrixClient into place 2021-06-02 13:42:20 -06:00
Travis Ralston f027ddaf35 Autoformat 2021-06-02 13:42:20 -06:00
Travis Ralston f3b27d1e06 Cleanup 2021-06-02 13:42:20 -06:00
Travis Ralston 92e18b32dc Import MatrixError 2021-06-02 13:42:20 -06:00
Travis Ralston 4030ec9c8b Fix easy typing errors 2021-06-02 13:42:20 -06:00
Travis Ralston 497c2dc8df Bring in BaseApis to MatrixClient 2021-06-02 13:42:20 -06:00
Travis Ralston 8a1d34c419 [Combined] First pass of JS->TS for MatrixClient 2021-06-02 13:42:20 -06:00
Travis Ralston caab5befaa Rename client.js -> 1client.ts for future commits 2021-06-02 13:42:20 -06:00
Travis Ralston 0d316e3d3e Move useful docs to ICreateClientOpts 2021-06-02 13:42:20 -06:00
David Baker 8f9a682d34 Merge pull request #1725 from matrix-org/dbkr/revert_1579
Revert 1579 (Fix extra negotiate message in Firefox)
2021-06-02 15:04:17 +01:00
David Baker 102d5acf09 Revert 1579
This is causing all sorts of problems, like
https://github.com/vector-im/element-web/issues/17450
and also issues where we fail to respond to negotiates because
we can't adjust a direction of a stopped transceiver. Until
s/inactive/sendonly/ let's live with the minor firefox bug rather
than all this brokenness.
2021-06-02 14:41:55 +01:00
J. Ryan Stinnett e2ec8952e3 Fix semicolons in TS files
This updates the linting config to include the semi fix in
https://github.com/matrix-org/eslint-plugin-matrix-org/pull/8. The various semi
errors have been auto-fixed.
2021-06-02 11:49:39 +01:00
Lars Richard b4eff9b996 Ignore m.room.power_levels events with unknown state_key on room-state initialization
Signed-off-by: Lars Richard <lars.richard@iserv.eu>
2021-06-02 11:42:50 +02:00
RiotRobot d050261fa9 v11.2.0-rc.1 2021-06-01 16:09:49 +01:00
RiotRobot d29330815e Prepare changelog for v11.2.0-rc.1 2021-06-01 16:09:48 +01:00
Michael Telatynski 801b4022de Merge pull request #1720 from matrix-org/t3chguy/fix/17521
Switch to stable endpoint/fields for MSC2858
2021-06-01 12:07:25 +01:00
Michael Telatynski e78fbd1dff Switch to stable endpoint/fields for MSC2858 2021-06-01 11:01:10 +01:00
Hubert Chathi c543358826 add unit test and minor fixes 2021-05-31 21:52:20 -04:00
Travis Ralston ff2954839b Merge pull request #1715 from matrix-org/dependabot/npm_and_yarn/ws-7.4.6
Bump ws from 7.4.2 to 7.4.6
2021-05-29 21:44:48 -06:00
dependabot[bot] 1a91b88968 Bump ws from 7.4.2 to 7.4.6
Bumps [ws](https://github.com/websockets/ws) from 7.4.2 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.4.2...7.4.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-29 21:09:19 +00:00
Germain 25ec81b6c7 Merge pull request #1712 from matrix-org/gsouquet/fix/17393
Make consistent call event type checks
2021-05-28 13:09:35 +01:00
J. Ryan Stinnett a50802a63f Merge pull request #1714 from matrix-org/jryans/babel-config
Apply new Babel linting config
2021-05-28 13:04:06 +01:00
J. Ryan Stinnett ce1d374a82 Merge pull request #1709 from matrix-org/dependabot/npm_and_yarn/browserslist-4.16.6
Bump browserslist from 4.16.1 to 4.16.6
2021-05-28 11:41:38 +01:00
J. Ryan Stinnett 6dca8ac460 Switch to new Babel lint config
This also adjusts the TypeScript project lint config to cover *.ts test files
too.
2021-05-28 11:39:06 +01:00
J. Ryan Stinnett 4dc21674d5 Fix log usage 2021-05-28 11:30:20 +01:00
J. Ryan Stinnett 8805dd8c01 Auto-fix lint errors 2021-05-28 11:15:10 +01:00
David Baker 73b624e761 Merge pull request #1713 from matrix-org/dbkr/user_busy
Add user_busy call hangup reason
2021-05-28 09:42:56 +01:00
David Baker c44fd972b6 Add user_busy call hangup reason
And fix hangup reasons in reject events
2021-05-27 18:56:27 +01:00
J. Ryan Stinnett ff344bc110 Switch lint parsers 2021-05-27 17:51:03 +01:00
J. Ryan Stinnett b0e2a38325 Auto-fix lint errors 2021-05-27 17:50:16 +01:00
J. Ryan Stinnett fbb741ab10 Switch to new Babel linting config 2021-05-27 17:49:35 +01:00
Germain 3a3be36f4c Merge pull request #1688 from matrix-org/gsouquet/pr-review-linting-rules 2021-05-27 16:03:36 +01:00
Germain Souquet cb91c4292c Merge branch 'develop' into gsouquet/pr-review-linting-rules 2021-05-27 16:00:12 +01:00
Germain Souquet 80722ce145 make consistent call event type checks 2021-05-27 09:55:50 +01:00
Hubert Chathi 07bfa5532e fix more unit tests 2021-05-26 18:18:30 -04:00
J. Ryan Stinnett cea1a3ff91 Merge pull request #1710 from matrix-org/jryans/hidden-events-reactions
Emit relations created when target event added later
2021-05-26 17:34:47 +01:00
J. Ryan Stinnett 270be2df7a Emit relations created when target event added later
This changes the "relations created" event to ensure it is properly emitted even
if the target event is added to the timeline after the relation event. There was
perhaps always a risk of this happening in the past, but is seems more likely to
bite now with delayed decryption.

Part of https://github.com/vector-im/element-web/issues/17461
2021-05-26 16:35:49 +01:00
Hubert Chathi e73b969066 lint 2021-05-25 22:10:15 -04:00
Hubert Chathi 98e2154f0f fix unit tests 2021-05-25 21:59:08 -04:00
dependabot[bot] 2c0549a772 Bump browserslist from 4.16.1 to 4.16.6
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.16.1 to 4.16.6.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.16.1...4.16.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-25 21:59:49 +00:00
RiotRobot acb9bc8cc5 Resetting package fields for development 2021-05-24 17:14:02 +01:00
RiotRobot 6f44aa39e8 Merge branch 'master' into develop 2021-05-24 17:13:42 +01:00
RiotRobot c706618229 v11.1.0 2021-05-24 17:09:09 +01:00
RiotRobot c26b571b1c Prepare changelog for v11.1.0 2021-05-24 17:09:08 +01:00
J. Ryan Stinnett cb3075b084 Merge pull request #1707 from matrix-org/jryans/olm-3.2.3-release
[Release] Bump libolm version and update package name
2021-05-24 16:49:25 +01:00
Hubert Chathi 66fb21bd0b bump olm to 3.2.3 2021-05-24 16:27:56 +01:00
Hubert Chathi 498109bd53 update test too 2021-05-24 16:27:56 +01:00
Hubert Chathi 6711ab5f9a Bump libolm version and update package name. 2021-05-24 16:27:56 +01:00
J. Ryan Stinnett ac8fa58845 Merge pull request #1705 from uhoreg/olm_3.2.2
Bump libolm version and update package name.
2021-05-24 16:22:15 +01:00
Hubert Chathi 095f656998 bump olm to 3.2.3 2021-05-24 11:10:52 -04:00
Hubert Chathi 9e30c4f7dd update test too 2021-05-21 16:28:37 -04:00
Hubert Chathi 56a2eeac77 Bump libolm version and update package name. 2021-05-21 16:04:54 -04:00
Michael Telatynski d104f2f4a7 Merge pull request #1703 from matrix-org/t3chguy/uploadContent
Fix uploadContent not rejecting promise when http status code >= 400
2021-05-20 18:43:12 +01:00
Michael Telatynski e8367ad241 fix httpStatus being resolved when response is undefined 2021-05-20 18:33:17 +01:00
Michael Telatynski f17cd142d5 Fix uploadContent not rejecting promise when http status code >= 400 2021-05-20 15:43:22 +01:00
J. Ryan Stinnett e8edc554a6 Merge pull request #1702 from matrix-org/jryans/test-noise
Reduce noise in tests
2021-05-20 13:42:31 +01:00
J. Ryan Stinnett bd8aca83ac Reduce noise in tests
This disables a common log message to cut down the test log size and make it
easier to read messages specific to each test.
2021-05-20 13:41:54 +01:00
J. Ryan Stinnett 7ebc1cfac5 Merge pull request #1700 from matrix-org/matthew/kill-invite-logspam
Only log once if a Room lacks an m.room.create event
2021-05-20 12:02:32 +01:00
Germain 2422204d6a Merge pull request #1701 from matrix-org/gsouquet/cache-normalized-name
Cache normalized room name
2021-05-20 11:12:00 +01:00
Germain Souquet 6c98c3c662 Remove reference to room name in utils 2021-05-20 10:58:20 +01:00
Germain Souquet 1d1310f034 Cache normalized room name 2021-05-20 10:44:47 +01:00
J. Ryan Stinnett 841888c480 Merge pull request #1699 from matrix-org/jryans/call-enc-release
[Release] Change call event handlers to adapt to undecrypted events
2021-05-19 21:28:19 +01:00
Matthew Hodgson 52c031f160 Only log once if a Room lacks an m.room.create event
Currently my account is logging ~20,000 warnings a minute about getType() being called
on invites which have no m.room.create events, which crashes my inspector and makes
my console unusable.  We still want the logline though, as it helps diagnose
space invite problems.  So instead, log this only once.  (Untested).
2021-05-19 19:02:31 +01:00
Germain Souquet 155113b75d move startEventCallHandler to MatrixClient prototype 2021-05-19 18:16:20 +01:00
Germain Souquet 2a863025c6 listen to call event handlers when sync is prepared 2021-05-19 18:15:39 +01:00
Germain Souquet b6763ce89f Change call event handlers to adapt to undecrypted events 2021-05-19 18:14:28 +01:00
Germain f346cd6b8d Merge pull request #1698 from matrix-org/gsouquet/fix-call-handlers 2021-05-19 17:23:51 +01:00
Germain Souquet 0c47412c75 move startEventCallHandler to MatrixClient prototype 2021-05-19 17:15:44 +01:00
Germain Souquet ea1ef3dbec listen to call event handlers when sync is prepared 2021-05-19 17:05:20 +01:00
Germain Souquet 61fd62ff81 Merge branch 'develop' into gsouquet/fix-call-handlers 2021-05-19 16:51:29 +01:00
Germain Souquet 32197ea903 Change call event handlers to adapt to undecrypted events 2021-05-19 15:45:21 +01:00
RiotRobot 65de184d88 v11.1.0-rc.1 2021-05-19 14:29:51 +01:00
RiotRobot 52764045ce Prepare changelog for v11.1.0-rc.1 2021-05-19 14:29:50 +01:00
J. Ryan Stinnett 4b0db6c472 Guard against duplicates in Relations model
The `Relations` model was relying on object reference equality to prevent
duplicates, which breaks down if we ever have two objects that represent the
same event.

This fixes things to additionally track event IDs we've seen before and discard
any attempts to add them twice.

Fixes https://github.com/vector-im/element-web/issues/11161
2021-05-19 13:20:40 +01:00
Hubert Chathi 3ec89a89df fix some types 2021-05-18 18:40:36 -04:00
Hubert Chathi 9e6b72bf38 some linting 2021-05-18 18:31:19 -04:00
Hubert Chathi 747723c8fb factor out backup management to a separate module 2021-05-18 18:15:22 -04:00
Germain 40cd4629db Decrypt relations before applying them to target event (#1696) 2021-05-18 17:21:06 +01:00
RiotRobot 52a893a811 Resetting package fields for development 2021-05-17 13:32:49 +01:00
RiotRobot 7cc94e1e1e Merge branch 'master' into develop 2021-05-17 13:32:48 +01:00
RiotRobot 88945a6d6d v11.0.0 2021-05-17 13:30:08 +01:00
RiotRobot 7203c5aaf0 Prepare changelog for v11.0.0 2021-05-17 13:30:07 +01:00
J. Ryan Stinnett c305058c46 Merge pull request #1695 from matrix-org/jryans/glare-streams-release
[Release] Fix regressed glare
2021-05-17 11:55:25 +01:00
Šimon Brandner 91bbf7d1a8 Fix glare
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-17 11:20:14 +01:00
J. Ryan Stinnett 2d5857f145 Merge pull request #1690 from SimonBrandner/fix/glare/17250
Fix regressed glare
2021-05-17 11:11:39 +01:00
Travis Ralston 91af9a411d Null-guard power level object usage (#1694) 2021-05-14 08:36:44 +01:00
Germain 9a9ed124f5 Use native Object and Array methods (#1693) 2021-05-13 17:20:09 +01:00
Michael Telatynski 4e5442d972 Merge pull request #1692 from matrix-org/t3chguy/fix/i80
Add m.reaction to EventType enum
2021-05-13 12:44:42 +01:00
Michael Telatynski b5fa54f91e Add m.reaction to EventType enum 2021-05-13 10:57:02 +01:00
Germain 2246aede4b Merge pull request #1684 from matrix-org/gsouquet/cache-decrypt 2021-05-12 12:19:59 +01:00
Šimon Brandner 4b2d409c69 Fix glare
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-12 07:47:48 +02:00
RiotRobot 684511ff06 v11.0.0-rc.1 2021-05-11 15:03:04 +01:00
RiotRobot 88b328df7e Prepare changelog for v11.0.0-rc.1 2021-05-11 15:03:03 +01:00
Michael Telatynski e3583dd04e Merge pull request #1679 from matrix-org/t3chguy/spaces-stable
Switch from MSC1772 unstable prefixes to stable
2021-05-11 13:39:47 +01:00
Germain Souquet e484a2ebb5 add missing coma to appease linter 2021-05-11 13:02:42 +01:00
Germain Souquet 5caf05cfa1 Apply new linting rules 2021-05-11 11:25:43 +01:00
Germain Souquet 72f86258d0 Add explainer for awaitDecryption 2021-05-11 10:05:24 +01:00
Germain Souquet 874cb3b779 make attemptDecryption backwards compatible 2021-05-11 10:02:32 +01:00
Germain f21e0228b4 Update documentation wording
Co-authored-by: Travis Ralston <travpc@gmail.com>
2021-05-11 09:20:14 +01:00
Germain Souquet 01cd82bc6a undo test timeout 2021-05-10 18:12:47 +01:00
Germain Souquet d2a6a8b283 Merge branch 'develop' into gsouquet/cache-decrypt 2021-05-10 17:35:05 +01:00
Germain Souquet f242e460ed fix tests 2021-05-10 17:28:00 +01:00
Germain Souquet 95e08253a6 Appease linter 2021-05-10 16:59:54 +01:00
Germain Souquet 202a4fa6f1 Better document new room methods 2021-05-10 15:55:16 +01:00
Germain Souquet 576f46cb88 Add flag to prevent emitting event.decrypted 2021-05-10 15:25:07 +01:00
J. Ryan Stinnett 2d73805ca3 Merge pull request #1680 from SimonBrandner/feed-example
Update the VoIP example to work with the new changes
2021-05-10 14:50:18 +01:00
J. Ryan Stinnett eedfa550a6 Merge pull request #1687 from matrix-org/dependabot/npm_and_yarn/hosted-git-info-2.8.9
Bump hosted-git-info from 2.8.8 to 2.8.9
2021-05-10 14:48:34 +01:00
dependabot[bot] 4ca718adc4 Bump hosted-git-info from 2.8.8 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-10 13:46:58 +00:00
RiotRobot 7c4ced8f46 Resetting package fields for development 2021-05-10 14:46:14 +01:00
RiotRobot 7018f4ab25 Merge branch 'master' into develop 2021-05-10 14:46:13 +01:00
RiotRobot fda13875ef v10.1.0 2021-05-10 14:43:18 +01:00
RiotRobot 8005917452 Prepare changelog for v10.1.0 2021-05-10 14:43:17 +01:00
Germain Souquet 91eee8587e extract shouldAttemptDecryption to event model 2021-05-07 15:16:04 +01:00
Germain Souquet e9132abc25 Do not attempt to decrypt already clear events 2021-05-07 12:58:53 +01:00
Germain Souquet 444eac5c6e consolidate critical event decryption implementation 2021-05-07 11:23:59 +01:00
Šimon Brandner a449c5f8c2 Merge branch 'develop' into feed-example 2021-05-06 16:44:29 +02:00
Germain Souquet 19d6dbaa52 Use read receipt instead of read marker 2021-05-06 14:07:38 +01:00
Germain Souquet 8820619e82 Pass decrypt flag to event mapper 2021-05-06 12:34:21 +01:00
Germain Souquet fb33bc7e07 Lazily decrypt event on room view 2021-05-06 11:46:14 +01:00
J. Ryan Stinnett 57d1fa8410 Merge pull request #1660 from SimonBrandner/feed
Support for multiple streams (not MSC3077)
2021-05-06 11:19:52 +01:00
Michael Telatynski 838e38d84c Merge pull request #1683 from matrix-org/t3chguy/spaces-logs
Tweak missing m.room.create errors to describe their source
2021-05-06 00:06:38 +01:00
Michael Telatynski fc29056530 Tweak missing m.room.create errors to describe their source 2021-05-05 23:58:40 +01:00
RiotRobot 4e967c979c v10.1.0-rc.1 2021-05-04 15:37:55 +01:00
RiotRobot 62a34848c7 Prepare changelog for v10.1.0-rc.1 2021-05-04 15:37:54 +01:00
Travis Ralston 01fe6cc542 Merge pull request #1681 from matrix-org/revert-1678-travis/event-logging
Revert "Raise logging dramatically to chase pending event errors"
2021-05-02 19:11:06 -06:00
Travis Ralston 3fdc25777d Revert "Raise logging dramatically to chase pending event errors" 2021-05-02 19:10:47 -06:00
Šimon Brandner 3b7d6f8334 This check doesn't seem to be necessary
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-30 14:43:00 +02:00
Šimon Brandner 4b3c8b2969 Update the example to work with the new feed code
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-30 14:18:20 +02:00
Šimon Brandner ad80d69369 Add some basic styling
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-30 14:17:30 +02:00
Michael Telatynski e2d2249686 Switch from MSC1772 unstable prefixes to stable 2021-04-30 11:33:45 +01:00
J. Ryan Stinnett dc64c34ccb Merge pull request #1677 from matrix-org/jryans/coverage
Add test coverage collection script
2021-04-28 17:18:07 +01:00
Travis Ralston 0bf50659ab Merge pull request #1678 from matrix-org/travis/event-logging
Raise logging dramatically to chase pending event errors
2021-04-28 09:30:04 -06:00
Travis Ralston ec26b16ddf Raise logging dramatically to chase pending event errors
For https://github.com/vector-im/element-web/issues/17090 and similar issues

This logging is expected to exist no longer than a day.
2021-04-28 09:24:19 -06:00
J. Ryan Stinnett a044b74a1d Add test coverage collection script
This makes it clear to how collect basic test coverage when desired.
2021-04-28 14:17:08 +01:00
J. Ryan Stinnett 1e7a1dce90 Move Jest options into config block 2021-04-28 12:58:39 +01:00
Šimon Brandner 41c2772cff Merge branch 'develop' into feed
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-28 10:57:59 +02:00
David Baker 1bba2bc0ed Merge pull request #1674 from matrix-org/dbkr/asserted_identity
Support MSC3086 asserted identity
2021-04-28 09:47:30 +01:00
Šimon Brandner e11c523a75 Add getLocalFeeds() and getRemoteFeeds()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-27 10:06:21 +02:00
RiotRobot b911a890cf Add breaking notice 2021-04-26 18:03:19 +01:00
RiotRobot c8f69c0b79 Resetting package fields for development 2021-04-26 17:37:06 +01:00
RiotRobot 8a6248f120 Merge branch 'master' into develop 2021-04-26 17:37:06 +01:00
RiotRobot 340fa6c63e v10.0.0 2021-04-26 17:34:17 +01:00
RiotRobot 1177bf39a2 Prepare changelog for v10.0.0 2021-04-26 17:34:16 +01:00
Šimon Brandner 88b310c394 Rename stuff to make it easy to read
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-26 16:00:17 +02:00
Šimon Brandner 973de2db55 stopAllMedia() before deleteAllFeeds()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-24 12:56:28 +02:00
Šimon Brandner 1fe92f10c1 Delint
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-24 12:26:54 +02:00
Šimon Brandner 0e2e906b24 Remove remoteStream prop
This is done in order to be more generic

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-24 12:24:15 +02:00
Šimon Brandner 4667f8be03 Use feeds in stopAllMedia()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-24 12:08:45 +02:00
David Baker 4a51ac7a74 Move createNewMatrixCall to the client object
So we can mock it out it tests (and also I'm not sure why it was
like this in the first place: we passed the client in anyway...)

Deprecate createNewMatrixCall
2021-04-23 14:36:56 +01:00
J. Ryan Stinnett 6099efe41a Merge pull request #1670 from timokoesters/fix-undefined-results
Fix `/search` with no results field work again
2021-04-23 13:02:52 +01:00
Germain 4254d595fc Merge pull request #1672 from hannojg/patch-5 2021-04-22 09:27:22 +01:00
Šimon Brandner e4fbbd56a9 Improve wording
Co-authored-by: Travis Ralston <travpc@gmail.com>
2021-04-22 07:36:53 +02:00
Timo Kösters 069ca4a89d fix: make /search with no results field work again 2021-04-21 22:56:36 +02:00
Šimon Brandner 4290e8e56b Merge branch 'develop' into disambiguate-prop 2021-04-21 18:07:50 +02:00
RiotRobot e3ba08fbbc v10.0.0-rc.1 2021-04-21 16:40:23 +01:00
RiotRobot dd84e51161 Prepare changelog for v10.0.0-rc.1 2021-04-21 16:40:22 +01:00
David Baker bca8568d64 missing semicolon 2021-04-20 12:58:33 +01:00
David Baker 5407717534 Assert event emitted 2021-04-20 12:57:11 +01:00
David Baker 74ef760591 Tests: They find bugs 2021-04-20 12:48:54 +01:00
Germain b435b582bd Merge pull request #1675 from matrix-org/gsouquet-powerlevels-perf
Restrict event emit for room members that had power levels changed
2021-04-20 11:57:18 +01:00
Germain Souquet d46021a05e Restrict event emit for room members that had power levels changed 2021-04-20 11:22:16 +01:00
David Baker 1b31d0622e Unit test for asserted identity messages 2021-04-20 11:08:06 +01:00
Šimon Brandner a416fd562b Oops - remove log
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-20 11:11:33 +02:00
Šimon Brandner 323a096dd7 Merge branch 'disambiguate-prop' of https://github.com/SimonBrandner/matrix-js-sdk into disambiguate-prop 2021-04-20 11:01:20 +02:00
Šimon Brandner 628dd7bf41 Return false by default
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-20 11:01:13 +02:00
Šimon Brandner 71c6d71cae Fix docs
Co-authored-by: Jonathan de Jong <jonathandejong02@gmail.com>
2021-04-20 10:53:39 +02:00
Šimon Brandner e049edd449 Add docs
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-20 10:44:17 +02:00
Michael Telatynski 68206a6e19 Merge pull request #1669 from hannojg/patch-3
Fix sync with misconfigured push rules
2021-04-20 08:57:16 +01:00
David Baker 56797948af lint 2021-04-19 20:36:00 +01:00
David Baker c0af2f25a1 Support MSC3086 asserted identity 2021-04-19 20:28:42 +01:00
Šimon Brandner 0fdfc3ff53 Rework how disambiguation is handled
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-18 21:01:53 +02:00
Hanno J. Gödecke dc12b1df00 feat: room.getMembers
Signed-off-by: Hanno Gödecke <hgoedecke@cuvent.com>
2021-04-18 16:01:51 +02:00
Hanno J. Gödecke b13f5aebfd lint 2021-04-17 09:42:44 +02:00
Hanno J. Gödecke 338301bb5d fix: failure during sync 2021-04-17 09:34:31 +02:00
Šimon Brandner 72a0931663 Remove comment: // Fix when client is TSified
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-16 11:38:01 +02:00
Šimon Brandner 67584b9cc3 Merge branch 'feed' of https://github.com/SimonBrandner/matrix-js-sdk into feed 2021-04-16 11:10:22 +02:00
Šimon Brandner 1bfaa28f9c Fix missing types
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-16 11:09:28 +02:00
Hubert Chathi cbe9b59222 Merge pull request #1665 from uhoreg/dehydrated_device_missing_await
Add missing await
2021-04-14 10:47:09 -04:00
Hubert Chathi 276b52f0fe add missing await 2021-04-14 10:29:39 -04:00
Šimon Brandner 07f49bcc37 Merge branch 'develop' into feed 2021-04-13 12:33:23 +02:00
J. Ryan Stinnett 09fac77ce0 Merge pull request #1642 from matrix-org/jryans/rework-linting
Migrate to `eslint-plugin-matrix-org`
2021-04-13 11:12:39 +01:00
J. Ryan Stinnett 102704e91a Migrate to eslint-plugin-matrix-org
This migrates to the new plugin form of our custom ESLint configs. As part of
this, some packages are de-duplicated, configs streamlined, etc.
2021-04-13 11:00:41 +01:00
Michael Telatynski c5fb351baa Merge pull request #1664 from matrix-org/t3chguy/fix/8665
Add missing event type enum for key verification done
2021-04-13 10:05:00 +01:00
Michael Telatynski 98f8d4414d Add missing event type enum for key verification done 2021-04-13 09:41:53 +01:00
Germain 1dddcd4925 Merge pull request #1663 from matrix-org/gsouquet-timeline-jumpiness 2021-04-12 15:00:49 +01:00
Germain Souquet 2666a271a5 fix tests when pendingEventsList does not exist 2021-04-12 14:51:45 +01:00
RiotRobot e277de6e3d Resetting package fields for development 2021-04-12 14:42:29 +01:00
RiotRobot daa17b3287 Merge branch 'master' into develop 2021-04-12 14:42:29 +01:00
RiotRobot c7f887131e v9.11.0 2021-04-12 14:39:33 +01:00
RiotRobot 58546b80d0 Prepare changelog for v9.11.0 2021-04-12 14:39:32 +01:00
Germain Souquet 466f749b71 fix typo 2021-04-12 14:38:27 +01:00
Germain Souquet 4c2a83c470 Add explanation for events persistence 2021-04-12 14:29:10 +01:00
Germain Souquet d534ab18c7 fix event filtering logic 2021-04-12 13:07:57 +01:00
Germain Souquet 64aaed833b Only persist encrypted events for encrypted rooms 2021-04-12 12:53:27 +01:00
Germain Souquet 2f05be599c undo changes to event#toJSON and persist encrypted events 2021-04-12 12:26:27 +01:00
Germain Souquet 7371f8dd3a Persist txnId to ensure idempotency 2021-04-12 09:55:01 +01:00
Germain Souquet 79aefe9707 Fix timeline jumpiness by setting correct txnId 2021-04-12 09:37:19 +01:00
Michael Telatynski 6bc80577ee Merge pull request #1661 from janpawellek/janpawellek-fix-addEventListener
Fix calling addEventListener if it does not exist
2021-04-12 09:15:19 +01:00
Germain 837764190f Merge pull request #1655 from matrix-org/gsouquet-persist-unsent-messages 2021-04-09 17:48:01 +01:00
Germain Souquet 61948d70e3 Merge branch 'develop' into gsouquet-persist-unsent-messages 2021-04-09 17:05:19 +01:00
Jan Pawellek 9fb0385694 Fix calling addEventListener if it does not exist
Some platforms (e.g. React Native) register global.window, but do not have global.window.addEventListener. In this case, this function should not be called.
2021-04-09 10:18:47 +02:00
Šimon Brandner 193de8d3a9 Merge branch 'feed' of https://github.com/SimonBrandner/matrix-js-sdk into feed 2021-04-08 11:15:07 +02:00
Šimon Brandner 1f2b3512c1 Move SDPStreamMetadataPurpose into callEventTypes.ts
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-08 11:14:42 +02:00
Šimon Brandner ddc5bd3b36 Add prefixes to SDPStreamMetadataPurpose
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-08 11:07:53 +02:00
Šimon Brandner ee828d454a Rename CallFeedPurpose to SDPStreamMetadataPurpose
This is to match MSC3077

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-08 11:07:22 +02:00
Šimon Brandner 2916a73f4c Make selectDesktopCapturerSource optional 2021-04-08 11:01:17 +02:00
Šimon Brandner ae69af7e70 Merge branch 'develop' into feed 2021-04-07 19:14:17 +02:00
RiotRobot 1c6459fe65 v9.11.0-rc.1 2021-04-07 12:55:25 +01:00
RiotRobot adaeb42416 Prepare changelog for v9.11.0-rc.1 2021-04-07 12:55:24 +01:00
J. Ryan Stinnett f1e1daa194 Merge pull request #1657 from matrix-org/jryans/cs-keys-test
Only try to cache private keys we know exist
2021-04-06 10:09:04 +01:00
J. Ryan Stinnett 401e89ef78 Only try to cache private keys we know exist
This tweaks https://github.com/matrix-org/matrix-js-sdk/pull/1649 to only try
caching private keys locally once we've confirmed they exist first. This is most
likely only an issue in tests, where we sometimes create only some but not all
keys.
2021-04-06 09:49:20 +01:00
Travis Ralston 59e0bd467c Merge pull request #1654 from SimonBrandner/terminate-screen-share
Properly terminate screen-share calls if NoUserMedia
2021-04-05 10:55:02 -06:00
Šimon Brandner 7f4397f8ca Use getAudioTracks() and getVideoTracks()
This is much nicer. Before I hadn't realized this was possible

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-05 10:31:27 +02:00
Šimon Brandner 32830b93f1 Rename audioOnly to videoMuted
This makes more sense and will match a possible mute events MSC

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-04 08:50:27 +02:00
Šimon Brandner cdc0d5623b Rename to match MSC3077
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-04 08:37:09 +02:00
Šimon Brandner e78b415832 Add getMember() to CallFeed
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-04-04 08:33:51 +02:00
Šimon Brandner ff1379fd29 Merge branch 'develop' into feed 2021-04-03 08:35:05 +02:00
David Baker 3820c15ecf Merge pull request #1652 from matrix-org/dbkr/attended_transfer
Attended transfer
2021-04-01 17:34:18 +01:00
Germain Souquet 26ef33e4f3 No this assign 2021-04-01 17:29:21 +01:00
Germain Souquet 0534a4ed1b prevent removePendingEvent being called when not in detached mode 2021-04-01 17:23:55 +01:00
Germain Souquet f29a24a915 specify TestClient when testing room model 2021-04-01 15:22:13 +01:00
Germain Souquet cecbcd941e Persist unsent messages for subsequent sessions 2021-04-01 10:59:16 +01:00
David Baker 6be99d6397 Terminate the other call too 2021-03-30 12:13:28 +01:00
J. Ryan Stinnett 4e5947af51 Merge pull request #1653 from matrix-org/jryans/cancel-security-key
Remove catch handlers in private key retrieval
2021-03-29 17:58:07 +01:00
RiotRobot 4204b2170a Resetting package fields for development 2021-03-29 13:28:09 +01:00
RiotRobot 0a5ad489b6 Merge branch 'master' into develop 2021-03-29 13:28:08 +01:00
RiotRobot 5de34a5c99 v9.10.0 2021-03-29 13:25:27 +01:00
RiotRobot 08da6b8800 Prepare changelog for v9.10.0 2021-03-29 13:25:27 +01:00
Šimon Brandner 02b283be78 Properly terminate screenshare calls if NoUserMedia
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-27 09:13:44 +01:00
J. Ryan Stinnett 10c49c0fd1 Remove catch handlers in private key retrieval
This removes some catch blocks originally added by
https://github.com/matrix-org/matrix-js-sdk/pull/1472 so that higher level
operations can handle them as needed.

Part of https://github.com/vector-im/element-web/issues/15584
2021-03-26 17:57:23 +00:00
David Baker 9ecc0f5d95 Terminate calls once we've replaced them 2021-03-26 13:52:23 +00:00
Travis Ralston 972b59b99e Merge pull request #1651 from DantrazTrev/CallErrorFix
Fixed the media fail error on caller's side
2021-03-25 19:05:36 -06:00
David Baker 34bb05bd88 WIP attended transfer 2021-03-25 19:57:20 +00:00
Hubert Chathi 37fb21f726 Merge pull request #1640 from uhoreg/room-history-key-sharing2
Add function to share megolm keys for historical messages.
2021-03-25 14:18:12 -04:00
Ayush PS b42efa4a07 Fixed lint errors 2021-03-25 23:38:18 +05:30
Ayush PS 20b20738a7 Fixed a slight error back to orignal in ScreenShareCall 2021-03-25 23:02:44 +05:30
Ayush PS b28a191c4e Fixed the media fail error on caller's side 2021-03-25 22:43:18 +05:30
Hubert Chathi f92b620434 Merge branch 'develop' into room-history-key-sharing2 2021-03-25 12:24:42 -04:00
RiotRobot ae6e2cca27 v9.10.0-rc.1 2021-03-25 12:06:34 +00:00
RiotRobot bd920eef1f Prepare changelog for v9.10.0-rc.1 2021-03-25 12:06:34 +00:00
J. Ryan Stinnett bf25cb68da Merge pull request #1649 from matrix-org/jryans/get-keys-bootstrap-only
Cache cross-signing private keys if needed on bootstrap
2021-03-24 15:56:15 +00:00
J. Ryan Stinnett 0b063f6b8b Cache cross-signing private keys if needed on bootstrap
This is a revised version of
https://github.com/matrix-org/matrix-js-sdk/pull/1472 which was previously
reverted for causing security prompts to appear on device list sync. In this
version, we only allow private key requests (which are likely to trigger user
dialogs) if we are coming from the bootstrap path.

This allows sessions that have already synced cross-signing public keys but
never got the private keys for some reason to make forward progress when e.g.
the user triggers bootstrap from security settings.
2021-03-24 11:48:45 +00:00
Travis Ralston ed6d4e5f6c Merge pull request #1647 from SimonBrandner/dont-send-hangup
Don't send m.call.hangup if m.call.invite wasn't sent either
2021-03-22 00:42:04 -06:00
Travis Ralston accfa325b5 Merge pull request #1641 from NicolaiSoeborg/fix-registerGuest
docs: registerGuest()
2021-03-21 20:37:49 -06:00
Šimon Brandner b6ef8d95cd Don't send hangup if invite wasn't sent
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-21 21:29:00 +01:00
Travis Ralston c1144e3810 Merge pull request #1639 from Johennes/feature/chunk-device-keys
Download device keys in chunks of 250
2021-03-18 13:17:15 -06:00
Johannes Marbach 8663fd402b Download device keys in chunks of 250
Depending on the number of users in the request, the server might
overload. To prevent this, the download is broken into chunks of
250 users each. Additionally, no more than 3 requests are kicked off
at the same time to avoid running into rate limiting. Responses are
processed once all chunks have been downloaded.

Fixes: #1619

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2021-03-17 20:56:25 +01:00
Šimon Brandner d8134aa168 Merge branch 'feed' into feed-audio 2021-03-17 16:16:33 +01:00
Šimon Brandner 702b3e8473 Merge branch 'develop' into feed
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-17 16:16:07 +01:00
David Baker f34052fd31 Merge pull request #1646 from matrix-org/dbkr/more_call_fixes
More VoIP connectivity fixes
2021-03-17 15:00:09 +00:00
David Baker 27d75a269f unintentional comment 2021-03-16 19:18:45 +00:00
David Baker d208a7fc5f Remove unintentionally committed stuff 2021-03-16 19:17:04 +00:00
David Baker 702e16e3df More VoIP connectivity fixes
* Don't ignore other candidates when we see a null one (continue
   rather than return)
 * await on addICECandidate()
 * Don't add ice candidates until we've set a remote description
 * More & better logging
2021-03-16 19:13:03 +00:00
Hubert Chathi 6381018658 add jsdoc and implementation for memory crypto store 2021-03-16 13:52:05 -04:00
Travis Ralston 12050b14f0 Merge pull request #1644 from SimonBrandner/fix-optional
Make selectDesktopCapturerSource param optional
2021-03-15 21:12:03 -06:00
Hubert Chathi 1c191b2278 use new terminology and field name from MSC 2021-03-15 22:49:43 -04:00
Nicolai Søborg 2d4a4f1736 docs: registerGuest 2021-03-15 22:46:43 +00:00
RiotRobot cd38fb9b4c Resetting package fields for development 2021-03-15 14:34:56 +00:00
RiotRobot 7941b16ec4 Merge branch 'master' into develop 2021-03-15 14:34:56 +00:00
RiotRobot 3ff517e76e v9.9.0 2021-03-15 14:31:58 +00:00
RiotRobot 9559b26310 Prepare changelog for v9.9.0 2021-03-15 14:31:57 +00:00
Šimon Brandner 56ea4b8741 Make selectDesktopCapturerSource param optional
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-13 15:03:36 +01:00
Hubert Chathi a489691151 various fixes 2021-03-11 17:30:05 -05:00
Nicolai Søborg 6dabfcda6f setGuest(true) when registerGuest()
Signed-off-by: Nicolai Søborg <git@xn--sb-lka.org>
2021-03-11 22:39:51 +01:00
J. Ryan Stinnett 0b7b35f800 Merge pull request #1632 from matrix-org/matthew/rework-cross-signing-login
Expose APIs needed for reworked cross-signing login flow
2021-03-11 12:54:18 +00:00
Hubert Chathi 0bfcb5071d fix test, lint 2021-03-10 20:04:34 -05:00
Hubert Chathi ceb162eb01 initial work on room history key sharing, take 2 2021-03-10 19:51:22 -05:00
RiotRobot 0ffdf7c0f1 v9.9.0-rc.1 2021-03-10 17:21:50 +00:00
RiotRobot 13b6db8eb4 Prepare changelog for v9.9.0-rc.1 2021-03-10 17:21:49 +00:00
J. Ryan Stinnett 481acb2a1a Merge pull request #1638 from matrix-org/jryans/rm-olm-profiling
Remove detailed Olm session logging
2021-03-10 12:44:22 +00:00
J. Ryan Stinnett 683092140d Remove OTK claim timeout logging 2021-03-10 12:43:45 +00:00
J. Ryan Stinnett 1bb8c2d1a5 Remove detailed Olm session logging
Now that we understand the Olm session deadlock, we shouldn't need this detailed
per-session logging.

Fixes https://github.com/vector-im/element-web/issues/16647
2021-03-10 12:43:45 +00:00
J. Ryan Stinnett 60fd3b0786 Remove extra space in log message 2021-03-10 11:25:44 +00:00
Šimon Brandner b307a177f4 Remove handling of audio from MatrixCall
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-10 12:24:48 +01:00
Šimon Brandner 059430bd0a Doc public methods
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-10 12:21:15 +01:00
Šimon Brandner 530b60cbc2 Make MatrixCall use CallFeed
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-10 12:21:07 +01:00
J. Ryan Stinnett cd4abc4e9b Disable crypto transaction profiling 2021-03-10 11:05:17 +00:00
Michael Telatynski e6a21cc487 Merge pull request #1637 from matrix-org/t3chguy/spaces4.5
Add space summary suggested only param
2021-03-10 10:55:27 +00:00
Šimon Brandner ba8577f268 Add CallFeed class
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-10 08:43:01 +01:00
David Baker 6c5fc153bf Merge pull request #1634 from matrix-org/dbkr/check_turn_interval
Check TURN servers periodically, and at start of calls
2021-03-09 17:06:02 +00:00
David Baker 07f15b41a2 Don't start the timer if voip not supported 2021-03-09 14:39:21 +00:00
David Baker 8375638d76 Fix tests
Bit of a re-organisation so a peerconnection exists when the tests
go to mock things out. placeCall methods return promises to make this
possible.
2021-03-09 14:09:55 +00:00
J. Ryan Stinnett bed7543b46 Merge pull request #1624 from robintown/invite-reasons
Support sending invite reasons
2021-03-09 11:44:04 +00:00
Travis Ralston dc55236263 Merge pull request #1636 from matrix-org/dependabot/npm_and_yarn/elliptic-6.5.4
Bump elliptic from 6.5.3 to 6.5.4
2021-03-08 18:23:50 -07:00
dependabot[bot] 5f3427c5d1 Bump elliptic from 6.5.3 to 6.5.4
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-09 01:23:08 +00:00
Travis Ralston 66e5af185d Merge pull request #1635 from matrix-org/travis/media-customization
Add a function to get a room's MXC URI
2021-03-08 18:23:00 -07:00
Travis Ralston 0ff611e033 Enum and linter 2021-03-08 18:16:39 -07:00
Travis Ralston 737cadaabc Add a function to get a room's MXC URI
This matches the RoomMember function of the same name.
2021-03-08 18:13:14 -07:00
Matthew Hodgson 9fb2fbaeec factor out getDehydratedDevice 2021-03-09 00:09:22 +00:00
David Baker 51e817a3a2 This is in ms, not seconds 2021-03-08 18:54:50 +00:00
David Baker 59c93b59bf Check TURN servers periodically, and at start of calls
Hopefully this should make our turn-credential checking code a bit
more robust (and possibly fix a seconds / ms mismatch).
2021-03-08 18:49:25 +00:00
David Baker c18ef051fc Merge pull request #1633 from matrix-org/dbkr/stop_streams_if_call_ended
Stop streams if the call has ended
2021-03-08 17:01:31 +00:00
David Baker 1ac5c9acbd Stop streams if the call has ended
When we get user media, don't forget to close the streams if the
call's ended by the time we got media.
2021-03-08 16:55:48 +00:00
J. Ryan Stinnett a034ca171e Merge pull request #1631 from SimonBrandner/remove-export
Remove export keyword from global.d.ts
2021-03-08 14:11:37 +00:00
Matthew Hodgson 977682d37f fix lint 2021-03-08 09:24:25 +00:00
Matthew Hodgson 0bafe263d7 fix lint 2021-03-08 05:05:14 +00:00
Matthew Hodgson 1a8fced80e Merge branch 'develop' into matthew/rework-cross-signing-login 2021-03-08 04:59:40 +00:00
Matthew Hodgson 1c4d0b5e99 expose getDevice API 2021-03-08 04:59:29 +00:00
Matthew Hodgson 844a2b457c expose getDehydratedDevice API 2021-03-08 04:59:19 +00:00
Matthew Hodgson ccf06f2216 don't cancel ourselves when selecting a self-verification partner 2021-03-08 04:58:55 +00:00
Šimon Brandner f630a9f297 Remove export
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-06 16:14:12 +01:00
Michael Telatynski 2f71c93b53 Add space summary suggested only param 2021-03-05 16:12:39 +00:00
J. Ryan Stinnett 92032a17a8 Merge pull request #1445 from florianjacob/patch-1
Fix IndexedDB store creation example
2021-03-04 16:28:05 +00:00
David Baker e531456d42 Merge pull request #1613 from SimonBrandner/constraint-cleanup
An attempt to  cleanup how constraints are handled in calls
2021-03-03 15:03:53 +00:00
Šimon Brandner f0b2d2fe4d Null-check screenshareConstraints
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-03 15:38:49 +01:00
Šimon Brandner 427500220d Remove AudioVideo ConstraintsType
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-03 15:35:02 +01:00
Šimon Brandner 32e19ead74 Merge branch 'develop' into constraint-cleanup 2021-03-03 15:30:46 +01:00
J. Ryan Stinnett d11adb6f43 Merge pull request #1628 from matrix-org/jryans/opt-display-name
Extract display name patterns to constants
2021-03-03 11:44:30 +00:00
Travis Ralston f6155a50f6 Merge pull request #1630 from matrix-org/dependabot/npm_and_yarn/pug-code-gen-2.0.3
Bump pug-code-gen from 2.0.2 to 2.0.3
2021-03-02 21:55:39 -07:00
dependabot[bot] 4efee9445d Bump pug-code-gen from 2.0.2 to 2.0.3
Bumps [pug-code-gen](https://github.com/pugjs/pug) from 2.0.2 to 2.0.3.
- [Release notes](https://github.com/pugjs/pug/releases)
- [Commits](https://github.com/pugjs/pug/compare/pug-code-gen@2.0.2...pug@2.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-03 03:35:37 +00:00
J. Ryan Stinnett 20746a433f Extract display name patterns to constants
This changes to constant patterns for calculating display names, which cuts out
~18% of the time spent and reduces GC pressure as well.
2021-03-02 17:52:57 +00:00
J. Ryan Stinnett 31dacc4206 Merge pull request #1627 from matrix-org/jryans/olm-session-deadlock
Avoid deadlocks when ensuring Olm sessions for devices
2021-03-02 14:35:04 +00:00
J. Ryan Stinnett 88e5c59a85 Fix lint warning on OTK result variable 2021-03-02 13:03:12 +00:00
J. Ryan Stinnett cf74920b36 Remove redundant Olm session in progress deletion
This removes extra steps that duplicated deletion of an in progress Olm session.
Resolving the promise handles removing the session from the in progress set, so
there's no need to do it again. There's also no need to delete from
`resolveSession`, as it's okay to resolve a promise multiple times.
2021-03-02 12:58:09 +00:00
J. Ryan Stinnett 972c900b58 Remove unused support for rejecting in progress Olm sessions
This removes the unused `reject` path for in progress Olm sessions to simplify
understanding the code.
2021-03-02 12:55:43 +00:00
J. Ryan Stinnett 12d5fd79f7 Avoid deadlocks when ensuring Olm sessions for devices
This reworks tracking the Olm sessions a particular task is updating to avoid
deadlocks. By ensuring we synchronously mark all sessions a task cares about as
in progress from the start, we know that no other tasks will own updating a
session in common, which avoids deadlocks across multiple tasks that might be
working on a shared set of devices.

Fixes https://github.com/vector-im/element-web/issues/16194
2021-03-02 12:50:49 +00:00
J. Ryan Stinnett a29f6979b2 Merge pull request #1626 from matrix-org/jryans/replacement-senders
Filter out edits from other senders in history
2021-03-02 12:33:21 +00:00
J. Ryan Stinnett 3a7146c77b Only log claim timeouts when a time was provided
This avoids logging immediately on various code paths (including tests) where no
timeout value is supplied.
2021-03-02 12:22:58 +00:00
J. Ryan Stinnett b178d8f629 Filter out edits from other senders in history
We currently don't support edits from other senders, but the server may not
filter them, so we filter them here on the client.
2021-03-02 12:15:27 +00:00
Šimon Brandner 0c94ee62a3 Pass in selectDesktopCapturerSource()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-02 13:00:57 +01:00
Šimon Brandner e7562898cd Add getScreenshareContraints()
This is nicer since we avoid some async functions

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-02 12:58:45 +01:00
RiotRobot fb73ab6878 Resetting package fields for development 2021-03-01 12:48:30 +00:00
RiotRobot 38f978791d Merge branch 'master' into develop 2021-03-01 12:48:30 +00:00
RiotRobot 5dd60de57d v9.8.0 2021-03-01 12:44:55 +00:00
RiotRobot 5efbfc2dba Prepare changelog for v9.8.0 2021-03-01 12:44:54 +00:00
J. Ryan Stinnett fcd1dbad89 Merge pull request #1618 from robintown/fix-content-helpers-export
Fix ContentHelpers export
2021-03-01 11:13:10 +00:00
J. Ryan Stinnett ad521bf4c2 Merge pull request #1621 from matrix-org/jryans/megolm-logs-2021-02-26
Add logging to in progress Olm sessions
2021-02-28 17:52:24 +00:00
J. Ryan Stinnett 8152fa44e0 Add more logging scopes to session IDs
This uses prefix chaining to correlate several scopes together.
2021-02-28 17:15:07 +00:00
J. Ryan Stinnett e217bf9e37 Enable prefixed loggers to chain 2021-02-28 17:15:07 +00:00
David Baker bfad21f811 Merge pull request #1623 from matrix-org/dbkr/ice_candidate_buffer
Don't ignore ICE candidates received before offer/answer
2021-02-27 15:11:26 +00:00
David Baker 81e68abce3 Merge pull request #1622 from matrix-org/dbkr/candidate_retries
Better handling of send failures on VoIP events
2021-02-27 15:11:00 +00:00
David Baker 7963bb352d Merge pull request #1620 from matrix-org/dbkr/log_turn_cred_expiry
Log when turn creds expire
2021-02-27 15:09:54 +00:00
Michael Telatynski 14d3882059 Merge pull request #1563 from matrix-org/t3chguy/spaces
Initial Spaces [MSC1772] support
2021-02-26 22:08:09 +00:00
Robin Townsend dede508e89 Support sending invite reasons
Added as the final argument to `invite` in order to keep backwards
compatibility.

Signed-off-by: Robin Townsend <robin@robin.town>
2021-02-26 16:46:18 -05:00
David Baker ea39b69f65 Don't ignore ICE candidates received before offer/answer
The main bug here was a race on the callee side because we await-ed
on setRemoteDescription before setting the opponent party ID, and
while we were await-ing, the callEventHandler could give us candidate
events which we'd duly ignore because we thought the party ID didn't
match.

This also meant that any candidates that arrived before the answer
would have been ignored. Save these up by party ID and then add the
ones from the party ID that we pick once the answer comes in.

Also fix the confusion on party IDs where we weren't sure whether
we hadn't picked an opponent or we'd picked an opponent without a
party ID. It's now undefined for the former and null for the latter,
as it claims to be in the comment.
2021-02-26 21:25:52 +00:00
David Baker eafecd36bc Better handling of send failures on VoIP events
Don't leave candidate message sin the queue, abort if we fail to
send the invite.
2021-02-26 18:42:05 +00:00
J. Ryan Stinnett 198c9a2507 Add logging to in progress Olm sessions
It seems like this might be where
https://github.com/vector-im/element-web/issues/16194 is deadlocking.
2021-02-26 17:27:06 +00:00
David Baker d07563013b Log when turn creds expire 2021-02-26 14:47:27 +00:00
Michael Telatynski 9e967832cd Update space summary API unstable prefix 2021-02-26 10:37:09 +00:00
Michael Telatynski bfe1987cd9 Add Spaces event types from MSC1772 2021-02-26 10:35:02 +00:00
Robin Townsend 1045538f1f Fix ContentHelpers export
This was previously exporting a promise, since it called the import
function manually but didn't await the result. However, since we have
Babel we can just use the new export … as … from syntax instead.

Signed-off-by: Robin Townsend <robin@robin.town>
2021-02-25 14:41:48 -05:00
J. Ryan Stinnett fccf08edcf Merge pull request #1617 from matrix-org/jryans/crypto-store-logging
Add logging to crypto store transactions
2021-02-25 16:58:45 +00:00
J. Ryan Stinnett f43fe366b5 Add logging to crypto store transactions
We churn through a huge number of crypto store transactions during startup,
which may be the cause of the symptoms in
https://github.com/vector-im/element-web/issues/16194.
2021-02-25 16:49:49 +00:00
Michael Telatynski 0f75f2ef9c Add base API for Space Summary MSC2946 2021-02-25 13:12:22 +00:00
Michael Telatynski 2cdc68f9c3 Merge pull request #1610 from matrix-org/t3chguy/spaces2
Room helper for getting type and checking if it is a space room
2021-02-25 11:30:49 +00:00
RiotRobot 6a7d58e22e v9.8.0-rc.1 2021-02-24 17:24:17 +00:00
RiotRobot 203829c1cd Prepare changelog for v9.8.0-rc.1 2021-02-24 17:24:16 +00:00
J. Ryan Stinnett b55e6c4ef0 Merge pull request #1615 from matrix-org/jryans/megolm-logs-2021-02-22
Optimise prefixed logger
2021-02-23 17:46:57 +00:00
J. Ryan Stinnett 8d779e8aec Optimise prefixed logger
Tweak the prefixed logger to only do the setup work the first time.
2021-02-23 16:32:10 +00:00
J. Ryan Stinnett dd1d48f688 Merge pull request #1614 from matrix-org/jryans/megolm-logs-2021-02-22
Add debug logs to encryption prep, take 3
2021-02-23 16:07:39 +00:00
J. Ryan Stinnett 8d14dc9ee3 Add debug logs to encryption prep, take 3
This continues adding more logs to work out the root cause of
https://github.com/vector-im/element-web/issues/16194.

Somehow, we're getting stuck while sharing keys with new sessions.
2021-02-23 14:22:44 +00:00
Šimon Brandner 5849ea8e63 Add AudioVideo constraint type
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-02-23 12:25:20 +01:00
Šimon Brandner 20afebf339 Set video to true
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-02-23 12:23:49 +01:00
Šimon Brandner 20eaba191e Simplifie placeScreenSharingCall()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-02-23 11:12:16 +01:00
Šimon Brandner ba58d3c544 Add screenshare type to getUserMediaContraints()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-02-23 10:47:43 +01:00
David Baker a8b9d8e3ae Merge pull request #1612 from matrix-org/dbkr/jitsi_conference_captialised
Add functions for upper & lowercase random strings
2021-02-22 17:33:35 +00:00
David Baker 83d1e61b2f Add functions for upper & lowercase random strings 2021-02-22 16:47:16 +00:00
Michael Telatynski 8e0fc8d460 Room helper for getting type and checking if it is a space room 2021-02-19 14:21:22 +00:00
Michael Telatynski f547fa732f Merge pull request #1609 from matrix-org/t3chguy/spaces1
Room helpers for invite permissions and join rules
2021-02-18 18:02:31 +00:00
Michael Telatynski e24b1519a4 Merge pull request #1606 from SimonBrandner/fix-log
Fixed wording in "Adding video track with id" log
2021-02-18 18:00:28 +00:00
Michael Telatynski 3028fe9c87 Improve room documentation 2021-02-18 14:52:32 +00:00
Michael Telatynski 0b970b05b6 Wire up helpers for checking if a user can invite to a room and getting its join rule 2021-02-18 14:52:23 +00:00
Šimon Brandner f7bfb1e49e Fixed log (audio -> video)
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-02-17 20:51:43 +01:00
J. Ryan Stinnett 371ca009e9 Merge pull request #1605 from matrix-org/jryans/more-megolm-logging
Add more debug logs to encryption prep
2021-02-17 13:56:46 +00:00
J. Ryan Stinnett 4a0f848551 Add more debug logs to encryption prep
This continues work from https://github.com/matrix-org/matrix-js-sdk/pull/1580
and adds more logging, including specialised logging for a potential cause of
https://github.com/vector-im/element-web/issues/16194.

So far, it seems clear that something's going wrong in the "sharing keys with
new Olm session" step.
2021-02-17 13:45:26 +00:00
David Baker 5e8b7b2a62 Merge pull request #1604 from matrix-org/dbkr/ice_candidate_pool_size
Add option to set ice candidate pool size
2021-02-16 16:01:53 +00:00
David Baker 0f27b703bd Should be optional 2021-02-16 15:51:10 +00:00
David Baker 61e19c30cb Add option to set ice candidate pool size 2021-02-16 15:47:48 +00:00
RiotRobot c82bc35202 Resetting package fields for development 2021-02-16 10:58:15 +00:00
RiotRobot 65934227c3 Merge branch 'master' into develop 2021-02-16 10:58:15 +00:00
RiotRobot 7519becd43 v9.7.0 2021-02-16 10:55:42 +00:00
RiotRobot fe83c15bc6 Prepare changelog for v9.7.0 2021-02-16 10:55:41 +00:00
J. Ryan Stinnett 07e6b47fa7 Merge pull request #1601 from SimonBrandner/cancel-call-if-no-source
Cancel call if no source was selected
2021-02-11 12:29:57 +00:00
RiotRobot b026e1c2f7 v9.7.0-rc.1 2021-02-10 15:54:02 +00:00
RiotRobot f8194d9418 Prepare changelog for v9.7.0-rc.1 2021-02-10 15:54:01 +00:00
Šimon Brandner 1ecd7f274f Cancel call if no source was selected
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-02-10 07:49:03 +01:00
David Baker 66bf0ec7af Merge pull request #1600 from SimonBrandner/handle-undefined-peerconn
Handle undefined peerconn
2021-02-09 16:29:46 +00:00
Šimon Brandner 1b22df2b7b Handle undefined peerconn
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-02-09 14:42:34 +01:00
David Baker 9f993f1f67 Merge pull request #1599 from matrix-org/dbkr/reemitter_dont_throw_if_no_error_handler
ReEmitter: Don't throw if no error handler is attached
2021-02-08 19:44:03 +00:00
David Baker 975518bd88 ReEmitter: Don't throw if no error handler is attached
As hopefully explained by lengthy comment

Fixes https://github.com/matrix-org/matrix-js-sdk/issues/1569
2021-02-08 19:37:17 +00:00
David Baker 66a863456c Merge pull request #1598 from matrix-org/dbkr/reemitter_ts
Convert ReEmitter to TS
2021-02-08 19:15:08 +00:00
David Baker 91290c0d25 Actually add the test 2021-02-08 19:09:32 +00:00
David Baker 8a23e89c87 Convert ReEmitter to TS
And also add a test so I can be confident it's actually doing the
same thing.

NB. There was some logic there previously to reduce the number of
bound functions that had to be kept around, but it subsequently
started adding the source object as the last arg, at which point
there's now one bound function in memory per re-emitted event name
(plus the previous per-event-name handlers). This reduces it so it's
just one per re-emitted event name, so still could be quite a few,
but fewer than before.
2021-02-08 19:04:23 +00:00
Michael Telatynski 9e9cf85ba1 Merge pull request #1597 from rherrmann/patch-1
Fix typo in main readme
2021-02-08 17:18:29 +00:00
David Baker 3dd365bbea Merge pull request #1596 from matrix-org/dbkr/rogue_plus
Remove rogue plus character
2021-02-08 16:49:47 +00:00
Rüdiger Herrmann 33a824b980 Fix typo in main readme
Signed-off-by: Rüdiger Herrmann <ruediger.herrmann@gmx.de>
2021-02-08 17:41:48 +01:00
David Baker 8571884304 Remove rogue plus character
Apparently this is perfectly valid javascript and somehow casts
this.callId to a number... possibly it's ignoring the whitespace
and trating it as `++this.callId`?
2021-02-08 16:27:38 +00:00
David Baker 4f1067e66c Merge pull request #1595 from matrix-org/dbkr/call_id_nan
Fix call ID NaN
2021-02-08 16:18:01 +00:00
David Baker 7b5b851db0 Fix call ID NaN
We were seeing call IDs of NaN in the wild somehow... hopefully this
should make sure they're all actually strings.
2021-02-08 16:12:39 +00:00
J. Ryan Stinnett ed0be0cf84 Merge pull request #1594 from matrix-org/jryans/electron-type-merge
Fix Electron type merging
2021-02-08 15:25:14 +00:00
J. Ryan Stinnett d3775e5cb1 Fix Electron type merging
This changes to an interface for Electron types so that other layers can merge
in further APIs as needed.
2021-02-08 15:13:00 +00:00
J. Ryan Stinnett 2c8f658810 Merge pull request #1593 from SimonBrandner/fix-browser-screens-share
Fix browser screen share
2021-02-08 14:56:57 +00:00
Šimon Brandner 5c52f5f579 Fix browser screen share
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-02-08 15:48:30 +01:00
David Baker 0a81bb3fdc Merge pull request #1570 from SimonBrandner/fix-screen-sharing
Fix desktop Matrix screen sharing
2021-02-08 13:54:38 +00:00
J. Ryan Stinnett f33196bc51 Merge pull request #1591 from matrix-org/jryans/pos-wait
Guard against confused server retry times
2021-02-05 17:42:19 +00:00
J. Ryan Stinnett 6beb90a835 Guard against confused server retry times
If a server happens to give a negative retry time, this would be passed to
`setTimeout`, and browsers interpret negative values as `0`, meaning "as soon as
possible", so we then start looping infinitely with no delay.
2021-02-05 17:37:40 +00:00
J. Ryan Stinnett 9d45e6acd6 Merge pull request #1589 from SimonBrandner/decrypt-redaction-reason
Decrypt redaction events
2021-02-04 16:10:59 +00:00
Šimon Brandner 516c464458 Call decryptEvent recursively
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-02-04 16:59:26 +01:00
RiotRobot 6ad3fb16b3 Resetting package fields for development 2021-02-03 12:01:13 +00:00
RiotRobot 277fdd9b8c Merge branch 'master' into develop 2021-02-03 12:01:13 +00:00
RiotRobot 7d56993b39 v9.6.0 2021-02-03 11:58:27 +00:00
RiotRobot 4e1442fcf6 Prepare changelog for v9.6.0 2021-02-03 11:58:27 +00:00
Michael Telatynski 4777bf3e75 Merge pull request #1588 from matrix-org/t3chguy/cherrypick/1587
[Release] Fix edge cases with peeking where a room is re-peeked
2021-02-01 13:01:01 +00:00
Šimon Brandner 14cd37ec56 Decrypt redaction events
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-02-01 13:52:56 +01:00
Michael Telatynski 8bcdfd50c9 Fix edge cases with peeking where a room is re-peeked
but two Room instances are created and things get duplicated
2021-02-01 12:30:04 +00:00
Michael Telatynski 6776df8e80 Merge pull request #1587 from matrix-org/t3chguy/peeking
Fix edge cases with peeking where a room is re-peeked
2021-02-01 12:26:35 +00:00
Michael Telatynski fbec079c9b Fix edge cases with peeking where a room is re-peeked
but two Room instances are created and things get duplicated
2021-01-31 16:08:03 +00:00
RiotRobot 93f6bc3780 v9.6.0-rc.1 2021-01-29 17:20:04 +00:00
RiotRobot dde8f23cc3 Prepare changelog for v9.6.0-rc.1 2021-01-29 17:20:03 +00:00
RiotRobot 7cfbd0da95 Merge branch 'master' into develop 2021-01-26 11:42:21 +00:00
RiotRobot a27ddfaaaf v9.5.1 2021-01-26 11:39:39 +00:00
RiotRobot b53f616015 Prepare changelog for v9.5.1 2021-01-26 11:39:38 +00:00
J. Ryan Stinnett 22dc175879 Merge pull request #1585 from matrix-org/dbkr/voip-v0-release
[Release] Fix compatibility with v0 calls
2021-01-26 11:24:07 +00:00
David Baker 5f23e4699c We were using undefined here too 2021-01-26 11:17:44 +00:00
David Baker a1bd258a7b Remove unintentional commit 2021-01-26 11:17:44 +00:00
David Baker 39a9c54589 Fix compatability with v0 calls
https://github.com/matrix-org/matrix-js-sdk/pull/1567 introduced a
bug where we'd leave opponentPartyId undefined, but we compared it
to null later when testing for its presence.

Fixes https://github.com/vector-im/element-web/issues/16239
2021-01-26 11:17:44 +00:00
David Baker 5f68370e07 Merge pull request #1584 from matrix-org/dbkr/callstats
Add support for getting call stats
2021-01-26 10:53:53 +00:00
David Baker dae2de703d Add support for getting call stats
Also add a few 'public' annotations
2021-01-26 09:40:20 +00:00
David Baker fa19c40868 Merge pull request #1583 from matrix-org/dbkr/fix_v0_compat
Fix compatibility with v0 calls
2021-01-25 17:29:59 +00:00
David Baker 1df69d259a We were using undefined here too 2021-01-25 16:34:28 +00:00
David Baker 90dda0ca68 Remove unintentional commit 2021-01-25 16:13:13 +00:00
David Baker e2d138cac6 Fix compatability with v0 calls
https://github.com/matrix-org/matrix-js-sdk/pull/1567 introduced a
bug where we'd leave opponentPartyId undefined, but we compared it
to null later when testing for its presence.

Fixes https://github.com/vector-im/element-web/issues/16239
2021-01-25 16:09:39 +00:00
J. Ryan Stinnett 15f968d5f8 Merge pull request #1582 from matrix-org/jryans/upgrade-deps-2021-01
Upgrade deps 2021-01
2021-01-22 10:16:41 +00:00
Šimon Brandner 4a3b68de8f Merge branch 'develop' into fix-screen-sharing 2021-01-21 19:15:33 +01:00
David Baker f6aec7f763 Merge pull request #1581 from matrix-org/dbkr/log_the_call_id
Log the call ID when logging that we've received VoIP events
2021-01-21 17:59:49 +00:00
J. Ryan Stinnett 212b6c3a0f Resolve linting errors after upgrades 2021-01-20 13:54:45 +00:00
J. Ryan Stinnett 820256d451 Nested upgrades via yarn upgrade 2021-01-20 11:07:11 +00:00
J. Ryan Stinnett 3aba538db3 Update to latest deps 2021-01-20 11:05:17 +00:00
David Baker 4820cf8cac Log call ID here too 2021-01-19 19:28:08 +00:00
David Baker c289effba0 Log the call ID when logging that we've received VoIP events
Should make the logs a bit clearer
2021-01-19 18:11:41 +00:00
David Baker 3edccf496a Merge pull request #1579 from matrix-org/dbkr/foxes_dont_like_to_be_held
Fix extra negotiate message in Firefox
2021-01-19 17:51:35 +00:00
J. Ryan Stinnett 97b4171b3e Merge pull request #1580 from matrix-org/jryans/debug-encryption-prep
Add debug logs to encryption prep
2021-01-19 15:47:34 +00:00
J. Ryan Stinnett 4a073a7ba5 Fix lint 2021-01-19 15:36:08 +00:00
J. Ryan Stinnett 9f275d57a9 Add debug logs to encryption prep
This extra debug logs may help isolate the cause of
https://github.com/vector-im/element-web/issues/16194.

These changes also fix a related (but most likely different) failure mode: if a
failure occurred in the `encryptionPreparation` async task, we would skip trying
to prepare in all future attempts for that room. This change ensures prep
failures are logged and we resume prep attempts on the next call from the
application.
2021-01-19 15:28:28 +00:00
David Baker d23bbaeb06 Fix extra negotiate message in Firefox
Hopefully explained by the comments: Firefox sees that it's been
put on hold and tries to negotiate itself off hold again.

Fixes https://github.com/vector-im/element-web/issues/16190
2021-01-19 12:25:36 +00:00
J. Ryan Stinnett c64f7a9ec4 Merge pull request #1578 from tzyl/tzyl/get-presence-endpoint
Expose getPresence endpoint
2021-01-19 11:03:02 +00:00
RiotRobot 214a9df382 Resetting package fields for development 2021-01-18 15:06:36 +00:00
RiotRobot 90f6620f1e Merge branch 'master' into develop 2021-01-18 15:06:36 +00:00
RiotRobot 45f3a2f909 v9.5.0 2021-01-18 15:04:00 +00:00
RiotRobot 5904378170 Prepare changelog for v9.5.0 2021-01-18 15:03:59 +00:00
tzyl f6e8048d9e Expose getPresence endpoint 2021-01-18 10:17:46 +00:00
Hubert Chathi 5afca17d27 Merge pull request #1577 from uhoreg/always_queue_backup
Queue keys for backup even if backup isn't enabled yet
2021-01-15 12:28:06 -05:00
J. Ryan Stinnett 2d7f5ae279 Merge pull request #1576 from matrix-org/jryans/forbidden-turn
Stop retrying TURN access when forbidden
2021-01-15 10:06:18 +00:00
Hubert Chathi 458384d658 queue keys for backup even if backup isn't enabled yet
We may not have managed to set up the backup yet when we get keys.  So we should
unconditionally queue up the keys for backup, so that when the backup is set up,
they will be sent instead of dropped.
2021-01-14 19:55:02 -05:00
J. Ryan Stinnett 159b98132d Stop retrying TURN access when forbidden
If we're not allowed to have TURN access, there's no reason to ask in a loop.
2021-01-14 17:49:15 +00:00
Šimon Brandner 349bb2730a Update thumbnails
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-01-14 12:44:50 +01:00
Šimon Brandner c13813348d Merge branch 'develop' into fix-screen-sharing 2021-01-14 08:35:58 +01:00
Šimon Brandner 26e70d6b30 Use contextBridge
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-01-14 08:34:46 +01:00
David Baker f6d3b50b08 Merge pull request #1573 from matrix-org/dbkr/dtmf
Add DTMF sending support
2021-01-13 13:07:18 +00:00
RiotRobot 50ee489079 v9.5.0-rc.1 2021-01-13 12:54:37 +00:00
RiotRobot b60e5f40ab Prepare changelog for v9.5.0-rc.1 2021-01-13 12:54:36 +00:00
David Baker 5b1fdb7b37 Typo
Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
2021-01-13 11:37:30 +00:00
David Baker 65d4015300 Merge pull request #1574 from matrix-org/dbkr/dont_log_on_no_webrtc
Don't log if no WebRTC
2021-01-12 18:03:27 +00:00
David Baker b692cd109e Don't log if no WebRTC
as hopefully explained in comment
2021-01-12 17:58:35 +00:00
Will Hunt 0f90f055ba Merge pull request #1417 from matrix-org/hs/shared-rooms-api
Add _unstable_getSharedRooms
2021-01-11 22:38:56 +00:00
J. Ryan Stinnett 28d5ce288c Merge pull request #1568 from matrix-org/dependabot/npm_and_yarn/node-notifier-8.0.1
Bump node-notifier from 8.0.0 to 8.0.1
2021-01-11 13:12:59 +00:00
Šimon Brandner c701bf279f Merge branch 'develop' into fix-screen-sharing 2021-01-05 20:48:34 +01:00
David Baker 6039066e7f Merge pull request #1567 from matrix-org/dbkr/ignore_party_id_v0_3
Ignore party ID if opponent is v0
2021-01-05 17:23:15 +00:00
David Baker c9a2f8b170 Merge pull request #1566 from matrix-org/dbkr/call_transfer_2
Basic call transfer initiation support
2021-01-05 17:22:08 +00:00
David Baker b34a36d853 Rename other supportsTransfers 2021-01-05 17:11:49 +00:00
David Baker f8f76f6806 Add DTMF sending support 2021-01-04 19:58:12 +00:00
David Baker e25ae546fc Merge pull request #1572 from matrix-org/dbkr/room_version_6
Room version 6 is now a thing
2021-01-04 15:23:01 +00:00
David Baker 5b73bf3e5d Room version 6 is now a thing
MSC2788 (https://github.com/matrix-org/matrix-doc/pull/2788) etc
2021-01-04 15:16:09 +00:00
Hubert Chathi c16b093bd7 Merge pull request #1571 from Sorunome/soru/receive-real-key-later
Store keys with same index but better trust level
2020-12-30 11:13:30 -05:00
Sorunome e406f32386 Store keys with same index but better trust level 2020-12-29 17:02:01 +01:00
Šimon Brandner c4e7c149a4 Type cleanup
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2020-12-26 18:09:38 +01:00
Šimon Brandner f91edfabbb Change formatting
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2020-12-26 16:58:08 +01:00
Šimon Brandner f410004d45 Clean up
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2020-12-26 08:50:46 +01:00
Šimon Brandner 49e238d580 Get screen-sharing working, somehow
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2020-12-26 08:32:50 +01:00
David Baker 489d188966 Add a separate file for types 2020-12-22 15:37:58 +00:00
J. Ryan Stinnett 79fb7bab0b Merge pull request #1561 from matrix-org/jryans/prepublish-only
Use TypeScript source for development, swap to build during release
2020-12-22 13:48:15 +00:00
J. Ryan Stinnett c410954bad Replace dot-json with jq equivalents 2020-12-22 13:39:42 +00:00
J. Ryan Stinnett 53a8a7d50f Remove temporary prepare script
This was as a temporary measure during the last release so that downstream
layers would still have types as they expect.
2020-12-22 11:49:59 +00:00
J. Ryan Stinnett 1c7f95c0ee Use TypeScript source for development, swap to build during release
This changes the JS SDK to point `main` to TypeScript source and remove any
indication of `typings`. For local development and CI workflows, it means many
steps can run without building first, which saves lots of time.

During release, we still build for Node and browsers as before. The release
script adjusts the `main` and `typings` fields before publishing and
distribution to point to the built output for those that use them.
2020-12-22 11:48:57 +00:00
RiotRobot 1717fcf499 Merge branch 'master' into develop 2020-12-21 17:34:12 +00:00
RiotRobot b25453cf87 v9.4.1 2020-12-21 17:31:21 +00:00
RiotRobot 07b596bf30 Prepare changelog for v9.4.1 2020-12-21 17:29:58 +00:00
RiotRobot 1166947c21 Further tweaks to get all layers building again 2020-12-21 17:29:22 +00:00
dependabot[bot] 6e7b9ca6c0 Bump node-notifier from 8.0.0 to 8.0.1
Bumps [node-notifier](https://github.com/mikaelbr/node-notifier) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/mikaelbr/node-notifier/releases)
- [Changelog](https://github.com/mikaelbr/node-notifier/blob/v8.0.1/CHANGELOG.md)
- [Commits](https://github.com/mikaelbr/node-notifier/compare/v8.0.0...v8.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-21 16:54:57 +00:00
RiotRobot 5b1e3537cc Merge branch 'master' into develop 2020-12-21 16:53:35 +00:00
RiotRobot 44843d418d v9.4.0 2020-12-21 16:50:47 +00:00
RiotRobot a103ffa038 Prepare changelog for v9.4.0 2020-12-21 16:50:46 +00:00
David Baker 712335789e Yes, thank you test, you've made your point 2020-12-21 16:30:16 +00:00
David Baker cdf8186f44 and also in the other direction 2020-12-21 16:30:08 +00:00
David Baker d06942d602 Ignore party ID if opponent is v0 2020-12-21 16:30:00 +00:00
RiotRobot 45ac3a60dc Revert "Remove postinstall script which also runs as a dependency"
This temporarily reverts commit 853363fdf5, which
will cause trouble for downstream layers without additional changes.
2020-12-21 16:23:16 +00:00
David Baker 4f244da3ec Makean interface for the replaces event 2020-12-21 15:42:34 +00:00
David Baker 4eefa05d3f Another typo 2020-12-21 15:20:45 +00:00
David Baker 40fb31099a Typo 2020-12-21 15:18:21 +00:00
David Baker bcd85f5397 Rename to supportsCallTransfer
to be less ambiguous
2020-12-21 15:14:59 +00:00
David Baker 89aeda45c6 Basic call transfer initiation support
Re-commit of f3ee164a7d after I accidentally
merged to develop
2020-12-21 13:54:16 +00:00
David Baker 7581e5ffdc Merge pull request #1565 from matrix-org/revert-1559-dbkr/ignore_party_id_v0
Revert "Ignore party ID if opponent is v0"
2020-12-21 13:48:40 +00:00
David Baker 7046fa3224 Revert "Ignore party ID if opponent is v0" 2020-12-21 13:48:06 +00:00
David Baker ef392785e8 Merge pull request #1559 from matrix-org/dbkr/ignore_party_id_v0
Ignore party ID if opponent is v0
2020-12-21 13:47:33 +00:00
David Baker 5bd029115c Merge pull request #1562 from matrix-org/dbkr/honour_reject_from_self
Honour a call reject event from another of our own devices
2020-12-18 17:24:32 +00:00
David Baker b6f42b25dd Honour a call reject event from another of our own devices
Fixes a bug where the call would ring again when you refreshed,
even though you'd previously rejected it.
2020-12-18 13:45:13 +00:00
RiotRobot 75dd9625a0 v9.4.0-rc.2 2020-12-16 15:53:01 +00:00
RiotRobot fd6110679e Prepare changelog for v9.4.0-rc.2 2020-12-16 15:53:00 +00:00
J. Ryan Stinnett c761019aca Merge pull request #1560 from matrix-org/jryans/rm-postinstall
Remove `postinstall` script which also runs as a dependency
2020-12-16 15:50:03 +00:00
J. Ryan Stinnett 853363fdf5 Remove postinstall script which also runs as a dependency
It seems I misunderstood the `postinstall` script and had thought it would only
run when installing the project root. Instead, it seems to run also as a
dependency as well.

This change should be fine for release, but it does mean when developing
the JS SDK, you'll need to manually build. CI pipelines will also need to be
changed to call an extra build step.
2020-12-16 15:43:06 +00:00
RiotRobot f7753f8be3 v9.4.0-rc.1 2020-12-16 14:17:25 +00:00
RiotRobot d2dfa29556 Prepare changelog for v9.4.0-rc.1 2020-12-16 14:17:25 +00:00
David Baker c0a88b7f4e Yes, thank you test, you've made your point 2020-12-15 18:06:48 +00:00
David Baker 150e5fede4 and also in the other direction 2020-12-15 16:22:11 +00:00
David Baker bb3ec322fb Ignore party ID if opponent is v0 2020-12-15 16:21:14 +00:00
David Baker f3ee164a7d Basic call transfer initiation support
Sends an m.call.replaces event and flag for whether to advertise
support.

MSC2747 (https://github.com/matrix-org/matrix-doc/pull/2747)
2020-12-15 14:51:29 +00:00
David Baker 035cb9fe08 Merge pull request #1553 from matrix-org/dbkr/line_1_2
Fixes to support line 1 / 2
2020-12-11 10:26:23 +00:00
David Baker 58d0018174 Merge remote-tracking branch 'origin/develop' into dbkr/line_1_2 2020-12-10 14:00:47 +00:00
David Baker 52ed0f8615 Merge pull request #1549 from matrix-org/dbkr/hold_ui
Add API for listening to remote hold status, advertise VoIP V1
2020-12-10 12:30:46 +00:00
David Baker 2a46513dfd remove outdated comment
(it did work - I just was not checking the flag in the right place)
2020-12-09 11:21:23 +00:00
David Baker 907567182d Mute speaker when putting a call on hold 2020-12-08 19:45:11 +00:00
David Baker 46cebcd1ca Merge pull request #1555 from matrix-org/dbkr/fix_hangup_from_other_client
A hangup from another client is still valid
2020-12-08 19:02:00 +00:00
David Baker 40198f95dc A hangup from another client is still valid
if we're in the ringing state

Fixes https://github.com/vector-im/element-web/issues/15933
2020-12-08 18:53:11 +00:00
David Baker 736b934b18 Merge remote-tracking branch 'origin/develop' into dbkr/hold_ui 2020-12-08 11:59:32 +00:00
J. Ryan Stinnett b009811ac9 Merge pull request #1554 from matrix-org/jryans/rm-temp-test-build
Remove temporary build step for tests
2020-12-08 10:48:56 +00:00
RiotRobot ff6612f9d0 Merge branch 'master' into develop 2020-12-07 12:10:31 +00:00
RiotRobot 565d446b1d v9.3.0 2020-12-07 12:07:32 +00:00
RiotRobot 044d334398 Prepare changelog for v9.3.0 2020-12-07 12:07:31 +00:00
J. Ryan Stinnett e31ef2dfb5 Remove temporary build step for tests
Now that the pipeline has been updated to build for tests, we can remove this
temporary build in the test script.

Related to https://github.com/matrix-org/pipelines/pull/113
2020-12-07 10:21:26 +00:00
J. Ryan Stinnett c20566083a Merge pull request #1552 from matrix-org/jryans/browser-prepublishonly
Move browser build steps to prepublish only
2020-12-07 10:19:07 +00:00
David Baker 9845553a5f playRemoteVideo should be with it's friend, playRemoteAudio 2020-12-04 20:03:58 +00:00
David Baker 03737546fe Remove the media operation queues
As per the comment on playRemoteVideo()
2020-12-04 20:03:11 +00:00
J. Ryan Stinnett 579cb00c98 Temporarily build browser mode for tests 2020-12-04 14:19:46 +00:00
David Baker a307950213 Merge remote-tracking branch 'origin/develop' into dbkr/hold_ui 2020-12-03 19:26:36 +00:00
David Baker 0f5c469be6 Merge remote-tracking branch 'origin/develop' into dbkr/line_1_2 2020-12-03 17:55:59 +00:00
David Baker 6f7e409e9a Some little fixes to support line 1 / 2
* Resume playing audio at the appropriate time
 * Re-emit call events (they were the exception before - all other events
   were re-emitted through the MatrixClient)
 * Fix an audio/video typo
2020-12-03 17:41:34 +00:00
J. Ryan Stinnett d707912d81 Move browser build steps to prepublish only
This speeds local development and CI runs by only running the browser build
steps at release time.
2020-12-03 14:58:28 +00:00
Michael Telatynski 97ab680f4e Merge pull request #1541 from matrix-org/t3chguy/socials
Extend getSsoLoginUrl for MSC2858
2020-12-02 15:48:40 +00:00
Michael Telatynski ea35a29bd8 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/socials 2020-12-02 15:18:37 +00:00
RiotRobot 63c182bc3e v9.3.0-rc.1 2020-12-02 14:20:20 +00:00
RiotRobot b1a0a12a5f Prepare changelog for v9.3.0-rc.1 2020-12-02 14:20:19 +00:00
David Baker cc242230be Update comment 2020-11-27 14:42:18 +00:00
David Baker aef9211ea8 Merge pull request #1551 from matrix-org/dbkr/user_media_error
Export CallError
2020-11-27 14:40:59 +00:00
David Baker de5d557882 Export CallError
So the types can be typed
2020-11-27 12:57:40 +00:00
J. Ryan Stinnett fa9adf199d Merge pull request #1550 from matrix-org/jryans/upgrade-deps-2020-11-25
Upgrade dependencies
2020-11-26 15:50:28 +00:00
J. Ryan Stinnett 0807066f1f Nested upgrades via yarn upgrade 2020-11-26 15:06:30 +00:00
J. Ryan Stinnett 142e79941d Upgrade to latest major version of direct deps 2020-11-26 15:04:52 +00:00
David Baker 5e9ce38a24 Add API for listening to remote hold status
And avoid isLocalOnHold() returning true whilst the remote side
is un-held.
2020-11-26 14:32:47 +00:00
J. Ryan Stinnett f42e6373c4 Merge pull request #1547 from dalcde/error
Don't log error when environment does not support WebRTC
2020-11-26 12:13:49 +00:00
Dexter Chua bc46609caa Don't log error when WebRTC not supported
This function is *always* called when a MatrixClient is created, e.g. in
an appservice. If the environment does not support WebRTC, this is not
necessarily an error; it is expected in many situations.

Fix a small typo

Signed-off-by: Dexter Chua <dec41@srcf.net>
2020-11-26 13:55:36 +08:00
J. Ryan Stinnett cc44abe2d3 Nested upgrades via yarn upgrade 2020-11-25 17:22:20 +00:00
J. Ryan Stinnett db6398acdd Upgrade to latest minor version of direct deps 2020-11-25 17:16:22 +00:00
RiotRobot 6661bde608 Merge branch 'master' into develop 2020-11-23 16:23:01 +00:00
RiotRobot 69f6bba964 v9.2.0 2020-11-23 16:19:22 +00:00
RiotRobot 2a4e722f0f Prepare changelog for v9.2.0 2020-11-23 16:19:22 +00:00
J. Ryan Stinnett dd20828ded Merge pull request #1545 from matrix-org/jryans/fix-dehydration-method-release
[Release] Fix dehydration method name
2020-11-19 16:30:35 +00:00
J. Ryan Stinnett 5993dd588c Merge pull request #1544 from matrix-org/jryans/fix-dehydration-method
Fix dehydration method name
2020-11-19 16:24:25 +00:00
J. Ryan Stinnett 30f86e2437 Fix dehydration method name
https://github.com/matrix-org/matrix-js-sdk/pull/1537 changed some dehydration
method names, but one call site was missed.
2020-11-19 16:22:38 +00:00
J. Ryan Stinnett 3ef91e16d8 Fix dehydration method name
https://github.com/matrix-org/matrix-js-sdk/pull/1537 changed some dehydration
method names, but one call site was missed.
2020-11-19 16:16:40 +00:00
RiotRobot 45e9b3ac68 v9.2.0-rc.1 2020-11-18 15:54:10 +00:00
RiotRobot a076e3f0fc Prepare changelog for v9.2.0-rc.1 2020-11-18 15:54:09 +00:00
Michael Telatynski 99bff04ccc Update base-apis.js 2020-11-17 15:25:17 +00:00
Michael Telatynski bd906e619d Extend getSsoLoginUrl for MSC2858 2020-11-17 09:55:14 +00:00
David Baker 34882cc438 Merge pull request #1532 from matrix-org/dbkr/call_hold
Implement call holding functionality
2020-11-10 08:41:26 +00:00
RiotRobot 5ac00e3465 Merge branch 'master' into develop 2020-11-09 16:21:25 +00:00
RiotRobot 622dd065ff v9.1.0 2020-11-09 16:17:53 +00:00
RiotRobot c5c98a6ac1 Prepare changelog for v9.1.0 2020-11-09 16:17:53 +00:00
David Baker da423ed508 Add comment that the timeout probably isn't
After a fair emount of investigation, it looks like there's not
really a particularly elegant way to have an environment where
setTimeout has a return type that makes sensible assertions in both
node and the browser. Ideally we want it to be something that asserts
that we don't try to use it as a NodeJS.Timeout (ie. call the methods)
because that will break in a browser. In practice, this involves making
wrappers or redefining the timeout functions or some similar kind of
hackery, the evils of which probably offset having perfect typing.

https://matrix.to/#/!bEWtlqtDwCLFIAKAcv:matrix.org/$vpFWl7p1_8A858RAccO0gud3sNVWIDNxNELRjdqaZQ4?via=matrix.org&via=mozilla.org&via=vector.modular.im
for the discussion
2020-11-09 15:59:54 +00:00
Bruno Windels 11c4337cfc Merge pull request #1537 from matrix-org/bwindels/dehydration
Support awaitable one-time dehydration
2020-11-04 15:22:09 +00:00
Bruno Windels 458164384d add client method for one-time dehydration that can be awaited 2020-11-04 16:05:31 +01:00
Bruno Windels 13c7f55a79 split up setKey and setKeyAndQueue
as dehydrating in the background prevents use-cases where you
want to await the creation of the dehydrated device
2020-11-04 16:02:31 +01:00
Bruno Windels 4ab675863a fix typo 2020-11-04 16:00:53 +01:00
RiotRobot 5414b3b39d v9.1.0-rc.1 2020-11-04 14:03:49 +00:00
RiotRobot c54db30dc8 Prepare changelog for v9.1.0-rc.1 2020-11-04 14:03:48 +00:00
Michael Telatynski f11103bfcc Merge pull request #1534 from matrix-org/t3chguy/fix/15604
Client set profile methods update own user
2020-11-04 13:59:58 +00:00
Bruno Windels b56936003d stop dehydration timer when stopping the client 2020-11-03 10:13:19 +01:00
Travis Ralston f61604a51e Merge pull request #1535 from matrix-org/travis/fix-acl-type
Fix spelling error in the server ACL event type
2020-11-02 12:54:14 -07:00
Travis Ralston ae77f900ef Fix spelling error in the server ACL event type 2020-11-02 12:51:15 -07:00
Michael Telatynski 645842f0fd Update comments 2020-11-02 18:09:32 +00:00
Bruno Windels 39d3640973 Merge pull request #1533 from matrix-org/bwindels/dehydration-await-crypto-store
await idb operations from crypto store for dehydration
2020-11-02 17:49:45 +00:00
Michael Telatynski 66aa9c4831 pass a presence event 2020-11-02 17:39:25 +00:00
Michael Telatynski 7de0ca2048 pass args 2020-11-02 17:29:14 +00:00
Michael Telatynski 5bbc5cad9f Client setProfile methods update own user 2020-11-02 17:20:03 +00:00
Bruno Windels 0a7a80d5a8 await idb operations from crypto store for dehydration 2020-11-02 17:35:32 +01:00
David Baker 33d1a33a17 Implement call holding functionality
Using m.call.negotiate
2020-10-29 17:54:54 +00:00
David Baker 7f130949c8 Merge pull request #1531 from matrix-org/dbkr/those_cached_promises_again
Fix stuck never-sending messages
2020-10-28 20:20:26 +00:00
David Baker ba7ee37899 Fix stuck never-sending messages
Another cached promise that wasn't cleared on failure
2020-10-28 18:29:05 +00:00
RiotRobot f8863d5c24 Merge branch 'master' into develop 2020-10-28 14:15:01 +00:00
RiotRobot 1d7954c831 v9.0.1 2020-10-28 14:11:30 +00:00
RiotRobot 2bb4e91dfd Prepare changelog for v9.0.1 2020-10-28 14:11:29 +00:00
J. Ryan Stinnett abe5bf4240 Merge pull request #1530 from matrix-org/jryans/await-key-cache-release
[Release] Await key cache check to avoid prompts
2020-10-28 11:42:19 +00:00
J. Ryan Stinnett c493bf7866 Deduplicate key backup signing paths 2020-10-28 11:07:56 +00:00
J. Ryan Stinnett 06bf0f22be Await key cache check to avoid prompts
`isStoredInKeyCache` is async, so we need to await the result to know whether to
proceed. In addition, this wraps the block in a try / catch, since signing the
key backup is an optional step.

Fixes https://github.com/vector-im/element-web/issues/15530
2020-10-28 11:07:56 +00:00
J. Ryan Stinnett 02d9fe1d30 Merge pull request #1529 from matrix-org/jryans/await-key-cache
Await key cache check to avoid prompts
2020-10-27 17:34:21 +00:00
J. Ryan Stinnett c3091c5aa4 Deduplicate key backup signing paths 2020-10-27 17:17:54 +00:00
J. Ryan Stinnett 677a427f1f Await key cache check to avoid prompts
`isStoredInKeyCache` is async, so we need to await the result to know whether to
proceed. In addition, this wraps the block in a try / catch, since signing the
key backup is an optional step.

Fixes https://github.com/vector-im/element-web/issues/15530
2020-10-27 17:07:04 +00:00
RiotRobot c416dd01a7 Merge branch 'master' into develop 2020-10-26 16:45:54 +00:00
RiotRobot 662fcb426b v9.0.0 2020-10-26 16:42:34 +00:00
RiotRobot fd0fe9e225 Prepare changelog for v9.0.0 2020-10-26 16:42:33 +00:00
David Baker 0ca8613896 Merge pull request #1524 from matrix-org/dbkr/optimise_ice_candidate_sending
Improve ICE candidate batching
2020-10-22 16:29:22 +01:00
David Baker 4b1817719e Merge pull request #1527 from matrix-org/dbkr/logger_ts_try_3
Convert logger to typescript
2020-10-22 16:08:09 +01:00
David Baker 295c591e95 Merge pull request #1528 from matrix-org/dbkr/debugl_rel
Fix logger typo
2020-10-22 16:06:31 +01:00
David Baker 9df0480b78 Merge remote-tracking branch 'origin/develop' into dbkr/logger_ts_try_3 2020-10-22 16:03:12 +01:00
David Baker 5260f40451 Merge pull request #1525 from matrix-org/dbkr/debugl
Fix logger typo
2020-10-22 16:02:10 +01:00
David Baker 0cbe35e41f Fix logger typo 2020-10-22 16:00:23 +01:00
David Baker 502745271d Convert logger to typescript
Because it's annoying for the IDE to have no idea what methods are
on the logger. `loglevel` has types so it should just pass them
through.
2020-10-22 15:59:38 +01:00
David Baker 95baa3cd27 Fix logger typo 2020-10-22 15:54:55 +01:00
David Baker 8fe4a29176 Stop typescript from trying to be clever 2020-10-22 10:10:06 +01:00
David Baker 1a1a0e7324 Abort if call has ended and remove stray self(!) 2020-10-21 18:55:57 +01:00
David Baker d00d07a1c1 Improve ICE candidate batching
Hopefully send fewer ICE candidate events by obeying the batching
guidelines in MSC2476.
2020-10-21 18:23:01 +01:00
Michael Telatynski f27db16e30 Merge pull request #1523 from matrix-org/t3chguy/fix/js-online
bind online listener to window instead of document
2020-10-21 16:08:18 +01:00
RiotRobot d4e107b3cd v9.0.0-rc.1 2020-10-21 14:33:50 +01:00
RiotRobot 881b60c85f Prepare changelog for v9.0.0-rc.1 2020-10-21 14:33:50 +01:00
Michael Telatynski c5e1aade12 bind online listener to window instead of document
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-21 13:58:34 +01:00
David Baker 28198a6f40 Merge pull request #1522 from matrix-org/dbke/select_answer
Support m.call.select_answer
2020-10-21 13:33:49 +01:00
David Baker 30a01e26de Make test pass
Don't send events all the way via the mock HTTP backend: we're not
trying to test that here. This meant we weren't actually getting
into the right state because the request to send the invite never
actually returned. Now this works, we need to clear the invite timer
otherwise jest has a timer hanging around at the end of the test
(plus we should be doing it anyway).
2020-10-21 12:41:36 +01:00
David Baker 8712703f7c Support m.call.select_answer
Only send one if the answer has a party_id set

Also change some event types to the enum and rename receivedAnswer
to be more consistent
2020-10-21 11:52:58 +01:00
David Baker e3a8631faa Merge pull request #1521 from matrix-org/dbkr/dont_cache_versions_failure
Don't cache failures when fetching /versions
2020-10-20 16:35:49 +01:00
David Baker 3eab51ce79 Don't cache failures when fetching /versions
Otherwise we never recover

Fixes https://github.com/vector-im/element-web/issues/15509
2020-10-20 16:25:50 +01:00
J. Ryan Stinnett 9db0fe0795 Merge pull request #1518 from matrix-org/jryans/release-install-first
Install deps first as part of release
2020-10-20 15:45:25 +01:00
Michael Telatynski 228af037e3 Merge pull request #1517 from matrix-org/t3chguy/fix/js-1504
[Breaking] Change hasPendingEvent to return false if pending ordering !detached
2020-10-20 15:28:29 +01:00
J. Ryan Stinnett 654f250fd8 Merge pull request #1519 from matrix-org/jryans/release-disable-editor
Skip editor prompts for merges
2020-10-20 12:06:34 +01:00
J. Ryan Stinnett a8693d9d68 Skip editor prompts for merges
The merges are never edited, and even with this change they will still abort if
there are conflicts.
2020-10-20 11:47:59 +01:00
J. Ryan Stinnett f9f345e428 Move cache clean above install 2020-10-20 11:42:28 +01:00
J. Ryan Stinnett e678706414 Install deps first as part of release
This ensures we always install (without running build scripts) as the first step
of release process, as otherwise it may fail later due to mismatched types or
any number of other errors.
2020-10-20 11:20:57 +01:00
Michael Telatynski 8018259480 Change hasPendingEvent to return false if pending event ordering !detached
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-19 14:56:58 +01:00
David Baker 9f713781cd Merge pull request #1516 from matrix-org/dbkr/aint_no_party_like_a_typescript_party
Convert call test to TypeScript
2020-10-19 09:57:52 +01:00
David Baker 48d56dc5c0 Merge pull request #1512 from matrix-org/dbkr/party_in_the_usa
Support party_id
2020-10-19 09:57:30 +01:00
David Baker 701dfa09b4 Merge pull request #1510 from matrix-org/dbkr/hang_up_your_hangups
Support m.call.reject
2020-10-19 09:57:00 +01:00
David Baker d965648fd7 Convert call test to TypeScript
Typescript tests basically just appear to work, apart from needing
the jest types imported so the typescript checker knows what's what.

DConvert the webrtc test to typescript, which actually mostly just
serves to point out that we're not mocking the whole of `document`,
but oh well.
2020-10-16 18:20:03 +01:00
David Baker 08c15e7203 Merge pull request #1515 from matrix-org/dbkr/remove_specbuild
Remove specbuild from .gitignore
2020-10-16 17:56:02 +01:00
David Baker 9b9e52d0a2 Remove specbuild from .gitignore
It's no longer a thing
2020-10-16 17:47:31 +01:00
David Baker 38cc8fe7dc Merge pull request #1514 from matrix-org/dbkr/log_candidate_send_error
Log the error when we failed to send candidates
2020-10-16 16:52:05 +01:00
David Baker 590fac0fa9 Log the error when we failed to send candidates 2020-10-16 16:46:07 +01:00
David Baker 9590c8aaf0 Don't ignore hangups if we don't have a partner 2020-10-16 14:37:50 +01:00
David Baker e2b79e4e7e linty lint lint 2020-10-16 13:02:47 +01:00
David Baker 2df588f95a Support party_id
Send party_id on events and check the party_id of incoming events matches

Includes a basic test to assert that it actually does: we should
build out a decent test suite for calls as there's a lot of edge-case
functionality that can break and slip through the cracks (eg. glare).
This is a start.

Fixes https://github.com/matrix-org/matrix-js-sdk/issues/1511
2020-10-16 12:53:08 +01:00
David Baker 7c3af91b42 Support m.call.reject
Start the migration to v1 VoIP by supporting m.call.reject, which
we'll send if the caller says they're v1. Our version stays as v0
for now, until we speak the rest of v1.

Honour the default reaosn in a hangup being user_hangup.
2020-10-15 14:51:05 +01:00
David Baker 4cc4b28c47 Merge pull request #1503 from matrix-org/dbkr/call_state_machine
Fixes for call state machine
2020-10-13 10:39:10 +01:00
David Baker fad9b4c67f Merge pull request #1506 from matrix-org/dbkr/fix_event_listener_remove
Fix call event handler listener removing
2020-10-12 14:17:53 +01:00
David Baker ade3b3a021 Fix call event handler listener removing
Wrong kind of event emitter...
2020-10-12 14:12:34 +01:00
RiotRobot d8c4101fdd Merge branch 'master' into develop 2020-10-12 13:18:23 +01:00
David Baker b67a179a54 Merge pull request #1501 from matrix-org/dbkr/set_type_based_on_tracks
Set the type of the call based on the tracks
2020-10-12 11:42:40 +01:00
David Baker 06044b39c3 Merge pull request #1499 from matrix-org/dbkr/new_age_calls
Use new local timestamp for calls
2020-10-12 11:42:28 +01:00
J. Ryan Stinnett d16cf26c5f Merge pull request #1502 from matrix-org/jryans/sso-4s-integration
Adjust types and APIs to match React SDK
2020-10-12 11:41:06 +01:00
J. Ryan Stinnett b060c5af38 Tune crypto types 2020-10-12 11:01:00 +01:00
David Baker b28bad651e Make events names an enum 2020-10-12 10:15:58 +01:00
David Baker 5c4b7a3213 Add user_hangup error code
but special case it for now & don't send it: it needs voip v1
2020-10-09 18:51:35 +01:00
David Baker 7f21c591ae Fixes for call state machine
* Set 'connecting' state before sending answer, otherwise it can
   race with ICE connecting
 * Ignore completed ice connection state: connected is what we care about
 * Null-check remotestream when stopping media
 * Comments
2020-10-09 18:45:45 +01:00
David Baker 65b24f595c Missed a reference to callList 2020-10-09 18:14:00 +01:00
David Baker 9d9c2720c2 Hopefully make if statement clearer 2020-10-09 17:43:40 +01:00
David Baker f845100062 add 'public' to public method 2020-10-09 17:43:34 +01:00
David Baker e6155f9e37 Use MatrixClient as type
and different array syntax
2020-10-09 17:43:28 +01:00
J. Ryan Stinnett e9590e9093 Adjust types and APIs to match React SDK
Various small tweaks and alignments to match React SDK as part of TypeScript
conversion.

Part of https://github.com/vector-im/element-web/issues/15350
2020-10-09 17:21:14 +01:00
David Baker 49f2d1501c Set the type of the call based on the tracks
Remove the old hack of inspecting the SDP which no longer seems to
be necessary.
2020-10-08 15:42:39 +01:00
J. Ryan Stinnett c6819e0450 Omit stack trace if rehydration fails 2020-10-08 15:11:46 +01:00
David Baker f518ea95f4 Use getLocalAge() & add grace period
Use the new local-age field for deciding whether a call is still
valid or not. Also add a grace period so we don't ring half a second
before the call becomes invalid.
2020-10-08 11:49:54 +01:00
David Baker 92c6332143 use more enums 2020-10-08 11:32:32 +01:00
David Baker 0df1a7da21 copyright header 2020-10-08 11:12:39 +01:00
David Baker 487a9c0967 Extract the call event handler out to its own class
and convert it to TypeScript
2020-10-08 11:10:20 +01:00
David Baker fb89761671 Merge pull request #1495 from matrix-org/dbkr/age_is_just_a_number
Make an accurate version of 'age' for events
2020-10-08 09:29:16 +01:00
David Baker d1d3ae074d Add tests & fix some bugs found by said tests 2020-10-07 18:29:33 +01:00
David Baker 7dedaf90c3 Add a test for the age mangling 2020-10-07 18:04:20 +01:00
David Baker 687b98a09d Don't add localTs if an event has no age 2020-10-07 17:39:19 +01:00
David Baker c6b2e9873c Merge pull request #1498 from matrix-org/dbkr/call_options_is_optional
Make 'options' parameter optional
2020-10-07 17:15:26 +01:00
David Baker 1e80491675 Make 'options' parameter optional
It's deprecated so generally nothing should be passing it
2020-10-07 17:08:21 +01:00
Michael Telatynski a727da9193 Merge pull request #1497 from matrix-org/t3chguy/fix/14804
Create a giant event type enum
2020-10-07 16:48:39 +01:00
Michael Telatynski 0b7754581a Update src/@types/event.ts
Co-authored-by: Travis Ralston <travpc@gmail.com>
2020-10-07 16:32:04 +01:00
David Baker 452e8ea385 Merge pull request #1494 from matrix-org/revert-1493-revert-1487-dbkr/tsify_call
Convert call.js to Typescript & update WebRTC APIs (re-apply)
2020-10-07 15:36:27 +01:00
David Baker a189de9a2e Add comment on mass event copying 2020-10-07 15:32:22 +01:00
David Baker 8632ca6e37 Merge remote-tracking branch 'origin/develop' into revert-1493-revert-1487-dbkr/tsify_call 2020-10-07 15:16:21 +01:00
Michael Telatynski 7529d2b638 Create a giant event type enum
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-07 09:57:56 +01:00
David Baker c8a18d51e6 Lint (& unused variable) 2020-10-06 17:45:21 +01:00
David Baker d77af1e67a Make an accurate version of 'age' for events
We've always had 'age' in events but it's never really been an
accurate representation of the event's age because we never did
anything with it. This transforms it into a local clock timestamp
when the event arives and when it comes out of the sync store, and
changes getLocalAge() to use it.

react-sdk doesn't appear to use getLocalAge() but any 3rd party apps
that do may notice a slight change in bahaviour.
2020-10-06 15:24:09 +01:00
David Baker 81c95224d1 Revert "Revert "Convert call.js to Typescript & update WebRTC APIs"" 2020-10-06 15:21:45 +01:00
florianjacob 22713d8f89 correct IndexedDB store creation example 2020-08-26 14:24:13 +02:00
Will Hunt 6ec7e3a0b7 UserId must be sane 2020-08-18 18:42:55 +01:00
Will Hunt 1a1fe759c3 fix encoding bug 2020-08-18 18:41:45 +01:00
Will Hunt a919c798f8 Update _unstable_getSharedRooms to match spec 2020-08-18 18:34:44 +01:00
Will Hunt 80fe66c481 Add /uk.half-shot.msc2666/ prefix 2020-08-18 18:34:44 +01:00
Will Hunt 4577dc8f44 s/msc2644/msc2666/ 2020-08-18 18:34:44 +01:00
Will Hunt cf0a5305e0 Throw if server does not support getSharedRooms 2020-08-18 18:34:44 +01:00
Will Hunt e69e6e1981 Update _unstable_getSharedRooms response format 2020-08-18 18:34:44 +01:00
Will Hunt 0febd99fbe Add _unstable_getSharedRooms 2020-08-18 18:34:43 +01:00
153 changed files with 20835 additions and 15211 deletions
+22 -7
View File
@@ -1,13 +1,14 @@
module.exports = {
extends: ["matrix-org"],
plugins: [
"babel",
"matrix-org",
],
extends: [
"plugin:matrix-org/babel",
],
env: {
browser: true,
node: true,
},
rules: {
"no-var": ["warn"],
"prefer-rest-params": ["warn"],
@@ -32,12 +33,26 @@ module.exports = {
"no-console": "error",
},
overrides: [{
"files": ["src/**/*.ts"],
"extends": ["matrix-org/ts"],
"rules": {
// While we're converting to ts we make heavy use of this
files: [
"**/*.ts",
],
extends: [
"plugin:matrix-org/typescript",
],
rules: {
// TypeScript has its own version of this
"@babel/no-invalid-this": "off",
// We're okay being explicit at the moment
"@typescript-eslint/no-empty-interface": "off",
// We disable this while we're transitioning
"@typescript-eslint/no-explicit-any": "off",
// We'd rather not do this but we do
"@typescript-eslint/ban-ts-comment": "off",
"quotes": "off",
// We use a `logger` intermediary module
"no-console": "error",
},
}],
};
-1
View File
@@ -12,7 +12,6 @@ lib-cov
out
/dist
/lib
/specbuild
# version file and tarball created by `npm pack` / `yarn pack`
/git-revision.txt
+594
View File
@@ -1,3 +1,597 @@
Changes in [12.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v12.0.0) (2021-06-21)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v12.0.0-rc.1...v12.0.0)
* No changes since rc.1
Changes in [12.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v12.0.0-rc.1) (2021-06-15)
============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v11.2.0...v12.0.0-rc.1)
* Rework how disambiguation is handled
[\#1730](https://github.com/matrix-org/matrix-js-sdk/pull/1730)
* Fix baseToString for n=0 edge case to match inverse stringToBase
[\#1735](https://github.com/matrix-org/matrix-js-sdk/pull/1735)
* Move various types from the react-sdk to the js-sdk
[\#1734](https://github.com/matrix-org/matrix-js-sdk/pull/1734)
* Unstable implementation of MSC3089: File Trees
[\#1732](https://github.com/matrix-org/matrix-js-sdk/pull/1732)
* Add MSC3230 event type to enum
[\#1729](https://github.com/matrix-org/matrix-js-sdk/pull/1729)
* Add separate reason code for transferred calls
[\#1731](https://github.com/matrix-org/matrix-js-sdk/pull/1731)
* Use sendonly for call hold
[\#1728](https://github.com/matrix-org/matrix-js-sdk/pull/1728)
* Stop breeding sync listeners
[\#1727](https://github.com/matrix-org/matrix-js-sdk/pull/1727)
* Fix semicolons in TS files
[\#1724](https://github.com/matrix-org/matrix-js-sdk/pull/1724)
* [BREAKING] Convert MatrixClient to TypeScript
[\#1718](https://github.com/matrix-org/matrix-js-sdk/pull/1718)
* Factor out backup management to a separate module
[\#1697](https://github.com/matrix-org/matrix-js-sdk/pull/1697)
* Ignore power_levels events with unknown state_key on room-state
initialization
[\#1723](https://github.com/matrix-org/matrix-js-sdk/pull/1723)
* Revert 1579 (Fix extra negotiate message in Firefox)
[\#1725](https://github.com/matrix-org/matrix-js-sdk/pull/1725)
Changes in [11.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v11.2.0) (2021-06-07)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v11.2.0-rc.1...v11.2.0)
* No changes since rc.1
Changes in [11.2.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v11.2.0-rc.1) (2021-06-01)
============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v11.1.0...v11.2.0-rc.1)
* Switch to stable endpoint/fields for MSC2858
[\#1720](https://github.com/matrix-org/matrix-js-sdk/pull/1720)
* Bump ws from 7.4.2 to 7.4.6
[\#1715](https://github.com/matrix-org/matrix-js-sdk/pull/1715)
* Make consistent call event type checks
[\#1712](https://github.com/matrix-org/matrix-js-sdk/pull/1712)
* Apply new Babel linting config
[\#1714](https://github.com/matrix-org/matrix-js-sdk/pull/1714)
* Bump browserslist from 4.16.1 to 4.16.6
[\#1709](https://github.com/matrix-org/matrix-js-sdk/pull/1709)
* Add user_busy call hangup reason
[\#1713](https://github.com/matrix-org/matrix-js-sdk/pull/1713)
* 👕 New linting rules
[\#1688](https://github.com/matrix-org/matrix-js-sdk/pull/1688)
* Emit relations created when target event added later
[\#1710](https://github.com/matrix-org/matrix-js-sdk/pull/1710)
* Bump libolm version and update package name.
[\#1705](https://github.com/matrix-org/matrix-js-sdk/pull/1705)
* Fix uploadContent not rejecting promise when http status code >= 400
[\#1703](https://github.com/matrix-org/matrix-js-sdk/pull/1703)
* Reduce noise in tests
[\#1702](https://github.com/matrix-org/matrix-js-sdk/pull/1702)
* Only log once if a Room lacks an m.room.create event
[\#1700](https://github.com/matrix-org/matrix-js-sdk/pull/1700)
* Cache normalized room name
[\#1701](https://github.com/matrix-org/matrix-js-sdk/pull/1701)
* Change call event handlers to adapt to undecrypted events
[\#1698](https://github.com/matrix-org/matrix-js-sdk/pull/1698)
Changes in [11.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v11.1.0) (2021-05-24)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v11.1.0-rc.1...v11.1.0)
* [Release] Bump libolm version and update package name
[\#1707](https://github.com/matrix-org/matrix-js-sdk/pull/1707)
* [Release] Change call event handlers to adapt to undecrypted events
[\#1699](https://github.com/matrix-org/matrix-js-sdk/pull/1699)
Changes in [11.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v11.1.0-rc.1) (2021-05-19)
============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v11.0.0...v11.1.0-rc.1)
* Fix regressed glare
[\#1690](https://github.com/matrix-org/matrix-js-sdk/pull/1690)
* Add m.reaction to EventType enum
[\#1692](https://github.com/matrix-org/matrix-js-sdk/pull/1692)
* Prioritise and reduce the amount of events decrypted on application startup
[\#1684](https://github.com/matrix-org/matrix-js-sdk/pull/1684)
* Decrypt relations before applying them to target event
[\#1696](https://github.com/matrix-org/matrix-js-sdk/pull/1696)
* Guard against duplicates in `Relations` model
Changes in [11.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v11.0.0) (2021-05-17)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v11.0.0-rc.1...v11.0.0)
* [Release] Fix regressed glare
[\#1695](https://github.com/matrix-org/matrix-js-sdk/pull/1695)
Changes in [11.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v11.0.0-rc.1) (2021-05-11)
============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v10.1.0...v11.0.0-rc.1)
BREAKING CHANGES
---
* `MatrixCall` and related APIs have been redesigned to support multiple streams
(see [\#1660](https://github.com/matrix-org/matrix-js-sdk/pull/1660) for more details)
All changes
---
* Switch from MSC1772 unstable prefixes to stable
[\#1679](https://github.com/matrix-org/matrix-js-sdk/pull/1679)
* Update the VoIP example to work with the new changes
[\#1680](https://github.com/matrix-org/matrix-js-sdk/pull/1680)
* Bump hosted-git-info from 2.8.8 to 2.8.9
[\#1687](https://github.com/matrix-org/matrix-js-sdk/pull/1687)
* Support for multiple streams (not MSC3077)
[\#1660](https://github.com/matrix-org/matrix-js-sdk/pull/1660)
* Tweak missing m.room.create errors to describe their source
[\#1683](https://github.com/matrix-org/matrix-js-sdk/pull/1683)
Changes in [10.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v10.1.0) (2021-05-10)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v10.1.0-rc.1...v10.1.0)
* No changes since rc.1
Changes in [10.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v10.1.0-rc.1) (2021-05-04)
============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v10.0.0...v10.1.0-rc.1)
* Revert "Raise logging dramatically to chase pending event errors"
[\#1681](https://github.com/matrix-org/matrix-js-sdk/pull/1681)
* Add test coverage collection script
[\#1677](https://github.com/matrix-org/matrix-js-sdk/pull/1677)
* Raise logging dramatically to chase pending event errors
[\#1678](https://github.com/matrix-org/matrix-js-sdk/pull/1678)
* Support MSC3086 asserted identity
[\#1674](https://github.com/matrix-org/matrix-js-sdk/pull/1674)
* Fix `/search` with no results field work again
[\#1670](https://github.com/matrix-org/matrix-js-sdk/pull/1670)
* Add room.getMembers method
[\#1672](https://github.com/matrix-org/matrix-js-sdk/pull/1672)
Changes in [10.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v10.0.0) (2021-04-26)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v10.0.0-rc.1...v10.0.0)
* No changes since rc.1
Changes in [10.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v10.0.0-rc.1) (2021-04-21)
============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.11.0...v10.0.0-rc.1)
BREAKING CHANGES
---
* The `RoomState.members` event is now only emitted when the room member's power level or the room's normal power level actually changes
All changes
---
* Restrict event emit for room members that had power levels changed
[\#1675](https://github.com/matrix-org/matrix-js-sdk/pull/1675)
* Fix sync with misconfigured push rules
[\#1669](https://github.com/matrix-org/matrix-js-sdk/pull/1669)
* Add missing await
[\#1665](https://github.com/matrix-org/matrix-js-sdk/pull/1665)
* Migrate to `eslint-plugin-matrix-org`
[\#1642](https://github.com/matrix-org/matrix-js-sdk/pull/1642)
* Add missing event type enum for key verification done
[\#1664](https://github.com/matrix-org/matrix-js-sdk/pull/1664)
* Fix timeline jumpiness by setting correct txnId
[\#1663](https://github.com/matrix-org/matrix-js-sdk/pull/1663)
* Fix calling addEventListener if it does not exist
[\#1661](https://github.com/matrix-org/matrix-js-sdk/pull/1661)
* Persist unsent messages for subsequent sessions
[\#1655](https://github.com/matrix-org/matrix-js-sdk/pull/1655)
Changes in [9.11.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.11.0) (2021-04-12)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.11.0-rc.1...v9.11.0)
* No changes since rc.1
Changes in [9.11.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.11.0-rc.1) (2021-04-07)
============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.10.0...v9.11.0-rc.1)
* Only try to cache private keys we know exist
[\#1657](https://github.com/matrix-org/matrix-js-sdk/pull/1657)
* Properly terminate screen-share calls if NoUserMedia
[\#1654](https://github.com/matrix-org/matrix-js-sdk/pull/1654)
* Attended transfer
[\#1652](https://github.com/matrix-org/matrix-js-sdk/pull/1652)
* Remove catch handlers in private key retrieval
[\#1653](https://github.com/matrix-org/matrix-js-sdk/pull/1653)
* Fixed the media fail error on caller's side
[\#1651](https://github.com/matrix-org/matrix-js-sdk/pull/1651)
* Add function to share megolm keys for historical messages, take 2
[\#1640](https://github.com/matrix-org/matrix-js-sdk/pull/1640)
* Cache cross-signing private keys if needed on bootstrap
[\#1649](https://github.com/matrix-org/matrix-js-sdk/pull/1649)
Changes in [9.10.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.10.0) (2021-03-29)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.10.0-rc.1...v9.10.0)
* No changes since rc.1
Changes in [9.10.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.10.0-rc.1) (2021-03-25)
============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.9.0...v9.10.0-rc.1)
* Don't send m.call.hangup if m.call.invite wasn't sent either
[\#1647](https://github.com/matrix-org/matrix-js-sdk/pull/1647)
* docs: registerGuest()
[\#1641](https://github.com/matrix-org/matrix-js-sdk/pull/1641)
* Download device keys in chunks of 250
[\#1639](https://github.com/matrix-org/matrix-js-sdk/pull/1639)
* More VoIP connectivity fixes
[\#1646](https://github.com/matrix-org/matrix-js-sdk/pull/1646)
* Make selectDesktopCapturerSource param optional
[\#1644](https://github.com/matrix-org/matrix-js-sdk/pull/1644)
* Expose APIs needed for reworked cross-signing login flow
[\#1632](https://github.com/matrix-org/matrix-js-sdk/pull/1632)
Changes in [9.9.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.9.0) (2021-03-15)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.9.0-rc.1...v9.9.0)
* No changes since rc.1
Changes in [9.9.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.9.0-rc.1) (2021-03-10)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.8.0...v9.9.0-rc.1)
* Remove detailed Olm session logging
[\#1638](https://github.com/matrix-org/matrix-js-sdk/pull/1638)
* Add space summary suggested only param
[\#1637](https://github.com/matrix-org/matrix-js-sdk/pull/1637)
* Check TURN servers periodically, and at start of calls
[\#1634](https://github.com/matrix-org/matrix-js-sdk/pull/1634)
* Support sending invite reasons
[\#1624](https://github.com/matrix-org/matrix-js-sdk/pull/1624)
* Bump elliptic from 6.5.3 to 6.5.4
[\#1636](https://github.com/matrix-org/matrix-js-sdk/pull/1636)
* Add a function to get a room's MXC URI
[\#1635](https://github.com/matrix-org/matrix-js-sdk/pull/1635)
* Stop streams if the call has ended
[\#1633](https://github.com/matrix-org/matrix-js-sdk/pull/1633)
* Remove export keyword from global.d.ts
[\#1631](https://github.com/matrix-org/matrix-js-sdk/pull/1631)
* Fix IndexedDB store creation example
[\#1445](https://github.com/matrix-org/matrix-js-sdk/pull/1445)
* An attempt to cleanup how constraints are handled in calls
[\#1613](https://github.com/matrix-org/matrix-js-sdk/pull/1613)
* Extract display name patterns to constants
[\#1628](https://github.com/matrix-org/matrix-js-sdk/pull/1628)
* Bump pug-code-gen from 2.0.2 to 2.0.3
[\#1630](https://github.com/matrix-org/matrix-js-sdk/pull/1630)
* Avoid deadlocks when ensuring Olm sessions for devices
[\#1627](https://github.com/matrix-org/matrix-js-sdk/pull/1627)
* Filter out edits from other senders in history
[\#1626](https://github.com/matrix-org/matrix-js-sdk/pull/1626)
* Fix ContentHelpers export
[\#1618](https://github.com/matrix-org/matrix-js-sdk/pull/1618)
* Add logging to in progress Olm sessions
[\#1621](https://github.com/matrix-org/matrix-js-sdk/pull/1621)
* Don't ignore ICE candidates received before offer/answer
[\#1623](https://github.com/matrix-org/matrix-js-sdk/pull/1623)
* Better handling of send failures on VoIP events
[\#1622](https://github.com/matrix-org/matrix-js-sdk/pull/1622)
* Log when turn creds expire
[\#1620](https://github.com/matrix-org/matrix-js-sdk/pull/1620)
* Initial Spaces [MSC1772] support
[\#1563](https://github.com/matrix-org/matrix-js-sdk/pull/1563)
* Add logging to crypto store transactions
[\#1617](https://github.com/matrix-org/matrix-js-sdk/pull/1617)
* Room helper for getting type and checking if it is a space room
[\#1610](https://github.com/matrix-org/matrix-js-sdk/pull/1610)
Changes in [9.8.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.8.0) (2021-03-01)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.8.0-rc.1...v9.8.0)
* No changes since rc.1
Changes in [9.8.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.8.0-rc.1) (2021-02-24)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.7.0...v9.8.0-rc.1)
* Optimise prefixed logger
[\#1615](https://github.com/matrix-org/matrix-js-sdk/pull/1615)
* Add debug logs to encryption prep, take 3
[\#1614](https://github.com/matrix-org/matrix-js-sdk/pull/1614)
* Add functions for upper & lowercase random strings
[\#1612](https://github.com/matrix-org/matrix-js-sdk/pull/1612)
* Room helpers for invite permissions and join rules
[\#1609](https://github.com/matrix-org/matrix-js-sdk/pull/1609)
* Fixed wording in "Adding video track with id" log
[\#1606](https://github.com/matrix-org/matrix-js-sdk/pull/1606)
* Add more debug logs to encryption prep
[\#1605](https://github.com/matrix-org/matrix-js-sdk/pull/1605)
* Add option to set ice candidate pool size
[\#1604](https://github.com/matrix-org/matrix-js-sdk/pull/1604)
* Cancel call if no source was selected
[\#1601](https://github.com/matrix-org/matrix-js-sdk/pull/1601)
Changes in [9.7.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.7.0) (2021-02-16)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.7.0-rc.1...v9.7.0)
* No changes since rc.1
Changes in [9.7.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.7.0-rc.1) (2021-02-10)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.6.0...v9.7.0-rc.1)
* Handle undefined peerconn
[\#1600](https://github.com/matrix-org/matrix-js-sdk/pull/1600)
* ReEmitter: Don't throw if no error handler is attached
[\#1599](https://github.com/matrix-org/matrix-js-sdk/pull/1599)
* Convert ReEmitter to TS
[\#1598](https://github.com/matrix-org/matrix-js-sdk/pull/1598)
* Fix typo in main readme
[\#1597](https://github.com/matrix-org/matrix-js-sdk/pull/1597)
* Remove rogue plus character
[\#1596](https://github.com/matrix-org/matrix-js-sdk/pull/1596)
* Fix call ID NaN
[\#1595](https://github.com/matrix-org/matrix-js-sdk/pull/1595)
* Fix Electron type merging
[\#1594](https://github.com/matrix-org/matrix-js-sdk/pull/1594)
* Fix browser screen share
[\#1593](https://github.com/matrix-org/matrix-js-sdk/pull/1593)
* Fix desktop Matrix screen sharing
[\#1570](https://github.com/matrix-org/matrix-js-sdk/pull/1570)
* Guard against confused server retry times
[\#1591](https://github.com/matrix-org/matrix-js-sdk/pull/1591)
* Decrypt redaction events
[\#1589](https://github.com/matrix-org/matrix-js-sdk/pull/1589)
* Fix edge cases with peeking where a room is re-peeked
[\#1587](https://github.com/matrix-org/matrix-js-sdk/pull/1587)
Changes in [9.6.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.6.0) (2021-02-03)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.6.0-rc.1...v9.6.0)
* [Release] Fix edge cases with peeking where a room is re-peeked
[\#1588](https://github.com/matrix-org/matrix-js-sdk/pull/1588)
Changes in [9.6.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.6.0-rc.1) (2021-01-29)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.5.1...v9.6.0-rc.1)
* Add support for getting call stats
[\#1584](https://github.com/matrix-org/matrix-js-sdk/pull/1584)
* Fix compatibility with v0 calls
[\#1583](https://github.com/matrix-org/matrix-js-sdk/pull/1583)
* Upgrade deps 2021-01
[\#1582](https://github.com/matrix-org/matrix-js-sdk/pull/1582)
* Log the call ID when logging that we've received VoIP events
[\#1581](https://github.com/matrix-org/matrix-js-sdk/pull/1581)
* Fix extra negotiate message in Firefox
[\#1579](https://github.com/matrix-org/matrix-js-sdk/pull/1579)
* Add debug logs to encryption prep
[\#1580](https://github.com/matrix-org/matrix-js-sdk/pull/1580)
* Expose getPresence endpoint
[\#1578](https://github.com/matrix-org/matrix-js-sdk/pull/1578)
* Queue keys for backup even if backup isn't enabled yet
[\#1577](https://github.com/matrix-org/matrix-js-sdk/pull/1577)
* Stop retrying TURN access when forbidden
[\#1576](https://github.com/matrix-org/matrix-js-sdk/pull/1576)
* Add DTMF sending support
[\#1573](https://github.com/matrix-org/matrix-js-sdk/pull/1573)
Changes in [9.5.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.5.1) (2021-01-26)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.5.0...v9.5.1)
* [Release] Fix compatibility with v0 calls
[\#1585](https://github.com/matrix-org/matrix-js-sdk/pull/1585)
Changes in [9.5.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.5.0) (2021-01-18)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.5.0-rc.1...v9.5.0)
* No changes since rc.1
Changes in [9.5.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.5.0-rc.1) (2021-01-13)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.1...v9.5.0-rc.1)
* Don't log if no WebRTC
[\#1574](https://github.com/matrix-org/matrix-js-sdk/pull/1574)
* Add _unstable_getSharedRooms
[\#1417](https://github.com/matrix-org/matrix-js-sdk/pull/1417)
* Bump node-notifier from 8.0.0 to 8.0.1
[\#1568](https://github.com/matrix-org/matrix-js-sdk/pull/1568)
* Ignore party ID if opponent is v0
[\#1567](https://github.com/matrix-org/matrix-js-sdk/pull/1567)
* Basic call transfer initiation support
[\#1566](https://github.com/matrix-org/matrix-js-sdk/pull/1566)
* Room version 6 is now a thing
[\#1572](https://github.com/matrix-org/matrix-js-sdk/pull/1572)
* Store keys with same index but better trust level
[\#1571](https://github.com/matrix-org/matrix-js-sdk/pull/1571)
* Use TypeScript source for development, swap to build during release
[\#1561](https://github.com/matrix-org/matrix-js-sdk/pull/1561)
* Revert "Ignore party ID if opponent is v0"
[\#1565](https://github.com/matrix-org/matrix-js-sdk/pull/1565)
* Basic call transfer initiation support
[\#1558](https://github.com/matrix-org/matrix-js-sdk/pull/1558)
* Ignore party ID if opponent is v0
[\#1559](https://github.com/matrix-org/matrix-js-sdk/pull/1559)
* Honour a call reject event from another of our own devices
[\#1562](https://github.com/matrix-org/matrix-js-sdk/pull/1562)
Changes in [9.4.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.1) (2020-12-21)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.0...v9.4.1)
* Further script tweaks to get all layers building again
Changes in [9.4.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.0) (2020-12-21)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.0-rc.2...v9.4.0)
* Revert `postinstall` script change, causes issues for other layers
Changes in [9.4.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.0-rc.2) (2020-12-16)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.0-rc.1...v9.4.0-rc.2)
* Remove `postinstall` script which also runs as a dependency
[\#1560](https://github.com/matrix-org/matrix-js-sdk/pull/1560)
Changes in [9.4.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.0-rc.1) (2020-12-16)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.3.0...v9.4.0-rc.1)
* Fixes to support line 1 / 2
[\#1553](https://github.com/matrix-org/matrix-js-sdk/pull/1553)
* Add API for listening to remote hold status, advertise VoIP V1
[\#1549](https://github.com/matrix-org/matrix-js-sdk/pull/1549)
* A hangup from another client is still valid
[\#1555](https://github.com/matrix-org/matrix-js-sdk/pull/1555)
* Remove temporary build step for tests
[\#1554](https://github.com/matrix-org/matrix-js-sdk/pull/1554)
* Move browser build steps to prepublish only
[\#1552](https://github.com/matrix-org/matrix-js-sdk/pull/1552)
* Extend getSsoLoginUrl for MSC2858
[\#1541](https://github.com/matrix-org/matrix-js-sdk/pull/1541)
Changes in [9.3.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.3.0) (2020-12-07)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.3.0-rc.1...v9.3.0)
* No changes since rc.1
Changes in [9.3.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.3.0-rc.1) (2020-12-02)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.2.0...v9.3.0-rc.1)
* Export CallError
[\#1551](https://github.com/matrix-org/matrix-js-sdk/pull/1551)
* Upgrade dependencies
[\#1550](https://github.com/matrix-org/matrix-js-sdk/pull/1550)
* Don't log error when environment does not support WebRTC
[\#1547](https://github.com/matrix-org/matrix-js-sdk/pull/1547)
* Fix dehydration method name
[\#1544](https://github.com/matrix-org/matrix-js-sdk/pull/1544)
Changes in [9.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.2.0) (2020-11-23)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.2.0-rc.1...v9.2.0)
* [Release] Fix dehydration method name
[\#1545](https://github.com/matrix-org/matrix-js-sdk/pull/1545)
Changes in [9.2.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.2.0-rc.1) (2020-11-18)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.1.0...v9.2.0-rc.1)
* Implement call holding functionality
[\#1532](https://github.com/matrix-org/matrix-js-sdk/pull/1532)
* Support awaitable one-time dehydration
[\#1537](https://github.com/matrix-org/matrix-js-sdk/pull/1537)
* Client set profile methods update own user
[\#1534](https://github.com/matrix-org/matrix-js-sdk/pull/1534)
Changes in [9.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.1.0) (2020-11-09)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.1.0-rc.1...v9.1.0)
* No changes since rc.1
Changes in [9.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.1.0-rc.1) (2020-11-04)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.0.1...v9.1.0-rc.1)
* Fix spelling error in the server ACL event type
[\#1535](https://github.com/matrix-org/matrix-js-sdk/pull/1535)
* await idb operations from crypto store for dehydration
[\#1533](https://github.com/matrix-org/matrix-js-sdk/pull/1533)
* Fix stuck never-sending messages
[\#1531](https://github.com/matrix-org/matrix-js-sdk/pull/1531)
* Await key cache check to avoid prompts
[\#1529](https://github.com/matrix-org/matrix-js-sdk/pull/1529)
* Improve ICE candidate batching
[\#1524](https://github.com/matrix-org/matrix-js-sdk/pull/1524)
* Convert logger to typescript
[\#1527](https://github.com/matrix-org/matrix-js-sdk/pull/1527)
* Fix logger typo
[\#1525](https://github.com/matrix-org/matrix-js-sdk/pull/1525)
* bind online listener to window instead of document
[\#1523](https://github.com/matrix-org/matrix-js-sdk/pull/1523)
* Support m.call.select_answer
[\#1522](https://github.com/matrix-org/matrix-js-sdk/pull/1522)
Changes in [9.0.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.0.1) (2020-10-28)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.0.0...v9.0.1)
* [Release] Await key cache check to avoid prompts
[\#1530](https://github.com/matrix-org/matrix-js-sdk/pull/1530)
Changes in [9.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.0.0) (2020-10-26)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.0.0-rc.1...v9.0.0)
* Fix logger typo
[\#1528](https://github.com/matrix-org/matrix-js-sdk/pull/1528)
Changes in [9.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.0.0-rc.1) (2020-10-21)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v8.5.0...v9.0.0-rc.1)
BREAKING CHANGES
---
* `hasPendingEvent` now returns false instead of throwing when pending ordering mode is not `detached`
All changes
---
* Don't cache failures when fetching /versions
[\#1521](https://github.com/matrix-org/matrix-js-sdk/pull/1521)
* Install deps first as part of release
[\#1518](https://github.com/matrix-org/matrix-js-sdk/pull/1518)
* [Breaking] Change hasPendingEvent to return false if pending ordering
!detached
[\#1517](https://github.com/matrix-org/matrix-js-sdk/pull/1517)
* Skip editor prompts for merges
[\#1519](https://github.com/matrix-org/matrix-js-sdk/pull/1519)
* Convert call test to TypeScript
[\#1516](https://github.com/matrix-org/matrix-js-sdk/pull/1516)
* Support party_id
[\#1512](https://github.com/matrix-org/matrix-js-sdk/pull/1512)
* Support m.call.reject
[\#1510](https://github.com/matrix-org/matrix-js-sdk/pull/1510)
* Remove specbuild from .gitignore
[\#1515](https://github.com/matrix-org/matrix-js-sdk/pull/1515)
* Log the error when we failed to send candidates
[\#1514](https://github.com/matrix-org/matrix-js-sdk/pull/1514)
* Fixes for call state machine
[\#1503](https://github.com/matrix-org/matrix-js-sdk/pull/1503)
* Fix call event handler listener removing
[\#1506](https://github.com/matrix-org/matrix-js-sdk/pull/1506)
* Set the type of the call based on the tracks
[\#1501](https://github.com/matrix-org/matrix-js-sdk/pull/1501)
* Use new local timestamp for calls
[\#1499](https://github.com/matrix-org/matrix-js-sdk/pull/1499)
* Adjust types and APIs to match React SDK
[\#1502](https://github.com/matrix-org/matrix-js-sdk/pull/1502)
* Make an accurate version of 'age' for events
[\#1495](https://github.com/matrix-org/matrix-js-sdk/pull/1495)
* Make 'options' parameter optional
[\#1498](https://github.com/matrix-org/matrix-js-sdk/pull/1498)
* Create a giant event type enum
[\#1497](https://github.com/matrix-org/matrix-js-sdk/pull/1497)
* Convert call.js to Typescript & update WebRTC APIs (re-apply)
[\#1494](https://github.com/matrix-org/matrix-js-sdk/pull/1494)
Changes in [8.5.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v8.5.0) (2020-10-12)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v8.5.0-rc.1...v8.5.0)
+1 -1
View File
@@ -307,7 +307,7 @@ The SDK supports end-to-end encryption via the Olm and Megolm protocols, using
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
application to make libolm available, via the ``Olm`` global.
It is also necessry to call ``matrixClient.initCrypto()`` after creating a new
It is also necessary to call ``matrixClient.initCrypto()`` after creating a new
``MatrixClient`` (but **before** calling ``matrixClient.startClient()``) to
initialise the crypto layer.
+18 -4
View File
@@ -31,6 +31,23 @@ function addListeners(call) {
call.hangup();
disableButtons(false, true, true);
});
call.on("feeds_changed", function(feeds) {
const localFeed = feeds.find((feed) => feed.isLocal());
const remoteFeed = feeds.find((feed) => !feed.isLocal());
const remoteElement = document.getElementById("remote");
const localElement = document.getElementById("local");
if (remoteFeed) {
remoteElement.srcObject = remoteFeed.stream;
remoteElement.play();
}
if (localFeed) {
localElement.muted = true;
localElement.srcObject = localFeed.stream;
localElement.play();
}
});
}
window.onload = function() {
@@ -62,10 +79,7 @@ function syncComplete() {
);
console.log("Call => %s", call);
addListeners(call);
call.placeVideoCall(
document.getElementById("remote"),
document.getElementById("local")
);
call.placeVideoCall();
document.getElementById("result").innerHTML = "<p>Placed call.</p>";
disableButtons(true, true, false);
};
+21 -13
View File
@@ -1,26 +1,34 @@
<html>
<head>
<title>VoIP Test</title>
<script src="lib/matrix.js"></script>
<script src="browserTest.js"></script>
<title>VoIP Test</title>
<script src="lib/matrix.js"></script>
<script src="browserTest.js"></script>
</head>
<body>
You can place and receive calls with this example. Make sure to edit the
You can place and receive calls with this example. Make sure to edit the
constants in <code>browserTest.js</code> first.
<div id="config"></div>
<div id="result"></div>
<button id="call">Place Call</button>
<button id="answer">Answer Call</button>
<button id="hangup">Hangup Call</button>
<div id="videoBackground">
<div id="videoContainer">
<video id="remote"></video>
</div>
</div>
<div id="videoBackground">
<div id="videoContainer">
<video id="local"></video>
</div>
<div id="videoBackground" class="video-background">
<video class="video-element" id="local"></video>
<video class="video-element" id="remote"></video>
</div>
</body>
</html>
<style>
.video-background {
height: 500px;
margin: 10px;
}
.video-element {
height: 100%;
}
</style>
+50 -33
View File
@@ -1,23 +1,25 @@
{
"name": "matrix-js-sdk",
"version": "8.5.0",
"version": "12.0.0",
"description": "Matrix Client-Server SDK for Javascript",
"scripts": {
"prepare": "yarn build",
"prepublishOnly": "yarn build",
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
"dist": "echo 'This is for the release script so it can make assets (browser bundle).' && yarn build",
"clean": "rimraf lib dist",
"build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:compile-browser && yarn build:minify-browser && yarn build:types",
"build": "yarn build:dev && yarn build:compile-browser && yarn build:minify-browser",
"build:dev": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types",
"build:types": "tsc --emitDeclarationOnly",
"build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src",
"build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
"build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js",
"gendoc": "jsdoc -c jsdoc.json -P package.json",
"lint": "yarn lint:types && yarn lint:js",
"lint:js": "eslint --max-warnings 76 src spec",
"lint:js": "eslint --max-warnings 57 src spec",
"lint:types": "tsc --noEmit",
"test": "jest spec/ --coverage --testEnvironment node",
"test:watch": "jest spec/ --coverage --testEnvironment node --watch"
"test": "jest",
"test:watch": "jest --watch",
"coverage": "yarn test --coverage"
},
"repository": {
"type": "git",
@@ -27,10 +29,11 @@
"matrix-org"
],
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
"browser": "./lib/browser-index.js",
"matrix_src_main": "./src/index.ts",
"matrix_src_browser": "./src/browser-index.js",
"matrix_lib_main": "./lib/index.js",
"matrix_lib_typings": "./lib/index.d.ts",
"author": "matrix.org",
"license": "Apache-2.0",
"files": [
@@ -46,51 +49,65 @@
"release.sh"
],
"dependencies": {
"@babel/runtime": "^7.11.2",
"@babel/runtime": "^7.12.5",
"another-json": "^0.2.0",
"browser-request": "^0.3.3",
"bs58": "^4.0.1",
"content-type": "^1.0.4",
"loglevel": "^1.7.0",
"qs": "^6.9.4",
"loglevel": "^1.7.1",
"qs": "^6.9.6",
"request": "^2.88.2",
"unhomoglyph": "^1.0.6"
},
"devDependencies": {
"@babel/cli": "^7.11.6",
"@babel/core": "^7.11.6",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-proposal-numeric-separator": "^7.10.4",
"@babel/plugin-proposal-object-rest-spread": "^7.11.0",
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/eslint-parser": "^7.12.10",
"@babel/eslint-plugin": "^7.12.10",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-numeric-separator": "^7.12.7",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@babel/preset-typescript": "^7.10.4",
"@babel/register": "^7.11.5",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-typescript": "^7.12.7",
"@babel/register": "^7.12.10",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@types/jest": "^26.0.20",
"@types/node": "12",
"@types/request": "^2.48.5",
"babel-eslint": "^10.1.0",
"babel-jest": "^24.9.0",
"@typescript-eslint/eslint-plugin": "^4.17.0",
"@typescript-eslint/parser": "^4.17.0",
"babel-jest": "^26.6.3",
"babelify": "^10.0.0",
"better-docs": "^2.3.2",
"browserify": "^16.5.2",
"browserify": "^17.0.0",
"docdash": "^1.2.0",
"eslint": "7.9.0",
"eslint-config-matrix-org": "^0.1.2",
"eslint-plugin-babel": "^5.3.1",
"eslint": "7.18.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#main",
"exorcist": "^1.0.1",
"fake-indexeddb": "^3.1.2",
"jest": "^24.9.0",
"jest-localstorage-mock": "^2.4.3",
"jest": "^26.6.3",
"jest-localstorage-mock": "^2.4.6",
"jsdoc": "^3.6.6",
"matrix-mock-request": "^1.2.3",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"rimraf": "^3.0.2",
"terser": "^4.8.0",
"tsify": "^4.0.2",
"typescript": "^3.9.7"
"terser": "^5.5.1",
"tsify": "^5.0.2",
"typescript": "^4.1.3"
},
"jest": {
"testEnvironment": "node"
}
"testEnvironment": "node",
"testMatch": [
"<rootDir>/spec/**/*.spec.{js,ts}"
],
"collectCoverageFrom": [
"<rootDir>/src/**/*.{js,ts}"
],
"coverageReporters": [
"text"
]
},
"typings": "./lib/index.d.ts"
}
+48 -7
View File
@@ -94,6 +94,14 @@ if [ $# -ne 1 ]; then
exit 1
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
# global cache here to ensure we get the right thing.
yarn cache clean
# Ensure all dependencies are updated
yarn install --ignore-scripts
if [ -z "$skip_changelog" ]; then
# update_changelog doesn't have a --version flag
update_changelog -h > /dev/null || (echo "github-changelog-generator is required: please install it"; exit)
@@ -170,6 +178,19 @@ echo "yarn version"
# manually commit the result.
yarn version --no-git-tag-version --new-version "$release"
# For the published and dist versions of the package, we copy the
# `matrix_lib_main` and `matrix_lib_typings` fields to `main` and `typings` (if
# they exist). This small bit of gymnastics allows us to use the TypeScript
# source directly for development without needing to build before linting or
# testing.
for i in main typings
do
lib_value=$(jq -r ".matrix_lib_$i" package.json)
if [ "$lib_value" != "null" ]; then
jq ".$i = .matrix_lib_$i" package.json > package.json.new && mv package.json.new package.json
fi
done
# commit yarn.lock if it exists, is versioned, and is modified
if [[ -f yarn.lock && `git status --porcelain yarn.lock | grep '^ M'` ]];
then
@@ -204,11 +225,6 @@ if [ $dodist -eq 0 ]; then
pushd "$builddir"
git clone "$projdir" .
git checkout "$rel_branch"
# 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
# global cache here to ensure we get the right thing.
yarn cache clean
yarn install
# We haven't tagged yet, so tell the dist script what version
# it's building
@@ -340,7 +356,7 @@ fi
echo "updating master branch"
git checkout master
git pull
git merge "$rel_branch"
git merge "$rel_branch" --no-edit
# push master to github
git push origin master
@@ -349,6 +365,31 @@ git push origin master
if [ $(git branch -lr | grep origin/develop -c) -ge 1 ]; then
git checkout develop
git pull
git merge master
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
+27
View File
@@ -0,0 +1,27 @@
/*
Copyright 2021 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.
*/
export class MockBlob {
private contents: number[] = [];
public constructor(private parts: ArrayLike<number>[]) {
parts.forEach(p => Array.from(p).forEach(e => this.contents.push(e)));
}
public get size(): number {
return this.contents.length;
}
}
+18 -14
View File
@@ -20,12 +20,12 @@ limitations under the License.
import './olm-loader';
import MockHttpBackend from 'matrix-mock-request';
import {LocalStorageCryptoStore} from '../src/crypto/store/localStorage-crypto-store';
import {logger} from '../src/logger';
import {WebStorageSessionStore} from "../src/store/session/webstorage";
import {syncPromise} from "./test-utils";
import {createClient} from "../src/matrix";
import {MockStorageApi} from "./MockStorageApi";
import { LocalStorageCryptoStore } from '../src/crypto/store/localStorage-crypto-store';
import { logger } from '../src/logger';
import { WebStorageSessionStore } from "../src/store/session/webstorage";
import { syncPromise } from "./test-utils";
import { createClient } from "../src/matrix";
import { MockStorageApi } from "./MockStorageApi";
/**
* Wrapper for a MockStorageApi, MockHttpBackend and MatrixClient
@@ -69,6 +69,9 @@ export function TestClient(
this.deviceKeys = null;
this.oneTimeKeys = {};
this.callEventHandler = {
calls: new Map(),
};
}
TestClient.prototype.toString = function() {
@@ -126,11 +129,10 @@ TestClient.prototype.expectDeviceKeyUpload = function() {
expect(Object.keys(self.oneTimeKeys).length).toEqual(0);
self.deviceKeys = content.device_keys;
return {one_time_key_counts: {signed_curve25519: 0}};
return { one_time_key_counts: { signed_curve25519: 0 } };
});
};
/**
* If one-time keys have already been uploaded, return them. Otherwise,
* set up an expectation that the keys will be uploaded, and wait for
@@ -148,9 +150,9 @@ TestClient.prototype.awaitOneTimeKeyUpload = function() {
.respond(200, (path, content) => {
expect(content.device_keys).toBe(undefined);
expect(content.one_time_keys).toBe(undefined);
return {one_time_key_counts: {
return { one_time_key_counts: {
signed_curve25519: Object.keys(this.oneTimeKeys).length,
}};
} };
});
this.httpBackend.when("POST", "/keys/upload")
@@ -161,9 +163,9 @@ TestClient.prototype.awaitOneTimeKeyUpload = function() {
logger.log('%s: received %i one-time keys', this,
Object.keys(content.one_time_keys).length);
this.oneTimeKeys = content.one_time_keys;
return {one_time_key_counts: {
return { one_time_key_counts: {
signed_curve25519: Object.keys(this.oneTimeKeys).length,
}};
} };
});
// this can take ages
@@ -194,7 +196,6 @@ TestClient.prototype.expectKeyQuery = function(response) {
});
};
/**
* get the uploaded curve25519 device key
*
@@ -205,7 +206,6 @@ TestClient.prototype.getDeviceKey = function() {
return this.deviceKeys.keys[keyId];
};
/**
* get the uploaded ed25519 device key
*
@@ -230,3 +230,7 @@ TestClient.prototype.flushSync = function() {
logger.log(`${this}: flushSync completed`);
});
};
TestClient.prototype.isFallbackICEServerAllowed = function() {
return true;
};
+4 -4
View File
@@ -17,10 +17,10 @@ limitations under the License.
// load XmlHttpRequest mock
import "./setupTests";
import "../../dist/browser-matrix"; // uses browser-matrix instead of the src
import {MockStorageApi} from "../MockStorageApi";
import {WebStorageSessionStore} from "../../src/store/session/webstorage";
import { MockStorageApi } from "../MockStorageApi";
import { WebStorageSessionStore } from "../../src/store/session/webstorage";
import MockHttpBackend from "matrix-mock-request";
import {LocalStorageCryptoStore} from "../../src/crypto/store/localStorage-crypto-store";
import { LocalStorageCryptoStore } from "../../src/crypto/store/localStorage-crypto-store";
import * as utils from "../test-utils";
const USER_ID = "@user:test.server";
@@ -58,7 +58,7 @@ describe("Browserify Test", function() {
}
beforeEach(async () => {
({client, httpBackend} = await createTestClient());
({ client, httpBackend } = await createTestClient());
await client.startClient();
});
+15 -18
View File
@@ -16,9 +16,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {TestClient} from '../TestClient';
import { TestClient } from '../TestClient';
import * as testUtils from '../test-utils';
import {logger} from '../../src/logger';
import { logger } from '../../src/logger';
const ROOM_ID = "!room:id";
@@ -67,7 +67,6 @@ function getSyncResponse(roomMembers) {
return syncResponse;
}
describe("DeviceList management:", function() {
if (!global.Olm) {
logger.warn('not running deviceList tests: Olm not present');
@@ -98,7 +97,7 @@ describe("DeviceList management:", function() {
});
it("Alice shouldn't do a second /query for non-e2e-capable devices", function() {
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} } });
return aliceTestClient.start().then(function() {
const syncResponse = getSyncResponse(['@bob:xyz']);
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
@@ -137,11 +136,10 @@ describe("DeviceList management:", function() {
});
});
it("We should not get confused by out-of-order device query responses",
() => {
// https://github.com/vector-im/element-web/issues/3126
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} } });
return aliceTestClient.start().then(() => {
aliceTestClient.httpBackend.when('GET', '/sync').respond(
200, getSyncResponse(['@bob:xyz', '@chris:abc']));
@@ -160,14 +158,14 @@ describe("DeviceList management:", function() {
);
aliceTestClient.httpBackend.when('PUT', '/send/').respond(
200, {event_id: '$event1'});
200, { event_id: '$event1' });
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
aliceTestClient.httpBackend.flush('/keys/query', 1).then(
() => aliceTestClient.httpBackend.flush('/send/', 1),
),
aliceTestClient.client._crypto._deviceList.saveIfDirty(),
aliceTestClient.client.crypto._deviceList.saveIfDirty(),
]);
}).then(() => {
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
@@ -199,12 +197,12 @@ describe("DeviceList management:", function() {
},
token: '3',
}).respond(200, {
device_keys: {'@chris:abc': {}},
device_keys: { '@chris:abc': {} },
});
return aliceTestClient.httpBackend.flush('/keys/query', 1);
}).then((flushed) => {
expect(flushed).toEqual(0);
return aliceTestClient.client._crypto._deviceList.saveIfDirty();
return aliceTestClient.client.crypto._deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -228,7 +226,7 @@ describe("DeviceList management:", function() {
},
token: '2',
}).respond(200, {
device_keys: {'@bob:xyz': {}},
device_keys: { '@bob:xyz': {} },
});
return aliceTestClient.httpBackend.flush('/keys/query', 1);
}).then((flushed) => {
@@ -237,7 +235,7 @@ describe("DeviceList management:", function() {
// wait for the client to stop processing the response
return aliceTestClient.client.downloadKeys(['@bob:xyz']);
}).then(() => {
return aliceTestClient.client._crypto._deviceList.saveIfDirty();
return aliceTestClient.client.crypto._deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -258,7 +256,7 @@ describe("DeviceList management:", function() {
// wait for the client to stop processing the response
return aliceTestClient.client.downloadKeys(['@chris:abc']);
}).then(() => {
return aliceTestClient.client._crypto._deviceList.saveIfDirty();
return aliceTestClient.client.crypto._deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -288,7 +286,7 @@ describe("DeviceList management:", function() {
},
);
await aliceTestClient.httpBackend.flush('/keys/query', 1);
await aliceTestClient.client._crypto._deviceList.saveIfDirty();
await aliceTestClient.client.crypto._deviceList.saveIfDirty();
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -323,9 +321,8 @@ describe("DeviceList management:", function() {
},
);
await aliceTestClient.flushSync();
await aliceTestClient.client._crypto._deviceList.saveIfDirty();
await aliceTestClient.client.crypto._deviceList.saveIfDirty();
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -361,7 +358,7 @@ describe("DeviceList management:", function() {
);
await aliceTestClient.flushSync();
await aliceTestClient.client._crypto._deviceList.saveIfDirty();
await aliceTestClient.client.crypto._deviceList.saveIfDirty();
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -382,7 +379,7 @@ describe("DeviceList management:", function() {
anotherTestClient.httpBackend.when('GET', '/sync').respond(
200, getSyncResponse([]));
await anotherTestClient.flushSync();
await anotherTestClient.client._crypto._deviceList.saveIfDirty();
await anotherTestClient.client.crypto._deviceList.saveIfDirty();
anotherTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
+20 -25
View File
@@ -28,11 +28,10 @@ limitations under the License.
// load olm before the sdk if possible
import '../olm-loader';
import {logger} from '../../src/logger';
import { logger } from '../../src/logger';
import * as testUtils from "../test-utils";
import * as utils from "../../src/utils";
import {TestClient} from "../TestClient";
import {CRYPTO_ENABLED} from "../../src/client";
import { TestClient } from "../TestClient";
import { CRYPTO_ENABLED } from "../../src/client";
let aliTestClient;
const roomId = "!room:localhost";
@@ -76,7 +75,7 @@ function expectAliQueryKeys() {
);
const result = {};
result[bobUserId] = bobKeys;
return {device_keys: result};
return { device_keys: result };
});
return aliTestClient.httpBackend.flush("/keys/query", 1);
}
@@ -104,7 +103,7 @@ function expectBobQueryKeys() {
);
const result = {};
result[aliUserId] = aliKeys;
return {device_keys: result};
return { device_keys: result };
});
return bobTestClient.httpBackend.flush("/keys/query", 1);
}
@@ -133,7 +132,7 @@ function expectAliClaimKeys() {
result[bobUserId] = {};
result[bobUserId][bobDeviceId] = {};
result[bobUserId][bobDeviceId][keyId] = keys[keyId];
return {one_time_keys: result};
return { one_time_keys: result };
});
}).then(() => {
// it can take a while to process the key query, so give it some extra
@@ -145,7 +144,6 @@ function expectAliClaimKeys() {
});
}
function aliDownloadsKeys() {
// can't query keys before bob has uploaded them
expect(bobTestClient.getSigningKey()).toBeTruthy();
@@ -161,7 +159,7 @@ function aliDownloadsKeys() {
// check that the localStorage is updated as we expect (not sure this is
// an integration test, but meh)
return Promise.all([p1, p2]).then(() => {
return aliTestClient.client._crypto._deviceList.saveIfDirty();
return aliTestClient.client.crypto._deviceList.saveIfDirty();
}).then(() => {
aliTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const devices = data.devices[bobUserId];
@@ -244,7 +242,7 @@ function bobSendsReplyMessage() {
function expectAliSendMessageRequest() {
return expectSendMessageRequest(aliTestClient.httpBackend).then(function(content) {
aliMessages.push(content);
expect(utils.keys(content.ciphertext)).toEqual([bobTestClient.getDeviceKey()]);
expect(Object.keys(content.ciphertext)).toEqual([bobTestClient.getDeviceKey()]);
const ciphertext = content.ciphertext[bobTestClient.getDeviceKey()];
expect(ciphertext).toBeTruthy();
return ciphertext;
@@ -261,7 +259,7 @@ function expectBobSendMessageRequest() {
bobMessages.push(content);
const aliKeyId = "curve25519:" + aliDeviceId;
const aliDeviceCurve25519Key = aliTestClient.deviceKeys.keys[aliKeyId];
expect(utils.keys(content.ciphertext)).toEqual([aliDeviceCurve25519Key]);
expect(Object.keys(content.ciphertext)).toEqual([aliDeviceCurve25519Key]);
const ciphertext = content.ciphertext[aliDeviceCurve25519Key];
expect(ciphertext).toBeTruthy();
return ciphertext;
@@ -270,7 +268,7 @@ function expectBobSendMessageRequest() {
function sendMessage(client) {
return client.sendMessage(
roomId, {msgtype: "m.text", body: "Hello, World"},
roomId, { msgtype: "m.text", body: "Hello, World" },
);
}
@@ -358,7 +356,6 @@ function recvMessage(httpBackend, client, sender, message) {
});
}
/**
* Send an initial sync response to the client (which just includes the member
* list for our test room).
@@ -396,7 +393,6 @@ function firstSync(testClient) {
return testClient.flushSync();
}
describe("MatrixClient crypto", function() {
if (!CRYPTO_ENABLED) {
return;
@@ -478,7 +474,7 @@ describe("MatrixClient crypto", function() {
).respond(200, function(path, content) {
const result = {};
result[bobUserId] = bobKeys;
return {device_keys: result};
return { device_keys: result };
});
return Promise.all([
@@ -520,7 +516,7 @@ describe("MatrixClient crypto", function() {
).respond(200, function(path, content) {
const result = {};
result[bobUserId] = bobKeys;
return {device_keys: result};
return { device_keys: result };
});
return Promise.all([
@@ -534,7 +530,6 @@ describe("MatrixClient crypto", function() {
});
});
it("Bob starts his client and uploads device keys and one-time keys", function() {
return Promise.resolve()
.then(() => bobTestClient.start())
@@ -546,7 +541,7 @@ describe("MatrixClient crypto", function() {
});
it("Ali sends a message", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } });
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
@@ -556,7 +551,7 @@ describe("MatrixClient crypto", function() {
});
it("Bob receives a message", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } });
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
@@ -567,7 +562,7 @@ describe("MatrixClient crypto", function() {
});
it("Bob receives a message with a bogus sender", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } });
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
@@ -621,7 +616,7 @@ describe("MatrixClient crypto", function() {
});
it("Ali blocks Bob's device", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } });
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
@@ -641,7 +636,7 @@ describe("MatrixClient crypto", function() {
});
it("Bob receives two pre-key messages", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } });
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
@@ -654,8 +649,8 @@ describe("MatrixClient crypto", function() {
});
it("Bob replies to the message", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
bobTestClient.expectKeyQuery({device_keys: {[bobUserId]: {}}});
aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } });
bobTestClient.expectKeyQuery({ device_keys: { [bobUserId]: {} } });
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
@@ -673,7 +668,7 @@ describe("MatrixClient crypto", function() {
it("Ali does a key query when encryption is enabled", function() {
// enabling encryption in the room should make alice download devices
// for both members.
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
aliTestClient.expectKeyQuery({ device_keys: { [aliUserId]: {} } });
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => firstSync(aliTestClient))
@@ -1,5 +1,5 @@
import * as utils from "../test-utils";
import {TestClient} from "../TestClient";
import { TestClient } from "../TestClient";
describe("MatrixClient events", function() {
let client;
@@ -1,7 +1,7 @@
import * as utils from "../test-utils";
import {EventTimeline} from "../../src/matrix";
import {logger} from "../../src/logger";
import {TestClient} from "../TestClient";
import { EventTimeline } from "../../src/matrix";
import { logger } from "../../src/logger";
import { TestClient } from "../TestClient";
const userId = "@alice:localhost";
const userName = "Alice";
@@ -127,7 +127,7 @@ describe("getEventTimeline support", function() {
"DEVICE",
accessToken,
undefined,
{timelineSupport: true},
{ timelineSupport: true },
);
client = testClient.client;
httpBackend = testClient.httpBackend;
@@ -141,7 +141,6 @@ describe("getEventTimeline support", function() {
});
});
it("scrollback should be able to scroll back to before a gappy /sync",
function() {
// need a client with timelineSupport disabled to make this work
@@ -218,7 +217,7 @@ describe("MatrixClient event timelines", function() {
"DEVICE",
accessToken,
undefined,
{timelineSupport: true},
{ timelineSupport: true },
);
client = testClient.client;
httpBackend = testClient.httpBackend;
@@ -516,7 +515,7 @@ describe("MatrixClient event timelines", function() {
client.getEventTimeline(timelineSet, EVENTS[0].event_id,
).then(function(tl0) {
tl = tl0;
return client.paginateEventTimeline(tl, {backwards: true});
return client.paginateEventTimeline(tl, { backwards: true });
}).then(function(success) {
expect(success).toBeTruthy();
expect(tl.getEvents().length).toEqual(3);
@@ -532,7 +531,6 @@ describe("MatrixClient event timelines", function() {
]);
});
it("should allow you to paginate forwards", function() {
const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0];
@@ -569,7 +567,7 @@ describe("MatrixClient event timelines", function() {
).then(function(tl0) {
tl = tl0;
return client.paginateEventTimeline(
tl, {backwards: false, limit: 20});
tl, { backwards: false, limit: 20 });
}).then(function(success) {
expect(success).toBeTruthy();
expect(tl.getEvents().length).toEqual(3);
@@ -591,7 +589,7 @@ describe("MatrixClient event timelines", function() {
const event = utils.mkMessage({
room: roomId, user: userId, msg: "a body",
});
event.unsigned = {transaction_id: TXN_ID};
event.unsigned = { transaction_id: TXN_ID };
beforeEach(function() {
// set up handlers for both the message send, and the
@@ -680,7 +678,6 @@ describe("MatrixClient event timelines", function() {
});
});
it("should handle gappy syncs after redactions", function() {
// https://github.com/vector-im/vector-web/issues/1389
+8 -9
View File
@@ -1,7 +1,7 @@
import * as utils from "../test-utils";
import {CRYPTO_ENABLED} from "../../src/client";
import {Filter, MemoryStore, Room} from "../../src/matrix";
import {TestClient} from "../TestClient";
import { CRYPTO_ENABLED } from "../../src/client";
import { Filter, MemoryStore, Room } from "../../src/matrix";
import { TestClient } from "../TestClient";
describe("MatrixClient", function() {
let client = null;
@@ -285,7 +285,6 @@ describe("MatrixClient", function() {
});
});
describe("downloadKeys", function() {
if (!CRYPTO_ENABLED) {
return;
@@ -337,7 +336,7 @@ describe("MatrixClient", function() {
var b = JSON.parse(JSON.stringify(o));
delete(b.signatures);
delete(b.unsigned);
return client._crypto._olmDevice.sign(anotherjson.stringify(b));
return client.crypto._olmDevice.sign(anotherjson.stringify(b));
};
logger.log("Ed25519: " + ed25519key);
@@ -346,10 +345,10 @@ describe("MatrixClient", function() {
*/
httpBackend.when("POST", "/keys/query").check(function(req) {
expect(req.data).toEqual({device_keys: {
expect(req.data).toEqual({ device_keys: {
'boris': [],
'chaz': [],
}});
} });
}).respond(200, {
device_keys: {
boris: borisKeys,
@@ -379,12 +378,12 @@ describe("MatrixClient", function() {
});
describe("deleteDevice", function() {
const auth = {a: 1};
const auth = { a: 1 };
it("should pass through an auth dict", function() {
httpBackend.when(
"DELETE", "/_matrix/client/r0/devices/my_device",
).check(function(req) {
expect(req.data).toEqual({auth: auth});
expect(req.data).toEqual({ auth: auth });
}).respond(200);
const prom = client.deleteDevice("my_device", auth);
+4 -4
View File
@@ -1,9 +1,9 @@
import * as utils from "../test-utils";
import HttpBackend from "matrix-mock-request";
import {MatrixClient} from "../../src/matrix";
import {MatrixScheduler} from "../../src/scheduler";
import {MemoryStore} from "../../src/store/memory";
import {MatrixError} from "../../src/http-api";
import { MatrixClient } from "../../src/matrix";
import { MatrixScheduler } from "../../src/scheduler";
import { MemoryStore } from "../../src/store/memory";
import { MatrixError } from "../../src/http-api";
describe("MatrixClient opts", function() {
const baseUrl = "http://localhost.or.something";
+5 -5
View File
@@ -1,7 +1,7 @@
import {EventStatus} from "../../src/matrix";
import {MatrixScheduler} from "../../src/scheduler";
import {Room} from "../../src/models/room";
import {TestClient} from "../TestClient";
import { EventStatus } from "../../src/matrix";
import { MatrixScheduler } from "../../src/scheduler";
import { Room } from "../../src/models/room";
import { TestClient } from "../TestClient";
describe("MatrixClient retrying", function() {
let client = null;
@@ -19,7 +19,7 @@ describe("MatrixClient retrying", function() {
"DEVICE",
accessToken,
undefined,
{scheduler},
{ scheduler },
);
httpBackend = testClient.httpBackend;
client = testClient.client;
+11 -12
View File
@@ -1,7 +1,6 @@
import * as utils from "../test-utils";
import {EventStatus} from "../../src/models/event";
import {TestClient} from "../TestClient";
import { EventStatus } from "../../src/models/event";
import { TestClient } from "../TestClient";
describe("MatrixClient room timelines", function() {
let client = null;
@@ -104,7 +103,7 @@ describe("MatrixClient room timelines", function() {
"DEVICE",
accessToken,
undefined,
{timelineSupport: true},
{ timelineSupport: true },
);
httpBackend = testClient.httpBackend;
client = testClient.client;
@@ -166,7 +165,7 @@ describe("MatrixClient room timelines", function() {
body: "I am a fish", user: userId, room: roomId,
});
ev.event_id = eventId;
ev.unsigned = {transaction_id: "txn1"};
ev.unsigned = { transaction_id: "txn1" };
setNextSyncData([ev]);
client.on("sync", function(state) {
@@ -198,7 +197,7 @@ describe("MatrixClient room timelines", function() {
body: "I am a fish", user: userId, room: roomId,
});
ev.event_id = eventId;
ev.unsigned = {transaction_id: "txn1"};
ev.unsigned = { transaction_id: "txn1" };
setNextSyncData([ev]);
client.on("sync", function(state) {
@@ -396,8 +395,8 @@ describe("MatrixClient room timelines", function() {
describe("new events", function() {
it("should be added to the right place in the timeline", function() {
const eventData = [
utils.mkMessage({user: userId, room: roomId}),
utils.mkMessage({user: userId, room: roomId}),
utils.mkMessage({ user: userId, room: roomId }),
utils.mkMessage({ user: userId, room: roomId }),
];
setNextSyncData(eventData);
@@ -434,11 +433,11 @@ describe("MatrixClient room timelines", function() {
it("should set the right event.sender values", function() {
const eventData = [
utils.mkMessage({user: userId, room: roomId}),
utils.mkMessage({ user: userId, room: roomId }),
utils.mkMembership({
user: userId, room: roomId, mship: "join", name: "New Name",
}),
utils.mkMessage({user: userId, room: roomId}),
utils.mkMessage({ user: userId, room: roomId }),
];
eventData[1].__prev_event = USER_MEMBERSHIP_EVENT;
setNextSyncData(eventData);
@@ -546,7 +545,7 @@ describe("MatrixClient room timelines", function() {
describe("gappy sync", function() {
it("should copy the last known state to the new timeline", function() {
const eventData = [
utils.mkMessage({user: userId, room: roomId}),
utils.mkMessage({ user: userId, room: roomId }),
];
setNextSyncData(eventData);
NEXT_SYNC_DATA.rooms.join[roomId].timeline.limited = true;
@@ -579,7 +578,7 @@ describe("MatrixClient room timelines", function() {
it("should emit a 'Room.timelineReset' event", function() {
const eventData = [
utils.mkMessage({user: userId, room: roomId}),
utils.mkMessage({ user: userId, room: roomId }),
];
setNextSyncData(eventData);
NEXT_SYNC_DATA.rooms.join[roomId].timeline.limited = true;
+5 -6
View File
@@ -1,7 +1,7 @@
import {MatrixEvent} from "../../src/models/event";
import {EventTimeline} from "../../src/models/event-timeline";
import { MatrixEvent } from "../../src/models/event";
import { EventTimeline } from "../../src/models/event-timeline";
import * as utils from "../test-utils";
import {TestClient} from "../TestClient";
import { TestClient } from "../TestClient";
describe("MatrixClient syncing", function() {
let client = null;
@@ -122,7 +122,6 @@ describe("MatrixClient syncing", function() {
resolveInvitesToProfiles: true,
});
return Promise.all([
httpBackend.flushAllExpected(),
awaitSyncEvent(),
@@ -677,8 +676,8 @@ describe("MatrixClient syncing", function() {
it("should create and use an appropriate filter", function() {
httpBackend.when("POST", "/filter").check(function(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) => {
+70 -16
View File
@@ -16,10 +16,9 @@ limitations under the License.
*/
import anotherjson from "another-json";
import * as utils from "../../src/utils";
import * as testUtils from "../test-utils";
import {TestClient} from "../TestClient";
import {logger} from "../../src/logger";
import { TestClient } from "../TestClient";
import { logger } from "../../src/logger";
const ROOM_ID = "!room:id";
@@ -32,7 +31,7 @@ const ROOM_ID = "!room:id";
*/
function createOlmSession(olmAccount, recipientTestClient) {
return recipientTestClient.awaitOneTimeKeyUpload().then((keys) => {
const otkId = utils.keys(keys)[0];
const otkId = Object.keys(keys)[0];
const otk = keys[otkId];
const session = new global.Olm.Session();
@@ -197,7 +196,6 @@ function getSyncResponse(roomMembers) {
return syncResponse;
}
describe("megolm", function() {
if (!global.Olm) {
logger.warn('not running megolm tests: Olm not present');
@@ -257,7 +255,7 @@ describe("megolm", function() {
const testOneTimeKeys = JSON.parse(testOlmAccount.one_time_keys());
testOlmAccount.mark_keys_as_published();
const keyId = utils.keys(testOneTimeKeys.curve25519)[0];
const keyId = Object.keys(testOneTimeKeys.curve25519)[0];
const oneTimeKey = testOneTimeKeys.curve25519[keyId];
const keyResult = {
'key': oneTimeKey,
@@ -269,7 +267,7 @@ describe("megolm", function() {
'ed25519:DEVICE_ID': sig,
};
const claimResponse = {one_time_keys: {}};
const claimResponse = { one_time_keys: {} };
claimResponse.one_time_keys[userId] = {
'DEVICE_ID': {},
};
@@ -484,8 +482,9 @@ describe("megolm", function() {
return aliceTestClient.flushSync().then(() => {
return aliceTestClient.flushSync();
});
}).then(function() {
}).then(async function() {
const room = aliceTestClient.client.getRoom(ROOM_ID);
await room.decryptCriticalEvents();
const event = room.getLiveTimeline().getEvents()[0];
expect(event.getContent().body).toEqual('42');
});
@@ -494,7 +493,7 @@ describe("megolm", function() {
it('Alice sends a megolm message', function() {
let p2pSession;
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} } });
return aliceTestClient.start().then(() => {
// establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient);
@@ -577,7 +576,7 @@ describe("megolm", function() {
});
it("We shouldn't attempt to send to blocked devices", function() {
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} } });
return aliceTestClient.start().then(() => {
// establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient);
@@ -634,7 +633,7 @@ describe("megolm", function() {
let p2pSession;
let megolmSessionId;
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} } });
return aliceTestClient.start().then(() => {
// establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient);
@@ -841,13 +840,12 @@ describe("megolm", function() {
});
});
it('Alice should wait for device list to complete when sending a megolm message',
function() {
let downloadPromise;
let sendPromise;
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} } });
return aliceTestClient.start().then(() => {
// establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient);
@@ -887,11 +885,10 @@ describe("megolm", function() {
});
});
it("Alice exports megolm keys and imports them to a new device", function() {
let messageEncrypted;
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} } });
return aliceTestClient.start().then(() => {
// establish an olm session with alice
return createOlmSession(testOlmAccount, aliceTestClient);
@@ -933,8 +930,9 @@ describe("megolm", function() {
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
return aliceTestClient.flushSync();
}).then(function() {
}).then(async function() {
const room = aliceTestClient.client.getRoom(ROOM_ID);
await room.decryptCriticalEvents();
const event = room.getLiveTimeline().getEvents()[0];
expect(event.getContent().body).toEqual('42');
@@ -971,4 +969,60 @@ describe("megolm", function() {
expect(event.getContent().body).toEqual('42');
});
});
it("Alice receives an untrusted megolm key, only to receive the trusted one shortly after", function() {
const testClient = new TestClient(
"@alice:localhost", "device2", "access_token2",
);
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const inboundGroupSession = new Olm.InboundGroupSession();
inboundGroupSession.create(groupSession.session_key());
const rawEvent = encryptMegolmEvent({
senderKey: testSenderKey,
groupSession: groupSession,
room_id: ROOM_ID,
});
return testClient.client.initCrypto().then(() => {
const keys = [{
room_id: ROOM_ID,
algorithm: 'm.megolm.v1.aes-sha2',
session_id: groupSession.session_id(),
session_key: inboundGroupSession.export_session(0),
sender_key: testSenderKey,
}];
return testClient.client.importRoomKeys(keys, { untrusted: true });
}).then(() => {
const event = testUtils.mkEvent({
event: true,
...rawEvent,
room: ROOM_ID,
});
return event.attemptDecryption(testClient.client.crypto, true).then(() => {
expect(event.isKeySourceUntrusted()).toBeTruthy();
});
}).then(() => {
const event = testUtils.mkEvent({
type: 'm.room_key',
content: {
room_id: ROOM_ID,
algorithm: 'm.megolm.v1.aes-sha2',
session_id: groupSession.session_id(),
session_key: groupSession.session_key(),
},
event: true,
});
event._senderCurve25519Key = testSenderKey;
return testClient.client.crypto._onRoomKeyEvent(event);
}).then(() => {
const event = testUtils.mkEvent({
event: true,
...rawEvent,
room: ROOM_ID,
});
return event.attemptDecryption(testClient.client.crypto, true).then(() => {
expect(event.isKeySourceUntrusted()).toBeFalsy();
});
});
});
});
+2 -2
View File
@@ -15,12 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {logger} from '../src/logger';
import { logger } from '../src/logger';
import * as utils from "../src/utils";
// try to load the olm library.
try {
global.Olm = require('olm');
global.Olm = require('@matrix-org/olm');
logger.log('loaded libolm');
} catch (e) {
logger.warn("unable to run crypto tests: libolm not available");
+20 -20
View File
@@ -1,8 +1,8 @@
// load olm before the sdk if possible
import './olm-loader';
import {logger} from '../src/logger';
import {MatrixEvent} from "../src/models/event";
import { logger } from '../src/logger';
import { MatrixEvent } from "../src/models/event";
/**
* Return a promise that is resolved when the client next emits a
@@ -177,7 +177,6 @@ export function mkMessage(opts) {
return mkEvent(opts);
}
/**
* A mock implementation of webstorage
*
@@ -204,7 +203,6 @@ MockStorageApi.prototype = {
},
};
/**
* If an event is being decrypted, wait for it to finish being decrypted.
*
@@ -212,21 +210,23 @@ MockStorageApi.prototype = {
* @returns {Promise} promise which resolves (to `event`) when the event has been decrypted
*/
export function awaitDecryption(event) {
if (!event.isBeingDecrypted()) {
return Promise.resolve(event);
}
// An event is not always decrypted ahead of time
// getClearContent is a good signal to know whether an event has been decrypted
// already
if (event.getClearContent() !== null) {
return event;
} else {
logger.log(`${Date.now()} event ${event.getId()} is being decrypted; waiting`);
logger.log(`${Date.now()} event ${event.getId()} is being decrypted; waiting`);
return new Promise((resolve, reject) => {
event.once('Event.decrypted', (ev) => {
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev);
return new Promise((resolve, reject) => {
event.once('Event.decrypted', (ev) => {
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev);
});
});
});
}
}
export function HttpResponse(
httpLookups, acceptKeepalives, ignoreUnhandledSync,
) {
@@ -357,12 +357,12 @@ export function setHttpResponses(
);
const httpReq = httpResponseObj.request.bind(httpResponseObj);
client._http = [
client.http = [
"authedRequest", "authedRequestWithPrefix", "getContentUri",
"request", "requestWithPrefix", "uploadContent",
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
client._http.authedRequest.mockImplementation(httpReq);
client._http.authedRequestWithPrefix.mockImplementation(httpReq);
client._http.requestWithPrefix.mockImplementation(httpReq);
client._http.request.mockImplementation(httpReq);
client.http.authedRequest.mockImplementation(httpReq);
client.http.authedRequestWithPrefix.mockImplementation(httpReq);
client.http.requestWithPrefix.mockImplementation(httpReq);
client.http.request.mockImplementation(httpReq);
}
+78
View File
@@ -0,0 +1,78 @@
/*
Copyright 2021 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 { NamespacedValue, UnstableValue } from "../../src/NamespacedValue";
describe("NamespacedValue", () => {
it("should prefer stable over unstable", () => {
const ns = new NamespacedValue("stable", "unstable");
expect(ns.name).toBe(ns.stable);
expect(ns.altName).toBe(ns.unstable);
});
it("should return unstable if there is no stable", () => {
const ns = new NamespacedValue(null, "unstable");
expect(ns.name).toBe(ns.unstable);
expect(ns.altName).toBeFalsy();
});
it("should have a falsey unstable if needed", () => {
const ns = new NamespacedValue("stable", null);
expect(ns.name).toBe(ns.stable);
expect(ns.altName).toBeFalsy();
});
it("should match against either stable or unstable", () => {
const ns = new NamespacedValue("stable", "unstable");
expect(ns.matches("no")).toBe(false);
expect(ns.matches(ns.stable)).toBe(true);
expect(ns.matches(ns.unstable)).toBe(true);
});
it("should not permit falsey values for both parts", () => {
try {
new UnstableValue(null, null);
// noinspection ExceptionCaughtLocallyJS
throw new Error("Failed to fail");
} catch (e) {
expect(e.message).toBe("One of stable or unstable values must be supplied");
}
});
});
describe("UnstableValue", () => {
it("should prefer unstable over stable", () => {
const ns = new UnstableValue("stable", "unstable");
expect(ns.name).toBe(ns.unstable);
expect(ns.altName).toBe(ns.stable);
});
it("should return unstable if there is no stable", () => {
const ns = new UnstableValue(null, "unstable");
expect(ns.name).toBe(ns.unstable);
expect(ns.altName).toBeFalsy();
});
it("should not permit falsey unstable values", () => {
try {
new UnstableValue("stable", null);
// noinspection ExceptionCaughtLocallyJS
throw new Error("Failed to fail");
} catch (e) {
expect(e.message).toBe("Unstable value must be supplied");
}
});
});
+72
View File
@@ -0,0 +1,72 @@
/*
Copyright 2021 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 { EventEmitter } from "events";
import { ReEmitter } from "../../src/ReEmitter";
const EVENTNAME = "UnknownEntry";
class EventSource extends EventEmitter {
doTheThing() {
this.emit(EVENTNAME, "foo", "bar");
}
doAnError() {
this.emit('error');
}
}
class EventTarget extends EventEmitter {
}
describe("ReEmitter", function() {
it("Re-Emits events with the same args", function() {
const src = new EventSource();
const tgt = new EventTarget();
const handler = jest.fn();
tgt.on(EVENTNAME, handler);
const reEmitter = new ReEmitter(tgt);
reEmitter.reEmit(src, [EVENTNAME]);
src.doTheThing();
// Args should be the args passed to 'emit' after the event name, and
// also the source object of the event which re-emitter adds
expect(handler).toHaveBeenCalledWith("foo", "bar", src);
});
it("Doesn't throw if no handler for 'error' event", function() {
const src = new EventSource();
const tgt = new EventTarget();
const reEmitter = new ReEmitter(tgt);
reEmitter.reEmit(src, ['error']);
// without the workaround in ReEmitter, this would throw
src.doAnError();
const handler = jest.fn();
tgt.on('error', handler);
src.doAnError();
// Now we've attached an error handler, it should be called
expect(handler).toHaveBeenCalled();
});
});
+1 -1
View File
@@ -17,7 +17,7 @@ limitations under the License.
import MockHttpBackend from "matrix-mock-request";
import * as sdk from "../../src";
import {AutoDiscovery} from "../../src/autodiscovery";
import { AutoDiscovery } from "../../src/autodiscovery";
describe("AutoDiscovery", function() {
let httpBackend = null;
+1 -1
View File
@@ -1,4 +1,4 @@
import {getHttpUriForMxc} from "../../src/content-repo";
import { getHttpUriForMxc } from "../../src/content-repo";
describe("ContentRepo", function() {
const baseUrl = "https://my.home.server";
+20 -20
View File
@@ -1,16 +1,16 @@
import '../olm-loader';
import {Crypto} from "../../src/crypto";
import {WebStorageSessionStore} from "../../src/store/session/webstorage";
import {MemoryCryptoStore} from "../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../MockStorageApi";
import {TestClient} from "../TestClient";
import {MatrixEvent} from "../../src/models/event";
import {Room} from "../../src/models/room";
import { Crypto } from "../../src/crypto";
import { WebStorageSessionStore } from "../../src/store/session/webstorage";
import { MemoryCryptoStore } from "../../src/crypto/store/memory-crypto-store";
import { MockStorageApi } from "../MockStorageApi";
import { TestClient } from "../TestClient";
import { MatrixEvent } from "../../src/models/event";
import { Room } from "../../src/models/room";
import * as olmlib from "../../src/crypto/olmlib";
import {sleep} from "../../src/utils";
import {EventEmitter} from "events";
import {CRYPTO_ENABLED} from "../../src/client";
import {DeviceInfo} from "../../src/crypto/deviceinfo";
import { sleep } from "../../src/utils";
import { EventEmitter } from "events";
import { CRYPTO_ENABLED } from "../../src/client";
import { DeviceInfo } from "../../src/crypto/deviceinfo";
const Olm = global.Olm;
@@ -46,7 +46,7 @@ describe("Crypto", function() {
// unknown sender (e.g. deleted device), forwarded megolm key (untrusted)
event.getSenderKey = () => 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI';
event.getWireContent = () => {return {algorithm: olmlib.MEGOLM_ALGORITHM};};
event.getWireContent = () => {return { algorithm: olmlib.MEGOLM_ALGORITHM };};
event.getForwardingCurve25519KeyChain = () => ["not empty"];
event.isKeySourceUntrusted = () => false;
event.getClaimedEd25519Key =
@@ -65,7 +65,7 @@ describe("Crypto", function() {
'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI';
device.keys["ed25519:FLIBBLE"] =
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
client._crypto._deviceList.getDeviceByIdentityKey = () => device;
client.crypto._deviceList.getDeviceByIdentityKey = () => device;
encryptionInfo = client.getEventEncryptionInfo(event);
expect(encryptionInfo.encrypted).toBeTruthy();
@@ -213,7 +213,7 @@ describe("Crypto", function() {
async function keyshareEventForEvent(event, index) {
const eventContent = event.getWireContent();
const key = await aliceClient._crypto._olmDevice
const key = await aliceClient.crypto._olmDevice
.getInboundGroupSessionKey(
roomId, eventContent.sender_key, eventContent.session_id,
index,
@@ -273,19 +273,19 @@ describe("Crypto", function() {
await Promise.all(events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient._crypto.encryptEvent(event, aliceRoom);
await aliceClient.crypto.encryptEvent(event, aliceRoom);
event._clearEvent = {};
event._senderCurve25519Key = null;
event._claimedEd25519Key = null;
try {
await bobClient._crypto.decryptEvent(event);
await bobClient.crypto.decryptEvent(event);
} catch (e) {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
const bobDecryptor = bobClient._crypto._getRoomDecryptor(
const bobDecryptor = bobClient.crypto._getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
@@ -302,7 +302,7 @@ describe("Crypto", function() {
expect(events[0].getContent().msgtype).toBe("m.bad.encrypted");
expect(events[1].getContent().msgtype).not.toBe("m.bad.encrypted");
const cryptoStore = bobClient._cryptoStore;
const cryptoStore = bobClient.cryptoStore;
const eventContent = events[0].getWireContent();
const senderKey = eventContent.sender_key;
const sessionId = eventContent.session_id;
@@ -344,7 +344,7 @@ describe("Crypto", function() {
},
});
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
const cryptoStore = aliceClient._cryptoStore;
const cryptoStore = aliceClient.cryptoStore;
const roomKeyRequestBody = {
algorithm: olmlib.MEGOLM_ALGORITHM,
room_id: "!someroom",
@@ -377,7 +377,7 @@ describe("Crypto", function() {
// key requests get queued until the sync has finished, but we don't
// let the client set up enough for that to happen, so gut-wrench a bit
// to force it to send now.
aliceClient._crypto._outgoingRoomKeyRequestManager.sendQueuedRequests();
aliceClient.crypto._outgoingRoomKeyRequestManager.sendQueuedRequests();
jest.runAllTimers();
await Promise.resolve();
expect(aliceClient.sendToDevice).toBeCalledTimes(1);
+4 -4
View File
@@ -22,11 +22,11 @@ import {
import {
IndexedDBCryptoStore,
} from '../../../src/crypto/store/indexeddb-crypto-store';
import {MemoryCryptoStore} from '../../../src/crypto/store/memory-crypto-store';
import { MemoryCryptoStore } from '../../../src/crypto/store/memory-crypto-store';
import 'fake-indexeddb/auto';
import 'jest-localstorage-mock';
import {OlmDevice} from "../../../src/crypto/OlmDevice";
import {logger} from '../../../src/logger';
import { OlmDevice } from "../../../src/crypto/OlmDevice";
import { logger } from '../../../src/logger';
const userId = "@alice:example.com";
@@ -66,7 +66,7 @@ describe("CrossSigningInfo.getCrossSigningKey", function() {
});
it.each(types)("should throw if the callback returns falsey",
async ({type, shouldCache}) => {
async ({ type, shouldCache }) => {
const info = new CrossSigningInfo(userId, {
getCrossSigningKey: () => false,
});
+61 -5
View File
@@ -16,10 +16,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {logger} from "../../../src/logger";
import { logger } from "../../../src/logger";
import * as utils from "../../../src/utils";
import {MemoryCryptoStore} from "../../../src/crypto/store/memory-crypto-store";
import {DeviceList} from "../../../src/crypto/DeviceList";
import { MemoryCryptoStore } from "../../../src/crypto/store/memory-crypto-store";
import { DeviceList } from "../../../src/crypto/DeviceList";
const signedDeviceList = {
"failures": {},
@@ -51,6 +51,36 @@ const signedDeviceList = {
},
};
const signedDeviceList2 = {
"failures": {},
"device_keys": {
"@test2:sw1v.org": {
"QJVRHWAKGH": {
"signatures": {
"@test2:sw1v.org": {
"ed25519:QJVRHWAKGH":
"w1xxdLe1iIqzEFHLRVYQeuiM6t2N2ZRiI8s5nDKxf054BP8" +
"1CPEX/AQXh5BhkKAVMlKnwg4T9zU1/wBALeajk3",
},
},
"user_id": "@test2:sw1v.org",
"keys": {
"ed25519:QJVRHWAKGH":
"Ig0/C6T+bBII1l2By2Wnnvtjp1nm/iXBlLU5/QESFXL",
"curve25519:QJVRHWAKGH":
"YR3eQnUvTQzGlWih4rsmJkKxpDxzgkgIgsBd1DEZIbm",
},
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2",
],
"device_id": "QJVRHWAKGH",
"unsigned": {},
},
},
},
};
describe('DeviceList', function() {
let downloadSpy;
let cryptoStore;
@@ -69,7 +99,7 @@ describe('DeviceList', function() {
}
});
function createTestDeviceList() {
function createTestDeviceList(keyDownloadChunkSize = 250) {
const baseApis = {
downloadKeysForUsers: downloadSpy,
getUserId: () => '@test1:sw1v.org',
@@ -78,7 +108,7 @@ describe('DeviceList', function() {
const mockOlm = {
verifySignature: function(key, message, signature) {},
};
const dl = new DeviceList(baseApis, cryptoStore, mockOlm);
const dl = new DeviceList(baseApis, cryptoStore, mockOlm, keyDownloadChunkSize);
deviceLists.push(dl);
return dl;
}
@@ -150,4 +180,30 @@ describe('DeviceList', function() {
expect(Object.keys(storedKeys)).toEqual(['HGKAWHRVJQ']);
});
});
it("should download device keys in batches", function() {
const dl = createTestDeviceList(1);
dl.startTrackingDeviceList('@test1:sw1v.org');
dl.startTrackingDeviceList('@test2:sw1v.org');
const queryDefer1 = utils.defer();
downloadSpy.mockReturnValueOnce(queryDefer1.promise);
const queryDefer2 = utils.defer();
downloadSpy.mockReturnValueOnce(queryDefer2.promise);
const prom1 = dl.refreshOutdatedDeviceLists();
expect(downloadSpy).toBeCalledTimes(2);
expect(downloadSpy).toHaveBeenNthCalledWith(1, ['@test1:sw1v.org'], {});
expect(downloadSpy).toHaveBeenNthCalledWith(2, ['@test2:sw1v.org'], {});
queryDefer1.resolve(utils.deepCopy(signedDeviceList));
queryDefer2.resolve(utils.deepCopy(signedDeviceList2));
return prom1.then(() => {
const storedKeys1 = dl.getRawStoredDevicesForUser('@test1:sw1v.org');
expect(Object.keys(storedKeys1)).toEqual(['HGKAWHRVJQ']);
const storedKeys2 = dl.getRawStoredDevicesForUser('@test2:sw1v.org');
expect(Object.keys(storedKeys2)).toEqual(['QJVRHWAKGH']);
});
});
});
+37 -35
View File
@@ -1,14 +1,14 @@
import '../../../olm-loader';
import * as algorithms from "../../../../src/crypto/algorithms";
import {MemoryCryptoStore} from "../../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../../MockStorageApi";
import { MemoryCryptoStore } from "../../../../src/crypto/store/memory-crypto-store";
import { MockStorageApi } from "../../../MockStorageApi";
import * as testUtils from "../../../test-utils";
import {OlmDevice} from "../../../../src/crypto/OlmDevice";
import {Crypto} from "../../../../src/crypto";
import {logger} from "../../../../src/logger";
import {MatrixEvent} from "../../../../src/models/event";
import {TestClient} from "../../../TestClient";
import {Room} from "../../../../src/models/room";
import { OlmDevice } from "../../../../src/crypto/OlmDevice";
import { Crypto } from "../../../../src/crypto";
import { logger } from "../../../../src/logger";
import { MatrixEvent } from "../../../../src/models/event";
import { TestClient } from "../../../TestClient";
import { Room } from "../../../../src/models/room";
import * as olmlib from "../../../../src/crypto/olmlib";
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
@@ -50,7 +50,6 @@ describe("MegolmDecryption", function() {
roomId: ROOM_ID,
});
// we stub out the olm encryption bits
mockOlmLib = {};
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
@@ -136,9 +135,9 @@ describe("MegolmDecryption", function() {
mockCrypto.getStoredDevice.mockReturnValue(deviceInfo);
mockOlmLib.ensureOlmSessionsForDevices.mockResolvedValue({
'@alice:foo': {'alidevice': {
'@alice:foo': { 'alidevice': {
sessionId: 'alisession',
}},
} },
});
const awaitEncryptForDevice = new Promise((res, rej) => {
@@ -258,6 +257,9 @@ describe("MegolmDecryption", function() {
});
it("re-uses sessions for sequential messages", async function() {
mockCrypto._backupManager = {
backupGroupSession: () => {},
};
const mockStorage = new MockStorageApi();
const cryptoStore = new MemoryCryptoStore(mockStorage);
@@ -313,7 +315,7 @@ describe("MegolmDecryption", function() {
});
const mockRoom = {
getEncryptionTargetMembers: jest.fn().mockReturnValue(
[{userId: "@alice:home.server"}],
[{ userId: "@alice:home.server" }],
),
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(false),
};
@@ -363,9 +365,9 @@ describe("MegolmDecryption", function() {
bobClient1.initCrypto(),
bobClient2.initCrypto(),
]);
const aliceDevice = aliceClient._crypto._olmDevice;
const bobDevice1 = bobClient1._crypto._olmDevice;
const bobDevice2 = bobClient2._crypto._olmDevice;
const aliceDevice = aliceClient.crypto._olmDevice;
const bobDevice1 = bobClient1.crypto._olmDevice;
const bobDevice2 = bobClient2.crypto._olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
@@ -373,7 +375,7 @@ describe("MegolmDecryption", function() {
const roomId = "!someroom";
const room = new Room(roomId, aliceClient, "@alice:example.com", {});
room.getEncryptionTargetMembers = async function() {
return [{userId: "@bob:example.com"}];
return [{ userId: "@bob:example.com" }];
};
room.setBlacklistUnverifiedDevices(true);
aliceClient.store.storeRoom(room);
@@ -402,10 +404,10 @@ describe("MegolmDecryption", function() {
},
};
aliceClient._crypto._deviceList.storeDevicesForUser(
aliceClient.crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient._crypto._deviceList.downloadKeys = async function(userIds) {
aliceClient.crypto._deviceList.downloadKeys = async function(userIds) {
return this._getDevicesFromStore(userIds);
};
@@ -446,7 +448,7 @@ describe("MegolmDecryption", function() {
body: "secret",
},
});
await aliceClient._crypto.encryptEvent(event, room);
await aliceClient.crypto.encryptEvent(event, room);
expect(run).toBe(true);
@@ -466,8 +468,8 @@ describe("MegolmDecryption", function() {
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const aliceDevice = aliceClient._crypto._olmDevice;
const bobDevice = bobClient._crypto._olmDevice;
const aliceDevice = aliceClient.crypto._olmDevice;
const bobDevice = bobClient.crypto._olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
@@ -506,10 +508,10 @@ describe("MegolmDecryption", function() {
},
};
aliceClient._crypto._deviceList.storeDevicesForUser(
aliceClient.crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient._crypto._deviceList.downloadKeys = async function(userIds) {
aliceClient.crypto._deviceList.downloadKeys = async function(userIds) {
return this._getDevicesFromStore(userIds);
};
@@ -544,7 +546,7 @@ describe("MegolmDecryption", function() {
event_id: "$event",
content: {},
});
await aliceClient._crypto.encryptEvent(event, aliceRoom);
await aliceClient.crypto.encryptEvent(event, aliceRoom);
await sendPromise;
});
@@ -559,11 +561,11 @@ describe("MegolmDecryption", function() {
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const bobDevice = bobClient._crypto._olmDevice;
const bobDevice = bobClient.crypto._olmDevice;
const roomId = "!someroom";
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
aliceClient.crypto._onToDeviceEvent(new MatrixEvent({
type: "org.matrix.room_key.withheld",
sender: "@bob:example.com",
content: {
@@ -576,7 +578,7 @@ describe("MegolmDecryption", function() {
},
}));
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
await expect(aliceClient.crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
@@ -602,14 +604,14 @@ describe("MegolmDecryption", function() {
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
aliceClient._crypto.downloadKeys = async () => {};
const bobDevice = bobClient._crypto._olmDevice;
aliceClient.crypto.downloadKeys = async () => {};
const bobDevice = bobClient.crypto._olmDevice;
const roomId = "!someroom";
const now = Date.now();
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
aliceClient.crypto._onToDeviceEvent(new MatrixEvent({
type: "org.matrix.room_key.withheld",
sender: "@bob:example.com",
content: {
@@ -626,7 +628,7 @@ describe("MegolmDecryption", function() {
setTimeout(resolve, 100);
});
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
await expect(aliceClient.crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
@@ -653,15 +655,15 @@ describe("MegolmDecryption", function() {
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const bobDevice = bobClient._crypto._olmDevice;
aliceClient._crypto.downloadKeys = async () => {};
const bobDevice = bobClient.crypto._olmDevice;
aliceClient.crypto.downloadKeys = async () => {};
const roomId = "!someroom";
const now = Date.now();
// pretend we got an event that we can't decrypt
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
aliceClient.crypto._onToDeviceEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
content: {
@@ -676,7 +678,7 @@ describe("MegolmDecryption", function() {
setTimeout(resolve, 100);
});
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
await expect(aliceClient.crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
+91 -5
View File
@@ -16,12 +16,12 @@ limitations under the License.
*/
import '../../../olm-loader';
import {MemoryCryptoStore} from "../../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../../MockStorageApi";
import {logger} from "../../../../src/logger";
import {OlmDevice} from "../../../../src/crypto/OlmDevice";
import { MemoryCryptoStore } from "../../../../src/crypto/store/memory-crypto-store";
import { MockStorageApi } from "../../../MockStorageApi";
import { logger } from "../../../../src/logger";
import { OlmDevice } from "../../../../src/crypto/OlmDevice";
import * as olmlib from "../../../../src/crypto/olmlib";
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
import { DeviceInfo } from "../../../../src/crypto/deviceinfo";
function makeOlmDevice() {
const mockStorage = new MockStorageApi();
@@ -190,5 +190,91 @@ describe("OlmDevice", function() {
// new session and will have called claimOneTimeKeys
expect(count).toBe(2);
});
it("avoids deadlocks when two tasks are ensuring the same devices", async function() {
// This test checks whether `ensureOlmSessionsForDevices` properly
// handles multiple tasks in flight ensuring some set of devices in
// common without deadlocks.
let claimRequestCount = 0;
const baseApis = {
claimOneTimeKeys: () => {
// simulate a very slow server (.5 seconds to respond)
claimRequestCount++;
return new Promise((resolve, reject) => {
setTimeout(reject, 500);
});
},
};
const deviceBobA = DeviceInfo.fromStorage({
keys: {
"curve25519:BOB-A": "akey",
},
}, "BOB-A");
const deviceBobB = DeviceInfo.fromStorage({
keys: {
"curve25519:BOB-B": "bkey",
},
}, "BOB-B");
// There's no required ordering of devices per user, so here we
// create two different orderings so that each task reserves a
// device the other task needs before continuing.
const devicesByUserAB = {
"@bob:example.com": [
deviceBobA,
deviceBobB,
],
};
const devicesByUserBA = {
"@bob:example.com": [
deviceBobB,
deviceBobA,
],
};
function alwaysSucceed(promise) {
// swallow any exception thrown by a promise, so that
// Promise.all doesn't abort
return promise.catch(() => {});
}
const task1 = alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
aliceOlmDevice, baseApis, devicesByUserAB,
));
// After a single tick through the first task, it should have
// claimed ownership of all devices to avoid deadlocking others.
expect(Object.keys(aliceOlmDevice._sessionsInProgress).length).toBe(2);
const task2 = alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
aliceOlmDevice, baseApis, devicesByUserBA,
));
// The second task should not have changed the ownership count, as
// it's waiting on the first task.
expect(Object.keys(aliceOlmDevice._sessionsInProgress).length).toBe(2);
// Track the tasks, but don't await them yet.
const promises = Promise.all([
task1,
task2,
]);
await new Promise((resolve) => {
setTimeout(resolve, 200);
});
// After .2s, the first task should have made an initial claim request.
expect(claimRequestCount).toBe(1);
await promises;
// After waiting for both tasks to complete, the first task should
// have failed, so the second task should have tried to create a
// new session and will have called claimOneTimeKeys
expect(claimRequestCount).toBe(2);
});
});
});
+83 -60
View File
@@ -16,18 +16,19 @@ limitations under the License.
*/
import '../../olm-loader';
import {logger} from "../../../src/logger";
import { logger } from "../../../src/logger";
import * as olmlib from "../../../src/crypto/olmlib";
import {MatrixClient} from "../../../src/client";
import {MatrixEvent} from "../../../src/models/event";
import { MatrixClient } from "../../../src/client";
import { MatrixEvent } from "../../../src/models/event";
import * as algorithms from "../../../src/crypto/algorithms";
import {WebStorageSessionStore} from "../../../src/store/session/webstorage";
import {MemoryCryptoStore} from "../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../MockStorageApi";
import { WebStorageSessionStore } from "../../../src/store/session/webstorage";
import { MemoryCryptoStore } from "../../../src/crypto/store/memory-crypto-store";
import { MockStorageApi } from "../../MockStorageApi";
import * as testUtils from "../../test-utils";
import {OlmDevice} from "../../../src/crypto/OlmDevice";
import {Crypto} from "../../../src/crypto";
import {resetCrossSigningKeys} from "./crypto-utils";
import { OlmDevice } from "../../../src/crypto/OlmDevice";
import { Crypto } from "../../../src/crypto";
import { resetCrossSigningKeys } from "./crypto-utils";
import { BackupManager } from "../../../src/crypto/backup";
const Olm = global.Olm;
@@ -73,7 +74,7 @@ const KEY_BACKUP_DATA = {
};
const BACKUP_INFO = {
algorithm: "m.megolm_backup.v1",
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
version: 1,
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
@@ -138,6 +139,7 @@ describe("MegolmBackup", function() {
let megolmDecryption;
beforeEach(async function() {
mockCrypto = testUtils.mock(Crypto, 'Crypto');
mockCrypto._backupManager = testUtils.mock(BackupManager, "BackupManager");
mockCrypto.backupKey = new Olm.PkEncryption();
mockCrypto.backupKey.set_recipient_key(
"hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
@@ -215,12 +217,14 @@ describe("MegolmBackup", function() {
};
mockCrypto.cancelRoomKeyRequest = function() {};
mockCrypto.backupGroupSession = jest.fn();
mockCrypto._backupManager = {
backupGroupSession: jest.fn(),
};
return event.attemptDecryption(mockCrypto).then(() => {
return megolmDecryption.onRoomKeyEvent(event);
}).then(() => {
expect(mockCrypto.backupGroupSession).toHaveBeenCalled();
expect(mockCrypto._backupManager.backupGroupSession).toHaveBeenCalled();
});
});
@@ -264,7 +268,7 @@ describe("MegolmBackup", function() {
})
.then(() => {
client.enableKeyBackup({
algorithm: "m.megolm_backup.v1",
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
version: 1,
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
@@ -272,7 +276,7 @@ describe("MegolmBackup", function() {
});
let numCalls = 0;
return new Promise((resolve, reject) => {
client._http.authedRequest = function(
client.http.authedRequest = function(
callback, method, path, queryParams, data, opts,
) {
++numCalls;
@@ -292,12 +296,9 @@ describe("MegolmBackup", function() {
resolve();
return Promise.resolve({});
};
client._crypto.backupGroupSession(
"roomId",
client.crypto._backupManager.backupGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
[],
groupSession.session_id(),
groupSession.session_key(),
);
}).then(() => {
expect(numCalls).toBe(1);
@@ -335,39 +336,48 @@ describe("MegolmBackup", function() {
});
await resetCrossSigningKeys(client);
let numCalls = 0;
await new Promise((resolve, reject) => {
client._http.authedRequest = function(
callback, method, path, queryParams, data, opts,
) {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(1);
if (numCalls >= 2) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
return Promise.resolve({});
}
expect(method).toBe("POST");
expect(path).toBe("/room_keys/version");
try {
// make sure auth_data is signed by the master key
olmlib.pkVerify(
data.auth_data, client.getCrossSigningId(), "@alice:bar",
);
} catch (e) {
reject(e);
return Promise.resolve({});
}
resolve();
return Promise.resolve({});
};
await Promise.all([
new Promise((resolve, reject) => {
let backupInfo;
client.http.authedRequest = function(
callback, method, path, queryParams, data, opts,
) {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(2);
if (numCalls === 1) {
expect(method).toBe("POST");
expect(path).toBe("/room_keys/version");
try {
// make sure auth_data is signed by the master key
olmlib.pkVerify(
data.auth_data, client.getCrossSigningId(), "@alice:bar",
);
} catch (e) {
reject(e);
return Promise.resolve({});
}
backupInfo = data;
return Promise.resolve({});
} else if (numCalls === 2) {
expect(method).toBe("GET");
expect(path).toBe("/room_keys/version");
resolve();
return Promise.resolve(backupInfo);
} else {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many times"));
return Promise.resolve({});
}
};
}),
client.createKeyBackupVersion({
algorithm: "m.megolm_backup.v1",
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
},
});
});
expect(numCalls).toBe(1);
}),
]);
expect(numCalls).toBe(2);
});
it('retries when a backup fails', function() {
@@ -434,7 +444,7 @@ describe("MegolmBackup", function() {
})
.then(() => {
client.enableKeyBackup({
algorithm: "foobar",
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
version: 1,
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
@@ -442,7 +452,7 @@ describe("MegolmBackup", function() {
});
let numCalls = 0;
return new Promise((resolve, reject) => {
client._http.authedRequest = function(
client.http.authedRequest = function(
callback, method, path, queryParams, data, opts,
) {
++numCalls;
@@ -468,12 +478,9 @@ describe("MegolmBackup", function() {
);
}
};
client._crypto.backupGroupSession(
"roomId",
client.crypto._backupManager.backupGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
[],
groupSession.session_id(),
groupSession.session_key(),
);
}).then(() => {
expect(numCalls).toBe(2);
@@ -506,7 +513,7 @@ describe("MegolmBackup", function() {
});
it('can restore from backup', function() {
client._http.authedRequest = function() {
client.http.authedRequest = function() {
return Promise.resolve(KEY_BACKUP_DATA);
};
return client.restoreKeyBackupWithRecoveryKey(
@@ -523,7 +530,7 @@ describe("MegolmBackup", function() {
});
it('can restore backup by room', function() {
client._http.authedRequest = function() {
client.http.authedRequest = function() {
return Promise.resolve({
rooms: {
[ROOM_ID]: {
@@ -546,15 +553,15 @@ describe("MegolmBackup", function() {
it('has working cache functions', async function() {
const key = Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8]);
await client._crypto.storeSessionBackupPrivateKey(key);
const result = await client._crypto.getSessionBackupPrivateKey();
await client.crypto.storeSessionBackupPrivateKey(key);
const result = await client.crypto.getSessionBackupPrivateKey();
expect(new Uint8Array(result)).toEqual(key);
});
it('caches session backup keys as it encounters them', async function() {
const cachedNull = await client._crypto.getSessionBackupPrivateKey();
const cachedNull = await client.crypto.getSessionBackupPrivateKey();
expect(cachedNull).toBeNull();
client._http.authedRequest = function() {
client.http.authedRequest = function() {
return Promise.resolve(KEY_BACKUP_DATA);
};
await new Promise((resolve) => {
@@ -566,8 +573,24 @@ describe("MegolmBackup", function() {
{ cacheCompleteCallback: resolve },
);
});
const cachedKey = await client._crypto.getSessionBackupPrivateKey();
const cachedKey = await client.crypto.getSessionBackupPrivateKey();
expect(cachedKey).not.toBeNull();
});
it("fails if an known algorithm is used", async function() {
const BAD_BACKUP_INFO = Object.assign({}, BACKUP_INFO, {
algorithm: "this.algorithm.does.not.exist",
});
client.http.authedRequest = function() {
return Promise.resolve(KEY_BACKUP_DATA);
};
await expect(client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
ROOM_ID,
SESSION_ID,
BAD_BACKUP_INFO,
)).rejects.toThrow();
});
});
});
+60 -54
View File
@@ -18,11 +18,11 @@ limitations under the License.
import '../../olm-loader';
import anotherjson from 'another-json';
import * as olmlib from "../../../src/crypto/olmlib";
import {TestClient} from '../../TestClient';
import {HttpResponse, setHttpResponses} from '../../test-utils';
import { TestClient } from '../../TestClient';
import { HttpResponse, setHttpResponses } from '../../test-utils';
import { resetCrossSigningKeys } from "./crypto-utils";
import { MatrixError } from '../../../src/http-api';
import {logger} from '../../../src/logger';
import { logger } from '../../../src/logger';
async function makeTestClient(userInfo, options, keys) {
if (!keys) keys = {};
@@ -60,12 +60,12 @@ describe("Cross Signing", function() {
it("should sign the master key with the device key", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = jest.fn(async (auth, keys) => {
await olmlib.verifySignature(
alice._crypto._olmDevice, keys.master_key, "@alice:example.com",
"Osborne2", alice._crypto._olmDevice.deviceEd25519Key,
alice.crypto._olmDevice, keys.master_key, "@alice:example.com",
"Osborne2", alice.crypto._olmDevice.deviceEd25519Key,
);
});
alice.uploadKeySignatures = async () => {};
@@ -80,7 +80,7 @@ describe("Cross Signing", function() {
it("should abort bootstrap if device signing auth fails", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = async (auth, keys) => {
const errorResponse = {
@@ -131,14 +131,14 @@ describe("Cross Signing", function() {
it("should upload a signature when a user is verified", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await resetCrossSigningKeys(alice);
// Alice downloads Bob's device key
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@@ -175,7 +175,7 @@ describe("Cross Signing", function() {
]);
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
// will be called to sign our own device
@@ -193,30 +193,36 @@ describe("Cross Signing", function() {
const keyChangePromise = new Promise((resolve, reject) => {
alice.once("crossSigning.keysChanged", async (e) => {
resolve(e);
await alice.checkOwnCrossSigningTrust();
await alice.checkOwnCrossSigningTrust({
allowPrivateKeyRequests: true,
});
});
});
const uploadSigsPromise = new Promise((resolve, reject) => {
alice.uploadKeySignatures = jest.fn(async (content) => {
await olmlib.verifySignature(
alice._crypto._olmDevice,
content["@alice:example.com"][
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
],
"@alice:example.com",
"Osborne2", alice._crypto._olmDevice.deviceEd25519Key,
);
olmlib.pkVerify(
content["@alice:example.com"]["Osborne2"],
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
"@alice:example.com",
);
resolve();
try {
await olmlib.verifySignature(
alice.crypto._olmDevice,
content["@alice:example.com"][
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
],
"@alice:example.com",
"Osborne2", alice.crypto._olmDevice.deviceEd25519Key,
);
olmlib.pkVerify(
content["@alice:example.com"]["Osborne2"],
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
"@alice:example.com",
);
resolve();
} catch (e) {
reject(e);
}
});
});
const deviceInfo = alice._crypto._deviceList._devices["@alice:example.com"]
const deviceInfo = alice.crypto._deviceList._devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
@@ -224,7 +230,7 @@ describe("Cross Signing", function() {
};
aliceDevice.keys = deviceInfo.keys;
aliceDevice.algorithms = deviceInfo.algorithms;
await alice._crypto._signObject(aliceDevice);
await alice.crypto._signObject(aliceDevice);
olmlib.pkSign(aliceDevice, selfSigningKey, "@alice:example.com");
// feed sync result that includes master key, ssk, device key
@@ -326,7 +332,7 @@ describe("Cross Signing", function() {
it("should use trust chain to determine device verification", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
@@ -352,7 +358,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@@ -381,7 +387,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobPubkey]: sig,
},
};
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device key should be TOFU
@@ -411,12 +417,12 @@ describe("Cross Signing", function() {
it("should trust signatures received from other devices", async function() {
const aliceKeys = {};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
null,
aliceKeys,
);
alice._crypto._deviceList.startTrackingDeviceList("@bob:example.com");
alice._crypto._deviceList.stopTrackingAllDeviceLists = () => {};
alice.crypto._deviceList.startTrackingDeviceList("@bob:example.com");
alice.crypto._deviceList.stopTrackingAllDeviceLists = () => {};
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
@@ -431,14 +437,14 @@ describe("Cross Signing", function() {
]);
const keyChangePromise = new Promise((resolve, reject) => {
alice._crypto._deviceList.once("userCrossSigningUpdated", (userId) => {
alice.crypto._deviceList.once("userCrossSigningUpdated", (userId) => {
if (userId === "@bob:example.com") {
resolve();
}
});
});
const deviceInfo = alice._crypto._deviceList._devices["@alice:example.com"]
const deviceInfo = alice.crypto._deviceList._devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
@@ -446,7 +452,7 @@ describe("Cross Signing", function() {
};
aliceDevice.keys = deviceInfo.keys;
aliceDevice.algorithms = deviceInfo.algorithms;
await alice._crypto._signObject(aliceDevice);
await alice.crypto._signObject(aliceDevice);
const bobOlmAccount = new global.Olm.Account();
bobOlmAccount.create();
@@ -573,7 +579,7 @@ describe("Cross Signing", function() {
it("should dis-trust an unsigned device", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
@@ -600,7 +606,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@@ -623,7 +629,7 @@ describe("Cross Signing", function() {
"ed25519:Dynabook": "someOtherPubkey",
},
};
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device key should be untrusted
@@ -642,7 +648,7 @@ describe("Cross Signing", function() {
it("should dis-trust a user when their ssk changes", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
@@ -667,7 +673,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@@ -695,7 +701,7 @@ describe("Cross Signing", function() {
bobDevice.signatures = {};
bobDevice.signatures["@bob:example.com"] = {};
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey] = sig;
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Alice verifies Bob's SSK
@@ -727,7 +733,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey2]: sskSig2,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@@ -764,7 +770,7 @@ describe("Cross Signing", function() {
// Alice gets new signature for device
const sig2 = bobSigning2.sign(bobDeviceString);
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey2] = sig2;
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
@@ -780,7 +786,7 @@ describe("Cross Signing", function() {
let upgradeResolveFunc;
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
shouldUpgradeDeviceVerifications: (verifs) => {
@@ -792,27 +798,27 @@ describe("Cross Signing", function() {
},
);
const bob = await makeTestClient(
{userId: "@bob:example.com", deviceId: "Dynabook"},
{ userId: "@bob:example.com", deviceId: "Dynabook" },
);
bob.uploadDeviceSigningKeys = async () => {};
bob.uploadKeySignatures = async () => {};
// set Bob's cross-signing key
await resetCrossSigningKeys(bob);
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: {
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": bob._crypto._olmDevice.deviceCurve25519Key,
"ed25519:Dynabook": bob._crypto._olmDevice.deviceEd25519Key,
"curve25519:Dynabook": bob.crypto._olmDevice.deviceCurve25519Key,
"ed25519:Dynabook": bob.crypto._olmDevice.deviceEd25519Key,
},
verified: 1,
known: true,
},
});
alice._crypto._deviceList.storeCrossSigningForUser(
alice.crypto._deviceList.storeCrossSigningForUser(
"@bob:example.com",
bob._crypto._crossSigningInfo.toStorage(),
bob.crypto._crossSigningInfo.toStorage(),
);
alice.uploadDeviceSigningKeys = async () => {};
@@ -832,7 +838,7 @@ describe("Cross Signing", function() {
expect(bobTrust.isTofu()).toBeTruthy();
// "forget" that Bob is trusted
delete alice._crypto._deviceList._crossSigningInfo["@bob:example.com"]
delete alice.crypto._deviceList._crossSigningInfo["@bob:example.com"]
.keys.master.signatures["@alice:example.com"];
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
@@ -842,9 +848,9 @@ describe("Cross Signing", function() {
upgradePromise = new Promise((resolve) => {
upgradeResolveFunc = resolve;
});
alice._crypto._deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
alice.crypto._deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
await new Promise((resolve) => {
alice._crypto.on("userTrustStatusChanged", resolve);
alice.crypto.on("userTrustStatusChanged", resolve);
});
await upgradePromise;
+2 -3
View File
@@ -1,5 +1,4 @@
import {IndexedDBCryptoStore} from '../../../src/crypto/store/indexeddb-crypto-store';
import { IndexedDBCryptoStore } from '../../../src/crypto/store/indexeddb-crypto-store';
// needs to be phased out and replaced with bootstrapSecretStorage,
// but that is doing too much extra stuff for it to be an easy transition.
@@ -7,7 +6,7 @@ export async function resetCrossSigningKeys(client, {
level,
authUploadDeviceSigningKeys = async func => await func(),
} = {}) {
const crypto = client._crypto;
const crypto = client.crypto;
const oldKeys = Object.assign({}, crypto._crossSigningInfo.keys);
try {
@@ -17,7 +17,7 @@ limitations under the License.
import {
IndexedDBCryptoStore,
} from '../../../src/crypto/store/indexeddb-crypto-store';
import {MemoryCryptoStore} from '../../../src/crypto/store/memory-crypto-store';
import { MemoryCryptoStore } from '../../../src/crypto/store/memory-crypto-store';
import 'fake-indexeddb/auto';
import 'jest-localstorage-mock';
+46 -46
View File
@@ -16,13 +16,13 @@ limitations under the License.
import '../../olm-loader';
import * as olmlib from "../../../src/crypto/olmlib";
import {SECRET_STORAGE_ALGORITHM_V1_AES} from "../../../src/crypto/SecretStorage";
import {MatrixEvent} from "../../../src/models/event";
import {TestClient} from '../../TestClient';
import {makeTestClients} from './verification/util';
import {encryptAES} from "../../../src/crypto/aes";
import {resetCrossSigningKeys, createSecretStorageKey} from "./crypto-utils";
import {logger} from '../../../src/logger';
import { SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/crypto/SecretStorage";
import { MatrixEvent } from "../../../src/models/event";
import { TestClient } from '../../TestClient';
import { makeTestClients } from './verification/util';
import { encryptAES } from "../../../src/crypto/aes";
import { resetCrossSigningKeys, createSecretStorageKey } from "./crypto-utils";
import { logger } from '../../../src/logger';
import * as utils from "../../../src/utils";
@@ -47,7 +47,7 @@ async function makeTestClient(userInfo, options) {
await client.initCrypto();
// No need to download keys for these tests
client._crypto.downloadKeys = async function() {};
client.crypto.downloadKeys = async function() {};
return client;
}
@@ -91,7 +91,7 @@ describe("Secrets", function() {
});
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
getCrossSigningKey: t => signingKey,
@@ -99,11 +99,11 @@ describe("Secrets", function() {
},
},
);
alice._crypto._crossSigningInfo.setKeys({
alice.crypto._crossSigningInfo.setKeys({
master: signingkeyInfo,
});
const secretStorage = alice._crypto._secretStorage;
const secretStorage = alice.crypto._secretStorage;
alice.setAccountData = async function(eventType, contents, callback) {
alice.store.storeAccountDataEvents([
@@ -120,7 +120,7 @@ describe("Secrets", function() {
const keyAccountData = {
algorithm: SECRET_STORAGE_ALGORITHM_V1_AES,
};
await alice._crypto._crossSigningInfo.signObject(keyAccountData, 'master');
await alice.crypto._crossSigningInfo.signObject(keyAccountData, 'master');
alice.store.storeAccountDataEvents([
new MatrixEvent({
@@ -141,7 +141,7 @@ describe("Secrets", function() {
it("should throw if given a key that doesn't exist", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
try {
@@ -155,7 +155,7 @@ describe("Secrets", function() {
it("should refuse to encrypt with zero keys", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
try {
@@ -175,7 +175,7 @@ describe("Secrets", function() {
let keys = {};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
getCrossSigningKey: t => keys[t],
@@ -194,7 +194,7 @@ describe("Secrets", function() {
};
resetCrossSigningKeys(alice);
const newKeyId = await alice.addSecretStorageKey(
const { keyId: newKeyId } = await alice.addSecretStorageKey(
SECRET_STORAGE_ALGORITHM_V1_AES,
);
// we don't await on this because it waits for the event to come down the sync
@@ -208,7 +208,7 @@ describe("Secrets", function() {
it("should refuse to encrypt if no keys given and no default key", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
try {
@@ -221,24 +221,24 @@ describe("Secrets", function() {
it("should request secrets from other clients", async function() {
const [osborne2, vax] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@alice:example.com", deviceId: "VAX"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{ userId: "@alice:example.com", deviceId: "VAX" },
],
{
cryptoCallbacks: {
onSecretRequested: e => {
expect(e.name).toBe("foo");
onSecretRequested: (userId, deviceId, requestId, secretName, deviceTrust) => {
expect(secretName).toBe("foo");
return "bar";
},
},
},
);
const vaxDevice = vax.client._crypto._olmDevice;
const osborne2Device = osborne2.client._crypto._olmDevice;
const secretStorage = osborne2.client._crypto._secretStorage;
const vaxDevice = vax.client.crypto._olmDevice;
const osborne2Device = osborne2.client.crypto._olmDevice;
const secretStorage = osborne2.client.crypto._secretStorage;
osborne2.client._crypto._deviceList.storeDevicesForUser("@alice:example.com", {
osborne2.client.crypto._deviceList.storeDevicesForUser("@alice:example.com", {
"VAX": {
user_id: "@alice:example.com",
device_id: "VAX",
@@ -249,7 +249,7 @@ describe("Secrets", function() {
},
},
});
vax.client._crypto._deviceList.storeDevicesForUser("@alice:example.com", {
vax.client.crypto._deviceList.storeDevicesForUser("@alice:example.com", {
"Osborne2": {
user_id: "@alice:example.com",
device_id: "Osborne2",
@@ -265,7 +265,7 @@ describe("Secrets", function() {
const otks = (await osborne2Device.getOneTimeKeys()).curve25519;
await osborne2Device.markKeysAsPublished();
await vax.client._crypto._olmDevice.createOutboundSession(
await vax.client.crypto._olmDevice.createOutboundSession(
osborne2Device.deviceCurve25519Key,
Object.values(otks)[0],
);
@@ -334,8 +334,8 @@ describe("Secrets", function() {
createSecretStorageKey,
});
const crossSigning = bob._crypto._crossSigningInfo;
const secretStorage = bob._crypto._secretStorage;
const crossSigning = bob.crypto._crossSigningInfo;
const secretStorage = bob.crypto._secretStorage;
expect(crossSigning.getId()).toBeTruthy();
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
@@ -376,10 +376,10 @@ describe("Secrets", function() {
]);
this.emit("accountData", event);
};
bob._crypto.checkKeyBackup = async () => {};
bob.crypto._backupManager.checkKeyBackup = async () => {};
const crossSigning = bob._crypto._crossSigningInfo;
const secretStorage = bob._crypto._secretStorage;
const crossSigning = bob.crypto._crossSigningInfo;
const secretStorage = bob.crypto._secretStorage;
// Set up cross-signing keys from scratch with specific storage key
await bob.bootstrapCrossSigning({
@@ -394,7 +394,7 @@ describe("Secrets", function() {
});
// Clear local cross-signing keys and read from secret storage
bob._crypto._deviceList.storeCrossSigningForUser(
bob.crypto._deviceList.storeCrossSigningForUser(
"@bob:example.com",
crossSigning.toStorage(),
);
@@ -419,12 +419,12 @@ describe("Secrets", function() {
key_id: SSSSKey,
};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
getCrossSigningKey: t => crossSigningKeys[t],
saveCrossSigningKeys: k => crossSigningKeys = k,
getSecretStorageKey: ({keys}, name) => {
getSecretStorageKey: ({ keys }, name) => {
for (const keyId of Object.keys(keys)) {
if (secretStorageKeys[keyId]) {
return [keyId, secretStorageKeys[keyId]];
@@ -458,7 +458,7 @@ describe("Secrets", function() {
type: "m.cross_signing.master",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
key_id: { ciphertext: "bla", mac: "bla", iv: "bla" },
},
},
}),
@@ -466,7 +466,7 @@ describe("Secrets", function() {
type: "m.cross_signing.self_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
key_id: { ciphertext: "bla", mac: "bla", iv: "bla" },
},
},
}),
@@ -474,12 +474,12 @@ describe("Secrets", function() {
type: "m.cross_signing.user_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
key_id: { ciphertext: "bla", mac: "bla", iv: "bla" },
},
},
}),
]);
alice._crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
alice.crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: {
master: {
user_id: "@alice:example.com",
@@ -525,7 +525,7 @@ describe("Secrets", function() {
await alice.bootstrapSecretStorage();
expect(alice.getAccountData("m.secret_storage.default_key").getContent())
.toEqual({key: "key_id"});
.toEqual({ key: "key_id" });
const keyInfo = alice.getAccountData("m.secret_storage.key.key_id")
.getContent();
expect(keyInfo.algorithm)
@@ -550,12 +550,12 @@ describe("Secrets", function() {
key_id: SSSSKey,
};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
getCrossSigningKey: t => crossSigningKeys[t],
saveCrossSigningKeys: k => crossSigningKeys = k,
getSecretStorageKey: ({keys}, name) => {
getSecretStorageKey: ({ keys }, name) => {
for (const keyId of Object.keys(keys)) {
if (secretStorageKeys[keyId]) {
return [keyId, secretStorageKeys[keyId]];
@@ -587,7 +587,7 @@ describe("Secrets", function() {
type: "m.cross_signing.master",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
key_id: { ciphertext: "bla", mac: "bla", iv: "bla" },
},
},
}),
@@ -595,7 +595,7 @@ describe("Secrets", function() {
type: "m.cross_signing.self_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
key_id: { ciphertext: "bla", mac: "bla", iv: "bla" },
},
},
}),
@@ -603,7 +603,7 @@ describe("Secrets", function() {
type: "m.cross_signing.user_signing",
content: {
encrypted: {
key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
key_id: { ciphertext: "bla", mac: "bla", iv: "bla" },
},
},
}),
@@ -619,7 +619,7 @@ describe("Secrets", function() {
},
}),
]);
alice._crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
alice.crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: {
master: {
user_id: "@alice:example.com",
@@ -13,9 +13,9 @@ 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 {InRoomChannel} from "../../../../src/crypto/verification/request/InRoomChannel";
import { InRoomChannel } from "../../../../src/crypto/verification/request/InRoomChannel";
"../../../../src/crypto/verification/request/ToDeviceChannel";
import {MatrixEvent} from "../../../../src/models/event";
import { MatrixEvent } from "../../../../src/models/event";
describe("InRoomChannel tests", function() {
const ALICE = "@alice:hs.tld";
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import "../../../olm-loader";
import {logger} from "../../../../src/logger";
import { logger } from "../../../../src/logger";
const Olm = global.Olm;
@@ -15,10 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import "../../../olm-loader";
import {verificationMethods} from "../../../../src/crypto";
import {logger} from "../../../../src/logger";
import {SAS} from "../../../../src/crypto/verification/SAS";
import {makeTestClients, setupWebcrypto, teardownWebcrypto} from './util';
import { verificationMethods } from "../../../../src/crypto";
import { logger } from "../../../../src/logger";
import { SAS } from "../../../../src/crypto/verification/SAS";
import { makeTestClients, setupWebcrypto, teardownWebcrypto } from './util';
const Olm = global.Olm;
@@ -42,14 +42,14 @@ describe("verification request integration tests with crypto layer", function()
it("should request and accept a verification", async function() {
const [alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@bob:example.com", deviceId: "Dynabook"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{ userId: "@bob:example.com", deviceId: "Dynabook" },
],
{
verificationMethods: [verificationMethods.SAS],
},
);
alice.client._crypto._deviceList.getRawStoredDevicesForUser = function() {
alice.client.crypto._deviceList.getRawStoredDevicesForUser = function() {
return {
Dynabook: {
keys: {
+19 -19
View File
@@ -15,14 +15,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import "../../../olm-loader";
import {makeTestClients, setupWebcrypto, teardownWebcrypto} from './util';
import {MatrixEvent} from "../../../../src/models/event";
import {SAS} from "../../../../src/crypto/verification/SAS";
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
import {verificationMethods} from "../../../../src/crypto";
import { makeTestClients, setupWebcrypto, teardownWebcrypto } from './util';
import { MatrixEvent } from "../../../../src/models/event";
import { SAS } from "../../../../src/crypto/verification/SAS";
import { DeviceInfo } from "../../../../src/crypto/deviceinfo";
import { verificationMethods } from "../../../../src/crypto";
import * as olmlib from "../../../../src/crypto/olmlib";
import {logger} from "../../../../src/logger";
import {resetCrossSigningKeys} from "../crypto-utils";
import { logger } from "../../../../src/logger";
import { resetCrossSigningKeys } from "../crypto-utils";
const Olm = global.Olm;
@@ -79,16 +79,16 @@ describe("SAS verification", function() {
beforeEach(async () => {
[alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@bob:example.com", deviceId: "Dynabook"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{ userId: "@bob:example.com", deviceId: "Dynabook" },
],
{
verificationMethods: [verificationMethods.SAS],
},
);
const aliceDevice = alice.client._crypto._olmDevice;
const bobDevice = bob.client._crypto._olmDevice;
const aliceDevice = alice.client.crypto._olmDevice;
const bobDevice = bob.client.crypto._olmDevice;
ALICE_DEVICES = {
Osborne2: {
@@ -114,14 +114,14 @@ describe("SAS verification", function() {
},
};
alice.client._crypto._deviceList.storeDevicesForUser(
alice.client.crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.client._crypto._deviceList.storeDevicesForUser(
bob.client.crypto._deviceList.storeDevicesForUser(
"@alice:example.com", ALICE_DEVICES,
);
bob.client.downloadKeys = () => {
@@ -296,9 +296,9 @@ describe("SAS verification", function() {
await resetCrossSigningKeys(bob.client);
bob.client._crypto._deviceList.storeCrossSigningForUser(
bob.client.crypto._deviceList.storeCrossSigningForUser(
"@alice:example.com", {
keys: alice.client._crypto._crossSigningInfo.keys,
keys: alice.client.crypto._crossSigningInfo.keys,
},
);
@@ -336,8 +336,8 @@ describe("SAS verification", function() {
it("should send a cancellation message on error", async function() {
const [alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@bob:example.com", deviceId: "Dynabook"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{ userId: "@bob:example.com", deviceId: "Dynabook" },
],
{
verificationMethods: [verificationMethods.SAS],
@@ -390,8 +390,8 @@ describe("SAS verification", function() {
beforeEach(async function() {
[alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@bob:example.com", deviceId: "Dynabook"},
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{ userId: "@bob:example.com", deviceId: "Dynabook" },
],
{
verificationMethods: [verificationMethods.SAS],
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {VerificationBase} from '../../../../src/crypto/verification/Base';
import {CrossSigningInfo} from '../../../../src/crypto/CrossSigning';
import {encodeBase64} from "../../../../src/crypto/olmlib";
import {setupWebcrypto, teardownWebcrypto} from './util';
import { VerificationBase } from '../../../../src/crypto/verification/Base';
import { CrossSigningInfo } from '../../../../src/crypto/CrossSigning';
import { encodeBase64 } from "../../../../src/crypto/olmlib";
import { setupWebcrypto, teardownWebcrypto } from './util';
jest.useFakeTimers();
@@ -69,7 +69,7 @@ describe("self-verifications", () => {
const restoreKeyBackupWithCache = jest.fn(() => Promise.resolve());
const client = {
_crypto: {
crypto: {
_crossSigningInfo,
_secretStorage,
storeSessionBackupPrivateKey,
+10 -10
View File
@@ -15,10 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {TestClient} from '../../../TestClient';
import {MatrixEvent} from "../../../../src/models/event";
import { TestClient } from '../../../TestClient';
import { MatrixEvent } from "../../../../src/models/event";
import nodeCrypto from "crypto";
import {logger} from '../../../../src/logger';
import { logger } from '../../../../src/logger';
export async function makeTestClients(userInfos, options) {
const clients = [];
@@ -30,13 +30,13 @@ export async function makeTestClients(userInfos, options) {
for (const [deviceId, msg] of Object.entries(devMap)) {
if (deviceId in clientMap[userId]) {
const event = new MatrixEvent({
sender: this.getUserId(), // eslint-disable-line babel/no-invalid-this
sender: this.getUserId(), // eslint-disable-line @babel/no-invalid-this
type: type,
content: msg,
});
const client = clientMap[userId][deviceId];
const decryptionPromise = event.isEncrypted() ?
event.attemptDecryption(client._crypto) :
event.attemptDecryption(client.crypto) :
Promise.resolve();
decryptionPromise.then(
@@ -49,9 +49,9 @@ export async function makeTestClients(userInfos, options) {
};
const sendEvent = function(room, type, content) {
// make up a unique ID as the event ID
const eventId = "$" + this.makeTxnId(); // eslint-disable-line babel/no-invalid-this
const eventId = "$" + this.makeTxnId(); // eslint-disable-line @babel/no-invalid-this
const rawEvent = {
sender: this.getUserId(), // eslint-disable-line babel/no-invalid-this
sender: this.getUserId(), // eslint-disable-line @babel/no-invalid-this
type: type,
content: content,
room_id: room,
@@ -61,13 +61,13 @@ export async function makeTestClients(userInfos, options) {
const event = new MatrixEvent(rawEvent);
const remoteEcho = new MatrixEvent(Object.assign({}, rawEvent, {
unsigned: {
transaction_id: this.makeTxnId(), // eslint-disable-line babel/no-invalid-this
transaction_id: this.makeTxnId(), // eslint-disable-line @babel/no-invalid-this
},
}));
setImmediate(() => {
for (const tc of clients) {
if (tc.client === this) { // eslint-disable-line babel/no-invalid-this
if (tc.client === this) { // eslint-disable-line @babel/no-invalid-this
logger.log("sending remote echo!!");
tc.client.emit("Room.timeline", remoteEcho);
} else {
@@ -76,7 +76,7 @@ export async function makeTestClients(userInfos, options) {
}
});
return Promise.resolve({event_id: eventId});
return Promise.resolve({ event_id: eventId });
};
for (const userInfo of userInfos) {
@@ -13,13 +13,13 @@ 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 {VerificationRequest, READY_TYPE, START_TYPE, DONE_TYPE} from
import { VerificationRequest, READY_TYPE, START_TYPE, DONE_TYPE } from
"../../../../src/crypto/verification/request/VerificationRequest";
import {InRoomChannel} from "../../../../src/crypto/verification/request/InRoomChannel";
import {ToDeviceChannel} from
import { InRoomChannel } from "../../../../src/crypto/verification/request/InRoomChannel";
import { ToDeviceChannel } from
"../../../../src/crypto/verification/request/ToDeviceChannel";
import {MatrixEvent} from "../../../../src/models/event";
import {setupWebcrypto, teardownWebcrypto} from "./util";
import { MatrixEvent } from "../../../../src/models/event";
import { setupWebcrypto, teardownWebcrypto } from "./util";
function makeMockClient(userId, deviceId) {
let counter = 1;
@@ -40,7 +40,7 @@ function makeMockClient(userId, deviceId) {
content,
origin_server_ts: Date.now(),
}));
return Promise.resolve({event_id: eventId});
return Promise.resolve({ event_id: eventId });
},
sendToDevice(type, msgMap) {
@@ -48,7 +48,7 @@ function makeMockClient(userId, deviceId) {
const deviceMap = msgMap[userId];
for (const deviceId of Object.keys(deviceMap)) {
const content = deviceMap[deviceId];
const event = new MatrixEvent({content, type});
const event = new MatrixEvent({ content, type });
deviceEvents[userId] = deviceEvents[userId] || {};
deviceEvents[userId][deviceId] = deviceEvents[userId][deviceId] || [];
deviceEvents[userId][deviceId].push(event);
@@ -90,7 +90,7 @@ class MockVerifier {
if (this._startEvent) {
await this._channel.send(DONE_TYPE, {});
} else {
await this._channel.send(START_TYPE, {method: MOCK_METHOD});
await this._channel.send(START_TYPE, { method: MOCK_METHOD });
}
}
@@ -226,7 +226,7 @@ describe("verification request unit tests", function() {
new ToDeviceChannel(bob1, bob1.getUserId(), ["device1", "device2"],
ToDeviceChannel.makeTransactionId(), "device2"),
new Map([[MOCK_METHOD, MockVerifier]]), bob1);
const to = {userId: "@bob:matrix.tld", deviceId: "device2"};
const to = { userId: "@bob:matrix.tld", deviceId: "device2" };
const verifier = bob1Request.beginKeyVerification(MOCK_METHOD, to);
expect(verifier).toBeInstanceOf(MockVerifier);
await verifier.start();
+7 -9
View File
@@ -1,6 +1,6 @@
import * as utils from "../test-utils";
import {EventTimeline} from "../../src/models/event-timeline";
import {RoomState} from "../../src/models/room-state";
import { EventTimeline } from "../../src/models/event-timeline";
import { RoomState } from "../../src/models/room-state";
function mockRoomStates(timeline) {
timeline._startState = utils.mock(RoomState, "startState");
@@ -15,7 +15,7 @@ describe("EventTimeline", function() {
beforeEach(function() {
// XXX: this is a horrid hack; should use sinon or something instead to mock
const timelineSet = { room: { roomId: roomId }};
const timelineSet = { room: { roomId: roomId } };
timelineSet.room.getUnfilteredTimelineSet = function() {
return timelineSet;
};
@@ -94,7 +94,6 @@ describe("EventTimeline", function() {
});
});
describe("neighbouringTimelines", function() {
it("neighbouring timelines should start null", function() {
expect(timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)).toBe(null);
@@ -102,8 +101,8 @@ describe("EventTimeline", function() {
});
it("setNeighbouringTimeline should set neighbour", function() {
const prev = {a: "a"};
const next = {b: "b"};
const prev = { a: "a" };
const next = { b: "b" };
timeline.setNeighbouringTimeline(prev, EventTimeline.BACKWARDS);
timeline.setNeighbouringTimeline(next, EventTimeline.FORWARDS);
expect(timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)).toBe(prev);
@@ -111,8 +110,8 @@ describe("EventTimeline", function() {
});
it("setNeighbouringTimeline should throw if called twice", function() {
const prev = {a: "a"};
const next = {b: "b"};
const prev = { a: "a" };
const next = { b: "b" };
expect(function() {
timeline.setNeighbouringTimeline(prev, EventTimeline.BACKWARDS);
}).not.toThrow();
@@ -278,7 +277,6 @@ describe("EventTimeline", function() {
not.toHaveBeenCalled();
});
it("should call setStateEvents on the right RoomState with the right " +
"forwardLooking value for old events", function() {
const events = [
+2 -2
View File
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {logger} from "../../src/logger";
import {MatrixEvent} from "../../src/models/event";
import { logger } from "../../src/logger";
import { MatrixEvent } from "../../src/models/event";
describe("MatrixEvent", () => {
describe(".attemptDecryption", () => {
+2 -2
View File
@@ -1,5 +1,5 @@
import {FilterComponent} from "../../src/filter-component";
import {mkEvent} from '../test-utils';
import { FilterComponent } from "../../src/filter-component";
import { mkEvent } from '../test-utils';
describe("Filter Component", function() {
describe("types", function() {
+1 -1
View File
@@ -1,4 +1,4 @@
import {Filter} from "../../src/filter";
import { Filter } from "../../src/filter";
describe("Filter", function() {
const filterId = "f1lt3ring15g00d4ursoul";
+5 -5
View File
@@ -15,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {logger} from "../../src/logger";
import {InteractiveAuth} from "../../src/interactive-auth";
import {MatrixError} from "../../src/http-api";
import { logger } from "../../src/logger";
import { InteractiveAuth } from "../../src/interactive-auth";
import { MatrixError } from "../../src/http-api";
// Trivial client object to test interactive auth
// (we do not need TestClient here)
@@ -63,7 +63,7 @@ describe("InteractiveAuth", function() {
});
// .. which should trigger a call here
const requestRes = {"a": "b"};
const requestRes = { "a": "b" };
doRequest.mockImplementation(function(authData) {
logger.log('cccc');
expect(authData).toEqual({
@@ -112,7 +112,7 @@ describe("InteractiveAuth", function() {
});
// .. which should be followed by a call to stateUpdated
const requestRes = {"a": "b"};
const requestRes = { "a": "b" };
stateUpdated.mockImplementation(function(stage) {
expect(stage).toEqual("logintype");
expect(ia.getSessionId()).toEqual("sessionId");
+1 -1
View File
@@ -1,4 +1,4 @@
import {TestClient} from '../TestClient';
import { TestClient } from '../TestClient';
describe('Login request', function() {
let client;
+190 -9
View File
@@ -1,6 +1,18 @@
import {logger} from "../../src/logger";
import {MatrixClient} from "../../src/client";
import {Filter} from "../../src/filter";
import { logger } from "../../src/logger";
import { MatrixClient } from "../../src/client";
import { Filter } from "../../src/filter";
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE } from "../../src/models/MSC3089TreeSpace";
import {
EventType,
RoomCreateTypeField,
RoomType,
UNSTABLE_MSC3088_ENABLED,
UNSTABLE_MSC3088_PURPOSE,
UNSTABLE_MSC3089_TREE_SUBTYPE,
} from "../../src/@types/event";
import { MEGOLM_ALGORITHM } from "../../src/crypto/olmlib";
import { MatrixEvent } from "../../src/models/event";
import { Preset } from "../../src/@types/partials";
jest.useFakeTimers();
@@ -144,12 +156,12 @@ describe("MatrixClient", function() {
scheduler: scheduler,
userId: userId,
});
// FIXME: We shouldn't be yanking _http like this.
client._http = [
// FIXME: We shouldn't be yanking http like this.
client.http = [
"authedRequest", "getContentUri", "request", "uploadContent",
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
client._http.authedRequest.mockImplementation(httpReq);
client._http.request.mockImplementation(httpReq);
client.http.authedRequest.mockImplementation(httpReq);
client.http.request.mockImplementation(httpReq);
// set reasonable working defaults
acceptKeepalives = true;
@@ -166,11 +178,165 @@ describe("MatrixClient", function() {
// means they may call /events and then fail an expect() which will fail
// a DIFFERENT test (pollution between tests!) - we return unresolved
// promises to stop the client from continuing to run.
client._http.authedRequest.mockImplementation(function() {
client.http.authedRequest.mockImplementation(function() {
return new Promise(() => {});
});
});
it("should create (unstable) file trees", async () => {
const userId = "@test:example.org";
const roomId = "!room:example.org";
const roomName = "Test Tree";
const mockRoom = {};
const fn = jest.fn().mockImplementation((opts) => {
expect(opts).toMatchObject({
name: roomName,
preset: Preset.PrivateChat,
power_level_content_override: {
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
users: {
[userId]: 100,
},
},
creation_content: {
[RoomCreateTypeField]: RoomType.Space,
},
initial_state: [
{
// We use `unstable` to ensure that the code is actually using the right identifier
type: UNSTABLE_MSC3088_PURPOSE.unstable,
state_key: UNSTABLE_MSC3089_TREE_SUBTYPE.unstable,
content: {
[UNSTABLE_MSC3088_ENABLED.unstable]: true,
},
},
{
type: EventType.RoomEncryption,
state_key: "",
content: {
algorithm: MEGOLM_ALGORITHM,
},
},
],
});
return { room_id: roomId };
});
client.getUserId = () => userId;
client.createRoom = fn;
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return mockRoom;
};
const tree = await client.unstableCreateFileTree(roomName);
expect(tree).toBeDefined();
expect(tree.roomId).toEqual(roomId);
expect(tree.room).toBe(mockRoom);
expect(fn.mock.calls.length).toBe(1);
});
it("should get (unstable) file trees with valid state", async () => {
const roomId = "!room:example.org";
const mockRoom = {
currentState: {
getStateEvents: (eventType, stateKey) => {
if (eventType === EventType.RoomCreate) {
expect(stateKey).toEqual("");
return new MatrixEvent({
content: {
[RoomCreateTypeField]: RoomType.Space,
},
});
} else if (eventType === UNSTABLE_MSC3088_PURPOSE.unstable) {
// We use `unstable` to ensure that the code is actually using the right identifier
expect(stateKey).toEqual(UNSTABLE_MSC3089_TREE_SUBTYPE.unstable);
return new MatrixEvent({
content: {
[UNSTABLE_MSC3088_ENABLED.unstable]: true,
},
});
} else {
throw new Error("Unexpected event type or state key");
}
},
},
};
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return mockRoom;
};
const tree = client.unstableGetFileTreeSpace(roomId);
expect(tree).toBeDefined();
expect(tree.roomId).toEqual(roomId);
expect(tree.room).toBe(mockRoom);
});
it("should not get (unstable) file trees with invalid create contents", async () => {
const roomId = "!room:example.org";
const mockRoom = {
currentState: {
getStateEvents: (eventType, stateKey) => {
if (eventType === EventType.RoomCreate) {
expect(stateKey).toEqual("");
return new MatrixEvent({
content: {
[RoomCreateTypeField]: "org.example.not_space",
},
});
} else if (eventType === UNSTABLE_MSC3088_PURPOSE.unstable) {
// We use `unstable` to ensure that the code is actually using the right identifier
expect(stateKey).toEqual(UNSTABLE_MSC3089_TREE_SUBTYPE.unstable);
return new MatrixEvent({
content: {
[UNSTABLE_MSC3088_ENABLED.unstable]: true,
},
});
} else {
throw new Error("Unexpected event type or state key");
}
},
},
};
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return mockRoom;
};
const tree = client.unstableGetFileTreeSpace(roomId);
expect(tree).toBeFalsy();
});
it("should not get (unstable) file trees with invalid purpose/subtype contents", async () => {
const roomId = "!room:example.org";
const mockRoom = {
currentState: {
getStateEvents: (eventType, stateKey) => {
if (eventType === EventType.RoomCreate) {
expect(stateKey).toEqual("");
return new MatrixEvent({
content: {
[RoomCreateTypeField]: RoomType.Space,
},
});
} else if (eventType === UNSTABLE_MSC3088_PURPOSE.unstable) {
expect(stateKey).toEqual(UNSTABLE_MSC3089_TREE_SUBTYPE.unstable);
return new MatrixEvent({
content: {
[UNSTABLE_MSC3088_ENABLED.unstable]: false,
},
});
} else {
throw new Error("Unexpected event type or state key");
}
},
},
};
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return mockRoom;
};
const tree = client.unstableGetFileTreeSpace(roomId);
expect(tree).toBeFalsy();
});
it("should not POST /filter if a matching filter already exists", async function() {
httpLookups = [];
httpLookups.push(PUSH_RULES_RESPONSE);
@@ -178,7 +344,7 @@ describe("MatrixClient", function() {
const filterId = "ehfewf";
store.getFilterIdByName.mockReturnValue(filterId);
const filter = new Filter(0, filterId);
filter.setDefinition({"room": {"timeline": {"limit": 8}}});
filter.setDefinition({ "room": { "timeline": { "limit": 8 } } });
store.getFilter.mockReturnValue(filter);
const syncPromise = new Promise((resolve, reject) => {
client.on("sync", function syncListener(state) {
@@ -521,4 +687,19 @@ describe("MatrixClient", function() {
xit("should be able to peek into a room using peekInRoom", function(done) {
});
});
describe("getPresence", function() {
it("should send a presence HTTP GET", function() {
httpLookups = [{
method: "GET",
path: `/presence/${encodeURIComponent(userId)}/status`,
data: {
"presence": "unavailable",
"last_active_ago": 420845,
},
}];
client.getPresence(userId);
expect(httpLookups.length).toEqual(0);
});
});
});
+155
View File
@@ -0,0 +1,155 @@
/*
Copyright 2021 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 { MatrixClient } from "../../../src";
import { Room } from "../../../src/models/room";
import { MatrixEvent } from "../../../src/models/event";
import { UNSTABLE_MSC3089_BRANCH } from "../../../src/@types/event";
import { EventTimelineSet } from "../../../src/models/event-timeline-set";
import { EventTimeline } from "../../../src/models/event-timeline";
import { MSC3089Branch } from "../../../src/models/MSC3089Branch";
describe("MSC3089Branch", () => {
let client: MatrixClient;
// @ts-ignore - TS doesn't know that this is a type
let indexEvent: MatrixEvent;
let branch: MSC3089Branch;
const branchRoomId = "!room:example.org";
const fileEventId = "$file";
const staticTimelineSets = {} as EventTimelineSet;
const staticRoom = {
getUnfilteredTimelineSet: () => staticTimelineSets,
} as any as Room; // partial
beforeEach(() => {
// TODO: Use utility functions to create test rooms and clients
client = <MatrixClient>{
getRoom: (roomId: string) => {
if (roomId === branchRoomId) {
return staticRoom;
} else {
throw new Error("Unexpected fetch for unknown room");
}
},
};
indexEvent = {
getRoomId: () => branchRoomId,
getStateKey: () => fileEventId,
};
branch = new MSC3089Branch(client, indexEvent);
});
it('should know the file event ID', () => {
expect(branch.id).toEqual(fileEventId);
});
it('should know if the file is active or not', () => {
indexEvent.getContent = () => ({});
expect(branch.isActive).toBe(false);
indexEvent.getContent = () => ({ active: false });
expect(branch.isActive).toBe(false);
indexEvent.getContent = () => ({ active: true });
expect(branch.isActive).toBe(true);
indexEvent.getContent = () => ({ active: "true" }); // invalid boolean, inactive
expect(branch.isActive).toBe(false);
});
it('should be able to delete the file', async () => {
const stateFn = jest.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(branchRoomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test that we're definitely using the unstable value
expect(content).toMatchObject({});
expect(content['active']).toBeUndefined();
expect(stateKey).toEqual(fileEventId);
return Promise.resolve(); // return value not used
});
client.sendStateEvent = stateFn;
const redactFn = jest.fn().mockImplementation((roomId: string, eventId: string) => {
expect(roomId).toEqual(branchRoomId);
expect(eventId).toEqual(fileEventId);
return Promise.resolve(); // return value not used
});
client.redactEvent = redactFn;
await branch.delete();
expect(stateFn).toHaveBeenCalledTimes(1);
expect(redactFn).toHaveBeenCalledTimes(1);
});
it('should know its name', async () => {
const name = "My File.txt";
indexEvent.getContent = () => ({ active: true, name: name });
const res = branch.getName();
expect(res).toEqual(name);
});
it('should be able to change its name', async () => {
const name = "My File.txt";
indexEvent.getContent = () => ({ active: true, retained: true });
const stateFn = jest.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(branchRoomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test that we're definitely using the unstable value
expect(content).toMatchObject({
retained: true, // canary for copying state
active: true,
name: name,
});
expect(stateKey).toEqual(fileEventId);
return Promise.resolve(); // return value not used
});
client.sendStateEvent = stateFn;
await branch.setName(name);
expect(stateFn).toHaveBeenCalledTimes(1);
});
it('should be able to return event information', async () => {
const mxcLatter = "example.org/file";
const fileContent = { isFile: "not quite", url: "mxc://" + mxcLatter };
const eventsArr = [
{ getId: () => "$not-file", getContent: () => ({}) },
{ getId: () => fileEventId, getContent: () => ({ file: fileContent }) },
];
client.getEventTimeline = () => Promise.resolve({
getEvents: () => eventsArr,
}) as any as Promise<EventTimeline>; // partial
client.mxcUrlToHttp = (mxc: string) => {
expect(mxc).toEqual("mxc://" + mxcLatter);
return `https://example.org/_matrix/media/v1/download/${mxcLatter}`;
};
client.decryptEventIfNeeded = () => Promise.resolve();
const res = await branch.getFileInfo();
expect(res).toBeDefined();
expect(res).toMatchObject({
info: fileContent,
// Escape regex from MDN guides: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
httpUrl: expect.stringMatching(`.+${mxcLatter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`),
});
});
});
+857
View File
@@ -0,0 +1,857 @@
/*
Copyright 2021 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 { MatrixClient } from "../../../src";
import { Room } from "../../../src/models/room";
import { MatrixEvent } from "../../../src/models/event";
import { EventType, MsgType, UNSTABLE_MSC3089_BRANCH, UNSTABLE_MSC3089_LEAF } from "../../../src/@types/event";
import {
DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
MSC3089TreeSpace,
TreePermissions,
} from "../../../src/models/MSC3089TreeSpace";
import { DEFAULT_ALPHABET } from "../../../src/utils";
import { MockBlob } from "../../MockBlob";
describe("MSC3089TreeSpace", () => {
let client: MatrixClient;
let room: Room;
let tree: MSC3089TreeSpace;
const roomId = "!tree:localhost";
const targetUser = "@target:example.org";
let powerLevels;
beforeEach(() => {
// TODO: Use utility functions to create test rooms and clients
client = <MatrixClient>{
getRoom: (fetchRoomId: string) => {
if (fetchRoomId === roomId) {
return room;
} else {
throw new Error("Unexpected fetch for unknown room");
}
},
};
room = <Room>{
currentState: {
getStateEvents: (evType: EventType, stateKey: string) => {
if (evType === EventType.RoomPowerLevels && stateKey === "") {
return powerLevels;
} else {
throw new Error("Accessed unexpected state event type or key");
}
},
},
};
tree = new MSC3089TreeSpace(client, roomId);
makePowerLevels(DEFAULT_TREE_POWER_LEVELS_TEMPLATE);
});
function makePowerLevels(content: any) {
powerLevels = new MatrixEvent({
type: EventType.RoomPowerLevels,
state_key: "",
sender: "@creator:localhost",
event_id: "$powerlevels",
room_id: roomId,
content: content,
});
}
it('should populate the room reference', () => {
expect(tree.room).toBe(room);
});
it('should proxy the ID member to room ID', () => {
expect(tree.id).toEqual(tree.roomId);
expect(tree.id).toEqual(roomId);
});
it('should support setting the name of the space', async () => {
const newName = "NEW NAME";
const fn = jest.fn()
.mockImplementation((stateRoomId: string, eventType: EventType, content: any, stateKey: string) => {
expect(stateRoomId).toEqual(roomId);
expect(eventType).toEqual(EventType.RoomName);
expect(stateKey).toEqual("");
expect(content).toMatchObject({ name: newName });
return Promise.resolve();
});
client.sendStateEvent = fn;
await tree.setName(newName);
expect(fn.mock.calls.length).toBe(1);
});
it('should support inviting users to the space', async () => {
const target = targetUser;
const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
expect(inviteRoomId).toEqual(roomId);
expect(userId).toEqual(target);
return Promise.resolve();
});
client.invite = fn;
await tree.invite(target);
expect(fn.mock.calls.length).toBe(1);
});
async function evaluatePowerLevels(pls: any, role: TreePermissions, expectedPl: number) {
makePowerLevels(pls);
const fn = jest.fn()
.mockImplementation((stateRoomId: string, eventType: EventType, content: any, stateKey: string) => {
expect(stateRoomId).toEqual(roomId);
expect(eventType).toEqual(EventType.RoomPowerLevels);
expect(stateKey).toEqual("");
expect(content).toMatchObject({
...pls,
users: {
[targetUser]: expectedPl,
},
});
return Promise.resolve();
});
client.sendStateEvent = fn;
await tree.setPermissions(targetUser, role);
expect(fn.mock.calls.length).toBe(1);
}
it('should support setting Viewer permissions', () => {
return evaluatePowerLevels({
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
users_default: 1024,
}, TreePermissions.Viewer, 1024);
});
it('should support setting Editor permissions', () => {
return evaluatePowerLevels({
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
events_default: 1024,
}, TreePermissions.Editor, 1024);
});
it('should support setting Owner permissions', () => {
return evaluatePowerLevels({
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
events: {
[EventType.RoomPowerLevels]: 1024,
},
}, TreePermissions.Owner, 1024);
});
it('should support demoting permissions', () => {
return evaluatePowerLevels({
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
users_default: 1024,
users: {
[targetUser]: 2222,
},
}, TreePermissions.Viewer, 1024);
});
it('should support promoting permissions', () => {
return evaluatePowerLevels({
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
events_default: 1024,
users: {
[targetUser]: 5,
},
}, TreePermissions.Editor, 1024);
});
it('should support defaults: Viewer', () => {
return evaluatePowerLevels({}, TreePermissions.Viewer, 0);
});
it('should support defaults: Editor', () => {
return evaluatePowerLevels({}, TreePermissions.Editor, 50);
});
it('should support defaults: Owner', () => {
return evaluatePowerLevels({}, TreePermissions.Owner, 100);
});
it('should create subdirectories', async () => {
const subspaceName = "subdirectory";
const subspaceId = "!subspace:localhost";
const domain = "domain.example.com";
client.getRoom = (roomId: string) => {
if (roomId === tree.roomId) {
return tree.room;
} else if (roomId === subspaceId) {
return {} as Room; // we don't need anything important off of this
} else {
throw new Error("Unexpected getRoom call");
}
};
client.getDomain = () => domain;
const createFn = jest.fn().mockImplementation(async (name: string) => {
expect(name).toEqual(subspaceName);
return new MSC3089TreeSpace(client, subspaceId);
});
const sendStateFn = jest.fn()
.mockImplementation(async (roomId: string, eventType: EventType, content: any, stateKey: string) => {
expect([tree.roomId, subspaceId]).toContain(roomId);
if (roomId === subspaceId) {
expect(eventType).toEqual(EventType.SpaceParent);
expect(stateKey).toEqual(tree.roomId);
} else {
expect(eventType).toEqual(EventType.SpaceChild);
expect(stateKey).toEqual(subspaceId);
}
expect(content).toMatchObject({ via: [domain] });
// return value not used
});
client.unstableCreateFileTree = createFn;
client.sendStateEvent = sendStateFn;
const directory = await tree.createDirectory(subspaceName);
expect(directory).toBeDefined();
expect(directory).not.toBeNull();
expect(directory).not.toBe(tree);
expect(directory.roomId).toEqual(subspaceId);
expect(createFn).toHaveBeenCalledTimes(1);
expect(sendStateFn).toHaveBeenCalledTimes(2);
const content = expect.objectContaining({ via: [domain] });
expect(sendStateFn).toHaveBeenCalledWith(subspaceId, EventType.SpaceParent, content, tree.roomId);
expect(sendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, content, subspaceId);
});
it('should find subdirectories', () => {
const firstChildRoom = "!one:example.org";
const secondChildRoom = "!two:example.org";
const thirdChildRoom = "!three:example.org"; // to ensure it doesn't end up in the subdirectories
room.currentState = {
getStateEvents: (eventType: EventType, stateKey?: string) => {
expect(eventType).toEqual(EventType.SpaceChild);
expect(stateKey).toBeUndefined();
return [
// Partial implementations of Room
{ getStateKey: () => firstChildRoom },
{ getStateKey: () => secondChildRoom },
{ getStateKey: () => thirdChildRoom },
];
},
};
client.getRoom = () => ({} as Room); // to appease the TreeSpace constructor
const getFn = jest.fn().mockImplementation((roomId: string) => {
if (roomId === thirdChildRoom) {
throw new Error("Mock not-a-space room case called (expected)");
}
expect([firstChildRoom, secondChildRoom]).toContain(roomId);
return new MSC3089TreeSpace(client, roomId);
});
client.unstableGetFileTreeSpace = getFn;
const subdirectories = tree.getDirectories();
expect(subdirectories).toBeDefined();
expect(subdirectories.length).toBe(2);
expect(subdirectories[0].roomId).toBe(firstChildRoom);
expect(subdirectories[1].roomId).toBe(secondChildRoom);
expect(getFn).toHaveBeenCalledTimes(3);
expect(getFn).toHaveBeenCalledWith(firstChildRoom);
expect(getFn).toHaveBeenCalledWith(secondChildRoom);
expect(getFn).toHaveBeenCalledWith(thirdChildRoom); // check to make sure it tried
});
it('should find specific directories', () => {
client.getRoom = () => ({} as Room); // to appease the TreeSpace constructor
// Only mocking used API
const firstSubdirectory = { roomId: "!first:example.org" } as any as MSC3089TreeSpace;
const searchedSubdirectory = { roomId: "!find_me:example.org" } as any as MSC3089TreeSpace;
const thirdSubdirectory = { roomId: "!third:example.org" } as any as MSC3089TreeSpace;
tree.getDirectories = () => [firstSubdirectory, searchedSubdirectory, thirdSubdirectory];
let result = tree.getDirectory(searchedSubdirectory.roomId);
expect(result).toBe(searchedSubdirectory);
result = tree.getDirectory("not a subdirectory");
expect(result).toBeFalsy();
});
it('should be able to delete itself', async () => {
const delete1 = jest.fn().mockImplementation(() => Promise.resolve());
const subdir1 = { delete: delete1 } as any as MSC3089TreeSpace; // mock tested bits
const delete2 = jest.fn().mockImplementation(() => Promise.resolve());
const subdir2 = { delete: delete2 } as any as MSC3089TreeSpace; // mock tested bits
const joinMemberId = "@join:example.org";
const knockMemberId = "@knock:example.org";
const inviteMemberId = "@invite:example.org";
const leaveMemberId = "@leave:example.org";
const banMemberId = "@ban:example.org";
const selfUserId = "@self:example.org";
tree.getDirectories = () => [subdir1, subdir2];
room.currentState = {
getStateEvents: (eventType: EventType, stateKey?: string) => {
expect(eventType).toEqual(EventType.RoomMember);
expect(stateKey).toBeUndefined();
return [
// Partial implementations
{ getContent: () => ({ membership: "join" }), getStateKey: () => joinMemberId },
{ getContent: () => ({ membership: "knock" }), getStateKey: () => knockMemberId },
{ getContent: () => ({ membership: "invite" }), getStateKey: () => inviteMemberId },
{ getContent: () => ({ membership: "leave" }), getStateKey: () => leaveMemberId },
{ getContent: () => ({ membership: "ban" }), getStateKey: () => banMemberId },
// ensure we don't kick ourselves
{ getContent: () => ({ membership: "join" }), getStateKey: () => selfUserId },
];
},
};
// These two functions are tested by input expectations, so no expectations in the function bodies
const kickFn = jest.fn().mockImplementation((userId) => Promise.resolve());
const leaveFn = jest.fn().mockImplementation(() => Promise.resolve());
client.kick = kickFn;
client.leave = leaveFn;
client.getUserId = () => selfUserId;
await tree.delete();
expect(delete1).toHaveBeenCalledTimes(1);
expect(delete2).toHaveBeenCalledTimes(1);
expect(kickFn).toHaveBeenCalledTimes(3);
expect(kickFn).toHaveBeenCalledWith(tree.roomId, joinMemberId, expect.any(String));
expect(kickFn).toHaveBeenCalledWith(tree.roomId, knockMemberId, expect.any(String));
expect(kickFn).toHaveBeenCalledWith(tree.roomId, inviteMemberId, expect.any(String));
expect(leaveFn).toHaveBeenCalledTimes(1);
});
describe('get and set order', () => {
// Danger: these are partial implementations for testing purposes only
// @ts-ignore - "MatrixEvent is a value but used as a type", which is true but not important
let childState: { [roomId: string]: MatrixEvent[] } = {};
// @ts-ignore - "MatrixEvent is a value but used as a type", which is true but not important
let parentState: MatrixEvent[] = [];
let parentRoom: Room;
let childTrees: MSC3089TreeSpace[];
let rooms: { [roomId: string]: Room };
let clientSendStateFn: jest.MockedFunction<typeof client.sendStateEvent>;
const staticDomain = "static.example.org";
function addSubspace(roomId: string, createTs?: number, order?: string) {
const content = {
via: [staticDomain],
};
if (order) content['order'] = order;
parentState.push({
getType: () => EventType.SpaceChild,
getStateKey: () => roomId,
getContent: () => content,
});
childState[roomId] = [
{
getType: () => EventType.SpaceParent,
getStateKey: () => tree.roomId,
getContent: () => ({
via: [staticDomain],
}),
},
];
if (createTs) {
childState[roomId].push({
getType: () => EventType.RoomCreate,
getStateKey: () => "",
getContent: () => ({}),
getTs: () => createTs,
});
}
rooms[roomId] = makeMockChildRoom(roomId);
childTrees.push(new MSC3089TreeSpace(client, roomId));
}
function expectOrder(childRoomId: string, order: number) {
const child = childTrees.find(c => c.roomId === childRoomId);
expect(child).toBeDefined();
expect(child.getOrder()).toEqual(order);
}
function makeMockChildRoom(roomId: string): Room {
return {
currentState: {
getStateEvents: (eventType: EventType, stateKey?: string) => {
expect([EventType.SpaceParent, EventType.RoomCreate]).toContain(eventType);
if (eventType === EventType.RoomCreate) {
expect(stateKey).toEqual("");
return childState[roomId].find(e => e.getType() === EventType.RoomCreate);
} else {
expect(stateKey).toBeUndefined();
return childState[roomId].filter(e => e.getType() === eventType);
}
},
},
} as Room; // partial
}
beforeEach(() => {
childState = {};
parentState = [];
parentRoom = {
...tree.room,
roomId: tree.roomId,
currentState: {
getStateEvents: (eventType: EventType, stateKey?: string) => {
expect([
EventType.SpaceChild,
EventType.RoomCreate,
EventType.SpaceParent,
]).toContain(eventType);
if (eventType === EventType.RoomCreate) {
expect(stateKey).toEqual("");
return parentState.filter(e => e.getType() === EventType.RoomCreate)[0];
} else {
if (stateKey !== undefined) {
expect(Object.keys(rooms)).toContain(stateKey);
expect(stateKey).not.toEqual(tree.roomId);
return parentState.find(e => e.getType() === eventType && e.getStateKey() === stateKey);
} // else fine
return parentState.filter(e => e.getType() === eventType);
}
},
},
} as Room;
childTrees = [];
rooms = {};
rooms[tree.roomId] = parentRoom;
(<any>tree).room = parentRoom; // override readonly
client.getRoom = (r) => rooms[r];
clientSendStateFn = jest.fn()
.mockImplementation((roomId: string, eventType: EventType, content: any, stateKey: string) => {
expect(roomId).toEqual(tree.roomId);
expect(eventType).toEqual(EventType.SpaceChild);
expect(content).toMatchObject(expect.objectContaining({
via: expect.any(Array),
order: expect.any(String),
}));
expect(Object.keys(rooms)).toContain(stateKey);
expect(stateKey).not.toEqual(tree.roomId);
const stateEvent = parentState.find(e => e.getType() === eventType && e.getStateKey() === stateKey);
expect(stateEvent).toBeDefined();
stateEvent.getContent = () => content;
return Promise.resolve(); // return value not used
});
client.sendStateEvent = clientSendStateFn;
});
it('should know when something is top level', () => {
const a = "!a:example.org";
addSubspace(a);
expect(tree.isTopLevel).toBe(true);
expect(childTrees[0].isTopLevel).toBe(false); // a bit of a hack to get at this, but it's fine
});
it('should return -1 for top level spaces', () => {
// The tree is what we've defined as top level, so it should work
expect(tree.getOrder()).toEqual(-1);
});
it('should throw when setting an order at the top level space', async () => {
try {
// The tree is what we've defined as top level, so it should work
await tree.setOrder(2);
// noinspection ExceptionCaughtLocallyJS
throw new Error("Failed to fail");
} catch (e) {
expect(e.message).toEqual("Cannot set order of top level spaces currently");
}
});
it('should return a stable order for unordered children', () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
// Add in reverse order to make sure it gets ordered correctly
addSubspace(c, 3);
addSubspace(b, 2);
addSubspace(a, 1);
expectOrder(a, 0);
expectOrder(b, 1);
expectOrder(c, 2);
});
it('should return a stable order for ordered children', () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
// Add in reverse order to make sure it gets ordered correctly
addSubspace(a, 1, "Z");
addSubspace(b, 2, "Y");
addSubspace(c, 3, "X");
expectOrder(c, 0);
expectOrder(b, 1);
expectOrder(a, 2);
});
it('should return a stable order for partially ordered children', () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
const d = "!d:example.org";
// Add in reverse order to make sure it gets ordered correctly
addSubspace(a, 1);
addSubspace(b, 2);
addSubspace(c, 3, "Y");
addSubspace(d, 4, "X");
expectOrder(d, 0);
expectOrder(c, 1);
expectOrder(b, 3); // note order diff due to room ID comparison expectation
expectOrder(a, 2);
});
it('should return a stable order if the create event timestamps are the same', () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
// Add in reverse order to make sure it gets ordered correctly
addSubspace(c, 3);
addSubspace(b, 3); // same as C
addSubspace(a, 3); // same as C
expectOrder(a, 0);
expectOrder(b, 1);
expectOrder(c, 2);
});
it('should return a stable order if there are no known create events', () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
// Add in reverse order to make sure it gets ordered correctly
addSubspace(c);
addSubspace(b);
addSubspace(a);
expectOrder(a, 0);
expectOrder(b, 1);
expectOrder(c, 2);
});
// XXX: These tests rely on `getOrder()` re-calculating and not caching values.
it('should allow reordering within unordered children', async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
// Add in reverse order to make sure it gets ordered correctly
addSubspace(c, 3);
addSubspace(b, 2);
addSubspace(a, 1);
// Order of this state is validated by other tests.
const treeA = childTrees.find(c => c.roomId === a);
expect(treeA).toBeDefined();
await treeA.setOrder(1);
expect(clientSendStateFn).toHaveBeenCalledTimes(3);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
// Because of how the reordering works (maintain stable ordering before moving), we end up calling this
// function twice for the same room.
order: DEFAULT_ALPHABET[0],
}), a);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: DEFAULT_ALPHABET[1],
}), b);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: DEFAULT_ALPHABET[2],
}), a);
expectOrder(a, 1);
expectOrder(b, 0);
expectOrder(c, 2);
});
it('should allow reordering within ordered children', async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
// Add in reverse order to make sure it gets ordered correctly
addSubspace(c, 3, "Z");
addSubspace(b, 2, "X");
addSubspace(a, 1, "V");
// Order of this state is validated by other tests.
const treeA = childTrees.find(c => c.roomId === a);
expect(treeA).toBeDefined();
await treeA.setOrder(1);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'Y',
}), a);
expectOrder(a, 1);
expectOrder(b, 0);
expectOrder(c, 2);
});
it('should allow reordering within partially ordered children', async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
const d = "!d:example.org";
// Add in reverse order to make sure it gets ordered correctly
addSubspace(a, 1);
addSubspace(b, 2);
addSubspace(c, 3, "Y");
addSubspace(d, 4, "W");
// Order of this state is validated by other tests.
const treeA = childTrees.find(c => c.roomId === a);
expect(treeA).toBeDefined();
await treeA.setOrder(2);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'Z',
}), a);
expectOrder(a, 2);
expectOrder(b, 3);
expectOrder(c, 1);
expectOrder(d, 0);
});
it('should support moving upwards', async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
const d = "!d:example.org";
// Add in reverse order to make sure it gets ordered correctly
addSubspace(d, 4, "Z");
addSubspace(c, 3, "X");
addSubspace(b, 2, "V");
addSubspace(a, 1, "T");
// Order of this state is validated by other tests.
const treeB = childTrees.find(c => c.roomId === b);
expect(treeB).toBeDefined();
await treeB.setOrder(2);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'Y',
}), b);
expectOrder(a, 0);
expectOrder(b, 2);
expectOrder(c, 1);
expectOrder(d, 3);
});
it('should support moving downwards', async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
const d = "!d:example.org";
// Add in reverse order to make sure it gets ordered correctly
addSubspace(d, 4, "Z");
addSubspace(c, 3, "X");
addSubspace(b, 2, "V");
addSubspace(a, 1, "T");
// Order of this state is validated by other tests.
const treeC = childTrees.find(ch => ch.roomId === c);
expect(treeC).toBeDefined();
await treeC.setOrder(1);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'U',
}), c);
expectOrder(a, 0);
expectOrder(b, 2);
expectOrder(c, 1);
expectOrder(d, 3);
});
it('should support moving over the partial ordering boundary', async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
const d = "!d:example.org";
// Add in reverse order to make sure it gets ordered correctly
addSubspace(d, 4);
addSubspace(c, 3);
addSubspace(b, 2, "V");
addSubspace(a, 1, "T");
// Order of this state is validated by other tests.
const treeB = childTrees.find(ch => ch.roomId === b);
expect(treeB).toBeDefined();
await treeB.setOrder(2);
expect(clientSendStateFn).toHaveBeenCalledTimes(2);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'W',
}), c);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'X',
}), b);
expectOrder(a, 0);
expectOrder(b, 2);
expectOrder(c, 1);
expectOrder(d, 3);
});
});
it('should upload files', async () => {
const mxc = "mxc://example.org/file";
const fileInfo = {
mimetype: "text/plain",
// other fields as required by encryption, but ignored here
};
const fileEventId = "$file";
const fileName = "My File.txt";
const fileContents = "This is a test file";
// Mock out Blob for the test environment
(<any>global).Blob = MockBlob;
const uploadFn = jest.fn().mockImplementation((contents: Blob, opts: any) => {
expect(contents).toBeInstanceOf(Blob);
expect(contents.size).toEqual(fileContents.length);
expect(opts).toMatchObject({
includeFilename: false,
onlyContentUri: true, // because the tests rely on this - we shouldn't really be testing for this.
});
return Promise.resolve(mxc);
});
client.uploadContent = uploadFn;
const sendMsgFn = jest.fn().mockImplementation((roomId: string, contents: any) => {
expect(roomId).toEqual(tree.roomId);
expect(contents).toMatchObject({
msgtype: MsgType.File,
body: fileName,
url: mxc,
file: fileInfo,
[UNSTABLE_MSC3089_LEAF.unstable]: {}, // test to ensure we're definitely using unstable
});
return Promise.resolve({ event_id: fileEventId }); // eslint-disable-line camelcase
});
client.sendMessage = sendMsgFn;
const sendStateFn = jest.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(tree.roomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
expect(stateKey).toEqual(fileEventId);
expect(content).toMatchObject({
active: true,
name: fileName,
});
return Promise.resolve(); // return value not used.
});
client.sendStateEvent = sendStateFn;
const buf = Uint8Array.from(Array.from(fileContents).map((_, i) => fileContents.charCodeAt(i)));
// We clone the file info just to make sure it doesn't get mutated for the test.
await tree.createFile(fileName, buf, Object.assign({}, fileInfo));
expect(uploadFn).toHaveBeenCalledTimes(1);
expect(sendMsgFn).toHaveBeenCalledTimes(1);
expect(sendStateFn).toHaveBeenCalledTimes(1);
});
it('should support getting files', () => {
const fileEventId = "$file";
const fileEvent = { forTest: true }; // MatrixEvent mock
room.currentState = {
getStateEvents: (eventType: string, stateKey?: string) => {
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
expect(stateKey).toEqual(fileEventId);
return fileEvent;
},
};
const file = tree.getFile(fileEventId);
expect(file).toBeDefined();
expect(file.indexEvent).toBe(fileEvent);
});
it('should return falsy for unknown files', () => {
const fileEventId = "$file";
room.currentState = {
getStateEvents: (eventType: string, stateKey?: string) => {
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
expect(stateKey).toEqual(fileEventId);
return null;
},
};
const file = tree.getFile(fileEventId);
expect(file).toBeFalsy();
});
it('should list files', () => {
const firstFile = { getContent: () => ({ active: true }) };
const secondFile = { getContent: () => ({ active: false }) }; // deliberately inactive
room.currentState = {
getStateEvents: (eventType: string, stateKey?: string) => {
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
expect(stateKey).toBeUndefined();
return [firstFile, secondFile];
},
};
const files = tree.listFiles();
expect(files).toBeDefined();
expect(files.length).toEqual(1);
expect(files[0].indexEvent).toBe(firstFile);
});
});
+1 -1
View File
@@ -1,5 +1,5 @@
import * as utils from "../test-utils";
import {PushProcessor} from "../../src/pushprocessor";
import { PushProcessor } from "../../src/pushprocessor";
describe('NotificationService', function() {
const testUserId = "@ali:matrix.org";
+2 -2
View File
@@ -46,8 +46,8 @@ describe("realtime-callbacks", function() {
it("should set 'this' to the global object", function() {
let passed = false;
const callback = function() {
expect(this).toBe(global); // eslint-disable-line babel/no-invalid-this
expect(this.console).toBeTruthy(); // eslint-disable-line babel/no-invalid-this
expect(this).toBe(global); // eslint-disable-line @babel/no-invalid-this
expect(this.console).toBeTruthy(); // eslint-disable-line @babel/no-invalid-this
passed = true;
};
callbacks.setTimeout(callback);
+133
View File
@@ -0,0 +1,133 @@
/*
Copyright 2021 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 { EventTimelineSet } from "../../src/models/event-timeline-set";
import { MatrixEvent } from "../../src/models/event";
import { Relations } from "../../src/models/relations";
describe("Relations", function() {
it("should deduplicate annotations", function() {
const relations = new Relations("m.annotation", "m.reaction");
// Create an instance of an annotation
const eventData = {
"sender": "@bob:example.com",
"type": "m.reaction",
"event_id": "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw",
"room_id": "!pzVjCQSoQPpXQeHpmK:example.com",
"content": {
"m.relates_to": {
"event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
"key": "👍️",
"rel_type": "m.annotation",
},
},
};
const eventA = new MatrixEvent(eventData);
// Add the event once and check results
{
relations.addEvent(eventA);
const annotationsByKey = relations.getSortedAnnotationsByKey();
expect(annotationsByKey.length).toEqual(1);
const [key, events] = annotationsByKey[0];
expect(key).toEqual("👍️");
expect(events.size).toEqual(1);
}
// Add the event again and expect the same
{
relations.addEvent(eventA);
const annotationsByKey = relations.getSortedAnnotationsByKey();
expect(annotationsByKey.length).toEqual(1);
const [key, events] = annotationsByKey[0];
expect(key).toEqual("👍️");
expect(events.size).toEqual(1);
}
// Create a fresh object with the same event content
const eventB = new MatrixEvent(eventData);
// Add the event again and expect the same
{
relations.addEvent(eventB);
const annotationsByKey = relations.getSortedAnnotationsByKey();
expect(annotationsByKey.length).toEqual(1);
const [key, events] = annotationsByKey[0];
expect(key).toEqual("👍️");
expect(events.size).toEqual(1);
}
});
it("should emit created regardless of ordering", async function() {
const targetEvent = new MatrixEvent({
"sender": "@bob:example.com",
"type": "m.room.message",
"event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
"room_id": "!pzVjCQSoQPpXQeHpmK:example.com",
"content": {},
});
const relationEvent = new MatrixEvent({
"sender": "@bob:example.com",
"type": "m.reaction",
"event_id": "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw",
"room_id": "!pzVjCQSoQPpXQeHpmK:example.com",
"content": {
"m.relates_to": {
"event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
"key": "👍️",
"rel_type": "m.annotation",
},
},
});
// Stub the room
const room = {
getPendingEvent() { return null; },
getUnfilteredTimelineSet() { return null; },
};
// Add the target event first, then the relation event
{
const relationsCreated = new Promise(resolve => {
targetEvent.once("Event.relationsCreated", resolve);
});
const timelineSet = new EventTimelineSet(room, {
unstableClientRelationAggregation: true,
});
timelineSet.addLiveEvent(targetEvent);
timelineSet.addLiveEvent(relationEvent);
await relationsCreated;
}
// Add the relation event first, then the target event
{
const relationsCreated = new Promise(resolve => {
targetEvent.once("Event.relationsCreated", resolve);
});
const timelineSet = new EventTimelineSet(room, {
unstableClientRelationAggregation: true,
});
timelineSet.addLiveEvent(relationEvent);
timelineSet.addLiveEvent(targetEvent);
await relationsCreated;
}
});
});
+1 -1
View File
@@ -1,5 +1,5 @@
import * as utils from "../test-utils";
import {RoomMember} from "../../src/models/room-member";
import { RoomMember } from "../../src/models/room-member";
describe("RoomMember", function() {
const roomId = "!foo:bar";
+26 -26
View File
@@ -1,6 +1,6 @@
import * as utils from "../test-utils";
import {RoomState} from "../../src/models/room-state";
import {RoomMember} from "../../src/models/room-member";
import { RoomState } from "../../src/models/room-state";
import { RoomMember } from "../../src/models/room-member";
describe("RoomState", function() {
const roomId = "!foo:bar";
@@ -471,13 +471,13 @@ describe("RoomState", function() {
it("should update after adding joined member", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userA, room: roomId}),
utils.mkMembership({ event: true, mship: "join",
user: userA, room: roomId }),
]);
expect(state.getJoinedMemberCount()).toEqual(1);
state.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userC, room: roomId}),
utils.mkMembership({ event: true, mship: "join",
user: userC, room: roomId }),
]);
expect(state.getJoinedMemberCount()).toEqual(2);
});
@@ -490,13 +490,13 @@ describe("RoomState", function() {
it("should update after adding invited member", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userA, room: roomId}),
utils.mkMembership({ event: true, mship: "invite",
user: userA, room: roomId }),
]);
expect(state.getInvitedMemberCount()).toEqual(1);
state.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userC, room: roomId}),
utils.mkMembership({ event: true, mship: "invite",
user: userC, room: roomId }),
]);
expect(state.getInvitedMemberCount()).toEqual(2);
});
@@ -509,15 +509,15 @@ describe("RoomState", function() {
it("should, once used, override counting members from state", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userA, room: roomId}),
utils.mkMembership({ event: true, mship: "join",
user: userA, room: roomId }),
]);
expect(state.getJoinedMemberCount()).toEqual(1);
state.setJoinedMemberCount(100);
expect(state.getJoinedMemberCount()).toEqual(100);
state.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userC, room: roomId}),
utils.mkMembership({ event: true, mship: "join",
user: userC, room: roomId }),
]);
expect(state.getJoinedMemberCount()).toEqual(100);
});
@@ -525,14 +525,14 @@ describe("RoomState", function() {
it("should, once used, override counting members from state, " +
"also after clone", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userA, room: roomId}),
utils.mkMembership({ event: true, mship: "join",
user: userA, room: roomId }),
]);
state.setJoinedMemberCount(100);
const copy = state.clone();
copy.setStateEvents([
utils.mkMembership({event: true, mship: "join",
user: userC, room: roomId}),
utils.mkMembership({ event: true, mship: "join",
user: userC, room: roomId }),
]);
expect(state.getJoinedMemberCount()).toEqual(100);
});
@@ -545,15 +545,15 @@ describe("RoomState", function() {
it("should, once used, override counting members from state", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userB, room: roomId}),
utils.mkMembership({ event: true, mship: "invite",
user: userB, room: roomId }),
]);
expect(state.getInvitedMemberCount()).toEqual(1);
state.setInvitedMemberCount(100);
expect(state.getInvitedMemberCount()).toEqual(100);
state.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userC, room: roomId}),
utils.mkMembership({ event: true, mship: "invite",
user: userC, room: roomId }),
]);
expect(state.getInvitedMemberCount()).toEqual(100);
});
@@ -561,14 +561,14 @@ describe("RoomState", function() {
it("should, once used, override counting members from state, " +
"also after clone", function() {
state.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userB, room: roomId}),
utils.mkMembership({ event: true, mship: "invite",
user: userB, room: roomId }),
]);
state.setInvitedMemberCount(100);
const copy = state.clone();
copy.setStateEvents([
utils.mkMembership({event: true, mship: "invite",
user: userC, room: roomId}),
utils.mkMembership({ event: true, mship: "invite",
user: userC, room: roomId }),
]);
expect(state.getInvitedMemberCount()).toEqual(100);
});
+35 -29
View File
@@ -1,8 +1,9 @@
import * as utils from "../test-utils";
import {EventStatus, MatrixEvent} from "../../src/models/event";
import {EventTimeline} from "../../src/models/event-timeline";
import {RoomState} from "../../src/models/room-state";
import {Room} from "../../src/models/room";
import { EventStatus, MatrixEvent } from "../../src/models/event";
import { EventTimeline } from "../../src/models/event-timeline";
import { RoomState } from "../../src/models/room-state";
import { Room } from "../../src/models/room";
import { TestClient } from "../TestClient";
describe("Room", function() {
const roomId = "!foo:bar";
@@ -190,7 +191,7 @@ describe("Room", function() {
const remoteEvent = utils.mkMessage({
room: roomId, user: userA, event: true,
});
remoteEvent.event.unsigned = {transaction_id: "TXN_ID"};
remoteEvent.event.unsigned = { transaction_id: "TXN_ID" };
const remoteEventId = remoteEvent.getId();
let callCount = 0;
@@ -374,7 +375,7 @@ describe("Room", function() {
let events = null;
beforeEach(function() {
room = new Room(roomId, null, null, {timelineSupport: timelineSupport});
room = new Room(roomId, null, null, { timelineSupport: timelineSupport });
// set events each time to avoid resusing Event objects (which
// doesn't work because they get frozen)
events = [
@@ -456,7 +457,7 @@ describe("Room", function() {
describe("compareEventOrdering", function() {
beforeEach(function() {
room = new Room(roomId, null, null, {timelineSupport: true});
room = new Room(roomId, null, null, { timelineSupport: true });
});
const events = [
@@ -712,7 +713,7 @@ describe("Room", function() {
it("uses hero name from state", function() {
const name = "Mr B";
addMember(userA, "invite");
addMember(userB, "join", {name});
addMember(userB, "join", { name });
room.setSummary({
"m.heroes": [userB],
});
@@ -723,7 +724,7 @@ describe("Room", function() {
it("uses counts from summary", function() {
const name = "Mr B";
addMember(userB, "join", {name});
addMember(userB, "join", { name });
room.setSummary({
"m.heroes": [userB],
"m.joined_member_count": 50,
@@ -736,8 +737,8 @@ describe("Room", function() {
it("relies on heroes in case of absent counts", function() {
const nameB = "Mr Bean";
const nameC = "Mel C";
addMember(userB, "join", {name: nameB});
addMember(userC, "join", {name: nameC});
addMember(userB, "join", { name: nameB });
addMember(userC, "join", { name: nameC });
room.setSummary({
"m.heroes": [userB, userC],
});
@@ -747,7 +748,7 @@ describe("Room", function() {
it("uses only heroes", function() {
const nameB = "Mr Bean";
addMember(userB, "join", {name: nameB});
addMember(userB, "join", { name: nameB });
addMember(userC, "join");
room.setSummary({
"m.heroes": [userB],
@@ -840,7 +841,7 @@ describe("Room", function() {
it("should show the other user's name for private" +
" (invite join_rules) rooms if you are invited to it.", function() {
setJoinRule("invite");
addMember(userA, "invite", {user: userB});
addMember(userA, "invite", { user: userB });
addMember(userB);
room.recalculate();
const name = room.name;
@@ -915,8 +916,8 @@ describe("Room", function() {
"available",
function() {
setJoinRule("invite");
addMember(userB, 'join', {name: "Alice"});
addMember(userA, "invite", {user: userA});
addMember(userB, 'join', { name: "Alice" });
addMember(userA, "invite", { user: userA });
room.recalculate();
const name = room.name;
expect(name).toEqual("Alice");
@@ -926,7 +927,7 @@ describe("Room", function() {
function() {
setJoinRule("invite");
addMember(userB);
addMember(userA, "invite", {user: userA});
addMember(userA, "invite", { user: userA });
room.recalculate();
const name = room.name;
expect(name).toEqual(userB);
@@ -1176,7 +1177,10 @@ describe("Room", function() {
describe("addPendingEvent", function() {
it("should add pending events to the pendingEventList if " +
"pendingEventOrdering == 'detached'", function() {
const room = new Room(roomId, null, userA, {
const client = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const room = new Room(roomId, client, userA, {
pendingEventOrdering: "detached",
});
const eventA = utils.mkMessage({
@@ -1226,7 +1230,10 @@ describe("Room", function() {
describe("updatePendingEvent", function() {
it("should remove cancelled events from the pending list", function() {
const room = new Room(roomId, null, userA, {
const client = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const room = new Room(roomId, client, userA, {
pendingEventOrdering: "detached",
});
const eventA = utils.mkMessage({
@@ -1260,7 +1267,6 @@ describe("Room", function() {
expect(callCount).toEqual(1);
});
it("should remove cancelled events from the timeline", function() {
const room = new Room(roomId, null, userA);
const eventA = utils.mkMessage({
@@ -1308,13 +1314,13 @@ describe("Room", function() {
isRoomEncrypted: function() {
return false;
},
_http: {
http: {
serverResponse,
authedRequest: function() {
if (this.serverResponse instanceof Error) {
return Promise.reject(this.serverResponse);
} else {
return Promise.resolve({chunk: this.serverResponse});
return Promise.resolve({ chunk: this.serverResponse });
}
},
},
@@ -1344,7 +1350,7 @@ describe("Room", function() {
it("should load members from server on first call", async function() {
const client = createClientMock([memberEvent]);
const room = new Room(roomId, client, null, {lazyLoadMembers: true});
const room = new Room(roomId, client, null, { lazyLoadMembers: true });
await room.loadMembersIfNeeded();
const memberA = room.getMember("@user_a:bar");
expect(memberA.name).toEqual("User A");
@@ -1359,7 +1365,7 @@ describe("Room", function() {
room: roomId, event: true, name: "Ms A",
});
const client = createClientMock([memberEvent2], [memberEvent]);
const room = new Room(roomId, client, null, {lazyLoadMembers: true});
const room = new Room(roomId, client, null, { lazyLoadMembers: true });
await room.loadMembersIfNeeded();
@@ -1369,7 +1375,7 @@ describe("Room", function() {
it("should allow retry on error", async function() {
const client = createClientMock(new Error("server says no"));
const room = new Room(roomId, client, null, {lazyLoadMembers: true});
const room = new Room(roomId, client, null, { lazyLoadMembers: true });
let hasThrown = false;
try {
await room.loadMembersIfNeeded();
@@ -1378,7 +1384,7 @@ describe("Room", function() {
}
expect(hasThrown).toEqual(true);
client._http.serverResponse = [memberEvent];
client.http.serverResponse = [memberEvent];
await room.loadMembersIfNeeded();
const memberA = room.getMember("@user_a:bar");
expect(memberA.name).toEqual("User A");
@@ -1397,17 +1403,17 @@ describe("Room", function() {
const room = new Room(roomId, null, userA);
const events = [];
room.on("Room.myMembership", (_room, membership, oldMembership) => {
events.push({membership, oldMembership});
events.push({ membership, oldMembership });
});
room.updateMyMembership("invite");
expect(room.getMyMembership()).toEqual("invite");
expect(events[0]).toEqual({membership: "invite", oldMembership: null});
expect(events[0]).toEqual({ membership: "invite", oldMembership: null });
events.splice(0); //clear
room.updateMyMembership("invite");
expect(events.length).toEqual(0);
room.updateMyMembership("join");
expect(room.getMyMembership()).toEqual("join");
expect(events[0]).toEqual({membership: "join", oldMembership: "invite"});
expect(events[0]).toEqual({ membership: "join", oldMembership: "invite" });
});
});
@@ -1415,7 +1421,7 @@ describe("Room", function() {
it("should return first hero id",
function() {
const room = new Room(roomId, null, userA);
room.setSummary({'m.heroes': [userB]});
room.setSummary({ 'm.heroes': [userB] });
expect(room.guessDMUserId()).toEqual(userB);
});
it("should return first member that isn't self",
+8 -8
View File
@@ -1,9 +1,9 @@
// This file had a function whose name is all caps, which displeases eslint
/* eslint new-cap: "off" */
import {defer} from '../../src/utils';
import {MatrixError} from "../../src/http-api";
import {MatrixScheduler} from "../../src/scheduler";
import { defer } from '../../src/utils';
import { MatrixError } from "../../src/http-api";
import { MatrixScheduler } from "../../src/scheduler";
import * as utils from "../test-utils";
jest.useFakeTimers();
@@ -62,8 +62,8 @@ describe("MatrixScheduler", function() {
scheduler.queueEvent(eventA),
scheduler.queueEvent(eventB),
]);
deferB.resolve({b: true});
deferA.resolve({a: true});
deferB.resolve({ b: true });
deferA.resolve({ a: true });
const [a, b] = await abPromise;
expect(a.a).toEqual(true);
expect(b.b).toEqual(true);
@@ -156,8 +156,8 @@ describe("MatrixScheduler", function() {
// Expect to have processFn invoked for A&B.
// Resolve A.
// Expect to have processFn invoked for D.
const eventC = utils.mkMessage({user: "@a:bar", room: roomId, event: true});
const eventD = utils.mkMessage({user: "@b:bar", room: roomId, event: true});
const eventC = utils.mkMessage({ user: "@a:bar", room: roomId, event: true });
const eventD = utils.mkMessage({ user: "@b:bar", room: roomId, event: true });
const buckets = {};
buckets[eventA.getId()] = "queue_A";
@@ -241,7 +241,7 @@ describe("MatrixScheduler", function() {
expect(queue).toEqual([eventA, eventB]);
// modify the queue
const eventC = utils.mkMessage(
{user: "@a:bar", room: roomId, event: true},
{ user: "@a:bar", room: roomId, event: true },
);
queue.push(eventC);
const queueAgain = scheduler.getQueueForEvent(eventA);
+67 -1
View File
@@ -15,7 +15,40 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {SyncAccumulator} from "../../src/sync-accumulator";
import { SyncAccumulator } from "../../src/sync-accumulator";
// The event body & unsigned object get frozen to assert that they don't get altered
// by the impl
const RES_WITH_AGE = {
next_batch: "abc",
rooms: {
invite: {},
leave: {},
join: {
"!foo:bar": {
account_data: { events: [] },
ephemeral: { events: [] },
unread_notifications: {},
timeline: {
events: [
Object.freeze({
content: {
body: "This thing is happening right now!",
},
origin_server_ts: 123456789,
sender: "@alice:localhost",
type: "m.room.message",
unsigned: Object.freeze({
age: 50,
}),
}),
],
prev_batch: "something",
},
},
},
},
};
describe("SyncAccumulator", function() {
let sa;
@@ -368,6 +401,39 @@ describe("SyncAccumulator", function() {
expect(summary["m.joined_member_count"]).toEqual(5);
expect(summary["m.heroes"]).toEqual(["@bob:bar"]);
});
it("should return correctly adjusted age attributes", () => {
const delta = 1000;
const startingTs = 1000;
const oldDateNow = Date.now;
try {
Date.now = jest.fn();
Date.now.mockReturnValue(startingTs);
sa.accumulate(RES_WITH_AGE);
Date.now.mockReturnValue(startingTs + delta);
const output = sa.getJSON();
expect(output.roomsData.join["!foo:bar"].timeline.events[0].unsigned.age).toEqual(
RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0].unsigned.age + delta,
);
expect(Object.keys(output.roomsData.join["!foo:bar"].timeline.events[0])).toEqual(
Object.keys(RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0]),
);
} finally {
Date.now = oldDateNow;
}
});
it("should mangle age without adding extra keys", () => {
sa.accumulate(RES_WITH_AGE);
const output = sa.getJSON();
expect(Object.keys(output.roomsData.join["!foo:bar"].timeline.events[0])).toEqual(
Object.keys(RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0]),
);
});
});
});
+11 -15
View File
@@ -1,5 +1,5 @@
import {EventTimeline} from "../../src/models/event-timeline";
import {TimelineIndex, TimelineWindow} from "../../src/timeline-window";
import { EventTimeline } from "../../src/models/event-timeline";
import { TimelineIndex, TimelineWindow } from "../../src/timeline-window";
import * as utils from "../test-utils";
const ROOM_ID = "roomId";
@@ -18,7 +18,7 @@ function createTimeline(numEvents, baseIndex) {
}
// XXX: this is a horrid hack
const timelineSet = { room: { roomId: ROOM_ID }};
const timelineSet = { room: { roomId: ROOM_ID } };
timelineSet.room.getUnfilteredTimelineSet = function() {
return timelineSet;
};
@@ -46,7 +46,6 @@ function addEventsToTimeline(timeline, numEvents, atStart) {
}
}
/*
* create a pair of linked timelines
*/
@@ -58,7 +57,6 @@ function createLinkedTimelines() {
return [tl1, tl2];
}
describe("TimelineIndex", function() {
describe("minIndex", function() {
it("should return the min index relative to BaseIndex", function() {
@@ -133,7 +131,6 @@ describe("TimelineIndex", function() {
});
});
describe("TimelineWindow", function() {
/**
* create a dummy eventTimelineSet and client, and a TimelineWindow
@@ -142,7 +139,7 @@ describe("TimelineWindow", function() {
let timelineSet;
let client;
function createWindow(timeline, opts) {
timelineSet = {getTimelineForEvent: () => null};
timelineSet = { getTimelineForEvent: () => null };
client = {};
client.getEventTimeline = function(timelineSet0, eventId0) {
expect(timelineSet0).toBe(timelineSet);
@@ -171,7 +168,7 @@ describe("TimelineWindow", function() {
const timeline = createTimeline();
const eventId = timeline.getEvents()[1].getId();
const timelineSet = {getTimelineForEvent: () => null};
const timelineSet = { getTimelineForEvent: () => null };
const client = {};
client.getEventTimeline = function(timelineSet0, eventId0) {
expect(timelineSet0).toBe(timelineSet);
@@ -193,7 +190,7 @@ describe("TimelineWindow", function() {
const eventId = timeline.getEvents()[1].getId();
const timelineSet = {getTimelineForEvent: () => null};
const timelineSet = { getTimelineForEvent: () => null };
const client = {};
const timelineWindow = new TimelineWindow(client, timelineSet);
@@ -266,7 +263,7 @@ describe("TimelineWindow", function() {
it("should advance into next timeline", function() {
const tls = createLinkedTimelines();
const eventId = tls[0].getEvents()[1].getId();
const timelineWindow = createWindow(tls[0], {windowLimit: 5});
const timelineWindow = createWindow(tls[0], { windowLimit: 5 });
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = tls[0].getEvents();
@@ -311,7 +308,7 @@ describe("TimelineWindow", function() {
it("should retreat into previous timeline", function() {
const tls = createLinkedTimelines();
const eventId = tls[1].getEvents()[1].getId();
const timelineWindow = createWindow(tls[1], {windowLimit: 5});
const timelineWindow = createWindow(tls[1], { windowLimit: 5 });
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = tls[1].getEvents();
@@ -357,7 +354,7 @@ describe("TimelineWindow", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.FORWARDS);
const timelineWindow = createWindow(timeline, {windowLimit: 5});
const timelineWindow = createWindow(timeline, { windowLimit: 5 });
const eventId = timeline.getEvents()[1].getId();
client.paginateEventTimeline = function(timeline0, opts) {
@@ -385,12 +382,11 @@ describe("TimelineWindow", function() {
});
});
it("should make backward pagination requests", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.BACKWARDS);
const timelineWindow = createWindow(timeline, {windowLimit: 5});
const timelineWindow = createWindow(timeline, { windowLimit: 5 });
const eventId = timeline.getEvents()[1].getId();
client.paginateEventTimeline = function(timeline0, opts) {
@@ -422,7 +418,7 @@ describe("TimelineWindow", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.FORWARDS);
const timelineWindow = createWindow(timeline, {windowLimit: 5});
const timelineWindow = createWindow(timeline, { windowLimit: 5 });
const eventId = timeline.getEvents()[1].getId();
let paginateCount = 0;
+1 -1
View File
@@ -1,4 +1,4 @@
import {User} from "../../src/models/user";
import { User } from "../../src/models/user";
import * as utils from "../test-utils";
describe("User", function() {
-285
View File
@@ -1,285 +0,0 @@
import * as utils from "../../src/utils";
describe("utils", function() {
describe("encodeParams", function() {
it("should url encode and concat with &s", function() {
const params = {
foo: "bar",
baz: "beer@",
};
expect(utils.encodeParams(params)).toEqual(
"foo=bar&baz=beer%40",
);
});
});
describe("encodeUri", function() {
it("should replace based on object keys and url encode", function() {
const path = "foo/bar/%something/%here";
const vals = {
"%something": "baz",
"%here": "beer@",
};
expect(utils.encodeUri(path, vals)).toEqual(
"foo/bar/baz/beer%40",
);
});
});
describe("forEach", function() {
it("should be invoked for each element", function() {
const arr = [];
utils.forEach([55, 66, 77], function(element) {
arr.push(element);
});
expect(arr).toEqual([55, 66, 77]);
});
});
describe("findElement", function() {
it("should find only 1 element if there is a match", function() {
const matchFn = function() {
return true;
};
const arr = [55, 66, 77];
expect(utils.findElement(arr, matchFn)).toEqual(55);
});
it("should be able to find in reverse order", function() {
const matchFn = function() {
return true;
};
const arr = [55, 66, 77];
expect(utils.findElement(arr, matchFn, true)).toEqual(77);
});
it("should find nothing if the function never returns true", function() {
const matchFn = function() {
return false;
};
const arr = [55, 66, 77];
expect(utils.findElement(arr, matchFn)).toBeFalsy();
});
});
describe("removeElement", function() {
it("should remove only 1 element if there is a match", function() {
const matchFn = function() {
return true;
};
const arr = [55, 66, 77];
utils.removeElement(arr, matchFn);
expect(arr).toEqual([66, 77]);
});
it("should be able to remove in reverse order", function() {
const matchFn = function() {
return true;
};
const arr = [55, 66, 77];
utils.removeElement(arr, matchFn, true);
expect(arr).toEqual([55, 66]);
});
it("should remove nothing if the function never returns true", function() {
const matchFn = function() {
return false;
};
const arr = [55, 66, 77];
utils.removeElement(arr, matchFn);
expect(arr).toEqual(arr);
});
});
describe("isFunction", function() {
it("should return true for functions", function() {
expect(utils.isFunction([])).toBe(false);
expect(utils.isFunction([5, 3, 7])).toBe(false);
expect(utils.isFunction()).toBe(false);
expect(utils.isFunction(null)).toBe(false);
expect(utils.isFunction({})).toBe(false);
expect(utils.isFunction("foo")).toBe(false);
expect(utils.isFunction(555)).toBe(false);
expect(utils.isFunction(function() {})).toBe(true);
const s = { foo: function() {} };
expect(utils.isFunction(s.foo)).toBe(true);
});
});
describe("isArray", function() {
it("should return true for arrays", function() {
expect(utils.isArray([])).toBe(true);
expect(utils.isArray([5, 3, 7])).toBe(true);
expect(utils.isArray()).toBe(false);
expect(utils.isArray(null)).toBe(false);
expect(utils.isArray({})).toBe(false);
expect(utils.isArray("foo")).toBe(false);
expect(utils.isArray(555)).toBe(false);
expect(utils.isArray(function() {})).toBe(false);
});
});
describe("checkObjectHasKeys", function() {
it("should throw for missing keys", function() {
expect(function() {
utils.checkObjectHasKeys({}, ["foo"]);
}).toThrow();
expect(function() {
utils.checkObjectHasKeys({
foo: "bar",
}, ["foo"]);
}).not.toThrow();
});
});
describe("checkObjectHasNoAdditionalKeys", function() {
it("should throw for extra keys", function() {
expect(function() {
utils.checkObjectHasNoAdditionalKeys({
foo: "bar",
baz: 4,
}, ["foo"]);
}).toThrow();
expect(function() {
utils.checkObjectHasNoAdditionalKeys({
foo: "bar",
}, ["foo"]);
}).not.toThrow();
});
});
describe("deepCompare", function() {
const assert = {
isTrue: function(x) {
expect(x).toBe(true);
},
isFalse: function(x) {
expect(x).toBe(false);
},
};
it("should handle primitives", function() {
assert.isTrue(utils.deepCompare(null, null));
assert.isFalse(utils.deepCompare(null, undefined));
assert.isTrue(utils.deepCompare("hi", "hi"));
assert.isTrue(utils.deepCompare(5, 5));
assert.isFalse(utils.deepCompare(5, 10));
});
it("should handle regexps", function() {
assert.isTrue(utils.deepCompare(/abc/, /abc/));
assert.isFalse(utils.deepCompare(/abc/, /123/));
const r = /abc/;
assert.isTrue(utils.deepCompare(r, r));
});
it("should handle dates", function() {
assert.isTrue(utils.deepCompare(new Date("2011-03-31"),
new Date("2011-03-31")));
assert.isFalse(utils.deepCompare(new Date("2011-03-31"),
new Date("1970-01-01")));
});
it("should handle arrays", function() {
assert.isTrue(utils.deepCompare([], []));
assert.isTrue(utils.deepCompare([1, 2], [1, 2]));
assert.isFalse(utils.deepCompare([1, 2], [2, 1]));
assert.isFalse(utils.deepCompare([1, 2], [1, 2, 3]));
});
it("should handle simple objects", function() {
assert.isTrue(utils.deepCompare({}, {}));
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.isTrue(utils.deepCompare({1: {name: "mhc", age: 28},
2: {name: "arb", age: 26}},
{1: {name: "mhc", age: 28},
2: {name: "arb", age: 26}}));
assert.isFalse(utils.deepCompare({1: {name: "mhc", age: 28},
2: {name: "arb", age: 26}},
{1: {name: "mhc", age: 28},
2: {name: "arb", age: 27}}));
assert.isFalse(utils.deepCompare({}, null));
assert.isFalse(utils.deepCompare({}, undefined));
});
it("should handle functions", function() {
// no two different function is equal really, they capture their
// context variables so even if they have same toString(), they
// won't have same functionality
const func = function(x) {
return true;
};
const func2 = function(x) {
return true;
};
assert.isTrue(utils.deepCompare(func, func));
assert.isFalse(utils.deepCompare(func, func2));
assert.isTrue(utils.deepCompare({ a: { b: func } }, { a: { b: func } }));
assert.isFalse(utils.deepCompare({ a: { b: func } }, { a: { b: func2 } }));
});
});
describe("extend", function() {
const SOURCE = { "prop2": 1, "string2": "x", "newprop": "new" };
it("should extend", function() {
const target = {
"prop1": 5, "prop2": 7, "string1": "baz", "string2": "foo",
};
const merged = {
"prop1": 5, "prop2": 1, "string1": "baz", "string2": "x",
"newprop": "new",
};
const sourceOrig = JSON.stringify(SOURCE);
utils.extend(target, SOURCE);
expect(JSON.stringify(target)).toEqual(JSON.stringify(merged));
// check the originial wasn't modified
expect(JSON.stringify(SOURCE)).toEqual(sourceOrig);
});
it("should ignore null", function() {
const target = {
"prop1": 5, "prop2": 7, "string1": "baz", "string2": "foo",
};
const merged = {
"prop1": 5, "prop2": 1, "string1": "baz", "string2": "x",
"newprop": "new",
};
const sourceOrig = JSON.stringify(SOURCE);
utils.extend(target, null, SOURCE);
expect(JSON.stringify(target)).toEqual(JSON.stringify(merged));
// check the originial wasn't modified
expect(JSON.stringify(SOURCE)).toEqual(sourceOrig);
});
it("should handle properties created with defineProperties", function() {
const source = Object.defineProperties({}, {
"enumerableProp": {
get: function() {
return true;
},
enumerable: true,
},
"nonenumerableProp": {
get: function() {
return true;
},
},
});
const target = {};
utils.extend(target, source);
expect(target.enumerableProp).toBe(true);
expect(target.nonenumerableProp).toBe(undefined);
});
});
});
+432
View File
@@ -0,0 +1,432 @@
import * as utils from "../../src/utils";
import {
alphabetPad,
averageBetweenStrings,
baseToString,
DEFAULT_ALPHABET,
lexicographicCompare,
nextString,
prevString,
stringToBase,
} from "../../src/utils";
import { logger } from "../../src/logger";
// TODO: Fix types throughout
describe("utils", function() {
describe("encodeParams", function() {
it("should url encode and concat with &s", function() {
const params = {
foo: "bar",
baz: "beer@",
};
expect(utils.encodeParams(params)).toEqual(
"foo=bar&baz=beer%40",
);
});
});
describe("encodeUri", function() {
it("should replace based on object keys and url encode", function() {
const path = "foo/bar/%something/%here";
const vals = {
"%something": "baz",
"%here": "beer@",
};
expect(utils.encodeUri(path, vals)).toEqual(
"foo/bar/baz/beer%40",
);
});
});
describe("removeElement", function() {
it("should remove only 1 element if there is a match", function() {
const matchFn = function() {
return true;
};
const arr = [55, 66, 77];
utils.removeElement(arr, matchFn);
expect(arr).toEqual([66, 77]);
});
it("should be able to remove in reverse order", function() {
const matchFn = function() {
return true;
};
const arr = [55, 66, 77];
utils.removeElement(arr, matchFn, true);
expect(arr).toEqual([55, 66]);
});
it("should remove nothing if the function never returns true", function() {
const matchFn = function() {
return false;
};
const arr = [55, 66, 77];
utils.removeElement(arr, matchFn);
expect(arr).toEqual(arr);
});
});
describe("isFunction", function() {
it("should return true for functions", function() {
expect(utils.isFunction([])).toBe(false);
expect(utils.isFunction([5, 3, 7])).toBe(false);
expect(utils.isFunction(undefined)).toBe(false);
expect(utils.isFunction(null)).toBe(false);
expect(utils.isFunction({})).toBe(false);
expect(utils.isFunction("foo")).toBe(false);
expect(utils.isFunction(555)).toBe(false);
expect(utils.isFunction(function() {})).toBe(true);
const s = { foo: function() {} };
expect(utils.isFunction(s.foo)).toBe(true);
});
});
describe("checkObjectHasKeys", function() {
it("should throw for missing keys", function() {
expect(function() {
utils.checkObjectHasKeys({}, ["foo"]);
}).toThrow();
expect(function() {
utils.checkObjectHasKeys({
foo: "bar",
}, ["foo"]);
}).not.toThrow();
});
});
describe("checkObjectHasNoAdditionalKeys", function() {
it("should throw for extra keys", function() {
expect(function() {
utils.checkObjectHasNoAdditionalKeys({ foo: "bar", baz: 4 }, ["foo"]);
}).toThrow();
expect(function() {
utils.checkObjectHasNoAdditionalKeys({ foo: "bar" }, ["foo"]);
}).not.toThrow();
});
});
describe("deepCompare", function() {
const assert = {
isTrue: function(x) {
expect(x).toBe(true);
},
isFalse: function(x) {
expect(x).toBe(false);
},
};
it("should handle primitives", function() {
assert.isTrue(utils.deepCompare(null, null));
assert.isFalse(utils.deepCompare(null, undefined));
assert.isTrue(utils.deepCompare("hi", "hi"));
assert.isTrue(utils.deepCompare(5, 5));
assert.isFalse(utils.deepCompare(5, 10));
});
it("should handle regexps", function() {
assert.isTrue(utils.deepCompare(/abc/, /abc/));
assert.isFalse(utils.deepCompare(/abc/, /123/));
const r = /abc/;
assert.isTrue(utils.deepCompare(r, r));
});
it("should handle dates", function() {
assert.isTrue(utils.deepCompare(new Date("2011-03-31"), new Date("2011-03-31")));
assert.isFalse(utils.deepCompare(new Date("2011-03-31"), new Date("1970-01-01")));
});
it("should handle arrays", function() {
assert.isTrue(utils.deepCompare([], []));
assert.isTrue(utils.deepCompare([1, 2], [1, 2]));
assert.isFalse(utils.deepCompare([1, 2], [2, 1]));
assert.isFalse(utils.deepCompare([1, 2], [1, 2, 3]));
});
it("should handle simple objects", function() {
assert.isTrue(utils.deepCompare({}, {}));
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.isTrue(utils.deepCompare({
1: { name: "mhc", age: 28 },
2: { name: "arb", age: 26 },
}, {
1: { name: "mhc", age: 28 },
2: { name: "arb", age: 26 },
}));
assert.isFalse(utils.deepCompare({
1: { name: "mhc", age: 28 },
2: { name: "arb", age: 26 },
}, {
1: { name: "mhc", age: 28 },
2: { name: "arb", age: 27 },
}));
assert.isFalse(utils.deepCompare({}, null));
assert.isFalse(utils.deepCompare({}, undefined));
});
it("should handle functions", function() {
// no two different function is equal really, they capture their
// context variables so even if they have same toString(), they
// won't have same functionality
const func = function(x) {
return true;
};
const func2 = function(x) {
return true;
};
assert.isTrue(utils.deepCompare(func, func));
assert.isFalse(utils.deepCompare(func, func2));
assert.isTrue(utils.deepCompare({ a: { b: func } }, { a: { b: func } }));
assert.isFalse(utils.deepCompare({ a: { b: func } }, { a: { b: func2 } }));
});
});
describe("extend", function() {
const SOURCE = { "prop2": 1, "string2": "x", "newprop": "new" };
it("should extend", function() {
const target = {
"prop1": 5, "prop2": 7, "string1": "baz", "string2": "foo",
};
const merged = {
"prop1": 5, "prop2": 1, "string1": "baz", "string2": "x",
"newprop": "new",
};
const sourceOrig = JSON.stringify(SOURCE);
utils.extend(target, SOURCE);
expect(JSON.stringify(target)).toEqual(JSON.stringify(merged));
// check the originial wasn't modified
expect(JSON.stringify(SOURCE)).toEqual(sourceOrig);
});
it("should ignore null", function() {
const target = {
"prop1": 5, "prop2": 7, "string1": "baz", "string2": "foo",
};
const merged = {
"prop1": 5, "prop2": 1, "string1": "baz", "string2": "x",
"newprop": "new",
};
const sourceOrig = JSON.stringify(SOURCE);
utils.extend(target, null, SOURCE);
expect(JSON.stringify(target)).toEqual(JSON.stringify(merged));
// check the originial wasn't modified
expect(JSON.stringify(SOURCE)).toEqual(sourceOrig);
});
it("should handle properties created with defineProperties", function() {
const source = Object.defineProperties({}, {
"enumerableProp": {
get: function() {
return true;
},
enumerable: true,
},
"nonenumerableProp": {
get: function() {
return true;
},
},
});
// TODO: Fix type
const target: any = {};
utils.extend(target, source);
expect(target.enumerableProp).toBe(true);
expect(target.nonenumerableProp).toBe(undefined);
});
});
describe("chunkPromises", function() {
it("should execute promises in chunks", async function() {
let promiseCount = 0;
async function fn1() {
await utils.sleep(1);
expect(promiseCount).toEqual(0);
++promiseCount;
}
async function fn2() {
expect(promiseCount).toEqual(1);
++promiseCount;
}
await utils.chunkPromises([fn1, fn2], 1);
expect(promiseCount).toEqual(2);
});
});
describe('DEFAULT_ALPHABET', () => {
it('should be usefully printable ASCII in order', () => {
expect(DEFAULT_ALPHABET).toEqual(
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
);
});
});
describe('alphabetPad', () => {
it('should pad to the alphabet length', () => {
const len = 12;
expect(alphabetPad("a", len)).toEqual("a" + ("".padEnd(len - 1, DEFAULT_ALPHABET[0])));
expect(alphabetPad("a", len, "123")).toEqual("a" + ("".padEnd(len - 1, '1')));
});
});
describe('baseToString', () => {
it('should calculate the appropriate string from numbers', () => {
// Verify the whole alphabet
for (let i = BigInt(1); i <= DEFAULT_ALPHABET.length; i++) {
logger.log({ i }); // for debugging
expect(baseToString(i)).toEqual(DEFAULT_ALPHABET[Number(i) - 1]);
}
// Just quickly double check that repeated characters aren't treated as padding, particularly
// at the beginning of the alphabet where they are most vulnerable to this behaviour.
expect(baseToString(BigInt(1))).toEqual(DEFAULT_ALPHABET[0].repeat(1));
expect(baseToString(BigInt(96))).toEqual(DEFAULT_ALPHABET[0].repeat(2));
expect(baseToString(BigInt(9121))).toEqual(DEFAULT_ALPHABET[0].repeat(3));
expect(baseToString(BigInt(866496))).toEqual(DEFAULT_ALPHABET[0].repeat(4));
expect(baseToString(BigInt(82317121))).toEqual(DEFAULT_ALPHABET[0].repeat(5));
expect(baseToString(BigInt(7820126496))).toEqual(DEFAULT_ALPHABET[0].repeat(6));
expect(baseToString(BigInt(10))).toEqual(DEFAULT_ALPHABET[9]);
expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual('j');
expect(baseToString(BigInt(6337))).toEqual("ab");
expect(baseToString(BigInt(80), "abcdefghijklmnopqrstuvwxyz")).toEqual('cb');
});
});
describe('stringToBase', () => {
it('should calculate the appropriate number for a string', () => {
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(1))).toEqual(BigInt(1));
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(2))).toEqual(BigInt(96));
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(3))).toEqual(BigInt(9121));
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(4))).toEqual(BigInt(866496));
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(5))).toEqual(BigInt(82317121));
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(6))).toEqual(BigInt(7820126496));
expect(stringToBase("a", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(1));
expect(stringToBase("a")).toEqual(BigInt(66));
expect(stringToBase("c", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(3));
expect(stringToBase("ab")).toEqual(BigInt(6337));
expect(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(80));
});
});
describe('averageBetweenStrings', () => {
it('should average appropriately', () => {
expect(averageBetweenStrings(" ", "!!")).toEqual(" P");
expect(averageBetweenStrings(" ", "!")).toEqual(" ");
expect(averageBetweenStrings('A', 'B')).toEqual('A ');
expect(averageBetweenStrings('AA', 'BB')).toEqual('Aq');
expect(averageBetweenStrings('A', 'z')).toEqual(']');
expect(averageBetweenStrings('a', 'z', "abcdefghijklmnopqrstuvwxyz")).toEqual('m');
expect(averageBetweenStrings('AA', 'zz')).toEqual('^.');
expect(averageBetweenStrings('aa', 'zz', "abcdefghijklmnopqrstuvwxyz")).toEqual('mz');
expect(averageBetweenStrings('cat', 'doggo')).toEqual("d9>Cw");
expect(averageBetweenStrings('cat', 'doggo', "abcdefghijklmnopqrstuvwxyz")).toEqual("cumqh");
});
});
describe('nextString', () => {
it('should find the next string appropriately', () => {
expect(nextString('A')).toEqual('B');
expect(nextString('b', 'abcdefghijklmnopqrstuvwxyz')).toEqual('c');
expect(nextString('cat')).toEqual('cau');
expect(nextString('cat', 'abcdefghijklmnopqrstuvwxyz')).toEqual('cau');
});
});
describe('prevString', () => {
it('should find the next string appropriately', () => {
expect(prevString('B')).toEqual('A');
expect(prevString('c', 'abcdefghijklmnopqrstuvwxyz')).toEqual('b');
expect(prevString('cau')).toEqual('cat');
expect(prevString('cau', 'abcdefghijklmnopqrstuvwxyz')).toEqual('cat');
});
});
// Let's just ensure the ordering is sensible for lexicographic ordering
describe('string averaging unified', () => {
it('should be truly previous and next', () => {
let midpoint = "cat";
// We run this test 100 times to ensure we end up with a sane sequence.
for (let i = 0; i < 100; i++) {
const next = nextString(midpoint);
const prev = prevString(midpoint);
logger.log({ i, midpoint, next, prev }); // for test debugging
expect(lexicographicCompare(midpoint, next) < 0).toBe(true);
expect(lexicographicCompare(midpoint, prev) > 0).toBe(true);
expect(averageBetweenStrings(prev, next)).toBe(midpoint);
midpoint = next;
}
});
it('should roll over', () => {
const lastAlpha = DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1];
const firstAlpha = DEFAULT_ALPHABET[0];
const highRoll = firstAlpha + firstAlpha;
const lowRoll = lastAlpha;
expect(nextString(lowRoll)).toEqual(highRoll);
expect(prevString(highRoll)).toEqual(lowRoll);
});
it('should be reversible on small strings', () => {
// Large scale reversibility is tested for max space order value
const input = "cats";
expect(prevString(nextString(input))).toEqual(input);
});
// We want to explicitly make sure that Space order values are supported and roll appropriately
it('should properly handle rolling over at 50 characters', () => {
// Note: we also test reversibility of large strings here.
const maxSpaceValue = DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1].repeat(50);
const fiftyFirstChar = DEFAULT_ALPHABET[0].repeat(51);
expect(nextString(maxSpaceValue)).toBe(fiftyFirstChar);
expect(prevString(fiftyFirstChar)).toBe(maxSpaceValue);
// We're testing that the rollover happened, which means that the next string come before
// the maximum space order value lexicographically.
expect(lexicographicCompare(maxSpaceValue, fiftyFirstChar) > 0).toBe(true);
});
});
describe('lexicographicCompare', () => {
it('should work', () => {
// Simple tests
expect(lexicographicCompare('a', 'b') < 0).toBe(true);
expect(lexicographicCompare('ab', 'b') < 0).toBe(true);
expect(lexicographicCompare('cat', 'dog') < 0).toBe(true);
// Simple tests (reversed)
expect(lexicographicCompare('b', 'a') > 0).toBe(true);
expect(lexicographicCompare('b', 'ab') > 0).toBe(true);
expect(lexicographicCompare('dog', 'cat') > 0).toBe(true);
// Simple equality tests
expect(lexicographicCompare('a', 'a') === 0).toBe(true);
expect(lexicographicCompare('A', 'A') === 0).toBe(true);
// ASCII rule testing
expect(lexicographicCompare('A', 'a') < 0).toBe(true);
expect(lexicographicCompare('a', 'A') > 0).toBe(true);
});
});
});
+301
View File
@@ -0,0 +1,301 @@
/*
Copyright 2020 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 { TestClient } from '../../TestClient';
import { MatrixCall, CallErrorCode, CallEvent } from '../../../src/webrtc/call';
const DUMMY_SDP = (
"v=0\r\n" +
"o=- 5022425983810148698 2 IN IP4 127.0.0.1\r\n" +
"s=-\r\nt=0 0\r\na=group:BUNDLE 0\r\n" +
"a=msid-semantic: WMS h3wAi7s8QpiQMH14WG3BnDbmlOqo9I5ezGZA\r\n" +
"m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n" +
"c=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:hLDR\r\n" +
"a=ice-pwd:bMGD9aOldHWiI+6nAq/IIlRw\r\n" +
"a=ice-options:trickle\r\n" +
"a=fingerprint:sha-256 E4:94:84:F9:4A:98:8A:56:F5:5F:FD:AF:72:B9:32:89:49:5C:4B:9A:" +
"4A:15:8E:41:8A:F3:69:E4:39:52:DC:D6\r\n" +
"a=setup:active\r\n" +
"a=mid:0\r\n" +
"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" +
"a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" +
"a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n" +
"a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n" +
"a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n" +
"a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n" +
"a=sendrecv\r\n" +
"a=msid:h3wAi7s8QpiQMH14WG3BnDbmlOqo9I5ezGZA 4357098f-3795-4131-bff4-9ba9c0348c49\r\n" +
"a=rtcp-mux\r\n" +
"a=rtpmap:111 opus/48000/2\r\n" +
"a=rtcp-fb:111 transport-cc\r\n" +
"a=fmtp:111 minptime=10;useinbandfec=1\r\n" +
"a=rtpmap:103 ISAC/16000\r\n" +
"a=rtpmap:104 ISAC/32000\r\n" +
"a=rtpmap:9 G722/8000\r\n" +
"a=rtpmap:0 PCMU/8000\r\n" +
"a=rtpmap:8 PCMA/8000\r\n" +
"a=rtpmap:106 CN/32000\r\n" +
"a=rtpmap:105 CN/16000\r\n" +
"a=rtpmap:13 CN/8000\r\n" +
"a=rtpmap:110 telephone-event/48000\r\n" +
"a=rtpmap:112 telephone-event/32000\r\n" +
"a=rtpmap:113 telephone-event/16000\r\n" +
"a=rtpmap:126 telephone-event/8000\r\n" +
"a=ssrc:3619738545 cname:2RWtmqhXLdoF4sOi\r\n"
);
class MockRTCPeerConnection {
localDescription: RTCSessionDescription;
constructor() {
this.localDescription = {
sdp: DUMMY_SDP,
type: 'offer',
toJSON: function() {},
};
}
addEventListener() {}
createOffer() {
return Promise.resolve({});
}
setRemoteDescription() {
return Promise.resolve();
}
setLocalDescription() {
return Promise.resolve();
}
close() {}
getStats() { return []; }
}
describe('Call', function() {
let client;
let call;
let prevNavigator;
let prevDocument;
let prevWindow;
beforeEach(function() {
prevNavigator = global.navigator;
prevDocument = global.document;
prevWindow = global.window;
global.navigator = {
mediaDevices: {
// @ts-ignore Mock
getUserMedia: () => {
return {
getTracks: () => [],
getAudioTracks: () => [],
getVideoTracks: () => [],
};
},
},
};
global.window = {
// @ts-ignore Mock
RTCPeerConnection: MockRTCPeerConnection,
// @ts-ignore Mock
RTCSessionDescription: {},
// @ts-ignore Mock
RTCIceCandidate: {},
getUserMedia: {},
};
// @ts-ignore Mock
global.document = {};
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
// We just stub out sendEvent: we're not interested in testing the client's
// event sending code here
client.client.sendEvent = () => {};
client.httpBackend.when("GET", "/voip/turnServer").respond(200, {});
call = new MatrixCall({
client: client.client,
roomId: '!foo:bar',
});
// call checks one of these is wired up
call.on('error', () => {});
});
afterEach(function() {
client.stop();
global.navigator = prevNavigator;
global.window = prevWindow;
global.document = prevDocument;
});
it('should ignore candidate events from non-matching party ID', async function() {
const callPromise = call.placeVoiceCall();
await client.httpBackend.flush();
await callPromise;
await call.onAnswerReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'the_correct_party_id',
answer: {
sdp: DUMMY_SDP,
},
};
},
});
call.peerConn.addIceCandidate = jest.fn();
call.onRemoteIceCandidatesReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'the_correct_party_id',
candidates: [
{
candidate: '',
sdpMid: '',
},
],
};
},
});
expect(call.peerConn.addIceCandidate.mock.calls.length).toBe(1);
call.onRemoteIceCandidatesReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'some_other_party_id',
candidates: [
{
candidate: '',
sdpMid: '',
},
],
};
},
});
expect(call.peerConn.addIceCandidate.mock.calls.length).toBe(1);
// Hangup to stop timers
call.hangup(CallErrorCode.UserHangup, true);
});
it('should add candidates received before answer if party ID is correct', async function() {
const callPromise = call.placeVoiceCall();
await client.httpBackend.flush();
await callPromise;
call.peerConn.addIceCandidate = jest.fn();
call.onRemoteIceCandidatesReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'the_correct_party_id',
candidates: [
{
candidate: 'the_correct_candidate',
sdpMid: '',
},
],
};
},
});
call.onRemoteIceCandidatesReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'some_other_party_id',
candidates: [
{
candidate: 'the_wrong_candidate',
sdpMid: '',
},
],
};
},
});
expect(call.peerConn.addIceCandidate.mock.calls.length).toBe(0);
await call.onAnswerReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'the_correct_party_id',
answer: {
sdp: DUMMY_SDP,
},
};
},
});
expect(call.peerConn.addIceCandidate.mock.calls.length).toBe(1);
expect(call.peerConn.addIceCandidate).toHaveBeenCalledWith({
candidate: 'the_correct_candidate',
sdpMid: '',
});
});
it('should map asserted identity messages to remoteAssertedIdentity', async function() {
const callPromise = call.placeVoiceCall();
await client.httpBackend.flush();
await callPromise;
await call.onAnswerReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'party_id',
answer: {
sdp: DUMMY_SDP,
},
};
},
});
const identChangedCallback = jest.fn();
call.on(CallEvent.AssertedIdentityChanged, identChangedCallback);
await call.onAssertedIdentityReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'party_id',
asserted_identity: {
id: "@steve:example.com",
display_name: "Steve Gibbons",
},
};
},
});
expect(identChangedCallback).toHaveBeenCalled();
const ident = call.getRemoteAssertedIdentity();
expect(ident.id).toEqual("@steve:example.com");
expect(ident.displayName).toEqual("Steve Gibbons");
// Hangup to stop timers
call.hangup(CallErrorCode.UserHangup, true);
});
});
+24
View File
@@ -0,0 +1,24 @@
/*
Copyright 2021 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.
*/
export interface IIdentityServerProvider {
/**
* Gets an access token for use against the identity server,
* for the associated client.
* @returns {Promise<string>} Resolves to the access token.
*/
getAccessToken(): Promise<string>;
}
+155
View File
@@ -0,0 +1,155 @@
/*
Copyright 2020 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 { UnstableValue } from "../NamespacedValue";
export enum EventType {
// Room state events
RoomCanonicalAlias = "m.room.canonical_alias",
RoomCreate = "m.room.create",
RoomJoinRules = "m.room.join_rules",
RoomMember = "m.room.member",
RoomThirdPartyInvite = "m.room.third_party_invite",
RoomPowerLevels = "m.room.power_levels",
RoomName = "m.room.name",
RoomTopic = "m.room.topic",
RoomAvatar = "m.room.avatar",
RoomPinnedEvents = "m.room.pinned_events",
RoomEncryption = "m.room.encryption",
RoomHistoryVisibility = "m.room.history_visibility",
RoomGuestAccess = "m.room.guest_access",
RoomServerAcl = "m.room.server_acl",
RoomTombstone = "m.room.tombstone",
/**
* @deprecated Should not be used.
*/
RoomAliases = "m.room.aliases", // deprecated https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
SpaceChild = "m.space.child",
SpaceParent = "m.space.parent",
// Room timeline events
RoomRedaction = "m.room.redaction",
RoomMessage = "m.room.message",
RoomMessageEncrypted = "m.room.encrypted",
Sticker = "m.sticker",
CallInvite = "m.call.invite",
CallCandidates = "m.call.candidates",
CallAnswer = "m.call.answer",
CallHangup = "m.call.hangup",
CallReject = "m.call.reject",
CallSelectAnswer = "m.call.select_answer",
CallNegotiate = "m.call.negotiate",
CallReplaces = "m.call.replaces",
CallAssertedIdentity = "m.call.asserted_identity",
CallAssertedIdentityPrefix = "org.matrix.call.asserted_identity",
KeyVerificationRequest = "m.key.verification.request",
KeyVerificationStart = "m.key.verification.start",
KeyVerificationCancel = "m.key.verification.cancel",
KeyVerificationMac = "m.key.verification.mac",
KeyVerificationDone = "m.key.verification.done",
// use of this is discouraged https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-feedback
RoomMessageFeedback = "m.room.message.feedback",
Reaction = "m.reaction",
// Room ephemeral events
Typing = "m.typing",
Receipt = "m.receipt",
Presence = "m.presence",
// Room account_data events
FullyRead = "m.fully_read",
Tag = "m.tag",
SpaceOrder = "org.matrix.msc3230.space_order", // MSC3230
// User account_data events
PushRules = "m.push_rules",
Direct = "m.direct",
IgnoredUserList = "m.ignored_user_list",
// to_device events
RoomKey = "m.room_key",
RoomKeyRequest = "m.room_key_request",
ForwardedRoomKey = "m.forwarded_room_key",
Dummy = "m.dummy",
}
export enum MsgType {
Text = "m.text",
Emote = "m.emote",
Notice = "m.notice",
Image = "m.image",
File = "m.file",
Audio = "m.audio",
Location = "m.location",
Video = "m.video",
}
export const RoomCreateTypeField = "type";
export enum RoomType {
Space = "m.space",
}
/**
* Identifier for an [MSC3088](https://github.com/matrix-org/matrix-doc/pull/3088)
* room purpose. Note that this reference is UNSTABLE and subject to breaking changes,
* including its eventual removal.
*/
export const UNSTABLE_MSC3088_PURPOSE = new UnstableValue("m.room.purpose", "org.matrix.msc3088.purpose");
/**
* Enabled flag for an [MSC3088](https://github.com/matrix-org/matrix-doc/pull/3088)
* room purpose. Note that this reference is UNSTABLE and subject to breaking changes,
* including its eventual removal.
*/
export const UNSTABLE_MSC3088_ENABLED = new UnstableValue("m.enabled", "org.matrix.msc3088.enabled");
/**
* Subtype for an [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) space-room.
* Note that this reference is UNSTABLE and subject to breaking changes, including its
* eventual removal.
*/
export const UNSTABLE_MSC3089_TREE_SUBTYPE = new UnstableValue("m.data_tree", "org.matrix.msc3089.data_tree");
/**
* Leaf type for an event in a [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) space-room.
* Note that this reference is UNSTABLE and subject to breaking changes, including its
* eventual removal.
*/
export const UNSTABLE_MSC3089_LEAF = new UnstableValue("m.leaf", "org.matrix.msc3089.leaf");
/**
* Branch (Leaf Reference) type for the index approach in a
* [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) space-room. Note that this reference is
* UNSTABLE and subject to breaking changes, including its eventual removal.
*/
export const UNSTABLE_MSC3089_BRANCH = new UnstableValue("m.branch", "org.matrix.msc3089.branch");
export interface IEncryptedFile {
url: string;
mimetype?: string;
key: {
alg: string;
key_ops: string[]; // eslint-disable-line camelcase
kty: string;
k: string;
ext: boolean;
};
iv: string;
hashes: {[alg: string]: string};
v: string;
}
+60 -1
View File
@@ -15,7 +15,7 @@ limitations under the License.
*/
// this is needed to tell TS about global.Olm
import * as Olm from "olm"; // eslint-disable-line @typescript-eslint/no-unused-vars
import * as Olm from "@matrix-org/olm"; // eslint-disable-line @typescript-eslint/no-unused-vars
export {};
@@ -25,4 +25,63 @@ declare global {
localStorage: Storage;
}
}
interface Window {
electron?: Electron;
}
interface Electron {
getDesktopCapturerSources(options: GetSourcesOptions): Promise<Array<DesktopCapturerSource>>;
}
interface MediaDevices {
// This is experimental and types don't know about it yet
// https://github.com/microsoft/TypeScript/issues/33232
getDisplayMedia(constraints: MediaStreamConstraints | DesktopCapturerConstraints): Promise<MediaStream>;
getUserMedia(constraints: MediaStreamConstraints | DesktopCapturerConstraints): Promise<MediaStream>;
}
interface DesktopCapturerConstraints {
audio: boolean | {
mandatory: {
chromeMediaSource: string;
chromeMediaSourceId: string;
};
};
video: boolean | {
mandatory: {
chromeMediaSource: string;
chromeMediaSourceId: string;
};
};
}
interface DesktopCapturerSource {
id: string;
name: string;
thumbnailURL: string;
}
interface GetSourcesOptions {
types: Array<string>;
thumbnailSize?: {
height: number;
width: number;
};
fetchWindowIcons?: boolean;
}
interface HTMLAudioElement {
// sinkId & setSinkId are experimental and typescript doesn't know about them
sinkId: string;
setSinkId(outputId: string);
}
interface DummyInterfaceWeShouldntBeUsingThis {}
interface Navigator {
// We check for the webkit-prefixed getUserMedia to detect if we're
// on webkit: we should check if we still need to do this
webkitGetUserMedia: DummyInterfaceWeShouldntBeUsingThis;
}
}
+39
View File
@@ -0,0 +1,39 @@
/*
Copyright 2021 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.
*/
export interface IImageInfo {
size?: number;
mimetype?: string;
thumbnail_info?: { // eslint-disable-line camelcase
w?: number;
h?: number;
size?: number;
mimetype?: string;
};
w?: number;
h?: number;
}
export enum Visibility {
Public = "public",
Private = "private",
}
export enum Preset {
PrivateChat = "private_chat",
TrustedPrivateChat = "trusted_private_chat",
PublicChat = "public_chat",
}
+122
View File
@@ -0,0 +1,122 @@
/*
Copyright 2021 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 { Callback } from "../client";
import { Preset, Visibility } from "./partials";
// allow camelcase as these are things go onto the wire
/* eslint-disable camelcase */
export interface IJoinRoomOpts {
/**
* True to do a room initial sync on the resulting
* room. If false, the <strong>returned Room object will have no current state.
* </strong> Default: true.
*/
syncRoom?: boolean;
/**
* If the caller has a keypair 3pid invite, the signing URL is passed in this parameter.
*/
inviteSignUrl?: string;
/**
* The server names to try and join through in addition to those that are automatically chosen.
*/
viaServers?: string[];
}
export interface IRedactOpts {
reason?: string;
}
export interface ISendEventResponse {
event_id: string;
}
export interface IPresenceOpts {
presence: "online" | "offline" | "unavailable";
status_msg?: string;
}
export interface IPaginateOpts {
backwards?: boolean;
limit?: number;
}
export interface IGuestAccessOpts {
allowJoin: boolean;
allowRead: boolean;
}
export interface ISearchOpts {
keys?: string[];
query: string;
}
export interface IEventSearchOpts {
filter: any; // TODO: Types
term: string;
}
export interface IInvite3PID {
id_server: string;
id_access_token?: string; // this gets injected by the js-sdk
medium: string;
address: string;
}
export interface ICreateRoomStateEvent {
type: string;
state_key?: string; // defaults to an empty string
content: object;
}
export interface ICreateRoomOpts {
room_alias_name?: string;
visibility?: Visibility;
name?: string;
topic?: string;
preset?: Preset;
power_level_content_override?: object;
creation_content?: object;
initial_state?: ICreateRoomStateEvent[];
invite?: string[];
invite_3pid?: IInvite3PID[];
is_direct?: boolean;
room_version?: string;
}
export interface IRoomDirectoryOptions {
server?: string;
limit?: number;
since?: string;
// TODO: Proper types
filter?: any & {generic_search_term: string};
}
export interface IUploadOpts {
name?: string;
includeFilename?: boolean;
type?: string;
rawResponse?: boolean;
onlyContentUri?: boolean;
callback?: Callback;
progressHandler?: (state: {loaded: number, total: number}) => void;
}
/* eslint-enable camelcase */
+21
View File
@@ -0,0 +1,21 @@
/*
Copyright 2021 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.
*/
export interface ISignatures {
[entity: string]: {
[keyId: string]: string;
};
}
+93
View File
@@ -0,0 +1,93 @@
/*
Copyright 2021 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.
*/
/**
* Represents a simple Matrix namespaced value. This will assume that if a stable prefix
* is provided that the stable prefix should be used when representing the identifier.
*/
export class NamespacedValue<S extends string, U extends string> {
// Stable is optional, but one of the two parameters is required, hence the weird-looking types.
// Goal is to to have developers explicitly say there is no stable value (if applicable).
public constructor(public readonly stable: S | null | undefined, public readonly unstable?: U) {
if (!this.unstable && !this.stable) {
throw new Error("One of stable or unstable values must be supplied");
}
}
public get name(): U | S {
if (this.stable) {
return this.stable;
}
return this.unstable;
}
public get altName(): U | S | null {
if (!this.stable) {
return null;
}
return this.unstable;
}
public matches(val: string): boolean {
return this.name === val || this.altName === val;
}
// this desperately wants https://github.com/microsoft/TypeScript/pull/26349 at the top level of the class
// so we can instantiate `NamespacedValue<string, _, _>` as a default type for that namespace.
public findIn<T>(obj: any): T {
let val: T;
if (this.name) {
val = obj?.[this.name];
}
if (!val && this.altName) {
val = obj?.[this.altName];
}
return val;
}
public includedIn(arr: any[]): boolean {
let included = false;
if (this.name) {
included = arr.includes(this.name);
}
if (!included && this.altName) {
included = arr.includes(this.altName);
}
return included;
}
}
/**
* Represents a namespaced value which prioritizes the unstable value over the stable
* value.
*/
export class UnstableValue<S extends string, U extends string> extends NamespacedValue<S, U> {
// Note: Constructor difference is that `unstable` is *required*.
public constructor(stable: S, unstable: U) {
super(stable, unstable);
if (!this.unstable) {
throw new Error("Unstable value must be supplied");
}
}
public get name(): U {
return this.unstable;
}
public get altName(): S {
return this.stable;
}
}
-52
View File
@@ -1,52 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @module
*/
export class ReEmitter {
constructor(target) {
this.target = target;
// We keep one bound event handler for each event name so we know
// what event is arriving
this.boundHandlers = {};
}
_handleEvent(eventName, ...args) {
this.target.emit(eventName, ...args);
}
reEmit(source, eventNames) {
// We include the source as the last argument for event handlers which may need it,
// such as read receipt listeners on the client class which won't have the context
// of the room.
const forSource = (handler, ...args) => {
handler(...args, source);
};
for (const eventName of eventNames) {
if (this.boundHandlers[eventName] === undefined) {
this.boundHandlers[eventName] = this._handleEvent.bind(this, eventName);
}
const boundHandler = forSource.bind(this, this.boundHandlers[eventName]);
source.on(eventName, boundHandler);
}
}
}
+50
View File
@@ -0,0 +1,50 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventEmitter } from "events";
export class ReEmitter {
private target: EventEmitter;
constructor(target: EventEmitter) {
this.target = target;
}
reEmit(source: EventEmitter, eventNames: string[]) {
for (const eventName of eventNames) {
// We include the source as the last argument for event handlers which may need it,
// such as read receipt listeners on the client class which won't have the context
// of the room.
const forSource = (...args) => {
// EventEmitter special cases 'error' to make the emit function throw if no
// handler is attached, which sort of makes sense for making sure that something
// handles an error, but for re-emitting, there could be a listener on the original
// source object so the test doesn't really work. We *could* try to replicate the
// same logic and throw if there is no listener on either the source or the target,
// but this behaviour is fairly undesireable for us anyway: the main place we throw
// 'error' events is for calls, where error events are usually emitted some time
// later by a different part of the code where 'emit' throwing because the app hasn't
// added an error handler isn't terribly helpful. (A better fix in retrospect may
// have been to just avoid using the event name 'error', but backwards compat...)
if (eventName === 'error' && this.target.listenerCount('error') === 0) return;
this.target.emit(eventName, ...args, source);
};
source.on(eventName, forSource);
}
}
}
+4 -4
View File
@@ -17,8 +17,8 @@ limitations under the License.
/** @module auto-discovery */
import {logger} from './logger';
import {URL as NodeURL} from "url";
import { logger } from './logger';
import { URL as NodeURL } from "url";
// Dev note: Auto discovery is part of the spec.
// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
@@ -511,12 +511,12 @@ export class AutoDiscovery {
action = "IGNORE";
reason = AutoDiscovery.ERROR_MISSING_WELLKNOWN;
}
resolve({raw: {}, action: action, reason: reason, error: err});
resolve({ raw: {}, action: action, reason: reason, error: err });
return;
}
try {
resolve({raw: JSON.parse(body), action: "SUCCESS"});
resolve({ raw: JSON.parse(body), action: "SUCCESS" });
} catch (e) {
let reason = AutoDiscovery.ERROR_INVALID;
if (e.name === "SyntaxError") {
-2369
View File
File diff suppressed because it is too large Load Diff
-6057
View File
File diff suppressed because it is too large Load Diff
+8472
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -59,7 +59,7 @@ export function getHttpUriForMxc(baseUrl, mxc, width, height,
if (resizeMethod) {
params.method = resizeMethod;
}
if (utils.keys(params).length > 0) {
if (Object.keys(params).length > 0) {
// these are thumbnailing params so they probably want the
// thumbnailing API...
prefix = "/_matrix/media/r0/thumbnail/";
@@ -72,6 +72,6 @@ export function getHttpUriForMxc(baseUrl, mxc, width, height,
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
}
return baseUrl + prefix + serverAndMediaId +
(utils.keys(params).length === 0 ? "" :
(Object.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params))) + fragment;
}
+9 -9
View File
@@ -20,11 +20,11 @@ limitations under the License.
* @module crypto/CrossSigning
*/
import {decodeBase64, encodeBase64, pkSign, pkVerify} from './olmlib';
import {EventEmitter} from 'events';
import {logger} from '../logger';
import {IndexedDBCryptoStore} from '../crypto/store/indexeddb-crypto-store';
import {decryptAES, encryptAES} from './aes';
import { decodeBase64, encodeBase64, pkSign, pkVerify } from './olmlib';
import { EventEmitter } from 'events';
import { logger } from '../logger';
import { IndexedDBCryptoStore } from '../crypto/store/indexeddb-crypto-store';
import { decryptAES, encryptAES } from './aes';
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
@@ -725,7 +725,7 @@ export function createCryptoStoreCacheCallbacks(store, olmdevice) {
/**
* Request cross-signing keys from another device during verification.
*
* @param {module:base-apis~MatrixBaseApis} baseApis base Matrix API interface
* @param {MatrixClient} baseApis base Matrix API interface
* @param {string} userId The user ID being verified
* @param {string} deviceId The device ID being verified
*/
@@ -739,7 +739,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
// it. We return here in order to test.
return new Promise((resolve, reject) => {
const client = baseApis;
const original = client._crypto._crossSigningInfo;
const original = client.crypto._crossSigningInfo;
// We already have all of the infrastructure we need to validate and
// cache cross-signing keys, so instead of replicating that, here we set
@@ -775,7 +775,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
// also request and cache the key backup key
const backupKeyPromise = new Promise(async resolve => {
const cachedKey = await client._crypto.getSessionBackupPrivateKey();
const cachedKey = await client.crypto.getSessionBackupPrivateKey();
if (!cachedKey) {
logger.info("No cached backup key found. Requesting...");
const secretReq = client.requestSecret(
@@ -785,7 +785,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
logger.info("Got key backup key, decoding...");
const decodedKey = decodeBase64(base64Key);
logger.info("Decoded backup key, storing...");
client._crypto.storeSessionBackupPrivateKey(
client.crypto.storeSessionBackupPrivateKey(
Uint8Array.from(decodedKey),
);
logger.info("Backup key stored. Starting backup restore...");
+21 -17
View File
@@ -22,14 +22,13 @@ limitations under the License.
* Manages the list of other users' devices
*/
import {EventEmitter} from 'events';
import {logger} from '../logger';
import {DeviceInfo} from './deviceinfo';
import {CrossSigningInfo} from './CrossSigning';
import { EventEmitter } from 'events';
import { logger } from '../logger';
import { DeviceInfo } from './deviceinfo';
import { CrossSigningInfo } from './CrossSigning';
import * as olmlib from './olmlib';
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import {defer, sleep} from '../utils';
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { chunkPromises, defer, sleep } from '../utils';
/* State transition diagram for DeviceList._deviceTrackingStatus
*
@@ -51,7 +50,6 @@ import {defer, sleep} from '../utils';
* +----------------------- UP_TO_DATE ------------------------+
*/
// constants for DeviceList._deviceTrackingStatus
const TRACKING_STATUS_NOT_TRACKED = 0;
const TRACKING_STATUS_PENDING_DOWNLOAD = 1;
@@ -62,7 +60,7 @@ const TRACKING_STATUS_UP_TO_DATE = 3;
* @alias module:crypto/DeviceList
*/
export class DeviceList extends EventEmitter {
constructor(baseApis, cryptoStore, olmDevice) {
constructor(baseApis, cryptoStore, olmDevice, keyDownloadChunkSize = 250) {
super();
this._cryptoStore = cryptoStore;
@@ -98,6 +96,9 @@ export class DeviceList extends EventEmitter {
// userId -> promise
this._keyDownloadsInProgressByUser = {};
// Maximum number of user IDs per request to prevent server overload (#1619)
this._keyDownloadChunkSize = keyDownloadChunkSize;
// Set whenever changes are made other than setting the sync token
this._dirty = false;
@@ -780,13 +781,17 @@ class DeviceListUpdateSerialiser {
opts.token = this._syncToken;
}
this._baseApis.downloadKeysForUsers(
downloadUsers, opts,
).then(async (res) => {
const dk = res.device_keys || {};
const masterKeys = res.master_keys || {};
const ssks = res.self_signing_keys || {};
const usks = res.user_signing_keys || {};
const factories = [];
for (let i = 0; i < downloadUsers.length; i += this._deviceList._keyDownloadChunkSize) {
const userSlice = downloadUsers.slice(i, i + this._deviceList._keyDownloadChunkSize);
factories.push(() => this._baseApis.downloadKeysForUsers(userSlice, opts));
}
chunkPromises(factories, 3).then(async (responses) => {
const dk = Object.assign({}, ...(responses.map(res => res.device_keys || {})));
const masterKeys = Object.assign({}, ...(responses.map(res => res.master_keys || {})));
const ssks = Object.assign({}, ...(responses.map(res => res.self_signing_keys || {})));
const usks = Object.assign({}, ...(responses.map(res => res.user_signing_keys || {})));
// yield to other things that want to execute in between users, to
// avoid wedging the CPU
@@ -885,7 +890,6 @@ class DeviceListUpdateSerialiser {
}
}
async function _updateStoredDeviceKeysForUser(
_olmDevice, userId, userStore, userResult, localUserId, localDeviceId,
) {
+13 -15
View File
@@ -1,8 +1,8 @@
import { logger } from "../logger";
import {MatrixEvent} from "../models/event";
import {EventEmitter} from "events";
import {createCryptoStoreCacheCallbacks} from "./CrossSigning";
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import { MatrixEvent } from "../models/event";
import { EventEmitter } from "events";
import { createCryptoStoreCacheCallbacks } from "./CrossSigning";
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import {
PREFIX_UNSTABLE,
} from "../http-api";
@@ -43,7 +43,7 @@ export class EncryptionSetupBuilder {
* @param {Object} keys the new keys
*/
addCrossSigningKeys(authUpload, keys) {
this._crossSigningKeys = {authUpload, keys};
this._crossSigningKeys = { authUpload, keys };
}
/**
@@ -86,7 +86,6 @@ export class EncryptionSetupBuilder {
userSignatures[deviceId] = signature;
}
/**
* @param {String} type
* @param {Object} content
@@ -205,27 +204,26 @@ export class EncryptionSetupOperation {
// The backup is trusted because the user provided the private key.
// Sign the backup with the cross signing key so the key backup can
// be trusted via cross-signing.
await baseApis._http.authedRequest(
await baseApis.http.authedRequest(
undefined, "PUT", "/room_keys/version/" + this._keyBackupInfo.version,
undefined, {
algorithm: this._keyBackupInfo.algorithm,
auth_data: this._keyBackupInfo.auth_data,
},
{prefix: PREFIX_UNSTABLE},
{ prefix: PREFIX_UNSTABLE },
);
} else {
// add new key backup
await baseApis._http.authedRequest(
await baseApis.http.authedRequest(
undefined, "POST", "/room_keys/version",
undefined, this._keyBackupInfo,
{prefix: PREFIX_UNSTABLE},
{ prefix: PREFIX_UNSTABLE },
);
}
}
}
}
/**
* Catches account data set by SecretStorage during bootstrapping by
* implementing the methods related to account data in MatrixClient
@@ -276,7 +274,7 @@ class AccountDataClientAdapter extends EventEmitter {
// as SecretStorage listens for it while calling this method
// and it seems to rely on this.
return Promise.resolve().then(() => {
const event = new MatrixEvent({type, content});
const event = new MatrixEvent({ type, content });
this.emit("accountData", event, lastEvent);
});
}
@@ -335,7 +333,7 @@ class SSSSCryptoCallbacks {
// for it to the general crypto callbacks and cache it
if (this._delegateCryptoCallbacks) {
const result = await this._delegateCryptoCallbacks.
getSecretStorageKey({keys}, name);
getSecretStorageKey({ keys }, name);
if (result) {
const [keyId, privateKey] = result;
this._privateKeys.set(keyId, privateKey);
@@ -344,14 +342,14 @@ class SSSSCryptoCallbacks {
}
}
addPrivateKey(keyId, privKey) {
addPrivateKey(keyId, keyInfo, privKey) {
this._privateKeys.set(keyId, privKey);
// Also pass along to application to cache if it wishes
if (
this._delegateCryptoCallbacks &&
this._delegateCryptoCallbacks.cacheSecretStorageKey
) {
this._delegateCryptoCallbacks.cacheSecretStorageKey(keyId, privKey);
this._delegateCryptoCallbacks.cacheSecretStorageKey(keyId, keyInfo, privKey);
}
}
}
+56 -22
View File
@@ -16,8 +16,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {logger} from '../logger';
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import { logger } from '../logger';
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import * as algorithms from './algorithms';
// The maximum size of an event is 65K, and we base64 the content, so this is a
@@ -48,7 +48,6 @@ function checkPayloadLength(payloadString) {
}
}
/**
* The type of object we use for importing and exporting megolm session data.
*
@@ -62,7 +61,6 @@ function checkPayloadLength(payloadString) {
* @property {String} session_key Base64'ed key data
*/
/**
* Manages the olm cryptography functions. Each OlmDevice has a single
* OlmAccount and a number of OlmSessions.
@@ -350,7 +348,7 @@ OlmDevice.prototype._unpickleSession = function(sessionInfo, func) {
const session = new global.Olm.Session();
try {
session.unpickle(this._pickleKey, sessionInfo.session);
const unpickledSessInfo = Object.assign({}, sessionInfo, {session});
const unpickledSessInfo = Object.assign({}, sessionInfo, { session });
func(unpickledSessInfo);
} finally {
@@ -376,7 +374,6 @@ OlmDevice.prototype._saveSession = function(deviceKey, sessionInfo, txn) {
);
};
/**
* get an OlmUtility and call the given function
*
@@ -393,7 +390,6 @@ OlmDevice.prototype._getUtility = function(func) {
}
};
/**
* Signs a message with the ed25519 key for this account.
*
@@ -434,7 +430,6 @@ OlmDevice.prototype.getOneTimeKeys = async function() {
return result;
};
/**
* Get the maximum number of one-time keys we can store.
*
@@ -545,11 +540,11 @@ OlmDevice.prototype.createOutboundSession = async function(
}
});
},
logger.withPrefix("[createOutboundSession]"),
);
return newSessionId;
};
/**
* Generate a new inbound session, given an incoming message
*
@@ -605,12 +600,12 @@ OlmDevice.prototype.createInboundSession = async function(
}
});
},
logger.withPrefix("[createInboundSession]"),
);
return result;
};
/**
* Get a list of known session IDs for the given device
*
@@ -619,8 +614,10 @@ OlmDevice.prototype.createInboundSession = async function(
* @return {Promise<string[]>} a list of known session ids for the device
*/
OlmDevice.prototype.getSessionIdsForDevice = async function(theirDeviceIdentityKey) {
const log = logger.withPrefix("[getSessionIdsForDevice]");
if (this._sessionsInProgress[theirDeviceIdentityKey]) {
logger.log("waiting for olm session to be created");
log.debug(`Waiting for Olm session for ${theirDeviceIdentityKey} to be created`);
try {
await this._sessionsInProgress[theirDeviceIdentityKey];
} catch (e) {
@@ -638,6 +635,7 @@ OlmDevice.prototype.getSessionIdsForDevice = async function(theirDeviceIdentityK
},
);
},
log,
);
return sessionIds;
@@ -651,13 +649,14 @@ OlmDevice.prototype.getSessionIdsForDevice = async function(theirDeviceIdentityK
* @param {boolean} nowait Don't wait for an in-progress session to complete.
* This should only be set to true of the calling function is the function
* that marked the session as being in-progress.
* @param {Logger} [log] A possibly customised log
* @return {Promise<?string>} session id, or null if no established session
*/
OlmDevice.prototype.getSessionIdForDevice = async function(
theirDeviceIdentityKey, nowait,
theirDeviceIdentityKey, nowait, log,
) {
const sessionInfos = await this.getSessionInfoForDevice(
theirDeviceIdentityKey, nowait,
theirDeviceIdentityKey, nowait, log,
);
if (sessionInfos.length === 0) {
@@ -697,11 +696,16 @@ OlmDevice.prototype.getSessionIdForDevice = async function(
* @param {boolean} nowait Don't wait for an in-progress session to complete.
* This should only be set to true of the calling function is the function
* that marked the session as being in-progress.
* @param {Logger} [log] A possibly customised log
* @return {Array.<{sessionId: string, hasReceivedMessage: Boolean}>}
*/
OlmDevice.prototype.getSessionInfoForDevice = async function(deviceIdentityKey, nowait) {
OlmDevice.prototype.getSessionInfoForDevice = async function(
deviceIdentityKey, nowait, log = logger,
) {
log = log.withPrefix("[getSessionInfoForDevice]");
if (this._sessionsInProgress[deviceIdentityKey] && !nowait) {
logger.log("waiting for olm session to be created");
log.debug(`Waiting for Olm session for ${deviceIdentityKey} to be created`);
try {
await this._sessionsInProgress[deviceIdentityKey];
} catch (e) {
@@ -727,6 +731,7 @@ OlmDevice.prototype.getSessionInfoForDevice = async function(deviceIdentityKey,
}
});
},
log,
);
return info;
@@ -761,6 +766,7 @@ OlmDevice.prototype.encryptMessage = async function(
this._saveSession(theirDeviceIdentityKey, sessionInfo, txn);
});
},
logger.withPrefix("[encryptMessage]"),
);
return res;
};
@@ -794,6 +800,7 @@ OlmDevice.prototype.decryptMessage = async function(
this._saveSession(theirDeviceIdentityKey, sessionInfo, txn);
});
},
logger.withPrefix("[decryptMessage]"),
);
return payloadString;
};
@@ -825,6 +832,7 @@ OlmDevice.prototype.matchesSession = async function(
matches = sessionInfo.session.matches_inbound(ciphertext);
});
},
logger.withPrefix("[matchesSession]"),
);
return matches;
};
@@ -841,7 +849,6 @@ OlmDevice.prototype.filterOutNotifiedErrorDevices = async function(devices) {
return await this._cryptoStore.filterOutNotifiedErrorDevices(devices);
};
// Outbound group session
// ======================
@@ -856,7 +863,6 @@ OlmDevice.prototype._saveOutboundGroupSession = function(session) {
this._outboundGroupSessionStore[session.session_id()] = pickledSession;
};
/**
* extract an OutboundGroupSession from _outboundGroupSessionStore and call the
* given function
@@ -881,7 +887,6 @@ OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) {
}
};
/**
* Generate a new outbound group session
*
@@ -898,7 +903,6 @@ OlmDevice.prototype.createOutboundGroupSession = function() {
}
};
/**
* Encrypt an outgoing message with an outbound group session
*
@@ -938,7 +942,6 @@ OlmDevice.prototype.getOutboundGroupSessionKey = function(sessionId) {
});
};
// Inbound group session
// =====================
@@ -1033,6 +1036,7 @@ OlmDevice.prototype.addInboundGroupSession = async function(
'readwrite', [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS,
], (txn) => {
/* if we already have this session, consider updating it */
this._getInboundGroupSession(
@@ -1059,9 +1063,14 @@ OlmDevice.prototype.addInboundGroupSession = async function(
+ senderKey + "/" + sessionId,
);
if (existingSession.first_known_index()
<= session.first_known_index()) {
<= session.first_known_index()
&& !(existingSession.first_known_index() == session.first_known_index()
&& !extraSessionData.untrusted
&& existingSessionData.untrusted)) {
// existing session has lower index (i.e. can
// decrypt more), so keep it
// decrypt more), or they have the same index and
// the new sessions trust does not win over the old
// sessions trust, so keep it
logger.log(
`Keeping existing megolm session ${sessionId}`,
);
@@ -1084,12 +1093,19 @@ OlmDevice.prototype.addInboundGroupSession = async function(
this._cryptoStore.storeEndToEndInboundGroupSession(
senderKey, sessionId, sessionData, txn,
);
if (!existingSession && extraSessionData.sharedHistory) {
this._cryptoStore.addSharedHistoryInboundGroupSession(
roomId, senderKey, sessionId, txn,
);
}
} finally {
session.free();
}
},
);
},
logger.withPrefix("[addInboundGroupSession]"),
);
};
@@ -1260,6 +1276,7 @@ OlmDevice.prototype.decryptGroupMessage = async function(
},
);
},
logger.withPrefix("[decryptGroupMessage]"),
);
if (error) {
@@ -1305,6 +1322,7 @@ OlmDevice.prototype.hasInboundSessionKeys = async function(roomId, senderKey, se
},
);
},
logger.withPrefix("[hasInboundSessionKeys]"),
);
return result;
@@ -1360,10 +1378,12 @@ OlmDevice.prototype.getInboundGroupSessionKey = async function(
"forwarding_curve25519_key_chain":
sessionData.forwardingCurve25519KeyChain || [],
"sender_claimed_ed25519_key": senderEd25519Key,
"shared_history": sessionData.sharedHistory || false,
};
},
);
},
logger.withPrefix("[getInboundGroupSessionKey]"),
);
return result;
@@ -1391,10 +1411,24 @@ OlmDevice.prototype.exportInboundGroupSession = function(
"session_key": session.export_session(messageIndex),
"forwarding_curve25519_key_chain": session.forwardingCurve25519KeyChain || [],
"first_known_index": session.first_known_index(),
"org.matrix.msc3061.shared_history": sessionData.sharedHistory || false,
};
});
};
OlmDevice.prototype.getSharedHistoryInboundGroupSessions = async function(roomId) {
let result;
await this._cryptoStore.doTxn(
'readonly', [
IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS,
], (txn) => {
result = this._cryptoStore.getSharedHistoryInboundGroupSessions(roomId, txn);
},
logger.withPrefix("[getSharedHistoryInboundGroupSessionsForRoom]"),
);
return result;
};
// Utilities
// =========
+2 -3
View File
@@ -14,8 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {logger} from '../logger';
import * as utils from '../utils';
import { logger } from '../logger';
/**
* Internal module. Management of outgoing room key requests.
@@ -496,7 +495,7 @@ function stringifyRequestBody(requestBody) {
function stringifyRecipientList(recipients) {
return '['
+ utils.map(recipients, (r) => `${r.userId}:${r.deviceId}`).join(",")
+ recipients.map((r) => `${r.userId}:${r.deviceId}`).join(",")
+ ']';
}
+1 -1
View File
@@ -20,7 +20,7 @@ limitations under the License.
* Manages the list of encrypted rooms
*/
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
/**
* @alias module:crypto/RoomList
+34 -29
View File
@@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {EventEmitter} from 'events';
import {logger} from '../logger';
import { EventEmitter } from 'events';
import { logger } from '../logger';
import * as olmlib from './olmlib';
import {randomString} from '../randomstring';
import {encryptAES, decryptAES} from './aes';
import {encodeBase64} from "./olmlib";
import { randomString } from '../randomstring';
import { encryptAES, decryptAES } from './aes';
import { encodeBase64 } from "./olmlib";
export const SECRET_STORAGE_ALGORITHM_V1_AES
= "m.secret_storage.v1.aes-hmac-sha2";
@@ -81,25 +81,27 @@ export class SecretStorage extends EventEmitter {
* @param {string} [keyId] the ID of the key. If not given, a random
* ID will be generated.
*
* @return {string} the ID of the key
* @return {object} An object with:
* keyId: {string} the ID of the key
* keyInfo: {object} details about the key (iv, mac, passphrase)
*/
async addKey(algorithm, opts, keyId) {
const keyData = {algorithm};
const keyInfo = { algorithm };
if (!opts) opts = {};
if (opts.name) {
keyData.name = opts.name;
keyInfo.name = opts.name;
}
if (algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (opts.passphrase) {
keyData.passphrase = opts.passphrase;
keyInfo.passphrase = opts.passphrase;
}
if (opts.key) {
const {iv, mac} = await SecretStorage._calculateKeyCheck(opts.key);
keyData.iv = iv;
keyData.mac = mac;
const { iv, mac } = await SecretStorage._calculateKeyCheck(opts.key);
keyInfo.iv = iv;
keyInfo.mac = mac;
}
} else {
throw new Error(`Unknown key algorithm ${opts.algorithm}`);
@@ -116,10 +118,13 @@ export class SecretStorage extends EventEmitter {
}
await this._baseApis.setAccountData(
`m.secret_storage.key.${keyId}`, keyData,
`m.secret_storage.key.${keyId}`, keyInfo,
);
return keyId;
return {
keyId,
keyInfo,
};
}
/**
@@ -166,7 +171,7 @@ export class SecretStorage extends EventEmitter {
async checkKey(key, info) {
if (info.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (info.mac) {
const {mac} = await SecretStorage._calculateKeyCheck(key, info.iv);
const { mac } = await SecretStorage._calculateKeyCheck(key, info.iv);
return info.mac.replace(/=+$/g, '') === mac.replace(/=+$/g, '');
} else {
// if we have no information, we have to assume the key is right
@@ -215,7 +220,7 @@ export class SecretStorage extends EventEmitter {
// encrypt secret, based on the algorithm
if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
const keys = {[keyId]: keyInfo};
const keys = { [keyId]: keyInfo };
const [, encryption] = await this._getSecretStorageKey(keys, name);
encrypted[keyId] = await encryption.encrypt(secret);
} else {
@@ -226,7 +231,7 @@ export class SecretStorage extends EventEmitter {
}
// save encrypted secret
await this._baseApis.setAccountData(name, {encrypted});
await this._baseApis.setAccountData(name, { encrypted });
}
/**
@@ -439,7 +444,7 @@ export class SecretStorage extends EventEmitter {
&& this._incomingRequests[deviceId][content.request_id]) {
logger.info("received request cancellation for secret (" + sender
+ ", " + deviceId + ", " + content.request_id + ")");
this.baseApis.emit("crypto.secrets.requestCancelled", {
this._baseApis.emit("crypto.secrets.requestCancelled", {
user_id: sender,
device_id: deviceId,
request_id: content.request_id,
@@ -457,13 +462,13 @@ export class SecretStorage extends EventEmitter {
if (!this._cryptoCallbacks.onSecretRequested) {
return;
}
const secret = await this._cryptoCallbacks.onSecretRequested({
user_id: sender,
device_id: deviceId,
request_id: content.request_id,
name: content.name,
device_trust: this._baseApis.checkDeviceTrust(sender, deviceId),
});
const secret = await this._cryptoCallbacks.onSecretRequested(
sender,
deviceId,
content.request_id,
content.name,
this._baseApis.checkDeviceTrust(sender, deviceId),
);
if (secret) {
logger.info(`Preparing ${content.name} secret for ${deviceId}`);
const payload = {
@@ -475,11 +480,11 @@ export class SecretStorage extends EventEmitter {
};
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this._baseApis._crypto._olmDevice.deviceCurve25519Key,
sender_key: this._baseApis.crypto._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
await olmlib.ensureOlmSessionsForDevices(
this._baseApis._crypto._olmDevice,
this._baseApis.crypto._olmDevice,
this._baseApis,
{
[sender]: [
@@ -491,7 +496,7 @@ export class SecretStorage extends EventEmitter {
encryptedContent.ciphertext,
this._baseApis.getUserId(),
this._baseApis.deviceId,
this._baseApis._crypto._olmDevice,
this._baseApis.crypto._olmDevice,
sender,
this._baseApis.getStoredDevice(sender, deviceId),
payload,
@@ -522,7 +527,7 @@ export class SecretStorage extends EventEmitter {
if (requestControl) {
// make sure that the device that sent it is one of the devices that
// we requested from
const deviceInfo = this._baseApis._crypto._deviceList.getDeviceByIdentityKey(
const deviceInfo = this._baseApis.crypto._deviceList.getDeviceByIdentityKey(
olmlib.OLM_ALGORITHM,
event.getSenderKey(),
);
+7 -7
View File
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {getCrypto} from '../utils';
import {decodeBase64, encodeBase64} from './olmlib';
import { getCrypto } from '../utils';
import { decodeBase64, encodeBase64 } from './olmlib';
const subtleCrypto = (typeof window !== "undefined" && window.crypto) ?
(window.crypto.subtle || window.crypto.webkitSubtle) : null;
@@ -148,7 +148,7 @@ async function encryptBrowser(data, key, name, ivStr) {
);
const hmac = await subtleCrypto.sign(
{name: 'HMAC'},
{ name: 'HMAC' },
hmacKey,
ciphertext,
);
@@ -176,7 +176,7 @@ async function decryptBrowser(data, key, name) {
const ciphertext = decodeBase64(data.ciphertext);
if (!await subtleCrypto.verify(
{name: "HMAC"},
{ name: "HMAC" },
hmacKey,
decodeBase64(data.mac),
ciphertext,
@@ -201,7 +201,7 @@ async function deriveKeysBrowser(key, name) {
const hkdfkey = await subtleCrypto.importKey(
'raw',
key,
{name: "HKDF"},
{ name: "HKDF" },
false,
["deriveBits"],
);
@@ -222,7 +222,7 @@ async function deriveKeysBrowser(key, name) {
const aesProm = subtleCrypto.importKey(
'raw',
aesKey,
{name: 'AES-CTR'},
{ name: 'AES-CTR' },
false,
['encrypt', 'decrypt'],
);
@@ -232,7 +232,7 @@ async function deriveKeysBrowser(key, name) {
hmacKey,
{
name: 'HMAC',
hash: {name: 'SHA-256'},
hash: { name: 'SHA-256' },
},
false,
['sign', 'verify'],
+2 -2
View File
@@ -46,7 +46,7 @@ export const DECRYPTION_CLASSES = {};
* @param {string} params.deviceId The identifier for this device.
* @param {module:crypto} params.crypto crypto core
* @param {module:crypto/OlmDevice} params.olmDevice olm.js wrapper
* @param {module:base-apis~MatrixBaseApis} baseApis base matrix api interface
* @param {MatrixClient} baseApis base matrix api interface
* @param {string} params.roomId The ID of the room we will be sending to
* @param {object} params.config The body of the m.room.encryption event
*/
@@ -102,7 +102,7 @@ export class EncryptionAlgorithm {
* @param {string} params.userId The UserID for the local user
* @param {module:crypto} params.crypto crypto core
* @param {module:crypto/OlmDevice} params.olmDevice olm.js wrapper
* @param {module:base-apis~MatrixBaseApis} baseApis base matrix api interface
* @param {MatrixClient} baseApis base matrix api interface
* @param {string=} params.roomId The ID of the room we will be receiving
* from. Null for to-device events.
*/
+236 -94
View File
@@ -22,9 +22,9 @@ limitations under the License.
* @module crypto/algorithms/megolm
*/
import {logger} from '../../logger';
import { logger } from '../../logger';
import * as utils from "../../utils";
import {polyfillSuper} from "../../utils";
import { polyfillSuper } from "../../utils";
import * as olmlib from "../olmlib";
import {
DecryptionAlgorithm,
@@ -34,13 +34,29 @@ import {
UnknownDeviceError,
} from "./base";
import {WITHHELD_MESSAGES} from '../OlmDevice';
import { WITHHELD_MESSAGES } from '../OlmDevice';
// determine whether the key can be shared with invitees
function isRoomSharedHistory(room) {
const visibilityEvent = room.currentState &&
room.currentState.getStateEvents("m.room.history_visibility", "");
// NOTE: if the room visibility is unset, it would normally default to
// "world_readable".
// (https://spec.matrix.org/unstable/client-server-api/#server-behaviour-5)
// But we will be paranoid here, and treat it as a situation where the room
// is not shared-history
const visibility = visibilityEvent && visibilityEvent.getContent() &&
visibilityEvent.getContent().history_visibility;
return ["world_readable", "shared"].includes(visibility);
}
/**
* @private
* @constructor
*
* @param {string} sessionId
* @param {boolean} sharedHistory whether the session can be freely shared with
* other group members, according to the room history visibility settings
*
* @property {string} sessionId
* @property {Number} useCount number of times this session has been used
@@ -50,15 +66,15 @@ import {WITHHELD_MESSAGES} from '../OlmDevice';
* devices with which we have shared the session key
* userId -> {deviceId -> msgindex}
*/
function OutboundSessionInfo(sessionId) {
function OutboundSessionInfo(sessionId, sharedHistory = false) {
this.sessionId = sessionId;
this.useCount = 0;
this.creationTime = new Date().getTime();
this.sharedWithDevices = {};
this.blockedDevicesNotified = {};
this.sharedHistory = sharedHistory;
}
/**
* Check if it's time to rotate the session
*
@@ -141,7 +157,6 @@ OutboundSessionInfo.prototype.sharedWithTooManyDevices = function(
}
};
/**
* Megolm encryption implementation
*
@@ -183,6 +198,7 @@ utils.inherits(MegolmEncryption, EncryptionAlgorithm);
/**
* @private
*
* @param {module:models/room} room
* @param {Object} devicesInRoom The devices in this room, indexed by user ID
* @param {Object} blocked The devices that are blocked, indexed by user ID
* @param {boolean} [singleOlmCreationPhase] Only perform one round of olm
@@ -192,7 +208,7 @@ utils.inherits(MegolmEncryption, EncryptionAlgorithm);
* OutboundSessionInfo when setup is complete.
*/
MegolmEncryption.prototype._ensureOutboundSession = async function(
devicesInRoom, blocked, singleOlmCreationPhase,
room, devicesInRoom, blocked, singleOlmCreationPhase,
) {
let session;
@@ -204,6 +220,13 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
const prepareSession = async (oldSession) => {
session = oldSession;
const sharedHistory = isRoomSharedHistory(room);
// history visibility changed
if (session && sharedHistory !== session.sharedHistory) {
session = null;
}
// need to make a brand new session?
if (session && session.needsRotation(this._sessionRotationPeriodMsgs,
this._sessionRotationPeriodMs)
@@ -219,7 +242,7 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
if (!session) {
logger.log(`Starting new megolm session for room ${this._roomId}`);
session = await this._prepareNewSession();
session = await this._prepareNewSession(sharedHistory);
logger.log(`Started new megolm session ${session.sessionId} ` +
`for room ${this._roomId}`);
this._outboundSessions[session.sessionId] = session;
@@ -250,11 +273,12 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
const payload = {
type: "m.room_key",
content: {
algorithm: olmlib.MEGOLM_ALGORITHM,
room_id: this._roomId,
session_id: session.sessionId,
session_key: key.key,
chain_index: key.chain_index,
"algorithm": olmlib.MEGOLM_ALGORITHM,
"room_id": this._roomId,
"session_id": session.sessionId,
"session_key": key.key,
"chain_index": key.chain_index,
"org.matrix.msc3061.shared_history": sharedHistory,
},
};
const [devicesWithoutSession, olmSessions] = await olmlib.getExistingOlmSessions(
@@ -264,11 +288,14 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
await Promise.all([
(async () => {
// share keys with devices that we already have a session for
logger.debug(`Sharing keys with existing Olm sessions in ${this._roomId}`);
await this._shareKeyWithOlmSessions(
session, key, payload, olmSessions,
);
logger.debug(`Shared keys with existing Olm sessions in ${this._roomId}`);
})(),
(async () => {
logger.debug(`Sharing keys (start phase 1) with new Olm sessions in ${this._roomId}`);
const errorDevices = [];
// meanwhile, establish olm sessions for devices that we don't
@@ -282,6 +309,7 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
session, key, payload, devicesWithoutSession, errorDevices,
singleOlmCreationPhase ? 10000 : 2000, failedServers,
);
logger.debug(`Shared keys (end phase 1) with new Olm sessions in ${this._roomId}`);
if (!singleOlmCreationPhase && (Date.now() - start < 10000)) {
// perform the second phase of olm session creation if requested,
@@ -298,7 +326,7 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
failedServerMap.add(server);
}
const failedDevices = [];
for (const {userId, deviceInfo} of errorDevices) {
for (const { userId, deviceInfo } of errorDevices) {
const userHS = userId.slice(userId.indexOf(":") + 1);
if (failedServerMap.has(userHS)) {
retryDevices[userId] = retryDevices[userId] || [];
@@ -306,23 +334,28 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
} else {
// if we aren't going to retry, then handle it
// as a failed device
failedDevices.push({userId, deviceInfo});
failedDevices.push({ userId, deviceInfo });
}
}
logger.debug(`Sharing keys (start phase 2) with new Olm sessions in ${this._roomId}`);
await this._shareKeyWithDevices(
session, key, payload, retryDevices, failedDevices, 30000,
);
logger.debug(`Shared keys (end phase 2) with new Olm sessions in ${this._roomId}`);
await this._notifyFailedOlmDevices(session, key, failedDevices);
})();
} else {
await this._notifyFailedOlmDevices(session, key, errorDevices);
}
logger.debug(`Shared keys (all phases done) with new Olm sessions in ${this._roomId}`);
})(),
(async () => {
logger.debug(`Notifying blocked devices in ${this._roomId}`);
// also, notify blocked devices that they're blocked
const blockedMap = {};
let blockedCount = 0;
for (const [userId, userBlockedDevices] of Object.entries(blocked)) {
for (const [deviceId, device] of Object.entries(userBlockedDevices)) {
if (
@@ -330,12 +363,14 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
session.blockedDevicesNotified[userId][deviceId] === undefined
) {
blockedMap[userId] = blockedMap[userId] || {};
blockedMap[userId][deviceId] = {device};
blockedMap[userId][deviceId] = { device };
blockedCount++;
}
}
}
await this._notifyBlockedDevices(session, blockedMap);
logger.debug(`Notified ${blockedCount} blocked devices in ${this._roomId}`);
})(),
]);
};
@@ -348,6 +383,11 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
// first wait for the previous share to complete
const prom = this._setupPromise.then(prepareSession);
// Ensure any failures are logged for debugging
prom.catch(e => {
logger.error(`Failed to ensure outbound session in ${this._roomId}`, e);
});
// _setupPromise resolves to `session` whether or not the share succeeds
this._setupPromise = prom.then(returnSession, returnSession);
@@ -358,30 +398,26 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
/**
* @private
*
* @param {boolean} sharedHistory
*
* @return {module:crypto/algorithms/megolm.OutboundSessionInfo} session
*/
MegolmEncryption.prototype._prepareNewSession = async function() {
MegolmEncryption.prototype._prepareNewSession = async function(sharedHistory) {
const sessionId = this._olmDevice.createOutboundGroupSession();
const key = this._olmDevice.getOutboundGroupSessionKey(sessionId);
await this._olmDevice.addInboundGroupSession(
this._roomId, this._olmDevice.deviceCurve25519Key, [], sessionId,
key.key, {ed25519: this._olmDevice.deviceEd25519Key},
key.key, { ed25519: this._olmDevice.deviceEd25519Key }, false,
{ sharedHistory: sharedHistory },
);
if (this._crypto.backupInfo) {
// don't wait for it to complete
this._crypto.backupGroupSession(
this._roomId, this._olmDevice.deviceCurve25519Key, [],
sessionId, key.key,
).catch((e) => {
// This throws if the upload failed, but this is fine
// since it will have written it to the db and will retry.
logger.log("Failed to back up megolm session", e);
});
}
// don't wait for it to complete
this._crypto._backupManager.backupGroupSession(
this._olmDevice.deviceCurve25519Key, sessionId,
);
return new OutboundSessionInfo(sessionId);
return new OutboundSessionInfo(sessionId, sharedHistory);
};
/**
@@ -415,7 +451,7 @@ MegolmEncryption.prototype._getDevicesWithoutSessions = function(
// no session with this device, probably because there
// were no one-time keys.
noOlmDevices.push({userId, deviceInfo});
noOlmDevices.push({ userId, deviceInfo });
delete sessionResults[deviceId];
// ensureOlmSessionsForUsers has already done the logging,
@@ -662,14 +698,15 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
const payload = {
type: "m.forwarded_room_key",
content: {
algorithm: olmlib.MEGOLM_ALGORITHM,
room_id: this._roomId,
session_id: sessionId,
session_key: key.key,
chain_index: key.chain_index,
sender_key: senderKey,
sender_claimed_ed25519_key: key.sender_claimed_ed25519_key,
forwarding_curve25519_key_chain: key.forwarding_curve25519_key_chain,
"algorithm": olmlib.MEGOLM_ALGORITHM,
"room_id": this._roomId,
"session_id": sessionId,
"session_key": key.key,
"chain_index": key.chain_index,
"sender_key": senderKey,
"sender_claimed_ed25519_key": key.sender_claimed_ed25519_key,
"forwarding_curve25519_key_chain": key.forwarding_curve25519_key_chain,
"org.matrix.msc3061.shared_history": key.shared_history || false,
},
};
@@ -723,13 +760,18 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
MegolmEncryption.prototype._shareKeyWithDevices = async function(
session, key, payload, devicesByUser, errorDevices, otkTimeout, failedServers,
) {
logger.debug(`Ensuring Olm sessions for devices in ${this._roomId}`);
const devicemap = await olmlib.ensureOlmSessionsForDevices(
this._olmDevice, this._baseApis, devicesByUser, otkTimeout, failedServers,
logger.withPrefix(`[${this._roomId}]`),
);
logger.debug(`Ensured Olm sessions for devices in ${this._roomId}`);
this._getDevicesWithoutSessions(devicemap, devicesByUser, errorDevices);
logger.debug(`Sharing keys with Olm sessions in ${this._roomId}`);
await this._shareKeyWithOlmSessions(session, key, payload, devicemap);
logger.debug(`Shared keys with Olm sessions in ${this._roomId}`);
};
MegolmEncryption.prototype._shareKeyWithOlmSessions = async function(
@@ -738,16 +780,17 @@ MegolmEncryption.prototype._shareKeyWithOlmSessions = async function(
const userDeviceMaps = this._splitDevices(devicemap);
for (let i = 0; i < userDeviceMaps.length; i++) {
const taskDetail =
`megolm keys for ${session.sessionId} ` +
`in ${this._roomId} (slice ${i + 1}/${userDeviceMaps.length})`;
try {
logger.debug(`Sharing ${taskDetail}`);
await this._encryptAndSendKeysToDevices(
session, key.chain_index, userDeviceMaps[i], payload,
);
logger.log(`Completed megolm keyshare for ${session.sessionId} `
+ `in ${this._roomId} (slice ${i + 1}/${userDeviceMaps.length})`);
logger.debug(`Shared ${taskDetail}`);
} catch (e) {
logger.log(`megolm keyshare for ${session.sessionId} in ${this._roomId} `
+ `(slice ${i + 1}/${userDeviceMaps.length}) failed`);
logger.error(`Failed to share ${taskDetail}`);
throw e;
}
}
@@ -766,9 +809,14 @@ MegolmEncryption.prototype._shareKeyWithOlmSessions = async function(
MegolmEncryption.prototype._notifyFailedOlmDevices = async function(
session, key, failedDevices,
) {
logger.debug(
`Notifying ${failedDevices.length} devices we failed to ` +
`create Olm sessions in ${this._roomId}`,
);
// mark the devices that failed as "handled" because we don't want to try
// to claim a one-time-key for dead devices on every message.
for (const {userId, deviceInfo} of failedDevices) {
for (const { userId, deviceInfo } of failedDevices) {
const deviceId = deviceInfo.deviceId;
session.markSharedWithDevice(
@@ -780,8 +828,12 @@ MegolmEncryption.prototype._notifyFailedOlmDevices = async function(
await this._olmDevice.filterOutNotifiedErrorDevices(
failedDevices,
);
logger.debug(
`Filtered down to ${filteredFailedDevices.length} error devices ` +
`in ${this._roomId}`,
);
const blockedMap = {};
for (const {userId, deviceInfo} of filteredFailedDevices) {
for (const { userId, deviceInfo } of filteredFailedDevices) {
blockedMap[userId] = blockedMap[userId] || {};
// we use a similar format to what
// olmlib.ensureOlmSessionsForDevices returns, so that
@@ -797,6 +849,10 @@ MegolmEncryption.prototype._notifyFailedOlmDevices = async function(
// send the notifications
await this._notifyBlockedDevices(session, blockedMap);
logger.debug(
`Notified ${filteredFailedDevices.length} devices we failed to ` +
`create Olm sessions in ${this._roomId}`,
);
};
/**
@@ -846,24 +902,41 @@ MegolmEncryption.prototype.prepareToEncrypt = function(room) {
// We're already preparing something, so don't do anything else.
// FIXME: check if we need to restart
// (https://github.com/matrix-org/matrix-js-sdk/issues/1255)
const elapsedTime = Date.now() - this.encryptionPreparationMetadata.startTime;
logger.debug(
`Already started preparing to encrypt for ${this._roomId} ` +
`${elapsedTime} ms ago, skipping`,
);
return;
}
logger.debug(`Preparing to encrypt events for ${this._roomId}`);
this.encryptionPreparationMetadata = {
startTime: Date.now(),
};
this.encryptionPreparation = (async () => {
const [devicesInRoom, blocked] = await this._getDevicesInRoom(room);
try {
logger.debug(`Getting devices in ${this._roomId}`);
const [devicesInRoom, blocked] = await this._getDevicesInRoom(room);
if (this._crypto.getGlobalErrorOnUnknownDevices()) {
// Drop unknown devices for now. When the message gets sent, we'll
// throw an error, but we'll still be prepared to send to the known
// devices.
this._removeUnknownDevices(devicesInRoom);
if (this._crypto.getGlobalErrorOnUnknownDevices()) {
// Drop unknown devices for now. When the message gets sent, we'll
// throw an error, but we'll still be prepared to send to the known
// devices.
this._removeUnknownDevices(devicesInRoom);
}
logger.debug(`Ensuring outbound session in ${this._roomId}`);
await this._ensureOutboundSession(room, devicesInRoom, blocked, true);
logger.debug(`Ready to encrypt events for ${this._roomId}`);
} catch (e) {
logger.error(`Failed to prepare to encrypt events for ${this._roomId}`, e);
} finally {
delete this.encryptionPreparationMetadata;
delete this.encryptionPreparation;
}
await this._ensureOutboundSession(devicesInRoom, blocked, true);
delete this.encryptionPreparation;
})();
};
@@ -899,7 +972,7 @@ MegolmEncryption.prototype.encryptMessage = async function(room, eventType, cont
this._checkForUnknownDevices(devicesInRoom);
}
const session = await this._ensureOutboundSession(devicesInRoom, blocked);
const session = await this._ensureOutboundSession(room, devicesInRoom, blocked);
const payloadJson = {
room_id: this._roomId,
type: eventType,
@@ -1000,7 +1073,7 @@ MegolmEncryption.prototype._removeUnknownDevices = function(devicesInRoom) {
*/
MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
const members = await room.getEncryptionTargetMembers();
const roomMembers = utils.map(members, function(u) {
const roomMembers = members.map(function(u) {
return u.userId;
});
@@ -1265,7 +1338,6 @@ MegolmDecryption.prototype._removeEventFromPendingList = function(event) {
}
};
/**
* @inheritdoc
*
@@ -1295,7 +1367,7 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
if (event.getType() == "m.forwarded_room_key") {
exportFormat = true;
forwardingKeyChain = content.forwarding_curve25519_key_chain;
if (!utils.isArray(forwardingKeyChain)) {
if (!Array.isArray(forwardingKeyChain)) {
forwardingKeyChain = [];
}
@@ -1324,10 +1396,14 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
keysClaimed = event.getKeysClaimed();
}
const extraSessionData = {};
if (content["org.matrix.msc3061.shared_history"]) {
extraSessionData.sharedHistory = true;
}
return this._olmDevice.addInboundGroupSession(
content.room_id, senderKey, forwardingKeyChain, sessionId,
content.session_key, keysClaimed,
exportFormat,
exportFormat, extraSessionData,
).then(() => {
// have another go at decrypting events sent with this session.
this._retryDecryption(senderKey, sessionId)
@@ -1347,18 +1423,8 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
}
});
}).then(() => {
if (this._crypto.backupInfo) {
// don't wait for the keys to be backed up for the server
this._crypto.backupGroupSession(
content.room_id, senderKey, forwardingKeyChain,
content.session_id, content.session_key, keysClaimed,
exportFormat,
).catch((e) => {
// This throws if the upload failed, but this is fine
// since it will have written it to the db and will retry.
logger.log("Failed to back up megolm session", e);
});
}
// don't wait for the keys to be backed up for the server
this._crypto._backupManager.backupGroupSession(senderKey, content.session_id);
}).catch((e) => {
logger.error(`Error handling m.room_key_event: ${e}`);
});
@@ -1415,7 +1481,7 @@ MegolmDecryption.prototype.onRoomKeyWithheldEvent = async function(event) {
}
}
await olmlib.ensureOlmSessionsForDevices(
this._olmDevice, this._baseApis, {[sender]: [device]}, false,
this._olmDevice, this._baseApis, { [sender]: [device] }, false,
);
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
@@ -1429,7 +1495,7 @@ MegolmDecryption.prototype.onRoomKeyWithheldEvent = async function(event) {
this._olmDevice,
sender,
device,
{type: "m.dummy"},
{ type: "m.dummy" },
);
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", true);
@@ -1533,14 +1599,15 @@ MegolmDecryption.prototype._buildKeyForwardingMessage = async function(
return {
type: "m.forwarded_room_key",
content: {
algorithm: olmlib.MEGOLM_ALGORITHM,
room_id: roomId,
sender_key: senderKey,
sender_claimed_ed25519_key: key.sender_claimed_ed25519_key,
session_id: sessionId,
session_key: key.key,
chain_index: key.chain_index,
forwarding_curve25519_key_chain: key.forwarding_curve25519_key_chain,
"algorithm": olmlib.MEGOLM_ALGORITHM,
"room_id": roomId,
"sender_key": senderKey,
"sender_claimed_ed25519_key": key.sender_claimed_ed25519_key,
"session_id": sessionId,
"session_key": key.key,
"chain_index": key.chain_index,
"forwarding_curve25519_key_chain": key.forwarding_curve25519_key_chain,
"org.matrix.msc3061.shared_history": key.shared_history || false,
},
};
};
@@ -1554,6 +1621,13 @@ MegolmDecryption.prototype._buildKeyForwardingMessage = async function(
* @param {string} [opts.source] where the key came from
*/
MegolmDecryption.prototype.importRoomKey = function(session, opts = {}) {
const extraSessionData = {};
if (opts.untrusted) {
extraSessionData.untrusted = true;
}
if (session["org.matrix.msc3061.shared_history"]) {
extraSessionData.sharedHistory = true;
}
return this._olmDevice.addInboundGroupSession(
session.room_id,
session.sender_key,
@@ -1562,18 +1636,12 @@ MegolmDecryption.prototype.importRoomKey = function(session, opts = {}) {
session.session_key,
session.sender_claimed_keys,
true,
opts.untrusted ? { untrusted: opts.untrusted } : {},
extraSessionData,
).then(() => {
if (this._crypto.backupInfo && opts.source !== "backup") {
if (opts.source !== "backup") {
// don't wait for it to complete
this._crypto.backupGroupSession(
session.room_id,
session.sender_key,
session.forwarding_curve25519_key_chain,
session.session_id,
session.session_key,
session.sender_claimed_keys,
true,
this._crypto._backupManager.backupGroupSession(
session.sender_key, session.session_id,
).catch((e) => {
// This throws if the upload failed, but this is fine
// since it will have written it to the db and will retry.
@@ -1610,7 +1678,7 @@ MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionI
await Promise.all([...pending].map(async (ev) => {
try {
await ev.attemptDecryption(this._crypto, true);
await ev.attemptDecryption(this._crypto, { isRetry: true });
} catch (e) {
// don't die if something goes wrong
}
@@ -1641,6 +1709,80 @@ MegolmDecryption.prototype.retryDecryptionFromSender = async function(senderKey)
return !this._pendingEvents[senderKey];
};
MegolmDecryption.prototype.sendSharedHistoryInboundSessions = async function(devicesByUser) {
await olmlib.ensureOlmSessionsForDevices(
this._olmDevice, this._baseApis, devicesByUser,
);
logger.log("sendSharedHistoryInboundSessions to users", Object.keys(devicesByUser));
const sharedHistorySessions =
await this._olmDevice.getSharedHistoryInboundGroupSessions(
this._roomId,
);
logger.log("shared-history sessions", sharedHistorySessions);
for (const [senderKey, sessionId] of sharedHistorySessions) {
const payload = await this._buildKeyForwardingMessage(
this._roomId, senderKey, sessionId,
);
const promises = [];
const contentMap = {};
for (const [userId, devices] of Object.entries(devicesByUser)) {
contentMap[userId] = {};
for (const deviceInfo of devices) {
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
contentMap[userId][deviceInfo.deviceId] = encryptedContent;
promises.push(
olmlib.encryptMessageForDevice(
encryptedContent.ciphertext,
this._userId,
this._deviceId,
this._olmDevice,
userId,
deviceInfo,
payload,
),
);
}
}
await Promise.all(promises);
// prune out any devices that encryptMessageForDevice could not encrypt for,
// in which case it will have just not added anything to the ciphertext object.
// There's no point sending messages to devices if we couldn't encrypt to them,
// since that's effectively a blank message.
for (const userId of Object.keys(contentMap)) {
for (const deviceId of Object.keys(contentMap[userId])) {
if (Object.keys(contentMap[userId][deviceId].ciphertext).length === 0) {
logger.log(
"No ciphertext for device " +
userId + ":" + deviceId + ": pruning",
);
delete contentMap[userId][deviceId];
}
}
// No devices left for that user? Strip that too.
if (Object.keys(contentMap[userId]).length === 0) {
logger.log("Pruned all devices for user " + userId);
delete contentMap[userId];
}
}
// Is there anything left?
if (Object.keys(contentMap).length === 0) {
logger.log("No users left to send to: aborting");
return;
}
await this._baseApis.sendToDevice("m.room.encrypted", contentMap);
}
};
registerAlgorithm(
olmlib.MEGOLM_ALGORITHM, MegolmEncryption, MegolmDecryption,
);
+4 -5
View File
@@ -20,11 +20,11 @@ limitations under the License.
* @module crypto/algorithms/olm
*/
import {logger} from '../../logger';
import { logger } from '../../logger';
import * as utils from "../../utils";
import {polyfillSuper} from "../../utils";
import { polyfillSuper } from "../../utils";
import * as olmlib from "../olmlib";
import {DeviceInfo} from "../deviceinfo";
import { DeviceInfo } from "../deviceinfo";
import {
DecryptionAlgorithm,
DecryptionError,
@@ -95,7 +95,7 @@ OlmEncryption.prototype.encryptMessage = async function(room, eventType, content
const members = await room.getEncryptionTargetMembers();
const users = utils.map(members, function(u) {
const users = members.map(function(u) {
return u.userId;
});
@@ -358,5 +358,4 @@ OlmDecryption.prototype._reallyDecryptMessage = async function(
return res.payload;
};
registerAlgorithm(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption);
+131
View File
@@ -0,0 +1,131 @@
/*
Copyright 2021 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 { DeviceInfo } from "./deviceinfo";
import { IKeyBackupVersion } from "./keybackup";
import { ISecretStorageKeyInfo } from "../matrix";
// TODO: Merge this with crypto.js once converted
export enum CrossSigningKey {
Master = "master",
SelfSigning = "self_signing",
UserSigning = "user_signing",
}
export interface IEncryptedEventInfo {
/**
* whether the event is encrypted (if not encrypted, some of the other properties may not be set)
*/
encrypted: boolean;
/**
* the sender's key
*/
senderKey: string;
/**
* the algorithm used to encrypt the event
*/
algorithm: string;
/**
* whether we can be sure that the owner of the senderKey sent the event
*/
authenticated: boolean;
/**
* the sender's device information, if available
*/
sender?: DeviceInfo;
/**
* if the event's ed25519 and curve25519 keys don't match (only meaningful if `sender` is set)
*/
mismatchedSender: boolean;
}
export interface IRecoveryKey {
keyInfo: {
pubkey: Uint8Array;
passphrase?: {
algorithm: string;
iterations: number;
salt: string;
};
};
privateKey: Uint8Array;
encodedPrivateKey: string;
}
export interface ICreateSecretStorageOpts {
/**
* Function called to await a secret storage key creation flow.
* Returns:
* {Promise<Object>} Object with public key metadata, encoded private
* recovery key which should be disposed of after displaying to the user,
* and raw private key to avoid round tripping if needed.
*/
createSecretStorageKey?: () => Promise<IRecoveryKey>;
/**
* The current key backup object. If passed,
* the passphrase and recovery key from this backup will be used.
*/
keyBackupInfo?: IKeyBackupVersion;
/**
* If true, a new key backup version will be
* created and the private key stored in the new SSSS store. Ignored if keyBackupInfo
* is supplied.
*/
setupNewKeyBackup?: boolean;
/**
* Reset even if keys already exist.
*/
setupNewSecretStorage?: boolean;
/**
* Function called to get the user's
* current key backup passphrase. Should return a promise that resolves with a Uint8Array
* containing the key, or rejects if the key cannot be obtained.
*/
getKeyBackupPassphrase?: () => Promise<Uint8Array>;
}
export interface ISecretStorageKey {
keyId: string;
keyInfo: ISecretStorageKeyInfo;
}
export interface IAddSecretStorageKeyOpts {
// depends on algorithm
// TODO: Types
}
export interface IImportOpts {
stage: string; // TODO: Enum
successes: number;
failures: number;
total: number;
}
export interface IImportRoomKeysOpts {
progressCallback: (stage: IImportOpts) => void;
untrusted?: boolean;
source?: string; // TODO: Enum
}
+651
View File
@@ -0,0 +1,651 @@
/*
Copyright 2021 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.
*/
/**
* @module crypto/backup
*
* Classes for dealing with key backup.
*/
import { MatrixClient } from "../client";
import { logger } from "../logger";
import { MEGOLM_ALGORITHM, verifySignature } from "./olmlib";
import { DeviceInfo } from "./deviceinfo";
import { DeviceTrustLevel } from './CrossSigning';
import { keyFromPassphrase } from './key_passphrase';
import { sleep } from "../utils";
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { encodeRecoveryKey } from './recoverykey';
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
type AuthData = Record<string, any>;
type BackupInfo = {
algorithm: string,
auth_data: AuthData, // eslint-disable-line camelcase
[properties: string]: any,
};
type SigInfo = {
deviceId: string,
valid?: boolean | null, // true: valid, false: invalid, null: cannot attempt validation
device?: DeviceInfo | null,
crossSigningId?: boolean,
deviceTrust?: DeviceTrustLevel,
};
type TrustInfo = {
usable: boolean, // is the backup trusted, true iff there is a sig that is valid & from a trusted device
sigs: SigInfo[],
};
/** A function used to get the secret key for a backup.
*/
type GetKey = () => Promise<Uint8Array>;
interface BackupAlgorithmClass {
algorithmName: string;
// initialize from an existing backup
init(authData: AuthData, getKey: GetKey): Promise<BackupAlgorithm>;
// prepare a brand new backup
prepare(
key: string | Uint8Array | null,
): Promise<[Uint8Array, AuthData]>;
}
interface BackupAlgorithm {
encryptSession(data: Record<string, any>): Promise<any>;
decryptSessions(ciphertexts: Record<string, any>): Promise<Record<string, any>[]>;
authData: AuthData;
keyMatches(key: Uint8Array): Promise<boolean>;
free(): void;
}
/**
* Manages the key backup.
*/
export class BackupManager {
private algorithm: BackupAlgorithm | undefined;
private backupInfo: BackupInfo | undefined; // The info dict from /room_keys/version
public checkedForBackup: boolean; // Have we checked the server for a backup we can use?
private sendingBackups: boolean; // Are we currently sending backups?
constructor(private readonly baseApis: MatrixClient, public readonly getKey: GetKey) {
this.checkedForBackup = false;
this.sendingBackups = false;
}
public get version(): string | undefined {
return this.backupInfo && this.backupInfo.version;
}
public static async makeAlgorithm(info: BackupInfo, getKey: GetKey): Promise<BackupAlgorithm> {
const Algorithm = algorithmsByName[info.algorithm];
if (!Algorithm) {
throw new Error("Unknown backup algorithm");
}
return await Algorithm.init(info.auth_data, getKey);
}
public async enableKeyBackup(info: BackupInfo): Promise<void> {
this.backupInfo = info;
if (this.algorithm) {
this.algorithm.free();
}
this.algorithm = await BackupManager.makeAlgorithm(info, this.getKey);
this.baseApis.emit('crypto.keyBackupStatus', true);
// There may be keys left over from a partially completed backup, so
// schedule a send to check.
this.scheduleKeyBackupSend();
}
/**
* Disable backing up of keys.
*/
public disableKeyBackup(): void {
if (this.algorithm) {
this.algorithm.free();
}
this.algorithm = undefined;
this.backupInfo = undefined;
this.baseApis.emit('crypto.keyBackupStatus', false);
}
public getKeyBackupEnabled(): boolean | null {
if (!this.checkedForBackup) {
return null;
}
return Boolean(this.algorithm);
}
public async prepareKeyBackupVersion(
key?: string | Uint8Array | null,
algorithm?: string | undefined,
): Promise<BackupInfo> {
const Algorithm = algorithm ? algorithmsByName[algorithm] : DefaultAlgorithm;
if (!Algorithm) {
throw new Error("Unknown backup algorithm");
}
const [privateKey, authData] = await Algorithm.prepare(key);
const recoveryKey = encodeRecoveryKey(privateKey);
return {
algorithm: Algorithm.algorithmName,
auth_data: authData,
recovery_key: recoveryKey,
privateKey,
};
}
public async createKeyBackupVersion(info: BackupInfo): Promise<void> {
this.algorithm = await BackupManager.makeAlgorithm(info, this.getKey);
}
/**
* Check the server for an active key backup and
* if one is present and has a valid signature from
* one of the user's verified devices, start backing up
* to it.
*/
public async checkAndStart(): Promise<{backupInfo: BackupInfo, trustInfo: TrustInfo}> {
logger.log("Checking key backup status...");
if (this.baseApis.isGuest()) {
logger.log("Skipping key backup check since user is guest");
this.checkedForBackup = true;
return null;
}
let backupInfo: BackupInfo;
try {
backupInfo = await this.baseApis.getKeyBackupVersion();
} catch (e) {
logger.log("Error checking for active key backup", e);
if (e.httpStatus === 404) {
// 404 is returned when the key backup does not exist, so that
// counts as successfully checking.
this.checkedForBackup = true;
}
return null;
}
this.checkedForBackup = true;
const trustInfo = await this.isKeyBackupTrusted(backupInfo);
if (trustInfo.usable && !this.backupInfo) {
logger.log(
"Found usable key backup v" + backupInfo.version +
": enabling key backups",
);
await this.enableKeyBackup(backupInfo);
} else if (!trustInfo.usable && this.backupInfo) {
logger.log("No usable key backup: disabling key backup");
this.disableKeyBackup();
} else if (!trustInfo.usable && !this.backupInfo) {
logger.log("No usable key backup: not enabling key backup");
} else if (trustInfo.usable && this.backupInfo) {
// may not be the same version: if not, we should switch
if (backupInfo.version !== this.backupInfo.version) {
logger.log(
"On backup version " + this.backupInfo.version + " but found " +
"version " + backupInfo.version + ": switching.",
);
this.disableKeyBackup();
await this.enableKeyBackup(backupInfo);
// We're now using a new backup, so schedule all the keys we have to be
// uploaded to the new backup. This is a bit of a workaround to upload
// keys to a new backup in *most* cases, but it won't cover all cases
// because we don't remember what backup version we uploaded keys to:
// see https://github.com/vector-im/element-web/issues/14833
await this.scheduleAllGroupSessionsForBackup();
} else {
logger.log("Backup version " + backupInfo.version + " still current");
}
}
return { backupInfo, trustInfo };
}
/**
* Forces a re-check of the key backup and enables/disables it
* as appropriate.
*
* @return {Object} Object with backup info (as returned by
* getKeyBackupVersion) in backupInfo and
* trust information (as returned by isKeyBackupTrusted)
* in trustInfo.
*/
public async checkKeyBackup(): Promise<{backupInfo: BackupInfo, trustInfo: TrustInfo}> {
this.checkedForBackup = false;
return this.checkAndStart();
}
/**
* Check if the given backup info is trusted.
*
* @param {object} backupInfo key backup info dict from /room_keys/version
* @return {object} {
* usable: [bool], // is the backup trusted, true iff there is a sig that is valid & from a trusted device
* sigs: [
* valid: [bool || null], // true: valid, false: invalid, null: cannot attempt validation
* deviceId: [string],
* device: [DeviceInfo || null],
* ]
* }
*/
public async isKeyBackupTrusted(backupInfo: BackupInfo): Promise<TrustInfo> {
const ret = {
usable: false,
trusted_locally: false,
sigs: [],
};
if (
!backupInfo ||
!backupInfo.algorithm ||
!backupInfo.auth_data ||
!backupInfo.auth_data.public_key ||
!backupInfo.auth_data.signatures
) {
logger.info("Key backup is absent or missing required data");
return ret;
}
const trustedPubkey = this.baseApis.crypto._sessionStore.getLocalTrustedBackupPubKey();
if (backupInfo.auth_data.public_key === trustedPubkey) {
logger.info("Backup public key " + trustedPubkey + " is trusted locally");
ret.trusted_locally = true;
}
const mySigs = backupInfo.auth_data.signatures[this.baseApis.getUserId()] || [];
for (const keyId of Object.keys(mySigs)) {
const keyIdParts = keyId.split(':');
if (keyIdParts[0] !== 'ed25519') {
logger.log("Ignoring unknown signature type: " + keyIdParts[0]);
continue;
}
// Could be a cross-signing master key, but just say this is the device
// ID for backwards compat
const sigInfo: SigInfo = { deviceId: keyIdParts[1] };
// first check to see if it's from our cross-signing key
const crossSigningId = this.baseApis.crypto._crossSigningInfo.getId();
if (crossSigningId === sigInfo.deviceId) {
sigInfo.crossSigningId = true;
try {
await verifySignature(
this.baseApis.crypto._olmDevice,
backupInfo.auth_data,
this.baseApis.getUserId(),
sigInfo.deviceId,
crossSigningId,
);
sigInfo.valid = true;
} catch (e) {
logger.warn(
"Bad signature from cross signing key " + crossSigningId, e,
);
sigInfo.valid = false;
}
ret.sigs.push(sigInfo);
continue;
}
// Now look for a sig from a device
// At some point this can probably go away and we'll just support
// it being signed by the cross-signing master key
const device = this.baseApis.crypto._deviceList.getStoredDevice(
this.baseApis.getUserId(), sigInfo.deviceId,
);
if (device) {
sigInfo.device = device;
sigInfo.deviceTrust = await this.baseApis.checkDeviceTrust(
this.baseApis.getUserId(), sigInfo.deviceId,
);
try {
await verifySignature(
this.baseApis.crypto._olmDevice,
backupInfo.auth_data,
this.baseApis.getUserId(),
device.deviceId,
device.getFingerprint(),
);
sigInfo.valid = true;
} catch (e) {
logger.info(
"Bad signature from key ID " + keyId + " userID " + this.baseApis.getUserId() +
" device ID " + device.deviceId + " fingerprint: " +
device.getFingerprint(), backupInfo.auth_data, e,
);
sigInfo.valid = false;
}
} else {
sigInfo.valid = null; // Can't determine validity because we don't have the signing device
logger.info("Ignoring signature from unknown key " + keyId);
}
ret.sigs.push(sigInfo);
}
ret.usable = ret.sigs.some((s) => {
return (
s.valid && (
(s.device && s.deviceTrust.isVerified()) ||
(s.crossSigningId)
)
);
});
ret.usable = ret.usable || ret.trusted_locally;
return ret;
}
/**
* Schedules sending all keys waiting to be sent to the backup, if not already
* scheduled. Retries if necessary.
*
* @param maxDelay Maximum delay to wait in ms. 0 means no delay.
*/
public async scheduleKeyBackupSend(maxDelay = 10000): Promise<void> {
if (this.sendingBackups) return;
this.sendingBackups = true;
try {
// wait between 0 and `maxDelay` seconds, to avoid backup
// requests from different clients hitting the server all at
// the same time when a new key is sent
const delay = Math.random() * maxDelay;
await sleep(delay, undefined);
let numFailures = 0; // number of consecutive failures
for (;;) {
if (!this.algorithm) {
return;
}
try {
const numBackedUp =
await this.backupPendingKeys(KEY_BACKUP_KEYS_PER_REQUEST);
if (numBackedUp === 0) {
// no sessions left needing backup: we're done
return;
}
numFailures = 0;
} catch (err) {
numFailures++;
logger.log("Key backup request failed", err);
if (err.data) {
if (
err.data.errcode == 'M_NOT_FOUND' ||
err.data.errcode == 'M_WRONG_ROOM_KEYS_VERSION'
) {
// Re-check key backup status on error, so we can be
// sure to present the current situation when asked.
await this.checkKeyBackup();
// Backup version has changed or this backup version
// has been deleted
this.baseApis.crypto.emit("crypto.keyBackupFailed", err.data.errcode);
throw err;
}
}
}
if (numFailures) {
// exponential backoff if we have failures
await sleep(1000 * Math.pow(2, Math.min(numFailures - 1, 4)), undefined);
}
}
} finally {
this.sendingBackups = false;
}
}
/**
* Take some e2e keys waiting to be backed up and send them
* to the backup.
*
* @param {integer} limit Maximum number of keys to back up
* @returns {integer} Number of sessions backed up
*/
private async backupPendingKeys(limit: number): Promise<number> {
const sessions = await this.baseApis.crypto._cryptoStore.getSessionsNeedingBackup(limit);
if (!sessions.length) {
return 0;
}
let remaining = await this.baseApis.crypto._cryptoStore.countSessionsNeedingBackup();
this.baseApis.crypto.emit("crypto.keyBackupSessionsRemaining", remaining);
const data = {};
for (const session of sessions) {
const roomId = session.sessionData.room_id;
if (data[roomId] === undefined) {
data[roomId] = { sessions: {} };
}
const sessionData = await this.baseApis.crypto._olmDevice.exportInboundGroupSession(
session.senderKey, session.sessionId, session.sessionData,
);
sessionData.algorithm = MEGOLM_ALGORITHM;
const forwardedCount =
(sessionData.forwarding_curve25519_key_chain || []).length;
const userId = this.baseApis.crypto._deviceList.getUserByIdentityKey(
MEGOLM_ALGORITHM, session.senderKey,
);
const device = this.baseApis.crypto._deviceList.getDeviceByIdentityKey(
MEGOLM_ALGORITHM, session.senderKey,
);
const verified = this.baseApis.crypto._checkDeviceInfoTrust(userId, device).isVerified();
data[roomId]['sessions'][session.sessionId] = {
first_message_index: sessionData.first_known_index,
forwarded_count: forwardedCount,
is_verified: verified,
session_data: await this.algorithm.encryptSession(sessionData),
};
}
await this.baseApis.sendKeyBackup(
undefined, undefined, this.backupInfo.version,
{ rooms: data },
);
await this.baseApis.crypto._cryptoStore.unmarkSessionsNeedingBackup(sessions);
remaining = await this.baseApis.crypto._cryptoStore.countSessionsNeedingBackup();
this.baseApis.crypto.emit("crypto.keyBackupSessionsRemaining", remaining);
return sessions.length;
}
public async backupGroupSession(
senderKey: string, sessionId: string,
): Promise<void> {
await this.baseApis.crypto._cryptoStore.markSessionsNeedingBackup([{
senderKey: senderKey,
sessionId: sessionId,
}]);
if (this.backupInfo) {
// don't wait for this to complete: it will delay so
// happens in the background
this.scheduleKeyBackupSend();
}
// if this.backupInfo is not set, then the keys will be backed up when
// this.enableKeyBackup is called
}
/**
* Marks all group sessions as needing to be backed up and schedules them to
* upload in the background as soon as possible.
*/
public async scheduleAllGroupSessionsForBackup(): Promise<void> {
await this.flagAllGroupSessionsForBackup();
// Schedule keys to upload in the background as soon as possible.
this.scheduleKeyBackupSend(0 /* maxDelay */);
}
/**
* Marks all group sessions as needing to be backed up without scheduling
* them to upload in the background.
* @returns {Promise<int>} Resolves to the number of sessions now requiring a backup
* (which will be equal to the number of sessions in the store).
*/
public async flagAllGroupSessionsForBackup(): Promise<number> {
await this.baseApis.crypto._cryptoStore.doTxn(
'readwrite',
[
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_BACKUP,
],
(txn) => {
this.baseApis.crypto._cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => {
if (session !== null) {
this.baseApis.crypto._cryptoStore.markSessionsNeedingBackup([session], txn);
}
});
},
);
const remaining = await this.baseApis.crypto._cryptoStore.countSessionsNeedingBackup();
this.baseApis.emit("crypto.keyBackupSessionsRemaining", remaining);
return remaining;
}
/**
* Counts the number of end to end session keys that are waiting to be backed up
* @returns {Promise<int>} Resolves to the number of sessions requiring backup
*/
public countSessionsNeedingBackup(): Promise<number> {
return this.baseApis.crypto._cryptoStore.countSessionsNeedingBackup();
}
}
export class Curve25519 implements BackupAlgorithm {
public static algorithmName = "m.megolm_backup.v1.curve25519-aes-sha2";
constructor(
public authData: AuthData,
private publicKey: any, // FIXME: PkEncryption
private getKey: () => Promise<Uint8Array>,
) {}
public static async init(
authData: AuthData,
getKey: () => Promise<Uint8Array>,
): Promise<Curve25519> {
if (!authData || !authData.public_key) {
throw new Error("auth_data missing required information");
}
const publicKey = new global.Olm.PkEncryption();
publicKey.set_recipient_key(authData.public_key);
return new Curve25519(authData, publicKey, getKey);
}
public static async prepare(
key: string | Uint8Array | null,
): Promise<[Uint8Array, AuthData]> {
const decryption = new global.Olm.PkDecryption();
try {
const authData: AuthData = {};
if (!key) {
authData.public_key = decryption.generate_key();
} else if (key instanceof Uint8Array) {
authData.public_key = decryption.init_with_private_key(key);
} else {
const derivation = await keyFromPassphrase(key);
authData.private_key_salt = derivation.salt;
authData.private_key_iterations = derivation.iterations;
authData.public_key = decryption.init_with_private_key(derivation.key);
}
const publicKey = new global.Olm.PkEncryption();
publicKey.set_recipient_key(authData.public_key);
return [
decryption.get_private_key(),
authData,
];
} finally {
decryption.free();
}
}
public async encryptSession(data: Record<string, any>): Promise<any> {
const plainText: Record<string, any> = Object.assign({}, data);
delete plainText.session_id;
delete plainText.room_id;
delete plainText.first_known_index;
return this.publicKey.encrypt(JSON.stringify(plainText));
}
public async decryptSessions(sessions: Record<string, Record<string, any>>): Promise<Record<string, any>[]> {
const privKey = await this.getKey();
const decryption = new global.Olm.PkDecryption();
try {
const backupPubKey = decryption.init_with_private_key(privKey);
if (backupPubKey !== this.authData.public_key) {
// eslint-disable-next-line no-throw-literal
throw { errcode: MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY };
}
const keys = [];
for (const [sessionId, sessionData] of Object.entries(sessions)) {
try {
const decrypted = JSON.parse(decryption.decrypt(
sessionData.session_data.ephemeral,
sessionData.session_data.mac,
sessionData.session_data.ciphertext,
));
decrypted.session_id = sessionId;
keys.push(decrypted);
} catch (e) {
logger.log("Failed to decrypt megolm session from backup", e, sessionData);
}
}
return keys;
} finally {
decryption.free();
}
}
public async keyMatches(key: Uint8Array): Promise<boolean> {
const decryption = new global.Olm.PkDecryption();
let pubKey;
try {
pubKey = decryption.init_with_private_key(key);
} finally {
decryption.free();
}
return pubKey === this.authData.public_key;
}
public free(): void {
this.publicKey.free();
}
}
export const algorithmsByName: Record<string, BackupAlgorithmClass> = {
[Curve25519.algorithmName]: Curve25519,
};
export const DefaultAlgorithm: BackupAlgorithmClass = Curve25519;
+54 -18
View File
@@ -1,5 +1,5 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2020-2021 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.
@@ -14,15 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {decodeBase64, encodeBase64} from './olmlib';
import {IndexedDBCryptoStore} from '../crypto/store/indexeddb-crypto-store';
import {decryptAES, encryptAES} from './aes';
import { decodeBase64, encodeBase64 } from './olmlib';
import { IndexedDBCryptoStore } from '../crypto/store/indexeddb-crypto-store';
import { decryptAES, encryptAES } from './aes';
import anotherjson from "another-json";
import {logger} from '../logger';
import { logger } from '../logger';
import { ISecretStorageKeyInfo } from "../matrix";
// FIXME: these types should eventually go in a different file
type Signatures = Record<string, Record<string, string>>;
export interface IDehydratedDevice {
device_id: string; // eslint-disable-line camelcase
device_data: ISecretStorageKeyInfo & { // eslint-disable-line camelcase
algorithm: string;
account: string; // pickle
};
}
export interface IDehydratedDeviceKeyInfo {
passphrase?: string;
}
interface DeviceKeys {
algorithms: Array<string>;
device_id: string; // eslint-disable-line camelcase
@@ -51,7 +64,7 @@ export class DehydrationManager {
this.getDehydrationKeyFromCache();
}
async getDehydrationKeyFromCache(): Promise<void> {
return this.crypto._cryptoStore.doTxn(
return await this.crypto._cryptoStore.doTxn(
'readonly',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
@@ -59,7 +72,7 @@ export class DehydrationManager {
txn,
async (result) => {
if (result) {
const {key, keyInfo, deviceDisplayName, time} = result;
const { key, keyInfo, deviceDisplayName, time } = result;
const pickleKey = Buffer.from(this.crypto._olmDevice._pickleKey);
const decrypted = await decryptAES(key, pickleKey, DEHYDRATION_ALGORITHM);
this.key = decodeBase64(decrypted);
@@ -77,10 +90,23 @@ export class DehydrationManager {
},
);
}
async setDehydrationKey(
/** set the key, and queue periodic dehydration to the server in the background */
async setKeyAndQueueDehydration(
key: Uint8Array, keyInfo: {[props: string]: any} = {},
deviceDisplayName: string = undefined,
): Promise<void> {
const matches = await this.setKey(key, keyInfo, deviceDisplayName);
if (!matches) {
// start dehydration in the background
this.dehydrateDevice();
}
}
async setKey(
key: Uint8Array, keyInfo: {[props: string]: any} = {},
deviceDisplayName: string = undefined,
): Promise<boolean> {
if (!key) {
// unsetting the key -- cancel any pending dehydration task
if (this.timeoutId) {
@@ -88,7 +114,7 @@ export class DehydrationManager {
this.timeoutId = undefined;
}
// clear storage
this.crypto._cryptoStore.doTxn(
await this.crypto._cryptoStore.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
@@ -104,7 +130,7 @@ export class DehydrationManager {
// Check to see if it's the same key as before. If it's different,
// dehydrate a new device. If it's the same, we can keep the same
// device. (Assume that keyInfo and deviceDisplayNamme will be the
// device. (Assume that keyInfo and deviceDisplayName will be the
// same if the key is the same.)
let matches: boolean = this.key && key.length == this.key.length;
for (let i = 0; matches && i < key.length; i++) {
@@ -116,11 +142,12 @@ export class DehydrationManager {
this.key = key;
this.keyInfo = keyInfo;
this.deviceDisplayName = deviceDisplayName;
// start dehydration in the background
this.dehydrateDevice();
}
return matches;
}
private async dehydrateDevice(): Promise<void> {
/** returns the device id of the newly created dehydrated device */
async dehydrateDevice(): Promise<string> {
if (this.inProgress) {
logger.log("Dehydration already in progress -- not starting new dehydration");
return;
@@ -135,7 +162,7 @@ export class DehydrationManager {
// update the crypto store with the timestamp
const key = await encryptAES(encodeBase64(this.key), pickleKey, DEHYDRATION_ALGORITHM);
this.crypto._cryptoStore.doTxn(
await this.crypto._cryptoStore.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
@@ -178,7 +205,7 @@ export class DehydrationManager {
}
logger.log("Uploading account to server");
const dehydrateResult = await this.crypto._baseApis._http.authedRequest(
const dehydrateResult = await this.crypto._baseApis.http.authedRequest(
undefined,
"PUT",
"/dehydrated_device",
@@ -217,7 +244,7 @@ export class DehydrationManager {
logger.log("Preparing one-time keys");
const oneTimeKeys = {};
for (const [keyId, key] of Object.entries(otks.curve25519)) {
const k: OneTimeKey = {key};
const k: OneTimeKey = { key };
const signature = account.sign(anotherjson.stringify(k));
k.signatures = {
[this.crypto._userId]: {
@@ -230,7 +257,7 @@ export class DehydrationManager {
logger.log("Preparing fallback keys");
const fallbackKeys = {};
for (const [keyId, key] of Object.entries(fallbacks.curve25519)) {
const k: OneTimeKey = {key, fallback: true};
const k: OneTimeKey = { key, fallback: true };
const signature = account.sign(anotherjson.stringify(k));
k.signatures = {
[this.crypto._userId]: {
@@ -241,7 +268,7 @@ export class DehydrationManager {
}
logger.log("Uploading keys to server");
await this.crypto._baseApis._http.authedRequest(
await this.crypto._baseApis.http.authedRequest(
undefined,
"POST",
"/keys/upload/" + encodeURI(deviceId),
@@ -258,8 +285,17 @@ export class DehydrationManager {
this.timeoutId = global.setTimeout(
this.dehydrateDevice.bind(this), oneweek,
);
return deviceId;
} finally {
this.inProgress = false;
}
}
private stop() {
if (this.timeoutId) {
global.clearTimeout(this.timeoutId);
this.timeoutId = undefined;
}
}
}
+173 -489
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {randomString} from '../randomstring';
import { randomString } from '../randomstring';
const DEFAULT_ITERATIONS = 500000;
@@ -63,7 +63,7 @@ export async function deriveKey(password, salt, iterations, numBits = DEFAULT_BI
const key = await subtleCrypto.importKey(
'raw',
new TextEncoder().encode(password),
{name: 'PBKDF2'},
{ name: 'PBKDF2' },
false,
['deriveBits'],
);
+70
View File
@@ -0,0 +1,70 @@
/*
Copyright 2021 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 { ISignatures } from "../@types/signed";
import { DeviceInfo } from "./deviceinfo";
export interface IKeyBackupSession {
first_message_index: number; // eslint-disable-line camelcase
forwarded_count: number; // eslint-disable-line camelcase
is_verified: boolean; // eslint-disable-line camelcase
session_data: { // eslint-disable-line camelcase
ciphertext: string;
ephemeral: string;
mac: string;
};
}
export interface IKeyBackupRoomSessions {
[sessionId: string]: IKeyBackupSession;
}
export interface IKeyBackupVersion {
algorithm: string;
auth_data: { // eslint-disable-line camelcase
public_key: string; // eslint-disable-line camelcase
signatures: ISignatures;
};
count: number;
etag: string;
version: string; // number contained within
}
// TODO: Verify types
export interface IKeyBackupTrustInfo {
/**
* is the backup trusted, true if there is a sig that is valid & from a trusted device
*/
usable: boolean[];
sigs: {
valid: boolean[];
device: DeviceInfo[];
}[];
}
export interface IKeyBackupPrepareOpts {
secureSecretStorage: boolean;
}
export interface IKeyBackupRestoreResult {
total: number;
imported: number;
}
export interface IKeyBackupRestoreOpts {
cacheCompleteCallback?: () => void;
progressCallback?: ({ stage: string }) => void;
}
+64 -44
View File
@@ -22,7 +22,7 @@ limitations under the License.
* Utilities common to olm encryption algorithms
*/
import {logger} from '../logger';
import { logger } from '../logger';
import * as utils from "../utils";
import anotherjson from "another-json";
@@ -41,7 +41,6 @@ export const MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
*/
export const MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2";
/**
* Encrypt an event payload for an Olm device
*
@@ -119,7 +118,7 @@ export async function encryptMessageForDevice(
*
* @param {module:crypto/OlmDevice} olmDevice
*
* @param {module:base-apis~MatrixBaseApis} baseApis
* @param {MatrixClient} baseApis
*
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* map from userid to list of devices to ensure sessions for
@@ -169,7 +168,7 @@ export async function getExistingOlmSessions(
*
* @param {module:crypto/OlmDevice} olmDevice
*
* @param {module:base-apis~MatrixBaseApis} baseApis
* @param {MatrixClient} baseApis
*
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* map from userid to list of devices to ensure sessions for
@@ -183,18 +182,24 @@ export async function getExistingOlmSessions(
* @param {Array} [failedServers] An array to fill with remote servers that
* failed to respond to one-time-key requests.
*
* @param {Logger} [log] A possibly customised log
*
* @return {Promise} resolves once the sessions are complete, to
* an Object mapping from userId to deviceId to
* {@link module:crypto~OlmSessionResult}
*/
export async function ensureOlmSessionsForDevices(
olmDevice, baseApis, devicesByUser, force, otkTimeout, failedServers,
olmDevice, baseApis, devicesByUser, force, otkTimeout, failedServers, log,
) {
if (typeof force === "number") {
log = failedServers;
failedServers = otkTimeout;
otkTimeout = force;
force = false;
}
if (!log) {
log = logger;
}
const devicesWithoutSession = [
// [userId, deviceId], ...
@@ -202,6 +207,35 @@ export async function ensureOlmSessionsForDevices(
const result = {};
const resolveSession = {};
// Mark all sessions this task intends to update as in progress. It is
// important to do this for all devices this task cares about in a single
// synchronous operation, as otherwise it is possible to have deadlocks
// where multiple tasks wait indefinitely on another task to update some set
// of common devices.
for (const [, devices] of Object.entries(devicesByUser)) {
for (const deviceInfo of devices) {
const key = deviceInfo.getIdentityKey();
if (key === olmDevice.deviceCurve25519Key) {
// We don't start sessions with ourself, so there's no need to
// mark it in progress.
continue;
}
if (!olmDevice._sessionsInProgress[key]) {
// pre-emptively mark the session as in-progress to avoid race
// conditions. If we find that we already have a session, then
// we'll resolve
olmDevice._sessionsInProgress[key] = new Promise(resolve => {
resolveSession[key] = (...args) => {
delete olmDevice._sessionsInProgress[key];
resolve(...args);
};
});
}
}
}
for (const [userId, devices] of Object.entries(devicesByUser)) {
result[userId] = {};
for (const deviceInfo of devices) {
@@ -216,7 +250,7 @@ export async function ensureOlmSessionsForDevices(
// new chain when this side has an active sender chain.
// If you see this message being logged in the wild, we should find
// the thing that is trying to send Olm messages to itself and fix it.
logger.info("Attempted to start session with ourself! Ignoring");
log.info("Attempted to start session with ourself! Ignoring");
// We must fill in the section in the return value though, as callers
// expect it to be there.
result[userId][deviceId] = {
@@ -226,41 +260,21 @@ export async function ensureOlmSessionsForDevices(
continue;
}
if (!olmDevice._sessionsInProgress[key]) {
// pre-emptively mark the session as in-progress to avoid race
// conditions. If we find that we already have a session, then
// we'll resolve
olmDevice._sessionsInProgress[key] = new Promise(
(resolve, reject) => {
resolveSession[key] = {
resolve: (...args) => {
delete olmDevice._sessionsInProgress[key];
resolve(...args);
},
reject: (...args) => {
delete olmDevice._sessionsInProgress[key];
reject(...args);
},
};
},
);
}
const forWhom = `for ${key} (${userId}:${deviceId})`;
const sessionId = await olmDevice.getSessionIdForDevice(
key, resolveSession[key],
key, resolveSession[key], log,
);
if (sessionId !== null && resolveSession[key]) {
// we found a session, but we had marked the session as
// in-progress, so unmark it and unblock anything that was
// waiting
delete olmDevice._sessionsInProgress[key];
resolveSession[key].resolve();
delete resolveSession[key];
// in-progress, so resolve it now, which will unmark it and
// unblock anything that was waiting
resolveSession[key]();
}
if (sessionId === null || force) {
if (force) {
logger.info("Forcing new Olm session for " + userId + ":" + deviceId);
log.info(`Forcing new Olm session ${forWhom}`);
} else {
logger.info("Making new Olm session for " + userId + ":" + deviceId);
log.info(`Making new Olm session ${forWhom}`);
}
devicesWithoutSession.push([userId, deviceId]);
}
@@ -277,15 +291,18 @@ export async function ensureOlmSessionsForDevices(
const oneTimeKeyAlgorithm = "signed_curve25519";
let res;
let taskDetail = `one-time keys for ${devicesWithoutSession.length} devices`;
try {
log.debug(`Claiming ${taskDetail}`);
res = await baseApis.claimOneTimeKeys(
devicesWithoutSession, oneTimeKeyAlgorithm, otkTimeout,
);
log.debug(`Claimed ${taskDetail}`);
} catch (e) {
for (const resolver of Object.values(resolveSession)) {
resolver.resolve();
resolver();
}
logger.log("failed to claim one-time keys", e, devicesWithoutSession);
log.log(`Failed to claim ${taskDetail}`, e, devicesWithoutSession);
throw e;
}
@@ -293,10 +310,10 @@ export async function ensureOlmSessionsForDevices(
failedServers.push(...Object.keys(res.failures));
}
const otk_res = res.one_time_keys || {};
const otkResult = res.one_time_keys || {};
const promises = [];
for (const [userId, devices] of Object.entries(devicesByUser)) {
const userRes = otk_res[userId] || {};
const userRes = otkResult[userId] || {};
for (let j = 0; j < devices.length; j++) {
const deviceInfo = devices[j];
const deviceId = deviceInfo.deviceId;
@@ -323,11 +340,12 @@ export async function ensureOlmSessionsForDevices(
}
if (!oneTimeKey) {
const msg = "No one-time keys (alg=" + oneTimeKeyAlgorithm +
") for device " + userId + ":" + deviceId;
logger.warn(msg);
log.warn(
`No one-time keys (alg=${oneTimeKeyAlgorithm}) ` +
`for device ${userId}:${deviceId}`,
);
if (resolveSession[key]) {
resolveSession[key].resolve();
resolveSession[key]();
}
continue;
}
@@ -337,12 +355,12 @@ export async function ensureOlmSessionsForDevices(
olmDevice, oneTimeKey, userId, deviceInfo,
).then((sid) => {
if (resolveSession[key]) {
resolveSession[key].resolve(sid);
resolveSession[key](sid);
}
result[userId][deviceId].sessionId = sid;
}, (e) => {
if (resolveSession[key]) {
resolveSession[key].resolve();
resolveSession[key]();
}
throw e;
}),
@@ -350,7 +368,10 @@ export async function ensureOlmSessionsForDevices(
}
}
taskDetail = `Olm sessions for ${promises.length} devices`;
log.debug(`Starting ${taskDetail}`);
await Promise.all(promises);
log.debug(`Started ${taskDetail}`);
return result;
}
@@ -386,7 +407,6 @@ async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceIn
return sid;
}
/**
* Verify the signature on an object
*
@@ -16,10 +16,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {logger} from '../../logger';
import { logger } from '../../logger';
import * as utils from "../../utils";
export const VERSION = 9;
export const VERSION = 10;
const PROFILE_TRANSACTIONS = false;
/**
* Implementation of a CryptoStore which is backed by an existing
@@ -34,6 +35,7 @@ export class Backend {
*/
constructor(db) {
this._db = db;
this._nextTxnId = 0;
// make sure we close the db on `onversionchange` - otherwise
// attempts to delete the database will block (and subsequent
@@ -228,7 +230,7 @@ export class Backend {
const cursor = ev.target.result;
if (cursor) {
const keyReq = cursor.value;
if (keyReq.recipients.includes({userId, deviceId})) {
if (keyReq.recipients.includes({ userId, deviceId })) {
results.push(keyReq);
}
cursor.continue();
@@ -494,7 +496,7 @@ export class Backend {
const lastProblem = problems[problems.length - 1];
for (const problem of problems) {
if (problem.time > timestamp) {
result = Object.assign({}, problem, {fixed: lastProblem.fixed});
result = Object.assign({}, problem, { fixed: lastProblem.fixed });
return;
}
}
@@ -517,11 +519,11 @@ export class Backend {
await Promise.all(devices.map((device) => {
return new Promise((resolve) => {
const {userId, deviceInfo} = device;
const { userId, deviceInfo } = device;
const getReq = objectStore.get([userId, deviceInfo.deviceId]);
getReq.onsuccess = function() {
if (!getReq.result) {
objectStore.put({userId, deviceId: deviceInfo.deviceId});
objectStore.put({ userId, deviceId: deviceInfo.deviceId });
ret.push(device);
}
resolve();
@@ -757,10 +759,59 @@ export class Backend {
}));
}
doTxn(mode, stores, func) {
addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) {
if (!txn) {
txn = this._db.transaction(
"shared_history_inbound_group_sessions", "readwrite",
);
}
const objectStore = txn.objectStore("shared_history_inbound_group_sessions");
const req = objectStore.get([roomId]);
req.onsuccess = () => {
const { sessions } = req.result || { sessions: [] };
sessions.push([senderKey, sessionId]);
objectStore.put({ roomId, sessions });
};
}
getSharedHistoryInboundGroupSessions(roomId, txn) {
if (!txn) {
txn = this._db.transaction(
"shared_history_inbound_group_sessions", "readonly",
);
}
const objectStore = txn.objectStore("shared_history_inbound_group_sessions");
const req = objectStore.get([roomId]);
return new Promise((resolve, reject) => {
req.onsuccess = () => {
const { sessions } = req.result || { sessions: [] };
resolve(sessions);
};
req.onerror = reject;
});
}
doTxn(mode, stores, func, log = logger) {
let startTime;
let description;
if (PROFILE_TRANSACTIONS) {
const txnId = this._nextTxnId++;
startTime = Date.now();
description = `${mode} crypto store transaction ${txnId} in ${stores}`;
log.debug(`Starting ${description}`);
}
const txn = this._db.transaction(stores, mode);
const promise = promiseifyTxn(txn);
const result = func(txn);
if (PROFILE_TRANSACTIONS) {
promise.then(() => {
const elapsedTime = Date.now() - startTime;
log.debug(`Finished ${description}, took ${elapsedTime} ms`);
}, () => {
const elapsedTime = Date.now() - startTime;
log.error(`Failed ${description}, took ${elapsedTime} ms`);
});
}
return promise.then(() => {
return result;
});
@@ -815,6 +866,11 @@ export function upgradeDatabase(db, oldVersion) {
keyPath: ["userId", "deviceId"],
});
}
if (oldVersion < 10) {
db.createObjectStore("shared_history_inbound_group_sessions", {
keyPath: ["roomId"],
});
}
// Expand as needed.
}
+32 -6
View File
@@ -16,11 +16,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {logger} from '../../logger';
import {LocalStorageCryptoStore} from './localStorage-crypto-store';
import {MemoryCryptoStore} from './memory-crypto-store';
import { logger } from '../../logger';
import { LocalStorageCryptoStore } from './localStorage-crypto-store';
import { MemoryCryptoStore } from './memory-crypto-store';
import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend';
import {InvalidCryptoStoreError} from '../../errors';
import { InvalidCryptoStoreError } from '../../errors';
import * as IndexedDBHelpers from "../../indexeddb-helpers";
/**
@@ -582,6 +582,29 @@ export class IndexedDBCryptoStore {
return this._backend.markSessionsNeedingBackup(sessions, txn);
}
/**
* Add a shared-history group session for a room.
* @param {string} roomId The room that the key belongs to
* @param {string} senderKey The sender's curve 25519 key
* @param {string} sessionId The ID of the session
* @param {*} txn An active transaction. See doTxn(). (optional)
*/
addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn) {
this._backend.addSharedHistoryInboundGroupSession(
roomId, senderKey, sessionId, txn,
);
}
/**
* Get the shared-history group session for a room.
* @param {string} roomId The room that the key belongs to
* @param {*} txn An active transaction. See doTxn(). (optional)
* @returns {Promise} Resolves to an array of [senderKey, sessionId]
*/
getSharedHistoryInboundGroupSessions(roomId, txn) {
return this._backend.getSharedHistoryInboundGroupSessions(roomId, txn);
}
/**
* Perform a transaction on the crypto store. Any store methods
* that require a transaction (txn) object to be passed in may
@@ -596,6 +619,7 @@ export class IndexedDBCryptoStore {
* @param {function(*)} func Function called with the
* transaction object: an opaque object that should be passed
* to store functions.
* @param {Logger} [log] A possibly customised log
* @return {Promise} Promise that resolves with the result of the `func`
* when the transaction is complete. If the backend is
* async (ie. the indexeddb backend) any of the callback
@@ -603,8 +627,8 @@ export class IndexedDBCryptoStore {
* reject with that exception. On synchronous backends, the
* exception will propagate to the caller of the getFoo method.
*/
doTxn(mode, stores, func) {
return this._backend.doTxn(mode, stores, func);
doTxn(mode, stores, func, log) {
return this._backend.doTxn(mode, stores, func, log);
}
}
@@ -613,6 +637,8 @@ IndexedDBCryptoStore.STORE_SESSIONS = 'sessions';
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD
= 'inbound_group_sessions_withheld';
IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS
= 'shared_history_inbound_group_sessions';
IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data';
IndexedDBCryptoStore.STORE_ROOMS = 'rooms';
IndexedDBCryptoStore.STORE_BACKUP = 'sessions_needing_backup';

Some files were not shown because too many files have changed in this diff Show More