Compare commits

...

614 Commits

Author SHA1 Message Date
RiotRobot 0d59963b53 v2.4.6 2019-12-09 11:23:37 +00:00
RiotRobot c669aafedb Prepare changelog for v2.4.6 2019-12-09 11:23:37 +00:00
RiotRobot 358f13500b v2.4.6-rc.1 2019-12-04 11:57:08 +00:00
RiotRobot 016f16954a Prepare changelog for v2.4.6-rc.1 2019-12-04 11:57:07 +00:00
Travis Ralston c1543545d2 Merge pull request #1102 from matrix-org/travis/aliases
Update alias handling
2019-12-03 13:54:27 -07:00
Travis Ralston 5da936d96a Fix tests 2019-12-03 13:38:40 -07:00
Travis Ralston 0dead73837 Update alias handling
Fixes https://github.com/vector-im/riot-web/issues/11551
2019-12-03 12:29:50 -07:00
Hubert Chathi 92ca2386ea Merge pull request #1096 from uhoreg/fix_sas_unit_test
increase timeout on flush to fix failing unit test
2019-11-28 17:19:41 -05:00
Hubert Chathi 59b25d6837 increase timeout on flush to fix failing unit test
also remove unused requests
2019-11-28 16:53:21 -05:00
Travis Ralston a6f7936311 Merge pull request #1095 from matrix-org/travis/fix-tests-18
Disable broken cross-signing test
2019-11-28 09:11:05 -07:00
Travis Ralston 7392b4de17 xit instead of comment 2019-11-27 19:23:59 -07:00
Travis Ralston 79b0a5fada Add issue to comment 2019-11-27 19:21:57 -07:00
Travis Ralston aee9442e52 Disable broken cross-signing test
I don't know why it's broken, but the two errors I can get out of it are "unknown device for verification" and "user_signing key does not match". Someone who knows a bit more about cross-signing will probably need to take a look at this one.

Fixes https://github.com/vector-im/riot-web/issues/11520 (technically)
Opened https://github.com/vector-im/riot-web/issues/11545 to fix this correctly.
2019-11-27 19:20:09 -07:00
Travis Ralston 569d5d1fce Merge pull request #1094 from matrix-org/travis/fix-tests-13
Fix a couple SAS tests
2019-11-27 09:24:27 -07:00
RiotRobot 6ea8003df2 Merge branch 'master' into develop 2019-11-27 10:28:14 +00:00
RiotRobot c8ab82010a v2.4.5 2019-11-27 10:17:54 +00:00
RiotRobot bf1bec9c6c Prepare changelog for v2.4.5 2019-11-27 10:17:54 +00:00
Travis Ralston e0c90ec9e3 Fix test flakes in SAS verification with old MAC
This has similar fixes to 7ad5021147

Part of https://github.com/vector-im/riot-web/issues/11520
2019-11-26 23:29:06 -07:00
Travis Ralston 7ad5021147 Fix SAS verification test
There's 3 things going on in this commit:
1. `this` is maintained in the tests. Some binds are added instead of the `.call(this, ...)` syntax.
2. We use the right `origSendToDevice`
3. We ensure `downloadKeys` is actually on the client

The combination of these 3 fixes makes the test pass.

Part of https://github.com/vector-im/riot-web/issues/11520
2019-11-26 23:24:37 -07:00
Matthew Hodgson fd73c3fb3a fix bogus logline 2019-11-27 01:19:17 +00:00
Travis Ralston e3dbf7cc41 Merge pull request #1093 from matrix-org/travis/fix-tests-3
Fix Olm unwedging test
2019-11-26 15:32:50 -07:00
Travis Ralston 18749c580e Fix Olm unwedging test
Deep within the crypto layers we call `getId()`, and when we don't have that function the async call on the emitter fails but doesn't fail the test. This manifests as a timeout because the code path that would call the thing blew up.
2019-11-26 15:06:56 -07:00
J. Ryan Stinnett 396db30fbf Update tests 2019-11-26 12:04:14 -07:00
J. Ryan Stinnett 6b38868de6 Relax identity server discovery checks to FAIL_PROMPT
As discussed in MSC2284, this relaxes the identity server discovery to a
`FAIL_PROMPT` state so that clients can choose to warn and continue.

Part of https://github.com/vector-im/riot-web/issues/11102
Implements https://github.com/matrix-org/matrix-doc/pull/2284
2019-11-26 12:04:07 -07:00
Matthew Hodgson 01a46ad880 log outbound to_device msgs for tracking keyshares 2019-11-26 12:03:34 -07:00
Matthew Hodgson 46f8251e94 s/console/logger/ as per review 2019-11-26 12:03:28 -07:00
Matthew Hodgson 77f882f45a log keyshare ID 2019-11-26 12:03:24 -07:00
Matthew Hodgson 8c72fd104e lint 2019-11-26 12:03:20 -07:00
Matthew Hodgson 549656884b expand e2ee logging to better debug UISIs 2019-11-26 12:03:11 -07:00
Matthew Hodgson 5b8b0a8aa3 log outbound to_device msgs for tracking keyshares 2019-11-26 18:35:25 +00:00
Travis Ralston b1924d4db6 Merge pull request #1089 from matrix-org/travis/upgrade-notifications
Fix empty string handling in push notifications
2019-11-26 11:00:24 -07:00
Travis Ralston 1b877118ef Only do one type check 2019-11-26 10:31:04 -07:00
Travis Ralston 682a5daf1c Merge branch 'develop' into travis/upgrade-notifications 2019-11-26 10:29:40 -07:00
Matthew Hodgson fcbfaac1fd Merge pull request #1090 from matrix-org/matthew/more_e2ee_logging
expand e2ee logging to better debug UISIs
2019-11-26 10:18:28 +00:00
Matthew Hodgson 3787b6f1c7 s/console/logger/ as per review 2019-11-26 09:07:23 +00:00
Matthew Hodgson 6e08835496 log keyshare ID 2019-11-26 01:58:04 +00:00
Matthew Hodgson 191695da5a lint 2019-11-26 01:41:59 +00:00
Matthew Hodgson 2215087f96 expand e2ee logging to better debug UISIs 2019-11-26 01:17:12 +00:00
Michael Telatynski 32234ee7fc Merge pull request #1087 from matrix-org/t3chguy/remove_bluebird_11
Remove Bluebird: phase 2
2019-11-26 00:03:22 +00:00
Travis Ralston aa37f697bf Fix empty string handling in push notifications
Fixes https://github.com/vector-im/riot-web/issues/11460

Empty strings are falsey, and the state key match for a tombstone event is an empty string. Ergo, nothing happens because all the conditions fail.
2019-11-25 16:35:27 -07:00
Michael Telatynski 057303d57c s/beforeEach/beforeAll/ for Olm.init() and cleanup sas.spec.js 2019-11-25 13:26:10 +00:00
RiotRobot ccc85d98e2 Merge branch 'master' into develop 2019-11-25 13:24:08 +00:00
RiotRobot c30a8b5a29 v2.4.4 2019-11-25 13:10:04 +00:00
RiotRobot 295010893d Prepare changelog for v2.4.4 2019-11-25 13:10:04 +00:00
Michael Telatynski 7fb807919c Stop using bluebird .returns and .spread 2019-11-25 12:31:46 +00:00
Michael Telatynski bd8f8ef28d Replace yet more deferreds 2019-11-25 11:28:09 +00:00
Michael Telatynski 3901a381cc replace another couple of deferreds 2019-11-25 11:18:32 +00:00
J. Ryan Stinnett 12f6e51ef6 Merge pull request #1062 from matrix-org/jryans/identity-disco-opt
Relax identity server discovery checks to FAIL_PROMPT
2019-11-25 10:48:53 +00:00
Travis Ralston aa8454e30a Merge pull request #1061 from beaclnd92/develop
Fix incorrect return value of MatrixClient.prototype.uploadKeys
2019-11-24 22:19:09 -07:00
Travis Ralston 6b70230e0d Merge branch 'develop' into develop2-test 2019-11-24 22:13:53 -07:00
Michael Telatynski 5e0ba9971c nothing works anymore :(( 2019-11-23 12:18:39 +00:00
David Baker fa577c9475 Merge pull request #1086 from matrix-org/dbkr/fix_calls_in_e2e_rooms
Fix calls in e2e rooms
2019-11-22 17:48:33 +00:00
Bruno Windels 11a958b8ca Merge pull request #1085 from matrix-org/bwindels/verif-toasts
Monitor verification request over DM as well
2019-11-22 16:39:07 +00:00
Bruno Windels 6952db6762 no need to filter here anymore when listening for timeline, also remove obsolete docs 2019-11-22 17:32:37 +01:00
Bruno Windels 51898cffe8 add comments for timeout constants 2019-11-22 17:31:48 +01:00
David Baker d8337d703d Use the right variable name 2019-11-22 15:59:36 +00:00
David Baker adac0c353c Fix calls in e2e rooms
Events will be decrypted after the sync event, so we were having
to wait until the next sync event before they got processed.
2019-11-22 15:56:06 +00:00
Michael Telatynski 04fca16420 Stop using Bluebird promise::value 2019-11-22 15:36:42 +00:00
Bruno Windels ca89b6e7a8 use adapter for to_device requests to have same api as for verif over DM
Riot doesn't fully implement to_device verifications, e.g.
it doesn't send a `request` but immediately sends a `start` event.

Because of this, `crypto.verification.request` doesn't get fired,
as that code path doesn't get triggered. This is why MatrixChat
in the react-sdk was listening for `crypto.verification.start`.

Verification over DM *does* send a `request` event first, so
to have the same API for both methods, we fake the request and
wrap the verifier in it.
2019-11-22 16:12:19 +01:00
Bruno Windels ac1173c628 also emit crypto.verification.request for verification over DM 2019-11-22 16:11:49 +01:00
Michael Telatynski 0a0ae111f6 replace Bluebird::map 2019-11-22 15:03:03 +00:00
David Baker 71a6e015f4 Merge pull request #1084 from matrix-org/dbkr/remove_check
Remove 'check' npm script
2019-11-22 10:53:48 +00:00
David Baker e8bbb8a1cc Remove 'check' npm script
...whose only purpose was to run the tests without coverage because
the coverage tool was awful and ruined all the line numbers (moreso).
2019-11-21 19:30:46 +00:00
David Baker 04764998cb Merge pull request #1083 from matrix-org/dbkr/advanced_anti_chirp
Always process call events in batches
2019-11-21 19:22:24 +00:00
David Baker 5262d716e4 Lint 2019-11-21 19:10:27 +00:00
David Baker 7addacba38 Always process call events in batches
We had a bunch of logic in place to suppress calls if the answer
or hangup had already arrived, but we only used it on startup.
This extends this logic to happen all the time, which means we'll
also do the same suppression if a call happenned while we were
offline.
2019-11-21 18:56:37 +00:00
David Baker 8f8c9c8ec0 Merge pull request #1082 from matrix-org/dbkr/death_to_the_reload_chirp
Fix ringing chirp on loading
2019-11-21 18:20:20 +00:00
David Baker 3a9832a8c6 Fix ringing chirp on loading
We have a heap of logic to do the right thing when a call event
arrives, eg. wait until the client is ready so we can see if there's
already been a hangup event before saying there's an incoming call.

Unfortunately it only waited until the client was prepared, not
until it was syncing, so any events that arrived from the server
in the catchup sync bypassed this logic altogether.

This was probably broken back when we introduced the sync accumulator,
since before then, "PREPARED" meant, "done initialsync" rather than
"loaded fake initialsync from storage".

Fixes https://github.com/vector-im/riot-web/issues/3572
2019-11-21 17:56:04 +00:00
Bruno Windels 4a40c10d4c add helper method on event for current age according to local clock 2019-11-21 17:13:43 +01:00
Michael Telatynski 58f8ca7d66 Merge pull request #1081 from matrix-org/t3chguy/remove_bluebird_2
Remove *most* bluebird specific things
2019-11-21 11:34:19 +00:00
Michael Telatynski 4d950fec66 fixxy
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:59:18 +00:00
Michael Telatynski b4f68f4fc6 Stop using Bluebird promise::nodeify
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:43:58 +00:00
Michael Telatynski ac742aad70 use Bluebird in promise utils sleep so it has .done and .nodeify
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:14:06 +00:00
Michael Telatynski 53d225a1d1 fix stub timelineSet for timeline-window.spec
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:09:36 +00:00
Michael Telatynski 549b0f9313 Stop using Bluebird.delay and Bluebird promise::delay
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:05:59 +00:00
Michael Telatynski 2ce106382a Stop using Bluebird promise::isFulfilled()
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 10:01:16 +00:00
Michael Telatynski b44f43e5db fix import in relations.js
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-21 09:45:00 +00:00
Michael Telatynski 2321b9a04e Merge pull request #1080 from matrix-org/t3chguy/jest
Switch to Jest
2019-11-20 22:03:25 +00:00
Michael Telatynski 3bd518cf7f update buildkite pipeline name
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 21:59:50 +00:00
Michael Telatynski c57109c2f3 tidy up
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 21:16:11 +00:00
Michael Telatynski 522640edd9 rip out lolex also
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 21:05:21 +00:00
Michael Telatynski 5fc0629201 fix expect calls
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 20:19:54 +00:00
Michael Telatynski 26edd7431a fix yarn scripts test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 19:56:50 +00:00
Michael Telatynski fd58957b06 migrate to jest from mocha+expect+istanbul
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-11-20 19:52:50 +00:00
RiotRobot 12bb0b86dd v2.4.4-rc.1 2019-11-20 18:19:30 +00:00
RiotRobot 165c1fc0b6 Prepare changelog for v2.4.4-rc.1 2019-11-20 18:19:30 +00:00
Bruno Windels c785b10603 Merge pull request #1077 from matrix-org/bwindels/dm-verif-in-e2ee-rooms
Fix SAS verification in encrypted DMs
2019-11-19 14:32:01 +00:00
Bruno Windels 90512bdd5f also listen for non-encrypted events when verifying over DM 2019-11-19 15:23:05 +01:00
J. Ryan Stinnett 10751e9a6d Merge pull request #1078 from matrix-org/jryans/4s-new-key-backup
Cross-signing / secret storage tweaks
2019-11-19 12:54:38 +00:00
J. Ryan Stinnett d2ebc58c3c Use secret storage alg const in tests 2019-11-19 11:46:08 +00:00
J. Ryan Stinnett d51c5a2d68 Rename secret storage file to match the default class 2019-11-19 11:21:20 +00:00
J. Ryan Stinnett 1f24845431 Standardise naming of key ID variables in secret storage
Keys in secret storage have both an ID and an optional name, but most `keyName`
variables were actually storing the ID value. This renames and standardises to
avoid confusion.
2019-11-19 10:48:47 +00:00
Bruno Windels 3b02b62ba5 add m.relates_to back to the content on the requesting side for e2e room
as it needs to be added to the commitment hash
as before, getContent() in an e2ee room doesn't return the relation
2019-11-18 18:34:05 +01:00
Bruno Windels 24ae787736 make it explicit that the transaction id is added for the start event
as it should included in the commitment hash
2019-11-18 18:33:11 +01:00
Bruno Windels cd735ef459 use getRelation as getContent()[m.relates_to] doesn't work in e2ee rooms 2019-11-18 18:31:39 +01:00
Bruno Windels 180fea8ace only send decrypted events to Verifier in e2ee rooms 2019-11-18 18:30:43 +01:00
J. Ryan Stinnett 5f02c4b5ad Namespace default secret storage key methods 2019-11-18 15:19:18 +00:00
David Baker 41680f6089 Merge pull request #1075 from matrix-org/dbkr/fix_key_backup_local_trust
Fix local trust for key backups
2019-11-18 14:57:35 +00:00
J. Ryan Stinnett 730f7d3dff Extract secret storage algorithm to constant 2019-11-18 14:38:02 +00:00
David Baker d32033f105 Merge remote-tracking branch 'origin/develop' into dbkr/fix_key_backup_local_trust 2019-11-18 14:10:04 +00:00
David Baker 440274d639 Fix local trust for key backups
https://github.com/matrix-org/matrix-js-sdk/pull/832 added
cross-signing checks for backup trust but we failed to merge in the
check for the the backup being trusted locally.

Fixes https://github.com/vector-im/riot-web/issues/11404
2019-11-18 13:30:00 +00:00
J. Ryan Stinnett f93130a8a7 Add method to check whether cross-signing has keys 2019-11-18 12:32:39 +00:00
Bruno Windels 3d9bddfb9f Merge pull request #1072 from matrix-org/bwindels/userinfomakeover
Add method to get last active timestamp in room
2019-11-15 16:39:31 +00:00
Travis Ralston 439abbcce9 Merge pull request #1071 from matrix-org/travis/new-deactivate
Check the right Synapse endpoint for determining admin capabilities
2019-11-15 09:17:12 -07:00
David Baker ac91367801 Merge pull request #832 from matrix-org/dbkr/cross_signing
Cross Signing Support
2019-11-15 16:09:06 +00:00
David Baker 2a63cc474c Update import 2019-11-15 15:57:25 +00:00
David Baker 56261263f5 Rename backup_password & functions
Not Just For Backups Anymore
2019-11-15 15:54:43 +00:00
David Baker 04b57bbe9d Remove ghost of some old code 2019-11-15 15:27:02 +00:00
David Baker c550f83a04 update jsdoc 2019-11-15 14:57:29 +00:00
David Baker 5224ef4b1f This is now implemented
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:56:05 +00:00
David Baker 2ab033e76e is now implemented 2019-11-15 14:45:43 +00:00
David Baker fa2e669eda More jsdoc updates
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:44:08 +00:00
David Baker f0ba1f2ac0 c+p fail
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:43:41 +00:00
David Baker 6d0237ec71 This now returns DeviceTrustLevel too 2019-11-15 14:42:26 +00:00
David Baker 97dff4640a Capitalise jsdoc
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:41:12 +00:00
David Baker 00b571a429 c+p fail
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:40:57 +00:00
David Baker 86e0f49231 c+p fail
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:40:16 +00:00
David Baker f2f205f9bd Typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:38:44 +00:00
David Baker f84ec090cb backticks in jsdoc
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 14:38:27 +00:00
David Baker d37ed9ff6f lint 2019-11-15 12:39:14 +00:00
David Baker f5a5f5e51a Update yarn.lock 2019-11-15 12:31:22 +00:00
David Baker fe010242d9 Why is 'cross-signing' so hard to type?
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 12:30:05 +00:00
David Baker 545ebf81bf Move Crypto.prototype.init back to its rightful place 2019-11-15 12:29:03 +00:00
David Baker 408934932a copy jsdoc to internal methods 2019-11-15 12:27:14 +00:00
David Baker 6f42824c35 Typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-15 12:26:24 +00:00
David Baker c3215d51bd Switch the CroosSigningLevel constants
we check in resetKeys and set all if it's & 4 anyway, so may as well
make the constants a normal bitmask and then we can use the MASTER
constant below.
2019-11-15 12:23:37 +00:00
David Baker e541b96a71 Change check{User|Device}Trust interfaces
...to return objects with functions rather than a bitmask
2019-11-15 12:15:13 +00:00
Travis Ralston 904a2f466e Maybe use the right user ID too 2019-11-14 11:54:13 -07:00
Travis Ralston bad48da11a Check the right Synapse endpoint for determining admin capabilities 2019-11-14 11:50:06 -07:00
David Baker ce2d1d6e2b Don't emit event here, as per comment 2019-11-14 17:41:58 +00:00
Bruno Windels 2820071db1 add method to get last active timestamp in room 2019-11-14 17:00:28 +01:00
David Baker 5937185ce9 Assert usage of setDeviceVerification for cross-signing keys
We can't mark a cross-signing key as blocked/unblocked, known/unknown
or unverified, so throw an exception instead of doing nothing.

Also comment what's going on in this function.
2019-11-14 14:24:41 +00:00
David Baker be9b7a0d24 Remove getPublicKey
which was the same as getId
2019-11-14 14:10:13 +00:00
David Baker 7ca09ad749 tariling space 2019-11-14 12:18:07 +00:00
David Baker 686a7a40f9 Remove outdated comment
uhoreg says this is fine now...
2019-11-14 12:11:07 +00:00
David Baker 2a7b2835b6 remove unused function 2019-11-14 12:11:07 +00:00
David Baker 69ecf3b145 jsdoc formatting 2019-11-14 12:11:07 +00:00
David Baker 2cd748b50c Add matrix foundation copyright
The creation of this file just predates matrix.org foundation so it
should have both
2019-11-14 12:11:07 +00:00
David Baker 291133beb9 Fix comment 2019-11-14 12:11:07 +00:00
David Baker e10c17c866 Use official name for SSSS
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-14 12:10:56 +00:00
David Baker 0048cbef08 Mark cross siging / SSSS APIs as unstable
also add missing jsdoc
2019-11-14 11:51:44 +00:00
David Baker d9d65309b3 More s/cross-signing/crossSigning/ 2019-11-14 11:29:08 +00:00
David Baker d5d8032b5b Camelcase event names
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-11-14 11:04:37 +00:00
David Baker 693c749da0 lint 2019-11-13 17:59:25 +00:00
David Baker 7218e31a9c Sign & verify SSSS keys 2019-11-13 17:52:24 +00:00
David Baker 1798f3921f Make setDeafultKeyId wait for event 2019-11-13 14:42:08 +00:00
David Baker d12c56a623 lint 2019-11-13 14:11:50 +00:00
David Baker 26aa3d3ce7 Support default keys 2019-11-13 14:09:40 +00:00
David Baker c97a87d1f6 Throw if an unknown key is specified
It's probably important that the app knows if a secret isn't going
to be stored under one or more of the keys it thought it was going
to be stored under.

Also add a test to assert it.
2019-11-12 14:07:05 +00:00
David Baker 9bc185d459 Fix what was probablyt a c+p fail 2019-11-12 13:21:37 +00:00
David Baker 4c651c15ea Convert secrets events to callbacks too 2019-11-11 20:01:11 +00:00
David Baker a98e6964ef Missed bits of callback renaming 2019-11-11 16:51:49 +00:00
David Baker 6f8d9c4693 Rename getPrivateKeys to getCrossSigningKeys 2019-11-11 16:45:01 +00:00
David Baker fbc4bd0c96 Merge pull request #1067 from matrix-org/bwindels/verification-over-dm
Support for verification requests in the timeline
2019-11-08 16:19:50 +00:00
J. Ryan Stinnett 03c9241783 Merge pull request #1066 from matrix-org/jryans/privacy-prefix-r0
Use stable API prefix for 3PID APIs when supported
2019-11-07 20:00:57 +02:00
David Baker 3a983271d6 add comments 2019-11-07 16:21:53 +00:00
David Baker 03fe4afe32 lint 2019-11-07 15:20:07 +00:00
David Baker 12627022d1 Convert sas verification test to callbacks 2019-11-07 15:18:16 +00:00
David Baker fabfe16d45 lint 2019-11-07 12:35:39 +00:00
David Baker a34758f938 Convert event interface to callbacks
Use options.cryptoCallbacks for things that require information
from the app rather than events, since events can have zero, one
or many listeners and the emitter doesn't know how many, so if
nobody's listening then we would have just waited forever for a
response.

Also a collection of other changes like renaming 'fu' to 'firstUse'
2019-11-07 12:31:44 +00:00
J. Ryan Stinnett 20f5c3ea28 Use stable API prefix for 3PID APIs when supported
If the server advertises spec version r0.6.0, it must have the 3PID APIs
available under the stable API prefix.

Fixes https://github.com/vector-im/riot-web/issues/11246
2019-11-06 18:02:10 +00:00
Bruno Windels 62e490cfe4 add FIXME note for (expected) uncaught rejection 2019-11-06 12:36:50 +01:00
Bruno Windels a9dba39623 include redacted event so has same signature as other Relations events 2019-11-04 15:56:15 +01:00
Bruno Windels f1d417597c only emit Event.relationsCreated once event has been added
so if we ask for the relations in the handler, we don't get
an empty result
2019-11-04 15:55:29 +01:00
RiotRobot 549f679bf1 Merge branch 'master' into develop 2019-11-04 13:59:24 +00:00
RiotRobot 6ba052dcc4 v2.4.3 2019-11-04 13:51:50 +00:00
RiotRobot de873b84f5 Prepare changelog for v2.4.3 2019-11-04 13:51:49 +00:00
Bruno Windels 37558ac1b4 detect other end cancelling and reject main promise 2019-11-04 14:43:44 +01:00
Bruno Windels 9140d5a091 don't clear expected type before including it in error 2019-11-04 14:37:12 +01:00
J. Ryan Stinnett 7827af0d90 Merge pull request #1063 from matrix-org/jryans/rm-jenkins
Remove Jenkins scripts
2019-11-01 18:23:16 +02:00
J. Ryan Stinnett 1af8d20adf Remove Jenkins scripts
We haven't used Jenkins for a while, so it seems safe to remove the scripts that
supported it.
2019-11-01 16:08:29 +00:00
J. Ryan Stinnett 91df096698 Update tests 2019-11-01 14:10:16 +00:00
J. Ryan Stinnett e8fd0498a7 Relax identity server discovery checks to FAIL_PROMPT
As discussed in MSC2284, this relaxes the identity server discovery to a
`FAIL_PROMPT` state so that clients can choose to warn and continue.

Part of https://github.com/vector-im/riot-web/issues/11102
Implements https://github.com/matrix-org/matrix-doc/pull/2284
2019-11-01 11:24:51 +00:00
David Baker f3073e120d Space 2019-11-01 10:51:49 +00:00
David Baker a571624e13 Typo 2019-11-01 10:46:43 +00:00
David Baker 74b649c04c Typo 2019-11-01 10:45:41 +00:00
ZengJing 7973b99f50 Fix incorrect return value of MatrixClient.prototype.uploadKeys
Signed-off-by: Zeng Jing <beaclnd92@gmail.com>
2019-11-01 09:25:50 +08:00
RiotRobot e8f5a8b89d v2.4.3-rc.1 2019-10-30 16:40:25 +00:00
RiotRobot 2d0bda933c Prepare changelog for v2.4.3-rc.1 2019-10-30 16:40:25 +00:00
David Baker 49588da73d Fix more tests 2019-10-29 19:39:31 +00:00
David Baker 3e2d845342 Merge remote-tracking branch 'origin/develop' into dbkr/cross_signing 2019-10-28 16:47:16 +00:00
David Baker e92d2bd70a Fix test again
That one was part of the protocol - don't camelcase that
2019-10-28 16:07:26 +00:00
David Baker de1b545df1 lint 2019-10-28 15:42:42 +00:00
David Baker 3bec28b2ff make other tests pass 2019-10-28 15:23:58 +00:00
David Baker 8cad116dd7 Make tests pass
* Pass the http backend out of makeTestClients so we can tell it
   to expect queries and flush requests out
 * Change colons to dots in the key events
2019-10-28 14:56:35 +00:00
Hubert Chathi 35adb75d80 Merge pull request #1056 from uhoreg/fix_logger_path
fix the path in references to logger.js
2019-10-26 13:45:24 -04:00
Hubert Chathi e9908b1d97 fix the path in references to logger.js 2019-10-25 23:24:30 -04:00
Hubert Chathi fffd2eb70a Merge pull request #1050 from uhoreg/verification_in_dms
verification in DMs
2019-10-23 12:09:52 -04:00
Hubert Chathi 136b9c0f50 remove unnecessary async 2019-10-23 12:03:17 -04:00
Hubert Chathi 0f1206b4ee apply suggestions from review 2019-10-22 13:29:24 -04:00
RiotRobot 46d7e4c707 Merge branch 'master' into develop 2019-10-18 14:38:58 +01:00
RiotRobot c874783742 v2.4.2 2019-10-18 14:36:57 +01:00
RiotRobot bb296f50d9 Prepare changelog for v2.4.2 2019-10-18 14:36:57 +01:00
Michael Telatynski da68b53ff9 Merge pull request #1054 from rcsm/develop
Properly document the function possible returns for getRelationsForEvent
2019-10-17 15:35:33 +01:00
Rafael Cascalho bbe141d44e chore: updated throws docs in function getRelationsForEvent 2019-10-17 11:30:41 -03:00
Rafael Cascalho 8a03e41a7c chore: corrected the docs the getRelationsForEvent function 2019-10-17 10:51:34 -03:00
Rafael Cascalho a79e1bc976 Merge remote-tracking branch 'upstream/develop' into develop 2019-10-17 10:15:10 -03:00
J. Ryan Stinnett 056bfbf7a3 Merge pull request #1055 from matrix-org/jryans/bluebird-3.5
Downgrade to Bluebird 3.5.5 to fix Firefox
2019-10-17 00:10:54 +02:00
J. Ryan Stinnett e0b64a487d Downgrade to Bluebird 3.5.5 to fix Firefox
Bluebird 3.6.0+ currently breaks in at least Firefox with errors in their
rejection tracking approach. For now, this retreats back to the version we had
used for a long time.

Regressed by https://github.com/matrix-org/matrix-js-sdk/pull/1053
Fixes https://github.com/vector-im/riot-web/issues/11148
2019-10-17 00:05:47 +02:00
Rafael Cascalho d47d1d8f26 Merge remote-tracking branch 'upstream/develop' into develop 2019-10-15 11:12:00 -03:00
Rafael Cascalho 42a07de9a7 chore: corrected getRelationForEvent return docs 2019-10-15 10:28:23 -03:00
J. Ryan Stinnett aead855470 Merge pull request #1053 from matrix-org/jryans/major-deps-2
Upgrade safe deps to latest major version
2019-10-11 13:53:58 +01:00
J. Ryan Stinnett 335b2314f1 Upgrade safe deps to latest major version 2019-10-11 12:44:36 +01:00
Travis Ralston 89bab24c14 Merge pull request #1052 from clokep/no-js-import
Don't include .js in the import string.
2019-10-10 00:14:52 +01:00
Patrick Cloke 3a439dcdad Don't include .js in the import string.
Signed-off-by: Patrick Cloke <clokep@patrick.cloke.us>
2019-10-09 18:44:41 -04:00
RiotRobot 20d82eb92f v2.4.2-rc.1 2019-10-09 16:49:43 +01:00
RiotRobot 319e1d1191 Prepare changelog for v2.4.2-rc.1 2019-10-09 16:49:42 +01:00
Hubert Chathi 5f3492dbf8 send the m.key.verification.done message when done 2019-10-09 11:13:32 -04:00
David Baker 107c8c0b1f Merge pull request #1047 from matrix-org/dbkr/olm_session_describe
Log state of Olm sessions
2019-10-09 15:48:54 +01:00
David Baker 8c6d9586bf Update docs to reflect minimum olm version 2019-10-09 15:41:34 +01:00
David Baker 1271fc6bf3 Actually bump olm 2019-10-09 15:38:47 +01:00
David Baker c9df03c40c Bump to olm 3.1.4 which has olm_session_describe 2019-10-09 10:19:13 +01:00
Hubert Chathi d8e8dddd25 initial implementation of verification in DMs 2019-10-08 15:44:51 -04:00
Bruno Windels 27f6745123 Merge pull request #1048 from matrix-org/bwindels/redact-all-more-robust
Add method to get access to all timelines
2019-10-08 06:58:13 +00:00
Bruno Windels 964f448334 moar doc fix 2019-10-02 15:30:28 +02:00
Bruno Windels 20ee03bb44 fix docs 2019-10-02 15:25:19 +02:00
Bruno Windels 77bd677182 add method to get access to all timelines 2019-10-02 10:00:11 +02:00
RiotRobot e024d047e3 v2.4.1 2019-10-01 11:30:13 +01:00
RiotRobot 40943edc06 Prepare changelog for v2.4.1 2019-10-01 11:30:12 +01:00
David Baker e6699c5424 Log state of Olm sessions
...whenever we encrypt or decrypt a message on them. This adds
another line of logging for every device in the room, so will
be reasonably verbose if you're in large encrypted rooms, but
the information ought to be valuable.

Requires https://gitlab.matrix.org/matrix-org/olm/merge_requests/9

Don't merge before a new version of Olm is released with this merge
request (it won't work).
2019-10-01 11:19:52 +01:00
J. Ryan Stinnett bd8a307e50 Merge pull request #1046 from matrix-org/jryans/deps-2019-09-27
Upgrade deps
2019-09-27 16:01:50 +01:00
J. Ryan Stinnett f71301cafc Upgrade deps 2019-09-27 15:04:21 +01:00
RiotRobot 562bf9331b Merge branch 'master' into develop 2019-09-27 11:44:14 +01:00
RiotRobot 11e6eb94b5 v2.4.0 2019-09-27 11:42:17 +01:00
RiotRobot cee3aa2a7a Prepare changelog for v2.4.0 2019-09-27 11:42:17 +01:00
David Baker 81e3783488 Merge pull request #1043 from matrix-org/dbkr/ignore_empty_crypto_event
Ignore crypto events with no content
2019-09-27 10:44:17 +01:00
J. Ryan Stinnett fc7f9786f8 Merge pull request #1045 from matrix-org/jryans/yarn-cache-clean
Clean Yarn cache during release
2019-09-26 11:37:18 +01:00
J. Ryan Stinnett 0808c0edf1 Clean Yarn cache during release
Always run `yarn cache clean` during the `dist` step to workaround a Yarn bug
with Git commit package dependencies.
2019-09-26 11:18:20 +01:00
RiotRobot 8de6746efd v2.4.0-rc.1 2019-09-25 17:14:17 +01:00
RiotRobot eb9b8ef7c6 Prepare changelog for v2.4.0-rc.1 2019-09-25 17:14:16 +01:00
J. Ryan Stinnett b09621b915 Merge pull request #1044 from matrix-org/jryans/rm-id-server-creds
Remove id_server from creds for interactive auth
2019-09-25 16:02:41 +01:00
J. Ryan Stinnett 8d667f9367 Remove id_server from creds for interactive auth
For HSes that do not require an IS, we can remove `id_server` from the auth
params.

Fixes https://github.com/vector-im/riot-web/issues/10959
2019-09-25 14:59:32 +01:00
David Baker 56dfe6630f Ignore crypto events with no content 2019-09-25 12:05:52 +01:00
J. Ryan Stinnett 8b3b181a48 Merge pull request #1041 from matrix-org/jryans/rm-id-params-request-token
Remove IS details from requestToken to HS
2019-09-23 14:43:03 +01:00
J. Ryan Stinnett c952768542 Remove IS details from requestToken to HS
This removes the IS details (server and access token) from `requestToken` calls
to the HS, as long as the HS supports the new separate add and bind mode. In
this mode, all of the 3PID validation is handled by the HS, so the IS details
are not used.

Fixes https://github.com/vector-im/riot-web/issues/10933
2019-09-23 13:36:07 +01:00
J. Ryan Stinnett 1a368aa996 Merge pull request #1040 from matrix-org/jryans/msisdn-submit-url
Add support for sending MSISDN tokens to alternate URLs
2019-09-23 13:21:28 +01:00
J. Ryan Stinnett 61449458cf Add support for sending MSISDN tokens to alternate URLs
Part of https://github.com/vector-im/riot-web/issues/10923
2019-09-23 12:20:28 +01:00
J. Ryan Stinnett 4eb547e535 Merge pull request #1038 from matrix-org/jryans/msc2290
Add separate 3PID add and bind APIs
2019-09-20 14:37:00 +01:00
J. Ryan Stinnett b54acffaef Tweak unbind 3PID params 2019-09-19 17:28:50 +01:00
J. Ryan Stinnett 65a1833e1f Add 3PID unbind API 2019-09-19 15:28:58 +01:00
J. Ryan Stinnett 1ce4f25811 Use unstable prefix for add and bind 2019-09-19 15:28:35 +01:00
J. Ryan Stinnett 3127105516 Merge pull request #1037 from matrix-org/dependabot/npm_and_yarn/eslint-utils-1.4.2
Bump eslint-utils from 1.4.0 to 1.4.2
2019-09-18 12:52:28 +01:00
dependabot[bot] d59ea4be78 Bump eslint-utils from 1.4.0 to 1.4.2
Bumps [eslint-utils](https://github.com/mysticatea/eslint-utils) from 1.4.0 to 1.4.2.
- [Release notes](https://github.com/mysticatea/eslint-utils/releases)
- [Commits](https://github.com/mysticatea/eslint-utils/compare/v1.4.0...v1.4.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-09-18 11:48:29 +00:00
J. Ryan Stinnett f256f04440 Add MSISDN validation API on the IS
This API has existed for quite a while, but historically we've instead proxied
this request via the homeserver. As part of MSC2290 work, we are changing
approaches such that we will request tokens directly from the IS when binding
for discovery.

Part of https://github.com/vector-im/riot-web/issues/10839
2019-09-18 11:47:37 +01:00
J. Ryan Stinnett b444aaa67e Add separate add and bind HS APIs
Part of https://github.com/vector-im/riot-web/issues/10839
2019-09-18 11:16:23 +01:00
J. Ryan Stinnett 745185e689 Fix function doc syntax 2019-09-18 10:38:52 +01:00
J. Ryan Stinnett 2bfa891f0a Add function to check for separate 3PID add and bind
This adds a way to test for MSC2290 support on the homeserver with separate add
and bind functions. It checks the unstable feature flag as well as the upcoming
r0.6.0 spec version.

Part of https://github.com/vector-im/riot-web/issues/10839
2019-09-18 10:36:41 +01:00
Travis Ralston 147167bed3 Merge pull request #1036 from matrix-org/travis/wrap-ff
Handle WebRTC security errors as non-fatal
2019-09-17 13:52:19 -06:00
Travis Ralston 565e18e8a3 Handle WebRTC security errors as non-fatal
Fixes https://github.com/vector-im/riot-web/issues/10898

In some restricted modes of Firefox, the WebRTC classes aren't super available: accessing them can cause SecurityErrors to be raised. In these conditions, we should just disable WebRTC support instead of falling apart.
2019-09-17 13:49:50 -06:00
Travis Ralston 55b4595bbf Merge pull request #1035 from matrix-org/travis/r0.6
Check for r0.6.0 support in addition to unstable feature flags
2019-09-16 14:43:00 -06:00
Travis Ralston eeb2c463dc Check for r0.6.0 support in addition to unstable feature flags
To avoid the same problem that happened with lazy-loading (see https://github.com/matrix-org/synapse/issues/5528).

Note that as of writing r0.6.0 is not yet released, however it is the next scheduled release of the client-server API.
2019-09-16 14:30:18 -06:00
RiotRobot d9bb0e9a52 Merge branch 'master' into develop 2019-09-16 17:43:01 +01:00
RiotRobot 7d07ab7c7e v2.3.2 2019-09-16 17:40:19 +01:00
RiotRobot f8ff3aac58 Prepare changelog for v2.3.2 2019-09-16 17:40:19 +01:00
David Baker 299a7728d1 Merge pull request #1034 from matrix-org/travis/t2
[Release] Fix addPendingEvent with pending event order == chronological
2019-09-16 17:02:32 +01:00
David Baker 39dc6a1742 Fix addPendingEvent with pending event order == chronological
When the pending event order setting was set to 'chronological'
(the default) `addPendingEvent` would NPE because Room no longer
has a `this._filter` property. It should get the filter from the
event timeline set instead, as it does in the previous line when
checking or the presence of a filter.

We should strongly consider changing the default pending event order
to 'detached' and probably removing 'chronological' or comitting to
support it properly: it's not really tested and is prone to breakage
like this.

Applies flumpt's fix from https://github.com/matrix-org/matrix-js-sdk/issues/599
Fixes https://github.com/matrix-org/matrix-js-sdk/issues/599
2019-09-16 09:36:14 -06:00
RiotRobot f21c5aa7f2 v2.3.2-rc.1 2019-09-13 16:13:27 +01:00
RiotRobot e9bc3f26a5 Prepare changelog for v2.3.2-rc.1 2019-09-13 16:13:27 +01:00
David Baker 23eaddd6ea Merge pull request #1033 from matrix-org/travis/t1
Synapse admin functions to release
2019-09-13 16:07:58 +01:00
Travis Ralston 8143ce8450 Update src/client.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-09-13 09:01:16 -06:00
Travis Ralston 0a487ec43e Add Synapse admin functions for deactivating a user
For https://github.com/matrix-org/matrix-react-sdk/pull/3371
2019-09-13 09:01:05 -06:00
Travis Ralston 0edb483802 Merge pull request #1032 from matrix-org/travis/t1
[To Release] Add matrix base API to report an event
2019-09-13 08:27:31 -06:00
Michael Telatynski 06a32ce0a1 Add matrix base API to report an event
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-09-13 08:24:49 -06:00
RiotRobot 8cae00407a Merge branch 'master' into develop 2019-09-12 12:51:29 +01:00
RiotRobot a57ec87c67 v2.3.1 2019-09-12 12:48:36 +01:00
RiotRobot 4e62491ea4 Prepare changelog for v2.3.1 2019-09-12 12:48:36 +01:00
RiotRobot 5758029c1e v2.3.1-rc.1 2019-09-11 18:38:22 +01:00
RiotRobot 8f08710c58 Prepare changelog for v2.3.1-rc.1 2019-09-11 18:38:21 +01:00
David Baker 90f98105f0 Merge pull request #1031 from matrix-org/dbkr/update_profile_on_redact_2
Update room members on member event redaction
2019-09-11 18:12:46 +01:00
David Baker 90354aa330 Update room members on member event redaction
If a member event was redacted, we weren't updating the current
state.
2019-09-11 18:09:54 +01:00
David Baker aaabebe7f5 Merge pull request #1030 from matrix-org/dbkr/update_profile_on_redact
Update room members on member event redaction
2019-09-11 18:07:49 +01:00
David Baker 80a92dcdc2 Update room members on member event redaction
If a member event was redacted, we weren't updating the current
state.
2019-09-11 16:07:34 +01:00
Travis Ralston dc9081e9d4 Merge pull request #1028 from matrix-org/travis/hidden_rr
Support hidden read receipts
2019-09-10 10:55:58 -06:00
Travis Ralston 3c299637b6 Merge pull request #1029 from matrix-org/travis/lowercase-lookups
Do 3pid lookups in lowercase
2019-09-09 15:19:48 -06:00
Travis Ralston 07af333943 clarify comment 2019-09-09 14:44:51 -06:00
Travis Ralston 0bbc781d0c Do 3pid lookups in lowercase
Fixes https://github.com/vector-im/riot-web/issues/10754
2019-09-07 14:04:30 -06:00
Travis Ralston 79bf64f079 Appease the linter 2019-09-05 20:40:16 -06:00
Travis Ralston ed67d39456 Support hidden read receipts 2019-09-05 19:38:49 -06:00
Travis Ralston 2f8cc75432 Merge pull request #1027 from matrix-org/travis/synapse_admin
Add Synapse admin functions for deactivating a user
2019-09-02 11:08:39 -06:00
Travis Ralston 03cccef805 Update src/client.js
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-09-02 11:06:54 -06:00
Travis Ralston 6d5a0c2718 Add Synapse admin functions for deactivating a user
For https://github.com/matrix-org/matrix-react-sdk/pull/3371
2019-09-01 18:05:12 -06:00
David Baker 42b359eb5c Merge pull request #1026 from matrix-org/dbkr/fix_add_pending_events_chronological
Fix addPendingEvent with pending event order == chronological
2019-08-28 17:02:20 -04:00
David Baker 3071587f11 Fix addPendingEvent with pending event order == chronological
When the pending event order setting was set to 'chronological'
(the default) `addPendingEvent` would NPE because Room no longer
has a `this._filter` property. It should get the filter from the
event timeline set instead, as it does in the previous line when
checking or the presence of a filter.

We should strongly consider changing the default pending event order
to 'detached' and probably removing 'chronological' or comitting to
support it properly: it's not really tested and is prone to breakage
like this.

Applies flumpt's fix from https://github.com/matrix-org/matrix-js-sdk/issues/599
Fixes https://github.com/matrix-org/matrix-js-sdk/issues/599
2019-08-28 16:34:09 -04:00
Hubert Chathi f3ec9768bc update to follow latest MSC 2019-08-27 16:53:36 -07:00
Travis Ralston 23159807b0 Merge pull request #1024 from matrix-org/travis/wk-raw
Add AutoDiscovery.getRawClientConfig() for easy .well-known lookups
2019-08-27 08:22:55 -06:00
Travis Ralston b1ba9f76b8 Merge pull request #1025 from matrix-org/travis/fix-identity-json-error
Don't convert errors to JSON if they are JSON already
2019-08-23 12:14:58 -06:00
Travis Ralston 0e51dfed46 Don't convert errors to JSON if they are JSON already
For example, if the identity server throws a 401 on `/account`, we end up here with a JSON object. Don't convert the string `object Object` to JSON because it'll fail: just use the object.
2019-08-23 12:00:07 -06:00
J. Ryan Stinnett 09b00335f8 Merge pull request #1022 from matrix-org/jryans/is-token-to-hs
Send id_access_token to HS for use in proxied IS requests
2019-08-23 17:13:41 +01:00
J. Ryan Stinnett 3d274815d9 Change to provider object 2019-08-23 11:17:39 +01:00
Travis Ralston 70d60b905d Add AutoDiscovery.getRawClientConfig() for easy .well-known lookups
Useful in cases where you don't need to validate the homeserver information.
2019-08-22 14:45:42 -06:00
Travis Ralston 3e2ffb25a6 Merge pull request #1023 from matrix-org/travis/cleanup-identity-http
Clean up JSON handling in identity server requests
2019-08-22 10:55:08 -06:00
Travis Ralston 8b9bef5cb3 Clean up JSON handling in identity server requests 2019-08-22 08:32:20 -06:00
J. Ryan Stinnett 31e72efc91 Send id_access_token to HS for use in proxied IS requests
This passes along the `id_access_token` to the HS, which it will need when
speaking v2 IS APIs to the IS.

Unfortunately, some HSes seem to explode when given this new parameter, so we
only pass it along for the moment if an unstable feature `m.id_access_token` is
also set.

Part of https://github.com/vector-im/riot-web/issues/10525
Defined in MSC2140
2019-08-22 14:31:41 +01:00
Travis Ralston 60b7252597 Merge pull request #1021 from matrix-org/travis/v2-identity-lookups
Use the v2 (hashed) lookup for identity server queries
2019-08-22 07:05:16 -06:00
Travis Ralston 3980b62df2 js-doc syntax is unknown to our js-doc 2019-08-21 14:32:38 -06:00
Travis Ralston b306df726a Lookups are URL safe 2019-08-21 14:30:24 -06:00
Travis Ralston 3d5a79be3b Hashes need tokens too 2019-08-21 14:30:16 -06:00
Travis Ralston ba78d1a9ae JSON is JSON 2019-08-21 14:30:04 -06:00
Travis Ralston 241811298f Appease the js-doc 2019-08-21 14:17:55 -06:00
Travis Ralston 8a0ddc43ab Use the v2 (hashed) lookup for identity server queries
Fixes https://github.com/vector-im/riot-web/issues/10556
Implements [MSC2134](https://github.com/matrix-org/matrix-doc/pull/2134) with assistance from [MSC2140](https://github.com/matrix-org/matrix-doc/pull/2140) for auth.

Note: this also changes all identity server requests to use JSON as a request body. URL encoded forms were allowed in v1 but deprecated in favour of JSON. v2 APIs do not allow URL encoded forms.
2019-08-21 14:08:46 -06:00
David Baker 898fa0e41b Merge pull request #1018 from matrix-org/dbkr/getversions
Add getIdServer() & doesServerRequireIdServerParam()
2019-08-19 11:23:53 +01:00
David Baker 081ff4dec0 Merge pull request #1019 from matrix-org/dbkr/requesttoken_optional_id_server
Make requestToken endpoints work without ID Server
2019-08-19 11:23:05 +01:00
David Baker 3c69b8511d cache should expire TODO 2019-08-19 11:21:32 +01:00
David Baker 6843d86ecf Truthiness not null check
Co-Authored-By: Travis Ralston <travpc@gmail.com>
2019-08-16 19:41:37 +01:00
David Baker 2e91200136 lint
also WE FIXED SOME WARNINGS! Go us.
2019-08-16 18:13:42 +01:00
David Baker 852304c417 Make requestToken endpoints work without ID Server
Hopefully with doc in appropriate functions
2019-08-16 18:09:29 +01:00
David Baker ee752e3885 Add getIdServer() & doesServerRequireIdServerParam()
Remove individual cache for lazy loading and just cache the whole
versions response, then we can cache both of these flags
2019-08-16 15:02:49 +01:00
David Baker b9480e4302 Merge pull request #1016 from matrix-org/dbkr/fix_setidentityserver
Fix setIdentityServer
2019-08-15 15:11:54 +01:00
David Baker 2ae4d07971 Fix setIdentityServer 2019-08-15 12:01:40 +01:00
J. Ryan Stinnett 90cac8a118 Merge pull request #1015 from matrix-org/jryans/stun-turn-fallback
Change ICE fallback server and make fallback opt-in
2019-08-15 10:50:56 +01:00
J. Ryan Stinnett db18274f6e Revert no TURN servers event
The intended flow has changed so we prompt about this case in context of making
a call, so this event is no longer needed.
2019-08-14 14:09:05 +01:00
J. Ryan Stinnett 172bad8b55 Support configuring ICE fallback at client init time
This adds a new client init option to configure whether an ICE fallback server
is allowed to be used.
2019-08-14 13:44:50 +01:00
J. Ryan Stinnett dfe454e18f Change ICE server fallback to opt-in
This changes the ICE server fallback to be disabled by default. The SDK consumer
will receive a new event in case the homeserver has no ICE servers of its own,
and can prompt the user to agree to the fallback if desired.

Part of https://github.com/vector-im/riot-web/issues/10173
2019-08-13 15:55:31 +01:00
J. Ryan Stinnett 3d8dd29b4c Change STUN fallback to turn.matrix.org
This changes the STUN fallback server from a Google server to one hosted at
`turn.matrix.org`.

Part of https://github.com/vector-im/riot-web/issues/10173
2019-08-13 13:31:43 +01:00
J. Ryan Stinnett c3ff213ec9 Improve WebRTC object logging 2019-08-13 13:29:59 +01:00
J. Ryan Stinnett e80e5e1f8c Firefox supports urls on RTCIceServer
There's no longer a need to translate ICE server objects for Firefox, as it
supports the `urls` array since version 37.
2019-08-13 13:15:56 +01:00
David Baker bba249d5ce Merge pull request #1014 from matrix-org/dbkr/throw_if_no_id_url
Throw an exception if trying to do an ID server request with no ID server
2019-08-13 10:41:04 +01:00
David Baker f57df2bee5 Catch no ID server on other method 2019-08-12 16:06:37 +01:00
David Baker b930638156 Be more verbose
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-08-12 16:00:40 +01:00
David Baker 39c1de19fc Throw an exception if trying to do an ID server request with no ID server
Will help with https://github.com/vector-im/riot-web/issues/10540
2019-08-12 15:39:50 +01:00
David Baker 17724fc8d3 Merge pull request #1013 from matrix-org/dbkr/set_is
Add setIdentityServerUrl
2019-08-09 18:11:45 +01:00
David Baker 4c6d11d9ed Add setIdentityServerUrl
Hopefully fairly self explanatory. The ID server URL can be changed
fairly readily, whereas the HS URL would require a different access
token etc.
2019-08-09 18:05:37 +01:00
Michael Telatynski 05d77a85c9 Merge pull request #1011 from matrix-org/t3chguy/add-rooms-roomid-report-eventid
Add matrix base API to report an event
2019-08-08 15:21:20 +01:00
J. Ryan Stinnett e95a133cdd Merge pull request #1010 from matrix-org/jryans/user-settings-toggle-3pid
Fix POST body for v2 IS requests
2019-08-08 15:17:53 +01:00
Michael Telatynski c21382d721 Add matrix base API to report an event
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-08-08 15:16:11 +01:00
J. Ryan Stinnett 8c15125e23 Fix POST body for v2 IS requests
POST bodies for v2 IS requests must be sent as JSON.

Part of https://github.com/vector-im/riot-web/issues/10159
2019-08-08 10:29:14 +01:00
J. Ryan Stinnett 64ddbd97dd Merge pull request #1009 from matrix-org/jryans/user-settings-toggle-3pid
Add API for bulk lookup on the Identity Server
2019-08-07 18:05:58 +01:00
J. Ryan Stinnett 9c24bcb7a9 Tweak the type 2019-08-07 18:05:32 +01:00
J. Ryan Stinnett 8f016726f0 Add API for bulk lookup on the Identity Server
This adds support for querying `/bulk_lookup` on the IS to check several 3PIDs
at the same time.

Part of https://github.com/vector-im/riot-web/issues/10159
2019-08-07 17:45:13 +01:00
J. Ryan Stinnett 649fe7a490 Merge pull request #1000 from matrix-org/t3chguy/remove_authedRequestWithPrefix
Remove deprecated authedRequestWithPrefix and requestWithPrefix
2019-08-06 18:58:24 +01:00
RiotRobot 35f1cdf89c Merge branch 'master' into develop 2019-08-05 11:48:35 +01:00
RiotRobot 06adc34fb3 v2.3.0 2019-08-05 11:46:46 +01:00
RiotRobot 87bf07f95e Prepare changelog for v2.3.0 2019-08-05 11:46:46 +01:00
J. Ryan Stinnett f05bf3f845 Merge pull request #1007 from matrix-org/jryans/is-account-info
Add API for checking IS account info
2019-08-01 17:28:08 +01:00
Travis Ralston ab512d087c Merge pull request #1008 from matrix-org/travis/tombstone-push-rel
[release] Support rewriting push rules when our internal defaults change
2019-08-01 08:30:01 -06:00
Travis Ralston 6799c29921 Appease the tests 2019-08-01 08:23:19 -06:00
Travis Ralston a3f1da1981 Appease the linter 2019-08-01 08:23:19 -06:00
Travis Ralston 3b225651cc Support rewriting push rules when our internal defaults change
and change our internal default for tombstones
2019-08-01 08:23:19 -06:00
Travis Ralston a40d691159 Merge pull request #1006 from matrix-org/travis/tombstone-push
Support rewriting push rules when our internal defaults change
2019-08-01 08:22:47 -06:00
J. Ryan Stinnett 4ebe60b2ad Add API for checking IS account info
Part of https://github.com/vector-im/riot-web/issues/10452
2019-08-01 12:02:56 +01:00
J. Ryan Stinnett 5a70859593 Merge pull request #1005 from matrix-org/jryans/upgrade-deps-2019-07-31
Upgrade dependencies
2019-07-31 18:51:14 +01:00
Travis Ralston c7be810e65 Appease the tests 2019-07-31 11:00:44 -06:00
Travis Ralston 101217cfb6 Appease the linter 2019-07-31 11:00:38 -06:00
Travis Ralston 5c2aa4677f Support rewriting push rules when our internal defaults change
and change our internal default for tombstones
2019-07-31 10:52:44 -06:00
J. Ryan Stinnett ab9bfa68ae Upgrade dependencies 2019-07-31 17:36:35 +01:00
RiotRobot aa8c2ca277 v2.3.0-rc.1 2019-07-31 16:20:54 +01:00
RiotRobot 84509087ac Prepare changelog for v2.3.0-rc.1 2019-07-31 16:20:54 +01:00
J. Ryan Stinnett 2450d461fd Merge pull request #1002 from matrix-org/jryans/is-v2-auth
Add support for IS v2 API with authentication
2019-07-30 18:13:08 +01:00
J. Ryan Stinnett 50c590ae26 Note cleanup issue 2019-07-30 10:38:53 +01:00
J. Ryan Stinnett 516dff06ee Rename isAccessToken to identityAccessToken 2019-07-30 10:06:52 +01:00
Travis Ralston 9a8af05bfb Merge pull request #1001 from matrix-org/hs/recursive-tombstone-fixes
Tombstone bugfixes
2019-07-29 08:52:59 -06:00
Will Hunt c9bf61c387 Simplify Set 2019-07-29 15:29:18 +01:00
Will Hunt 4f0f2e8c16 Fix issues with recursive tombstones 2019-07-29 15:27:32 +01:00
J. Ryan Stinnett 6f042a2142 Add IS v2 support to other IS APIs
This adds v2 support with fallback to other IS APIs in the SDK.
2019-07-29 14:55:40 +01:00
J. Ryan Stinnett 91416bdbb2 Add IS v1 API fallback for lookup 2019-07-29 14:44:15 +01:00
J. Ryan Stinnett 9b093f7569 Add first pass of IS v2 API with authentication
This only updates the `/lookup` API so far. It also doesn't handle falling back
to v1.
2019-07-29 13:15:19 +01:00
Michael Telatynski b004d1602d Remove deprecated authedRequestWithPrefix and requestWithPrefix
replacing as documented with authedRequest

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-07-29 12:25:38 +01:00
David Baker 6cca73b999 Merge pull request #988 from matrix-org/dbkr/terms
Support for MSC2140 (terms of service for IS/IM)
2019-07-23 10:32:05 +01:00
David Baker fafd6df13e Use standard _matrix path for terms endpoints 2019-07-22 18:53:36 +01:00
RiotRobot 8f77870526 Merge branch 'master' into develop 2019-07-18 15:44:16 +01:00
RiotRobot eb0462e89b v2.2.0 2019-07-18 15:42:21 +01:00
RiotRobot 80748d7d85 Prepare changelog for v2.2.0 2019-07-18 15:42:20 +01:00
David Baker b694d53b73 Revert 8004e82c50
We need to switch the paths over all at once, so we can't commit
this yet: leave it until scalar suypports the new API then we can
update develop to use the _matrix paths.

(Also for some reason I broke the IS path too)
2019-07-15 14:26:16 +01:00
David Baker 8004e82c50 Use _matrix prefix for terms API 2019-07-15 13:51:39 +01:00
J. Ryan Stinnett 85b5849228 Upgrade lodash 2019-07-12 19:25:47 +01:00
J. Ryan Stinnett 6b86777e96 Upgrade lodash 2019-07-12 19:25:21 +01:00
RiotRobot efe64a4817 v2.2.0-rc.2 2019-07-12 17:25:20 +01:00
RiotRobot ad777f36b2 Prepare changelog for v2.2.0-rc.2 2019-07-12 17:25:20 +01:00
J. Ryan Stinnett de77ad867c Merge pull request #995 from matrix-org/jryans/v2.2.0/devices
Add a request method to /devices
2019-07-12 16:47:06 +01:00
Travis Ralston 9b35f86497 Add a request method to /devices
Turns out `HTTP /devices undefined` is invalid.

Regressed in https://github.com/matrix-org/matrix-js-sdk/pull/990
2019-07-12 16:38:43 +01:00
Travis Ralston 84fc8b1931 Merge pull request #994 from matrix-org/travis/fix-devices
Add a request method to /devices
2019-07-12 08:12:59 -06:00
RiotRobot 455c85fb69 v2.2.0-rc.1 2019-07-12 11:12:26 +01:00
RiotRobot 2a2fed695b Prepare changelog for v2.2.0-rc.1 2019-07-12 11:12:25 +01:00
Travis Ralston c1f28bd410 Add a request method to /devices
Turns out `HTTP /devices undefined` is invalid.

Regressed in https://github.com/matrix-org/matrix-js-sdk/pull/990
2019-07-11 14:35:04 -06:00
David Baker c74e0bb6b3 tell people what an IS/IM are 2019-07-11 16:29:27 +01:00
David Baker 5b9e158035 unused param
getTerms is un-authed so doesn't need the access token
2019-07-11 16:28:41 +01:00
Travis Ralston a8d200dd02 Merge pull request #993 from matrix-org/travis/sas-is-done
End the verification timer when verification is done
2019-07-11 08:20:30 -06:00
Travis Ralston 34ae967cb8 Merge pull request #990 from matrix-org/travis/stably-use-stable-apis
Stabilize usage of stably stable APIs (in a stable way)
2019-07-11 08:20:14 -06:00
RiotRobot a67f14825e Merge branch 'master' into develop 2019-07-11 10:36:44 +01:00
RiotRobot 50c14d0ab8 v2.1.1 2019-07-11 10:34:58 +01:00
RiotRobot 0edb6e6f6f Prepare changelog for v2.1.1 2019-07-11 10:34:58 +01:00
Travis Ralston 36d0dacda1 Process ephemeral events outside timeline handling 2019-07-11 10:21:44 +01:00
Bruno Windels 83b74070aa Merge pull request #987 from matrix-org/bwindels/include-orig-in-history
Expose original_event for /relations
2019-07-11 08:09:44 +00:00
Travis Ralston f80af68686 End the verification timer when verification is done
Fixes https://github.com/matrix-org/matrix-js-sdk/issues/980

This also improves cleanliness in the tests to cancel/terminate timers when needed.
2019-07-10 14:51:12 -06:00
Travis Ralston fe4ac06f43 Use the correct media endpoints 2019-07-10 13:24:11 -06:00
Travis Ralston eaaa3e980a Use unstable prefix for key backup 2019-07-10 13:17:31 -06:00
Travis Ralston 07629bfb9a unstable -> stable 2019-07-10 13:11:46 -06:00
Travis Ralston 88fdeca2bf Merge pull request #989 from matrix-org/travis/edu-timeline
Process ephemeral events outside timeline handling
2019-07-10 10:38:02 -06:00
Travis Ralston a3c8eac38b Process ephemeral events outside timeline handling 2019-07-10 10:26:21 -06:00
Bruno Windels bd5380c0b4 Merge pull request #986 from matrix-org/bwindels/include-ssa-for-replace
Don't accept any locally known edits earlier than the last known server-side aggregated edit
2019-07-10 14:38:26 +00:00
David Baker c3b5767999 update comment to reflect right version 2019-07-10 12:19:12 +01:00
David Baker 9e5c2732c9 consistent spacing 2019-07-10 12:18:41 +01:00
David Baker b8957fa917 omit null params 2019-07-10 12:17:52 +01:00
David Baker 52c139dcdc Forgot /terms for ISes
and IMs shouldn't have a slash
2019-07-10 12:15:31 +01:00
David Baker 39d4bf1494 SERVICE_TYPES 2019-07-10 12:08:13 +01:00
David Baker 4c713e3387 s/servicetypes/service-types/ 2019-07-10 11:53:59 +01:00
David Baker bb486f5148 SERVICE_TYPES
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-07-10 11:47:36 +01:00
David Baker 524fea1297 lint 2019-07-10 10:43:54 +01:00
David Baker e9528ebb98 Support for MSC2140 (terms of service for IS/IM) 2019-07-09 18:50:01 +01:00
Bruno Windels de18283c3b map, decrypt and return original_event if present 2019-07-09 17:52:58 +02:00
Bruno Windels cc1c7561a3 Merge pull request #984 from matrix-org/bwindels/replace-server-date
Get edit date transparently from server aggregations or local echo
2019-07-09 15:03:02 +00:00
Bruno Windels 5d928f07a0 don't accept any edit earlier than the server-side set edit. 2019-07-09 15:06:07 +02:00
Bruno Windels 01b882480f method to get edit date transparently from server aggregations or local echo 2019-07-09 11:29:19 +02:00
Travis Ralston 21d52fdbdd Merge pull request #982 from matrix-org/travis/soft-logout-keys
Add a function to flag keys for backup without scheduling a backup
2019-07-08 11:48:09 -06:00
Hubert Chathi 7f8b9de560 offer to upgrade device verifications to cross-signing 2019-07-08 12:26:00 -04:00
Hubert Chathi 761f22b63d minor cleanups 2019-07-08 12:25:28 -04:00
RiotRobot f9baff2a3a Merge branch 'master' into develop 2019-07-08 10:46:33 +01:00
RiotRobot 71eca4ffcc v2.1.0 2019-07-08 10:44:47 +01:00
RiotRobot bc8dca5105 Prepare changelog for v2.1.0 2019-07-08 10:44:47 +01:00
David Baker 3ae3dffff7 Lint 2019-07-08 10:37:24 +01:00
David Baker 81c6023940 Fix exception whilst syncing
event.getPushRules() may return null (for better or worse...).
Use client.getPushRulesForEvent which will calculate them if they
haven't already been calculated.

Fixes https://github.com/vector-im/riot-web/issues/10269
2019-07-08 10:37:24 +01:00
Travis Ralston 3a0f27fa7e Add a function to flag keys for backup without scheduling a backup
For https://github.com/vector-im/riot-web/issues/10263

Starting/scheduling the backup won't help us because the token would be invalid from a server perspective. Instead, we should update what needs to be done and return a count.
2019-07-05 13:50:11 -06:00
J. Ryan Stinnett 60e339bac0 Merge pull request #981 from matrix-org/jryans/reactions-send-marks-unread
Block read marker and read receipt from advancing into pending events
2019-07-05 17:55:52 +01:00
J. Ryan Stinnett ecb88f45b7 Merge pull request #977 from matrix-org/jryans/upgrade-deps
Upgrade dependencies
2019-07-05 17:50:10 +01:00
J. Ryan Stinnett 24a869d15b Update copyright header 2019-07-05 15:07:56 +01:00
J. Ryan Stinnett 1d427a1ea8 Block read marker and read receipt from advancing into pending events
This changes the methods that update the read marker and read receipts to
prevent advancing into pending events.

Part of https://github.com/vector-im/riot-web/issues/9952
2019-07-05 13:59:04 +01:00
J. Ryan Stinnett 90e25867ad Merge pull request #976 from matrix-org/jryans/push-rule-reactions
Add default push rule to ignore reactions
2019-07-05 12:01:31 +01:00
David Baker 2ae56e61cb Merge pull request #979 from matrix-org/dbkr/fix_sync_exception
Fix exception whilst syncing
2019-07-05 11:08:00 +01:00
David Baker 56c0830328 Lint 2019-07-05 10:32:33 +01:00
David Baker 093f139d34 Fix exception whilst syncing
event.getPushRules() may return null (for better or worse...).
Use client.getPushRulesForEvent which will calculate them if they
haven't already been calculated.

Fixes https://github.com/vector-im/riot-web/issues/10269
2019-07-05 10:26:47 +01:00
Travis Ralston 1083efc212 Merge pull request #975 from matrix-org/travis/soft-logout-base
Include the error object when raising Session.logged_out
2019-07-04 09:57:22 -06:00
J. Ryan Stinnett 69773c2619 Upgrade dependencies 2019-07-04 15:04:34 +01:00
J. Ryan Stinnett 2525b5a5d8 Add default push rule to ignore reactions
This adds a default push rule to ignore reactions as proposed in
[MSC2153](https://github.com/matrix-org/matrix-doc/pull/2153). By adding it here
in the client directly, we can try out the idea early even if it hasn't appeared
in the user's HS yet.

Part of https://github.com/vector-im/riot-web/issues/10208
2019-07-04 14:41:04 +01:00
Hubert Chathi b00804102d obsolete todo 2019-07-03 21:37:18 -04:00
Hubert Chathi 8d1d657c44 add unit test for backups signed by cross-signing key 2019-07-03 19:16:26 -04:00
Travis Ralston ff9c84ff94 Fix tests 2019-07-03 16:50:24 -06:00
Travis Ralston 3aa2bf8a76 Include the error object when raising Session.logged_out
Note: The `call` argument previously defined in the SDK was never actually populated, and appears to be a documentation error when the definition was copied from `Call.incoming` directly above it.
2019-07-03 16:42:33 -06:00
Hubert Chathi 6cd09c6af2 pksign was moved to olmlib 2019-07-03 16:00:44 -04:00
Hubert Chathi 46a8486245 rename m.secrets.share to m.secrets.send to agree with latest MSC 2019-07-03 15:15:56 -04:00
Hubert Chathi c5caf8f8f4 sign backups with master key 2019-07-03 15:15:41 -04:00
RiotRobot a229ece693 v2.1.0-rc.1 2019-07-03 16:40:08 +01:00
RiotRobot b435137332 Prepare changelog for v2.1.0-rc.1 2019-07-03 16:40:07 +01:00
Travis Ralston 2cdbc9f4db Merge pull request #974 from matrix-org/travis/e2e-self-notif
Handle self read receipts for fixing e2e notification counts
2019-07-03 09:16:22 -06:00
Michael Telatynski aa6884e484 Merge pull request #973 from matrix-org/t3chguy/show_hidden_redactions_missing_redacts
Add redacts field to event.toJSON
2019-07-03 13:10:16 +01:00
Bruno Windels 4f4d694687 Merge pull request #972 from matrix-org/bwindels/handle-associated-failures
Handle associated event send failures
2019-07-03 09:02:49 +00:00
Travis Ralston 1b47999e80 Handle self read receipts for fixing e2e notification counts
Fixes https://github.com/vector-im/riot-web/issues/9421

This also adds a context to the ReEmitter so we have access to the Room at the time of read receipt. Without this, we have to bind handlers to every encrypted room (which is tedious to maintain) or figure out which room `$something` belong to (CPU intensive).
2019-07-02 13:12:29 -06:00
Michael Telatynski 02427651dd Add redacts field to event.toJSON
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2019-07-02 19:44:16 +01:00
Bruno Windels 8eeb088e50 allow to clear replacement on redacted events
this is needed when cancelling sending anything related to an event,
to not keep the event as edited when we cancel the edit
before the redaction.
2019-07-02 13:12:13 +02:00
Travis Ralston 3f62581556 Merge pull request #971 from matrix-org/travis/cleanup-debugging
Remove irrelevant debug line from timeline handling
2019-07-01 09:47:36 -06:00
Travis Ralston b53a7f6ee8 Remove irrelevant debug line from timeline handling
See https://github.com/matrix-org/matrix-js-sdk/pull/877#discussion_r271704218
2019-06-28 16:29:18 -06:00
Hubert Chathi 4356603665 save public part of cross-signing keys 2019-06-27 23:37:57 -04:00
Hubert Chathi 1cae5e8b97 fix unit tests to match event name changes 2019-06-27 23:33:07 -04:00
Bruno Windels a4591afba6 clarify when this will return something 2019-06-27 17:53:49 +02:00
Bruno Windels 6ea4c77dd5 expose local echo redaction event on redacted event 2019-06-27 17:53:29 +02:00
Bruno Windels dc3d90d696 keep local redaction event to return accurate status 2019-06-27 17:17:53 +02:00
Bruno Windels 00de919eb4 Merge pull request #969 from matrix-org/bwindels/e2e-edit-history
Handle relations in encrypted rooms
2019-06-27 13:15:59 +00:00
Bruno Windels fdacc2f7ab dont try to decrypt reactions 2019-06-27 15:03:47 +02:00
J. Ryan Stinnett 5a64c29228 Fix logging typo
Regression from https://github.com/matrix-org/matrix-js-sdk/pull/924
2019-06-27 13:27:13 +01:00
Bruno Windels c5cddf1607 take reactions into account when fetching relations from e2e rooms 2019-06-27 14:06:57 +02:00
Bruno Windels 44c7844a4b handle relations in encrypted rooms 2019-06-27 12:35:34 +02:00
Bruno Windels f293da5b34 Merge pull request #967 from matrix-org/bwindels/edit-history
Relations endpoint support
2019-06-26 14:38:46 +00:00
Bruno Windels 4f044de79e add jsdocs 2019-06-26 15:48:39 +02:00
Bruno Windels da116e6077 remove c/p leftover 2019-06-26 15:48:27 +02:00
Bruno Windels 2489180c47 rename pagination token option to from, more in line with cs api 2019-06-26 15:47:59 +02:00
J. Ryan Stinnett 4ec4d330aa Merge pull request #968 from matrix-org/jryans/reactions-disable-encryption
Disable event encryption for reactions
2019-06-26 11:29:47 +01:00
Bruno Windels c75ca1c2d6 fix lint and encode path params properly 2019-06-26 12:10:24 +02:00
Bruno Windels 67462e9fc4 support paginating relations 2019-06-26 12:00:25 +02:00
Bruno Windels 424b6303ef basic support for /relations endpoint 2019-06-26 12:00:25 +02:00
J. Ryan Stinnett 59c4e2c354 Disable event encryption for reactions
For reactions, there is a very little gained by encrypting the entire event, as
relation data is already kept in the clear. Event encryption for a reaction
effectively only obscures the event type, but the purpose is still obvious from
the relation data, so nothing is really gained. It also causes quite a few
problems, such as:

  * triggers notifications via default push rules
  * prevents server-side bundling for reactions

The reaction key / content / emoji value does warrant encrypting, but this will
be handled separately by encrypting just this value.

See https://github.com/matrix-org/matrix-doc/pull/1849#pullrequestreview-248763642
Fixes https://github.com/vector-im/riot-web/issues/10130
2019-06-26 10:59:44 +01:00
Travis Ralston ecca8bc86e Merge pull request #966 from matrix-org/travis/known-safe
Change the known safe room version to version 4
2019-06-25 17:29:24 -06:00
Travis Ralston 43ca920b10 Change the known safe room version to version 4
The default room version in the spec is v4 due to widespread adoption. We should mirror that.
2019-06-25 15:19:28 -06:00
Travis Ralston e1a3f8f053 Merge pull request #965 from matrix-org/travis/detect-ll-r0.5
Check for lazy-loading support in the spec versions instead
2019-06-25 09:02:20 -06:00
Travis Ralston 6e7cb63e7d Check for lazy-loading support in the spec versions instead
Fixes https://github.com/vector-im/riot-web/issues/9966
2019-06-24 13:03:06 -06:00
Travis Ralston ab1177d987 Merge pull request #963 from huan/patch-1
Use cameCase instead of underscore
2019-06-24 11:09:36 -06:00
Huan LI (李卓桓) ee0a1d281d Use cameCase instead of underscore
To make all the variable names to be consistent
2019-06-23 11:56:05 +08:00
Travis Ralston aef7b9a1dc Merge pull request #961 from matrix-org/travis/sas-timeouts
Time out verification attempts after 10 minutes of inactivity
2019-06-20 14:29:18 -06:00
Travis Ralston 7cb8de5b69 Appease the linter 2019-06-20 14:17:51 -06:00
Travis Ralston 5c2fb1c42b Null check and naming 2019-06-20 14:12:16 -06:00
Travis Ralston 553857583d Merge pull request #962 from matrix-org/travis/sas-cancel
Don't handle key verification requests which are immediately cancelled
2019-06-20 09:10:31 -06:00
Travis Ralston 6d0923153f Don't handle key verification requests which are immediately cancelled
Fixes https://github.com/vector-im/riot-web/issues/10083
Fixes https://github.com/vector-im/riot-web/issues/9197
Fixes https://github.com/vector-im/riot-web/issues/8629

The issue is partially fixed by https://github.com/matrix-org/matrix-react-sdk/pull/3123 in that users would no longer see "Incoming request", but would launch their client to a bunch of "key verification cancelled" dialogs. To work around this, we just don't handle key verification requests which we know are cancelled.

The changes are a bit awkward (flagging the event as cancelled instead of filtering it) because:
* We probably don't want to prevent events getting sent over the EventEmitter because applications may still rely on them.
* The cypto side only has visibility of 1 event at a time, so it needs to have some kind of flag to rely on.

An attempt has been made to generalize the new event flag for possible future cases.
2019-06-19 16:46:38 -06:00
Travis Ralston e34eb48914 Don't timeout cancelled requests
The cancelled flag is used upstream and is therefore public.
2019-06-19 14:44:36 -06:00
Travis Ralston 05ab6ef3ab Time out verification attempts after 10 minutes of inactivity
Fixes https://github.com/vector-im/riot-web/issues/10117
2019-06-19 14:15:58 -06:00
RiotRobot 81eefc1377 v2.0.1 2019-06-19 15:43:38 +01:00
RiotRobot 895d854e1c Prepare changelog for v2.0.1 2019-06-19 15:43:38 +01:00
RiotRobot e432d4f808 v2.0.1-rc.2 2019-06-18 15:28:47 +01:00
RiotRobot 56c0ae294b Prepare changelog for v2.0.1-rc.2 2019-06-18 15:28:47 +01:00
David Baker 0b9d68b4f2 Merge remote-tracking branch 'origin/develop' into release-v2.0.1 2019-06-18 15:24:51 +01:00
Bruno Windels e2e034f795 Merge pull request #960 from matrix-org/bwindels/redactions-blended-echo
return 'sending' status for an event that is only locally redacted
2019-06-18 13:09:03 +00:00
Bruno Windels bb5e3d51b8 remove redundant localecho part from method name 2019-06-18 14:58:17 +02:00
Bruno Windels 70b23614b5 comment typo 2019-06-18 14:55:58 +02:00
Bruno Windels 24a75e3765 return 'sending' status for an event that is only locally redacted 2019-06-18 13:46:34 +02:00
Hubert Chathi 07c2e34d87 Merge branch 'develop' into dbkr/cross_signing 2019-06-14 22:57:02 -04:00
Hubert Chathi 5bcbe76f2c cleanups and a lot more docs 2019-06-14 22:50:29 -04:00
Hubert Chathi d694ee3ef3 Merge pull request #954 from uhoreg/fix_verification_request
Key verification request fixes
2019-06-14 15:39:01 -04:00
David Baker efbdf4e1a8 Merge pull request #956 from matrix-org/dbkr/resurrect_riot_bot
Add flag to force saving sync store
2019-06-14 17:20:27 +01:00
David Baker 44bfc2e846 Add flag to force saving sync store
Add a 'force' flag to to the save method of the store to force the
store to sace its data even if it wouldn't normally.
2019-06-14 15:57:49 +01:00
Travis Ralston 0121bdbb75 welcome back, Olm 2019-06-14 08:23:27 -06:00
David Baker b8ba77a7b5 Merge pull request #953 from matrix-org/dbkr/simplify_email_reg
Expose the inhibit_login flag to register
2019-06-13 18:22:23 +01:00
David Baker 65dd5cc6ad use right variable 2019-06-13 16:30:39 +01:00
David Baker 8aeb994839 Expose the inhibit_login flag to register 2019-06-13 16:23:11 +01:00
Hubert Chathi 64daa444dd Key verification request fixes
- fix requestVerification in MatrixClient to match the function in crypto
  - reorder the arguments so that the arguments actually do what they say they
    do
  - pass through the third argument, which was accidentally omitted
- ignore verification requests from ourselves
- also fix a comment
2019-06-13 10:55:06 -04:00
Bruno Windels 26aab4f38d Merge pull request #947 from matrix-org/bwindels/relations-unsent
Support redactions and relations of/with unsent events.
2019-06-13 11:32:37 +00:00
Bruno Windels 6059df1b67 move CANCELLED check deeper into aggregation path 2019-06-13 12:28:02 +02:00
Bruno Windels 2a0c85c772 add hasAssociation helper 2019-06-13 12:28:02 +02:00
Bruno Windels 3488fbe64c expand comment why need to preserve redaction local echo on remote echo 2019-06-13 12:28:02 +02:00
Bruno Windels 811a98ad19 whitespace, newlines 2019-06-13 12:28:02 +02:00
Bruno Windels 4462f4b90e add isRedaction helper on Event 2019-06-13 12:28:02 +02:00
Bruno Windels 4143a79f7b rename related id to associated id 2019-06-13 12:26:38 +02:00
Bruno Windels 3ed9b00398 clarify why we need to listen for remote echo of related event 2019-06-13 12:26:38 +02:00
Bruno Windels b005b75331 comment typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-06-13 12:26:38 +02:00
Bruno Windels a9f9e2cf35 comment typo
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-06-13 12:26:38 +02:00
Bruno Windels 5602b94dcb make sure where not re-adding cancelled events when undoing local red. 2019-06-13 12:26:38 +02:00
Bruno Windels 930de640ac don't add events from /sync that have been locally redacted
it'll cause the reactions counter to go up and down while reactions
and redactions come in.

In case the local redaction gets cancelled,
Room._revertRedactionLocalEcho will add the relation back to
the relations collection.
2019-06-13 12:26:38 +02:00
Bruno Windels 6d9fba8191 preserve (locally) redacted state after applying remote echo
because the RedactionDimensions was trying to redact an event
that was already redacted after it's remote echo had come in
but it's redaction hadn't synced yet.
2019-06-13 12:26:38 +02:00
Bruno Windels 624c6f0a6e get the txnId from the correct place to delete event after remote echo 2019-06-13 12:26:38 +02:00
Bruno Windels 7d2f7fae45 fix tests 2019-06-13 12:26:38 +02:00
Bruno Windels 3f917b39c9 fix lint 2019-06-13 12:26:38 +02:00
Bruno Windels f1336a5ce7 rename target id to related id and add jsdoc comments 2019-06-13 12:26:38 +02:00
Bruno Windels 7a10d504b2 emit Relations.redaction synchronously, timeout should not be needed
listeners shouldn't care about the original event, as it's removed
from the Relations collection already.
2019-06-13 12:26:38 +02:00
Bruno Windels 831aec6488 emit remote id once received so enqueued relations have it when sent 2019-06-13 12:26:38 +02:00
Bruno Windels 6eb229ac1e first look in pending event list for event being redacted
in case the redacted event hasn't been sent yet
2019-06-13 12:26:38 +02:00
Bruno Windels c58db665dd give the client a chance to run room.updatePendingEvent after sending
before the next event is sent. This is needed to update the target id
if it was the local id of the event that was just sent.
2019-06-13 12:26:38 +02:00
Bruno Windels e222fb1783 enqueue relations and redactions as well
as they might need to wait until their target has been sent
2019-06-13 12:26:38 +02:00
Hubert Chathi 4c6fa89053 various cross-signing fixes and improvements 2019-06-12 11:47:12 -04:00
Hubert Chathi 98815ffdf6 allow http request stub to ignore unhandled syncs 2019-06-12 11:41:26 -04:00
RiotRobot 31a0192c2d v2.0.1-rc.1 2019-06-12 11:47:36 +01:00
RiotRobot 53f8091e3a Prepare changelog for v2.0.1-rc.1 2019-06-12 11:47:35 +01:00
David Baker 012cbf7995 Merge pull request #952 from matrix-org/jryans/file-api-changes
Fix content uploads for modern browsers
2019-06-11 13:11:53 +01:00
J. Ryan Stinnett ac26c91cba Fix content uploads for modern browsers
Modern browsers now expose a `stream` function on the Blob and File interfaces.
This conflicts with an older style of passing data to the `uploadContent` SDK
method, which supported supplying the data to upload in the `stream` property of
an object.

Since this old style is still in active use in the Matrix JS ecosystem, we
preserve the backwards compatibility for now by checking whether `stream` is a
function.

This fix has been tested in Firefox Nightly (69), Firefox Release (67), Chrome
Canary (77), and Chrome Stable (75).

Fixes https://github.com/vector-im/riot-web/issues/9913
Fixes https://github.com/matrix-org/matrix-js-sdk/issues/949
2019-06-11 13:02:42 +01:00
David Baker c13162aada Merge pull request #951 from matrix-org/dbkr/one_request_at_a_time_two
Don't overlap auth submissions with polls
2019-06-11 10:42:32 +01:00
David Baker 9fb6eea8b7 Document what to use instead 2019-06-10 18:04:30 +01:00
David Baker 23c4f19cda lint 2019-06-10 16:29:55 +01:00
David Baker 3b34570749 doc background flag deprecation 2019-06-10 16:26:09 +01:00
David Baker 0412ca5810 make busyChanged optional 2019-06-10 16:24:20 +01:00
David Baker c80518bf3e s/setBusy/busyChanged/ 2019-06-10 16:23:06 +01:00
David Baker 61ee6eb8af This should be null, not false
Co-Authored-By: J. Ryan Stinnett <jryans@gmail.com>
2019-06-10 16:19:45 +01:00
David Baker 654e8b41fa Don't overlap auth submissions with polls
Wait for polls to complete before submitting auth dicts, otherwise
we risk the requests overlapping and both returning a 200.

Also introduces a setBusy interface to interactive-auth to explicitly
set the busy status, since otherwise it doesn't get set until the
request actually starts.
2019-06-10 15:18:46 +01:00
J. Ryan Stinnett 7080458f7e Merge pull request #945 from matrix-org/jryans/funding
Add funding details for GitHub sponsor button
2019-06-07 20:42:28 +01:00
J. Ryan Stinnett 08d236f5ec Add funding details for GitHub sponsor button 2019-06-07 11:46:56 +01:00
David Baker e332a7d113 Merge pull request #944 from matrix-org/dbkr/verify_signature_modifies_the_object_because_everything_is_awful
Fix backup sig validation with multiple sigs
2019-06-07 11:26:13 +01:00
David Baker 7879709f62 Fix backup sig validation with multiple sigs
verifySignature modifies the object so we need to clone if we're
verifying more than one signature.

Fixes https://github.com/vector-im/riot-web/issues/9357
2019-06-07 11:05:44 +01:00
David Baker 56e030762e Merge pull request #943 from matrix-org/dbkr/wait_for_token_request
Don't send another token request while one's in flight
2019-06-06 19:18:20 +01:00
David Baker bac73150ca Don't send another token request while one's in flight
Otherwise we end up with more tokens than are strictly necessary
2019-06-06 19:03:29 +01:00
David Baker 2e1fb15ada Merge pull request #942 from matrix-org/dbkr/one_poll_at_a_time
Don't poll UI auth again until current poll finishes
2019-06-06 18:36:34 +01:00
David Baker ae9bcd6f6c Don't poll UI auth again until current poll finishes
On slow networks/servers we were ending up with lots of requests in
flight.
2019-06-06 18:31:54 +01:00
Travis Ralston c18c679b9b Merge pull request #938 from matrix-org/travis/fail-fast-but-not-too-fast
Provide the discovered URLs when a liveliness error occurs
2019-06-06 09:03:39 -06:00
Travis Ralston d014ee0b72 Merge pull request #941 from matrix-org/travis/redact-v3
Encode event IDs when redacting events
2019-06-06 08:29:15 -06:00
Travis Ralston a30ef7250b Encode event IDs when redacting events
Because v3 rooms are a thing.
2019-06-05 15:27:55 -06:00
Hubert Chathi 6f6e7ea921 verify cross-signing key with SAS 2019-06-05 15:27:31 -04:00
Hubert Chathi 0c714ba4a1 some cleanups 2019-06-05 15:24:03 -04:00
Hubert Chathi 5f539aacd9 Merge branch 'develop' into dbkr/cross_signing 2019-06-05 15:21:17 -04:00
Hubert Chathi 570ce4f4b7 Merge pull request #940 from uhoreg/fix_missing_logger
add missing logger
2019-06-05 14:49:38 -04:00
Hubert Chathi 3c7c9048eb add missing logger 2019-06-05 14:04:39 -04:00
Hubert Chathi 6a77df7b41 Merge branch 'develop' into dbkr/cross_signing 2019-06-05 12:48:17 -04:00
Hubert Chathi 41243757ee Merge pull request #939 from uhoreg/unpicky_verification
verification: don't error if we don't know about some keys
2019-06-05 12:18:53 -04:00
Hubert Chathi 2af311bd7d verification: don't error if we don't know about some keys 2019-06-05 11:56:37 -04:00
Bruno Windels 1bc9ee7110 Merge pull request #937 from matrix-org/bwindels/redactions-local-echo
Local echo for redactions
2019-06-05 07:49:31 +00:00
Bruno Windels 4e040f8e77 correct comments about redaction events 2019-06-05 09:41:52 +02:00
Travis Ralston 26c1c6db3b Fix tests and populate the right IS validation object 2019-06-04 23:51:41 -06:00
Travis Ralston d38da83656 Provide the discovered URLs when a liveliness error occurs
See https://github.com/vector-im/riot-web/issues/9828
2019-06-04 23:39:31 -06:00
Hubert Chathi 4a9a1b40e9 initial implementation of secret storage and sharing 2019-06-04 15:04:45 -04:00
Hubert Chathi dc971b9a59 add missing semicolon 2019-06-04 14:58:46 -04:00
Bruno Windels 58f163ed5c emit Room.redactionCancelled event when undoing redaction local echo 2019-06-04 18:45:13 +02:00
Bruno Windels c0c9f0122c remove leftover newline 2019-06-04 16:08:14 +02:00
Bruno Windels d33395e46d improve naming and commenting for _aggregateNonLiveRelation 2019-06-04 15:54:31 +02:00
Bruno Windels b83c7d3929 unneeded check, as redacted_because is now also set for local echo 2019-06-04 15:49:52 +02:00
Bruno Windels b5df016b1b remove unused method 2019-06-04 15:28:40 +02:00
Bruno Windels a8b6be3b38 also set redacted_because with redaction local echo 2019-06-04 13:37:24 +02:00
Bruno Windels 78cf175f5a also look for redaction local echo event in pendingList
also re-aggregate the relation if it's redaction has been cancelled
2019-06-04 11:55:48 +02:00
Travis Ralston 1b78856a7d Merge pull request #934 from matrix-org/travis/re-check-version
Refresh safe room versions when the server looks more modern than us
2019-06-04 01:40:57 -06:00
Bruno Windels 8194287391 make redactEvent go through same local-echo aware path as other events 2019-06-03 18:37:25 +02:00
Bruno Windels 2eecea9a07 handle redactions in room pending event logic 2019-06-03 18:37:01 +02:00
Bruno Windels 465032dd4f support marking an event as redacted in a way we can undo it later
in case the redaction can't be sent
2019-06-03 18:36:03 +02:00
Travis Ralston e473315a89 Check for the correct capability when refreshing 2019-06-03 09:56:55 -06:00
Travis Ralston 9d34ad5287 Merge pull request #935 from matrix-org/travis/v4-rooms
Add v4 as a safe room version
2019-06-03 07:11:13 -06:00
Travis Ralston a532cc5cf9 Add v4 as a safe room version
It's listed as stable in the spec, and this is for our fallback.
2019-06-02 23:36:02 -06:00
Travis Ralston 60c6c5bc41 Refresh safe room versions when the server looks more modern than us
Fixes https://github.com/vector-im/riot-web/issues/9845
2019-06-02 23:34:58 -06:00
J. Ryan Stinnett 0cbbbe8503 Merge pull request #933 from matrix-org/jryans/disable-guard-for-in
Disable guard-for-in rule
2019-05-31 18:10:57 +01:00
J. Ryan Stinnett 47a8d3e50a Disable guard-for-in rule
The Google code style config for ESLint turns on `guard-for-in` to require
for-in loops to check own properties. This makes it annoying to iterate objects,
and we seem to disable the rule by line comments when it comes up anyway, so
this just disables it globally.
2019-05-31 16:36:54 +01:00
RiotRobot 304da09f3b Merge branch 'master' into develop 2019-05-31 10:30:58 +01:00
J. Ryan Stinnett 137c6919f6 Fix undefined logger in webstorage.js 2019-05-31 10:01:32 +01:00
J. Ryan Stinnett 842ce30190 Fix lint error in login.spec.js 2019-05-31 09:54:47 +01:00
Travis Ralston a0456dc430 Merge pull request #924 from jkasun/loglevel-extend
Extend loglevel logging for the whole project
2019-05-30 11:20:49 -06:00
Travis Ralston 52ec831b16 Merge pull request #930 from stalniy/fix/save-login-state
fix(login): saves access_token and user_id after login for all login types
2019-05-30 11:20:01 -06:00
janith 9a2bf78a8e logger.dir changed to a log 2019-05-30 12:58:32 +05:30
janith cb16f7a60b Minor Fixes 2019-05-30 09:35:37 +05:30
janith ad84631ddb Change logger import to ES6 2019-05-30 09:27:25 +05:30
Hubert Chathi 95131c7658 add test for syncing trust on another user 2019-05-29 17:01:25 -04:00
Hubert Chathi 936eef194a minor fixes to tests 2019-05-29 17:01:13 -04:00
Hubert Chathi 941d871daf fix check for empty cross-signing repsonse 2019-05-29 16:59:51 -04:00
Hubert Chathi 609ee663fa use the right path for logger 2019-05-29 16:58:49 -04:00
RiotRobot d78426d708 Merge branch 'master' into develop 2019-05-29 15:53:51 +01:00
Hubert Chathi 53804cac5c save cross-signing keys from sync and verify new keys for user 2019-05-28 22:28:54 -04:00
Sergii Stotskyi 4ef970b4da fix(login): saves access_token and user_id after login for all login types
Signed-off-by: Sergii Stotskyi <sergiy.stotskiy@gmail.com>

Fixes #876
2019-05-28 17:47:49 +03:00
Travis Ralston b199f133b3 fixup readme 2019-05-27 10:41:04 -06:00
Travis Ralston 7d1b183a1b Merge pull request #929 from spantaleev/fix-non-integer-media-sizes
Do not try to request thumbnails with non-integer sizes
2019-05-27 08:37:03 -06:00
Slavi Pantaleev 49d119e92e Do not try to request thumbnails with non-integer sizes
Issue described in: https://github.com/vector-im/riot-web/issues/9690

matrix-react-sdk was patched separately, so that it won't call `mxcUrlToHttp()`
(and in turn `getHttpUriForMxc()`) with non-integer sizes.

This patch fixes the JS SDK as well, hoping to prevent the same issue
from happening on other clients (besides matrix-react-sdk / riot-web).

Signed-off-by: Slavi Pantaleev <slavi@devture.com>
2019-05-27 11:25:35 +03:00
Janith Kasun feed1da570 Merge branch 'develop' into loglevel-extend 2019-05-24 18:00:40 +05:30
Hubert Chathi 193ad9e09d use 3 keys for cross-signing 2019-05-23 18:18:21 -04:00
Travis Ralston a3ad835d84 Merge pull request #928 from matrix-org/revert-927-travis/wk-discovery
Revert "Add a bunch of debugging to .well-known IS validation"
2019-05-23 14:47:48 -06:00
Travis Ralston f8afee8ebd Revert "Add a bunch of debugging to .well-known IS validation" 2019-05-23 14:47:38 -06:00
Travis Ralston 7e955fc312 Merge pull request #927 from matrix-org/travis/wk-discovery
Add a bunch of debugging to .well-known IS validation
2019-05-23 14:19:33 -06:00
Travis Ralston eebf92366f Add a bunch of debugging to .well-known IS validation 2019-05-23 14:00:17 -06:00
RiotRobot 3c23e166a7 Upgrade jsdoc 2019-05-23 16:30:30 +01:00
Janith Kasun de8063a43a Merge branch 'develop' into loglevel-extend 2019-05-19 09:40:38 +05:30
jkasun a73dabcb67 Console logging to loglevel 2019-05-19 09:29:40 +05:30
Hubert Chathi 405451d783 complete some more unit tests 2019-05-03 23:23:08 -04:00
Hubert Chathi b0275afac2 remove some debugging lines 2019-05-03 23:22:51 -04:00
Hubert Chathi ae71f41138 add missing files 2019-05-03 18:12:17 -04:00
Hubert Chathi ec2f07e1aa add methods for signing and checking users and devices with cross-signing 2019-05-03 18:05:36 -04:00
Hubert Chathi 32814d1833 Merge branch 'develop' into dbkr/cross_signing 2019-04-03 19:28:51 -04:00
David Baker e54f71718f Olm pre2 for cross-signing 2019-02-05 13:41:14 +00:00
David Baker 7f5584e4f5 All the linting 2019-02-05 13:03:27 +00:00
David Baker b3513dc8f8 Make linting rules more consistent
* Put back babel-eslint for class-properties
 * Allow arrow functions without params

This makes the style more consistent with react-sdk.

NB. The line lengths are still inconsistent but it's not clear which
way to go on that yet.
2019-02-05 11:56:08 +00:00
David Baker 1b82dffcb4 Merge remote-tracking branch 'origin/develop' into dbkr/cross_signing 2019-02-01 22:40:14 +00:00
David Baker 5500f0d794 Re-track own device list
Sp we don't stop tracking our own
2019-02-01 22:39:12 +00:00
David Baker c8082535de Always track your own devices
This was causing all the cross-signing stuff to fail and was almost
certainly the cause of https://github.com/vector-im/riot-web/issues/8213
2019-02-01 19:19:00 +00:00
David Baker 7dedcb82b2 Lint
or at least the rules that are consistent with the rest of our
codebase
2019-02-01 18:12:27 +00:00
David Baker 7195365188 Update package-lock.json
because Travis and npm now have a thing where they combust if your
package-lock is out of sync
2019-02-01 15:59:53 +00:00
David Baker 910d0ec9c1 Sign & trust the key backup from the SSK 2019-02-01 15:49:20 +00:00
David Baker 1d58a64ee1 Track SSKs for users
and verify our own against our locally stored private part
2019-02-01 13:04:21 +00:00
David Baker 1f77cc6d1a Cross sign the current device with the SSK
whenever we get the SSK, ie. when creating or restoring a backup
2019-01-31 21:13:01 +00:00
David Baker 02d4dcb128 Store SSK & USK in crypto store
and restore them from the key backup.

NB. This has an interface change to restoreKeyBackup where I've
changed it to take a backupInfo rather than a version (this also
saves us re-fetching the backup metadata in the case of a passphrase
restore).
2019-01-31 15:48:05 +00:00
David Baker 2b54f442d1 Add cross signing key creation into key backup
Start of cross-signing impl
2019-01-30 18:10:40 +00:00
99 changed files with 9683 additions and 2438 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{
"presets": ["es2015"],
"presets": ["es2015", "es2016"],
"plugins": [
"transform-class-properties",
+1 -1
View File
@@ -7,7 +7,7 @@ steps:
- docker#v3.0.1:
image: "node:10"
- label: ":karma: Tests"
- label: ":jest: Tests"
command:
- "yarn install"
- "yarn test"
+3
View File
@@ -12,10 +12,12 @@ module.exports = {
// babel's transform-runtime converts references to ES6 globals such as
// Promise and Map to core-js polyfills, so we can use ES6 globals.
es6: true,
jest: true,
},
extends: ["eslint:recommended", "google"],
plugins: [
"babel",
"jest",
],
rules: {
// rules we've always adhered to or now do
@@ -53,6 +55,7 @@ module.exports = {
// rules we do not want from the google styleguide
"object-curly-spacing": ["off"],
"spaced-comment": ["off"],
"guard-for-in": ["off"],
// in principle we prefer single quotes, but life is too short
quotes: ["off"],
+2
View File
@@ -0,0 +1,2 @@
patreon: matrixdotorg
liberapay: matrixdotorg
-1
View File
@@ -10,7 +10,6 @@ build/Release
coverage
lib-cov
out
reports
/dist
/lib
/specbuild
+401
View File
@@ -1,3 +1,404 @@
Changes in [2.4.6](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.6) (2019-12-09)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.6-rc.1...v2.4.6)
* No changes since rc.1
Changes in [2.4.6-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.6-rc.1) (2019-12-04)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.5...v2.4.6-rc.1)
* Update alias handling
[\#1102](https://github.com/matrix-org/matrix-js-sdk/pull/1102)
* increase timeout on flush to fix failing unit test
[\#1096](https://github.com/matrix-org/matrix-js-sdk/pull/1096)
* Disable broken cross-signing test
[\#1095](https://github.com/matrix-org/matrix-js-sdk/pull/1095)
* Fix a couple SAS tests
[\#1094](https://github.com/matrix-org/matrix-js-sdk/pull/1094)
* Fix Olm unwedging test
[\#1093](https://github.com/matrix-org/matrix-js-sdk/pull/1093)
* Fix empty string handling in push notifications
[\#1089](https://github.com/matrix-org/matrix-js-sdk/pull/1089)
* expand e2ee logging to better debug UISIs
[\#1090](https://github.com/matrix-org/matrix-js-sdk/pull/1090)
* Remove Bluebird: phase 2
[\#1087](https://github.com/matrix-org/matrix-js-sdk/pull/1087)
* Relax identity server discovery checks to FAIL_PROMPT
[\#1062](https://github.com/matrix-org/matrix-js-sdk/pull/1062)
* Fix incorrect return value of MatrixClient.prototype.uploadKeys
[\#1061](https://github.com/matrix-org/matrix-js-sdk/pull/1061)
* Fix calls in e2e rooms
[\#1086](https://github.com/matrix-org/matrix-js-sdk/pull/1086)
* Monitor verification request over DM as well
[\#1085](https://github.com/matrix-org/matrix-js-sdk/pull/1085)
* Remove 'check' npm script
[\#1084](https://github.com/matrix-org/matrix-js-sdk/pull/1084)
* Always process call events in batches
[\#1083](https://github.com/matrix-org/matrix-js-sdk/pull/1083)
* Fix ringing chirp on loading
[\#1082](https://github.com/matrix-org/matrix-js-sdk/pull/1082)
* Remove *most* bluebird specific things
[\#1081](https://github.com/matrix-org/matrix-js-sdk/pull/1081)
* Switch to Jest
[\#1080](https://github.com/matrix-org/matrix-js-sdk/pull/1080)
Changes in [2.4.5](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.5) (2019-11-27)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.4...v2.4.5)
* Relax identity server discovery checks to FAIL_PROMPT
* Expand E2EE debug logging to diagnose "unable to decrypt" errors
Changes in [2.4.4](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.4) (2019-11-25)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.4-rc.1...v2.4.4)
* No changes since rc.1
Changes in [2.4.4-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.4-rc.1) (2019-11-20)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.3...v2.4.4-rc.1)
* Fix SAS verification in encrypted DMs
[\#1077](https://github.com/matrix-org/matrix-js-sdk/pull/1077)
* Cross-signing / secret storage tweaks
[\#1078](https://github.com/matrix-org/matrix-js-sdk/pull/1078)
* Fix local trust for key backups
[\#1075](https://github.com/matrix-org/matrix-js-sdk/pull/1075)
* Add method to get last active timestamp in room
[\#1072](https://github.com/matrix-org/matrix-js-sdk/pull/1072)
* Check the right Synapse endpoint for determining admin capabilities
[\#1071](https://github.com/matrix-org/matrix-js-sdk/pull/1071)
* Cross Signing Support
[\#832](https://github.com/matrix-org/matrix-js-sdk/pull/832)
* Don't double cancel verification request
[\#1064](https://github.com/matrix-org/matrix-js-sdk/pull/1064)
* Support for verification requests in the timeline
[\#1067](https://github.com/matrix-org/matrix-js-sdk/pull/1067)
* Use stable API prefix for 3PID APIs when supported
[\#1066](https://github.com/matrix-org/matrix-js-sdk/pull/1066)
* Remove Jenkins scripts
[\#1063](https://github.com/matrix-org/matrix-js-sdk/pull/1063)
Changes in [2.4.3](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.3) (2019-11-04)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.3-rc.1...v2.4.3)
* No changes since rc.1
Changes in [2.4.3-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.3-rc.1) (2019-10-30)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.2...v2.4.3-rc.1)
* fix the path in references to logger.js
[\#1056](https://github.com/matrix-org/matrix-js-sdk/pull/1056)
* verification in DMs
[\#1050](https://github.com/matrix-org/matrix-js-sdk/pull/1050)
* Properly documented the function possible returns
[\#1054](https://github.com/matrix-org/matrix-js-sdk/pull/1054)
* Downgrade to Bluebird 3.5.5 to fix Firefox
[\#1055](https://github.com/matrix-org/matrix-js-sdk/pull/1055)
* Upgrade safe deps to latest major version
[\#1053](https://github.com/matrix-org/matrix-js-sdk/pull/1053)
* Don't include .js in the import string.
[\#1052](https://github.com/matrix-org/matrix-js-sdk/pull/1052)
Changes in [2.4.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.2) (2019-10-18)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.2-rc.1...v2.4.2)
* No changes since v2.4.2-rc.1
Changes in [2.4.2-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.2-rc.1) (2019-10-09)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.1...v2.4.2-rc.1)
* Log state of Olm sessions
[\#1047](https://github.com/matrix-org/matrix-js-sdk/pull/1047)
* Add method to get access to all timelines
[\#1048](https://github.com/matrix-org/matrix-js-sdk/pull/1048)
Changes in [2.4.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.1) (2019-10-01)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.0...v2.4.1)
* Upgrade deps
[\#1046](https://github.com/matrix-org/matrix-js-sdk/pull/1046)
* Ignore crypto events with no content
[\#1043](https://github.com/matrix-org/matrix-js-sdk/pull/1043)
Changes in [2.4.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.0) (2019-09-27)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.0-rc.1...v2.4.0)
* Clean Yarn cache during release
[\#1045](https://github.com/matrix-org/matrix-js-sdk/pull/1045)
Changes in [2.4.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.0-rc.1) (2019-09-25)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.3.2...v2.4.0-rc.1)
* Remove id_server from creds for interactive auth
[\#1044](https://github.com/matrix-org/matrix-js-sdk/pull/1044)
* Remove IS details from requestToken to HS
[\#1041](https://github.com/matrix-org/matrix-js-sdk/pull/1041)
* Add support for sending MSISDN tokens to alternate URLs
[\#1040](https://github.com/matrix-org/matrix-js-sdk/pull/1040)
* Add separate 3PID add and bind APIs
[\#1038](https://github.com/matrix-org/matrix-js-sdk/pull/1038)
* Bump eslint-utils from 1.4.0 to 1.4.2
[\#1037](https://github.com/matrix-org/matrix-js-sdk/pull/1037)
* Handle WebRTC security errors as non-fatal
[\#1036](https://github.com/matrix-org/matrix-js-sdk/pull/1036)
* Check for r0.6.0 support in addition to unstable feature flags
[\#1035](https://github.com/matrix-org/matrix-js-sdk/pull/1035)
* Update room members on member event redaction
[\#1030](https://github.com/matrix-org/matrix-js-sdk/pull/1030)
* Support hidden read receipts
[\#1028](https://github.com/matrix-org/matrix-js-sdk/pull/1028)
* Do 3pid lookups in lowercase
[\#1029](https://github.com/matrix-org/matrix-js-sdk/pull/1029)
* Add Synapse admin functions for deactivating a user
[\#1027](https://github.com/matrix-org/matrix-js-sdk/pull/1027)
* Fix addPendingEvent with pending event order == chronological
[\#1026](https://github.com/matrix-org/matrix-js-sdk/pull/1026)
* Add AutoDiscovery.getRawClientConfig() for easy .well-known lookups
[\#1024](https://github.com/matrix-org/matrix-js-sdk/pull/1024)
* Don't convert errors to JSON if they are JSON already
[\#1025](https://github.com/matrix-org/matrix-js-sdk/pull/1025)
* Send id_access_token to HS for use in proxied IS requests
[\#1022](https://github.com/matrix-org/matrix-js-sdk/pull/1022)
* Clean up JSON handling in identity server requests
[\#1023](https://github.com/matrix-org/matrix-js-sdk/pull/1023)
* Use the v2 (hashed) lookup for identity server queries
[\#1021](https://github.com/matrix-org/matrix-js-sdk/pull/1021)
* Add getIdServer() & doesServerRequireIdServerParam()
[\#1018](https://github.com/matrix-org/matrix-js-sdk/pull/1018)
* Make requestToken endpoints work without ID Server
[\#1019](https://github.com/matrix-org/matrix-js-sdk/pull/1019)
* Fix setIdentityServer
[\#1016](https://github.com/matrix-org/matrix-js-sdk/pull/1016)
* Change ICE fallback server and make fallback opt-in
[\#1015](https://github.com/matrix-org/matrix-js-sdk/pull/1015)
* Throw an exception if trying to do an ID server request with no ID server
[\#1014](https://github.com/matrix-org/matrix-js-sdk/pull/1014)
* Add setIdentityServerUrl
[\#1013](https://github.com/matrix-org/matrix-js-sdk/pull/1013)
* Add matrix base API to report an event
[\#1011](https://github.com/matrix-org/matrix-js-sdk/pull/1011)
* Fix POST body for v2 IS requests
[\#1010](https://github.com/matrix-org/matrix-js-sdk/pull/1010)
* Add API for bulk lookup on the Identity Server
[\#1009](https://github.com/matrix-org/matrix-js-sdk/pull/1009)
* Remove deprecated authedRequestWithPrefix and requestWithPrefix
[\#1000](https://github.com/matrix-org/matrix-js-sdk/pull/1000)
* Add API for checking IS account info
[\#1007](https://github.com/matrix-org/matrix-js-sdk/pull/1007)
* Support rewriting push rules when our internal defaults change
[\#1006](https://github.com/matrix-org/matrix-js-sdk/pull/1006)
* Upgrade dependencies
[\#1005](https://github.com/matrix-org/matrix-js-sdk/pull/1005)
Changes in [2.3.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.3.2) (2019-09-16)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.3.2-rc.1...v2.3.2)
* [Release] Fix addPendingEvent with pending event order == chronological
[\#1034](https://github.com/matrix-org/matrix-js-sdk/pull/1034)
Changes in [2.3.2-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.3.2-rc.1) (2019-09-13)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.3.1...v2.3.2-rc.1)
* Synapse admin functions to release
[\#1033](https://github.com/matrix-org/matrix-js-sdk/pull/1033)
* [To Release] Add matrix base API to report an event
[\#1032](https://github.com/matrix-org/matrix-js-sdk/pull/1032)
Changes in [2.3.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.3.1) (2019-09-12)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.3.1-rc.1...v2.3.1)
* No changes since rc.1
Changes in [2.3.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.3.1-rc.1) (2019-09-11)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.3.0...v2.3.1-rc.1)
* Update room members on member event redaction
[\#1031](https://github.com/matrix-org/matrix-js-sdk/pull/1031)
Changes in [2.3.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.3.0) (2019-08-05)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.3.0-rc.1...v2.3.0)
* [release] Support rewriting push rules when our internal defaults change
[\#1008](https://github.com/matrix-org/matrix-js-sdk/pull/1008)
Changes in [2.3.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.3.0-rc.1) (2019-07-31)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.2.0...v2.3.0-rc.1)
* Add support for IS v2 API with authentication
[\#1002](https://github.com/matrix-org/matrix-js-sdk/pull/1002)
* Tombstone bugfixes
[\#1001](https://github.com/matrix-org/matrix-js-sdk/pull/1001)
* Support for MSC2140 (terms of service for IS/IM)
[\#988](https://github.com/matrix-org/matrix-js-sdk/pull/988)
* Add a request method to /devices
[\#994](https://github.com/matrix-org/matrix-js-sdk/pull/994)
Changes in [2.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.2.0) (2019-07-18)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.2.0-rc.2...v2.2.0)
* Upgrade lodash dependencies
Changes in [2.2.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.2.0-rc.2) (2019-07-12)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.2.0-rc.1...v2.2.0-rc.2)
* Fix regression from 2.2.0-rc.1 in request to /devices
[\#995](https://github.com/matrix-org/matrix-js-sdk/pull/995)
Changes in [2.2.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.2.0-rc.1) (2019-07-12)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.1.1...v2.2.0-rc.1)
* End the verification timer when verification is done
[\#993](https://github.com/matrix-org/matrix-js-sdk/pull/993)
* Stabilize usage of stably stable APIs (in a stable way)
[\#990](https://github.com/matrix-org/matrix-js-sdk/pull/990)
* Expose original_event for /relations
[\#987](https://github.com/matrix-org/matrix-js-sdk/pull/987)
* Process ephemeral events outside timeline handling
[\#989](https://github.com/matrix-org/matrix-js-sdk/pull/989)
* Don't accept any locally known edits earlier than the last known server-side
aggregated edit
[\#986](https://github.com/matrix-org/matrix-js-sdk/pull/986)
* Get edit date transparently from server aggregations or local echo
[\#984](https://github.com/matrix-org/matrix-js-sdk/pull/984)
* Add a function to flag keys for backup without scheduling a backup
[\#982](https://github.com/matrix-org/matrix-js-sdk/pull/982)
* Block read marker and read receipt from advancing into pending events
[\#981](https://github.com/matrix-org/matrix-js-sdk/pull/981)
* Upgrade dependencies
[\#977](https://github.com/matrix-org/matrix-js-sdk/pull/977)
* Add default push rule to ignore reactions
[\#976](https://github.com/matrix-org/matrix-js-sdk/pull/976)
* Fix exception whilst syncing
[\#979](https://github.com/matrix-org/matrix-js-sdk/pull/979)
* Include the error object when raising Session.logged_out
[\#975](https://github.com/matrix-org/matrix-js-sdk/pull/975)
Changes in [2.1.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.1.1) (2019-07-11)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.1.0...v2.1.1)
* Process emphemeral events outside timeline handling
[\#989](https://github.com/matrix-org/matrix-js-sdk/pull/989)
Changes in [2.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.1.0) (2019-07-08)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.1.0-rc.1...v2.1.0)
* Fix exception whilst syncing
[\#979](https://github.com/matrix-org/matrix-js-sdk/pull/979)
Changes in [2.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.1.0-rc.1) (2019-07-03)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.0.1...v2.1.0-rc.1)
* Handle self read receipts for fixing e2e notification counts
[\#974](https://github.com/matrix-org/matrix-js-sdk/pull/974)
* Add redacts field to event.toJSON
[\#973](https://github.com/matrix-org/matrix-js-sdk/pull/973)
* Handle associated event send failures
[\#972](https://github.com/matrix-org/matrix-js-sdk/pull/972)
* Remove irrelevant debug line from timeline handling
[\#971](https://github.com/matrix-org/matrix-js-sdk/pull/971)
* Handle relations in encrypted rooms
[\#969](https://github.com/matrix-org/matrix-js-sdk/pull/969)
* Relations endpoint support
[\#967](https://github.com/matrix-org/matrix-js-sdk/pull/967)
* Disable event encryption for reactions
[\#968](https://github.com/matrix-org/matrix-js-sdk/pull/968)
* Change the known safe room version to version 4
[\#966](https://github.com/matrix-org/matrix-js-sdk/pull/966)
* Check for lazy-loading support in the spec versions instead
[\#965](https://github.com/matrix-org/matrix-js-sdk/pull/965)
* Use camelCase instead of underscore
[\#963](https://github.com/matrix-org/matrix-js-sdk/pull/963)
* Time out verification attempts after 10 minutes of inactivity
[\#961](https://github.com/matrix-org/matrix-js-sdk/pull/961)
* Don't handle key verification requests which are immediately cancelled
[\#962](https://github.com/matrix-org/matrix-js-sdk/pull/962)
Changes in [2.0.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.0.1) (2019-06-19)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.0.1-rc.2...v2.0.1)
No changes since rc.2
Changes in [2.0.1-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.0.1-rc.2) (2019-06-18)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.0.1-rc.1...v2.0.1-rc.2)
* return 'sending' status for an event that is only locally redacted
[\#960](https://github.com/matrix-org/matrix-js-sdk/pull/960)
* Key verification request fixes
[\#954](https://github.com/matrix-org/matrix-js-sdk/pull/954)
* Add flag to force saving sync store
[\#956](https://github.com/matrix-org/matrix-js-sdk/pull/956)
* Expose the inhibit_login flag to register
[\#953](https://github.com/matrix-org/matrix-js-sdk/pull/953)
* Support redactions and relations of/with unsent events.
[\#947](https://github.com/matrix-org/matrix-js-sdk/pull/947)
Changes in [2.0.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.0.1-rc.1) (2019-06-12)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.0.0...v2.0.1-rc.1)
* Fix content uploads for modern browsers
[\#952](https://github.com/matrix-org/matrix-js-sdk/pull/952)
* Don't overlap auth submissions with polls
[\#951](https://github.com/matrix-org/matrix-js-sdk/pull/951)
* Add funding details for GitHub sponsor button
[\#945](https://github.com/matrix-org/matrix-js-sdk/pull/945)
* Fix backup sig validation with multiple sigs
[\#944](https://github.com/matrix-org/matrix-js-sdk/pull/944)
* Don't send another token request while one's in flight
[\#943](https://github.com/matrix-org/matrix-js-sdk/pull/943)
* Don't poll UI auth again until current poll finishes
[\#942](https://github.com/matrix-org/matrix-js-sdk/pull/942)
* Provide the discovered URLs when a liveliness error occurs
[\#938](https://github.com/matrix-org/matrix-js-sdk/pull/938)
* Encode event IDs when redacting events
[\#941](https://github.com/matrix-org/matrix-js-sdk/pull/941)
* add missing logger
[\#940](https://github.com/matrix-org/matrix-js-sdk/pull/940)
* verification: don't error if we don't know about some keys
[\#939](https://github.com/matrix-org/matrix-js-sdk/pull/939)
* Local echo for redactions
[\#937](https://github.com/matrix-org/matrix-js-sdk/pull/937)
* Refresh safe room versions when the server looks more modern than us
[\#934](https://github.com/matrix-org/matrix-js-sdk/pull/934)
* Add v4 as a safe room version
[\#935](https://github.com/matrix-org/matrix-js-sdk/pull/935)
* Disable guard-for-in rule
[\#933](https://github.com/matrix-org/matrix-js-sdk/pull/933)
* Extend loglevel logging for the whole project
[\#924](https://github.com/matrix-org/matrix-js-sdk/pull/924)
* fix(login): saves access_token and user_id after login for all login types
[\#930](https://github.com/matrix-org/matrix-js-sdk/pull/930)
* Do not try to request thumbnails with non-integer sizes
[\#929](https://github.com/matrix-org/matrix-js-sdk/pull/929)
* Revert "Add a bunch of debugging to .well-known IS validation"
[\#928](https://github.com/matrix-org/matrix-js-sdk/pull/928)
* Add a bunch of debugging to .well-known IS validation
[\#927](https://github.com/matrix-org/matrix-js-sdk/pull/927)
Changes in [2.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.0.0) (2019-05-31)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.2.0...v2.0.0)
+4 -5
View File
@@ -1,8 +1,7 @@
Matrix Javascript SDK
=====================
[![Build Status](http://matrix.org/jenkins/buildStatus/icon?job=JavascriptSDK)](http://matrix.org/jenkins/job/JavascriptSDK/)
This is the [Matrix](https://matrix.org) Client-Server v1/v2 alpha SDK for
This is the [Matrix](https://matrix.org) Client-Server r0 SDK for
JavaScript. This SDK can be run in a browser or in Node.js.
Quickstart
@@ -297,7 +296,7 @@ Then visit ``http://localhost:8005`` to see the API docs.
End-to-end encryption support
=============================
The SDK supports end-to-end encryption via the and Megolm protocols, using
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.
@@ -323,13 +322,13 @@ To provide the Olm library in a browser application:
To provide the Olm library in a node.js application:
* ``yarn add https://packages.matrix.org/npm/olm/olm-3.0.0.tgz``
* ``yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz``
(replace the URL with the latest version you want to use from
https://packages.matrix.org/npm/olm/)
* ``global.Olm = require('olm');`` *before* loading ``matrix-js-sdk``.
If you want to package Olm as dependency for your node.js application, you can
use ``yarn add https://packages.matrix.org/npm/olm/olm-3.0.0.tgz``. If your
use ``yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz``. If your
application also works without e2e crypto enabled, add ``--optional`` to mark it
as an optional dependency.
-36
View File
@@ -1,36 +0,0 @@
#!/bin/bash -l
set -x
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm use 10 || exit $?
yarn install || exit $?
RC=0
function fail {
echo $@ >&2
RC=1
}
# don't use last time's test reports
rm -rf reports coverage || exit $?
yarn test || fail "yarn test finished with return code $?"
yarn -s lint -f checkstyle > eslint.xml ||
fail "eslint finished with return code $?"
# delete the old tarball, if it exists
rm -f matrix-js-sdk-*.tgz
# `yarn pack` doesn't seem to run scripts, however that seems okay here as we
# just built as part of `install` above.
yarn pack ||
fail "yarn pack finished with return code $?"
yarn gendoc || fail "JSDoc failed with code $?"
exit $RC
+16 -21
View File
@@ -1,14 +1,11 @@
{
"name": "matrix-js-sdk",
"version": "2.0.0",
"version": "2.4.6",
"description": "Matrix Client-Server SDK for Javascript",
"main": "index.js",
"scripts": {
"test:build": "babel -s -d specbuild spec",
"test:run": "istanbul cover --report text --report cobertura --config .istanbul.yml -i \"lib/**/*.js\" node_modules/mocha/bin/_mocha -- --recursive specbuild --colors --reporter mocha-jenkins-reporter --reporter-options junit_report_path=reports/test-results.xml",
"test:watch": "mocha --watch --compilers js:babel-core/register --recursive spec --colors",
"test": "yarn test:build && yarn test:run",
"check": "yarn test:build && _mocha --recursive specbuild --colors",
"test:watch": "jest spec/ --coverage --testEnvironment node --watch",
"test": "jest spec/ --coverage --testEnvironment node",
"gendoc": "babel --no-babelrc --plugins transform-class-properties -d .jsdocbuild src && jsdoc -r .jsdocbuild -P package.json -R README.md -d .jsdoc",
"start": "yarn start:init && yarn start:watch",
"start:watch": "babel -s -w --skip-initial-build -d lib src",
@@ -17,7 +14,7 @@
"build": "babel -s -d lib src && rimraf dist && mkdir dist && browserify -d browser-index.js | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js && terser -c -m -o dist/browser-matrix.min.js --source-map \"content='dist/browser-matrix.js.map'\" dist/browser-matrix.js",
"dist": "yarn build",
"watch": "watchify -d browser-index.js -o 'exorcist dist/browser-matrix.js.map > dist/browser-matrix.js' -v",
"lint": "eslint --max-warnings 101 src spec",
"lint": "eslint --max-warnings 93 src spec",
"prepare": "yarn clean && yarn build && git rev-parse HEAD > git-revision.txt"
},
"repository": {
@@ -44,7 +41,6 @@
"git-revision.txt",
"index.js",
"browser-index.js",
"jenkins.sh",
"lib",
"package.json",
"release.sh",
@@ -54,11 +50,11 @@
"dependencies": {
"another-json": "^0.2.0",
"babel-runtime": "^6.26.0",
"bluebird": "^3.5.0",
"bluebird": "3.5.5",
"browser-request": "^0.3.3",
"bs58": "^4.0.1",
"content-type": "^1.0.2",
"loglevel": "1.6.1",
"loglevel": "^1.6.4",
"qs": "^6.5.2",
"request": "^2.88.0",
"unhomoglyph": "^1.0.2"
@@ -66,28 +62,27 @@
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.6.0",
"babel-plugin-transform-async-to-bluebird": "^1.1.1",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-es2016": "^6.24.1",
"browserify": "^16.2.3",
"browserify-shim": "^3.8.13",
"eslint": "^5.12.0",
"eslint-config-google": "^0.7.1",
"eslint-plugin-babel": "^5.3.0",
"exorcist": "^0.4.0",
"expect": "^1.20.2",
"istanbul": "^0.4.5",
"eslint-plugin-jest": "^23.0.4",
"exorcist": "^1.0.1",
"jest": "^23.6.0",
"jsdoc": "^3.5.5",
"lolex": "^1.5.2",
"matrix-mock-request": "^1.2.3",
"mocha": "^5.2.0",
"mocha-jenkins-reporter": "^0.4.0",
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.0.tgz",
"rimraf": "^2.5.4",
"source-map-support": "^0.4.11",
"sourceify": "^0.1.0",
"terser": "^4.0.0",
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
"rimraf": "^3.0.0",
"source-map-support": "^0.5.13",
"sourceify": "^1.0.0",
"terser": "^4.3.8",
"watchify": "^3.11.1"
},
"browserify": {
+5
View File
@@ -195,6 +195,11 @@ 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
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
env: {
mocha: true,
},
}
+8 -8
View File
@@ -24,9 +24,9 @@ import './olm-loader';
import sdk from '..';
import testUtils from './test-utils';
import MockHttpBackend from 'matrix-mock-request';
import expect from 'expect';
import Promise from 'bluebird';
import LocalStorageCryptoStore from '../lib/crypto/store/localStorage-crypto-store';
import logger from '../src/logger';
/**
* Wrapper for a MockStorageApi, MockHttpBackend and MatrixClient
@@ -82,7 +82,7 @@ TestClient.prototype.toString = function() {
* @return {Promise}
*/
TestClient.prototype.start = function() {
console.log(this + ': starting');
logger.log(this + ': starting');
this.httpBackend.when("GET", "/pushrules").respond(200, {});
this.httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
this.expectDeviceKeyUpload();
@@ -100,7 +100,7 @@ TestClient.prototype.start = function() {
this.httpBackend.flushAllExpected(),
testUtils.syncPromise(this.client),
]).then(() => {
console.log(this + ': started');
logger.log(this + ': started');
});
};
@@ -122,7 +122,7 @@ TestClient.prototype.expectDeviceKeyUpload = function() {
expect(content.one_time_keys).toBe(undefined);
expect(content.device_keys).toBeTruthy();
console.log(self + ': received device keys');
logger.log(self + ': received device keys');
// we expect this to happen before any one-time keys are uploaded.
expect(Object.keys(self.oneTimeKeys).length).toEqual(0);
@@ -158,8 +158,8 @@ TestClient.prototype.awaitOneTimeKeyUpload = function() {
.respond(200, (path, content) => {
expect(content.device_keys).toBe(undefined);
expect(content.one_time_keys).toBeTruthy();
expect(content.one_time_keys).toNotEqual({});
console.log('%s: received %i one-time keys', this,
expect(content.one_time_keys).not.toEqual({});
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: {
@@ -223,11 +223,11 @@ TestClient.prototype.getSigningKey = function() {
* @returns {Promise} promise which completes once the sync has been flushed
*/
TestClient.prototype.flushSync = function() {
console.log(`${this}: flushSync`);
logger.log(`${this}: flushSync`);
return Promise.all([
this.httpBackend.flush('/sync', 1),
testUtils.syncPromise(this.client),
]).then(() => {
console.log(`${this}: flushSync completed`);
logger.log(`${this}: flushSync completed`);
});
};
+4 -6
View File
@@ -15,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import expect from 'expect';
import Promise from 'bluebird';
import TestClient from '../TestClient';
import testUtils from '../test-utils';
import logger from '../../src/logger';
const ROOM_ID = "!room:id";
@@ -71,7 +71,7 @@ function getSyncResponse(roomMembers) {
describe("DeviceList management:", function() {
if (!global.Olm) {
console.warn('not running deviceList tests: Olm not present');
logger.warn('not running deviceList tests: Olm not present');
return;
}
@@ -87,8 +87,6 @@ describe("DeviceList management:", function() {
}
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
// we create our own sessionStoreBackend so that we can use it for
// another TestClient.
sessionStoreBackend = new testUtils.MockStorageApi();
@@ -108,7 +106,7 @@ describe("DeviceList management:", function() {
return aliceTestClient.flushSync();
}).then(function() {
console.log("Forcing alice to download our device keys");
logger.log("Forcing alice to download our device keys");
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(200, {
device_keys: {
@@ -121,7 +119,7 @@ describe("DeviceList management:", function() {
aliceTestClient.httpBackend.flush('/keys/query', 1),
]);
}).then(function() {
console.log("Telling alice to send a megolm message");
logger.log("Telling alice to send a megolm message");
aliceTestClient.httpBackend.when(
'PUT', '/send/',
+44 -49
View File
@@ -30,12 +30,12 @@ import 'source-map-support/register';
// load olm before the sdk if possible
import '../olm-loader';
import expect from 'expect';
const sdk = require("../..");
import Promise from 'bluebird';
const utils = require("../../lib/utils");
const testUtils = require("../test-utils");
const TestClient = require('../TestClient').default;
import logger from '../../src/logger';
let aliTestClient;
const roomId = "!room:localhost";
@@ -55,7 +55,7 @@ function bobUploadsDeviceKeys() {
bobTestClient.client.uploadKeys(),
bobTestClient.httpBackend.flush(),
]).then(() => {
expect(Object.keys(bobTestClient.deviceKeys).length).toNotEqual(0);
expect(Object.keys(bobTestClient.deviceKeys).length).not.toEqual(0);
});
}
@@ -95,7 +95,7 @@ function expectBobQueryKeys() {
const aliKeys = {};
aliKeys[aliDeviceId] = aliTestClient.deviceKeys;
console.log("query result will be", aliKeys);
logger.log("query result will be", aliKeys);
bobTestClient.httpBackend.when(
"POST", "/keys/query",
@@ -203,7 +203,7 @@ function aliSendsFirstMessage() {
expectAliQueryKeys()
.then(expectAliClaimKeys)
.then(expectAliSendMessageRequest),
]).spread(function(_, ciphertext) {
]).then(function([_, ciphertext]) {
return ciphertext;
});
}
@@ -218,7 +218,7 @@ function aliSendsMessage() {
return Promise.all([
sendMessage(aliTestClient.client),
expectAliSendMessageRequest(),
]).spread(function(_, ciphertext) {
]).then(function([_, ciphertext]) {
return ciphertext;
});
}
@@ -234,7 +234,7 @@ function bobSendsReplyMessage() {
sendMessage(bobTestClient.client),
expectBobQueryKeys()
.then(expectBobSendMessageRequest),
]).spread(function(_, ciphertext) {
]).then(function([_, ciphertext]) {
return ciphertext;
});
}
@@ -279,16 +279,17 @@ function sendMessage(client) {
function expectSendMessageRequest(httpBackend) {
const path = "/send/m.room.encrypted/";
const deferred = Promise.defer();
httpBackend.when("PUT", path).respond(200, function(path, content) {
deferred.resolve(content);
return {
event_id: "asdfgh",
};
const prom = new Promise((resolve) => {
httpBackend.when("PUT", path).respond(200, function(path, content) {
resolve(content);
return {
event_id: "asdfgh",
};
});
});
// it can take a while to process the key query
return httpBackend.flush(path, 1).then(() => deferred.promise);
return httpBackend.flush(path, 1).then(() => prom);
}
function aliRecvMessage() {
@@ -334,7 +335,7 @@ function recvMessage(httpBackend, client, sender, message) {
if (event.getType() == "m.room.member") {
return;
}
console.log(client.credentials.userId + " received event",
logger.log(client.credentials.userId + " received event",
event);
client.removeListener("event", onEvent);
@@ -405,8 +406,6 @@ describe("MatrixClient crypto", function() {
}
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
aliTestClient = new TestClient(aliUserId, aliDeviceId, aliAccessToken);
await aliTestClient.client.initCrypto();
@@ -429,15 +428,14 @@ describe("MatrixClient crypto", function() {
.then(bobUploadsDeviceKeys);
});
it("Ali downloads Bobs device keys", function(done) {
Promise.resolve()
it("Ali downloads Bobs device keys", function() {
return Promise.resolve()
.then(bobUploadsDeviceKeys)
.then(aliDownloadsKeys)
.nodeify(done);
.then(aliDownloadsKeys);
});
it("Ali gets keys with an invalid signature", function(done) {
Promise.resolve()
it("Ali gets keys with an invalid signature", function() {
return Promise.resolve()
.then(bobUploadsDeviceKeys)
.then(function() {
// tamper bob's keys
@@ -454,11 +452,10 @@ describe("MatrixClient crypto", function() {
}).then((devices) => {
// should get an empty list
expect(devices).toEqual([]);
})
.nodeify(done);
});
});
it("Ali gets keys with an incorrect userId", function(done) {
it("Ali gets keys with an incorrect userId", function() {
const eveUserId = "@eve:localhost";
const bobDeviceKeys = {
@@ -487,7 +484,7 @@ describe("MatrixClient crypto", function() {
return {device_keys: result};
});
Promise.all([
return Promise.all([
aliTestClient.client.downloadKeys([bobUserId, eveUserId]),
aliTestClient.httpBackend.flush("/keys/query", 1),
]).then(function() {
@@ -495,14 +492,14 @@ describe("MatrixClient crypto", function() {
aliTestClient.client.getStoredDevicesForUser(bobUserId),
aliTestClient.client.getStoredDevicesForUser(eveUserId),
]);
}).spread((bobDevices, eveDevices) => {
}).then(([bobDevices, eveDevices]) => {
// should get an empty list
expect(bobDevices).toEqual([]);
expect(eveDevices).toEqual([]);
}).nodeify(done);
});
});
it("Ali gets keys with an incorrect deviceId", function(done) {
it("Ali gets keys with an incorrect deviceId", function() {
const bobDeviceKeys = {
algorithms: ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'],
device_id: 'bad_device',
@@ -529,7 +526,7 @@ describe("MatrixClient crypto", function() {
return {device_keys: result};
});
Promise.all([
return Promise.all([
aliTestClient.client.downloadKeys([bobUserId]),
aliTestClient.httpBackend.flush("/keys/query", 1),
]).then(function() {
@@ -537,7 +534,7 @@ describe("MatrixClient crypto", function() {
}).then((devices) => {
// should get an empty list
expect(devices).toEqual([]);
}).nodeify(done);
});
});
@@ -547,19 +544,18 @@ describe("MatrixClient crypto", function() {
.then(() => bobTestClient.awaitOneTimeKeyUpload())
.then((keys) => {
expect(Object.keys(keys).length).toEqual(5);
expect(Object.keys(bobTestClient.deviceKeys).length).toNotEqual(0);
expect(Object.keys(bobTestClient.deviceKeys).length).not.toEqual(0);
});
});
it("Ali sends a message", function(done) {
it("Ali sends a message", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
Promise.resolve()
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
.then(() => firstSync(aliTestClient))
.then(aliEnablesEncryption)
.then(aliSendsFirstMessage)
.nodeify(done);
.then(aliSendsFirstMessage);
});
it("Bob receives a message", function() {
@@ -607,7 +603,7 @@ describe("MatrixClient crypto", function() {
const eventPromise = new Promise((resolve, reject) => {
const onEvent = function(event) {
console.log(bobUserId + " received event",
logger.log(bobUserId + " received event",
event);
resolve(event);
};
@@ -627,9 +623,9 @@ describe("MatrixClient crypto", function() {
});
});
it("Ali blocks Bob's device", function(done) {
it("Ali blocks Bob's device", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
Promise.resolve()
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
.then(() => firstSync(aliTestClient))
@@ -644,12 +640,12 @@ describe("MatrixClient crypto", function() {
expect(sentContent.ciphertext).toEqual({});
});
return Promise.all([p1, p2]);
}).nodeify(done);
});
});
it("Bob receives two pre-key messages", function(done) {
it("Bob receives two pre-key messages", function() {
aliTestClient.expectKeyQuery({device_keys: {[aliUserId]: {}}});
Promise.resolve()
return Promise.resolve()
.then(() => aliTestClient.start())
.then(() => bobTestClient.start())
.then(() => firstSync(aliTestClient))
@@ -657,8 +653,7 @@ describe("MatrixClient crypto", function() {
.then(aliSendsFirstMessage)
.then(bobRecvMessage)
.then(aliSendsMessage)
.then(bobRecvMessage)
.nodeify(done);
.then(bobRecvMessage);
});
it("Bob replies to the message", function() {
@@ -734,7 +729,7 @@ describe("MatrixClient crypto", function() {
return Promise.resolve()
.then(() => {
console.log(aliTestClient + ': starting');
logger.log(aliTestClient + ': starting');
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
aliTestClient.expectDeviceKeyUpload();
@@ -746,16 +741,16 @@ describe("MatrixClient crypto", function() {
aliTestClient.client.startClient({});
return httpBackend.flushAllExpected().then(() => {
console.log(aliTestClient + ': started');
logger.log(aliTestClient + ': started');
});
})
.then(() => httpBackend.when("POST", "/keys/upload")
.respond(200, (path, content) => {
expect(content.one_time_keys).toBeTruthy();
expect(content.one_time_keys).toNotEqual({});
expect(content.one_time_keys).not.toEqual({});
expect(Object.keys(content.one_time_keys).length)
.toBeGreaterThanOrEqualTo(1);
console.log('received %i one-time keys',
.toBeGreaterThanOrEqual(1);
logger.log('received %i one-time keys',
Object.keys(content.one_time_keys).length);
// cancel futher calls by telling the client
// we have more than we need
+24 -5
View File
@@ -4,7 +4,6 @@ const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import expect from 'expect';
import Promise from 'bluebird';
describe("MatrixClient events", function() {
@@ -15,7 +14,6 @@ describe("MatrixClient events", function() {
const selfAccessToken = "aseukfgwef";
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
@@ -219,7 +217,7 @@ describe("MatrixClient events", function() {
client.on("RoomState.events", function(event, state) {
eventsInvokeCount++;
const index = roomStateEventTypes.indexOf(event.getType());
expect(index).toNotEqual(
expect(index).not.toEqual(
-1, "Unexpected room state event type: " + event.getType(),
);
if (index >= 0) {
@@ -302,11 +300,32 @@ describe("MatrixClient events", function() {
});
it("should emit Session.logged_out on M_UNKNOWN_TOKEN", function() {
httpBackend.when("GET", "/sync").respond(401, { errcode: 'M_UNKNOWN_TOKEN' });
const error = { errcode: 'M_UNKNOWN_TOKEN' };
httpBackend.when("GET", "/sync").respond(401, error);
let sessionLoggedOutCount = 0;
client.on("Session.logged_out", function(event, member) {
client.on("Session.logged_out", function(errObj) {
sessionLoggedOutCount++;
expect(errObj.data).toEqual(error);
});
client.startClient();
return httpBackend.flushAllExpected().then(function() {
expect(sessionLoggedOutCount).toEqual(
1, "Session.logged_out fired wrong number of times",
);
});
});
it("should emit Session.logged_out on M_UNKNOWN_TOKEN (soft logout)", function() {
const error = { errcode: 'M_UNKNOWN_TOKEN', soft_logout: true };
httpBackend.when("GET", "/sync").respond(401, error);
let sessionLoggedOutCount = 0;
client.on("Session.logged_out", function(errObj) {
sessionLoggedOutCount++;
expect(errObj.data).toEqual(error);
});
client.startClient();
+38 -42
View File
@@ -5,6 +5,7 @@ const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
const EventTimeline = sdk.EventTimeline;
import logger from '../../src/logger';
const baseUrl = "http://localhost.or.something";
const userId = "@alice:localhost";
@@ -82,18 +83,19 @@ function startClient(httpBackend, client) {
client.startClient();
// set up a promise which will resolve once the client is initialised
const deferred = Promise.defer();
client.on("sync", function(state) {
console.log("sync", state);
if (state != "SYNCING") {
return;
}
deferred.resolve();
const prom = new Promise((resolve) => {
client.on("sync", function(state) {
logger.log("sync", state);
if (state != "SYNCING") {
return;
}
resolve();
});
});
return Promise.all([
httpBackend.flushAllExpected(),
deferred.promise,
prom,
]);
}
@@ -102,7 +104,6 @@ describe("getEventTimeline support", function() {
let client;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
});
@@ -114,21 +115,20 @@ describe("getEventTimeline support", function() {
return httpBackend.stop();
});
it("timeline support must be enabled to work", function(done) {
it("timeline support must be enabled to work", function() {
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
});
startClient(httpBackend, client,
).then(function() {
return startClient(httpBackend, client).then(function() {
const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0];
expect(function() {
client.getEventTimeline(timelineSet, "event");
}).toThrow();
}).nodeify(done);
});
});
it("timeline support works when enabled", function() {
@@ -144,13 +144,13 @@ describe("getEventTimeline support", function() {
const timelineSet = room.getTimelineSets()[0];
expect(function() {
client.getEventTimeline(timelineSet, "event");
}).toNotThrow();
}).not.toThrow();
});
});
it("scrollback should be able to scroll back to before a gappy /sync",
function(done) {
function() {
// need a client with timelineSupport disabled to make this work
client = sdk.createClient({
baseUrl: baseUrl,
@@ -159,8 +159,7 @@ describe("getEventTimeline support", function() {
});
let room;
startClient(httpBackend, client,
).then(function() {
return startClient(httpBackend, client).then(function() {
room = client.getRoom(roomId);
httpBackend.when("GET", "/sync").respond(200, {
@@ -216,18 +215,15 @@ describe("getEventTimeline support", function() {
expect(room.timeline[0].event).toEqual(EVENTS[0]);
expect(room.timeline[1].event).toEqual(EVENTS[1]);
expect(room.oldState.paginationToken).toEqual("pagin_end");
}).nodeify(done);
});
});
});
import expect from 'expect';
describe("MatrixClient event timelines", function() {
let client = null;
let httpBackend = null;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
@@ -348,25 +344,25 @@ describe("MatrixClient event timelines", function() {
};
});
const deferred = Promise.defer();
client.on("sync", function() {
client.getEventTimeline(timelineSet, EVENTS[2].event_id,
).then(function(tl) {
expect(tl.getEvents().length).toEqual(4);
expect(tl.getEvents()[0].event).toEqual(EVENTS[1]);
expect(tl.getEvents()[1].event).toEqual(EVENTS[2]);
expect(tl.getEvents()[3].event).toEqual(EVENTS[3]);
expect(tl.getPaginationToken(EventTimeline.BACKWARDS))
.toEqual("start_token");
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
// .toEqual("s_5_4");
}).done(() => deferred.resolve(),
(e) => deferred.reject(e));
const prom = new Promise((resolve, reject) => {
client.on("sync", function() {
client.getEventTimeline(timelineSet, EVENTS[2].event_id,
).then(function(tl) {
expect(tl.getEvents().length).toEqual(4);
expect(tl.getEvents()[0].event).toEqual(EVENTS[1]);
expect(tl.getEvents()[1].event).toEqual(EVENTS[2]);
expect(tl.getEvents()[3].event).toEqual(EVENTS[3]);
expect(tl.getPaginationToken(EventTimeline.BACKWARDS))
.toEqual("start_token");
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
// .toEqual("s_5_4");
}).done(resolve, reject);
});
});
return Promise.all([
httpBackend.flushAllExpected(),
deferred.promise,
prom,
]);
});
@@ -669,11 +665,11 @@ describe("MatrixClient event timelines", function() {
// initiate the send, and set up checks to be done when it completes
// - but note that it won't complete until after the /sync does, below.
client.sendTextMessage(roomId, "a body", TXN_ID).then(function(res) {
console.log("sendTextMessage completed");
logger.log("sendTextMessage completed");
expect(res.event_id).toEqual(event.event_id);
return client.getEventTimeline(timelineSet, event.event_id);
}).then(function(tl) {
console.log("getEventTimeline completed (2)");
logger.log("getEventTimeline completed (2)");
expect(tl.getEvents().length).toEqual(2);
expect(tl.getEvents()[1].getContent().body).toEqual("a body");
}),
@@ -684,7 +680,7 @@ describe("MatrixClient event timelines", function() {
]).then(function() {
return client.getEventTimeline(timelineSet, event.event_id);
}).then(function(tl) {
console.log("getEventTimeline completed (1)");
logger.log("getEventTimeline completed (1)");
expect(tl.getEvents().length).toEqual(2);
expect(tl.getEvents()[1].event).toEqual(event);
@@ -696,7 +692,7 @@ describe("MatrixClient event timelines", function() {
});
it("should handle gappy syncs after redactions", function(done) {
it("should handle gappy syncs after redactions", function() {
// https://github.com/vector-im/vector-web/issues/1389
// a state event, followed by a redaction thereof
@@ -728,7 +724,7 @@ describe("MatrixClient event timelines", function() {
};
httpBackend.when("GET", "/sync").respond(200, syncData);
Promise.all([
return Promise.all([
httpBackend.flushAllExpected(),
utils.syncPromise(client),
]).then(function() {
@@ -764,6 +760,6 @@ describe("MatrixClient event timelines", function() {
const room = client.getRoom(roomId);
const tl = room.getLiveTimeline();
expect(tl.getEvents().length).toEqual(1);
}).nodeify(done);
});
});
});
+30 -29
View File
@@ -9,8 +9,6 @@ const Filter = publicGlobals.Filter;
const utils = require("../test-utils");
const MockStorageApi = require("../MockStorageApi");
import expect from 'expect';
describe("MatrixClient", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
@@ -21,7 +19,6 @@ describe("MatrixClient", function() {
const accessToken = "aseukfgwef";
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
store = new MemoryStore();
@@ -46,9 +43,9 @@ describe("MatrixClient", function() {
describe("uploadContent", function() {
const buf = new Buffer('hello world');
it("should upload the file", function(done) {
it("should upload the file", function() {
httpBackend.when(
"POST", "/_matrix/media/v1/upload",
"POST", "/_matrix/media/r0/upload",
).check(function(req) {
expect(req.rawData).toEqual(buf);
expect(req.queryParams.filename).toEqual("hi.txt");
@@ -74,25 +71,26 @@ describe("MatrixClient", function() {
expect(uploads[0].promise).toBe(prom);
expect(uploads[0].loaded).toEqual(0);
prom.then(function(response) {
const prom2 = prom.then(function(response) {
// for backwards compatibility, we return the raw JSON
expect(response).toEqual("content");
const uploads = client.getCurrentUploads();
expect(uploads.length).toEqual(0);
}).nodeify(done);
});
httpBackend.flush();
return prom2;
});
it("should parse the response if rawResponse=false", function(done) {
it("should parse the response if rawResponse=false", function() {
httpBackend.when(
"POST", "/_matrix/media/v1/upload",
"POST", "/_matrix/media/r0/upload",
).check(function(req) {
expect(req.opts.json).toBeFalsy();
}).respond(200, { "content_uri": "uri" });
client.uploadContent({
const prom = client.uploadContent({
stream: buf,
name: "hi.txt",
type: "text/plain",
@@ -100,14 +98,15 @@ describe("MatrixClient", function() {
rawResponse: false,
}).then(function(response) {
expect(response.content_uri).toEqual("uri");
}).nodeify(done);
});
httpBackend.flush();
return prom;
});
it("should parse errors into a MatrixError", function(done) {
it("should parse errors into a MatrixError", function() {
httpBackend.when(
"POST", "/_matrix/media/v1/upload",
"POST", "/_matrix/media/r0/upload",
).check(function(req) {
expect(req.rawData).toEqual(buf);
expect(req.opts.json).toBeFalsy();
@@ -116,7 +115,7 @@ describe("MatrixClient", function() {
"error": "broken",
});
client.uploadContent({
const prom = client.uploadContent({
stream: buf,
name: "hi.txt",
type: "text/plain",
@@ -126,12 +125,13 @@ describe("MatrixClient", function() {
expect(error.httpStatus).toEqual(400);
expect(error.errcode).toEqual("M_SNAFU");
expect(error.message).toEqual("broken");
}).nodeify(done);
});
httpBackend.flush();
return prom;
});
it("should return a promise which can be cancelled", function(done) {
it("should return a promise which can be cancelled", function() {
const prom = client.uploadContent({
stream: buf,
name: "hi.txt",
@@ -143,17 +143,18 @@ describe("MatrixClient", function() {
expect(uploads[0].promise).toBe(prom);
expect(uploads[0].loaded).toEqual(0);
prom.then(function(response) {
const prom2 = prom.then(function(response) {
throw Error("request not aborted");
}, function(error) {
expect(error).toEqual("aborted");
const uploads = client.getCurrentUploads();
expect(uploads.length).toEqual(0);
}).nodeify(done);
});
const r = client.cancelUpload(prom);
expect(r).toBe(true);
return prom2;
});
});
@@ -310,7 +311,7 @@ describe("MatrixClient", function() {
return client.initCrypto();
});
it("should do an HTTP request and then store the keys", function(done) {
it("should do an HTTP request and then store the keys", function() {
const ed25519key = "7wG2lzAqbjcyEkOP7O4gU7ItYcn+chKzh5sT/5r2l78";
// ed25519key = client.getDeviceEd25519Key();
const borisKeys = {
@@ -355,9 +356,9 @@ describe("MatrixClient", function() {
return client._crypto._olmDevice.sign(anotherjson.stringify(b));
};
console.log("Ed25519: " + ed25519key);
console.log("boris:", sign(borisKeys.dev1));
console.log("chaz:", sign(chazKeys.dev2));
logger.log("Ed25519: " + ed25519key);
logger.log("boris:", sign(borisKeys.dev1));
logger.log("chaz:", sign(chazKeys.dev2));
*/
httpBackend.when("POST", "/keys/query").check(function(req) {
@@ -372,7 +373,7 @@ describe("MatrixClient", function() {
},
});
client.downloadKeys(["boris", "chaz"]).then(function(res) {
const prom = client.downloadKeys(["boris", "chaz"]).then(function(res) {
assertObjectContains(res.boris.dev1, {
verified: 0, // DeviceVerification.UNVERIFIED
keys: { "ed25519:dev1": ed25519key },
@@ -386,26 +387,26 @@ describe("MatrixClient", function() {
algorithms: ["2"],
unsigned: { "ghi": "def" },
});
}).nodeify(done);
});
httpBackend.flush();
return prom;
});
});
describe("deleteDevice", function() {
const auth = {a: 1};
it("should pass through an auth dict", function(done) {
it("should pass through an auth dict", function() {
httpBackend.when(
"DELETE", "/_matrix/client/unstable/devices/my_device",
"DELETE", "/_matrix/client/r0/devices/my_device",
).check(function(req) {
expect(req.data).toEqual({auth: auth});
}).respond(200);
client.deleteDevice(
"my_device", auth,
).nodeify(done);
const prom = client.deleteDevice("my_device", auth);
httpBackend.flush();
return prom;
});
});
});
+1 -3
View File
@@ -5,7 +5,6 @@ const MatrixClient = sdk.MatrixClient;
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import expect from 'expect';
import Promise from 'bluebird';
describe("MatrixClient opts", function() {
@@ -58,7 +57,6 @@ describe("MatrixClient opts", function() {
};
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
});
@@ -101,7 +99,7 @@ describe("MatrixClient opts", function() {
"m.room.create",
];
client.on("event", function(event) {
expect(expectedEventTypes.indexOf(event.getType())).toNotEqual(
expect(expectedEventTypes.indexOf(event.getType())).not.toEqual(
-1, "Recv unexpected event type: " + event.getType(),
);
expectedEventTypes.splice(
@@ -4,11 +4,8 @@ import Promise from 'bluebird';
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
const EventStatus = sdk.EventStatus;
import expect from 'expect';
describe("MatrixClient retrying", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
@@ -20,7 +17,6 @@ describe("MatrixClient retrying", function() {
let room;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
scheduler = new sdk.MatrixScheduler();
@@ -6,7 +6,6 @@ const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import Promise from 'bluebird';
import expect from 'expect';
describe("MatrixClient room timelines", function() {
const baseUrl = "http://localhost.or.something";
@@ -103,8 +102,7 @@ describe("MatrixClient room timelines", function() {
});
}
beforeEach(function(done) {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
beforeEach(function() {
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
@@ -122,9 +120,9 @@ describe("MatrixClient room timelines", function() {
return NEXT_SYNC_DATA;
});
client.startClient();
httpBackend.flush("/pushrules").then(function() {
return httpBackend.flush("/pushrules").then(function() {
return httpBackend.flush("/filter");
}).nodeify(done);
});
});
afterEach(function() {
+8 -10
View File
@@ -6,7 +6,6 @@ const utils = require("../test-utils");
const MatrixEvent = sdk.MatrixEvent;
const EventTimeline = sdk.EventTimeline;
import expect from 'expect';
import Promise from 'bluebird';
describe("MatrixClient syncing", function() {
@@ -23,7 +22,6 @@ describe("MatrixClient syncing", function() {
const roomTwo = "!bar:localhost";
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
@@ -528,7 +526,7 @@ describe("MatrixClient syncing", function() {
awaitSyncEvent(),
]).then(function() {
const room = client.getRoom(roomTwo);
expect(room).toExist();
expect(room).toBeDefined();
const tok = room.getLiveTimeline()
.getPaginationToken(EventTimeline.BACKWARDS);
expect(tok).toEqual("roomtwotok");
@@ -693,12 +691,12 @@ describe("MatrixClient syncing", function() {
include_leave: true }});
}).respond(200, { filter_id: "another_id" });
const defer = Promise.defer();
httpBackend.when("GET", "/sync").check(function(req) {
expect(req.queryParams.filter).toEqual("another_id");
defer.resolve();
}).respond(200, {});
const prom = new Promise((resolve) => {
httpBackend.when("GET", "/sync").check(function(req) {
expect(req.queryParams.filter).toEqual("another_id");
resolve();
}).respond(200, {});
});
client.syncLeftRooms();
@@ -709,7 +707,7 @@ describe("MatrixClient syncing", function() {
// flush the syncs
return httpBackend.flushAllExpected();
}),
defer.promise,
prom,
]);
});
+19 -21
View File
@@ -18,11 +18,11 @@ limitations under the License.
const anotherjson = require('another-json');
import Promise from 'bluebird';
import expect from 'expect';
const utils = require('../../lib/utils');
const testUtils = require('../test-utils');
const TestClient = require('../TestClient').default;
import logger from '../../src/logger';
const ROOM_ID = "!room:id";
@@ -203,7 +203,7 @@ function getSyncResponse(roomMembers) {
describe("megolm", function() {
if (!global.Olm) {
console.warn('not running megolm tests: Olm not present');
logger.warn('not running megolm tests: Olm not present');
return;
}
const Olm = global.Olm;
@@ -282,8 +282,6 @@ describe("megolm", function() {
}
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
aliceTestClient = new TestClient(
"@alice:localhost", "xzcvb", "akjgkrgjs",
);
@@ -416,7 +414,7 @@ describe("megolm", function() {
return new Promise((resolve, reject) => {
event.once('Event.decrypted', (ev) => {
console.log(`${Date.now()} event ${event.getId()} now decrypted`);
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev);
});
});
@@ -555,7 +553,7 @@ describe("megolm", function() {
).respond(200, function(path, content) {
const ct = content.ciphertext;
const r = inboundGroupSession.decrypt(ct);
console.log('Decrypted received megolm message', r);
logger.log('Decrypted received megolm message', r);
expect(r.message_index).toEqual(0);
const decrypted = JSON.parse(r.plaintext);
@@ -600,7 +598,7 @@ describe("megolm", function() {
return aliceTestClient.flushSync();
}).then(function() {
console.log('Forcing alice to download our device keys');
logger.log('Forcing alice to download our device keys');
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, getTestKeysQueryResponse('@bob:xyz'),
@@ -611,10 +609,10 @@ describe("megolm", function() {
aliceTestClient.httpBackend.flush('/keys/query', 1),
]);
}).then(function() {
console.log('Telling alice to block our device');
logger.log('Telling alice to block our device');
aliceTestClient.client.setDeviceBlocked('@bob:xyz', 'DEVICE_ID');
console.log('Telling alice to send a megolm message');
logger.log('Telling alice to send a megolm message');
aliceTestClient.httpBackend.when(
'PUT', '/send/',
).respond(200, {
@@ -656,7 +654,7 @@ describe("megolm", function() {
return aliceTestClient.flushSync();
}).then(function() {
console.log("Fetching bob's devices and marking known");
logger.log("Fetching bob's devices and marking known");
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, getTestKeysQueryResponse('@bob:xyz'),
@@ -669,17 +667,17 @@ describe("megolm", function() {
aliceTestClient.client.setDeviceKnown('@bob:xyz', 'DEVICE_ID');
});
}).then(function() {
console.log('Telling alice to send a megolm message');
logger.log('Telling alice to send a megolm message');
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room.encrypted/',
).respond(200, function(path, content) {
console.log('sendToDevice: ', content);
logger.log('sendToDevice: ', content);
const m = content.messages['@bob:xyz'].DEVICE_ID;
const ct = m.ciphertext[testSenderKey];
expect(ct.type).toEqual(1); // normal message
const decrypted = JSON.parse(p2pSession.decrypt(ct.type, ct.body));
console.log('decrypted sendToDevice:', decrypted);
logger.log('decrypted sendToDevice:', decrypted);
expect(decrypted.type).toEqual('m.room_key');
megolmSessionId = decrypted.content.session_id;
return {};
@@ -688,7 +686,7 @@ describe("megolm", function() {
aliceTestClient.httpBackend.when(
'PUT', '/send/',
).respond(200, function(path, content) {
console.log('/send:', content);
logger.log('/send:', content);
expect(content.session_id).toEqual(megolmSessionId);
return {
event_id: '$event_id',
@@ -704,15 +702,15 @@ describe("megolm", function() {
}),
]);
}).then(function() {
console.log('Telling alice to block our device');
logger.log('Telling alice to block our device');
aliceTestClient.client.setDeviceBlocked('@bob:xyz', 'DEVICE_ID');
console.log('Telling alice to send another megolm message');
logger.log('Telling alice to send another megolm message');
aliceTestClient.httpBackend.when(
'PUT', '/send/',
).respond(200, function(path, content) {
console.log('/send:', content);
expect(content.session_id).toNotEqual(megolmSessionId);
logger.log('/send:', content);
expect(content.session_id).not.toEqual(megolmSessionId);
return {
event_id: '$event_id',
};
@@ -792,7 +790,7 @@ describe("megolm", function() {
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room.encrypted/',
).respond(200, function(path, content) {
console.log("sendToDevice: ", content);
logger.log("sendToDevice: ", content);
const m = content.messages[aliceTestClient.userId].DEVICE_ID;
const ct = m.ciphertext[testSenderKey];
expect(ct.type).toEqual(0); // pre-key message
@@ -812,7 +810,7 @@ describe("megolm", function() {
).respond(200, function(path, content) {
const ct = content.ciphertext;
const r = inboundGroupSession.decrypt(ct);
console.log('Decrypted received megolm message', r);
logger.log('Decrypted received megolm message', r);
decrypted = JSON.parse(r.plaintext);
return {
@@ -865,7 +863,7 @@ describe("megolm", function() {
return aliceTestClient.flushSync();
}).then(function() {
// this will block
console.log('Forcing alice to download our device keys');
logger.log('Forcing alice to download our device keys');
downloadPromise = aliceTestClient.client.downloadKeys(['@bob:xyz']);
// so will this.
+4 -3
View File
@@ -14,11 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// try to load the olm library.
import logger from '../src/logger';
// try to load the olm library.
try {
global.Olm = require('olm');
console.log('loaded libolm');
logger.log('loaded libolm');
} catch (e) {
console.warn("unable to run crypto tests: libolm not available");
logger.warn("unable to run crypto tests: libolm not available");
}
+146 -17
View File
@@ -1,10 +1,10 @@
"use strict";
import expect from 'expect';
import Promise from 'bluebird';
// load olm before the sdk if possible
import './olm-loader';
import logger from '../src/logger';
import sdk from '..';
const MatrixEvent = sdk.MatrixEvent;
@@ -25,7 +25,7 @@ module.exports.syncPromise = function(client, count) {
const p = new Promise((resolve, reject) => {
const cb = (state) => {
console.log(`${Date.now()} syncPromise(${count}): ${state}`);
logger.log(`${Date.now()} syncPromise(${count}): ${state}`);
if (state == 'SYNCING') {
resolve();
} else {
@@ -40,18 +40,6 @@ module.exports.syncPromise = function(client, count) {
});
};
/**
* Perform common actions before each test case, e.g. printing the test case
* name to stdout.
* @param {Mocha.Context} context The test context
*/
module.exports.beforeEach = function(context) {
const desc = context.currentTest.fullTitle();
console.log(desc);
console.log(new Array(1 + desc.length).join("="));
};
/**
* Create a spy for an object and automatically spy its methods.
* @param {*} constr The class constructor (used with 'new')
@@ -70,7 +58,7 @@ module.exports.mock = function(constr, name) {
for (const key in constr.prototype) { // eslint-disable-line guard-for-in
try {
if (constr.prototype[key] instanceof Function) {
result[key] = expect.createSpy();
result[key] = jest.fn();
}
} catch (ex) {
// Direct access to some non-function fields of DOM prototypes may
@@ -232,12 +220,153 @@ module.exports.awaitDecryption = function(event) {
return Promise.resolve(event);
}
console.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) => {
console.log(`${Date.now()} event ${event.getId()} now decrypted`);
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev);
});
});
};
const HttpResponse = module.exports.HttpResponse = function(
httpLookups, acceptKeepalives, ignoreUnhandledSync,
) {
this.httpLookups = httpLookups;
this.acceptKeepalives = acceptKeepalives === undefined ? true : acceptKeepalives;
this.ignoreUnhandledSync = ignoreUnhandledSync;
this.pendingLookup = null;
};
HttpResponse.prototype.request = function HttpResponse(
cb, method, path, qp, data, prefix,
) {
if (path === HttpResponse.KEEP_ALIVE_PATH && this.acceptKeepalives) {
return Promise.resolve();
}
const next = this.httpLookups.shift();
const logLine = (
"MatrixClient[UT] RECV " + method + " " + path + " " +
"EXPECT " + (next ? next.method : next) + " " + (next ? next.path : next)
);
logger.log(logLine);
if (!next) { // no more things to return
if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
logger.log("MatrixClient[UT] Ignoring.");
return Promise.defer().promise;
}
if (this.pendingLookup) {
if (this.pendingLookup.method === method
&& this.pendingLookup.path === path) {
return this.pendingLookup.promise;
}
// >1 pending thing, and they are different, whine.
expect(false).toBe(
true, ">1 pending request. You should probably handle them. " +
"PENDING: " + JSON.stringify(this.pendingLookup) + " JUST GOT: " +
method + " " + path,
);
}
this.pendingLookup = {
promise: Promise.defer().promise,
method: method,
path: path,
};
return this.pendingLookup.promise;
}
if (next.path === path && next.method === method) {
logger.log(
"MatrixClient[UT] Matched. Returning " +
(next.error ? "BAD" : "GOOD") + " response",
);
if (next.expectBody) {
expect(next.expectBody).toEqual(data);
}
if (next.expectQueryParams) {
Object.keys(next.expectQueryParams).forEach(function(k) {
expect(qp[k]).toEqual(next.expectQueryParams[k]);
});
}
if (next.thenCall) {
process.nextTick(next.thenCall, 0); // next tick so we return first.
}
if (next.error) {
return Promise.reject({
errcode: next.error.errcode,
httpStatus: next.error.httpStatus,
name: next.error.errcode,
message: "Expected testing error",
data: next.error,
});
}
return Promise.resolve(next.data);
} else if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
logger.log("MatrixClient[UT] Ignoring.");
this.httpLookups.unshift(next);
return Promise.defer().promise;
}
expect(true).toBe(false, "Expected different request. " + logLine);
return Promise.defer().promise;
};
HttpResponse.KEEP_ALIVE_PATH = "/_matrix/client/versions";
HttpResponse.PUSH_RULES_RESPONSE = {
method: "GET",
path: "/pushrules/",
data: {},
};
HttpResponse.USER_ID = "@alice:bar";
HttpResponse.filterResponse = function(userId) {
const filterPath = "/user/" + encodeURIComponent(userId) + "/filter";
return {
method: "POST",
path: filterPath,
data: { filter_id: "f1lt3r" },
};
};
HttpResponse.SYNC_DATA = {
next_batch: "s_5_3",
presence: { events: [] },
rooms: {},
};
HttpResponse.SYNC_RESPONSE = {
method: "GET",
path: "/sync",
data: HttpResponse.SYNC_DATA,
};
HttpResponse.defaultResponses = function(userId) {
return [
HttpResponse.PUSH_RULES_RESPONSE,
HttpResponse.filterResponse(userId),
HttpResponse.SYNC_RESPONSE,
];
};
module.exports.setHttpResponses = function setHttpResponses(
client, responses, acceptKeepalives, ignoreUnhandledSyncs,
) {
const httpResponseObj = new HttpResponse(
responses, acceptKeepalives, ignoreUnhandledSyncs,
);
const httpReq = httpResponseObj.request.bind(httpResponseObj);
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);
};
+25 -28
View File
@@ -18,11 +18,9 @@ limitations under the License.
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const utils = require("../test-utils");
const AutoDiscovery = sdk.AutoDiscovery;
import expect from 'expect';
import MockHttpBackend from "matrix-mock-request";
@@ -30,7 +28,6 @@ describe("AutoDiscovery", function() {
let httpBackend = null;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new MockHttpBackend();
sdk.request(httpBackend.requestFn);
});
@@ -275,7 +272,7 @@ describe("AutoDiscovery", function() {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
base_url: null,
base_url: "https://example.org",
},
"m.identity_server": {
state: "PROMPT",
@@ -304,7 +301,7 @@ describe("AutoDiscovery", function() {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
base_url: null,
base_url: "https://example.org",
},
"m.identity_server": {
state: "PROMPT",
@@ -335,7 +332,7 @@ describe("AutoDiscovery", function() {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
base_url: null,
base_url: "https://example.org",
},
"m.identity_server": {
state: "PROMPT",
@@ -416,8 +413,8 @@ describe("AutoDiscovery", function() {
]);
});
it("should return FAIL_ERROR when the identity server configuration is wrong " +
"(missing base_url)", function() {
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (missing base_url)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
@@ -438,14 +435,14 @@ describe("AutoDiscovery", function() {
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_IS,
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes.
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_ERROR",
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IS_BASE_URL,
base_url: null,
},
@@ -456,8 +453,8 @@ describe("AutoDiscovery", function() {
]);
});
it("should return FAIL_ERROR when the identity server configuration is wrong " +
"(empty base_url)", function() {
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (empty base_url)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
@@ -478,14 +475,14 @@ describe("AutoDiscovery", function() {
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_IS,
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes.
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_ERROR",
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IS_BASE_URL,
base_url: null,
},
@@ -496,8 +493,8 @@ describe("AutoDiscovery", function() {
]);
});
it("should return FAIL_ERROR when the identity server configuration is wrong " +
"(validation error: 404)", function() {
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (validation error: 404)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
@@ -519,16 +516,16 @@ describe("AutoDiscovery", function() {
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_IS,
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes.
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_ERROR",
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
base_url: null,
base_url: "https://identity.example.org",
},
};
@@ -537,8 +534,8 @@ describe("AutoDiscovery", function() {
]);
});
it("should return FAIL_ERROR when the identity server configuration is wrong " +
"(validation error: 500)", function() {
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (validation error: 500)", function() {
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.opts.uri)
.toEqual("https://chat.example.org/_matrix/client/versions");
@@ -560,16 +557,16 @@ describe("AutoDiscovery", function() {
AutoDiscovery.findClientConfig("example.org").then((conf) => {
const expected = {
"m.homeserver": {
state: "FAIL_ERROR",
error: AutoDiscovery.ERROR_INVALID_IS,
state: "SUCCESS",
error: null,
// We still expect the base_url to be here for debugging purposes
base_url: "https://chat.example.org",
},
"m.identity_server": {
state: "FAIL_ERROR",
state: "FAIL_PROMPT",
error: AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
base_url: null,
base_url: "https://identity.example.org",
},
};
+7 -14
View File
@@ -1,17 +1,10 @@
"use strict";
import 'source-map-support/register';
const ContentRepo = require("../../lib/content-repo");
const testUtils = require("../test-utils");
import expect from 'expect';
describe("ContentRepo", function() {
const baseUrl = "https://my.home.server";
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
describe("getHttpUriForMxc", function() {
it("should do nothing to HTTP URLs when allowing direct links", function() {
const httpUrl = "http://example.com/image.jpeg";
@@ -31,7 +24,7 @@ describe("ContentRepo", function() {
function() {
const mxcUri = "mxc://server.name/resourceid";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/v1/download/server.name/resourceid",
baseUrl + "/_matrix/media/r0/download/server.name/resourceid",
);
});
@@ -43,7 +36,7 @@ describe("ContentRepo", function() {
function() {
const mxcUri = "mxc://server.name/resourceid";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri, 32, 64, "crop")).toEqual(
baseUrl + "/_matrix/media/v1/thumbnail/server.name/resourceid" +
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
"?width=32&height=64&method=crop",
);
});
@@ -52,7 +45,7 @@ describe("ContentRepo", function() {
function() {
const mxcUri = "mxc://server.name/resourceid#automade";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri, 32)).toEqual(
baseUrl + "/_matrix/media/v1/thumbnail/server.name/resourceid" +
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
"?width=32#automade",
);
});
@@ -61,7 +54,7 @@ describe("ContentRepo", function() {
function() {
const mxcUri = "mxc://server.name/resourceid#automade";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/v1/download/server.name/resourceid#automade",
baseUrl + "/_matrix/media/r0/download/server.name/resourceid#automade",
);
});
});
@@ -73,21 +66,21 @@ describe("ContentRepo", function() {
it("should set w/h by default to 96", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foobar")).toEqual(
baseUrl + "/_matrix/media/v1/identicon/foobar" +
baseUrl + "/_matrix/media/unstable/identicon/foobar" +
"?width=96&height=96",
);
});
it("should be able to set custom w/h", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foobar", 32, 64)).toEqual(
baseUrl + "/_matrix/media/v1/identicon/foobar" +
baseUrl + "/_matrix/media/unstable/identicon/foobar" +
"?width=32&height=64",
);
});
it("should URL encode the identicon string", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foo#bar", 32, 64)).toEqual(
baseUrl + "/_matrix/media/v1/identicon/foo%23bar" +
baseUrl + "/_matrix/media/unstable/identicon/foo%23bar" +
"?width=32&height=64",
);
});
+41 -46
View File
@@ -3,7 +3,6 @@ import 'source-map-support/register';
import '../olm-loader';
import Crypto from '../../lib/crypto';
import expect from 'expect';
import WebStorageSessionStore from '../../lib/store/session/webstorage';
import MemoryCryptoStore from '../../lib/crypto/store/memory-crypto-store.js';
@@ -12,7 +11,6 @@ import TestClient from '../TestClient';
import {MatrixEvent} from '../../lib/models/event';
import Room from '../../lib/models/room';
import olmlib from '../../lib/crypto/olmlib';
import lolex from 'lolex';
const EventEmitter = require("events").EventEmitter;
@@ -20,13 +18,15 @@ const sdk = require("../..");
const Olm = global.Olm;
jest.useFakeTimers();
describe("Crypto", function() {
if (!sdk.CRYPTO_ENABLED) {
return;
}
beforeEach(function(done) {
Olm.init().then(done);
beforeAll(function() {
return Olm.init();
});
it("Crypto exposes the correct olm library version", function() {
@@ -76,9 +76,9 @@ describe("Crypto", function() {
});
mockBaseApis = {
sendToDevice: expect.createSpy(),
getKeyBackupVersion: expect.createSpy(),
isGuest: expect.createSpy(),
sendToDevice: jest.fn(),
getKeyBackupVersion: jest.fn(),
isGuest: jest.fn(),
};
mockRoomList = {};
@@ -110,15 +110,16 @@ describe("Crypto", function() {
});
fakeEmitter.emit('toDeviceEvent', {
getType: expect.createSpy().andReturn('m.room.message'),
getContent: expect.createSpy().andReturn({
getId: jest.fn().mockReturnValue("$wedged"),
getType: jest.fn().mockReturnValue('m.room.message'),
getContent: jest.fn().mockReturnValue({
msgtype: 'm.bad.encrypted',
}),
getWireContent: expect.createSpy().andReturn({
getWireContent: jest.fn().mockReturnValue({
algorithm: 'm.olm.v1.curve25519-aes-sha2',
sender_key: 'this is a key',
}),
getSender: expect.createSpy().andReturn('@bob:home.server'),
getSender: jest.fn().mockReturnValue('@bob:home.server'),
});
await prom;
@@ -245,7 +246,7 @@ describe("Crypto", function() {
await bobDecryptor.onRoomKeyEvent(ksEvent);
await eventPromise;
expect(events[0].getContent().msgtype).toBe("m.bad.encrypted");
expect(events[1].getContent().msgtype).toNotBe("m.bad.encrypted");
expect(events[1].getContent().msgtype).not.toBe("m.bad.encrypted");
const cryptoStore = bobClient._cryptoStore;
const eventContent = events[0].getWireContent();
@@ -260,7 +261,7 @@ describe("Crypto", function() {
// the room key request should still be there, since we haven't
// decrypted everything
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
.toExist();
.toBeDefined();
// keyshare the session key starting at the first message, so
// that it can now be decrypted
@@ -268,10 +269,10 @@ describe("Crypto", function() {
ksEvent = await keyshareEventForEvent(events[0], 0);
await bobDecryptor.onRoomKeyEvent(ksEvent);
await eventPromise;
expect(events[0].getContent().msgtype).toNotBe("m.bad.encrypted");
expect(events[0].getContent().msgtype).not.toBe("m.bad.encrypted");
// the room key request should be gone since we've now decypted everything
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
.toNotExist();
.toBeFalsy();
},
);
@@ -296,7 +297,7 @@ describe("Crypto", function() {
sender_key: "senderkey",
};
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
.toExist();
.toBeDefined();
});
it("uses a new txnid for re-requesting keys", async function() {
@@ -329,38 +330,32 @@ describe("Crypto", function() {
aliceClient.startClient();
const clock = lolex.install();
let promise;
// make a room key request, and record the transaction ID for the
// sendToDevice call
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
jest.runAllTimers();
let args = await promise;
const txnId = args[2];
jest.runAllTimers();
try {
let promise;
// make a room key request, and record the transaction ID for the
// sendToDevice call
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
clock.runToLast();
let args = await promise;
const txnId = args[2];
clock.runToLast();
// give the room key request manager time to update the state
// of the request
await Promise.resolve();
// give the room key request manager time to update the state
// of the request
await Promise.resolve();
// cancel and resend the room key request
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
clock.runToLast();
// the first call to sendToDevice will be the cancellation
args = await promise;
// the second call to sendToDevice will be the key request
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
clock.runToLast();
args = await promise;
clock.runToLast();
expect(args[2]).toNotBe(txnId);
} finally {
clock.uninstall();
}
// cancel and resend the room key request
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
jest.runAllTimers();
// the first call to sendToDevice will be the cancellation
args = await promise;
// the second call to sendToDevice will be the key request
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
jest.runAllTimers();
args = await promise;
jest.runAllTimers();
expect(args[2]).not.toBe(txnId);
});
});
});
+9 -12
View File
@@ -17,10 +17,9 @@ limitations under the License.
import DeviceList from '../../../lib/crypto/DeviceList';
import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js';
import testUtils from '../../test-utils';
import utils from '../../../lib/utils';
import logger from '../../../src/logger';
import expect from 'expect';
import Promise from 'bluebird';
const signedDeviceList = {
@@ -59,11 +58,9 @@ describe('DeviceList', function() {
let deviceLists = [];
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
deviceLists = [];
downloadSpy = expect.createSpy();
downloadSpy = jest.fn();
cryptoStore = new MemoryCryptoStore();
});
@@ -91,7 +88,7 @@ describe('DeviceList', function() {
dl.startTrackingDeviceList('@test1:sw1v.org');
const queryDefer1 = Promise.defer();
downloadSpy.andReturn(queryDefer1.promise);
downloadSpy.mockReturnValue(queryDefer1.promise);
const prom1 = dl.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
@@ -110,15 +107,15 @@ describe('DeviceList', function() {
dl.startTrackingDeviceList('@test1:sw1v.org');
const queryDefer1 = Promise.defer();
downloadSpy.andReturn(queryDefer1.promise);
downloadSpy.mockReturnValue(queryDefer1.promise);
const prom1 = dl.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
downloadSpy.reset();
downloadSpy.mockReset();
// outdated notif arrives while the request is in flight.
const queryDefer2 = Promise.defer();
downloadSpy.andReturn(queryDefer2.promise);
downloadSpy.mockReturnValue(queryDefer2.promise);
dl.invalidateUserDeviceList('@test1:sw1v.org');
dl.refreshOutdatedDeviceLists();
@@ -134,11 +131,11 @@ describe('DeviceList', function() {
}).then(() => {
// uh-oh; user restarts before second request completes. The new instance
// should know we never got a complete device list.
console.log("Creating new devicelist to simulate app reload");
downloadSpy.reset();
logger.log("Creating new devicelist to simulate app reload");
downloadSpy.mockReset();
const dl2 = createTestDeviceList();
const queryDefer3 = Promise.defer();
downloadSpy.andReturn(queryDefer3.promise);
downloadSpy.mockReturnValue(queryDefer3.promise);
const prom3 = dl2.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
+38 -40
View File
@@ -1,6 +1,5 @@
import '../../../olm-loader';
import expect from 'expect';
import Promise from 'bluebird';
import sdk from '../../../..';
@@ -10,6 +9,7 @@ import MockStorageApi from '../../../MockStorageApi';
import testUtils from '../../../test-utils';
import OlmDevice from '../../../../lib/crypto/OlmDevice';
import Crypto from '../../../../lib/crypto';
import logger from '../../../../src/logger';
const MatrixEvent = sdk.MatrixEvent;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
@@ -21,20 +21,20 @@ const Olm = global.Olm;
describe("MegolmDecryption", function() {
if (!global.Olm) {
console.warn('Not running megolm unit tests: libolm not present');
logger.warn('Not running megolm unit tests: libolm not present');
return;
}
beforeAll(function() {
return Olm.init();
});
let megolmDecryption;
let mockOlmLib;
let mockCrypto;
let mockBaseApis;
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
await Olm.init();
mockCrypto = testUtils.mock(Crypto, 'Crypto');
mockBaseApis = {};
@@ -54,9 +54,9 @@ describe("MegolmDecryption", function() {
// we stub out the olm encryption bits
mockOlmLib = {};
mockOlmLib.ensureOlmSessionsForDevices = expect.createSpy();
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
mockOlmLib.encryptMessageForDevice =
expect.createSpy().andReturn(Promise.resolve());
jest.fn().mockReturnValue(Promise.resolve());
megolmDecryption.olmlib = mockOlmLib;
});
@@ -134,22 +134,22 @@ describe("MegolmDecryption", function() {
// set up some pre-conditions for the share call
const deviceInfo = {};
mockCrypto.getStoredDevice.andReturn(deviceInfo);
mockCrypto.getStoredDevice.mockReturnValue(deviceInfo);
mockOlmLib.ensureOlmSessionsForDevices.andReturn(
mockOlmLib.ensureOlmSessionsForDevices.mockReturnValue(
Promise.resolve({'@alice:foo': {'alidevice': {
sessionId: 'alisession',
}}}),
);
const awaitEncryptForDevice = new Promise((res, rej) => {
mockOlmLib.encryptMessageForDevice.andCall(() => {
mockOlmLib.encryptMessageForDevice.mockImplementation(() => {
res();
return Promise.resolve();
});
});
mockBaseApis.sendToDevice = expect.createSpy();
mockBaseApis.sendToDevice = jest.fn();
// do the share
megolmDecryption.shareKeysWithDevice(keyRequest);
@@ -159,21 +159,20 @@ describe("MegolmDecryption", function() {
}).then(() => {
// check that it called encryptMessageForDevice with
// appropriate args.
expect(mockOlmLib.encryptMessageForDevice.calls.length)
.toEqual(1);
expect(mockOlmLib.encryptMessageForDevice).toBeCalledTimes(1);
const call = mockOlmLib.encryptMessageForDevice.calls[0];
const payload = call.arguments[6];
const call = mockOlmLib.encryptMessageForDevice.mock.calls[0];
const payload = call[6];
expect(payload.type).toEqual("m.forwarded_room_key");
expect(payload.content).toInclude({
expect(payload.content).toMatchObject({
sender_key: "SENDER_CURVE25519",
sender_claimed_ed25519_key: "SENDER_ED25519",
session_id: groupSession.session_id(),
chain_index: 0,
forwarding_curve25519_key_chain: [],
});
expect(payload.content.session_key).toExist();
expect(payload.content.session_key).toBeDefined();
});
});
@@ -200,13 +199,12 @@ describe("MegolmDecryption", function() {
origin_server_ts: 1507753886000,
});
const successHandler = expect.createSpy();
const failureHandler = expect.createSpy()
.andCall((err) => {
expect(err.toString()).toMatch(
/Duplicate message index, possible replay attack/,
);
});
const successHandler = jest.fn();
const failureHandler = jest.fn((err) => {
expect(err.toString()).toMatch(
/Duplicate message index, possible replay attack/,
);
});
return megolmDecryption.decryptEvent(event1).then((res) => {
const event2 = new MatrixEvent({
@@ -227,7 +225,7 @@ describe("MegolmDecryption", function() {
successHandler,
failureHandler,
).then(() => {
expect(successHandler).toNotHaveBeenCalled();
expect(successHandler).not.toHaveBeenCalled();
expect(failureHandler).toHaveBeenCalled();
});
});
@@ -265,10 +263,10 @@ describe("MegolmDecryption", function() {
const cryptoStore = new MemoryCryptoStore(mockStorage);
const olmDevice = new OlmDevice(cryptoStore);
olmDevice.verifySignature = expect.createSpy();
olmDevice.verifySignature = jest.fn();
await olmDevice.init();
mockBaseApis.claimOneTimeKeys = expect.createSpy().andReturn(Promise.resolve({
mockBaseApis.claimOneTimeKeys = jest.fn().mockReturnValue(Promise.resolve({
one_time_keys: {
'@alice:home.server': {
aliceDevice: {
@@ -284,18 +282,18 @@ describe("MegolmDecryption", function() {
},
},
}));
mockBaseApis.sendToDevice = expect.createSpy().andReturn(Promise.resolve());
mockBaseApis.sendToDevice = jest.fn().mockReturnValue(Promise.resolve());
mockCrypto.downloadKeys.andReturn(Promise.resolve({
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
'@alice:home.server': {
aliceDevice: {
deviceId: 'aliceDevice',
isBlocked: expect.createSpy().andReturn(false),
isUnverified: expect.createSpy().andReturn(false),
getIdentityKey: expect.createSpy().andReturn(
isBlocked: jest.fn().mockReturnValue(false),
isUnverified: jest.fn().mockReturnValue(false),
getIdentityKey: jest.fn().mockReturnValue(
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE',
),
getFingerprint: expect.createSpy().andReturn(''),
getFingerprint: jest.fn().mockReturnValue(''),
},
},
}));
@@ -311,10 +309,10 @@ describe("MegolmDecryption", function() {
},
});
const mockRoom = {
getEncryptionTargetMembers: expect.createSpy().andReturn(
getEncryptionTargetMembers: jest.fn().mockReturnValue(
[{userId: "@alice:home.server"}],
),
getBlacklistUnverifiedDevices: expect.createSpy().andReturn(false),
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(false),
};
const ct1 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some text",
@@ -322,25 +320,25 @@ describe("MegolmDecryption", function() {
expect(mockRoom.getEncryptionTargetMembers).toHaveBeenCalled();
// this should have claimed a key for alice as it's starting a new session
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalled(
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519',
);
expect(mockCrypto.downloadKeys).toHaveBeenCalledWith(
['@alice:home.server'], false,
);
expect(mockBaseApis.sendToDevice).toHaveBeenCalled();
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalled(
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519',
);
mockBaseApis.claimOneTimeKeys.reset();
mockBaseApis.claimOneTimeKeys.mockReset();
const ct2 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some more text",
});
// this should *not* have claimed a key as it should be using the same session
expect(mockBaseApis.claimOneTimeKeys).toNotHaveBeenCalled();
expect(mockBaseApis.claimOneTimeKeys).not.toHaveBeenCalled();
// likewise they should show the same session ID
expect(ct2.session_id).toEqual(ct1.session_id);
+6 -7
View File
@@ -16,10 +16,9 @@ limitations under the License.
import '../../../olm-loader';
import expect from 'expect';
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
import MockStorageApi from '../../../MockStorageApi';
import testUtils from '../../../test-utils';
import logger from '../../../../src/logger';
import OlmDevice from '../../../../lib/crypto/OlmDevice';
import olmlib from '../../../../lib/crypto/olmlib';
@@ -45,18 +44,18 @@ async function setupSession(initiator, opponent) {
describe("OlmDecryption", function() {
if (!global.Olm) {
console.warn('Not running megolm unit tests: libolm not present');
logger.warn('Not running megolm unit tests: libolm not present');
return;
}
beforeAll(function() {
return global.Olm.init();
});
let aliceOlmDevice;
let bobOlmDevice;
beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
await global.Olm.init();
aliceOlmDevice = makeOlmDevice();
bobOlmDevice = makeOlmDevice();
await aliceOlmDevice.init();
+102 -24
View File
@@ -16,7 +16,6 @@ limitations under the License.
import '../../olm-loader';
import expect from 'expect';
import Promise from 'bluebird';
import sdk from '../../..';
@@ -28,6 +27,8 @@ import testUtils from '../../test-utils';
import OlmDevice from '../../../lib/crypto/OlmDevice';
import Crypto from '../../../lib/crypto';
import logger from '../../../src/logger';
import olmlib from '../../../lib/crypto/olmlib';
const Olm = global.Olm;
@@ -82,20 +83,30 @@ const BACKUP_INFO = {
},
};
const keys = {};
function getCrossSigningKey(type) {
return keys[type];
}
function saveCrossSigningKeys(k) {
Object.assign(keys, k);
}
function makeTestClient(sessionStore, cryptoStore) {
const scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {});
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
const store = [
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom",
"storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter",
"storeFilter", "getSyncAccumulator", "startup", "deleteAllData",
].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {});
store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null));
store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null));
store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null));
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null));
return new MatrixClient({
baseUrl: "https://my.home.server",
idBaseUrl: "https://identity.server",
@@ -107,15 +118,20 @@ function makeTestClient(sessionStore, cryptoStore) {
deviceId: "device",
sessionStore: sessionStore,
cryptoStore: cryptoStore,
cryptoCallbacks: { getCrossSigningKey, saveCrossSigningKeys },
});
}
describe("MegolmBackup", function() {
if (!global.Olm) {
console.warn('Not running megolm backup unit tests: libolm not present');
logger.warn('Not running megolm backup unit tests: libolm not present');
return;
}
beforeAll(function() {
return Olm.init();
});
let olmDevice;
let mockOlmLib;
let mockCrypto;
@@ -124,9 +140,6 @@ describe("MegolmBackup", function() {
let cryptoStore;
let megolmDecryption;
beforeEach(async function() {
await Olm.init();
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
mockCrypto = testUtils.mock(Crypto, 'Crypto');
mockCrypto.backupKey = new Olm.PkEncryption();
mockCrypto.backupKey.set_recipient_key(
@@ -142,9 +155,9 @@ describe("MegolmBackup", function() {
// we stub out the olm encryption bits
mockOlmLib = {};
mockOlmLib.ensureOlmSessionsForDevices = expect.createSpy();
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
mockOlmLib.encryptMessageForDevice =
expect.createSpy().andReturn(Promise.resolve());
jest.fn().mockReturnValue(Promise.resolve());
});
describe("backup", function() {
@@ -205,7 +218,7 @@ describe("MegolmBackup", function() {
};
mockCrypto.cancelRoomKeyRequest = function() {};
mockCrypto.backupGroupSession = expect.createSpy();
mockCrypto.backupGroupSession = jest.fn();
return event.attemptDecryption(mockCrypto).then(() => {
return megolmDecryption.onRoomKeyEvent(event);
@@ -266,7 +279,7 @@ describe("MegolmBackup", function() {
callback, method, path, queryParams, data, opts,
) {
++numCalls;
expect(numCalls).toBeLessThanOrEqualTo(1);
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"));
@@ -275,8 +288,8 @@ describe("MegolmBackup", function() {
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams.version).toBe(1);
expect(data.rooms[ROOM_ID].sessions).toExist();
expect(data.rooms[ROOM_ID].sessions).toIncludeKey(
expect(data.rooms[ROOM_ID].sessions).toBeDefined();
expect(data.rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
);
resolve();
@@ -295,6 +308,71 @@ describe("MegolmBackup", function() {
});
});
it('signs backups with the cross-signing master key', async function() {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const ibGroupSession = new Olm.InboundGroupSession();
ibGroupSession.create(groupSession.session_key());
const client = makeTestClient(sessionStore, cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
megolmDecryption.olmlib = mockOlmLib;
await client.initCrypto();
let privateKeys;
client.uploadDeviceSigningKeys = async function(e) {return;};
client.uploadKeySignatures = async function(e) {return;};
client.on("crossSigning.saveCrossSigningKeys", function(e) {
privateKeys = e;
});
client.on("crossSigning.getKey", function(e) {
e.done(privateKeys[e.type]);
});
await client.resetCrossSigningKeys();
let numCalls = 0;
await new Promise(async (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 client.createKeyBackupVersion({
algorithm: "m.megolm_backup.v1",
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
},
});
});
expect(numCalls).toBe(1);
});
it('retries when a backup fails', function() {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
@@ -304,16 +382,16 @@ describe("MegolmBackup", function() {
const scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {});
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
const store = [
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom",
"storeUser", "getFilterIdByName", "setFilterIdByName", "getFilter",
"storeFilter", "getSyncAccumulator", "startup", "deleteAllData",
].reduce((r, k) => {r[k] = expect.createSpy(); return r;}, {});
store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null));
store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null));
store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null));
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null));
const client = new MatrixClient({
baseUrl: "https://my.home.server",
idBaseUrl: "https://identity.server",
@@ -371,7 +449,7 @@ describe("MegolmBackup", function() {
callback, method, path, queryParams, data, opts,
) {
++numCalls;
expect(numCalls).toBeLessThanOrEqualTo(2);
expect(numCalls).toBeLessThanOrEqual(2);
if (numCalls >= 3) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
@@ -380,8 +458,8 @@ describe("MegolmBackup", function() {
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams.version).toBe(1);
expect(data.rooms[ROOM_ID].sessions).toExist();
expect(data.rooms[ROOM_ID].sessions).toIncludeKey(
expect(data.rooms[ROOM_ID].sessions).toBeDefined();
expect(data.rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
);
if (numCalls > 1) {
+798
View File
@@ -0,0 +1,798 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 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 '../../olm-loader';
import anotherjson from 'another-json';
import olmlib from '../../../lib/crypto/olmlib';
import TestClient from '../../TestClient';
import {HttpResponse, setHttpResponses} from '../../test-utils';
async function makeTestClient(userInfo, options, keys) {
if (!keys) keys = {};
function getCrossSigningKey(type) {
return keys[type];
}
function saveCrossSigningKeys(k) {
Object.assign(keys, k);
}
if (!options) options = {};
options.cryptoCallbacks = Object.assign(
{}, { getCrossSigningKey, saveCrossSigningKeys }, options.cryptoCallbacks || {},
);
const client = (new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
)).client;
await client.initCrypto();
return client;
}
describe("Cross Signing", function() {
if (!global.Olm) {
console.warn('Not running megolm backup unit tests: libolm not present');
return;
}
beforeAll(function() {
return global.Olm.init();
});
it("should sign the master key with the device key", async function() {
const alice = await makeTestClient(
{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.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await alice.resetCrossSigningKeys();
expect(alice.uploadDeviceSigningKeys).toHaveBeenCalled();
});
it("should upload a signature when a user is verified", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await alice.resetCrossSigningKeys();
// Alice downloads Bob's device key
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
"ed25519:bobs+master+pubkey": "bobs+master+pubkey",
},
},
},
});
// Alice verifies Bob's key
const promise = new Promise((resolve, reject) => {
alice.uploadKeySignatures = (...args) => {
resolve(...args);
};
});
await alice.setDeviceVerified("@bob:example.com", "bobs+master+pubkey", true);
// Alice should send a signature of Bob's key to the server
await promise;
});
it("should get cross-signing keys from sync", async function() {
const masterKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
]);
const selfSigningKey = new Uint8Array([
0x1e, 0xf4, 0x01, 0x6d, 0x4f, 0xa1, 0x73, 0x66,
0x6b, 0xf8, 0x93, 0xf5, 0xb0, 0x4d, 0x17, 0xc0,
0x17, 0xb5, 0xa5, 0xf6, 0x59, 0x11, 0x8b, 0x49,
0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
]);
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
// will be called to sign our own device
getCrossSigningKey: type => {
if (type === 'master') {
return masterKey;
} else {
return selfSigningKey;
}
},
},
},
);
const keyChangePromise = new Promise((resolve, reject) => {
alice.once("crossSigning.keysChanged", async (e) => {
resolve(e);
await alice.checkOwnCrossSigningTrust();
});
});
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();
});
});
const deviceInfo = alice._crypto._deviceList._devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
device_id: "Osborne2",
};
aliceDevice.keys = deviceInfo.keys;
aliceDevice.algorithms = deviceInfo.algorithms;
await alice._crypto._signObject(aliceDevice);
olmlib.pkSign(aliceDevice, selfSigningKey, "@alice:example.com");
// feed sync result that includes master key, ssk, device key
const responses = [
HttpResponse.PUSH_RULES_RESPONSE,
{
method: "POST",
path: "/keys/upload/Osborne2",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
HttpResponse.filterResponse("@alice:example.com"),
{
method: "GET",
path: "/sync",
data: {
next_batch: "abcdefg",
device_lists: {
changed: [
"@alice:example.com",
"@bob:example.com",
],
},
},
},
{
method: "POST",
path: "/keys/query",
data: {
"failures": {},
"device_keys": {
"@alice:example.com": {
"Osborne2": aliceDevice,
},
},
"master_keys": {
"@alice:example.com": {
user_id: "@alice:example.com",
usage: ["master"],
keys: {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
},
},
},
"self_signing_keys": {
"@alice:example.com": {
user_id: "@alice:example.com",
usage: ["self-signing"],
keys: {
"ed25519:EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ":
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
},
signatures: {
"@alice:example.com": {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"Wqx/HXR851KIi8/u/UX+fbAMtq9Uj8sr8FsOcqrLfVYa6lAmbXs"
+ "Vhfy4AlZ3dnEtjgZx0U0QDrghEn2eYBeOCA",
},
},
},
},
},
},
{
method: "POST",
path: "/keys/upload/Osborne2",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
];
setHttpResponses(alice, responses, true, true);
await alice.startClient();
// once ssk is confirmed, device key should be trusted
await keyChangePromise;
await uploadSigsPromise;
const aliceTrust = alice.checkUserTrust("@alice:example.com");
expect(aliceTrust.isCrossSigningVerified()).toBeTruthy();
expect(aliceTrust.isTofu()).toBeTruthy();
expect(aliceTrust.isVerified()).toBeTruthy();
const aliceDeviceTrust = alice.checkDeviceTrust("@alice:example.com", "Osborne2");
expect(aliceDeviceTrust.isCrossSigningVerified()).toBeTruthy();
expect(aliceDeviceTrust.isLocallyVerified()).toBeTruthy();
expect(aliceDeviceTrust.isTofu()).toBeTruthy();
expect(aliceDeviceTrust.isVerified()).toBeTruthy();
});
it("should use trust chain to determine device verification", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await alice.resetCrossSigningKeys();
// Alice downloads Bob's ssk and device key
const bobMasterSigning = new global.Olm.PkSigning();
const bobMasterPrivkey = bobMasterSigning.generate_seed();
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
const bobSigning = new global.Olm.PkSigning();
const bobPrivkey = bobSigning.generate_seed();
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
const bobSSK = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey]: bobPubkey,
},
};
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
bobSSK.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
},
},
self_signing: bobSSK,
},
firstUse: 1,
unsigned: {},
});
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": "somePubkey",
"ed25519:Dynabook": "someOtherPubkey",
},
};
const sig = bobSigning.sign(anotherjson.stringify(bobDevice));
bobDevice.signatures = {
"@bob:example.com": {
["ed25519:" + bobPubkey]: sig,
},
};
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device key should be TOFU
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isVerified()).toBeFalsy();
expect(bobTrust.isTofu()).toBeTruthy();
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isVerified()).toBeFalsy();
expect(bobDeviceTrust.isTofu()).toBeTruthy();
// Alice verifies Bob's SSK
alice.uploadKeySignatures = () => {};
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
// Bob's device key should be trusted
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust2.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust2.isTofu()).toBeTruthy();
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust2.isCrossSigningVerified()).toBeTruthy();
expect(bobDeviceTrust2.isLocallyVerified()).toBeFalsy();
expect(bobDeviceTrust2.isTofu()).toBeTruthy();
});
it("should trust signatures received from other devices", async function() {
const aliceKeys = {};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
null,
aliceKeys,
);
alice._crypto._deviceList.startTrackingDeviceList("@bob:example.com");
alice._crypto._deviceList.stopTrackingAllDeviceLists = () => {};
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await alice.resetCrossSigningKeys();
const selfSigningKey = new Uint8Array([
0x1e, 0xf4, 0x01, 0x6d, 0x4f, 0xa1, 0x73, 0x66,
0x6b, 0xf8, 0x93, 0xf5, 0xb0, 0x4d, 0x17, 0xc0,
0x17, 0xb5, 0xa5, 0xf6, 0x59, 0x11, 0x8b, 0x49,
0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
]);
const keyChangePromise = new Promise((resolve, reject) => {
alice._crypto._deviceList.once("userCrossSigningUpdated", (userId) => {
if (userId === "@bob:example.com") {
resolve();
}
});
});
const deviceInfo = alice._crypto._deviceList._devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
device_id: "Osborne2",
};
aliceDevice.keys = deviceInfo.keys;
aliceDevice.algorithms = deviceInfo.algorithms;
await alice._crypto._signObject(aliceDevice);
const bobOlmAccount = new global.Olm.Account();
bobOlmAccount.create();
const bobKeys = JSON.parse(bobOlmAccount.identity_keys());
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobKeys.ed25519,
"curve25519:Dynabook": bobKeys.curve25519,
},
};
const deviceStr = anotherjson.stringify(bobDevice);
bobDevice.signatures = {
"@bob:example.com": {
"ed25519:Dynabook": bobOlmAccount.sign(deviceStr),
},
};
olmlib.pkSign(bobDevice, selfSigningKey, "@bob:example.com");
const bobMaster = {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
},
};
olmlib.pkSign(bobMaster, aliceKeys.user_signing, "@alice:example.com");
// Alice downloads Bob's keys
// - device key
// - ssk
// - master key signed by her usk (pretend that it was signed by another
// of Alice's devices)
const responses = [
HttpResponse.PUSH_RULES_RESPONSE,
{
method: "POST",
path: "/keys/upload/Osborne2",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
HttpResponse.filterResponse("@alice:example.com"),
{
method: "GET",
path: "/sync",
data: {
next_batch: "abcdefg",
device_lists: {
changed: [
"@bob:example.com",
],
},
},
},
{
method: "POST",
path: "/keys/query",
data: {
"failures": {},
"device_keys": {
"@alice:example.com": {
"Osborne2": aliceDevice,
},
"@bob:example.com": {
"Dynabook": bobDevice,
},
},
"master_keys": {
"@bob:example.com": bobMaster,
},
"self_signing_keys": {
"@bob:example.com": {
user_id: "@bob:example.com",
usage: ["self-signing"],
keys: {
"ed25519:EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ":
"EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
},
signatures: {
"@bob:example.com": {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"2KLiufImvEbfJuAFvsaZD+PsL8ELWl7N1u9yr/9hZvwRghBfQMB"
+ "LAI86b1kDV9+Cq1lt85ykReeCEzmTEPY2BQ",
},
},
},
},
},
},
{
method: "POST",
path: "/keys/upload/Osborne2",
data: {
one_time_key_counts: {
curve25519: 100,
signed_curve25519: 100,
},
},
},
];
setHttpResponses(alice, responses);
await alice.startClient();
await keyChangePromise;
// Bob's device key should be trusted
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust.isTofu()).toBeTruthy();
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isCrossSigningVerified()).toBeTruthy();
expect(bobDeviceTrust.isLocallyVerified()).toBeFalsy();
expect(bobDeviceTrust.isTofu()).toBeTruthy();
});
it("should dis-trust an unsigned device", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// set Alice's cross-signing key
await alice.resetCrossSigningKeys();
// Alice downloads Bob's ssk and device key
// (NOTE: device key is not signed by ssk)
const bobMasterSigning = new global.Olm.PkSigning();
const bobMasterPrivkey = bobMasterSigning.generate_seed();
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
const bobSigning = new global.Olm.PkSigning();
const bobPrivkey = bobSigning.generate_seed();
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
const bobSSK = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey]: bobPubkey,
},
};
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
bobSSK.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
},
},
self_signing: bobSSK,
},
firstUse: 1,
unsigned: {},
});
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": "somePubkey",
"ed25519:Dynabook": "someOtherPubkey",
},
};
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device key should be untrusted
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isVerified()).toBeFalsy();
expect(bobDeviceTrust.isTofu()).toBeFalsy();
// Alice verifies Bob's SSK
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
// Bob's device key should be untrusted
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust2.isVerified()).toBeFalsy();
expect(bobDeviceTrust2.isTofu()).toBeFalsy();
});
it("should dis-trust a user when their ssk changes", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
await alice.resetCrossSigningKeys();
// Alice downloads Bob's keys
const bobMasterSigning = new global.Olm.PkSigning();
const bobMasterPrivkey = bobMasterSigning.generate_seed();
const bobMasterPubkey = bobMasterSigning.init_with_seed(bobMasterPrivkey);
const bobSigning = new global.Olm.PkSigning();
const bobPrivkey = bobSigning.generate_seed();
const bobPubkey = bobSigning.init_with_seed(bobPrivkey);
const bobSSK = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey]: bobPubkey,
},
};
const sskSig = bobMasterSigning.sign(anotherjson.stringify(bobSSK));
bobSSK.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey]: bobMasterPubkey,
},
},
self_signing: bobSSK,
},
firstUse: 1,
unsigned: {},
});
const bobDevice = {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": "somePubkey",
"ed25519:Dynabook": "someOtherPubkey",
},
};
const bobDeviceString = anotherjson.stringify(bobDevice);
const sig = bobSigning.sign(bobDeviceString);
bobDevice.signatures = {};
bobDevice.signatures["@bob:example.com"] = {};
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey] = sig;
alice._crypto._deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Alice verifies Bob's SSK
alice.uploadKeySignatures = () => {};
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey, true);
// Bob's device key should be trusted
const bobDeviceTrust = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isVerified()).toBeTruthy();
expect(bobDeviceTrust.isTofu()).toBeTruthy();
// Alice downloads new SSK for Bob
const bobMasterSigning2 = new global.Olm.PkSigning();
const bobMasterPrivkey2 = bobMasterSigning2.generate_seed();
const bobMasterPubkey2 = bobMasterSigning2.init_with_seed(bobMasterPrivkey2);
const bobSigning2 = new global.Olm.PkSigning();
const bobPrivkey2 = bobSigning2.generate_seed();
const bobPubkey2 = bobSigning2.init_with_seed(bobPrivkey2);
const bobSSK2 = {
user_id: "@bob:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + bobPubkey2]: bobPubkey2,
},
};
const sskSig2 = bobMasterSigning2.sign(anotherjson.stringify(bobSSK2));
bobSSK2.signatures = {
"@bob:example.com": {
["ed25519:" + bobMasterPubkey2]: sskSig2,
},
};
alice._crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
["ed25519:" + bobMasterPubkey2]: bobMasterPubkey2,
},
},
self_signing: bobSSK2,
},
firstUse: 0,
unsigned: {},
});
// Bob's and his device should be untrusted
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isVerified()).toBeFalsy();
expect(bobTrust.isTofu()).toBeFalsy();
const bobDeviceTrust2 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust2.isVerified()).toBeFalsy();
expect(bobDeviceTrust2.isTofu()).toBeFalsy();
// Alice verifies Bob's SSK
alice.uploadKeySignatures = () => {};
await alice.setDeviceVerified("@bob:example.com", bobMasterPubkey2, true);
// Bob should be trusted but not his device
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust2.isVerified()).toBeTruthy();
const bobDeviceTrust3 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust3.isVerified()).toBeFalsy();
// 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", {
Dynabook: bobDevice,
});
// Bob's device should be trusted again (but not TOFU)
const bobTrust3 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust3.isVerified()).toBeTruthy();
const bobDeviceTrust4 = alice.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust4.isCrossSigningVerified()).toBeTruthy();
});
it("should offer to upgrade device verifications to cross-signing", async function() {
let upgradeResolveFunc;
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
shouldUpgradeDeviceVerifications: (verifs) => {
expect(verifs.users["@bob:example.com"]).toBeDefined();
upgradeResolveFunc();
return ["@bob:example.com"];
},
},
},
);
const bob = await makeTestClient(
{userId: "@bob:example.com", deviceId: "Dynabook"},
);
bob.uploadDeviceSigningKeys = async () => {};
bob.uploadKeySignatures = async () => {};
// set Bob's cross-signing key
await bob.resetCrossSigningKeys();
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,
},
verified: 1,
known: true,
},
});
alice._crypto._deviceList.storeCrossSigningForUser(
"@bob:example.com",
bob._crypto._crossSigningInfo.toStorage(),
);
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
// when alice sets up cross-signing, she should notice that bob's
// cross-signing key is signed by his Dynabook, which alice has
// verified, and ask if the device verification should be upgraded to a
// cross-signing verification
let upgradePromise = new Promise((resolve) => {
upgradeResolveFunc = resolve;
});
await alice.resetCrossSigningKeys();
await upgradePromise;
const bobTrust = alice.checkUserTrust("@bob:example.com");
expect(bobTrust.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust.isTofu()).toBeTruthy();
// "forget" that Bob is trusted
delete alice._crypto._deviceList._crossSigningInfo["@bob:example.com"]
.keys.master.signatures["@alice:example.com"];
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust2.isCrossSigningVerified()).toBeFalsy();
expect(bobTrust2.isTofu()).toBeTruthy();
upgradePromise = new Promise((resolve) => {
upgradeResolveFunc = resolve;
});
alice._crypto._deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
await upgradePromise;
const bobTrust3 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust3.isCrossSigningVerified()).toBeTruthy();
expect(bobTrust3.isTofu()).toBeTruthy();
});
});
+247
View File
@@ -0,0 +1,247 @@
/*
Copyright 2019 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 '../../olm-loader';
import { MatrixEvent } from '../../../lib/models/event';
import { SECRET_STORAGE_ALGORITHM_V1 } from '../../../lib/crypto/SecretStorage';
import olmlib from '../../../lib/crypto/olmlib';
import TestClient from '../../TestClient';
import { makeTestClients } from './verification/util';
async function makeTestClient(userInfo, options) {
const client = (new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
)).client;
await client.initCrypto();
return client;
}
describe("Secrets", function() {
if (!global.Olm) {
console.warn('Not running megolm backup unit tests: libolm not present');
return;
}
beforeAll(function() {
return global.Olm.init();
});
it("should store and retrieve a secret", async function() {
const decryption = new global.Olm.PkDecryption();
const pubkey = decryption.generate_key();
const privkey = decryption.get_private_key();
const signing = new global.Olm.PkSigning();
const signingKey = signing.generate_seed();
const signingPubKey = signing.init_with_seed(signingKey);
const signingkeyInfo = {
user_id: "@alice:example.com",
usage: ['master'],
keys: {
['ed25519:' + signingPubKey]: signingPubKey,
},
};
const getKey = jest.fn(e => {
expect(Object.keys(e.keys)).toEqual(["abc"]);
return ['abc', privkey];
});
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
getCrossSigningKey: t => signingKey,
getSecretStorageKey: getKey,
},
},
);
alice._crypto._crossSigningInfo.setKeys({
master: signingkeyInfo,
});
const secretStorage = alice._crypto._secretStorage;
alice.setAccountData = async function(eventType, contents, callback) {
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: eventType,
content: contents,
}),
]);
if (callback) {
callback();
}
};
const keyAccountData = {
algorithm: SECRET_STORAGE_ALGORITHM_V1,
pubkey: pubkey,
};
await alice._crypto._crossSigningInfo.signObject(keyAccountData, 'master');
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: "m.secret_storage.key.abc",
content: keyAccountData,
}),
]);
expect(secretStorage.isStored("foo")).toBe(false);
await secretStorage.store("foo", "bar", ["abc"]);
expect(secretStorage.isStored("foo")).toBe(true);
expect(await secretStorage.get("foo")).toBe("bar");
expect(getKey).toHaveBeenCalled();
});
it("should throw if given a key that doesn't exist", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
try {
await alice.storeSecret("foo", "bar", ["this secret does not exist"]);
// should be able to use expect(...).toThrow() but mocha still fails
// the test even when it throws for reasons I have no inclination to debug
expect(true).toBeFalsy();
} catch (e) {
}
});
it("should refuse to encrypt with zero keys", async function() {
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
);
try {
await alice.storeSecret("foo", "bar", []);
expect(true).toBeFalsy();
} catch (e) {
}
});
it("should encrypt with default key if keys is null", async function() {
let keys = {};
const alice = await makeTestClient(
{userId: "@alice:example.com", deviceId: "Osborne2"},
{
cryptoCallbacks: {
getCrossSigningKey: t => keys[t],
saveCrossSigningKeys: k => keys = k,
},
},
);
alice.setAccountData = async function(eventType, contents, callback) {
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: eventType,
content: contents,
}),
]);
};
alice.resetCrossSigningKeys();
const newKeyId = await alice.addSecretKey(
SECRET_STORAGE_ALGORITHM_V1,
);
// we don't await on this because it waits for the event to come down the sync
// which won't happen in the test setup
alice.setDefaultSecretStorageKeyId(newKeyId);
await alice.storeSecret("foo", "bar");
const accountData = alice.getAccountData('foo');
expect(accountData.getContent().encrypted).toBeTruthy();
});
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"},
);
try {
await alice.storeSecret("foo", "bar");
expect(true).toBeFalsy();
} catch (e) {
}
});
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"},
],
{
cryptoCallbacks: {
onSecretRequested: e => {
expect(e.name).toBe("foo");
return "bar";
},
},
},
);
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", {
"VAX": {
user_id: "@alice:example.com",
device_id: "VAX",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:VAX": vaxDevice.deviceEd25519Key,
"curve25519:VAX": vaxDevice.deviceCurve25519Key,
},
},
});
vax.client._crypto._deviceList.storeDevicesForUser("@alice:example.com", {
"Osborne2": {
user_id: "@alice:example.com",
device_id: "Osborne2",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Osborne2": osborne2Device.deviceEd25519Key,
"curve25519:Osborne2": osborne2Device.deviceCurve25519Key,
},
},
});
await osborne2Device.generateOneTimeKeys(1);
const otks = (await osborne2Device.getOneTimeKeys()).curve25519;
await osborne2Device.markKeysAsPublished();
await vax.client._crypto._olmDevice.createOutboundSession(
osborne2Device.deviceCurve25519Key,
Object.values(otks)[0],
);
const request = await secretStorage.request("foo", ["VAX"]);
const secret = await request.promise;
expect(secret).toBe("bar");
});
});
+17 -18
View File
@@ -13,15 +13,14 @@ 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 logger from '../../../../src/logger';
try {
global.Olm = require('olm');
} catch (e) {
console.warn("unable to run device verification tests: libolm not available");
logger.warn("unable to run device verification tests: libolm not available");
}
import expect from 'expect';
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
import {ShowQRCode, ScanQRCode} from '../../../../lib/crypto/verification/QRCode';
@@ -30,12 +29,12 @@ const Olm = global.Olm;
describe("QR code verification", function() {
if (!global.Olm) {
console.warn('Not running device verification tests: libolm not present');
logger.warn('Not running device verification tests: libolm not present');
return;
}
beforeEach(async function() {
await Olm.init();
beforeAll(function() {
return Olm.init();
});
describe("showing", function() {
@@ -47,7 +46,7 @@ describe("QR code verification", function() {
return "device+ed25519+key";
},
});
const spy = expect.createSpy().andCall((e) => {
const spy = jest.fn((e) => {
qrCode.done();
});
qrCode.on("show_qr_code", spy);
@@ -77,8 +76,8 @@ describe("QR code verification", function() {
"ABCDEFG",
);
const client = {
getStoredDevice: expect.createSpy().andReturn(device),
setDeviceVerified: expect.createSpy(),
getStoredDevice: jest.fn().mockReturnValue(device),
setDeviceVerified: jest.fn(),
};
const qrCode = new ScanQRCode(client);
qrCode.on("confirm_user_id", ({userId, confirm}) => {
@@ -100,18 +99,18 @@ describe("QR code verification", function() {
it("should error when the user ID doesn't match", async function() {
const client = {
getStoredDevice: expect.createSpy(),
setDeviceVerified: expect.createSpy(),
getStoredDevice: jest.fn(),
setDeviceVerified: jest.fn(),
};
const qrCode = new ScanQRCode(client, "@bob:example.com", "ABCDEFG");
qrCode.on("scan", ({done}) => {
done(QR_CODE_URL);
});
const spy = expect.createSpy();
const spy = jest.fn();
await qrCode.verify().catch(spy);
expect(spy).toHaveBeenCalled();
expect(client.getStoredDevice).toNotHaveBeenCalled();
expect(client.setDeviceVerified).toNotHaveBeenCalled();
expect(client.getStoredDevice).not.toHaveBeenCalled();
expect(client.setDeviceVerified).not.toHaveBeenCalled();
});
it("should error if the key doesn't match", async function() {
@@ -129,18 +128,18 @@ describe("QR code verification", function() {
"ABCDEFG",
);
const client = {
getStoredDevice: expect.createSpy().andReturn(device),
setDeviceVerified: expect.createSpy(),
getStoredDevice: jest.fn().mockReturnValue(device),
setDeviceVerified: jest.fn(),
};
const qrCode = new ScanQRCode(client, "@alice:example.com", "ABCDEFG");
qrCode.on("scan", ({done}) => {
done(QR_CODE_URL);
});
const spy = expect.createSpy();
const spy = jest.fn();
await qrCode.verify().catch(spy);
expect(spy).toHaveBeenCalled();
expect(client.getStoredDevice).toHaveBeenCalled();
expect(client.setDeviceVerified).toNotHaveBeenCalled();
expect(client.setDeviceVerified).not.toHaveBeenCalled();
});
});
});
+17 -12
View File
@@ -13,15 +13,14 @@ 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 logger from '../../../../src/logger';
try {
global.Olm = require('olm');
} catch (e) {
console.warn("unable to run device verification tests: libolm not available");
logger.warn("unable to run device verification tests: libolm not available");
}
import expect from 'expect';
import {verificationMethods} from '../../../../lib/crypto';
import SAS from '../../../../lib/crypto/verification/SAS';
@@ -32,12 +31,12 @@ import {makeTestClients} from './util';
describe("verification request", function() {
if (!global.Olm) {
console.warn('Not running device verification unit tests: libolm not present');
logger.warn('Not running device verification unit tests: libolm not present');
return;
}
beforeEach(async function() {
await Olm.init();
beforeAll(function() {
return Olm.init();
});
it("should request and accept a verification", async function() {
@@ -50,7 +49,7 @@ describe("verification request", function() {
verificationMethods: [verificationMethods.SAS],
},
);
alice._crypto._deviceList.getRawStoredDevicesForUser = function() {
alice.client._crypto._deviceList.getRawStoredDevicesForUser = function() {
return {
Dynabook: {
keys: {
@@ -59,17 +58,23 @@ describe("verification request", function() {
},
};
};
alice.downloadKeys = () => {
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.downloadKeys = () => {
bob.client.downloadKeys = () => {
return Promise.resolve();
};
bob.on("crypto.verification.request", (request) => {
bob.client.on("crypto.verification.request", (request) => {
const bobVerifier = request.beginKeyVerification(verificationMethods.SAS);
bobVerifier.verify();
// XXX: Private function access (but it's a test, so we're okay)
bobVerifier._endTimer();
});
const aliceVerifier = await alice.requestVerification("@bob:example.com");
expect(aliceVerifier).toBeAn(SAS);
const aliceVerifier = await alice.client.requestVerification("@bob:example.com");
expect(aliceVerifier).toBeInstanceOf(SAS);
// XXX: Private function access (but it's a test, so we're okay)
aliceVerifier._endTimer();
});
});
+367 -143
View File
@@ -13,14 +13,15 @@ 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 logger from '../../../../src/logger';
try {
global.Olm = require('olm');
} catch (e) {
console.warn("unable to run device verification tests: libolm not available");
logger.warn("unable to run device verification tests: libolm not available");
}
import expect from 'expect';
import olmlib from '../../../../lib/crypto/olmlib';
import sdk from '../../../..';
@@ -35,14 +36,17 @@ const MatrixEvent = sdk.MatrixEvent;
import {makeTestClients} from './util';
let ALICE_DEVICES;
let BOB_DEVICES;
describe("SAS verification", function() {
if (!global.Olm) {
console.warn('Not running device verification unit tests: libolm not present');
logger.warn('Not running device verification unit tests: libolm not present');
return;
}
beforeEach(async function() {
await Olm.init();
beforeAll(function() {
return Olm.init();
});
it("should error on an unexpected event", async function() {
@@ -52,13 +56,15 @@ describe("SAS verification", function() {
type: "es.inquisition",
content: {},
}));
const spy = expect.createSpy();
await sas.verify()
.catch(spy);
const spy = jest.fn();
await sas.verify().catch(spy);
expect(spy).toHaveBeenCalled();
// Cancel the SAS for cleanup (we started a verification, so abort)
sas.cancel();
});
describe("verification", function() {
describe("verification", () => {
let alice;
let bob;
let aliceSasEvent;
@@ -66,7 +72,7 @@ describe("SAS verification", function() {
let aliceVerifier;
let bobPromise;
beforeEach(async function() {
beforeEach(async () => {
[alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
@@ -77,39 +83,44 @@ describe("SAS verification", function() {
},
);
alice.setDeviceVerified = expect.createSpy();
alice.getDeviceEd25519Key = () => {
return "alice+base64+ed25519+key";
};
alice.getStoredDevice = () => {
return DeviceInfo.fromStorage(
{
keys: {
"ed25519:Dynabook": "bob+base64+ed25519+key",
},
const aliceDevice = alice.client._crypto._olmDevice;
const bobDevice = bob.client._crypto._olmDevice;
ALICE_DEVICES = {
Osborne2: {
user_id: "@alice:example.com",
device_id: "Osborne2",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Osborne2": aliceDevice.deviceEd25519Key,
"curve25519:Osborne2": aliceDevice.deviceCurve25519Key,
},
"Dynabook",
);
},
};
alice.downloadKeys = () => {
BOB_DEVICES = {
Dynabook: {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice.deviceEd25519Key,
"curve25519:Dynabook": bobDevice.deviceCurve25519Key,
},
},
};
alice.client._crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.setDeviceVerified = expect.createSpy();
bob.getStoredDevice = () => {
return DeviceInfo.fromStorage(
{
keys: {
"ed25519:Osborne2": "alice+base64+ed25519+key",
},
},
"Osborne2",
);
};
bob.getDeviceEd25519Key = () => {
return "bob+base64+ed25519+key";
};
bob.downloadKeys = () => {
bob.client._crypto._deviceList.storeDevicesForUser(
"@alice:example.com", ALICE_DEVICES,
);
bob.client.downloadKeys = () => {
return Promise.resolve();
};
@@ -117,7 +128,7 @@ describe("SAS verification", function() {
bobSasEvent = null;
bobPromise = new Promise((resolve, reject) => {
bob.on("crypto.verification.start", (verifier) => {
bob.client.on("crypto.verification.start", (verifier) => {
verifier.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
@@ -138,8 +149,318 @@ describe("SAS verification", function() {
});
});
aliceVerifier = alice.beginKeyVerification(
verificationMethods.SAS, bob.getUserId(), bob.deviceId,
aliceVerifier = alice.client.beginKeyVerification(
verificationMethods.SAS, bob.client.getUserId(), bob.deviceId,
);
aliceVerifier.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!bobSasEvent) {
aliceSasEvent = e;
} else {
try {
expect(e.sas).toEqual(bobSasEvent.sas);
e.confirm();
bobSasEvent.confirm();
} catch (error) {
e.mismatch();
bobSasEvent.mismatch();
}
}
});
});
afterEach(async () => {
await Promise.all([
alice.stop(),
bob.stop(),
]);
});
it("should verify a key", async () => {
let macMethod;
const origSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = function(type, map) {
if (type === "m.key.verification.accept") {
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
.message_authentication_code;
}
return origSendToDevice(type, map);
};
alice.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@bob:example.com": BOB_DEVICES,
},
});
bob.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@alice:example.com": ALICE_DEVICES,
},
});
await Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => verifier.verify()),
alice.httpBackend.flush(),
bob.httpBackend.flush(),
]);
// make sure that it uses the preferred method
expect(macMethod).toBe("hkdf-hmac-sha256");
// make sure Alice and Bob verified each other
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice.isVerified()).toBeTruthy();
});
it("should be able to verify using the old MAC", async () => {
// pretend that Alice can only understand the old (incorrect) MAC,
// and make sure that she can still verify with Bob
let macMethod;
const aliceOrigSendToDevice = alice.client.sendToDevice.bind(alice.client);
alice.client.sendToDevice = (type, map) => {
if (type === "m.key.verification.start") {
// Note: this modifies not only the message that Bob
// receives, but also the copy of the message that Alice
// has, since it is the same object. If this does not
// happen, the verification will fail due to a hash
// commitment mismatch.
map[bob.client.getUserId()][bob.client.deviceId]
.message_authentication_codes = ['hmac-sha256'];
}
return aliceOrigSendToDevice(type, map);
};
const bobOrigSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = (type, map) => {
if (type === "m.key.verification.accept") {
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
.message_authentication_code;
}
return bobOrigSendToDevice(type, map);
};
alice.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@bob:example.com": BOB_DEVICES,
},
});
bob.httpBackend.when('POST', '/keys/query').respond(200, {
failures: {},
device_keys: {
"@alice:example.com": ALICE_DEVICES,
},
});
await Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => verifier.verify()),
alice.httpBackend.flush(),
bob.httpBackend.flush(),
]);
expect(macMethod).toBe("hmac-sha256");
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice.isVerified()).toBeTruthy();
});
it("should verify a cross-signing key", async () => {
alice.httpBackend.when('POST', '/keys/device_signing/upload').respond(
200, {},
);
alice.httpBackend.when('POST', '/keys/signatures/upload').respond(200, {});
alice.httpBackend.flush(undefined, 2);
await alice.client.resetCrossSigningKeys();
bob.httpBackend.when('POST', '/keys/device_signing/upload').respond(200, {});
bob.httpBackend.when('POST', '/keys/signatures/upload').respond(200, {});
bob.httpBackend.flush(undefined, 2);
await bob.client.resetCrossSigningKeys();
bob.client._crypto._deviceList.storeCrossSigningForUser(
"@alice:example.com", {
keys: alice.client._crypto._crossSigningInfo.keys,
},
);
const verifyProm = Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => {
bob.httpBackend.when(
'POST', '/keys/signatures/upload',
).respond(200, {});
bob.httpBackend.flush(undefined, 1, 2000);
return verifier.verify();
}),
]);
await verifyProm;
const bobDeviceTrust = alice.client.checkDeviceTrust(
"@bob:example.com", "Dynabook",
);
expect(bobDeviceTrust.isLocallyVerified()).toBeTruthy();
expect(bobDeviceTrust.isCrossSigningVerified()).toBeFalsy();
const aliceTrust = bob.client.checkUserTrust("@alice:example.com");
expect(aliceTrust.isCrossSigningVerified()).toBeTruthy();
expect(aliceTrust.isTofu()).toBeTruthy();
const aliceDeviceTrust = bob.client.checkDeviceTrust(
"@alice:example.com", "Osborne2",
);
expect(aliceDeviceTrust.isLocallyVerified()).toBeTruthy();
expect(aliceDeviceTrust.isCrossSigningVerified()).toBeFalsy();
});
});
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"},
],
{
verificationMethods: [verificationMethods.SAS],
},
);
alice.client.setDeviceVerified = jest.fn();
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.client.setDeviceVerified = jest.fn();
bob.client.downloadKeys = () => {
return Promise.resolve();
};
const bobPromise = new Promise((resolve, reject) => {
bob.client.on("crypto.verification.start", (verifier) => {
verifier.on("show_sas", (e) => {
e.mismatch();
});
resolve(verifier);
});
});
const aliceVerifier = alice.client.beginKeyVerification(
verificationMethods.SAS, bob.client.getUserId(), bob.client.deviceId,
);
const aliceSpy = jest.fn();
const bobSpy = jest.fn();
await Promise.all([
aliceVerifier.verify().catch(aliceSpy),
bobPromise.then((verifier) => verifier.verify()).catch(bobSpy),
]);
expect(aliceSpy).toHaveBeenCalled();
expect(bobSpy).toHaveBeenCalled();
expect(alice.client.setDeviceVerified)
.not.toHaveBeenCalled();
expect(bob.client.setDeviceVerified)
.not.toHaveBeenCalled();
});
describe("verification in DM", function() {
let alice;
let bob;
let aliceSasEvent;
let bobSasEvent;
let aliceVerifier;
let bobPromise;
beforeEach(async function() {
[alice, bob] = await makeTestClients(
[
{userId: "@alice:example.com", deviceId: "Osborne2"},
{userId: "@bob:example.com", deviceId: "Dynabook"},
],
{
verificationMethods: [verificationMethods.SAS],
},
);
alice.client.setDeviceVerified = jest.fn();
alice.client.getDeviceEd25519Key = () => {
return "alice+base64+ed25519+key";
};
alice.client.getStoredDevice = () => {
return DeviceInfo.fromStorage(
{
keys: {
"ed25519:Dynabook": "bob+base64+ed25519+key",
},
},
"Dynabook",
);
};
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.client.setDeviceVerified = jest.fn();
bob.client.getStoredDevice = () => {
return DeviceInfo.fromStorage(
{
keys: {
"ed25519:Osborne2": "alice+base64+ed25519+key",
},
},
"Osborne2",
);
};
bob.client.getDeviceEd25519Key = () => {
return "bob+base64+ed25519+key";
};
bob.client.downloadKeys = () => {
return Promise.resolve();
};
aliceSasEvent = null;
bobSasEvent = null;
bobPromise = new Promise((resolve, reject) => {
bob.client.on("event", async (event) => {
const content = event.getContent();
if (event.getType() === "m.room.message"
&& content.msgtype === "m.key.verification.request") {
expect(content.methods).toContain(SAS.NAME);
expect(content.to).toBe(bob.client.getUserId());
const verifier = bob.client.acceptVerificationDM(event, SAS.NAME);
verifier.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!aliceSasEvent) {
bobSasEvent = e;
} else {
try {
expect(e.sas).toEqual(aliceSasEvent.sas);
e.confirm();
aliceSasEvent.confirm();
} catch (error) {
e.mismatch();
aliceSasEvent.mismatch();
}
}
});
await verifier.verify();
resolve();
}
});
});
aliceVerifier = await alice.client.requestVerificationDM(
bob.client.getUserId(), "!room_id", [verificationMethods.SAS],
);
aliceVerifier.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
@@ -160,113 +481,16 @@ describe("SAS verification", function() {
});
it("should verify a key", async function() {
let macMethod;
const origSendToDevice = alice.sendToDevice;
bob.sendToDevice = function(type, map) {
if (type === "m.key.verification.accept") {
macMethod = map[alice.getUserId()][alice.deviceId]
.message_authentication_code;
}
return origSendToDevice.call(this, type, map);
};
await Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => verifier.verify()),
bobPromise,
]);
// make sure that it uses the preferred method
expect(macMethod).toBe("hkdf-hmac-sha256");
// make sure Alice and Bob verified each other
expect(alice.setDeviceVerified)
.toHaveBeenCalledWith(bob.getUserId(), bob.deviceId);
expect(bob.setDeviceVerified)
.toHaveBeenCalledWith(alice.getUserId(), alice.deviceId);
expect(alice.client.setDeviceVerified)
.toHaveBeenCalledWith(bob.client.getUserId(), bob.client.deviceId);
expect(bob.client.setDeviceVerified)
.toHaveBeenCalledWith(alice.client.getUserId(), alice.client.deviceId);
});
it("should be able to verify using the old MAC", async function() {
// pretend that Alice can only understand the old (incorrect) MAC,
// and make sure that she can still verify with Bob
let macMethod;
const origSendToDevice = alice.sendToDevice;
alice.sendToDevice = function(type, map) {
if (type === "m.key.verification.start") {
// Note: this modifies not only the message that Bob
// receives, but also the copy of the message that Alice
// has, since it is the same object. If this does not
// happen, the verification will fail due to a hash
// commitment mismatch.
map[bob.getUserId()][bob.deviceId]
.message_authentication_codes = ['hmac-sha256'];
}
return origSendToDevice.call(this, type, map);
};
bob.sendToDevice = function(type, map) {
if (type === "m.key.verification.accept") {
macMethod = map[alice.getUserId()][alice.deviceId]
.message_authentication_code;
}
return origSendToDevice.call(this, type, map);
};
await Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => verifier.verify()),
]);
expect(macMethod).toBe("hmac-sha256");
expect(alice.setDeviceVerified)
.toHaveBeenCalledWith(bob.getUserId(), bob.deviceId);
expect(bob.setDeviceVerified)
.toHaveBeenCalledWith(alice.getUserId(), alice.deviceId);
});
});
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"},
],
{
verificationMethods: [verificationMethods.SAS],
},
);
alice.setDeviceVerified = expect.createSpy();
alice.downloadKeys = () => {
return Promise.resolve();
};
bob.setDeviceVerified = expect.createSpy();
bob.downloadKeys = () => {
return Promise.resolve();
};
const bobPromise = new Promise((resolve, reject) => {
bob.on("crypto.verification.start", (verifier) => {
verifier.on("show_sas", (e) => {
e.mismatch();
});
resolve(verifier);
});
});
const aliceVerifier = alice.beginKeyVerification(
verificationMethods.SAS, bob.getUserId(), bob.deviceId,
);
const aliceSpy = expect.createSpy();
const bobSpy = expect.createSpy();
await Promise.all([
aliceVerifier.verify().catch(aliceSpy),
bobPromise.then((verifier) => verifier.verify()).catch(bobSpy),
]);
expect(aliceSpy).toHaveBeenCalled();
expect(bobSpy).toHaveBeenCalled();
expect(alice.setDeviceVerified)
.toNotHaveBeenCalled();
expect(bob.setDeviceVerified)
.toNotHaveBeenCalled();
});
});
+43 -11
View File
@@ -33,31 +33,63 @@ export async function makeTestClients(userInfos, options) {
type: type,
content: msg,
});
setTimeout(
() => clientMap[userId][deviceId]
.emit("toDeviceEvent", event),
0,
);
const client = clientMap[userId][deviceId];
if (event.isEncrypted()) {
event.attemptDecryption(client._crypto)
.then(() => client.emit("toDeviceEvent", event));
} else {
setTimeout(
() => client.emit("toDeviceEvent", event),
0,
);
}
}
}
}
}
};
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 event = new MatrixEvent({
sender: this.getUserId(), // eslint-disable-line babel/no-invalid-this
type: type,
content: content,
room_id: room,
event_id: eventId,
});
for (const tc of clients) {
setTimeout(
() => tc.client.emit("event", event),
0,
);
}
return {event_id: eventId};
};
for (const userInfo of userInfos) {
const client = (new TestClient(
let keys = {};
if (!options) options = {};
if (!options.cryptoCallbacks) options.cryptoCallbacks = {};
if (!options.cryptoCallbacks.saveCrossSigningKeys) {
options.cryptoCallbacks.saveCrossSigningKeys = k => { keys = k; };
options.cryptoCallbacks.getCrossSigningKey = typ => keys[typ];
}
const testClient = new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined,
options,
)).client;
);
if (!(userInfo.userId in clientMap)) {
clientMap[userInfo.userId] = {};
}
clientMap[userInfo.userId][userInfo.deviceId] = client;
client.sendToDevice = sendToDevice;
clients.push(client);
clientMap[userInfo.userId][userInfo.deviceId] = testClient.client;
testClient.client.sendToDevice = sendToDevice;
testClient.client.sendEvent = sendEvent;
clients.push(testClient);
}
await Promise.all(clients.map((client) => client.initCrypto()));
await Promise.all(clients.map((testClient) => testClient.client.initCrypto()));
return clients;
}
+9 -13
View File
@@ -9,8 +9,6 @@ function mockRoomStates(timeline) {
timeline._endState = utils.mock(sdk.RoomState, "endState");
}
import expect from 'expect';
describe("EventTimeline", function() {
const roomId = "!foo:bar";
const userA = "@alice:bar";
@@ -18,8 +16,6 @@ describe("EventTimeline", function() {
let timeline;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
// XXX: this is a horrid hack; should use sinon or something instead to mock
const timelineSet = { room: { roomId: roomId }};
timelineSet.room.getUnfilteredTimelineSet = function() {
@@ -78,7 +74,7 @@ describe("EventTimeline", function() {
expect(function() {
timeline.initialiseState(state);
}).toNotThrow();
}).not.toThrow();
timeline.addEvent(event, false);
expect(function() {
timeline.initialiseState(state);
@@ -121,7 +117,7 @@ describe("EventTimeline", function() {
const next = {b: "b"};
expect(function() {
timeline.setNeighbouringTimeline(prev, EventTimeline.BACKWARDS);
}).toNotThrow();
}).not.toThrow();
expect(timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS))
.toBe(prev);
expect(function() {
@@ -130,7 +126,7 @@ describe("EventTimeline", function() {
expect(function() {
timeline.setNeighbouringTimeline(next, EventTimeline.FORWARDS);
}).toNotThrow();
}).not.toThrow();
expect(timeline.getNeighbouringTimeline(EventTimeline.FORWARDS))
.toBe(next);
expect(function() {
@@ -187,14 +183,14 @@ describe("EventTimeline", function() {
name: "Old Alice",
};
timeline.getState(EventTimeline.FORWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
timeline.getState(EventTimeline.BACKWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -229,14 +225,14 @@ describe("EventTimeline", function() {
name: "Old Alice",
};
timeline.getState(EventTimeline.FORWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
timeline.getState(EventTimeline.BACKWARDS).getSentinelMember
.andCall(function(uid) {
.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -281,7 +277,7 @@ describe("EventTimeline", function() {
expect(events[1].forwardLooking).toBe(true);
expect(timeline.getState(EventTimeline.BACKWARDS).setStateEvents).
toNotHaveBeenCalled();
not.toHaveBeenCalled();
});
@@ -311,7 +307,7 @@ describe("EventTimeline", function() {
expect(events[1].forwardLooking).toBe(false);
expect(timeline.getState(EventTimeline.FORWARDS).setStateEvents).
toNotHaveBeenCalled();
not.toHaveBeenCalled();
});
});
+5 -9
View File
@@ -17,16 +17,10 @@ limitations under the License.
import sdk from '../..';
const MatrixEvent = sdk.MatrixEvent;
import testUtils from '../test-utils';
import expect from 'expect';
import Promise from 'bluebird';
import logger from '../../src/logger';
describe("MatrixEvent", () => {
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
describe(".attemptDecryption", () => {
let encryptedEvent;
@@ -44,21 +38,23 @@ describe("MatrixEvent", () => {
let callCount = 0;
let prom2;
let prom2Fulfilled = false;
const crypto = {
decryptEvent: function() {
++callCount;
console.log(`decrypt: ${callCount}`);
logger.log(`decrypt: ${callCount}`);
if (callCount == 1) {
// schedule a second decryption attempt while
// the first one is still running.
prom2 = encryptedEvent.attemptDecryption(crypto);
prom2.then(() => prom2Fulfilled = true);
const error = new Error("nope");
error.name = 'DecryptionError';
return Promise.reject(error);
} else {
expect(prom2.isFulfilled()).toBe(
expect(prom2Fulfilled).toBe(
false, 'second attemptDecryption resolved too soon');
return Promise.resolve({
-4
View File
@@ -2,9 +2,6 @@
import 'source-map-support/register';
const sdk = require("../..");
const Filter = sdk.Filter;
const utils = require("../test-utils");
import expect from 'expect';
describe("Filter", function() {
const filterId = "f1lt3ring15g00d4ursoul";
@@ -12,7 +9,6 @@ describe("Filter", function() {
let filter;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
filter = new Filter(userId);
});
+24 -29
View File
@@ -18,12 +18,11 @@ limitations under the License.
import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const utils = require("../test-utils");
const InteractiveAuth = sdk.InteractiveAuth;
const MatrixError = sdk.MatrixError;
import expect from 'expect';
import logger from '../../src/logger';
// Trivial client object to test interactive auth
// (we do not need TestClient here)
@@ -34,13 +33,9 @@ class FakeClient {
}
describe("InteractiveAuth", function() {
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
it("should start an auth stage and complete it", function(done) {
const doRequest = expect.createSpy();
const stateUpdated = expect.createSpy();
it("should start an auth stage and complete it", function() {
const doRequest = jest.fn();
const stateUpdated = jest.fn();
const ia = new InteractiveAuth({
matrixClient: new FakeClient(),
@@ -63,8 +58,8 @@ describe("InteractiveAuth", function() {
});
// first we expect a call here
stateUpdated.andCall(function(stage) {
console.log('aaaa');
stateUpdated.mockImplementation(function(stage) {
logger.log('aaaa');
expect(stage).toEqual("logintype");
ia.submitAuthDict({
type: "logintype",
@@ -74,8 +69,8 @@ describe("InteractiveAuth", function() {
// .. which should trigger a call here
const requestRes = {"a": "b"};
doRequest.andCall(function(authData) {
console.log('cccc');
doRequest.mockImplementation(function(authData) {
logger.log('cccc');
expect(authData).toEqual({
session: "sessionId",
type: "logintype",
@@ -84,16 +79,16 @@ describe("InteractiveAuth", function() {
return Promise.resolve(requestRes);
});
ia.attemptAuth().then(function(res) {
return ia.attemptAuth().then(function(res) {
expect(res).toBe(requestRes);
expect(doRequest.calls.length).toEqual(1);
expect(stateUpdated.calls.length).toEqual(1);
}).nodeify(done);
expect(doRequest).toBeCalledTimes(1);
expect(stateUpdated).toBeCalledTimes(1);
});
});
it("should make a request if no authdata is provided", function(done) {
const doRequest = expect.createSpy();
const stateUpdated = expect.createSpy();
it("should make a request if no authdata is provided", function() {
const doRequest = jest.fn();
const stateUpdated = jest.fn();
const ia = new InteractiveAuth({
matrixClient: new FakeClient(),
@@ -105,8 +100,8 @@ describe("InteractiveAuth", function() {
expect(ia.getStageParams("logintype")).toBe(undefined);
// first we expect a call to doRequest
doRequest.andCall(function(authData) {
console.log("request1", authData);
doRequest.mockImplementation(function(authData) {
logger.log("request1", authData);
expect(authData).toEqual({});
const err = new MatrixError({
session: "sessionId",
@@ -123,7 +118,7 @@ describe("InteractiveAuth", function() {
// .. which should be followed by a call to stateUpdated
const requestRes = {"a": "b"};
stateUpdated.andCall(function(stage) {
stateUpdated.mockImplementation(function(stage) {
expect(stage).toEqual("logintype");
expect(ia.getSessionId()).toEqual("sessionId");
expect(ia.getStageParams("logintype")).toEqual({
@@ -131,8 +126,8 @@ describe("InteractiveAuth", function() {
});
// submitAuthDict should trigger another call to doRequest
doRequest.andCall(function(authData) {
console.log("request2", authData);
doRequest.mockImplementation(function(authData) {
logger.log("request2", authData);
expect(authData).toEqual({
session: "sessionId",
type: "logintype",
@@ -147,10 +142,10 @@ describe("InteractiveAuth", function() {
});
});
ia.attemptAuth().then(function(res) {
return ia.attemptAuth().then(function(res) {
expect(res).toBe(requestRes);
expect(doRequest.calls.length).toEqual(2);
expect(stateUpdated.calls.length).toEqual(1);
}).nodeify(done);
expect(doRequest).toBeCalledTimes(2);
expect(stateUpdated).toBeCalledTimes(1);
});
});
});
-1
View File
@@ -1,4 +1,3 @@
import expect from 'expect';
import TestClient from '../TestClient';
describe('Login request', function() {
+27 -37
View File
@@ -3,10 +3,10 @@ import 'source-map-support/register';
import Promise from 'bluebird';
const sdk = require("../..");
const MatrixClient = sdk.MatrixClient;
const utils = require("../test-utils");
import expect from 'expect';
import lolex from 'lolex';
import logger from '../../src/logger';
jest.useFakeTimers();
describe("MatrixClient", function() {
const userId = "@alice:bar";
@@ -15,7 +15,6 @@ describe("MatrixClient", function() {
let client;
let store;
let scheduler;
let clock;
const KEEP_ALIVE_PATH = "/_matrix/client/versions";
@@ -69,7 +68,7 @@ describe("MatrixClient", function() {
"MatrixClient[UT] RECV " + method + " " + path + " " +
"EXPECT " + (next ? next.method : next) + " " + (next ? next.path : next)
);
console.log(logLine);
logger.log(logLine);
if (!next) { // no more things to return
if (pendingLookup) {
@@ -91,7 +90,7 @@ describe("MatrixClient", function() {
return pendingLookup.promise;
}
if (next.path === path && next.method === method) {
console.log(
logger.log(
"MatrixClient[UT] Matched. Returning " +
(next.error ? "BAD" : "GOOD") + " response",
);
@@ -124,24 +123,22 @@ describe("MatrixClient", function() {
}
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
clock = lolex.install();
scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
store = [
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser",
"getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter",
"getSyncAccumulator", "startup", "deleteAllData",
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null));
store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null));
store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null));
store.getClientOptions = expect.createSpy().andReturn(Promise.resolve(null));
store.storeClientOptions = expect.createSpy().andReturn(Promise.resolve(null));
store.isNewlyCreated = expect.createSpy().andReturn(Promise.resolve(true));
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null));
store.getClientOptions = jest.fn().mockReturnValue(Promise.resolve(null));
store.storeClientOptions = jest.fn().mockReturnValue(Promise.resolve(null));
store.isNewlyCreated = jest.fn().mockReturnValue(Promise.resolve(true));
client = new MatrixClient({
baseUrl: "https://my.home.server",
idBaseUrl: identityServerUrl,
@@ -153,13 +150,10 @@ describe("MatrixClient", function() {
});
// FIXME: We shouldn't be yanking _http like this.
client._http = [
"authedRequest", "authedRequestWithPrefix", "getContentUri",
"request", "requestWithPrefix", "uploadContent",
].reduce((r, k) => { r[k] = expect.createSpy(); return r; }, {});
client._http.authedRequest.andCall(httpReq);
client._http.authedRequestWithPrefix.andCall(httpReq);
client._http.requestWithPrefix.andCall(httpReq);
client._http.request.andCall(httpReq);
"authedRequest", "getContentUri", "request", "uploadContent",
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
client._http.authedRequest.mockImplementation(httpReq);
client._http.request.mockImplementation(httpReq);
// set reasonable working defaults
acceptKeepalives = true;
@@ -171,16 +165,12 @@ describe("MatrixClient", function() {
});
afterEach(function() {
clock.uninstall();
// need to re-stub the requests with NOPs because there are no guarantees
// clients from previous tests will be GC'd before the next test. This
// 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.andCall(function() {
return Promise.defer().promise;
});
client._http.authedRequestWithPrefix.andCall(function() {
client._http.authedRequest.mockImplementation(function() {
return Promise.defer().promise;
});
});
@@ -190,10 +180,10 @@ describe("MatrixClient", function() {
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push(SYNC_RESPONSE);
const filterId = "ehfewf";
store.getFilterIdByName.andReturn(filterId);
store.getFilterIdByName.mockReturnValue(filterId);
const filter = new sdk.Filter(0, filterId);
filter.setDefinition({"room": {"timeline": {"limit": 8}}});
store.getFilter.andReturn(filter);
store.getFilter.mockReturnValue(filter);
const syncPromise = new Promise((resolve, reject) => {
client.on("sync", function syncListener(state) {
if (state === "SYNCING") {
@@ -254,7 +244,7 @@ describe("MatrixClient", function() {
},
});
httpLookups.push(FILTER_RESPONSE);
store.getFilterIdByName.andReturn(invalidFilterId);
store.getFilterIdByName.mockReturnValue(invalidFilterId);
const filterName = getFilterName(client.credentials.userId);
client.store.setFilterIdByName(filterName, invalidFilterId);
@@ -286,7 +276,7 @@ describe("MatrixClient", function() {
if (state === "ERROR" && httpLookups.length > 0) {
expect(httpLookups.length).toEqual(2);
expect(client.retryImmediately()).toBe(true);
clock.tick(1);
jest.advanceTimersByTime(1);
} else if (state === "PREPARED" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
done();
@@ -312,9 +302,9 @@ describe("MatrixClient", function() {
expect(client.retryImmediately()).toBe(
true, "retryImmediately returned false",
);
clock.tick(1);
jest.advanceTimersByTime(1);
} else if (state === "RECONNECTING" && httpLookups.length > 0) {
clock.tick(10000);
jest.advanceTimersByTime(10000);
} else if (state === "SYNCING" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
done();
@@ -336,7 +326,7 @@ describe("MatrixClient", function() {
if (state === "ERROR" && httpLookups.length > 0) {
expect(httpLookups.length).toEqual(3);
expect(client.retryImmediately()).toBe(true);
clock.tick(1);
jest.advanceTimersByTime(1);
} else if (state === "PREPARED" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
done();
@@ -353,7 +343,7 @@ describe("MatrixClient", function() {
function syncChecker(expectedStates, done) {
return function syncListener(state, old) {
const expected = expectedStates.shift();
console.log(
logger.log(
"'sync' curr=%s old=%s EXPECT=%s", state, old, expected,
);
if (!expected) {
@@ -367,7 +357,7 @@ describe("MatrixClient", function() {
done();
}
// standard retry time is 5 to 10 seconds
clock.tick(10000);
jest.advanceTimersByTime(10000);
};
}
-2
View File
@@ -3,8 +3,6 @@ import 'source-map-support/register';
const PushProcessor = require("../../lib/pushprocessor");
const utils = require("../test-utils");
import expect from 'expect';
describe('NotificationService', function() {
const testUserId = "@ali:matrix.org";
const testDisplayName = "Alice M";
+43 -52
View File
@@ -1,53 +1,46 @@
"use strict";
import 'source-map-support/register';
const callbacks = require("../../lib/realtime-callbacks");
const testUtils = require("../test-utils.js");
const callbacks = require("../../src/realtime-callbacks");
import expect from 'expect';
import lolex from 'lolex';
let wallTime = 1234567890;
jest.useFakeTimers();
describe("realtime-callbacks", function() {
let clock;
function tick(millis) {
clock.tick(millis);
wallTime += millis;
jest.advanceTimersByTime(millis);
}
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
clock = lolex.install();
const fakeDate = clock.Date;
callbacks.setNow(fakeDate.now.bind(fakeDate));
callbacks.setNow(() => wallTime);
});
afterEach(function() {
callbacks.setNow();
clock.uninstall();
});
describe("setTimeout", function() {
it("should call the callback after the timeout", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback, 100);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
tick(100);
expect(callback).toHaveBeenCalled();
});
it("should default to a zero timeout", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
tick(0);
expect(callback).toHaveBeenCalled();
});
it("should pass any parameters to the callback", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback, 0, "a", "b", "c");
tick(0);
expect(callback).toHaveBeenCalledWith("a", "b", "c");
@@ -66,10 +59,10 @@ describe("realtime-callbacks", function() {
});
it("should handle timeouts of several seconds", function() {
const callback = expect.createSpy();
const callback = jest.fn();
callbacks.setTimeout(callback, 2000);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
for (let i = 0; i < 4; i++) {
tick(500);
}
@@ -77,24 +70,24 @@ describe("realtime-callbacks", function() {
});
it("should call multiple callbacks in the right order", function() {
const callback1 = expect.createSpy();
const callback2 = expect.createSpy();
const callback3 = expect.createSpy();
const callback1 = jest.fn();
const callback2 = jest.fn();
const callback3 = jest.fn();
callbacks.setTimeout(callback2, 200);
callbacks.setTimeout(callback1, 100);
callbacks.setTimeout(callback3, 300);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback3).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
expect(callback3).not.toHaveBeenCalled();
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback3).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
expect(callback3).not.toHaveBeenCalled();
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
expect(callback3).toNotHaveBeenCalled();
expect(callback3).not.toHaveBeenCalled();
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
@@ -102,35 +95,34 @@ describe("realtime-callbacks", function() {
});
it("should treat -ve timeouts the same as a zero timeout", function() {
const callback1 = expect.createSpy();
const callback2 = expect.createSpy();
const callback1 = jest.fn();
const callback2 = jest.fn();
// check that cb1 is called before cb2
callback1.andCall(function() {
expect(callback2).toNotHaveBeenCalled();
callback1.mockImplementation(function() {
expect(callback2).not.toHaveBeenCalled();
});
callbacks.setTimeout(callback1);
callbacks.setTimeout(callback2, -100);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(0);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
});
it("should not get confused by chained calls", function() {
const callback2 = expect.createSpy();
const callback1 = expect.createSpy();
callback1.andCall(function() {
const callback2 = jest.fn();
const callback1 = jest.fn(function() {
callbacks.setTimeout(callback2, 0);
expect(callback2).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
});
callbacks.setTimeout(callback1);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(0);
expect(callback1).toHaveBeenCalled();
// the fake timer won't actually run callbacks registered during
@@ -140,16 +132,15 @@ describe("realtime-callbacks", function() {
});
it("should be immune to exceptions", function() {
const callback1 = expect.createSpy();
callback1.andCall(function() {
const callback1 = jest.fn(function() {
throw new Error("prepare to die");
});
const callback2 = expect.createSpy();
const callback2 = jest.fn();
callbacks.setTimeout(callback1, 0);
callbacks.setTimeout(callback2, 0);
expect(callback1).toNotHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback1).not.toHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(0);
expect(callback1).toHaveBeenCalled();
expect(callback2).toHaveBeenCalled();
@@ -158,16 +149,16 @@ describe("realtime-callbacks", function() {
describe("cancelTimeout", function() {
it("should cancel a pending timeout", function() {
const callback = expect.createSpy();
const callback = jest.fn();
const k = callbacks.setTimeout(callback);
callbacks.clearTimeout(k);
tick(0);
expect(callback).toNotHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
});
it("should not affect sooner timeouts", function() {
const callback1 = expect.createSpy();
const callback2 = expect.createSpy();
const callback1 = jest.fn();
const callback2 = jest.fn();
callbacks.setTimeout(callback1, 100);
const k = callbacks.setTimeout(callback2, 200);
@@ -175,10 +166,10 @@ describe("realtime-callbacks", function() {
tick(100);
expect(callback1).toHaveBeenCalled();
expect(callback2).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
tick(150);
expect(callback2).toNotHaveBeenCalled();
expect(callback2).not.toHaveBeenCalled();
});
});
});
+5 -8
View File
@@ -4,8 +4,6 @@ const sdk = require("../..");
const RoomMember = sdk.RoomMember;
const utils = require("../test-utils");
import expect from 'expect';
describe("RoomMember", function() {
const roomId = "!foo:bar";
const userA = "@alice:bar";
@@ -14,7 +12,6 @@ describe("RoomMember", function() {
let member;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
member = new RoomMember(roomId, userA);
});
@@ -36,7 +33,7 @@ describe("RoomMember", function() {
const url = member.getAvatarUrl(hsUrl);
// we don't care about how the mxc->http conversion is done, other
// than it contains the mxc body.
expect(url.indexOf("flibble/wibble")).toNotEqual(-1);
expect(url.indexOf("flibble/wibble")).not.toEqual(-1);
});
it("should return an identicon HTTP URL if allowDefault was set and there " +
@@ -255,9 +252,9 @@ describe("RoomMember", function() {
member.setMembershipEvent(joinEvent);
expect(member.name).toEqual("Alice"); // prefer displayname
member.setMembershipEvent(joinEvent, roomState);
expect(member.name).toNotEqual("Alice"); // it should disambig.
expect(member.name).not.toEqual("Alice"); // it should disambig.
// user_id should be there somewhere
expect(member.name.indexOf(userA)).toNotEqual(-1);
expect(member.name.indexOf(userA)).not.toEqual(-1);
});
it("should emit 'RoomMember.membership' if the membership changes", function() {
@@ -328,9 +325,9 @@ describe("RoomMember", function() {
};
expect(member.name).toEqual(userA); // default = user_id
member.setMembershipEvent(joinEvent, roomState);
expect(member.name).toNotEqual("Alíce"); // it should disambig.
expect(member.name).not.toEqual("Alíce"); // it should disambig.
// user_id should be there somewhere
expect(member.name.indexOf(userA)).toNotEqual(-1);
expect(member.name.indexOf(userA)).not.toEqual(-1);
});
});
});
+6 -9
View File
@@ -5,8 +5,6 @@ const RoomState = sdk.RoomState;
const RoomMember = sdk.RoomMember;
const utils = require("../test-utils");
import expect from 'expect';
describe("RoomState", function() {
const roomId = "!foo:bar";
const userA = "@alice:bar";
@@ -17,7 +15,6 @@ describe("RoomState", function() {
let state;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
state = new RoomState(roomId);
state.setStateEvents([
utils.mkMembership({ // userA joined
@@ -49,8 +46,8 @@ describe("RoomState", function() {
const members = state.getMembers();
expect(members.length).toEqual(2);
// ordering unimportant
expect([userA, userB].indexOf(members[0].userId)).toNotEqual(-1);
expect([userA, userB].indexOf(members[1].userId)).toNotEqual(-1);
expect([userA, userB].indexOf(members[0].userId)).not.toEqual(-1);
expect([userA, userB].indexOf(members[1].userId)).not.toEqual(-1);
});
});
@@ -120,8 +117,8 @@ describe("RoomState", function() {
const events = state.getStateEvents("m.room.member");
expect(events.length).toEqual(2);
// ordering unimportant
expect([userA, userB].indexOf(events[0].getStateKey())).toNotEqual(-1);
expect([userA, userB].indexOf(events[1].getStateKey())).toNotEqual(-1);
expect([userA, userB].indexOf(events[0].getStateKey())).not.toEqual(-1);
expect([userA, userB].indexOf(events[1].getStateKey())).not.toEqual(-1);
});
it("should return a single MatrixEvent if a state_key was specified",
@@ -258,7 +255,7 @@ describe("RoomState", function() {
});
state.setStateEvents([memberEvent]);
expect(state.members[userA].setMembershipEvent).toNotHaveBeenCalled();
expect(state.members[userA].setMembershipEvent).not.toHaveBeenCalled();
expect(state.members[userB].setMembershipEvent).toHaveBeenCalledWith(
memberEvent, state,
);
@@ -306,7 +303,7 @@ describe("RoomState", function() {
state.markOutOfBandMembersStarted();
state.setOutOfBandMembers([oobMemberEvent]);
const memberA = state.getMember(userA);
expect(memberA.events.member.getId()).toNotEqual(oobMemberEvent.getId());
expect(memberA.events.member.getId()).not.toEqual(oobMemberEvent.getId());
expect(memberA.isOutOfBand()).toEqual(false);
});
+33 -34
View File
@@ -8,8 +8,6 @@ const EventStatus = sdk.EventStatus;
const EventTimeline = sdk.EventTimeline;
const utils = require("../test-utils");
import expect from 'expect';
describe("Room", function() {
const roomId = "!foo:bar";
const userA = "@alice:bar";
@@ -19,7 +17,6 @@ describe("Room", function() {
let room;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
room = new Room(roomId);
// mock RoomStates
room.oldState = room.getLiveTimeline()._startState =
@@ -32,7 +29,7 @@ describe("Room", function() {
const hsUrl = "https://my.home.server";
it("should return the URL from m.room.avatar preferentially", function() {
room.currentState.getStateEvents.andCall(function(type, key) {
room.currentState.getStateEvents.mockImplementation(function(type, key) {
if (type === "m.room.avatar" && key === "") {
return utils.mkEvent({
event: true,
@@ -49,7 +46,7 @@ describe("Room", function() {
const url = room.getAvatarUrl(hsUrl);
// we don't care about how the mxc->http conversion is done, other
// than it contains the mxc body.
expect(url.indexOf("flibble/wibble")).toNotEqual(-1);
expect(url.indexOf("flibble/wibble")).not.toEqual(-1);
});
it("should return an identicon HTTP URL if allowDefault was set and there " +
@@ -67,13 +64,13 @@ describe("Room", function() {
describe("getMember", function() {
beforeEach(function() {
room.currentState.getMember.andCall(function(userId) {
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": {
userId: userA,
roomId: roomId,
},
}[userId];
}[userId] || null;
});
});
@@ -82,7 +79,7 @@ describe("Room", function() {
});
it("should return the member from current state", function() {
expect(room.getMember(userA)).toNotEqual(null);
expect(room.getMember(userA)).not.toEqual(null);
});
});
@@ -104,7 +101,7 @@ describe("Room", function() {
user_ids: [userA],
},
});
room.addLiveEvents([typing]);
room.addEphemeralEvents([typing]);
expect(room.currentState.setTypingEvent).toHaveBeenCalledWith(typing);
});
@@ -174,7 +171,7 @@ describe("Room", function() {
);
expect(events[0].forwardLooking).toBe(true);
expect(events[1].forwardLooking).toBe(true);
expect(room.oldState.setStateEvents).toNotHaveBeenCalled();
expect(room.oldState.setStateEvents).not.toHaveBeenCalled();
});
it("should synthesize read receipts for the senders of events", function() {
@@ -183,7 +180,7 @@ describe("Room", function() {
membership: "join",
name: "Alice",
};
room.currentState.getSentinelMember.andCall(function(uid) {
room.currentState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
@@ -292,13 +289,13 @@ describe("Room", function() {
membership: "join",
name: "Old Alice",
};
room.currentState.getSentinelMember.andCall(function(uid) {
room.currentState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.andCall(function(uid) {
room.oldState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -331,13 +328,13 @@ describe("Room", function() {
membership: "join",
name: "Old Alice",
};
room.currentState.getSentinelMember.andCall(function(uid) {
room.currentState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.andCall(function(uid) {
room.oldState.getSentinelMember.mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@@ -379,7 +376,7 @@ describe("Room", function() {
);
expect(events[0].forwardLooking).toBe(false);
expect(events[1].forwardLooking).toBe(false);
expect(room.currentState.setStateEvents).toNotHaveBeenCalled();
expect(room.currentState.setStateEvents).not.toHaveBeenCalled();
});
});
@@ -545,7 +542,7 @@ describe("Room", function() {
describe("getJoinedMembers", function() {
it("should return members whose membership is 'join'", function() {
room.currentState.getMembers.andCall(function() {
room.currentState.getMembers.mockImplementation(function() {
return [
{ userId: "@alice:bar", membership: "join" },
{ userId: "@bob:bar", membership: "invite" },
@@ -558,7 +555,7 @@ describe("Room", function() {
});
it("should return an empty list if no membership is 'join'", function() {
room.currentState.getMembers.andCall(function() {
room.currentState.getMembers.mockImplementation(function() {
return [
{ userId: "@bob:bar", membership: "invite" },
];
@@ -571,7 +568,7 @@ describe("Room", function() {
describe("hasMembershipState", function() {
it("should return true for a matching userId and membership",
function() {
room.currentState.getMember.andCall(function(userId) {
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
"@bob:bar": { userId: "@bob:bar", membership: "invite" },
@@ -582,7 +579,7 @@ describe("Room", function() {
it("should return false if match membership but no match userId",
function() {
room.currentState.getMember.andCall(function(userId) {
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
@@ -592,7 +589,7 @@ describe("Room", function() {
it("should return false if match userId but no match membership",
function() {
room.currentState.getMember.andCall(function(userId) {
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
@@ -602,7 +599,7 @@ describe("Room", function() {
it("should return false if no match membership or userId",
function() {
room.currentState.getMember.andCall(function(userId) {
room.currentState.getMember.mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
@@ -626,7 +623,9 @@ describe("Room", function() {
};
const setAliases = function(aliases, stateKey) {
if (!stateKey) {
stateKey = "flibble";
stateKey = aliases.length
? aliases[0].split(':').splice(1).join(':') // domain+port
: 'fibble';
}
room.addLiveEvents([utils.mkEvent({
type: "m.room.aliases", room: roomId, skey: stateKey, content: {
@@ -814,8 +813,8 @@ describe("Room", function() {
addMember(userC);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userC)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
expect(name.indexOf(userC)).not.toEqual(-1, name);
});
it("should return the names of members in a public (public join_rules)" +
@@ -827,8 +826,8 @@ describe("Room", function() {
addMember(userC);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userC)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
expect(name.indexOf(userC)).not.toEqual(-1, name);
});
it("should show the other user's name for public (public join_rules)" +
@@ -839,7 +838,7 @@ describe("Room", function() {
addMember(userB);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
});
it("should show the other user's name for private " +
@@ -850,7 +849,7 @@ describe("Room", function() {
addMember(userB);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
});
it("should show the other user's name for private" +
@@ -860,14 +859,14 @@ describe("Room", function() {
addMember(userB);
room.recalculate();
const name = room.name;
expect(name.indexOf(userB)).toNotEqual(-1, name);
expect(name.indexOf(userB)).not.toEqual(-1, name);
});
it("should show the room alias if one exists for private " +
"(invite join_rules) rooms if a room name doesn't exist.", function() {
const alias = "#room_alias:here";
setJoinRule("invite");
setAliases([alias, "#another:one"]);
setAliases([alias, "#another:here"]);
room.recalculate();
const name = room.name;
expect(name).toEqual(alias);
@@ -877,7 +876,7 @@ describe("Room", function() {
"(public join_rules) rooms if a room name doesn't exist.", function() {
const alias = "#room_alias:here";
setJoinRule("public");
setAliases([alias, "#another:one"]);
setAliases([alias, "#another:here"]);
room.recalculate();
const name = room.name;
expect(name).toEqual(alias);
@@ -1004,7 +1003,7 @@ describe("Room", function() {
it("should emit an event when a receipt is added",
function() {
const listener = expect.createSpy();
const listener = jest.fn();
room.on("Room.receipt", listener);
const ts = 13787898424;
@@ -1175,7 +1174,7 @@ describe("Room", function() {
it("should emit Room.tags event when new tags are " +
"received on the event stream",
function() {
const listener = expect.createSpy();
const listener = jest.fn();
room.on("Room.tags", listener);
const tags = { "m.foo": { "order": 0.5 } };
+41 -37
View File
@@ -8,11 +8,9 @@ const MatrixScheduler = sdk.MatrixScheduler;
const MatrixError = sdk.MatrixError;
const utils = require("../test-utils");
import expect from 'expect';
import lolex from 'lolex';
jest.useFakeTimers();
describe("MatrixScheduler", function() {
let clock;
let scheduler;
let retryFn;
let queueFn;
@@ -26,8 +24,6 @@ describe("MatrixScheduler", function() {
});
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
clock = lolex.install();
scheduler = new MatrixScheduler(function(ev, attempts, err) {
if (retryFn) {
return retryFn(ev, attempts, err);
@@ -44,11 +40,7 @@ describe("MatrixScheduler", function() {
defer = Promise.defer();
});
afterEach(function() {
clock.uninstall();
});
it("should process events in a queue in a FIFO manner", function(done) {
it("should process events in a queue in a FIFO manner", async function() {
retryFn = function() {
return 0;
};
@@ -57,28 +49,30 @@ describe("MatrixScheduler", function() {
};
const deferA = Promise.defer();
const deferB = Promise.defer();
let resolvedA = false;
let yieldedA = false;
scheduler.setProcessFunction(function(event) {
if (resolvedA) {
if (yieldedA) {
expect(event).toEqual(eventB);
return deferB.promise;
} else {
yieldedA = true;
expect(event).toEqual(eventA);
return deferA.promise;
}
});
scheduler.queueEvent(eventA);
scheduler.queueEvent(eventB).done(function() {
expect(resolvedA).toBe(true);
done();
});
deferA.resolve({});
resolvedA = true;
deferB.resolve({});
const abPromise = Promise.all([
scheduler.queueEvent(eventA),
scheduler.queueEvent(eventB),
]);
deferB.resolve({b: true});
deferA.resolve({a: true});
const [a, b] = await abPromise;
expect(a.a).toEqual(true);
expect(b.b).toEqual(true);
});
it("should invoke the retryFn on failure and wait the amount of time specified",
function(done) {
async function() {
const waitTimeMs = 1500;
const retryDefer = Promise.defer();
retryFn = function() {
@@ -97,24 +91,26 @@ describe("MatrixScheduler", function() {
return defer.promise;
} else if (procCount === 2) {
// don't care about this defer
return Promise.defer().promise;
return new Promise();
}
expect(procCount).toBeLessThan(3);
});
scheduler.queueEvent(eventA);
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
// wait just long enough before it does
await Promise.resolve();
expect(procCount).toEqual(1);
defer.reject({});
retryDefer.promise.done(function() {
expect(procCount).toEqual(1);
clock.tick(waitTimeMs);
expect(procCount).toEqual(2);
done();
});
await retryDefer.promise;
expect(procCount).toEqual(1);
jest.advanceTimersByTime(waitTimeMs);
await Promise.resolve();
expect(procCount).toEqual(2);
});
it("should give up if the retryFn on failure returns -1 and try the next event",
function(done) {
async function() {
// Queue A & B.
// Reject A and return -1 on retry.
// Expect B to be tried next and the promise for A to be rejected.
@@ -122,8 +118,8 @@ describe("MatrixScheduler", function() {
return -1;
};
queueFn = function() {
return "yep";
};
return "yep";
};
const deferA = Promise.defer();
const deferB = Promise.defer();
@@ -142,13 +138,17 @@ describe("MatrixScheduler", function() {
const globalA = scheduler.queueEvent(eventA);
scheduler.queueEvent(eventB);
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
// wait just long enough before it does
await Promise.resolve();
expect(procCount).toEqual(1);
deferA.reject({});
globalA.catch(function() {
try {
await globalA;
} catch(err) {
await Promise.resolve();
expect(procCount).toEqual(2);
done();
});
}
});
it("should treat each queue separately", function(done) {
@@ -195,7 +195,7 @@ describe("MatrixScheduler", function() {
setTimeout(function() {
deferA.resolve({});
}, 1000);
clock.tick(1000);
jest.advanceTimersByTime(1000);
});
describe("queueEvent", function() {
@@ -300,7 +300,11 @@ describe("MatrixScheduler", function() {
expect(ev).toEqual(eventA);
return defer.promise;
});
expect(procCount).toEqual(1);
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
// wait just long enough before it does
Promise.resolve().then(() => {
expect(procCount).toEqual(1);
});
});
it("should not call the processFn if there are no queued events", function() {
-3
View File
@@ -16,9 +16,7 @@ limitations under the License.
"use strict";
import 'source-map-support/register';
import utils from "../test-utils";
import sdk from "../..";
import expect from 'expect';
const SyncAccumulator = sdk.SyncAccumulator;
@@ -26,7 +24,6 @@ describe("SyncAccumulator", function() {
let sa;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
sa = new SyncAccumulator({
maxTimelineEntries: 10,
});
+30 -42
View File
@@ -7,7 +7,6 @@ const TimelineWindow = sdk.TimelineWindow;
const TimelineIndex = require("../../lib/timeline-window").TimelineIndex;
const utils = require("../test-utils");
import expect from 'expect';
const ROOM_ID = "roomId";
const USER_ID = "userId";
@@ -67,10 +66,6 @@ function createLinkedTimelines() {
describe("TimelineIndex", function() {
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
describe("minIndex", function() {
it("should return the min index relative to BaseIndex", function() {
const timelineIndex = new TimelineIndex(createTimeline(), 0);
@@ -153,7 +148,7 @@ describe("TimelineWindow", function() {
let timelineSet;
let client;
function createWindow(timeline, opts) {
timelineSet = {};
timelineSet = {getTimelineForEvent: () => null};
client = {};
client.getEventTimeline = function(timelineSet0, eventId0) {
expect(timelineSet0).toBe(timelineSet);
@@ -163,12 +158,8 @@ describe("TimelineWindow", function() {
return new TimelineWindow(client, timelineSet, opts);
}
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
describe("load", function() {
it("should initialise from the live timeline", function(done) {
it("should initialise from the live timeline", function() {
const liveTimeline = createTimeline();
const room = {};
room.getLiveTimeline = function() {
@@ -176,17 +167,17 @@ describe("TimelineWindow", function() {
};
const timelineWindow = new TimelineWindow(undefined, room);
timelineWindow.load(undefined, 2).then(function() {
return timelineWindow.load(undefined, 2).then(function() {
const expectedEvents = liveTimeline.getEvents().slice(1);
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("should initialise from a specific event", function(done) {
it("should initialise from a specific event", function() {
const timeline = createTimeline();
const eventId = timeline.getEvents()[1].getId();
const timelineSet = {};
const timelineSet = {getTimelineForEvent: () => null};
const client = {};
client.getEventTimeline = function(timelineSet0, eventId0) {
expect(timelineSet0).toBe(timelineSet);
@@ -195,21 +186,20 @@ describe("TimelineWindow", function() {
};
const timelineWindow = new TimelineWindow(client, timelineSet);
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("canPaginate should return false until load has returned",
function(done) {
it("canPaginate should return false until load has returned", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok1", EventTimeline.BACKWARDS);
timeline.setPaginationToken("toktok2", EventTimeline.FORWARDS);
const eventId = timeline.getEvents()[1].getId();
const timelineSet = {};
const timelineSet = {getTimelineForEvent: () => null};
const client = {};
const timelineWindow = new TimelineWindow(client, timelineSet);
@@ -222,25 +212,24 @@ describe("TimelineWindow", function() {
return Promise.resolve(timeline);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
expect(timelineWindow.canPaginate(EventTimeline.BACKWARDS))
.toBe(true);
expect(timelineWindow.canPaginate(EventTimeline.FORWARDS))
.toBe(true);
}).nodeify(done);
});
});
});
describe("pagination", function() {
it("should be able to advance across the initial timeline",
function(done) {
it("should be able to advance across the initial timeline", function() {
const timeline = createTimeline();
const eventId = timeline.getEvents()[1].getId();
const timelineWindow = createWindow(timeline);
timelineWindow.load(eventId, 1).then(function() {
return timelineWindow.load(eventId, 1).then(function() {
const expectedEvents = [timeline.getEvents()[1]];
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -277,15 +266,15 @@ describe("TimelineWindow", function() {
return timelineWindow.paginate(EventTimeline.BACKWARDS, 2);
}).then(function(success) {
expect(success).toBe(false);
}).nodeify(done);
});
});
it("should advance into next timeline", function(done) {
it("should advance into next timeline", function() {
const tls = createLinkedTimelines();
const eventId = tls[0].getEvents()[1].getId();
const timelineWindow = createWindow(tls[0], {windowLimit: 5});
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = tls[0].getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -322,15 +311,15 @@ describe("TimelineWindow", function() {
return timelineWindow.paginate(EventTimeline.FORWARDS, 2);
}).then(function(success) {
expect(success).toBe(false);
}).nodeify(done);
});
});
it("should retreat into previous timeline", function(done) {
it("should retreat into previous timeline", function() {
const tls = createLinkedTimelines();
const eventId = tls[1].getEvents()[1].getId();
const timelineWindow = createWindow(tls[1], {windowLimit: 5});
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = tls[1].getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -367,10 +356,10 @@ describe("TimelineWindow", function() {
return timelineWindow.paginate(EventTimeline.BACKWARDS, 2);
}).then(function(success) {
expect(success).toBe(false);
}).nodeify(done);
});
});
it("should make forward pagination requests", function(done) {
it("should make forward pagination requests", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.FORWARDS);
@@ -386,7 +375,7 @@ describe("TimelineWindow", function() {
return Promise.resolve(true);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -399,11 +388,11 @@ describe("TimelineWindow", function() {
expect(success).toBe(true);
const expectedEvents = timeline.getEvents().slice(0, 5);
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("should make backward pagination requests", function(done) {
it("should make backward pagination requests", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.BACKWARDS);
@@ -419,7 +408,7 @@ describe("TimelineWindow", function() {
return Promise.resolve(true);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -432,11 +421,10 @@ describe("TimelineWindow", function() {
expect(success).toBe(true);
const expectedEvents = timeline.getEvents().slice(1, 6);
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
}).nodeify(done);
});
});
it("should limit the number of unsuccessful pagination requests",
function(done) {
it("should limit the number of unsuccessful pagination requests", function() {
const timeline = createTimeline();
timeline.setPaginationToken("toktok", EventTimeline.FORWARDS);
@@ -452,7 +440,7 @@ describe("TimelineWindow", function() {
return Promise.resolve(true);
};
timelineWindow.load(eventId, 3).then(function() {
return timelineWindow.load(eventId, 3).then(function() {
const expectedEvents = timeline.getEvents();
expect(timelineWindow.getEvents()).toEqual(expectedEvents);
@@ -471,7 +459,7 @@ describe("TimelineWindow", function() {
.toBe(false);
expect(timelineWindow.canPaginate(EventTimeline.FORWARDS))
.toBe(true);
}).nodeify(done);
});
});
});
});
-3
View File
@@ -4,14 +4,11 @@ const sdk = require("../..");
const User = sdk.User;
const utils = require("../test-utils");
import expect from 'expect';
describe("User", function() {
const userId = "@alice:bar";
let user;
beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
user = new User(userId);
});
+2 -9
View File
@@ -1,15 +1,8 @@
"use strict";
import 'source-map-support/register';
const utils = require("../../lib/utils");
const testUtils = require("../test-utils");
import expect from 'expect';
describe("utils", function() {
beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
});
describe("encodeParams", function() {
it("should url encode and concat with &s", function() {
const params = {
@@ -135,7 +128,7 @@ describe("utils", function() {
utils.checkObjectHasKeys({
foo: "bar",
}, ["foo"]);
}).toNotThrow();
}).not.toThrow();
});
});
@@ -152,7 +145,7 @@ describe("utils", function() {
utils.checkObjectHasNoAdditionalKeys({
foo: "bar",
}, ["foo"]);
}).toNotThrow();
}).not.toThrow();
});
});
+7 -1
View File
@@ -34,12 +34,18 @@ export default class Reemitter {
}
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 = this.boundHandlers[eventName];
const boundHandler = forSource.bind(this, this.boundHandlers[eventName]);
source.on(eventName, boundHandler);
}
}
+35 -14
View File
@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,7 +18,7 @@ limitations under the License.
/** @module auto-discovery */
import Promise from 'bluebird';
const logger = require("./logger");
import logger from './logger';
import { URL as NodeURL } from "url";
// Dev note: Auto discovery is part of the spec.
@@ -256,6 +257,11 @@ export class AutoDiscovery {
if (!hsVersions || !hsVersions.raw["versions"]) {
logger.error("Invalid /versions response");
clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HOMESERVER;
// Supply the base_url to the caller because they may be ignoring liveliness
// errors, like this one.
clientConfig["m.homeserver"].base_url = hsUrl;
return Promise.resolve(clientConfig);
}
@@ -270,21 +276,11 @@ export class AutoDiscovery {
let isUrl = "";
if (wellknown["m.identity_server"]) {
// We prepare a failing identity server response to save lines later
// in this branch. Note that we also fail the homeserver check in the
// object because according to the spec we're supposed to FAIL_ERROR
// if *anything* goes wrong with the IS validation, including invalid
// format. This means we're supposed to stop discovery completely.
// in this branch.
const failingClientConfig = {
"m.homeserver": {
state: AutoDiscovery.FAIL_ERROR,
error: AutoDiscovery.ERROR_INVALID_IS,
// We'll provide the base_url that was previously valid for
// debugging purposes.
base_url: clientConfig["m.homeserver"].base_url,
},
"m.homeserver": clientConfig["m.homeserver"],
"m.identity_server": {
state: AutoDiscovery.FAIL_ERROR,
state: AutoDiscovery.FAIL_PROMPT,
error: AutoDiscovery.ERROR_INVALID_IS,
base_url: null,
},
@@ -311,6 +307,11 @@ export class AutoDiscovery {
logger.error("Invalid /api/v1 response");
failingClientConfig["m.identity_server"].error =
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
// Supply the base_url to the caller because they may be ignoring
// liveliness errors, like this one.
failingClientConfig["m.identity_server"].base_url = isUrl;
return Promise.resolve(failingClientConfig);
}
}
@@ -419,6 +420,26 @@ export class AutoDiscovery {
return AutoDiscovery.fromDiscoveryConfig(wellknown.raw);
}
/**
* Gets the raw discovery client configuration for the given domain name.
* Should only be used if there's no validation to be done on the resulting
* object, otherwise use findClientConfig().
* @param {string} domain The domain to get the client config for.
* @returns {Promise<object>} Resolves to the domain's client config. Can
* be an empty object.
*/
static async getRawClientConfig(domain) {
if (!domain || typeof(domain) !== "string" || domain.length === 0) {
throw new Error("'domain' must be a string of non-zero length");
}
const response = await this._fetchWellKnownObject(
`https://${domain}/.well-known/matrix/client`,
);
if (!response) return {};
return response.raw || {};
}
/**
* Sanitizes a given URL to ensure it is either an HTTP or HTTP URL and
* is suitable for the requirements laid out by .well-known auto discovery.
+660 -105
View File
File diff suppressed because it is too large Load Diff
+1016 -233
View File
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -47,14 +47,14 @@ module.exports = {
}
}
let serverAndMediaId = mxc.slice(6); // strips mxc://
let prefix = "/_matrix/media/v1/download/";
let prefix = "/_matrix/media/r0/download/";
const params = {};
if (width) {
params.width = width;
params.width = Math.round(width);
}
if (height) {
params.height = height;
params.height = Math.round(height);
}
if (resizeMethod) {
params.method = resizeMethod;
@@ -62,7 +62,7 @@ module.exports = {
if (utils.keys(params).length > 0) {
// these are thumbnailing params so they probably want the
// thumbnailing API...
prefix = "/_matrix/media/v1/thumbnail/";
prefix = "/_matrix/media/r0/thumbnail/";
}
const fragmentOffset = serverAndMediaId.indexOf("#");
@@ -83,6 +83,7 @@ module.exports = {
* @param {Number} width The desired width of the image in pixels. Default: 96.
* @param {Number} height The desired height of the image in pixels. Default: 96.
* @return {string} The complete URL to the identicon.
* @deprecated This is no longer in the specification.
*/
getIdenticonUri: function(baseUrl, identiconString, width, height) {
if (!identiconString) {
@@ -99,7 +100,7 @@ module.exports = {
height: height,
};
const path = utils.encodeUri("/_matrix/media/v1/identicon/$ident", {
const path = utils.encodeUri("/_matrix/media/unstable/identicon/$ident", {
$ident: identiconString,
});
return baseUrl + path +
+487
View File
@@ -0,0 +1,487 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 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.
*/
/**
* Cross signing methods
* @module crypto/CrossSigning
*/
import {pkSign, pkVerify} from './olmlib';
import {EventEmitter} from 'events';
import logger from '../logger';
function publicKeyFromKeyInfo(keyInfo) {
return Object.entries(keyInfo.keys)[0];
}
export class CrossSigningInfo extends EventEmitter {
/**
* Information about a user's cross-signing keys
*
* @class
*
* @param {string} userId the user that the information is about
* @param {object} callbacks Callbacks used to interact with the app
* Requires getCrossSigningKey and saveCrossSigningKeys
*/
constructor(userId, callbacks) {
super();
// you can't change the userId
Object.defineProperty(this, 'userId', {
enumerable: true,
value: userId,
});
this._callbacks = callbacks || {};
this.keys = {};
this.firstUse = true;
}
/**
* Calls the app callback to ask for a private key
* @param {string} type The key type ("master", "self_signing", or "user_signing")
* @param {Uint8Array} expectedPubkey The matching public key or undefined to use
* the stored public key for the given key type.
*/
async getCrossSigningKey(type, expectedPubkey) {
if (!this._callbacks.getCrossSigningKey) {
throw new Error("No getCrossSigningKey callback supplied");
}
if (expectedPubkey === undefined) {
expectedPubkey = this.getId(type);
}
const privkey = await this._callbacks.getCrossSigningKey(type, expectedPubkey);
if (!privkey) {
throw new Error(
"getCrossSigningKey callback for " + type + " returned falsey",
);
}
const signing = new global.Olm.PkSigning();
const gotPubkey = signing.init_with_seed(privkey);
if (gotPubkey !== expectedPubkey) {
signing.free();
throw new Error(
"Key type " + type + " from getCrossSigningKey callback did not match",
);
} else {
return [gotPubkey, signing];
}
}
static fromStorage(obj, userId) {
const res = new CrossSigningInfo(userId);
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
res[prop] = obj[prop];
}
}
return res;
}
toStorage() {
return {
keys: this.keys,
firstUse: this.firstUse,
};
}
hasKeys() {
return Object.keys(this.keys).length > 0;
}
/**
* Get the ID used to identify the user
*
* @param {string} type The type of key to get the ID of. One of "master",
* "self_signing", or "user_signing". Defaults to "master".
*
* @return {string} the ID
*/
getId(type) {
type = type || "master";
if (!this.keys[type]) return null;
const keyInfo = this.keys[type];
return publicKeyFromKeyInfo(keyInfo)[1];
}
async resetKeys(level) {
if (!this._callbacks.saveCrossSigningKeys) {
throw new Error("No saveCrossSigningKeys callback supplied");
}
// If we're resetting the master key, we reset all keys
if (
level === undefined ||
level & CrossSigningLevel.MASTER ||
!this.keys.master
) {
level = (
CrossSigningLevel.MASTER |
CrossSigningLevel.USER_SIGNING |
CrossSigningLevel.SELF_SIGNING
);
} else if (level === 0) {
return;
}
const privateKeys = {};
const keys = {};
let masterSigning;
let masterPub;
try {
if (level & CrossSigningLevel.MASTER) {
masterSigning = new global.Olm.PkSigning();
privateKeys.master = masterSigning.generate_seed();
masterPub = masterSigning.init_with_seed(privateKeys.master);
keys.master = {
user_id: this.userId,
usage: ['master'],
keys: {
['ed25519:' + masterPub]: masterPub,
},
};
} else {
[masterPub, masterSigning] = await this.getCrossSigningyKey("master");
}
if (level & CrossSigningLevel.SELF_SIGNING) {
const sskSigning = new global.Olm.PkSigning();
try {
privateKeys.self_signing = sskSigning.generate_seed();
const sskPub = sskSigning.init_with_seed(privateKeys.self_signing);
keys.self_signing = {
user_id: this.userId,
usage: ['self_signing'],
keys: {
['ed25519:' + sskPub]: sskPub,
},
};
pkSign(keys.self_signing, masterSigning, this.userId, masterPub);
} finally {
sskSigning.free();
}
}
if (level & CrossSigningLevel.USER_SIGNING) {
const uskSigning = new global.Olm.PkSigning();
try {
privateKeys.user_signing = uskSigning.generate_seed();
const uskPub = uskSigning.init_with_seed(privateKeys.user_signing);
keys.user_signing = {
user_id: this.userId,
usage: ['user_signing'],
keys: {
['ed25519:' + uskPub]: uskPub,
},
};
pkSign(keys.user_signing, masterSigning, this.userId, masterPub);
} finally {
uskSigning.free();
}
}
Object.assign(this.keys, keys);
this._callbacks.saveCrossSigningKeys(privateKeys);
} finally {
if (masterSigning) {
masterSigning.free();
}
}
}
setKeys(keys) {
const signingKeys = {};
if (keys.master) {
if (keys.master.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in master key from " + this.userId;
logger.error(error);
throw new Error(error);
}
if (!this.keys.master) {
// this is the first key we've seen, so first-use is true
this.firstUse = true;
} else if (publicKeyFromKeyInfo(keys.master)[1] !== this.getId()) {
// this is a different key, so first-use is false
this.firstUse = false;
} // otherwise, same key, so no change
signingKeys.master = keys.master;
} else if (this.keys.master) {
signingKeys.master = this.keys.master;
} else {
throw new Error("Tried to set cross-signing keys without a master key");
}
const masterKey = publicKeyFromKeyInfo(signingKeys.master)[1];
// verify signatures
if (keys.user_signing) {
if (keys.user_signing.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in user_signing key from " + this.userId;
logger.error(error);
throw new Error(error);
}
try {
pkVerify(keys.user_signing, masterKey, this.userId);
} catch (e) {
logger.error("invalid signature on user-signing key");
// FIXME: what do we want to do here?
throw e;
}
}
if (keys.self_signing) {
if (keys.self_signing.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
" in self_signing key from " + this.userId;
logger.error(error);
throw new Error(error);
}
try {
pkVerify(keys.self_signing, masterKey, this.userId);
} catch (e) {
logger.error("invalid signature on self-signing key");
// FIXME: what do we want to do here?
throw e;
}
}
// if everything checks out, then save the keys
if (keys.master) {
this.keys.master = keys.master;
// if the master key is set, then the old self-signing and
// user-signing keys are obsolete
delete this.keys.self_signing;
delete this.keys.user_signing;
}
if (keys.self_signing) {
this.keys.self_signing = keys.self_signing;
}
if (keys.user_signing) {
this.keys.user_signing = keys.user_signing;
}
}
async signObject(data, type) {
if (!this.keys[type]) {
throw new Error(
"Attempted to sign with " + type + " key but no such key present",
);
}
const [pubkey, signing] = await this.getCrossSigningKey(type);
try {
pkSign(data, signing, this.userId, pubkey);
return data;
} finally {
signing.free();
}
}
async signUser(key) {
if (!this.keys.user_signing) {
return;
}
return this.signObject(key.keys.master, "user_signing");
}
async signDevice(userId, device) {
if (userId !== this.userId) {
throw new Error(
`Trying to sign ${userId}'s device; can only sign our own device`,
);
}
if (!this.keys.self_signing) {
return;
}
return this.signObject(
{
algorithms: device.algorithms,
keys: device.keys,
device_id: device.deviceId,
user_id: userId,
}, "self_signing",
);
}
/**
* Check whether a given user is trusted.
*
* @param {CrossSigningInfo} userCrossSigning Cross signing info for user
*
* @returns {UserTrustLevel}
*/
checkUserTrust(userCrossSigning) {
// if we're checking our own key, then it's trusted if the master key
// and self-signing key match
if (this.userId === userCrossSigning.userId
&& this.getId() && this.getId() === userCrossSigning.getId()
&& this.getId("self_signing")
&& this.getId("self_signing") === userCrossSigning.getId("self_signing")
) {
return new UserTrustLevel(true, this.firstUse);
}
if (!this.keys.user_signing) {
// If there's no user signing key, they can't possibly be verified.
// They may be TOFU trusted though.
return new UserTrustLevel(false, userCrossSigning.firstUse);
}
let userTrusted;
const userMaster = userCrossSigning.keys.master;
const uskId = this.getId('user_signing');
try {
pkVerify(userMaster, uskId, this.userId);
userTrusted = true;
} catch (e) {
userTrusted = false;
}
return new UserTrustLevel(userTrusted, userCrossSigning.firstUse);
}
/**
* Check whether a given device is trusted.
*
* @param {CrossSigningInfo} userCrossSigning Cross signing info for user
* @param {module:crypto/deviceinfo} device The device to check
* @param {bool} localTrust Whether the device is trusted locally
*
* @returns {DeviceTrustLevel}
*/
checkDeviceTrust(userCrossSigning, device, localTrust) {
const userTrust = this.checkUserTrust(userCrossSigning);
const userSSK = userCrossSigning.keys.self_signing;
if (!userSSK) {
// if the user has no self-signing key then we cannot make any
// trust assertions about this device from cross-signing
return new DeviceTrustLevel(false, false, localTrust);
}
const deviceObj = deviceToObject(device, userCrossSigning.userId);
try {
// if we can verify the user's SSK from their master key...
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
// ...and this device's key from their SSK...
pkVerify(
deviceObj, publicKeyFromKeyInfo(userSSK)[1], userCrossSigning.userId,
);
// ...then we trust this device as much as far as we trust the user
return DeviceTrustLevel.fromUserTrustLevel(userTrust, localTrust);
} catch (e) {
return new DeviceTrustLevel(false, false, localTrust);
}
}
}
function deviceToObject(device, userId) {
return {
algorithms: device.algorithms,
keys: device.keys,
device_id: device.deviceId,
user_id: userId,
signatures: device.signatures,
};
}
export const CrossSigningLevel = {
MASTER: 4,
USER_SIGNING: 2,
SELF_SIGNING: 1,
};
/**
* Represents the ways in which we trust a user
*/
export class UserTrustLevel {
constructor(crossSigningVerified, tofu) {
this._crossSigningVerified = crossSigningVerified;
this._tofu = tofu;
}
/**
* @returns {bool} true if this user is verified via any means
*/
isVerified() {
return this.isCrossSigningVerified();
}
/**
* @returns {bool} true if this user is verified via cross signing
*/
isCrossSigningVerified() {
return this._crossSigningVerified;
}
/**
* @returns {bool} true if this user's key is trusted on first use
*/
isTofu() {
return this._tofu;
}
}
/**
* Represents the ways in which we trust a device
*/
export class DeviceTrustLevel {
constructor(crossSigningVerified, tofu, localVerified) {
this._crossSigningVerified = crossSigningVerified;
this._tofu = tofu;
this._localVerified = localVerified;
}
static fromUserTrustLevel(userTrustLevel, localVerified) {
return new DeviceTrustLevel(
userTrustLevel._crossSigningVerified,
userTrustLevel._tofu,
localVerified,
);
}
/**
* @returns {bool} true if this device is verified via any means
*/
isVerified() {
return this.isCrossSigningVerified() || this.isLocallyVerified();
}
/**
* @returns {bool} true if this device is verified via cross signing
*/
isCrossSigningVerified() {
return this._crossSigningVerified;
}
/**
* @returns {bool} true if this device is verified locally
*/
isLocallyVerified() {
return this._localVerified;
}
/**
* @returns {bool} true if this device is trusted from a user's key
* that is trusted on first use
*/
isTofu() {
return this._tofu;
}
}
+89 -21
View File
@@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 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.
@@ -23,11 +24,14 @@ limitations under the License.
*/
import Promise from 'bluebird';
import {EventEmitter} from 'events';
import logger from '../logger';
import DeviceInfo from './deviceinfo';
import {CrossSigningInfo} from './CrossSigning';
import olmlib from './olmlib';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
import {sleep} from '../utils';
/* State transition diagram for DeviceList._deviceTrackingStatus
@@ -60,8 +64,10 @@ const TRACKING_STATUS_UP_TO_DATE = 3;
/**
* @alias module:crypto/DeviceList
*/
export default class DeviceList {
export default class DeviceList extends EventEmitter {
constructor(baseApis, cryptoStore, olmDevice) {
super();
this._cryptoStore = cryptoStore;
// userId -> {
@@ -71,6 +77,11 @@ export default class DeviceList {
// }
this._devices = {};
// userId -> {
// [key info]
// }
this._crossSigningInfo = {};
// map of identity keys to the user who owns it
this._userByIdentityKey = {};
@@ -111,6 +122,7 @@ export default class DeviceList {
'readonly', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
this._cryptoStore.getEndToEndDeviceData(txn, (deviceData) => {
this._devices = deviceData ? deviceData.devices : {},
this._ssks = deviceData ? deviceData.self_signing_keys || {} : {};
this._deviceTrackingStatus = deviceData ?
deviceData.trackingStatus : {};
this._syncToken = deviceData ? deviceData.syncToken : null;
@@ -201,6 +213,7 @@ export default class DeviceList {
'readwrite', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
this._cryptoStore.storeEndToEndDeviceData({
devices: this._devices,
self_signing_keys: this._ssks,
trackingStatus: this._deviceTrackingStatus,
syncToken: this._syncToken,
}, txn);
@@ -334,6 +347,17 @@ export default class DeviceList {
return this._devices[userId];
}
getStoredCrossSigningForUser(userId) {
if (!this._crossSigningInfo[userId]) return null;
return CrossSigningInfo.fromStorage(this._crossSigningInfo[userId], userId);
}
storeCrossSigningForUser(userId, info) {
this._crossSigningInfo[userId] = info;
this._dirty = true;
}
/**
* Get the stored keys for a single device
*
@@ -561,6 +585,10 @@ export default class DeviceList {
}
}
setRawStoredCrossSigningForUser(userId, info) {
this._crossSigningInfo[userId] = info;
}
/**
* Fire off download update requests for the given users, and update the
* device list tracking status for them, and the
@@ -624,6 +652,7 @@ export default class DeviceList {
}
});
this.saveIfDirty();
this.emit("crypto.devicesUpdated", users);
};
return prom;
@@ -724,6 +753,9 @@ class DeviceListUpdateSerialiser {
downloadUsers, opts,
).then((res) => {
const dk = res.device_keys || {};
const masterKeys = res.master_keys || {};
const ssks = res.self_signing_keys || {};
const usks = res.user_signing_keys || {};
// do each user in a separate promise, to avoid wedging the CPU
// (https://github.com/vector-im/riot-web/issues/3158)
@@ -732,8 +764,14 @@ class DeviceListUpdateSerialiser {
// this serves as an easy solution for now.
let prom = Promise.resolve();
for (const userId of downloadUsers) {
prom = prom.delay(5).then(() => {
return this._processQueryResponseForUser(userId, dk[userId]);
prom = prom.then(sleep(5)).then(() => {
return this._processQueryResponseForUser(
userId, dk[userId], {
master: masterKeys[userId],
self_signing: ssks[userId],
user_signing: usks[userId],
},
);
});
}
@@ -757,30 +795,58 @@ class DeviceListUpdateSerialiser {
return deferred.promise;
}
async _processQueryResponseForUser(userId, response) {
logger.log('got keys for ' + userId + ':', response);
async _processQueryResponseForUser(
userId, dkResponse, crossSigningResponse, sskResponse,
) {
logger.log('got device keys for ' + userId + ':', dkResponse);
logger.log('got cross-signing keys for ' + userId + ':', crossSigningResponse);
// map from deviceid -> deviceinfo for this user
const userStore = {};
const devs = this._deviceList.getRawStoredDevicesForUser(userId);
if (devs) {
Object.keys(devs).forEach((deviceId) => {
const d = DeviceInfo.fromStorage(devs[deviceId], deviceId);
userStore[deviceId] = d;
{
// map from deviceid -> deviceinfo for this user
const userStore = {};
const devs = this._deviceList.getRawStoredDevicesForUser(userId);
if (devs) {
Object.keys(devs).forEach((deviceId) => {
const d = DeviceInfo.fromStorage(devs[deviceId], deviceId);
userStore[deviceId] = d;
});
}
await _updateStoredDeviceKeysForUser(
this._olmDevice, userId, userStore, dkResponse || {},
);
// put the updates into the object that will be returned as our results
const storage = {};
Object.keys(userStore).forEach((deviceId) => {
storage[deviceId] = userStore[deviceId].toStorage();
});
this._deviceList._setRawStoredDevicesForUser(userId, storage);
}
await _updateStoredDeviceKeysForUser(
this._olmDevice, userId, userStore, response || {},
);
// now do the same for the cross-signing keys
{
// FIXME: should we be ignoring empty cross-signing responses, or
// should we be dropping the keys?
if (crossSigningResponse
&& (crossSigningResponse.master || crossSigningResponse.self_signing
|| crossSigningResponse.user_signing)) {
const crossSigning
= this._deviceList.getStoredCrossSigningForUser(userId)
|| new CrossSigningInfo(userId);
// put the updates into thr object that will be returned as our results
const storage = {};
Object.keys(userStore).forEach((deviceId) => {
storage[deviceId] = userStore[deviceId].toStorage();
});
crossSigning.setKeys(crossSigningResponse);
this._deviceList._setRawStoredDevicesForUser(userId, storage);
this._deviceList.setRawStoredCrossSigningForUser(
userId, crossSigning.toStorage(),
);
// NB. Unlike most events in the js-sdk, this one is internal to the
// js-sdk and is not re-emitted
this._deviceList.emit('userCrossSigningUpdated', userId);
}
}
}
}
@@ -854,6 +920,7 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
}
const unsigned = deviceResult.unsigned || {};
const signatures = deviceResult.signatures || {};
try {
await olmlib.verifySignature(_olmDevice, deviceResult, userId, deviceId, signKey);
@@ -886,5 +953,6 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
deviceStore.keys = deviceResult.keys || {};
deviceStore.algorithms = deviceResult.algorithms || [];
deviceStore.unsigned = unsigned;
deviceStore.signatures = signatures;
return true;
}
+17 -3
View File
@@ -462,7 +462,7 @@ OlmDevice.prototype.createInboundSession = async function(
*/
OlmDevice.prototype.getSessionIdsForDevice = async function(theirDeviceIdentityKey) {
if (this._sessionsInProgress[theirDeviceIdentityKey]) {
console.log("waiting for session to be created");
logger.log("waiting for olm session to be created");
try {
await this._sessionsInProgress[theirDeviceIdentityKey];
} catch (e) {
@@ -543,7 +543,7 @@ OlmDevice.prototype.getSessionIdForDevice = async function(
*/
OlmDevice.prototype.getSessionInfoForDevice = async function(deviceIdentityKey, nowait) {
if (this._sessionsInProgress[deviceIdentityKey] && !nowait) {
logger.log("waiting for session to be created");
logger.log("waiting for olm session to be created");
try {
await this._sessionsInProgress[deviceIdentityKey];
} catch (e) {
@@ -594,6 +594,11 @@ OlmDevice.prototype.encryptMessage = async function(
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
this._getSession(theirDeviceIdentityKey, sessionId, txn, (sessionInfo) => {
const sessionDesc = sessionInfo.session.describe();
logger.log(
"encryptMessage: Olm Session ID " + sessionId + " to " +
theirDeviceIdentityKey + ": " + sessionDesc,
);
res = sessionInfo.session.encrypt(payloadString);
this._saveSession(theirDeviceIdentityKey, sessionInfo, txn);
});
@@ -621,6 +626,11 @@ OlmDevice.prototype.decryptMessage = async function(
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
this._getSession(theirDeviceIdentityKey, sessionId, txn, (sessionInfo) => {
const sessionDesc = sessionInfo.session.describe();
logger.log(
"decryptMessage: Olm Session ID " + sessionId + " from " +
theirDeviceIdentityKey + ": " + sessionDesc,
);
payloadString = sessionInfo.session.decrypt(messageType, ciphertext);
sessionInfo.lastReceivedMessageTs = Date.now();
this._saveSession(theirDeviceIdentityKey, sessionInfo, txn);
@@ -730,6 +740,8 @@ OlmDevice.prototype.createOutboundGroupSession = function() {
OlmDevice.prototype.encryptGroupMessage = function(sessionId, payloadString) {
const self = this;
logger.log(`encrypting msg with megolm session ${sessionId}`);
checkPayloadLength(payloadString);
return this._getOutboundGroupSession(sessionId, function(session) {
@@ -876,7 +888,9 @@ OlmDevice.prototype.addInboundGroupSession = async function(
<= session.first_known_index()) {
// existing session has lower index (i.e. can
// decrypt more), so keep it
logger.log("Keeping existing session");
logger.log(
`Keeping existing megolm session ${sessionId}`,
);
return;
}
}
+529
View File
@@ -0,0 +1,529 @@
/*
Copyright 2019 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 logger from '../logger';
import olmlib from './olmlib';
import { randomString } from '../randomstring';
import { keyFromPassphrase } from './key_passphrase';
import { encodeRecoveryKey } from './recoverykey';
import { pkVerify } from './olmlib';
export const SECRET_STORAGE_ALGORITHM_V1 = "m.secret_storage.v1.curve25519-aes-sha2";
/**
* Implements Secure Secret Storage and Sharing (MSC1946)
* @module crypto/SecretStorage
*/
export default class SecretStorage extends EventEmitter {
constructor(baseApis, cryptoCallbacks, crossSigningInfo) {
super();
this._baseApis = baseApis;
this._cryptoCallbacks = cryptoCallbacks;
this._crossSigningInfo = crossSigningInfo;
this._requests = {};
this._incomingRequests = {};
}
getDefaultKeyId() {
const defaultKeyEvent = this._baseApis.getAccountData(
'm.secret_storage.default_key',
);
if (!defaultKeyEvent) return null;
return defaultKeyEvent.getContent().key;
}
setDefaultKeyId(keyId) {
return new Promise((resolve) => {
const listener = (ev) => {
if (
ev.getType() === 'm.secret_storage.default_key' &&
ev.getContent().key === keyId
) {
this._baseApis.removeListener('accountData', listener);
resolve();
}
};
this._baseApis.on('accountData', listener);
this._baseApis.setAccountData(
'm.secret_storage.default_key',
{ key: keyId },
);
});
}
/**
* Add a key for encrypting secrets.
*
* @param {string} algorithm the algorithm used by the key.
* @param {object} opts the options for the algorithm. The properties used
* depend on the algorithm given. This object may be modified to pass
* information back about the key.
* @param {string} [keyId] the ID of the key. If not given, a random
* ID will be generated.
*
* @return {string} the ID of the key
*/
async addKey(algorithm, opts, keyId) {
const keyData = {algorithm};
if (!opts) opts = {};
if (opts.name) {
keyData.name = opts.name;
}
switch (algorithm) {
case SECRET_STORAGE_ALGORITHM_V1:
{
const decryption = new global.Olm.PkDecryption();
try {
if (opts.passphrase) {
const key = await keyFromPassphrase(opts.passphrase);
keyData.passphrase = {
algorithm: "m.pbkdf2",
iterations: key.iterations,
salt: key.salt,
};
opts.encodedkey = encodeRecoveryKey(key.key);
keyData.pubkey = decryption.init_with_private_key(key.key);
} else if (opts.privkey) {
keyData.pubkey = decryption.init_with_private_key(opts.privkey);
opts.encodedkey = encodeRecoveryKey(opts.privkey);
} else {
keyData.pubkey = decryption.generate_key();
opts.encodedkey = encodeRecoveryKey(decryption.get_private_key());
}
} finally {
decryption.free();
}
break;
}
default:
throw new Error(`Unknown key algorithm ${opts.algorithm}`);
}
if (!keyId) {
do {
keyId = randomString(32);
} while (this._baseApis.getAccountData(`m.secret_storage.key.${keyId}`));
}
await this._crossSigningInfo.signObject(keyData, 'master');
await this._baseApis.setAccountData(
`m.secret_storage.key.${keyId}`, keyData,
);
return keyId;
}
// TODO: need a function to get all the secret keys
/**
* Store an encrypted secret on the server
*
* @param {string} name The name of the secret
* @param {string} secret The secret contents.
* @param {Array} keys The IDs of the keys to use to encrypt the secret
* or null/undefined to use the default key.
*/
async store(name, secret, keys) {
const encrypted = {};
if (!keys) {
const defaultKeyId = this.getDefaultKeyId();
if (!defaultKeyId) {
throw new Error("No keys specified and no default key present");
}
keys = [defaultKeyId];
}
if (keys.length === 0) {
throw new Error("Zero keys given to encrypt with!");
}
for (const keyId of keys) {
// get key information from key storage
const keyInfo = this._baseApis.getAccountData(
"m.secret_storage.key." + keyId,
);
if (!keyInfo) {
throw new Error("Unknown key: " + keyId);
}
const keyInfoContent = keyInfo.getContent();
// check signature of key info
pkVerify(
keyInfoContent,
this._crossSigningInfo.getId('master'),
this._crossSigningInfo.userId,
);
// encrypt secret, based on the algorithm
switch (keyInfoContent.algorithm) {
case SECRET_STORAGE_ALGORITHM_V1:
{
const encryption = new global.Olm.PkEncryption();
try {
encryption.set_recipient_key(keyInfoContent.pubkey);
encrypted[keyId] = encryption.encrypt(secret);
} finally {
encryption.free();
}
break;
}
default:
logger.warn("unknown algorithm for secret storage key " + keyId
+ ": " + keyInfoContent.algorithm);
// do nothing if we don't understand the encryption algorithm
}
}
// save encrypted secret
await this._baseApis.setAccountData(name, {encrypted});
}
/**
* Get a secret from storage.
*
* @param {string} name the name of the secret
*
* @return {string} the contents of the secret
*/
async get(name) {
const secretInfo = this._baseApis.getAccountData(name);
if (!secretInfo) {
return;
}
const secretContent = secretInfo.getContent();
if (!secretContent.encrypted) {
throw new Error("Content is not encrypted!");
}
// get possible keys to decrypt
const keys = {};
for (const keyId of Object.keys(secretContent.encrypted)) {
// get key information from key storage
const keyInfo = this._baseApis.getAccountData(
"m.secret_storage.key." + keyId,
).getContent();
const encInfo = secretContent.encrypted[keyId];
switch (keyInfo.algorithm) {
case SECRET_STORAGE_ALGORITHM_V1:
if (keyInfo.pubkey && encInfo.ciphertext && encInfo.mac
&& encInfo.ephemeral) {
keys[keyId] = keyInfo;
}
break;
default:
// do nothing if we don't understand the encryption algorithm
}
}
let keyId;
let decryption;
try {
// fetch private key from app
[keyId, decryption] = await this._getSecretStorageKey(keys);
// decrypt secret
const encInfo = secretContent.encrypted[keyId];
switch (keys[keyId].algorithm) {
case SECRET_STORAGE_ALGORITHM_V1:
return decryption.decrypt(
encInfo.ephemeral, encInfo.mac, encInfo.ciphertext,
);
}
} finally {
if (decryption) decryption.free();
}
}
/**
* Check if a secret is stored on the server.
*
* @param {string} name the name of the secret
* @param {boolean} checkKey check if the secret is encrypted by a trusted key
*
* @return {boolean} whether or not the secret is stored
*/
isStored(name, checkKey) {
// check if secret exists
const secretInfo = this._baseApis.getAccountData(name);
if (!secretInfo) {
return false;
}
if (checkKey === undefined) checkKey = true;
const secretContent = secretInfo.getContent();
if (!secretContent.encrypted) {
return false;
}
// check if secret is encrypted by a known/trusted secret and
// encryption looks sane
for (const keyId of Object.keys(secretContent.encrypted)) {
// get key information from key storage
const keyInfo = this._baseApis.getAccountData(
"m.secret_storage.key." + keyId,
).getContent();
const encInfo = secretContent.encrypted[keyId];
if (checkKey) {
pkVerify(
keyInfo,
this._crossSigningInfo.getId('master'),
this._crossSigningInfo.userId,
);
}
switch (keyInfo.algorithm) {
case SECRET_STORAGE_ALGORITHM_V1:
if (keyInfo.pubkey && encInfo.ciphertext && encInfo.mac
&& encInfo.ephemeral) {
return true;
}
break;
default:
// do nothing if we don't understand the encryption algorithm
}
}
return false;
}
/**
* Request a secret from another device
*
* @param {string} name the name of the secret to request
* @param {string[]} devices the devices to request the secret from
*
* @return {string} the contents of the secret
*/
request(name, devices) {
const requestId = this._baseApis.makeTxnId();
const requestControl = this._requests[requestId] = {
devices,
};
const promise = new Promise((resolve, reject) => {
requestControl.resolve = resolve;
requestControl.reject = reject;
});
const cancel = (reason) => {
// send cancellation event
const cancelData = {
action: "request_cancellation",
requesting_device_id: this._baseApis.deviceId,
request_id: requestId,
};
const toDevice = {};
for (const device of devices) {
toDevice[device] = cancelData;
}
this._baseApis.sendToDevice("m.secret.request", {
[this._baseApis.getUserId()]: toDevice,
});
// and reject the promise so that anyone waiting on it will be
// notified
requestControl.reject(new Error(reason || "Cancelled"));
};
// send request to devices
const requestData = {
name,
action: "request",
requesting_device_id: this._baseApis.deviceId,
request_id: requestId,
};
const toDevice = {};
for (const device of devices) {
toDevice[device] = requestData;
}
this._baseApis.sendToDevice("m.secret.request", {
[this._baseApis.getUserId()]: toDevice,
});
return {
request_id: requestId,
promise,
cancel,
};
}
async _onRequestReceived(event) {
const sender = event.getSender();
const content = event.getContent();
if (sender !== this._baseApis.getUserId()
|| !(content.name && content.action
&& content.requesting_device_id && content.request_id)) {
// ignore requests from anyone else, for now
return;
}
const deviceId = content.requesting_device_id;
// check if it's a cancel
if (content.action === "request_cancellation") {
if (this._incomingRequests[deviceId]
&& this._incomingRequests[deviceId][content.request_id]) {
logger.info("received request cancellation for secret (" + sender
+ ", " + deviceId + ", " + content.request_id + ")");
this.baseApis.emit("crypto.secrets.requestCancelled", {
user_id: sender,
device_id: deviceId,
request_id: content.request_id,
});
}
} else if (content.action === "request") {
if (deviceId === this._baseApis.deviceId) {
// no point in trying to send ourself the secret
return;
}
// check if we have the secret
logger.info("received request for secret (" + sender
+ ", " + deviceId + ", " + content.request_id + ")");
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),
});
if (secret) {
const payload = {
type: "m.secret.send",
content: {
request_id: content.request_id,
secret: secret,
},
};
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this._baseApis._crypto._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
await olmlib.ensureOlmSessionsForDevices(
this._baseApis._crypto._olmDevice,
this._baseApis,
{
[sender]: [
await this._baseApis.getStoredDevice(sender, deviceId),
],
},
);
await olmlib.encryptMessageForDevice(
encryptedContent.ciphertext,
this._baseApis.getUserId(),
this._baseApis.deviceId,
this._baseApis._crypto._olmDevice,
sender,
this._baseApis._crypto.getStoredDevice(sender, deviceId),
payload,
);
const contentMap = {
[sender]: {
[deviceId]: encryptedContent,
},
};
this._baseApis.sendToDevice("m.room.encrypted", contentMap);
}
}
}
_onSecretReceived(event) {
if (event.getSender() !== this._baseApis.getUserId()) {
// we shouldn't be receiving secrets from anyone else, so ignore
// because someone could be trying to send us bogus data
return;
}
const content = event.getContent();
logger.log("got secret share for request ", content.request_id);
const requestControl = this._requests[content.request_id];
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(
olmlib.OLM_ALGORITHM,
event.getSenderKey(),
);
if (!deviceInfo) {
logger.log(
"secret share from unknown device with key", event.getSenderKey(),
);
return;
}
if (!requestControl.devices.includes(deviceInfo.deviceId)) {
logger.log("unsolicited secret share from device", deviceInfo.deviceId);
return;
}
requestControl.resolve(content.secret);
}
}
async _getSecretStorageKey(keys) {
if (!this._cryptoCallbacks.getSecretStorageKey) {
throw new Error("No getSecretStorageKey callback supplied");
}
const returned = await Promise.resolve(
this._cryptoCallbacks.getSecretStorageKey({keys}),
);
if (!returned) {
throw new Error("getSecretStorageKey callback returned falsey");
}
if (returned.length < 2) {
throw new Error("getSecretStorageKey callback returned invalid data");
}
const [keyId, privateKey] = returned;
if (!keys[keyId]) {
throw new Error("App returned unknown key from getSecretStorageKey!");
}
switch (keys[keyId].algorithm) {
case SECRET_STORAGE_ALGORITHM_V1:
{
const decryption = new global.Olm.PkDecryption();
let pubkey;
try {
pubkey = decryption.init_with_private_key(privateKey);
} catch (e) {
decryption.free();
throw new Error("getSecretStorageKey callback returned invalid key");
}
if (pubkey !== keys[keyId].pubkey) {
decryption.free();
throw new Error(
"getSecretStorageKey callback returned incorrect key",
);
}
return [keyId, decryption];
}
default:
throw new Error("Unknown key type: " + keys[keyId].algorithm);
}
}
}
+18 -17
View File
@@ -23,8 +23,8 @@ limitations under the License.
*/
import Promise from 'bluebird';
import logger from '../../logger';
const logger = require("../../logger");
const utils = require("../../utils");
const olmlib = require("../olmlib");
const base = require("./base");
@@ -104,7 +104,7 @@ OutboundSessionInfo.prototype.sharedWithTooManyDevices = function(
}
if (!devicesInRoom.hasOwnProperty(userId)) {
logger.log("Starting new session because we shared with " + userId);
logger.log("Starting new megolm session because we shared with " + userId);
return true;
}
@@ -115,7 +115,7 @@ OutboundSessionInfo.prototype.sharedWithTooManyDevices = function(
if (!devicesInRoom[userId].hasOwnProperty(deviceId)) {
logger.log(
"Starting new session because we shared with " +
"Starting new megolm session because we shared with " +
userId + ":" + deviceId,
);
return true;
@@ -200,6 +200,8 @@ MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
if (!session) {
logger.log(`Starting new megolm session for room ${self._roomId}`);
session = await self._prepareNewSession();
logger.log(`Started new megolm session ${session.sessionId} ` +
`for room ${self._roomId}`);
self._outboundSessions[session.sessionId] = session;
}
@@ -278,7 +280,7 @@ MegolmEncryption.prototype._prepareNewSession = async function() {
).catch((e) => {
// This throws if the upload failed, but this is fine
// since it will have written it to the db and will retry.
console.log("Failed to back up group session", e);
logger.log("Failed to back up megolm session", e);
});
}
@@ -440,19 +442,19 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
) {
const obSessionInfo = this._outboundSessions[sessionId];
if (!obSessionInfo) {
logger.debug("Session ID " + sessionId + " not found: not re-sharing keys");
logger.debug(`megolm session ${sessionId} not found: not re-sharing keys`);
return;
}
// The chain index of the key we previously sent this device
if (obSessionInfo.sharedWithDevices[userId] === undefined) {
logger.debug("Session ID " + sessionId + " never shared with user " + userId);
logger.debug(`megolm session ${sessionId} never shared with user ${userId}`);
return;
}
const sentChainIndex = obSessionInfo.sharedWithDevices[userId][device.deviceId];
if (sentChainIndex === undefined) {
logger.debug(
"Session ID " + sessionId + " never shared with device " +
"megolm session ID " + sessionId + " never shared with device " +
userId + ":" + device.deviceId,
);
return;
@@ -466,7 +468,7 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
if (!key) {
logger.warn(
"No outbound session key found for " + sessionId + ": not re-sharing keys",
`No inbound session key found for megolm ${sessionId}: not re-sharing keys`,
);
return;
}
@@ -513,9 +515,8 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
[device.deviceId]: encryptedContent,
},
});
logger.debug(
`Re-shared key for session ${sessionId} with ${userId}:${device.deviceId}`,
);
logger.debug(`Re-shared key for megolm session ${sessionId} ` +
`with ${userId}:${device.deviceId}`);
};
/**
@@ -550,10 +551,10 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
await this._encryptAndSendKeysToDevices(
session, key.chain_index, userDeviceMaps[i], payload,
);
logger.log(`Completed megolm keyshare in ${this._roomId} `
+ `(slice ${i + 1}/${userDeviceMaps.length})`);
logger.log(`Completed megolm keyshare for ${session.sessionId} `
+ `in ${this._roomId} (slice ${i + 1}/${userDeviceMaps.length})`);
} catch (e) {
logger.log(`megolm keyshare in ${this._roomId} `
logger.log(`megolm keyshare for ${session.sessionId} in ${this._roomId} `
+ `(slice ${i + 1}/${userDeviceMaps.length}) failed`);
throw e;
@@ -922,7 +923,7 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
keysClaimed = event.getKeysClaimed();
}
logger.log(`Adding key for megolm session ${senderKey}|${sessionId}`);
logger.log(`Received and adding key for megolm session ${senderKey}|${sessionId}`);
return this._olmDevice.addInboundGroupSession(
content.room_id, senderKey, forwardingKeyChain, sessionId,
content.session_key, keysClaimed,
@@ -955,7 +956,7 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
).catch((e) => {
// This throws if the upload failed, but this is fine
// since it will have written it to the db and will retry.
console.log("Failed to back up group session", e);
logger.log("Failed to back up megolm session", e);
});
}
}).catch((e) => {
@@ -1088,7 +1089,7 @@ MegolmDecryption.prototype.importRoomKey = function(session) {
).catch((e) => {
// This throws if the upload failed, but this is fine
// since it will have written it to the db and will retry.
console.log("Failed to back up group session", e);
logger.log("Failed to back up megolm session", e);
});
}
// have another go at decrypting events sent with this session.
+2 -2
View File
@@ -22,7 +22,7 @@ limitations under the License.
*/
import Promise from 'bluebird';
const logger = require("../../logger");
import logger from '../../logger';
const utils = require("../../utils");
const olmlib = require("../olmlib");
const DeviceInfo = require("../deviceinfo");
@@ -139,7 +139,7 @@ OlmEncryption.prototype.encryptMessage = async function(room, eventType, content
}
}
return await Promise.all(promises).return(encryptedContent);
return await Promise.all(promises).then(() => encryptedContent);
};
/**
+2
View File
@@ -56,6 +56,7 @@ function DeviceInfo(deviceId) {
this.verified = DeviceVerification.UNVERIFIED;
this.known = false;
this.unsigned = {};
this.signatures = {};
}
/**
@@ -88,6 +89,7 @@ DeviceInfo.prototype.toStorage = function() {
verified: this.verified,
known: this.known,
unsigned: this.unsigned,
signatures: this.signatures,
};
};
+868 -86
View File
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 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.
@@ -18,13 +19,11 @@ import { randomString } from '../randomstring';
const DEFAULT_ITERATIONS = 500000;
export async function keyForExistingBackup(backupData, password) {
export async function keyFromAuthData(authData, password) {
if (!global.Olm) {
throw new Error("Olm is not available");
}
const authData = backupData.auth_data;
if (!authData.private_key_salt || !authData.private_key_iterations) {
throw new Error(
"Salt and/or iterations not found: " +
@@ -33,12 +32,12 @@ export async function keyForExistingBackup(backupData, password) {
}
return await deriveKey(
password, backupData.auth_data.private_key_salt,
backupData.auth_data.private_key_iterations,
password, authData.private_key_salt,
authData.private_key_iterations,
);
}
export async function keyForNewBackup(password) {
export async function keyFromPassphrase(password) {
if (!global.Olm) {
throw new Error("Olm is not available");
}
+69 -6
View File
@@ -24,7 +24,7 @@ limitations under the License.
import Promise from 'bluebird';
const anotherjson = require('another-json');
const logger = require("../logger");
import logger from '../logger';
const utils = require("../utils");
/**
@@ -287,12 +287,12 @@ async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceIn
);
} catch (e) {
// possibly a bad key
logger.error("Error starting session with device " +
logger.error("Error starting olm session with device " +
userId + ":" + deviceId + ": " + e);
return null;
}
logger.log("Started new sessionid " + sid +
logger.log("Started new olm sessionid " + sid +
" for device " + userId + ":" + deviceId);
return sid;
}
@@ -328,11 +328,74 @@ const _verifySignature = module.exports.verifySignature = async function(
// prepare the canonical json: remove unsigned and signatures, and stringify with
// anotherjson
delete obj.unsigned;
delete obj.signatures;
const json = anotherjson.stringify(obj);
const mangledObj = Object.assign({}, obj);
delete mangledObj.unsigned;
delete mangledObj.signatures;
const json = anotherjson.stringify(mangledObj);
olmDevice.verifySignature(
signingKey, json, signature,
);
};
/**
* Sign a JSON object using public key cryptography
* @param {Object} obj Object to sign. The object will be modified to include
* the new signature
* @param {Olm.PkSigning|Uint8Array} key the signing object or the private key
* seed
* @param {string} userId The user ID who owns the signing key
* @param {string} pubkey The public key (ignored if key is a seed)
* @returns {string} the signature for the object
*/
module.exports.pkSign = function(obj, key, userId, pubkey) {
let createdKey = false;
if (key instanceof Uint8Array) {
const keyObj = new global.Olm.PkSigning();
pubkey = keyObj.init_with_seed(key);
key = keyObj;
createdKey = true;
}
const sigs = obj.signatures || {};
delete obj.signatures;
const unsigned = obj.unsigned;
if (obj.unsigned) delete obj.unsigned;
try {
const mysigs = sigs[userId] || {};
sigs[userId] = mysigs;
return mysigs['ed25519:' + pubkey] = key.sign(anotherjson.stringify(obj));
} finally {
obj.signatures = sigs;
if (unsigned) obj.unsigned = unsigned;
if (createdKey) {
key.free();
}
}
};
/**
* Verify a signed JSON object
* @param {Object} obj Object to verify
* @param {string} pubkey The public key to use to verify
* @param {string} userId The user ID who signed the object
*/
module.exports.pkVerify = function(obj, pubkey, userId) {
const keyId = "ed25519:" + pubkey;
if (!(obj.signatures && obj.signatures[userId] && obj.signatures[userId][keyId])) {
throw new Error("No signature");
}
const signature = obj.signatures[userId][keyId];
const util = new global.Olm.Utility();
const sigs = obj.signatures;
delete obj.signatures;
const unsigned = obj.unsigned;
if (obj.unsigned) delete obj.unsigned;
try {
util.ed25519_verify(pubkey, anotherjson.stringify(obj), signature);
} finally {
obj.signatures = sigs;
if (unsigned) obj.unsigned = unsigned;
util.free();
}
};
@@ -58,35 +58,34 @@ export class Backend {
getOrAddOutgoingRoomKeyRequest(request) {
const requestBody = request.requestBody;
const deferred = Promise.defer();
const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite");
txn.onerror = deferred.reject;
return new Promise((resolve, reject) => {
const txn = this._db.transaction("outgoingRoomKeyRequests", "readwrite");
txn.onerror = reject;
// first see if we already have an entry for this request.
this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => {
if (existing) {
// this entry matches the request - return it.
// first see if we already have an entry for this request.
this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => {
if (existing) {
// this entry matches the request - return it.
logger.log(
`already have key request outstanding for ` +
`${requestBody.room_id} / ${requestBody.session_id}: ` +
`not sending another`,
);
resolve(existing);
return;
}
// we got to the end of the list without finding a match
// - add the new request.
logger.log(
`already have key request outstanding for ` +
`${requestBody.room_id} / ${requestBody.session_id}: ` +
`not sending another`,
`enqueueing key request for ${requestBody.room_id} / ` +
requestBody.session_id,
);
deferred.resolve(existing);
return;
}
// we got to the end of the list without finding a match
// - add the new request.
logger.log(
`enqueueing key request for ${requestBody.room_id} / ` +
requestBody.session_id,
);
txn.oncomplete = () => { deferred.resolve(request); };
const store = txn.objectStore("outgoingRoomKeyRequests");
store.add(request);
txn.oncomplete = () => { resolve(request); };
const store = txn.objectStore("outgoingRoomKeyRequests");
store.add(request);
});
});
return deferred.promise;
}
/**
@@ -100,15 +99,14 @@ export class Backend {
* not found
*/
getOutgoingRoomKeyRequest(requestBody) {
const deferred = Promise.defer();
return new Promise((resolve, reject) => {
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
txn.onerror = reject;
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
txn.onerror = deferred.reject;
this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => {
deferred.resolve(existing);
this._getOutgoingRoomKeyRequest(txn, requestBody, (existing) => {
resolve(existing);
});
});
return deferred.promise;
}
/**
@@ -332,6 +330,23 @@ export class Backend {
objectStore.put(newData, "-");
}
getCrossSigningKeys(txn, func) {
const objectStore = txn.objectStore("account");
const getReq = objectStore.get("crossSigningKeys");
getReq.onsuccess = function() {
try {
func(getReq.result || null);
} catch (e) {
abortWithException(txn, e);
}
};
}
storeCrossSigningKeys(txn, keys) {
const objectStore = txn.objectStore("account");
objectStore.put(keys, "crossSigningKeys");
}
// Olm Sessions
countEndToEndSessions(txn, func) {
@@ -694,7 +709,7 @@ function promiseifyTxn(txn) {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
} else {
console.log("Error performing indexeddb txn", event);
logger.log("Error performing indexeddb txn", event);
reject(event.target.error);
}
};
@@ -702,7 +717,7 @@ function promiseifyTxn(txn) {
if (txn._mx_abortexception !== undefined) {
reject(txn._mx_abortexception);
} else {
console.log("Error performing indexeddb txn", event);
logger.log("Error performing indexeddb txn", event);
reject(event.target.error);
}
};
+83 -27
View File
@@ -90,7 +90,7 @@ export default class IndexedDBCryptoStore {
};
req.onerror = (ev) => {
console.log("Error connecting to indexeddb", ev);
logger.log("Error connecting to indexeddb", ev);
reject(ev.target.error);
};
@@ -160,7 +160,7 @@ export default class IndexedDBCryptoStore {
};
req.onerror = (ev) => {
console.log("Error deleting data from indexeddb", ev);
logger.log("Error deleting data from indexeddb", ev);
reject(ev.target.error);
};
@@ -287,10 +287,12 @@ export default class IndexedDBCryptoStore {
* @param {function(string)} func Called with the account pickle
*/
getAccount(txn, func) {
this._backendPromise.value().getAccount(txn, func);
this._backendPromise.then(backend => {
backend.getAccount(txn, func);
});
}
/*
/**
* Write the account pickle to the store.
* This requires an active transaction. See doTxn().
*
@@ -298,7 +300,35 @@ export default class IndexedDBCryptoStore {
* @param {string} newData The new account pickle to store.
*/
storeAccount(txn, newData) {
this._backendPromise.value().storeAccount(txn, newData);
this._backendPromise.then(backend => {
backend.storeAccount(txn, newData);
});
}
/**
* Get the public part of the cross-signing keys (eg. self-signing key,
* user signing key).
*
* @param {*} txn An active transaction. See doTxn().
* @param {function(string)} func Called with the account keys object:
* { key_type: base64 encoded seed } where key type = user_signing_key_seed or self_signing_key_seed
*/
getCrossSigningKeys(txn, func) {
this._backendPromise.then(backend => {
backend.getCrossSigningKeys(txn, func);
});
}
/**
* Write the cross-signing keys back to the store
*
* @param {*} txn An active transaction. See doTxn().
* @param {string} keys keys object as getCrossSigningKeys()
*/
storeCrossSigningKeys(txn, keys) {
this._backendPromise.then(backend => {
backend.storeCrossSigningKeys(txn, keys);
});
}
// Olm sessions
@@ -309,7 +339,9 @@ export default class IndexedDBCryptoStore {
* @param {function(int)} func Called with the count of sessions
*/
countEndToEndSessions(txn, func) {
this._backendPromise.value().countEndToEndSessions(txn, func);
this._backendPromise.then(backend => {
backend.countEndToEndSessions(txn, func);
});
}
/**
@@ -325,7 +357,9 @@ export default class IndexedDBCryptoStore {
* a message.
*/
getEndToEndSession(deviceKey, sessionId, txn, func) {
this._backendPromise.value().getEndToEndSession(deviceKey, sessionId, txn, func);
this._backendPromise.then(backend => {
backend.getEndToEndSession(deviceKey, sessionId, txn, func);
});
}
/**
@@ -340,7 +374,9 @@ export default class IndexedDBCryptoStore {
* a message.
*/
getEndToEndSessions(deviceKey, txn, func) {
this._backendPromise.value().getEndToEndSessions(deviceKey, txn, func);
this._backendPromise.then(backend => {
backend.getEndToEndSessions(deviceKey, txn, func);
});
}
/**
@@ -351,7 +387,9 @@ export default class IndexedDBCryptoStore {
* and session keys.
*/
getAllEndToEndSessions(txn, func) {
this._backendPromise.value().getAllEndToEndSessions(txn, func);
this._backendPromise.then(backend => {
backend.getAllEndToEndSessions(txn, func);
});
}
/**
@@ -362,12 +400,14 @@ export default class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn().
*/
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
this._backendPromise.value().storeEndToEndSession(
deviceKey, sessionId, sessionInfo, txn,
);
this._backendPromise.then(backend => {
backend.storeEndToEndSession(
deviceKey, sessionId, sessionInfo, txn,
);
});
}
// Inbound group saessions
// Inbound group sessions
/**
* Retrieve the end-to-end inbound group session for a given
@@ -379,9 +419,11 @@ export default class IndexedDBCryptoStore {
* to Base64 end-to-end session.
*/
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
this._backendPromise.value().getEndToEndInboundGroupSession(
senderCurve25519Key, sessionId, txn, func,
);
this._backendPromise.then(backend => {
backend.getEndToEndInboundGroupSession(
senderCurve25519Key, sessionId, txn, func,
);
});
}
/**
@@ -392,7 +434,9 @@ export default class IndexedDBCryptoStore {
* sessionData}, then once with null to indicate the end of the list.
*/
getAllEndToEndInboundGroupSessions(txn, func) {
this._backendPromise.value().getAllEndToEndInboundGroupSessions(txn, func);
this._backendPromise.then(backend => {
backend.getAllEndToEndInboundGroupSessions(txn, func);
});
}
/**
@@ -405,9 +449,11 @@ export default class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn().
*/
addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
this._backendPromise.value().addEndToEndInboundGroupSession(
senderCurve25519Key, sessionId, sessionData, txn,
);
this._backendPromise.then(backend => {
backend.addEndToEndInboundGroupSession(
senderCurve25519Key, sessionId, sessionData, txn,
);
});
}
/**
@@ -420,9 +466,11 @@ export default class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn().
*/
storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn) {
this._backendPromise.value().storeEndToEndInboundGroupSession(
senderCurve25519Key, sessionId, sessionData, txn,
);
this._backendPromise.then(backend => {
backend.storeEndToEndInboundGroupSession(
senderCurve25519Key, sessionId, sessionData, txn,
);
});
}
// End-to-end device tracking
@@ -438,7 +486,9 @@ export default class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn().
*/
storeEndToEndDeviceData(deviceData, txn) {
this._backendPromise.value().storeEndToEndDeviceData(deviceData, txn);
this._backendPromise.then(backend => {
backend.storeEndToEndDeviceData(deviceData, txn);
});
}
/**
@@ -449,7 +499,9 @@ export default class IndexedDBCryptoStore {
* device data
*/
getEndToEndDeviceData(txn, func) {
this._backendPromise.value().getEndToEndDeviceData(txn, func);
this._backendPromise.then(backend => {
backend.getEndToEndDeviceData(txn, func);
});
}
// End to End Rooms
@@ -461,7 +513,9 @@ export default class IndexedDBCryptoStore {
* @param {*} txn An active transaction. See doTxn().
*/
storeEndToEndRoom(roomId, roomInfo, txn) {
this._backendPromise.value().storeEndToEndRoom(roomId, roomInfo, txn);
this._backendPromise.then(backend => {
backend.storeEndToEndRoom(roomId, roomInfo, txn);
});
}
/**
@@ -470,7 +524,9 @@ export default class IndexedDBCryptoStore {
* @param {function(Object)} func Function called with the end to end encrypted rooms
*/
getEndToEndRooms(txn, func) {
this._backendPromise.value().getEndToEndRooms(txn, func);
this._backendPromise.then(backend => {
backend.getEndToEndRooms(txn, func);
});
}
// session backups
+13 -1
View File
@@ -17,7 +17,7 @@ limitations under the License.
import Promise from 'bluebird';
import logger from '../../logger';
import MemoryCryptoStore from './memory-crypto-store.js';
import MemoryCryptoStore from './memory-crypto-store';
/**
* Internal module. Partial localStorage backed storage for e2e.
@@ -31,6 +31,7 @@ import MemoryCryptoStore from './memory-crypto-store.js';
const E2E_PREFIX = "crypto.";
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
const KEY_CROSS_SIGNING_KEYS = E2E_PREFIX + "cross_signing_keys";
const KEY_DEVICE_DATA = E2E_PREFIX + "device_data";
const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/";
const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/";
@@ -284,6 +285,17 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
);
}
getCrossSigningKeys(txn, func) {
const keys = getJsonItem(this.store, KEY_CROSS_SIGNING_KEYS);
func(keys);
}
storeCrossSigningKeys(txn, keys) {
setJsonItem(
this.store, KEY_CROSS_SIGNING_KEYS, keys,
);
}
doTxn(mode, stores, func) {
return Promise.resolve(func(null));
}
+9
View File
@@ -33,6 +33,7 @@ export default class MemoryCryptoStore {
constructor() {
this._outgoingRoomKeyRequests = [];
this._account = null;
this._crossSigningKeys = null;
// Map of {devicekey -> {sessionId -> session pickle}}
this._sessions = {};
@@ -234,6 +235,14 @@ export default class MemoryCryptoStore {
this._account = newData;
}
getCrossSigningKeys(txn, func) {
func(this._crossSigningKeys);
}
storeCrossSigningKeys(txn, keys) {
this._crossSigningKeys = keys;
}
// Olm Sessions
countEndToEndSessions(txn, func) {
+171 -20
View File
@@ -21,6 +21,11 @@ limitations under the License.
import {MatrixEvent} from '../../models/event';
import {EventEmitter} from 'events';
import logger from '../../logger';
import DeviceInfo from '../deviceinfo';
import {newTimeoutError} from "./Error";
const timeoutException = new Error("Verification timed out");
export default class VerificationBase extends EventEmitter {
/**
@@ -43,37 +48,116 @@ export default class VerificationBase extends EventEmitter {
*
* @param {string} transactionId the transaction ID to be used when sending events
*
* @param {object} startEvent the m.key.verification.start event that
* @param {string} [roomId] the room to use for verification
*
* @param {object} [startEvent] the m.key.verification.start event that
* initiated this verification, if any
*
* @param {object} request the key verification request object related to
* @param {object} [request] the key verification request object related to
* this verification, if any
*
* @param {object} parent parent verification for this verification, if any
*/
constructor(baseApis, userId, deviceId, transactionId, startEvent, request, parent) {
constructor(baseApis, userId, deviceId, transactionId, roomId, startEvent, request) {
super();
this._baseApis = baseApis;
this.userId = userId;
this.deviceId = deviceId;
this.transactionId = transactionId;
this.startEvent = startEvent;
this.request = request;
this._parent = parent;
if (typeof(roomId) === "string" || roomId instanceof String) {
this.roomId = roomId;
this.startEvent = startEvent;
this.request = request;
} else {
// if room ID was omitted, but start event and request were not
this.startEvent= roomId;
this.request = startEvent;
}
this.cancelled = false;
this._done = false;
this._promise = null;
this._transactionTimeoutTimer = null;
this._eventsSubscription = null;
// At this point, the verification request was received so start the timeout timer.
this._resetTimer();
if (this.roomId) {
this._sendWithTxnId = this._sendMessage;
} else {
this._sendWithTxnId = this._sendToDevice;
}
}
_resetTimer() {
logger.info("Refreshing/starting the verification transaction timeout timer");
if (this._transactionTimeoutTimer !== null) {
clearTimeout(this._transactionTimeoutTimer);
}
this._transactionTimeoutTimer = setTimeout(() => {
if (!this._done && !this.cancelled) {
logger.info("Triggering verification timeout");
this.cancel(timeoutException);
}
}, 10 * 60 * 1000); // 10 minutes
}
_endTimer() {
if (this._transactionTimeoutTimer !== null) {
clearTimeout(this._transactionTimeoutTimer);
this._transactionTimeoutTimer = null;
}
}
_contentFromEventWithTxnId(event) {
if (this.roomId) { // verification as timeline event
// ensure m.related_to is included in e2ee rooms
// as the field is excluded from encryption
const content = Object.assign({}, event.getContent());
content["m.relates_to"] = event.getRelation();
return content;
} else { // verification as to_device event
return event.getContent();
}
}
/* creates a content object with the transaction id added to it */
_contentWithTxnId(content) {
const copy = Object.assign({}, content);
if (this.roomId) { // verification as timeline event
copy["m.relates_to"] = {
rel_type: "m.reference",
event_id: this.transactionId,
};
} else { // verification as to_device event
copy.transaction_id = this.transactionId;
}
return copy;
}
_send(type, contentWithoutTxnId) {
const content = this._contentWithTxnId(contentWithoutTxnId);
return this._sendWithTxnId(type, content);
}
/* send a message to the other participant, using to-device messages
*/
_sendToDevice(type, content) {
if (this._done) {
return Promise.reject(new Error("Verification is already done"));
}
content.transaction_id = this.transactionId;
return this._baseApis.sendToDevice(type, {
[this.userId]: { [this.deviceId]: content },
});
}
/* send a message to the other participant, using in-roomm messages
*/
_sendMessage(type, content) {
if (this._done) {
return Promise.reject(new Error("Verification is already done"));
}
return this._baseApis.sendEvent(this.roomId, type, content);
}
_waitForEvent(type) {
if (this._done) {
return Promise.reject(new Error("Verification is already done"));
@@ -91,13 +175,18 @@ export default class VerificationBase extends EventEmitter {
} else if (e.getType() === this._expectedEvent) {
this._expectedEvent = undefined;
this._rejectEvent = undefined;
this._resetTimer();
this._resolveEvent(e);
} else if (e.getType() === "m.key.verification.cancel") {
const reject = this._reject;
this._reject = undefined;
reject(new Error("Other side cancelled verification"));
} else {
this._expectedEvent = undefined;
const exception = new Error(
"Unexpected message: expecting " + this._expectedEvent
+ " but got " + e.getType(),
);
this._expectedEvent = undefined;
if (this._rejectEvent) {
const reject = this._rejectEvent;
this._rejectEvent = undefined;
@@ -108,17 +197,27 @@ export default class VerificationBase extends EventEmitter {
}
done() {
this._endTimer(); // always kill the activity timer
if (!this._done) {
if (this.roomId) {
// verification in DM requires a done message
this._send("m.key.verification.done", {});
}
this._resolve();
}
}
cancel(e) {
this._endTimer(); // always kill the activity timer
if (!this._done) {
this.cancelled = true;
if (this.userId && this.deviceId && this.transactionId) {
// send a cancellation to the other user (if it wasn't
// cancelled by the other user)
if (e instanceof MatrixEvent) {
if (e === timeoutException) {
const timeoutEvent = newTimeoutError();
this._send(timeoutEvent.getType(), timeoutEvent.getContent());
} else if (e instanceof MatrixEvent) {
const sender = e.getSender();
if (sender !== this.userId) {
const content = e.getContent();
@@ -127,9 +226,9 @@ export default class VerificationBase extends EventEmitter {
content.reason = content.reason || content.body
|| "Unknown reason";
content.transaction_id = this.transactionId;
this._sendToDevice("m.key.verification.cancel", content);
this._send("m.key.verification.cancel", content);
} else {
this._sendToDevice("m.key.verification.cancel", {
this._send("m.key.verification.cancel", {
code: "m.unknown",
reason: content.body || "Unknown reason",
transaction_id: this.transactionId,
@@ -137,7 +236,7 @@ export default class VerificationBase extends EventEmitter {
}
}
} else {
this._sendToDevice("m.key.verification.cancel", {
this._send("m.key.verification.cancel", {
code: "m.unknown",
reason: e.toString(),
transaction_id: this.transactionId,
@@ -145,8 +244,16 @@ export default class VerificationBase extends EventEmitter {
}
}
if (this._promise !== null) {
this._reject(e);
// when we cancel without a promise, we end up with a promise
// but no reject function. If cancel is called again, we'd error.
if (this._reject) this._reject(e);
} else {
// unsubscribe from events, this happens in _reject usually but we don't have one here
if (this._eventsSubscription) {
this._eventsSubscription = this._eventsSubscription();
}
// FIXME: this causes an "Uncaught promise" console message
// if nothing ends up chaining this promise.
this._promise = Promise.reject(e);
}
// Also emit a 'cancel' event that the app can listen for to detect cancellation
@@ -167,15 +274,30 @@ export default class VerificationBase extends EventEmitter {
this._promise = new Promise((resolve, reject) => {
this._resolve = (...args) => {
this._done = true;
this._endTimer();
if (this.handler) {
// these listeners are attached in Crypto.acceptVerificationDM
if (this._eventsSubscription) {
this._eventsSubscription = this._eventsSubscription();
}
}
resolve(...args);
};
this._reject = (...args) => {
this._done = true;
this._endTimer();
if (this.handler) {
// these listeners are attached in Crypto.acceptVerificationDM
if (this._eventsSubscription) {
this._eventsSubscription = this._eventsSubscription();
}
}
reject(...args);
};
});
if (this._doVerification && !this._started) {
this._started = true;
this._resetTimer(); // restart the timeout
Promise.resolve(this._doVerification())
.then(this.done.bind(this), this.cancel.bind(this));
}
@@ -183,18 +305,47 @@ export default class VerificationBase extends EventEmitter {
}
async _verifyKeys(userId, keys, verifier) {
// we try to verify all the keys that we're told about, but we might
// not know about all of them, so keep track of the keys that we know
// about, and ignore the rest
const verifiedDevices = [];
for (const [keyId, keyInfo] of Object.entries(keys)) {
const deviceId = keyId.split(':', 2)[1];
const device = await this._baseApis.getStoredDevice(userId, deviceId);
if (!device) {
throw new Error(`Could not find device ${deviceId}`);
} else {
if (device) {
await verifier(keyId, device, keyInfo);
verifiedDevices.push(deviceId);
} else {
const crossSigningInfo = this._baseApis._crypto._deviceList
.getStoredCrossSigningForUser(userId);
if (crossSigningInfo && crossSigningInfo.getId() === deviceId) {
await verifier(keyId, DeviceInfo.fromStorage({
keys: {
[keyId]: deviceId,
},
}, deviceId), keyInfo);
verifiedDevices.push(deviceId);
} else {
logger.warn(
`verification: Could not find device ${deviceId} to verify`,
);
}
}
}
for (const keyId of Object.keys(keys)) {
const deviceId = keyId.split(':', 2)[1];
// if none of the keys could be verified, then error because the app
// should be informed about that
if (!verifiedDevices.length) {
throw new Error("No devices could be verified");
}
for (const deviceId of verifiedDevices) {
await this._baseApis.setDeviceVerified(userId, deviceId);
}
}
setEventsSubscription(subscription) {
this._eventsSubscription = subscription;
}
}
+30 -13
View File
@@ -205,7 +205,7 @@ export default class SAS extends Base {
}
async _doSendVerification() {
const initialMessage = {
const initialMessage = this._contentWithTxnId({
method: SAS.NAME,
from_device: this._baseApis.deviceId,
key_agreement_protocols: KEY_AGREEMENT_LIST,
@@ -213,9 +213,10 @@ export default class SAS extends Base {
message_authentication_codes: MAC_LIST,
// FIXME: allow app to specify what SAS methods can be used
short_authentication_string: SAS_LIST,
transaction_id: this.transactionId,
};
this._sendToDevice("m.key.verification.start", initialMessage);
});
// add the transaction id to the message beforehand because
// it needs to be included in the commitment hash later on
this._sendWithTxnId("m.key.verification.start", initialMessage);
let e = await this._waitForEvent("m.key.verification.accept");
@@ -235,7 +236,7 @@ export default class SAS extends Base {
const hashCommitment = content.commitment;
const olmSAS = new global.Olm.SAS();
try {
this._sendToDevice("m.key.verification.key", {
this._send("m.key.verification.key", {
key: olmSAS.get_pubkey(),
});
@@ -280,7 +281,10 @@ export default class SAS extends Base {
}
async _doRespondVerification() {
let content = this.startEvent.getContent();
// as m.related_to is not included in the encrypted content in e2e rooms,
// we need to make sure it is added
let content = this._contentFromEventWithTxnId(this.startEvent);
// Note: we intersect using our pre-made lists, rather than the sets,
// so that the result will be in our order of preference. Then
// fetching the first element from the array will give our preferred
@@ -306,7 +310,7 @@ export default class SAS extends Base {
const olmSAS = new global.Olm.SAS();
try {
const commitmentStr = olmSAS.get_pubkey() + anotherjson.stringify(content);
this._sendToDevice("m.key.verification.accept", {
this._send("m.key.verification.accept", {
key_agreement_protocol: keyAgreement,
hash: hashMethod,
message_authentication_code: macMethod,
@@ -320,7 +324,7 @@ export default class SAS extends Base {
// FIXME: make sure event is properly formed
content = e.getContent();
olmSAS.set_their_key(content.key);
this._sendToDevice("m.key.verification.key", {
this._send("m.key.verification.key", {
key: olmSAS.get_pubkey(),
});
@@ -354,22 +358,35 @@ export default class SAS extends Base {
}
_sendMAC(olmSAS, method) {
const keyId = `ed25519:${this._baseApis.deviceId}`;
const mac = {};
const keyList = [];
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
+ this._baseApis.getUserId() + this._baseApis.deviceId
+ this.userId + this.deviceId
+ this.transactionId;
mac[keyId] = olmSAS[macMethods[method]](
const deviceKeyId = `ed25519:${this._baseApis.deviceId}`;
mac[deviceKeyId] = olmSAS[macMethods[method]](
this._baseApis.getDeviceEd25519Key(),
baseInfo + keyId,
baseInfo + deviceKeyId,
);
keyList.push(deviceKeyId);
const crossSigningId = this._baseApis.getCrossSigningId();
if (crossSigningId) {
const crossSigningKeyId = `ed25519:${crossSigningId}`;
mac[crossSigningKeyId] = olmSAS[macMethods[method]](
crossSigningId,
baseInfo + crossSigningKeyId,
);
keyList.push(crossSigningKeyId);
}
const keys = olmSAS[macMethods[method]](
keyId,
keyList.sort().join(","),
baseInfo + "KEY_IDS",
);
this._sendToDevice("m.key.verification.mac", { mac, keys });
this._send("m.key.verification.mac", { mac, keys });
}
async _checkMAC(olmSAS, content, method) {
+59 -92
View File
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 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.
@@ -22,6 +23,7 @@ import Promise from 'bluebird';
const parseContentType = require('content-type').parse;
const utils = require("./utils");
import logger from './logger';
// we use our own implementation of setTimeout, so that if we get suspended in
// the middle of a /sync, we cancel the sync as soon as we awake, rather than
@@ -45,10 +47,15 @@ module.exports.PREFIX_R0 = "/_matrix/client/r0";
module.exports.PREFIX_UNSTABLE = "/_matrix/client/unstable";
/**
* URI path for the identity API
* URI path for v1 of the the identity API
*/
module.exports.PREFIX_IDENTITY_V1 = "/_matrix/identity/api/v1";
/**
* URI path for the v2 identity API
*/
module.exports.PREFIX_IDENTITY_V2 = "/_matrix/identity/v2";
/**
* URI path for the media repo API
*/
@@ -89,6 +96,13 @@ module.exports.MatrixHttpApi = function MatrixHttpApi(event_emitter, opts) {
};
module.exports.MatrixHttpApi.prototype = {
/**
* Sets the baase URL for the identity server
* @param {string} url The new base url
*/
setIdBaseUrl: function(url) {
this.opts.idBaseUrl = url;
},
/**
* Get the content repository url with query parameters.
@@ -101,7 +115,7 @@ module.exports.MatrixHttpApi.prototype = {
};
return {
base: this.opts.baseUrl,
path: "/_matrix/media/v1/upload",
path: "/_matrix/media/r0/upload",
params: params,
};
},
@@ -164,9 +178,21 @@ module.exports.MatrixHttpApi.prototype = {
const contentType = opts.type || file.type || 'application/octet-stream';
const fileName = opts.name || file.name;
// we used to recommend setting file.stream to the thing to upload on
// nodejs.
const body = file.stream ? file.stream : file;
// We used to recommend setting file.stream to the thing to upload on
// Node.js. As of 2019-06-11, this is still in widespread use in various
// clients, so we should preserve this for simple objects used in
// Node.js. File API objects (via either the File or Blob interfaces) in
// the browser now define a `stream` method, which leads to trouble
// here, so we also check the type of `stream`.
let body = file;
if (body.stream && typeof body.stream !== "function") {
logger.warn(
"Using `file.stream` as the content to upload. Future " +
"versions of the js-sdk will change this to expect `file` to " +
"be the content directly.",
);
body = body.stream;
}
// backwards-compatibility hacks where we used to do different things
// between browser and node.
@@ -175,7 +201,7 @@ module.exports.MatrixHttpApi.prototype = {
if (global.XMLHttpRequest) {
rawResponse = false;
} else {
console.warn(
logger.warn(
"Returning the raw JSON from uploadContent(). Future " +
"versions of the js-sdk will change this default, to " +
"return the parsed object. Set opts.rawResponse=false " +
@@ -188,7 +214,7 @@ module.exports.MatrixHttpApi.prototype = {
let onlyContentUri = opts.onlyContentUri;
if (!rawResponse && onlyContentUri === undefined) {
if (global.XMLHttpRequest) {
console.warn(
logger.warn(
"Returning only the content-uri from uploadContent(). " +
"Future versions of the js-sdk will change this " +
"default, to return the whole response object. Set " +
@@ -278,7 +304,7 @@ module.exports.MatrixHttpApi.prototype = {
});
}
});
let url = this.opts.baseUrl + "/_matrix/media/v1/upload";
let url = this.opts.baseUrl + "/_matrix/media/r0/upload";
const queryArgs = [];
@@ -314,7 +340,7 @@ module.exports.MatrixHttpApi.prototype = {
promise = this.authedRequest(
opts.callback, "POST", "/upload", queryParams, body, {
prefix: "/_matrix/media/v1",
prefix: "/_matrix/media/r0",
headers: {"Content-Type": contentType},
json: false,
bodyParser: bodyParser,
@@ -355,7 +381,18 @@ module.exports.MatrixHttpApi.prototype = {
return this.uploads;
},
idServerRequest: function(callback, method, path, params, prefix) {
idServerRequest: function(
callback,
method,
path,
params,
prefix,
accessToken,
) {
if (!this.opts.idBaseUrl) {
throw new Error("No Identity Server base URL set");
}
const fullUri = this.opts.idBaseUrl + prefix + path;
if (callback !== undefined && !utils.isFunction(callback)) {
@@ -368,13 +405,17 @@ module.exports.MatrixHttpApi.prototype = {
uri: fullUri,
method: method,
withCredentials: false,
json: false,
json: true, // we want a JSON response if we can
_matrix_opts: this.opts,
headers: {},
};
if (method == 'GET') {
if (method === 'GET') {
opts.qs = params;
} else {
opts.form = params;
} else if (typeof params === "object") {
opts.json = params;
}
if (accessToken) {
opts.headers['Authorization'] = `Bearer ${accessToken}`;
}
const defer = Promise.defer();
@@ -382,12 +423,7 @@ module.exports.MatrixHttpApi.prototype = {
opts,
requestCallback(defer, callback, this.opts.onlyData),
);
// ID server does not always take JSON, so we can't use requests' 'json'
// option as we do with the home server, but it does return JSON, so
// parse it manually
return defer.promise.then(function(response) {
return JSON.parse(response);
});
return defer.promise;
},
/**
@@ -457,7 +493,7 @@ module.exports.MatrixHttpApi.prototype = {
const self = this;
requestPromise.catch(function(err) {
if (err.errcode == 'M_UNKNOWN_TOKEN') {
self.event_emitter.emit("Session.logged_out");
self.event_emitter.emit("Session.logged_out", err);
} else if (err.errcode == 'M_CONSENT_NOT_GIVEN') {
self.event_emitter.emit(
"no_consent",
@@ -512,76 +548,6 @@ module.exports.MatrixHttpApi.prototype = {
);
},
/**
* Perform an authorised request to the homeserver with a specific path
* prefix which overrides the default for this call only. Useful for hitting
* different Matrix Client-Server versions.
* @param {Function} callback Optional. The callback to invoke on
* success/failure. See the promise return values for more information.
* @param {string} method The HTTP method e.g. "GET".
* @param {string} path The HTTP path <b>after</b> the supplied prefix e.g.
* "/createRoom".
* @param {Object} queryParams A dict of query params (these will NOT be
* urlencoded).
* @param {Object} data The HTTP JSON body.
* @param {string} prefix The full prefix to use e.g.
* "/_matrix/client/v2_alpha".
* @param {Number=} localTimeoutMs The maximum amount of time to wait before
* timing out the request. If not specified, there is no timeout.
* @return {module:client.Promise} Resolves to <code>{data: {Object},
* headers: {Object}, code: {Number}}</code>.
* If <code>onlyData</code> is set, this will resolve to the <code>data</code>
* object only.
* @return {module:http-api.MatrixError} Rejects with an error if a problem
* occurred. This includes network problems and Matrix-specific error JSON.
*
* @deprecated prefer authedRequest with opts.prefix
*/
authedRequestWithPrefix: function(callback, method, path, queryParams, data,
prefix, localTimeoutMs) {
return this.authedRequest(
callback, method, path, queryParams, data, {
localTimeoutMs: localTimeoutMs,
prefix: prefix,
},
);
},
/**
* Perform a request to the homeserver without any credentials but with a
* specific path prefix which overrides the default for this call only.
* Useful for hitting different Matrix Client-Server versions.
* @param {Function} callback Optional. The callback to invoke on
* success/failure. See the promise return values for more information.
* @param {string} method The HTTP method e.g. "GET".
* @param {string} path The HTTP path <b>after</b> the supplied prefix e.g.
* "/createRoom".
* @param {Object} queryParams A dict of query params (these will NOT be
* urlencoded).
* @param {Object} data The HTTP JSON body.
* @param {string} prefix The full prefix to use e.g.
* "/_matrix/client/v2_alpha".
* @param {Number=} localTimeoutMs The maximum amount of time to wait before
* timing out the request. If not specified, there is no timeout.
* @return {module:client.Promise} Resolves to <code>{data: {Object},
* headers: {Object}, code: {Number}}</code>.
* If <code>onlyData</code> is set, this will resolve to the <code>data</code>
* object only.
* @return {module:http-api.MatrixError} Rejects with an error if a problem
* occurred. This includes network problems and Matrix-specific error JSON.
*
* @deprecated prefer request with opts.prefix
*/
requestWithPrefix: function(callback, method, path, queryParams, data, prefix,
localTimeoutMs) {
return this.request(
callback, method, path, queryParams, data, {
localTimeoutMs: localTimeoutMs,
prefix: prefix,
},
);
},
/**
* Perform a request to an arbitrary URL.
* @param {Function} callback Optional. The callback to invoke on
@@ -870,7 +836,8 @@ function parseErrorResponse(response, body) {
let err;
if (contentType) {
if (contentType.type === 'application/json') {
err = new module.exports.MatrixError(JSON.parse(body));
const jsonBody = typeof(body) === 'object' ? body : JSON.parse(body);
err = new module.exports.MatrixError(jsonBody);
} else if (contentType.type === 'text/plain') {
err = new Error(`Server returned ${httpStatus} error: ${body}`);
}
+73 -20
View File
@@ -22,6 +22,7 @@ import Promise from 'bluebird';
const url = require("url");
const utils = require("./utils");
import logger from './logger';
const EMAIL_STAGE_TYPE = "m.login.email.identity";
const MSISDN_STAGE_TYPE = "m.login.msisdn";
@@ -48,11 +49,18 @@ const MSISDN_STAGE_TYPE = "m.login.msisdn";
* @param {object?} opts.authData error response from the last request. If
* null, a request will be made with no auth before starting.
*
* @param {function(object?, bool?): module:client.Promise} opts.doRequest
* called with the new auth dict to submit the request and a flag set
* to true if this request is a background request. Should return a
* promise which resolves to the successful response or rejects with a
* MatrixError.
* @param {function(object?): module:client.Promise} opts.doRequest
* called with the new auth dict to submit the request. Also passes a
* second deprecated arg which is a flag set to true if this request
* is a background request. The busyChanged callback should be used
* instead of the backfround flag. Should return a promise which resolves
* to the successful response or rejects with a MatrixError.
*
* @param {function(bool): module:client.Promise} opts.busyChanged
* called whenever the interactive auth logic becomes busy submitting
* information provided by the user or finsihes. After this has been
* called with true the UI should indicate that a request is in progress
* until it is called again with false.
*
* @param {function(string, object?)} opts.stateUpdated
* called when the status of the UI auth changes, ie. when the state of
@@ -100,6 +108,7 @@ function InteractiveAuth(opts) {
this._matrixClient = opts.matrixClient;
this._data = opts.authData || {};
this._requestCallback = opts.doRequest;
this._busyChangedCallback = opts.busyChanged;
// startAuthStage included for backwards compat
this._stateUpdatedCallback = opts.stateUpdated || opts.startAuthStage;
this._resolveFunc = null;
@@ -111,9 +120,14 @@ function InteractiveAuth(opts) {
this._clientSecret = opts.clientSecret || this._matrixClient.generateClientSecret();
this._emailSid = opts.emailSid;
if (this._emailSid === undefined) this._emailSid = null;
this._requestingEmailToken = false;
this._chosenFlow = null;
this._currentStage = null;
// if we are currently trying to submit an auth dict (which includes polling)
// the promise the will resolve/reject when it completes
this._submitPromise = null;
}
InteractiveAuth.prototype = {
@@ -134,7 +148,10 @@ InteractiveAuth.prototype = {
// if we have no flows, try a request (we'll have
// just a session ID in _data if resuming)
if (!this._data.flows) {
this._doRequest(this._data);
if (this._busyChangedCallback) this._busyChangedCallback(true);
this._doRequest(this._data).finally(() => {
if (this._busyChangedCallback) this._busyChangedCallback(false);
});
} else {
this._startNextAuthStage();
}
@@ -146,24 +163,30 @@ InteractiveAuth.prototype = {
* completed out-of-band. If so, the attemptAuth promise will
* be resolved.
*/
poll: function() {
poll: async function() {
if (!this._data.session) return;
// if we currently have a request in flight, there's no point making
// another just to check what the status is
if (this._submitPromise) return;
let authDict = {};
if (this._currentStage == EMAIL_STAGE_TYPE) {
// The email can be validated out-of-band, but we need to provide the
// creds so the HS can go & check it.
if (this._emailSid) {
const idServerParsedUrl = url.parse(
this._matrixClient.getIdentityServerUrl(),
);
const creds = {
sid: this._emailSid,
client_secret: this._clientSecret,
};
if (await this._matrixClient.doesServerRequireIdServerParam()) {
const idServerParsedUrl = url.parse(
this._matrixClient.getIdentityServerUrl(),
);
creds.id_server = idServerParsedUrl.host;
}
authDict = {
type: EMAIL_STAGE_TYPE,
threepid_creds: {
sid: this._emailSid,
client_secret: this._clientSecret,
id_server: idServerParsedUrl.host,
},
threepid_creds: creds,
};
}
}
@@ -220,18 +243,44 @@ InteractiveAuth.prototype = {
* in the attemptAuth promise being rejected. This can be set to true
* for requests that just poll to see if auth has been completed elsewhere.
*/
submitAuthDict: function(authData, background) {
submitAuthDict: async function(authData, background) {
if (!this._resolveFunc) {
throw new Error("submitAuthDict() called before attemptAuth()");
}
if (!background && this._busyChangedCallback) {
this._busyChangedCallback(true);
}
// if we're currently trying a request, wait for it to finish
// as otherwise we can get multiple 200 responses which can mean
// things like multiple logins for register requests.
// (but discard any expections as we only care when its done,
// not whether it worked or not)
while (this._submitPromise) {
try {
await this._submitPromise;
} catch (e) {
}
}
// use the sessionid from the last request.
const auth = {
session: this._data.session,
};
utils.extend(auth, authData);
this._doRequest(auth, background);
try {
// NB. the 'background' flag is deprecated by the busyChanged
// callback and is here for backwards compat
this._submitPromise = this._doRequest(auth, background);
await this._submitPromise;
} finally {
this._submitPromise = null;
if (!background && this._busyChangedCallback) {
this._busyChangedCallback(false);
}
}
},
/**
@@ -283,7 +332,7 @@ InteractiveAuth.prototype = {
// We ignore all failures here (even non-UI auth related ones)
// since we don't want to suddenly fail if the internet connection
// had a blip whilst we were polling
console.log(
logger.log(
"Background poll request failed doing UI auth: ignoring",
error,
);
@@ -304,12 +353,14 @@ InteractiveAuth.prototype = {
if (
!this._emailSid &&
!this._requestingEmailToken &&
this._chosenFlow.stages.includes('m.login.email.identity')
) {
// If we've picked a flow with email auth, we send the email
// now because we want the request to fail as soon as possible
// if the email address is not valid (ie. already taken or not
// registered, depending on what the operation is).
this._requestingEmailToken = true;
try {
const requestTokenResult = await this._requestEmailTokenCallback(
this._inputs.emailAddress,
@@ -332,6 +383,8 @@ InteractiveAuth.prototype = {
// the failure up as the user can't complete auth if we can't
// send the email, foe whatever reason.
this._rejectFunc(e);
} finally {
this._requestingEmailToken = false;
}
}
}
@@ -383,9 +436,9 @@ InteractiveAuth.prototype = {
if (this._chosenFlow === null) {
this._chosenFlow = this._chooseFlow();
}
console.log("Active flow => %s", JSON.stringify(this._chosenFlow));
logger.log("Active flow => %s", JSON.stringify(this._chosenFlow));
const nextStage = this._firstUncompletedStage(this._chosenFlow);
console.log("Next stage: %s", nextStage);
logger.log("Next stage: %s", nextStage);
return nextStage;
},
+2
View File
@@ -1,6 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2019 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.
@@ -76,6 +77,7 @@ module.exports.InteractiveAuth = require("./interactive-auth");
/** The {@link module:auto-discovery|AutoDiscovery} class. */
module.exports.AutoDiscovery = require("./autodiscovery").AutoDiscovery;
module.exports.SERVICE_TYPES = require('./service-types').SERVICE_TYPES;
module.exports.MemoryCryptoStore =
require("./crypto/store/memory-crypto-store").default;
+33 -11
View File
@@ -20,6 +20,8 @@ limitations under the License.
const EventEmitter = require("events").EventEmitter;
const utils = require("../utils");
const EventTimeline = require("./event-timeline");
import {EventStatus} from "./event";
import logger from '../logger';
import Relations from './relations';
// var DEBUG = false;
@@ -28,7 +30,7 @@ const DEBUG = true;
let debuglog;
if (DEBUG) {
// using bind means that we get to keep useful line numbers in the console
debuglog = console.log.bind(console);
debuglog = logger.log.bind(logger);
} else {
debuglog = function() {};
}
@@ -90,6 +92,13 @@ function EventTimelineSet(room, opts) {
}
utils.inherits(EventTimelineSet, EventEmitter);
/**
* Get all the timelines in this set
* @return {module:models/event-timeline~EventTimeline[]} the timelines in this set
*/
EventTimelineSet.prototype.getTimelines = function() {
return this._timelines;
};
/**
* Get the filter object this timeline set is filtered on, if any
* @return {?Filter} the optional filter for this timelineSet
@@ -422,7 +431,7 @@ EventTimelineSet.prototype.addEventsToTimeline = function(events, toStartOfTimel
}
// time to join the timelines.
console.info("Already have timeline for " + eventId +
logger.info("Already have timeline for " + eventId +
" - joining timeline " + timeline + " to " +
existingTimeline);
@@ -436,15 +445,14 @@ EventTimelineSet.prototype.addEventsToTimeline = function(events, toStartOfTimel
if (backwardsIsLive || forwardsIsLive) {
// The live timeline should never be spliced into a non-live position.
// We use independent logging to better discover the problem at a glance.
console.warn({backwardsIsLive, forwardsIsLive}); // debugging
if (backwardsIsLive) {
console.warn(
logger.warn(
"Refusing to set a preceding existingTimeLine on our " +
"timeline as the existingTimeLine is live (" + existingTimeline + ")",
);
}
if (forwardsIsLive) {
console.warn(
logger.warn(
"Refusing to set our preceding timeline on a existingTimeLine " +
"as our timeline is live (" + timeline + ")",
);
@@ -464,8 +472,8 @@ EventTimelineSet.prototype.addEventsToTimeline = function(events, toStartOfTimel
// timeline we ended up on.
if (lastEventWasNew || !didUpdate) {
if (direction === EventTimeline.FORWARDS && timeline === this._liveTimeline) {
console.warn({lastEventWasNew, didUpdate}); // for debugging
console.warn(
logger.warn({lastEventWasNew, didUpdate}); // for debugging
logger.warn(
`Refusing to set forwards pagination token of live timeline ` +
`${timeline} to ${paginationToken}`,
);
@@ -687,9 +695,12 @@ EventTimelineSet.prototype.compareEventOrdering = function(eventId1, eventId2) {
* The type of relation involved, such as "m.annotation", "m.reference", "m.replace", etc.
* @param {String} eventType
* The relation event's type, such as "m.reaction", etc.
* @throws If <code>eventId</code>, <code>relationType</code> or <code>eventType</code>
* are not valid.
*
* @returns {Relations}
* A container for relation events.
* @returns {?Relations}
* A container for relation events or undefined if there are no relation events for
* the relationType.
*/
EventTimelineSet.prototype.getRelationsForEvent = function(
eventId, relationType, eventType,
@@ -748,6 +759,10 @@ EventTimelineSet.prototype.aggregateRelations = function(event) {
return;
}
if (event.isRedacted() || event.status === EventStatus.CANCELLED) {
return;
}
// If the event is currently encrypted, wait until it has been decrypted.
if (event.isBeingDecrypted()) {
event.once("Event.decrypted", () => {
@@ -777,20 +792,27 @@ EventTimelineSet.prototype.aggregateRelations = function(event) {
}
let relationsWithEventType = relationsWithRelType[eventType];
let isNewRelations = false;
let relatesToEvent;
if (!relationsWithEventType) {
relationsWithEventType = relationsWithRelType[eventType] = new Relations(
relationType,
eventType,
this.room,
);
const relatesToEvent = this.findEventById(relatesToEventId);
isNewRelations = true;
relatesToEvent = this.findEventById(relatesToEventId);
if (relatesToEvent) {
relationsWithEventType.setTargetEvent(relatesToEvent);
relatesToEvent.emit("Event.relationsCreated", relationType, eventType);
}
}
relationsWithEventType.addEvent(event);
// only emit once event has been added to relations
if (isNewRelations && relatesToEvent) {
relatesToEvent.emit("Event.relationsCreated", relationType, eventType);
}
};
/**
+195 -13
View File
@@ -24,13 +24,14 @@ limitations under the License.
import Promise from 'bluebird';
import {EventEmitter} from 'events';
import utils from '../utils.js';
import logger from '../logger';
/**
* Enum for event statuses.
* @readonly
* @enum {string}
*/
module.exports.EventStatus = {
const EventStatus = {
/** The event was not sent and will no longer be retried. */
NOT_SENT: "not_sent",
@@ -48,6 +49,7 @@ module.exports.EventStatus = {
/** The event was cancelled before it was successfully sent. */
CANCELLED: "cancelled",
};
module.exports.EventStatus = EventStatus;
const interns = {};
function intern(str) {
@@ -123,6 +125,8 @@ module.exports.MatrixEvent = function MatrixEvent(
this.forwardLooking = true;
this._pushActions = null;
this._replacingEvent = null;
this._localRedactionEvent = null;
this._isCancelled = false;
this._clearEvent = {};
@@ -227,6 +231,9 @@ utils.extend(module.exports.MatrixEvent.prototype, {
* @return {Object} The event content JSON, or an empty object.
*/
getOriginalContent: function() {
if (this._localRedactionEvent) {
return {};
}
return this._clearEvent.content || this.event.content || {};
},
@@ -238,7 +245,9 @@ utils.extend(module.exports.MatrixEvent.prototype, {
* @return {Object} The event content JSON, or an empty object.
*/
getContent: function() {
if (this._replacingEvent) {
if (this._localRedactionEvent) {
return {};
} else if (this._replacingEvent) {
return this._replacingEvent.getContent()["m.new_content"] || {};
} else {
return this.getOriginalContent();
@@ -288,6 +297,15 @@ utils.extend(module.exports.MatrixEvent.prototype, {
return this.getUnsigned().age || this.event.age; // v2 / v1
},
/**
* Get the age of the event when this function was called.
* Relies on the local clock being in sync with the clock of the original homeserver.
* @return {Number} The age of this event in milliseconds.
*/
getLocalAge: function() {
return Date.now() - this.getTs();
},
/**
* Get the event state_key if it has one. This will return <code>undefined
* </code> for message events.
@@ -395,7 +413,7 @@ utils.extend(module.exports.MatrixEvent.prototype, {
// new info.
//
if (this._decryptionPromise) {
console.log(
logger.log(
`Event ${this.getId()} already being decrypted; queueing a retry`,
);
this._retryDecryption = true;
@@ -469,7 +487,7 @@ utils.extend(module.exports.MatrixEvent.prototype, {
if (e.name !== "DecryptionError") {
// not a decryption error: log the whole exception as an error
// (and don't bother with a retry)
console.error(
logger.error(
`Error decrypting event (id=${this.getId()}): ${e.stack || e}`,
);
this._decryptionPromise = null;
@@ -495,7 +513,7 @@ utils.extend(module.exports.MatrixEvent.prototype, {
//
if (this._retryDecryption) {
// decryption error, but we have a retry queued.
console.log(
logger.log(
`Got error decrypting event (id=${this.getId()}: ` +
`${e}), but retrying`,
);
@@ -504,7 +522,7 @@ utils.extend(module.exports.MatrixEvent.prototype, {
// decryption error, no retries queued. Warn about the error and
// set it to m.bad.encrypted.
console.warn(
logger.warn(
`Error decrypting event (id=${this.getId()}): ${e.detailedString}`,
);
@@ -665,6 +683,27 @@ utils.extend(module.exports.MatrixEvent.prototype, {
return this.event.unsigned || {};
},
unmarkLocallyRedacted: function() {
const value = this._localRedactionEvent;
this._localRedactionEvent = null;
if (this.event.unsigned) {
this.event.unsigned.redacted_because = null;
}
return !!value;
},
markLocallyRedacted: function(redactionEvent) {
if (this._localRedactionEvent) {
return;
}
this.emit("Event.beforeRedaction", this, redactionEvent);
this._localRedactionEvent = redactionEvent;
if (!this.event.unsigned) {
this.event.unsigned = {};
}
this.event.unsigned.redacted_because = redactionEvent.event;
},
/**
* Update the content of an event in the same way it would be by the server
* if it were redacted before it was sent to us
@@ -678,6 +717,8 @@ utils.extend(module.exports.MatrixEvent.prototype, {
throw new Error("invalid redaction_event in makeRedacted");
}
this._localRedactionEvent = null;
this.emit("Event.beforeRedaction", this, redaction_event);
this._replacingEvent = null;
@@ -723,6 +764,15 @@ utils.extend(module.exports.MatrixEvent.prototype, {
return Boolean(this.getUnsigned().redacted_because);
},
/**
* Check if this event is a redaction of another event
*
* @return {boolean} True if this event is a redaction
*/
isRedaction: function() {
return this.getType() === "m.room.redaction";
},
/**
* Get the push actions, if known, for this event
*
@@ -746,9 +796,26 @@ utils.extend(module.exports.MatrixEvent.prototype, {
* @param {Object} event the object to assign to the `event` property
*/
handleRemoteEcho: function(event) {
const oldUnsigned = this.getUnsigned();
const oldId = this.getId();
this.event = event;
// if this event was redacted before it was sent, it's locally marked as redacted.
// At this point, we've received the remote echo for the event, but not yet for
// the redaction that we are sending ourselves. Preserve the locally redacted
// state by copying over redacted_because so we don't get a flash of
// redacted, not-redacted, redacted as remote echos come in
if (oldUnsigned.redacted_because) {
if (!this.event.unsigned) {
this.event.unsigned = {};
}
this.event.unsigned.redacted_because = oldUnsigned.redacted_because;
}
// successfully sent.
this.setStatus(null);
if (this.getId() !== oldId) {
// emit the event if it changed
this.emit("Event.localEventIdReplaced", this);
}
},
/**
@@ -771,6 +838,11 @@ utils.extend(module.exports.MatrixEvent.prototype, {
this.emit("Event.status", this, status);
},
replaceLocalEventId(eventId) {
this.event.event_id = eventId;
this.emit("Event.localEventIdReplaced", this);
},
/**
* Get whether the event is a relation event, and of a given type if
* `relType` is passed in.
@@ -806,7 +878,11 @@ utils.extend(module.exports.MatrixEvent.prototype, {
* @param {MatrixEvent?} newEvent the event with the replacing content, if any.
*/
makeReplaced(newEvent) {
if (this.isRedacted()) {
// don't allow redacted events to be replaced.
// if newEvent is null we allow to go through though,
// as with local redaction, the replacing event might get
// cancelled, which should be reflected on the target event.
if (this.isRedacted() && newEvent) {
return;
}
if (this._replacingEvent !== newEvent) {
@@ -816,15 +892,25 @@ utils.extend(module.exports.MatrixEvent.prototype, {
},
/**
* Returns the status of the event, or the replacing event in case `makeReplace` has been called.
* Returns the status of any associated edit or redaction
* (not for reactions/annotations as their local echo doesn't affect the orignal event),
* or else the status of the event.
*
* @return {EventStatus}
*/
replacementOrOwnStatus() {
getAssociatedStatus() {
if (this._replacingEvent) {
return this._replacingEvent.status;
} else {
return this.status;
} else if (this._localRedactionEvent) {
return this._localRedactionEvent.status;
}
return this.status;
},
getServerAggregatedRelation(relType) {
const relations = this.getUnsigned()["m.relations"];
if (relations) {
return relations[relType];
}
},
@@ -834,11 +920,18 @@ utils.extend(module.exports.MatrixEvent.prototype, {
* @return {string?}
*/
replacingEventId() {
return this._replacingEvent && this._replacingEvent.getId();
const replaceRelation = this.getServerAggregatedRelation("m.replace");
if (replaceRelation) {
return replaceRelation.event_id;
} else if (this._replacingEvent) {
return this._replacingEvent.getId();
}
},
/**
* Returns the event replacing the content of this event, if any.
* Replacements are aggregated on the server, so this would only
* return an event in case it came down the sync, or for local echo of edits.
*
* @return {MatrixEvent?}
*/
@@ -846,6 +939,90 @@ utils.extend(module.exports.MatrixEvent.prototype, {
return this._replacingEvent;
},
/**
* Returns the origin_server_ts of the event replacing the content of this event, if any.
*
* @return {Date?}
*/
replacingEventDate() {
const replaceRelation = this.getServerAggregatedRelation("m.replace");
if (replaceRelation) {
const ts = replaceRelation.origin_server_ts;
if (Number.isFinite(ts)) {
return new Date(ts);
}
} else if (this._replacingEvent) {
return this._replacingEvent.getDate();
}
},
/**
* Returns the event that wants to redact this event, but hasn't been sent yet.
* @return {MatrixEvent} the event
*/
localRedactionEvent() {
return this._localRedactionEvent;
},
/**
* For relations and redactions, returns the event_id this event is referring to.
*
* @return {string?}
*/
getAssociatedId() {
const relation = this.getRelation();
if (relation) {
return relation.event_id;
} else if (this.isRedaction()) {
return this.event.redacts;
}
},
/**
* Checks if this event is associated with another event. See `getAssociatedId`.
*
* @return {bool}
*/
hasAssocation() {
return !!this.getAssociatedId();
},
/**
* Update the related id with a new one.
*
* Used to replace a local id with remote one before sending
* an event with a related id.
*
* @param {string} eventId the new event id
*/
updateAssociatedId(eventId) {
const relation = this.getRelation();
if (relation) {
relation.event_id = eventId;
} else if (this.isRedaction()) {
this.event.redacts = eventId;
}
},
/**
* Flags an event as cancelled due to future conditions. For example, a verification
* request event in the same sync transaction may be flagged as cancelled to warn
* listeners that a cancellation event is coming down the same pipe shortly.
* @param {boolean} cancelled Whether the event is to be cancelled or not.
*/
flagCancelled(cancelled = true) {
this._isCancelled = cancelled;
},
/**
* Gets whether or not the event is flagged as cancelled. See flagCancelled() for
* more information.
* @returns {boolean} True if the event is cancelled, false otherwise.
*/
isCancelled() {
return this._isCancelled;
},
/**
* Summarise the event as JSON for debugging. If encrypted, include both the
* decrypted and encrypted view of the event. This is named `toJSON` for use
@@ -865,6 +1042,11 @@ utils.extend(module.exports.MatrixEvent.prototype, {
room_id: this.getRoomId(),
};
// if this is a redaction then attach the redacts key
if (this.isRedaction()) {
event.redacts = this.event.redacts;
}
if (!this.isEncrypted()) {
return event;
}
@@ -880,7 +1062,7 @@ utils.extend(module.exports.MatrixEvent.prototype, {
/* _REDACT_KEEP_KEY_MAP gives the keys we keep when an event is redacted
*
* This is specified here:
* http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#redactions
* http://matrix.org/speculator/spec/HEAD/client_server/latest.html#redactions
*
* Also:
* - We keep 'unsigned' since that is created by the local server
+12 -7
View File
@@ -15,7 +15,7 @@ limitations under the License.
*/
import EventEmitter from 'events';
import { EventStatus } from '../../lib/models/event';
import { EventStatus } from '../models/event';
/**
* A container for relation events that supports easy access to common ways of
@@ -242,12 +242,7 @@ export default class Relations extends EventEmitter {
redactedEvent.removeListener("Event.beforeRedaction", this._onBeforeRedaction);
// Dispatch a redaction event on this collection. `setTimeout` is used
// to wait until the next event loop iteration by which time the event
// has actually been marked as redacted.
setTimeout(() => {
this.emit("Relations.redaction");
}, 0);
this.emit("Relations.redaction", redactedEvent);
}
/**
@@ -306,10 +301,20 @@ export default class Relations extends EventEmitter {
// event is known anyway.
return null;
}
// the all-knowning server tells us that the event at some point had
// this timestamp for its replacement, so any following replacement should definitely not be less
const replaceRelation =
this._targetEvent.getServerAggregatedRelation("m.replace");
const minTs = replaceRelation && replaceRelation.origin_server_ts;
return this.getRelations().reduce((last, event) => {
if (event.getSender() !== this._targetEvent.getSender()) {
return last;
}
if (minTs && minTs > event.getTs()) {
return last;
}
if (last && last.getTs() > event.getTs()) {
return last;
}
+1 -1
View File
@@ -249,7 +249,7 @@ RoomMember.prototype.getDMInviter = function() {
* "crop" or "scale".
* @param {Boolean} allowDefault (optional) Passing false causes this method to
* return null if the user has no avatar image. Otherwise, a default image URL
* will be returned. Default: true.
* will be returned. Default: true. (Deprecated)
* @param {Boolean} allowDirectLinks (optional) If true, the avatar URL will be
* returned even if it is a direct hyperlink rather than a matrix content URL.
* If false, any non-matrix content URLs will be ignored. Setting this option to
+4 -3
View File
@@ -21,6 +21,7 @@ const EventEmitter = require("events").EventEmitter;
const utils = require("../utils");
const RoomMember = require("./room-member");
import logger from '../logger';
// possible statuses for out-of-band member loading
const OOB_STATUS_NOTSTARTED = 1;
@@ -447,7 +448,7 @@ RoomState.prototype.clearOutOfBandMembers = function() {
delete this.members[userId];
}
});
console.log(`LL: RoomState removed ${count} members...`);
logger.log(`LL: RoomState removed ${count} members...`);
this._oobMemberFlags.status = OOB_STATUS_NOTSTARTED;
};
@@ -456,11 +457,11 @@ RoomState.prototype.clearOutOfBandMembers = function() {
* @param {MatrixEvent[]} stateEvents array of membership state events
*/
RoomState.prototype.setOutOfBandMembers = function(stateEvents) {
console.log(`LL: RoomState about to set ${stateEvents.length} OOB members ...`);
logger.log(`LL: RoomState about to set ${stateEvents.length} OOB members ...`);
if (this._oobMemberFlags.status !== OOB_STATUS_INPROGRESS) {
return;
}
console.log(`LL: RoomState put in OOB_STATUS_FINISHED state ...`);
logger.log(`LL: RoomState put in OOB_STATUS_FINISHED state ...`);
this._oobMemberFlags.status = OOB_STATUS_FINISHED;
stateEvents.forEach((e) => this._setOutOfBandMember(e));
};
+214 -70
View File
@@ -1,6 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 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.
@@ -29,6 +30,7 @@ const ContentRepo = require("../content-repo");
const EventTimeline = require("./event-timeline");
const EventTimelineSet = require("./event-timeline-set");
import logger from '../logger';
import ReEmitter from '../ReEmitter';
// These constants are used as sane defaults when the homeserver doesn't support
@@ -37,8 +39,8 @@ import ReEmitter from '../ReEmitter';
// room versions which are considered okay for people to run without being asked
// to upgrade (ie: "stable"). Eventually, we should remove these when all homeservers
// return an m.room_versions capability.
const KNOWN_SAFE_ROOM_VERSION = '1';
const SAFE_ROOM_VERSIONS = ['1', '2', '3'];
const KNOWN_SAFE_ROOM_VERSION = '4';
const SAFE_ROOM_VERSIONS = ['1', '2', '3', '4'];
function synthesizeReceipt(userId, event, receiptType) {
// console.log("synthesizing receipt for "+event.getId());
@@ -209,7 +211,7 @@ utils.inherits(Room, EventEmitter);
Room.prototype.getVersion = function() {
const createEvent = this.currentState.getStateEvents("m.room.create", "");
if (!createEvent) {
console.warn("Room " + this.room_id + " does not have an m.room.create event");
logger.warn("Room " + this.room_id + " does not have an m.room.create event");
return '1';
}
const ver = createEvent.getContent()['room_version'];
@@ -262,9 +264,36 @@ Room.prototype.getRecommendedVersion = async function() {
}
}
let result = this._checkVersionAgainstCapability(versionCap);
if (result.urgent && result.needsUpgrade) {
// Something doesn't feel right: we shouldn't need to update
// because the version we're on should be in the protocol's
// namespace. This usually means that the server was updated
// before the client was, making us think the newest possible
// room version is not stable. As a solution, we'll refresh
// the capability we're using to determine this.
logger.warn(
"Refreshing room version capability because the server looks " +
"to be supporting a newer room version we don't know about.",
);
const caps = await this._client.getCapabilities(true);
versionCap = caps["m.room_versions"];
if (!versionCap) {
logger.warn("No room version capability - assuming upgrade required.");
return result;
} else {
result = this._checkVersionAgainstCapability(versionCap);
}
}
return result;
};
Room.prototype._checkVersionAgainstCapability = function(versionCap) {
const currentVersion = this.getVersion();
console.log(`[${this.roomId}] Current version: ${currentVersion}`);
console.log(`[${this.roomId}] Version capability: `, versionCap);
logger.log(`[${this.roomId}] Current version: ${currentVersion}`);
logger.log(`[${this.roomId}] Version capability: `, versionCap);
const result = {
version: currentVersion,
@@ -273,7 +302,7 @@ Room.prototype.getRecommendedVersion = async function() {
};
// If the room is on the default version then nothing needs to change
if (currentVersion === versionCap.default) return Promise.resolve(result);
if (currentVersion === versionCap.default) return result;
const stableVersions = Object.keys(versionCap.available)
.filter((v) => versionCap.available[v] === 'stable');
@@ -286,16 +315,16 @@ Room.prototype.getRecommendedVersion = async function() {
result.needsUpgrade = true;
result.urgent = !!this.getVersion().match(/^[0-9]+[0-9.]*$/g);
if (result.urgent) {
console.warn(`URGENT upgrade required on ${this.roomId}`);
logger.warn(`URGENT upgrade required on ${this.roomId}`);
} else {
console.warn(`Non-urgent upgrade required on ${this.roomId}`);
logger.warn(`Non-urgent upgrade required on ${this.roomId}`);
}
return Promise.resolve(result);
return result;
}
// The room is on a stable, but non-default, version by this point.
// No upgrade needed.
return Promise.resolve(result);
return result;
};
/**
@@ -318,13 +347,30 @@ Room.prototype.userMayUpgradeRoom = function(userId) {
Room.prototype.getPendingEvents = function() {
if (this._opts.pendingEventOrdering !== "detached") {
throw new Error(
"Cannot call getPendingEventList with pendingEventOrdering == " +
"Cannot call getPendingEvents with pendingEventOrdering == " +
this._opts.pendingEventOrdering);
}
return this._pendingEventList;
};
/**
* Check whether the pending event list contains a given event by ID.
*
* @param {string} eventId The event ID to check for.
* @return {boolean}
* @throws If <code>opts.pendingEventOrdering</code> was not 'detached'
*/
Room.prototype.hasPendingEvent = function(eventId) {
if (this._opts.pendingEventOrdering !== "detached") {
throw new Error(
"Cannot call hasPendingEvent with pendingEventOrdering == " +
this._opts.pendingEventOrdering);
}
return this._pendingEventList.some(event => event.getId() === eventId);
};
/**
* Get the live unfiltered timeline for this room.
*
@@ -334,6 +380,23 @@ Room.prototype.getLiveTimeline = function() {
return this.getUnfilteredTimelineSet().getLiveTimeline();
};
/**
* Get the timestamp of the last message in the room
*
* @return {number} the timestamp of the last message in the room
*/
Room.prototype.getLastActiveTimestamp = function() {
const timeline = this.getLiveTimeline();
const events = timeline.getEvents();
if (events.length) {
const lastEvent = events[events.length - 1];
return lastEvent.getTs();
} else {
return Number.MIN_SAFE_INTEGER;
}
};
/**
* @param {string} myUserId the user id for the logged in member
* @return {string} the membership type (join | leave | invite) for the logged in user
@@ -471,7 +534,7 @@ Room.prototype._loadMembers = async function() {
if (rawMembersEvents === null) {
fromServer = true;
rawMembersEvents = await this._loadMembersFromServer();
console.log(`LL: got ${rawMembersEvents.length} ` +
logger.log(`LL: got ${rawMembersEvents.length} ` +
`members from server for room ${this.roomId}`);
}
const memberEvents = rawMembersEvents.map(this._client.getEventMapper());
@@ -515,21 +578,21 @@ Room.prototype.loadMembersIfNeeded = function() {
const oobMembers = this.currentState.getMembers()
.filter((m) => m.isOutOfBand())
.map((m) => m.events.member.event);
console.log(`LL: telling store to write ${oobMembers.length}`
logger.log(`LL: telling store to write ${oobMembers.length}`
+ ` members for room ${this.roomId}`);
const store = this._client.store;
return store.setOutOfBandMembers(this.roomId, oobMembers)
// swallow any IDB error as we don't want to fail
// because of this
.catch((err) => {
console.log("LL: storing OOB room members failed, oh well",
logger.log("LL: storing OOB room members failed, oh well",
err);
});
}
}).catch((err) => {
// as this is not awaited anywhere,
// at least show the error in the console
console.error(err);
logger.error(err);
});
this._membersPromise = inMemoryUpdate;
@@ -555,9 +618,9 @@ Room.prototype.clearLoadedMembersIfNeeded = async function() {
*/
Room.prototype._cleanupAfterLeaving = function() {
this.clearLoadedMembersIfNeeded().catch((err) => {
console.error(`error after clearing loaded members from ` +
logger.error(`error after clearing loaded members from ` +
`room ${this.roomId} after leaving`);
console.dir(err);
logger.log(err);
});
};
@@ -735,7 +798,7 @@ Room.prototype.getBlacklistUnverifiedDevices = function() {
* @param {string} resizeMethod The thumbnail resize method to use, either
* "crop" or "scale".
* @param {boolean} allowDefault True to allow an identicon for this room if an
* avatar URL wasn't explicitly set. Default: true.
* avatar URL wasn't explicitly set. Default: true. (Deprecated)
* @return {?string} the avatar URL or null.
*/
Room.prototype.getAvatarUrl = function(baseUrl, width, height, resizeMethod,
@@ -769,20 +832,26 @@ Room.prototype.getAvatarUrl = function(baseUrl, width, height, resizeMethod,
* @return {array} The room's alias as an array of strings
*/
Room.prototype.getAliases = function() {
const alias_strings = [];
const aliasStrings = [];
const alias_events = this.currentState.getStateEvents("m.room.aliases");
if (alias_events) {
for (let i = 0; i < alias_events.length; ++i) {
const alias_event = alias_events[i];
if (utils.isArray(alias_event.getContent().aliases)) {
Array.prototype.push.apply(
alias_strings, alias_event.getContent().aliases,
);
const aliasEvents = this.currentState.getStateEvents("m.room.aliases");
if (aliasEvents) {
for (let i = 0; i < aliasEvents.length; ++i) {
const aliasEvent = aliasEvents[i];
if (utils.isArray(aliasEvent.getContent().aliases)) {
const filteredAliases = aliasEvent.getContent().aliases.filter(a => {
if (typeof(a) !== "string") return false;
if (a[0] !== '#') return false;
if (!a.endsWith(`:${aliasEvent.getStateKey()}`)) return false;
// It's probably valid by here.
return true;
});
Array.prototype.push.apply(aliasStrings, filteredAliases);
}
}
}
return alias_strings;
return aliasStrings;
};
/**
@@ -832,7 +901,7 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline,
* @return {RoomMember} The member or <code>null</code>.
*/
Room.prototype.getMember = function(userId) {
return this.currentState.getMember(userId);
return this.currentState.getMember(userId);
};
/**
@@ -840,7 +909,7 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline,
* @return {RoomMember[]} A list of currently joined members.
*/
Room.prototype.getJoinedMembers = function() {
return this.getMembersWithMembership("join");
return this.getMembersWithMembership("join");
};
/**
@@ -1004,13 +1073,25 @@ Room.prototype.removeFilteredTimelineSet = function(filter) {
* @private
*/
Room.prototype._addLiveEvent = function(event, duplicateStrategy) {
if (event.getType() === "m.room.redaction") {
if (event.isRedaction()) {
const redactId = event.event.redacts;
// if we know about this event, redact its contents now.
const redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId);
if (redactedEvent) {
redactedEvent.makeRedacted(event);
// If this is in the current state, replace it with the redacted version
if (redactedEvent.getStateKey()) {
const currentStateEvent = this.currentState.getStateEvents(
redactedEvent.getType(),
redactedEvent.getStateKey(),
);
if (currentStateEvent.getId() === redactedEvent.getId()) {
this.currentState.setStateEvents([redactedEvent]);
}
}
this.emit("Room.redaction", event, this);
// TODO: we stash user displaynames (among other things) in
@@ -1101,7 +1182,7 @@ Room.prototype.addPendingEvent = function(event, txnId) {
if (this._opts.pendingEventOrdering == "detached") {
if (this._pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) {
console.warn("Setting event as NOT_SENT due to messages in the same state");
logger.warn("Setting event as NOT_SENT due to messages in the same state");
event.setStatus(EventStatus.NOT_SENT);
}
this._pendingEventList.push(event);
@@ -1110,24 +1191,26 @@ Room.prototype.addPendingEvent = function(event, txnId) {
// For pending events, add them to the relations collection immediately.
// (The alternate case below already covers this as part of adding to
// the timeline set.)
// TODO: We should consider whether this means it would be a better
// design to lift the relations handling up to the room instead.
for (let i = 0; i < this._timelineSets.length; i++) {
const timelineSet = this._timelineSets[i];
if (timelineSet.getFilter()) {
if (this._filter.filterRoomTimeline([event]).length) {
timelineSet.aggregateRelations(event);
}
} else {
timelineSet.aggregateRelations(event);
}
this._aggregateNonLiveRelation(event);
}
if (event.isRedaction()) {
const redactId = event.event.redacts;
let redactedEvent = this._pendingEventList &&
this._pendingEventList.find(e => e.getId() === redactId);
if (!redactedEvent) {
redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId);
}
if (redactedEvent) {
redactedEvent.markLocallyRedacted(event);
this.emit("Room.redaction", event, this);
}
}
} else {
for (let i = 0; i < this._timelineSets.length; i++) {
const timelineSet = this._timelineSets[i];
if (timelineSet.getFilter()) {
if (this._filter.filterRoomTimeline([event]).length) {
if (timelineSet.getFilter().filterRoomTimeline([event]).length) {
timelineSet.addEventToTimeline(event,
timelineSet.getLiveTimeline(), false);
}
@@ -1140,6 +1223,30 @@ Room.prototype.addPendingEvent = function(event, txnId) {
this.emit("Room.localEchoUpdated", event, this, null, null);
};
/**
* Used to aggregate the local echo for a relation, and also
* for re-applying a relation after it's redaction has been cancelled,
* as the local echo for the redaction of the relation would have
* un-aggregated the relation. Note that this is different from regular messages,
* which are just kept detached for their local echo.
*
* Also note that live events are aggregated in the live EventTimelineSet.
* @param {module:models/event.MatrixEvent} event the relation event that needs to be aggregated.
*/
Room.prototype._aggregateNonLiveRelation = function(event) {
// TODO: We should consider whether this means it would be a better
// design to lift the relations handling up to the room instead.
for (let i = 0; i < this._timelineSets.length; i++) {
const timelineSet = this._timelineSets[i];
if (timelineSet.getFilter()) {
if (timelineSet.getFilter().filterRoomTimeline([event]).length) {
timelineSet.aggregateRelations(event);
}
} else {
timelineSet.aggregateRelations(event);
}
}
};
/**
* Deal with the echo of a message we sent.
@@ -1161,7 +1268,7 @@ Room.prototype._handleRemoteEcho = function(remoteEvent, localEvent) {
const oldStatus = localEvent.status;
// no longer pending
delete this._txnToEvent[remoteEvent.transaction_id];
delete this._txnToEvent[remoteEvent.getUnsigned().transaction_id];
// if it's in the pending list, remove it
if (this._pendingEventList) {
@@ -1229,7 +1336,7 @@ ALLOWED_TRANSITIONS[EventStatus.CANCELLED] =
* @fires module:client~MatrixClient#event:"Room.localEchoUpdated"
*/
Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
console.log(`setting pendingEvent status to ${newStatus} in ${event.getRoomId()}`);
logger.log(`setting pendingEvent status to ${newStatus} in ${event.getRoomId()}`);
// if the message was sent, we expect an event id
if (newStatus == EventStatus.SENT && !newEventId) {
@@ -1265,7 +1372,7 @@ Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
if (newStatus == EventStatus.SENT) {
// update the event id
event.event.event_id = newEventId;
event.replaceLocalEventId(newEventId);
// if the event was already in the timeline (which will be the case if
// opts.pendingEventOrdering==chronological), we need to update the
@@ -1276,12 +1383,13 @@ Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
} else if (newStatus == EventStatus.CANCELLED) {
// remove it from the pending event list, or the timeline.
if (this._pendingEventList) {
utils.removeElement(
this._pendingEventList,
function(ev) {
return ev.getId() == oldEventId;
}, false,
);
const idx = this._pendingEventList.findIndex(ev => ev.getId() === oldEventId);
if (idx !== -1) {
const [removedEvent] = this._pendingEventList.splice(idx, 1);
if (removedEvent.isRedaction()) {
this._revertRedactionLocalEcho(removedEvent);
}
}
}
this.removeEvent(oldEventId);
}
@@ -1289,6 +1397,23 @@ Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
this.emit("Room.localEchoUpdated", event, this, oldEventId, oldStatus);
};
Room.prototype._revertRedactionLocalEcho = function(redactionEvent) {
const redactId = redactionEvent.event.redacts;
if (!redactId) {
return;
}
const redactedEvent = this.getUnfilteredTimelineSet()
.findEventById(redactId);
if (redactedEvent) {
redactedEvent.unmarkLocallyRedacted();
// re-render after undoing redaction
this.emit("Room.redactionCancelled", redactionEvent, this);
// reapply relation now redaction failed
if (redactedEvent.isRelation()) {
this._aggregateNonLiveRelation(redactedEvent);
}
}
};
/**
* Add some events to this room. This can include state events, message
@@ -1330,28 +1455,33 @@ Room.prototype.addLiveEvents = function(events, duplicateStrategy) {
}
for (i = 0; i < events.length; i++) {
if (events[i].getType() === "m.typing") {
this.currentState.setTypingEvent(events[i]);
} else if (events[i].getType() === "m.receipt") {
this.addReceipt(events[i]);
}
// N.B. account_data is added directly by /sync to avoid
// having to maintain an event.isAccountData() here
else {
// TODO: We should have a filter to say "only add state event
// types X Y Z to the timeline".
this._addLiveEvent(events[i], duplicateStrategy);
}
// TODO: We should have a filter to say "only add state event
// types X Y Z to the timeline".
this._addLiveEvent(events[i], duplicateStrategy);
}
};
/**
* Adds/handles ephemeral events such as typing notifications and read receipts.
* @param {MatrixEvent[]} events A list of events to process
*/
Room.prototype.addEphemeralEvents = function(events) {
for (const event of events) {
if (event.getType() === 'm.typing') {
this.currentState.setTypingEvent(event);
} else if (event.getType() === 'm.receipt') {
this.addReceipt(event);
} // else ignore - life is too short for us to care about these events
}
};
/**
* Removes events from this room.
* @param {String[]} event_ids A list of event_ids to remove.
* @param {String[]} eventIds A list of eventIds to remove.
*/
Room.prototype.removeEvents = function(event_ids) {
for (let i = 0; i < event_ids.length; ++i) {
this.removeEvent(event_ids[i]);
Room.prototype.removeEvents = function(eventIds) {
for (let i = 0; i < eventIds.length; ++i) {
this.removeEvent(eventIds[i]);
}
};
@@ -1367,6 +1497,9 @@ Room.prototype.removeEvent = function(eventId) {
for (let i = 0; i < this._timelineSets.length; i++) {
const removed = this._timelineSets[i].removeEvent(eventId);
if (removed) {
if (removed.isRedaction()) {
this._revertRedactionLocalEcho(removed);
}
removedAny = true;
}
}
@@ -1790,10 +1923,21 @@ module.exports = Room;
* event).
*
* @event module:client~MatrixClient#"Room.redaction"
* @param {MatrixEvent} event The matrix event which was redacted
* @param {MatrixEvent} event The matrix redaction event
* @param {Room} room The room containing the redacted event
*/
/**
* Fires when an event that was previously redacted isn't anymore.
* This happens when the redaction couldn't be sent and
* was subsequently cancelled by the user. Redactions have a local echo
* which is undone in this scenario.
*
* @event module:client~MatrixClient#"Room.redactionCancelled"
* @param {MatrixEvent} event The matrix redaction event that was cancelled.
* @param {Room} room The room containing the unredacted event
*/
/**
* Fires whenever the name of a room is updated.
* @event module:client~MatrixClient#"Room.name"
+60 -7
View File
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {escapeRegExp, globToRegexp} from "./utils";
import {escapeRegExp, globToRegexp, isNullOrUndefined} from "./utils";
/**
* @module pushprocessor
@@ -43,6 +43,11 @@ const DEFAULT_OVERRIDE_RULES = [
key: "type",
pattern: "m.room.tombstone",
},
{
kind: "event_match",
key: "state_key",
pattern: "",
},
],
actions: [
"notify",
@@ -52,6 +57,22 @@ const DEFAULT_OVERRIDE_RULES = [
},
],
},
{
// For homeservers which don't support MSC2153 yet
rule_id: ".m.rule.reaction",
default: true,
enabled: true,
conditions: [
{
kind: "event_match",
key: "type",
pattern: "m.reaction",
},
],
actions: [
"dont_notify",
],
},
];
/**
@@ -247,7 +268,7 @@ function PushProcessor(client) {
}
const val = valueForDottedKey(cond.key, ev);
if (!val || typeof val != 'string') {
if (typeof val !== 'string') {
return false;
}
@@ -283,10 +304,10 @@ function PushProcessor(client) {
// special-case the first component to deal with encrypted messages
const firstPart = parts[0];
if (firstPart == 'content') {
if (firstPart === 'content') {
val = ev.getContent();
parts.shift();
} else if (firstPart == 'type') {
} else if (firstPart === 'type') {
val = ev.getType();
parts.shift();
} else {
@@ -295,11 +316,11 @@ function PushProcessor(client) {
}
while (parts.length > 0) {
const thispart = parts.shift();
if (!val[thispart]) {
const thisPart = parts.shift();
if (isNullOrUndefined(val[thisPart])) {
return null;
}
val = val[thispart];
val = val[thisPart];
}
return val;
};
@@ -439,6 +460,38 @@ PushProcessor.actionListToActionsObject = function(actionlist) {
return actionobj;
};
/**
* Rewrites conditions on a client's push rules to match the defaults
* where applicable. Useful for upgrading push rules to more strict
* conditions when the server is falling behind on defaults.
* @param {object} incomingRules The client's existing push rules
* @returns {object} The rewritten rules
*/
PushProcessor.rewriteDefaultRules = function(incomingRules) {
let newRules = JSON.parse(JSON.stringify(incomingRules)); // deep clone
// These lines are mostly to make the tests happy. We shouldn't run into these
// properties missing in practice.
if (!newRules) newRules = {};
if (!newRules.global) newRules.global = {};
if (!newRules.global.override) newRules.global.override = [];
// Fix default override rules
newRules.global.override = newRules.global.override.map(r => {
const defaultRule = DEFAULT_OVERRIDE_RULES.find(d => d.rule_id === r.rule_id);
if (!defaultRule) return r;
// Copy over the actions, default, and conditions. Don't touch the user's
// preference.
r.default = defaultRule.default;
r.conditions = defaultRule.conditions;
r.actions = defaultRule.actions;
return r;
});
return newRules;
};
/**
* @typedef {Object} PushAction
* @type {Object}
+4 -3
View File
@@ -24,6 +24,7 @@ limitations under the License.
*/
"use strict";
import logger from './logger';
// we schedule a callback at least this often, to check if we've missed out on
// some wall-clock time due to being suspended.
@@ -39,7 +40,7 @@ let _realCallbackKey;
// each is an object with keys [runAt, func, params, key].
const _callbackList = [];
// var debuglog = console.log.bind(console);
// var debuglog = logger.log.bind(logger);
const debuglog = function() {};
/**
@@ -47,7 +48,7 @@ const debuglog = function() {};
*
* Intended for use by the unit tests.
*
* @param {function} f function which should return a millisecond counter
* @param {function} [f] function which should return a millisecond counter
*
* @internal
*/
@@ -170,7 +171,7 @@ function _runCallbacks() {
try {
cb.func.apply(global, cb.params);
} catch (e) {
console.error("Uncaught exception in callback function",
logger.error("Uncaught exception in callback function",
e.stack || e);
}
}
+12 -3
View File
@@ -21,6 +21,7 @@ limitations under the License.
*/
const utils = require("./utils");
import Promise from 'bluebird';
import logger from './logger';
const DEBUG = false; // set true to enable console logging.
@@ -176,7 +177,8 @@ MatrixScheduler.RETRY_BACKOFF_RATELIMIT = function(event, attempts, err) {
* @see module:scheduler~queueAlgorithm
*/
MatrixScheduler.QUEUE_MESSAGES = function(event) {
if (event.getType() === "m.room.message") {
// enqueue messages or events that associate with another event (redactions and relations)
if (event.getType() === "m.room.message" || event.hasAssocation()) {
// put these events in the 'message' queue.
return "message";
}
@@ -219,7 +221,14 @@ function _processQueue(scheduler, queueName) {
);
// fire the process function and if it resolves, resolve the deferred. Else
// invoke the retry algorithm.
scheduler._procFn(obj.event).done(function(res) {
// First wait for a resolved promise, so the resolve handlers for
// the deferred of the previously sent event can run.
// This way enqueued relations/redactions to enqueued events can receive
// the remove id of their target before being sent.
Promise.resolve().then(() => {
return scheduler._procFn(obj.event);
}).then(function(res) {
// remove this from the queue
_removeNextEvent(scheduler, queueName);
debuglog("Queue '%s' sent event %s", queueName, obj.event.getId());
@@ -269,7 +278,7 @@ function _removeNextEvent(scheduler, queueName) {
function debuglog() {
if (DEBUG) {
console.log(...arguments);
logger.log(...arguments);
}
}
+20
View File
@@ -0,0 +1,20 @@
/*
Copyright 2019 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 const SERVICE_TYPES = Object.freeze({
IS: 'SERVICE_TYPE_IS', // An Identity Service
IM: 'SERVICE_TYPE_IM', // An Integration Manager
});
+22 -21
View File
@@ -19,6 +19,7 @@ import Promise from 'bluebird';
import SyncAccumulator from "../sync-accumulator";
import utils from "../utils";
import * as IndexedDBHelpers from "../indexeddb-helpers";
import logger from '../logger';
const VERSION = 3;
@@ -146,7 +147,7 @@ LocalIndexedDBStoreBackend.prototype = {
*/
connect: function() {
if (!this._disconnected) {
console.log(
logger.log(
`LocalIndexedDBStoreBackend.connect: already connected or connecting`,
);
return Promise.resolve();
@@ -154,14 +155,14 @@ LocalIndexedDBStoreBackend.prototype = {
this._disconnected = false;
console.log(
logger.log(
`LocalIndexedDBStoreBackend.connect: connecting...`,
);
const req = this.indexedDB.open(this._dbName, VERSION);
req.onupgradeneeded = (ev) => {
const db = ev.target.result;
const oldVersion = ev.oldVersion;
console.log(
logger.log(
`LocalIndexedDBStoreBackend.connect: upgrading from ${oldVersion}`,
);
if (oldVersion < 1) { // The database did not previously exist.
@@ -178,16 +179,16 @@ LocalIndexedDBStoreBackend.prototype = {
};
req.onblocked = () => {
console.log(
logger.log(
`can't yet open LocalIndexedDBStoreBackend because it is open elsewhere`,
);
};
console.log(
logger.log(
`LocalIndexedDBStoreBackend.connect: awaiting connection...`,
);
return reqAsEventPromise(req).then((ev) => {
console.log(
logger.log(
`LocalIndexedDBStoreBackend.connect: connected`,
);
this.db = ev.target.result;
@@ -215,7 +216,7 @@ LocalIndexedDBStoreBackend.prototype = {
this._loadAccountData(),
this._loadSyncData(),
]).then(([accountData, syncData]) => {
console.log(
logger.log(
`LocalIndexedDBStoreBackend: loaded initial data`,
);
this._syncAccumulator.accumulate({
@@ -273,7 +274,7 @@ LocalIndexedDBStoreBackend.prototype = {
reject(err);
};
}).then((events) => {
console.log(`LL: got ${events && events.length}` +
logger.log(`LL: got ${events && events.length}` +
` membershipEvents from storage for room ${roomId} ...`);
return events;
});
@@ -287,7 +288,7 @@ LocalIndexedDBStoreBackend.prototype = {
* @param {event[]} membershipEvents the membership events to store
*/
setOutOfBandMembers: async function(roomId, membershipEvents) {
console.log(`LL: backend about to store ${membershipEvents.length}` +
logger.log(`LL: backend about to store ${membershipEvents.length}` +
` members for ${roomId}`);
const tx = this.db.transaction(["oob_membership_events"], "readwrite");
const store = tx.objectStore("oob_membership_events");
@@ -306,7 +307,7 @@ LocalIndexedDBStoreBackend.prototype = {
};
store.put(markerObject);
await txnAsPromise(tx);
console.log(`LL: backend done storing for ${roomId}!`);
logger.log(`LL: backend done storing for ${roomId}!`);
},
clearOutOfBandMembers: async function(roomId) {
@@ -341,7 +342,7 @@ LocalIndexedDBStoreBackend.prototype = {
[roomId, maxStateKey],
);
console.log(`LL: Deleting all users + marker in storage for ` +
logger.log(`LL: Deleting all users + marker in storage for ` +
`room ${roomId}, with key range:`,
[roomId, minStateKey], [roomId, maxStateKey]);
await reqAsPromise(writeStore.delete(membersKeyRange));
@@ -354,11 +355,11 @@ LocalIndexedDBStoreBackend.prototype = {
*/
clearDatabase: function() {
return new Promise((resolve, reject) => {
console.log(`Removing indexeddb instance: ${this._dbName}`);
logger.log(`Removing indexeddb instance: ${this._dbName}`);
const req = this.indexedDB.deleteDatabase(this._dbName);
req.onblocked = () => {
console.log(
logger.log(
`can't yet delete indexeddb ${this._dbName}` +
` because it is open elsewhere`,
);
@@ -368,14 +369,14 @@ LocalIndexedDBStoreBackend.prototype = {
// in firefox, with indexedDB disabled, this fails with a
// DOMError. We treat this as non-fatal, so that we can still
// use the app.
console.warn(
logger.warn(
`unable to delete js-sdk store indexeddb: ${ev.target.error}`,
);
resolve();
};
req.onsuccess = () => {
console.log(`Removed indexeddb instance: ${this._dbName}`);
logger.log(`Removed indexeddb instance: ${this._dbName}`);
resolve();
};
});
@@ -434,7 +435,7 @@ LocalIndexedDBStoreBackend.prototype = {
* @return {Promise} Resolves if the data was persisted.
*/
_persistSyncData: function(nextBatch, roomsData, groupsData) {
console.log("Persisting sync data up to ", nextBatch);
logger.log("Persisting sync data up to ", nextBatch);
return Promise.try(() => {
const txn = this.db.transaction(["sync"], "readwrite");
const store = txn.objectStore("sync");
@@ -508,7 +509,7 @@ LocalIndexedDBStoreBackend.prototype = {
* @return {Promise<Object[]>} A list of raw global account events.
*/
_loadAccountData: function() {
console.log(
logger.log(
`LocalIndexedDBStoreBackend: loading account data...`,
);
return Promise.try(() => {
@@ -517,7 +518,7 @@ LocalIndexedDBStoreBackend.prototype = {
return selectQuery(store, undefined, (cursor) => {
return cursor.value;
}).then((result) => {
console.log(
logger.log(
`LocalIndexedDBStoreBackend: loaded account data`,
);
return result;
@@ -530,7 +531,7 @@ LocalIndexedDBStoreBackend.prototype = {
* @return {Promise<Object>} An object with "roomsData" and "nextBatch" keys.
*/
_loadSyncData: function() {
console.log(
logger.log(
`LocalIndexedDBStoreBackend: loading sync data...`,
);
return Promise.try(() => {
@@ -539,11 +540,11 @@ LocalIndexedDBStoreBackend.prototype = {
return selectQuery(store, undefined, (cursor) => {
return cursor.value;
}).then((results) => {
console.log(
logger.log(
`LocalIndexedDBStoreBackend: loaded sync data`,
);
if (results.length > 1) {
console.warn("loadSyncData: More than 1 sync row found.");
logger.warn("loadSyncData: More than 1 sync row found.");
}
return (results.length > 0 ? results[0] : {});
});
+7 -5
View File
@@ -16,6 +16,8 @@ limitations under the License.
*/
import Promise from 'bluebird';
import logger from '../logger';
import {defer} from '../utils';
/**
* An IndexedDB store backend where the actual backend sits in a web
@@ -140,7 +142,7 @@ RemoteIndexedDBStoreBackend.prototype = {
// tell the worker the db name.
this._startPromise = this._doCmd('_setupWorker', [this._dbName]).then(() => {
console.log("IndexedDB worker is ready");
logger.log("IndexedDB worker is ready");
});
}
return this._startPromise;
@@ -151,7 +153,7 @@ RemoteIndexedDBStoreBackend.prototype = {
// the promise automatically gets rejected
return Promise.resolve().then(() => {
const seq = this._nextSeq++;
const def = Promise.defer();
const def = defer();
this._inFlight[seq] = def;
@@ -170,13 +172,13 @@ RemoteIndexedDBStoreBackend.prototype = {
if (msg.command == 'cmd_success' || msg.command == 'cmd_fail') {
if (msg.seq === undefined) {
console.error("Got reply from worker with no seq");
logger.error("Got reply from worker with no seq");
return;
}
const def = this._inFlight[msg.seq];
if (def === undefined) {
console.error("Got reply for unknown seq " + msg.seq);
logger.error("Got reply for unknown seq " + msg.seq);
return;
}
delete this._inFlight[msg.seq];
@@ -189,7 +191,7 @@ RemoteIndexedDBStoreBackend.prototype = {
def.reject(error);
}
} else {
console.warn("Unrecognised message from worker: " + msg);
logger.warn("Unrecognised message from worker: " + msg);
}
},
};
+3 -2
View File
@@ -17,6 +17,7 @@ limitations under the License.
import Promise from 'bluebird';
import LocalIndexedDBStoreBackend from "./indexeddb-local-backend.js";
import logger from '../logger';
/**
* This class lives in the webworker and drives a LocalIndexedDBStoreBackend
@@ -129,8 +130,8 @@ class IndexedDBStoreWorker {
result: ret,
});
}, (err) => {
console.error("Error running command: "+msg.command);
console.error(err);
logger.error("Error running command: "+msg.command);
logger.error(err);
this.postMessage.call(null, {
command: 'cmd_fail',
seq: msg.seq,
+15 -12
View File
@@ -25,6 +25,7 @@ import LocalIndexedDBStoreBackend from "./indexeddb-local-backend.js";
import RemoteIndexedDBStoreBackend from "./indexeddb-remote-backend.js";
import User from "../models/user";
import {MatrixEvent} from "../models/event";
import logger from '../logger';
/**
* This is an internal module. See {@link IndexedDBStore} for the public class.
@@ -124,16 +125,16 @@ IndexedDBStore.exists = function(indexedDB, dbName) {
*/
IndexedDBStore.prototype.startup = function() {
if (this.startedUp) {
console.log(`IndexedDBStore.startup: already started`);
logger.log(`IndexedDBStore.startup: already started`);
return Promise.resolve();
}
console.log(`IndexedDBStore.startup: connecting to backend`);
logger.log(`IndexedDBStore.startup: connecting to backend`);
return this.backend.connect().then(() => {
console.log(`IndexedDBStore.startup: loading presence events`);
logger.log(`IndexedDBStore.startup: loading presence events`);
return this.backend.getUserPresenceEvents();
}).then((userPresenceEvents) => {
console.log(`IndexedDBStore.startup: processing presence events`);
logger.log(`IndexedDBStore.startup: processing presence events`);
userPresenceEvents.forEach(([userId, rawEvent]) => {
const u = new User(userId);
if (rawEvent) {
@@ -174,9 +175,9 @@ IndexedDBStore.prototype.getSavedSyncToken = degradable(function() {
IndexedDBStore.prototype.deleteAllData = degradable(function() {
MemoryStore.prototype.deleteAllData.call(this);
return this.backend.clearDatabase().then(() => {
console.log("Deleted indexeddb data.");
logger.log("Deleted indexeddb data.");
}, (err) => {
console.error(`Failed to delete indexeddb data: ${err}`);
logger.error(`Failed to delete indexeddb data: ${err}`);
throw err;
});
});
@@ -197,11 +198,13 @@ IndexedDBStore.prototype.wantsSave = function() {
/**
* Possibly write data to the database.
*
* @param {bool} force True to force a save to happen
* @return {Promise} Promise resolves after the write completes
* (or immediately if no write is performed)
*/
IndexedDBStore.prototype.save = function() {
if (this.wantsSave()) {
IndexedDBStore.prototype.save = function(force) {
if (force || this.wantsSave()) {
return this._reallySave();
}
return Promise.resolve();
@@ -290,18 +293,18 @@ function degradable(func, fallback) {
try {
return await func.call(this, ...args);
} catch (e) {
console.error("IndexedDBStore failure, degrading to MemoryStore", e);
logger.error("IndexedDBStore failure, degrading to MemoryStore", e);
this.emit("degraded", e);
try {
// We try to delete IndexedDB after degrading since this store is only a
// cache (the app will still function correctly without the data).
// It's possible that deleting repair IndexedDB for the next app load,
// potenially by making a little more space available.
console.log("IndexedDBStore trying to delete degraded data");
logger.log("IndexedDBStore trying to delete degraded data");
await this.backend.clearDatabase();
console.log("IndexedDBStore delete after degrading succeeeded");
logger.log("IndexedDBStore delete after degrading succeeeded");
} catch (e) {
console.warn("IndexedDBStore delete after degrading failed", e);
logger.warn("IndexedDBStore delete after degrading failed", e);
}
// Degrade the store from being an instance of `IndexedDBStore` to instead be
// an instance of `MemoryStore` so that future API calls use the memory path
+3 -1
View File
@@ -335,8 +335,10 @@ module.exports.MemoryStore.prototype = {
/**
* Save does nothing as there is no backing data store.
* @param {bool} force True to force a save (but the memory
* store still can't save anything)
*/
save: function() {},
save: function(force) {},
/**
* Startup does nothing as this store doesn't require starting up.
+2 -1
View File
@@ -22,6 +22,7 @@ limitations under the License.
*/
const utils = require("../../utils");
import logger from '../../logger';
const DEBUG = false; // set true to enable console logging.
const E2E_PREFIX = "session.e2e.";
@@ -257,7 +258,7 @@ function removeByPrefix(store, prefix) {
function debuglog() {
if (DEBUG) {
console.log(...arguments);
logger.log(...arguments);
}
}
+2 -1
View File
@@ -21,6 +21,7 @@ limitations under the License.
*/
import utils from "./utils";
import logger from './logger';
/**
@@ -168,7 +169,7 @@ class SyncAccumulator {
}
break;
default:
console.error("Unknown cateogory: ", category);
logger.error("Unknown cateogory: ", category);
}
}
+51 -22
View File
@@ -32,6 +32,8 @@ const Group = require('./models/group');
const utils = require("./utils");
const Filter = require("./filter");
const EventTimeline = require("./models/event-timeline");
const PushProcessor = require("./pushprocessor");
import logger from './logger';
import {InvalidStoreError} from './errors';
@@ -58,7 +60,7 @@ function debuglog(...params) {
if (!DEBUG) {
return;
}
console.log(...params);
logger.log(...params);
}
@@ -126,7 +128,9 @@ SyncApi.prototype.createRoom = function(roomId) {
timelineSupport,
unstableClientRelationAggregation,
});
client.reEmitter.reEmit(room, ["Room.name", "Room.timeline", "Room.redaction",
client.reEmitter.reEmit(room, ["Room.name", "Room.timeline",
"Room.redaction",
"Room.redactionCancelled",
"Room.receipt", "Room.tags",
"Room.timelineReset",
"Room.localEchoUpdated",
@@ -392,7 +396,7 @@ SyncApi.prototype._peekPoll = function(peekRoom, token) {
peekRoom.addLiveEvents(events);
self._peekPoll(peekRoom, res.end);
}, function(err) {
console.error("[%s] Peek poll failed: %s", peekRoom.roomId, err);
logger.error("[%s] Peek poll failed: %s", peekRoom.roomId, err);
setTimeout(function() {
self._peekPoll(peekRoom, token);
}, 30 * 1000);
@@ -454,7 +458,7 @@ SyncApi.prototype._wasLazyLoadingToggled = async function(lazyLoadMembers) {
SyncApi.prototype._shouldAbortSync = function(error) {
if (error.errcode === "M_UNKNOWN_TOKEN") {
// The logout already happened, we just need to stop.
console.warn("Token no longer valid - assuming logout");
logger.warn("Token no longer valid - assuming logout");
this.stop();
return true;
}
@@ -494,7 +498,7 @@ SyncApi.prototype.sync = function() {
client.pushRules = result;
} catch (err) {
console.error("Getting push rules failed", err);
logger.error("Getting push rules failed", err);
if (self._shouldAbortSync(err)) return;
// wait for saved sync to complete before doing anything else,
// otherwise the sync state will end up being incorrect
@@ -522,7 +526,7 @@ SyncApi.prototype.sync = function() {
);
debuglog("Created and stored lazy load sync filter");
} catch (err) {
console.error(
logger.error(
"Creating and storing lazy load sync filter failed",
err,
);
@@ -546,7 +550,7 @@ SyncApi.prototype.sync = function() {
// we leave the state as 'ERROR' which isn't great since this normally means
// we're retrying. The client must be stopped before clearing the stores anyway
// so the app should stop the client, clear the store and start it again.
console.warn("InvalidStoreError: store is not usable: stopping sync.");
logger.warn("InvalidStoreError: store is not usable: stopping sync.");
return;
}
if (this.opts.lazyLoadMembers && this.opts.crypto) {
@@ -557,7 +561,7 @@ SyncApi.prototype.sync = function() {
await this.client._storeClientOptions();
debuglog("Stored client options");
} catch (err) {
console.error("Storing client options failed", err);
logger.error("Storing client options failed", err);
throw err;
}
@@ -580,7 +584,7 @@ SyncApi.prototype.sync = function() {
getFilterName(client.credentials.userId), filter,
);
} catch (err) {
console.error("Getting filter failed", err);
logger.error("Getting filter failed", err);
if (self._shouldAbortSync(err)) return;
// wait for saved sync to complete before doing anything else,
// otherwise the sync state will end up being incorrect
@@ -627,7 +631,7 @@ SyncApi.prototype.sync = function() {
return self._syncFromCache(savedSync);
}
}).catch(err => {
console.error("Getting saved sync failed", err);
logger.error("Getting saved sync failed", err);
});
// Now start the first incremental sync request: this can also
// take a while so if we set it going now, we can wait for it
@@ -699,7 +703,7 @@ SyncApi.prototype._syncFromCache = async function(savedSync) {
try {
await this._processSyncResponse(syncEventData, data);
} catch(e) {
console.error("Error processing cached sync", e.stack || e);
logger.error("Error processing cached sync", e.stack || e);
}
// Don't emit a prepared if we've bailed because the store is invalid:
@@ -774,7 +778,7 @@ SyncApi.prototype._sync = async function(syncOptions) {
} catch(e) {
// log the exception with stack if we have it, else fall back
// to the plain description
console.error("Caught /sync error", e.stack || e);
logger.error("Caught /sync error", e.stack || e);
// Emit the exception for client handling
this.client.emit("sync.unexpectedError", e);
@@ -888,15 +892,15 @@ SyncApi.prototype._onSyncError = function(err, syncOptions) {
return;
}
console.error("/sync error %s", err);
console.error(err);
logger.error("/sync error %s", err);
logger.error(err);
if(this._shouldAbortSync(err)) {
return;
}
this._failedSyncCount++;
console.log('Number of consecutive failed sync requests:', this._failedSyncCount);
logger.log('Number of consecutive failed sync requests:', this._failedSyncCount);
debuglog("Starting keep-alive");
// Note that we do *not* mark the sync connection as
@@ -1027,8 +1031,9 @@ SyncApi.prototype._processSyncResponse = async function(
// honour push rules that were previously cached. Base rules
// will be updated when we recieve push rules via getPushRules
// (see SyncApi.prototype.sync) before syncing over the network.
if (accountDataEvent.getType() == 'm.push_rules') {
client.pushRules = accountDataEvent.getContent();
if (accountDataEvent.getType() === 'm.push_rules') {
const rules = accountDataEvent.getContent();
client.pushRules = PushProcessor.rewriteDefaultRules(rules);
}
client.emit("accountData", accountDataEvent);
return accountDataEvent;
@@ -1040,8 +1045,26 @@ SyncApi.prototype._processSyncResponse = async function(
if (data.to_device && utils.isArray(data.to_device.events) &&
data.to_device.events.length > 0
) {
const cancelledKeyVerificationTxns = [];
data.to_device.events
.map(client.getEventMapper())
.map((toDeviceEvent) => { // map is a cheap inline forEach
// We want to flag m.key.verification.start events as cancelled
// if there's an accompanying m.key.verification.cancel event, so
// we pull out the transaction IDs from the cancellation events
// so we can flag the verification events as cancelled in the loop
// below.
if (toDeviceEvent.getType() === "m.key.verification.cancel") {
const txnId = toDeviceEvent.getContent()['transaction_id'];
if (txnId) {
cancelledKeyVerificationTxns.push(txnId);
}
}
// as mentioned above, .map is a cheap inline forEach, so return
// the unmodified event.
return toDeviceEvent;
})
.forEach(
function(toDeviceEvent) {
const content = toDeviceEvent.getContent();
@@ -1050,13 +1073,21 @@ SyncApi.prototype._processSyncResponse = async function(
content.msgtype == "m.bad.encrypted"
) {
// the mapper already logged a warning.
console.log(
logger.log(
'Ignoring undecryptable to-device event from ' +
toDeviceEvent.getSender(),
);
return;
}
if (toDeviceEvent.getType() === "m.key.verification.start"
|| toDeviceEvent.getType() === "m.key.verification.request") {
const txnId = content['transaction_id'];
if (cancelledKeyVerificationTxns.includes(txnId)) {
toDeviceEvent.flagCancelled();
}
}
client.emit("toDeviceEvent", toDeviceEvent);
},
);
@@ -1216,10 +1247,8 @@ SyncApi.prototype._processSyncResponse = async function(
room.setSummary(joinObj.summary);
}
// XXX: should we be adding ephemeralEvents to the timeline?
// It feels like that for symmetry with room.addAccountData()
// there should be a room.addEphemeralEvents() or similar.
room.addLiveEvents(ephemeralEvents);
// we deliberately don't add ephemeral events to the timeline
room.addEphemeralEvents(ephemeralEvents);
// we deliberately don't add accountData to the timeline
room.addAccountData(accountDataEvents);
+10 -8
View File
@@ -19,6 +19,7 @@ limitations under the License.
import Promise from 'bluebird';
const EventTimeline = require("./models/event-timeline");
import logger from './logger';
/**
* @private
@@ -28,7 +29,7 @@ const DEBUG = false;
/**
* @private
*/
const debuglog = DEBUG ? console.log.bind(console) : function() {};
const debuglog = DEBUG ? logger.log.bind(logger) : function() {};
/**
* the number of times we ask the server for more events before giving up
@@ -131,14 +132,15 @@ TimelineWindow.prototype.load = function(initialEventId, initialWindowSize) {
// feeling snappy.
//
if (initialEventId) {
const prom = this._client.getEventTimeline(this._timelineSet, initialEventId);
if (prom.isFulfilled()) {
initFields(prom.value());
return Promise.resolve();
} else {
return prom.then(initFields);
const timeline = this._timelineSet.getTimelineForEvent(initialEventId);
if (timeline) {
// hot-path optimization to save a reactor tick by replicating the sync check getTimelineForEvent does.
initFields(timeline);
return Promise.resolve(timeline);
}
const prom = this._client.getEventTimeline(this._timelineSet, initialEventId);
return prom.then(initFields);
} else {
const tl = this._timelineSet.getLiveTimeline();
initFields(tl);
+32
View File
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 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.
@@ -20,6 +21,7 @@ limitations under the License.
*/
const unhomoglyph = require('unhomoglyph');
import Promise from 'bluebird';
/**
* Encode a dictionary of query parameters.
@@ -699,3 +701,33 @@ module.exports.globToRegexp = function(glob, extended) {
}
return pat;
};
module.exports.ensureNoTrailingSlash = function(url) {
if (url && url.endsWith("/")) {
return url.substr(0, url.length - 1);
} else {
return url;
}
};
// Returns a promise which resolves with a given value after the given number of ms
module.exports.sleep = (ms, value) => new Promise((resolve => {
setTimeout(resolve, ms, value);
}));
module.exports.isNullOrUndefined = function(val) {
return val === null || val === undefined;
};
// Returns a Deferred
module.exports.defer = () => {
let resolve;
let reject;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
return {resolve, reject, promise};
};
+50 -50
View File
@@ -21,6 +21,7 @@ limitations under the License.
*/
const utils = require("../utils");
const EventEmitter = require("events").EventEmitter;
import logger from '../logger';
const DEBUG = true; // set true to enable console logging.
// events: hangup, error(err), replaced(call), state(state, oldState)
@@ -60,9 +61,9 @@ function MatrixCall(opts) {
this.URL = opts.URL;
// Array of Objects with urls, username, credential keys
this.turnServers = opts.turnServers || [];
if (this.turnServers.length === 0) {
if (this.turnServers.length === 0 && this.client.isFallbackICEServerAllowed()) {
this.turnServers.push({
urls: [MatrixCall.FALLBACK_STUN_SERVER],
urls: [MatrixCall.FALLBACK_ICE_SERVER],
});
}
utils.forEach(this.turnServers, function(server) {
@@ -91,8 +92,8 @@ function MatrixCall(opts) {
}
/** The length of time a call can be ringing for. */
MatrixCall.CALL_TIMEOUT_MS = 60000;
/** The fallback server to use for STUN. */
MatrixCall.FALLBACK_STUN_SERVER = 'stun:stun.l.google.com:19302';
/** The fallback ICE server to use for STUN or TURN protocols. */
MatrixCall.FALLBACK_ICE_SERVER = 'stun:turn.matrix.org';
/** An error code when the local client failed to create an offer. */
MatrixCall.ERR_LOCAL_OFFER_FAILED = "local_offer_failed";
/**
@@ -195,7 +196,7 @@ MatrixCall.prototype.placeScreenSharingCall =
* @param {string} queueId Arbitrary ID to track the chain of promises to be used
*/
MatrixCall.prototype.playElement = function(element, queueId) {
console.log("queuing play on " + queueId + " and element " + element);
logger.log("queuing play on " + queueId + " and element " + element);
// XXX: FIXME: Does this leak elements, given the old promises
// may hang around and retain a reference to them?
if (this.mediaPromises[queueId]) {
@@ -206,10 +207,10 @@ MatrixCall.prototype.playElement = function(element, queueId) {
// these failures may be non-fatal (as in the case of unmounts)
this.mediaPromises[queueId] =
this.mediaPromises[queueId].then(function() {
console.log("previous promise completed for " + queueId);
logger.log("previous promise completed for " + queueId);
return element.play();
}, function() {
console.log("previous promise failed for " + queueId);
logger.log("previous promise failed for " + queueId);
return element.play();
});
} else {
@@ -224,14 +225,14 @@ MatrixCall.prototype.playElement = function(element, queueId) {
* @param {string} queueId Arbitrary ID to track the chain of promises to be used
*/
MatrixCall.prototype.pauseElement = function(element, queueId) {
console.log("queuing pause on " + queueId + " and element " + element);
logger.log("queuing pause on " + queueId + " and element " + element);
if (this.mediaPromises[queueId]) {
this.mediaPromises[queueId] =
this.mediaPromises[queueId].then(function() {
console.log("previous promise completed for " + queueId);
logger.log("previous promise completed for " + queueId);
return element.pause();
}, function() {
console.log("previous promise failed for " + queueId);
logger.log("previous promise failed for " + queueId);
return element.pause();
});
} else {
@@ -250,15 +251,15 @@ MatrixCall.prototype.pauseElement = function(element, queueId) {
* @param {string} queueId Arbitrary ID to track the chain of promises to be used
*/
MatrixCall.prototype.assignElement = function(element, srcObject, queueId) {
console.log("queuing assign on " + queueId + " element " + element + " for " +
logger.log("queuing assign on " + queueId + " element " + element + " for " +
srcObject);
if (this.mediaPromises[queueId]) {
this.mediaPromises[queueId] =
this.mediaPromises[queueId].then(function() {
console.log("previous promise completed for " + queueId);
logger.log("previous promise completed for " + queueId);
element.srcObject = srcObject;
}, function() {
console.log("previous promise failed for " + queueId);
logger.log("previous promise failed for " + queueId);
element.srcObject = srcObject;
});
} else {
@@ -664,7 +665,7 @@ MatrixCall.prototype._maybeGotUserMediaForAnswer = function(stream) {
},
};
self.peerConn.createAnswer(function(description) {
debuglog("Created answer: " + description);
debuglog("Created answer: ", description);
self.peerConn.setLocalDescription(description, function() {
self._answerContent = {
version: 0,
@@ -753,7 +754,7 @@ MatrixCall.prototype._receivedAnswer = function(msg) {
*/
MatrixCall.prototype._gotLocalOffer = function(description) {
const self = this;
debuglog("Created offer: " + description);
debuglog("Created offer: ", description);
if (self.state == 'ended') {
debuglog("Ignoring newly created offer on call ID " + self.callId +
@@ -1159,7 +1160,7 @@ const callError = function(code, msg) {
const debuglog = function() {
if (DEBUG) {
console.log(...arguments);
logger.log(...arguments);
}
};
@@ -1216,24 +1217,9 @@ const _placeCallWithConstraints = function(self, constraints) {
};
const _createPeerConnection = function(self) {
let servers = self.turnServers;
if (self.webRtc.vendor === "mozilla") {
// modify turnServers struct to match what mozilla expects.
servers = [];
for (let i = 0; i < self.turnServers.length; i++) {
for (let j = 0; j < self.turnServers[i].urls.length; j++) {
servers.push({
url: self.turnServers[i].urls[j],
username: self.turnServers[i].username,
credential: self.turnServers[i].credential,
});
}
}
}
const pc = new self.webRtc.RtcPeerConnection({
iceTransportPolicy: self.forceTURN ? 'relay' : undefined,
iceServers: servers,
iceServers: self.turnServers,
});
pc.oniceconnectionstatechange = hookCallback(self, self._onIceConnectionStateChanged);
pc.onsignalingstatechange = hookCallback(self, self._onSignallingStateChanged);
@@ -1351,7 +1337,9 @@ module.exports.setVideoInput = function(deviceId) { videoInput = deviceId; };
* @param {MatrixClient} client The client instance to use.
* @param {string} roomId The room the call is in.
* @param {Object?} options DEPRECATED optional options map.
* @param {boolean} options.forceTURN DEPRECATED whether relay through TURN should be forced. This option is deprecated - use opts.forceTURN when creating the matrix client since it's only possible to set this option on outbound calls.
* @param {boolean} options.forceTURN DEPRECATED whether relay through TURN should be
* forced. This option is deprecated - use opts.forceTURN when creating the matrix client
* since it's only possible to set this option on outbound calls.
* @return {MatrixCall} the call or null if the browser doesn't support calling.
*/
module.exports.createNewMatrixCall = function(client, roomId, options) {
@@ -1382,24 +1370,36 @@ module.exports.createNewMatrixCall = function(client, roomId, options) {
return getUserMedia.apply(w.navigator, arguments);
};
}
webRtc.RtcPeerConnection = (
w.RTCPeerConnection || w.webkitRTCPeerConnection || w.mozRTCPeerConnection
);
webRtc.RtcSessionDescription = (
w.RTCSessionDescription || w.webkitRTCSessionDescription ||
w.mozRTCSessionDescription
);
webRtc.RtcIceCandidate = (
w.RTCIceCandidate || w.webkitRTCIceCandidate || w.mozRTCIceCandidate
);
webRtc.vendor = null;
if (w.mozRTCPeerConnection) {
webRtc.vendor = "mozilla";
} else if (w.webkitRTCPeerConnection) {
webRtc.vendor = "webkit";
} else if (w.RTCPeerConnection) {
webRtc.vendor = "generic";
// Firefox throws on so little as accessing the RTCPeerConnection when operating in
// a secure mode. There's some information at https://bugzilla.mozilla.org/show_bug.cgi?id=1542616
// though the concern is that the browser throwing a SecurityError will brick the
// client creation process.
try {
webRtc.RtcPeerConnection = (
w.RTCPeerConnection || w.webkitRTCPeerConnection || w.mozRTCPeerConnection
);
webRtc.RtcSessionDescription = (
w.RTCSessionDescription || w.webkitRTCSessionDescription ||
w.mozRTCSessionDescription
);
webRtc.RtcIceCandidate = (
w.RTCIceCandidate || w.webkitRTCIceCandidate || w.mozRTCIceCandidate
);
webRtc.vendor = null;
if (w.mozRTCPeerConnection) {
webRtc.vendor = "mozilla";
} else if (w.webkitRTCPeerConnection) {
webRtc.vendor = "webkit";
} else if (w.RTCPeerConnection) {
webRtc.vendor = "generic";
}
} catch (e) {
logger.error("Failed to set up WebRTC object: possible browser interference?");
logger.error(e);
return null;
}
if (!webRtc.RtcIceCandidate || !webRtc.RtcSessionDescription ||
!webRtc.RtcPeerConnection || !webRtc.getUserMedia) {
return null; // WebRTC is not supported.
+1928 -572
View File
File diff suppressed because it is too large Load Diff