Compare commits

...

382 Commits

Author SHA1 Message Date
RiotRobot 7519becd43 v9.7.0 2021-02-16 10:55:42 +00:00
RiotRobot fe83c15bc6 Prepare changelog for v9.7.0 2021-02-16 10:55:41 +00:00
RiotRobot b026e1c2f7 v9.7.0-rc.1 2021-02-10 15:54:02 +00:00
RiotRobot f8194d9418 Prepare changelog for v9.7.0-rc.1 2021-02-10 15:54:01 +00:00
David Baker 66bf0ec7af Merge pull request #1600 from SimonBrandner/handle-undefined-peerconn
Handle undefined peerconn
2021-02-09 16:29:46 +00:00
Šimon Brandner 1b22df2b7b Handle undefined peerconn
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-02-09 14:42:34 +01:00
David Baker 9f993f1f67 Merge pull request #1599 from matrix-org/dbkr/reemitter_dont_throw_if_no_error_handler
ReEmitter: Don't throw if no error handler is attached
2021-02-08 19:44:03 +00:00
David Baker 975518bd88 ReEmitter: Don't throw if no error handler is attached
As hopefully explained by lengthy comment

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

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

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

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

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

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

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

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

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

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

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

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

Fix a small typo

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

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

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

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

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

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

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

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

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

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

Part of https://github.com/vector-im/element-web/issues/15350
2020-10-09 17:21:14 +01:00
David Baker 49f2d1501c Set the type of the call based on the tracks
Remove the old hack of inspecting the SDP which no longer seems to
be necessary.
2020-10-08 15:42:39 +01:00
J. Ryan Stinnett c6819e0450 Omit stack trace if rehydration fails 2020-10-08 15:11:46 +01:00
David Baker f518ea95f4 Use getLocalAge() & add grace period
Use the new local-age field for deciding whether a call is still
valid or not. Also add a grace period so we don't ring half a second
before the call becomes invalid.
2020-10-08 11:49:54 +01:00
David Baker 92c6332143 use more enums 2020-10-08 11:32:32 +01:00
David Baker 0df1a7da21 copyright header 2020-10-08 11:12:39 +01:00
David Baker 487a9c0967 Extract the call event handler out to its own class
and convert it to TypeScript
2020-10-08 11:10:20 +01:00
David Baker fb89761671 Merge pull request #1495 from matrix-org/dbkr/age_is_just_a_number
Make an accurate version of 'age' for events
2020-10-08 09:29:16 +01:00
David Baker d1d3ae074d Add tests & fix some bugs found by said tests 2020-10-07 18:29:33 +01:00
David Baker 7dedaf90c3 Add a test for the age mangling 2020-10-07 18:04:20 +01:00
David Baker 687b98a09d Don't add localTs if an event has no age 2020-10-07 17:39:19 +01:00
David Baker c6b2e9873c Merge pull request #1498 from matrix-org/dbkr/call_options_is_optional
Make 'options' parameter optional
2020-10-07 17:15:26 +01:00
David Baker 1e80491675 Make 'options' parameter optional
It's deprecated so generally nothing should be passing it
2020-10-07 17:08:21 +01:00
Michael Telatynski a727da9193 Merge pull request #1497 from matrix-org/t3chguy/fix/14804
Create a giant event type enum
2020-10-07 16:48:39 +01:00
Michael Telatynski 0b7754581a Update src/@types/event.ts
Co-authored-by: Travis Ralston <travpc@gmail.com>
2020-10-07 16:32:04 +01:00
David Baker 452e8ea385 Merge pull request #1494 from matrix-org/revert-1493-revert-1487-dbkr/tsify_call
Convert call.js to Typescript & update WebRTC APIs (re-apply)
2020-10-07 15:36:27 +01:00
David Baker a189de9a2e Add comment on mass event copying 2020-10-07 15:32:22 +01:00
David Baker 8632ca6e37 Merge remote-tracking branch 'origin/develop' into revert-1493-revert-1487-dbkr/tsify_call 2020-10-07 15:16:21 +01:00
RiotRobot 293860b6c5 v8.5.0-rc.1 2020-10-07 14:18:52 +01:00
RiotRobot 07667172cd Prepare changelog for v8.5.0-rc.1 2020-10-07 14:18:52 +01:00
J. Ryan Stinnett f5240cdac6 Merge pull request #1467 from uhoreg/fallback_keys
Add support for olm fallback keys
2020-10-07 10:24:12 +01:00
Michael Telatynski 7529d2b638 Create a giant event type enum
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-07 09:57:56 +01:00
Michael Telatynski 03fc12e888 Merge pull request #1492 from matrix-org/t3chguy/fix/10545
Fix editing local echoes not updating them in real time
2020-10-07 00:15:16 +01:00
Hubert Chathi e05a50528e make test no longer dependent on emscripten internals 2020-10-06 19:01:01 -04:00
Hubert Chathi 356ee90417 lint 2020-10-06 18:33:45 -04:00
Hubert Chathi 5dced57724 bump olm release again and fix global.d.ts to work with the olm TS definitions 2020-10-06 17:55:40 -04:00
Michael Telatynski 8ec3b88c5d Update comments
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-06 22:55:34 +01:00
Michael Telatynski d7ef128510 Merge pull request #1491 from matrix-org/t3chguy/fix/9836
Fix re-emit of Event.replaced to be on client and not room
2020-10-06 22:50:14 +01:00
Hubert Chathi bb2502409b update yarn.lock 2020-10-06 15:34:02 -04:00
Hubert Chathi aa6aab4245 Merge remote-tracking branch 'origin/develop' into fallback_keys 2020-10-06 15:22:25 -04:00
Hubert Chathi 12aab0caeb depend on newer olm 2020-10-06 15:18:18 -04:00
David Baker 2f316c558d Merge pull request #1496 from matrix-org/dbkr/space_in_log_line
Add space to log line
2020-10-06 19:16:25 +01:00
David Baker f447273b75 Add space to log line 2020-10-06 18:59:34 +01:00
David Baker c8a18d51e6 Lint (& unused variable) 2020-10-06 17:45:21 +01:00
David Baker d77af1e67a Make an accurate version of 'age' for events
We've always had 'age' in events but it's never really been an
accurate representation of the event's age because we never did
anything with it. This transforms it into a local clock timestamp
when the event arives and when it comes out of the sync store, and
changes getLocalAge() to use it.

react-sdk doesn't appear to use getLocalAge() but any 3rd party apps
that do may notice a slight change in bahaviour.
2020-10-06 15:24:09 +01:00
David Baker 81c95224d1 Revert "Revert "Convert call.js to Typescript & update WebRTC APIs"" 2020-10-06 15:21:45 +01:00
David Baker a0e66291df Merge pull request #1493 from matrix-org/revert-1487-dbkr/tsify_call
Revert "Convert call.js to Typescript & update WebRTC APIs"
2020-10-06 15:21:38 +01:00
David Baker 5733f46f4c Revert "Convert call.js to Typescript & update WebRTC APIs" 2020-10-06 15:16:32 +01:00
David Baker da2128feff Merge pull request #1487 from matrix-org/dbkr/tsify_call
Convert call.js to Typescript & update WebRTC APIs
2020-10-06 15:09:10 +01:00
Michael Telatynski d413faefdb double sync-browserify jest timeout
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-06 12:39:18 +01:00
Michael Telatynski 0d1d767d96 Fix editing local echoes not updating them in real time
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-06 12:32:50 +01:00
David Baker 5619554023 use MatrixEvent for event types 2020-10-06 11:03:05 +01:00
David Baker 53880a4bb2 This is how plurals work 2020-10-06 10:54:51 +01:00
David Baker 812ae227b6 Make type enums PascalCase 2020-10-06 10:53:42 +01:00
David Baker c6b44098ac remove space before type colon 2020-10-06 09:55:02 +01:00
David Baker 92b95a98d0 comment other snake case identifiers
and also sto mixing quotes
2020-10-06 09:52:59 +01:00
David Baker a3505ff42d Use right case for private enum field 2020-10-06 09:49:51 +01:00
Michael Telatynski 15b2e3ff1d Fix re-emit of Event.replaced to be on client and not room
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-06 08:23:19 +01:00
Hubert Chathi 1845d1ac55 Merge pull request #1436 from uhoreg/dehydration
Dehydrate and rehydrate devices
2020-10-05 17:19:28 -04:00
J. Ryan Stinnett 463819caa7 Merge pull request #1490 from matrix-org/jryans/key-setup-device-undefined
Keep local device after processing device list sync
2020-10-05 14:49:18 +01:00
J. Ryan Stinnett a0317d9587 Fix tests 2020-10-05 14:29:30 +01:00
J. Ryan Stinnett fa6ce0cb70 Keep local device after processing device list sync
This change ensure we always keep the local device in the device list, even if
the server claims it doesn't exist. This is important for initial startup, as it
is possible the device key upload takes a while to apply, and thus might not
appear in the first device list sync.

Fixes https://github.com/vector-im/element-web/issues/15319
2020-10-05 14:09:16 +01:00
David Baker 7471ff4b0d Make setSinkId reliable
As per comment, this seems to be the way to make setSinkId work reliably on Chrome.

Also don't make our own mediastreams if we don't need to (ie. in a normal call
our tracks should come with their owen MediaStreams).
2020-10-03 16:57:33 +01:00
Hubert Chathi 65a5bfac88 update property name 2020-10-02 15:52:09 -04:00
David Baker d2321410f8 Pass stream to addTrack too
Otherwise we end up with separate, unbundled 'streamless tracks'
which is not what we want.
2020-10-02 15:12:12 +01:00
Hubert Chathi f90b5a99cd Merge remote-tracking branch 'origin/develop' into dehydration 2020-10-01 21:55:05 -04:00
David Baker cc4656b36a Update call.js to use new WebRTC APIs
and some other related & semi-related refactoring
2020-10-01 19:15:49 +01:00
J. Ryan Stinnett 34bc63a146 Merge pull request #1489 from matrix-org/jryans/no-console
Enforce logger module via lint rules
2020-10-01 14:56:15 +01:00
J. Ryan Stinnett 09bd91a588 Enforce logger module via lint rules
This adds lint rules (and fixes various errors) to ensure we use the `logger`
intermediary module, rather than accessing the console directly.
2020-10-01 14:28:24 +01:00
David Baker bb3e082e5b Make direction public, remove didConnect
Various flags that may or may not have been part of the poorly defined
external API before. Neither were used internally. Make direction a
public export but remove didConnect.
2020-10-01 10:15:27 +01:00
Hubert Chathi 60c863f829 lint 2020-09-30 18:33:27 -04:00
Hubert Chathi 2d2a73bf52 make dehydrated device name configurable 2020-09-30 18:05:52 -04:00
Hubert Chathi 7b9f73709d use "dehydration" as the cache name 2020-09-30 17:50:37 -04:00
Hubert Chathi 1dc89f642d use logger instead of console 2020-09-30 17:46:18 -04:00
David Baker a78b59010a Convert call.js to Typescript
This does a fairly basic rewriting of the whole file to bring it up
to a vaguely modern standard of code. Nothing should have changed
fundamentally, but it probably should do: as per the global type
overrides, we use a lot of deprecated WebRTC APIs and should probably
update.
2020-09-30 18:54:48 +01:00
Hubert Chathi a2e1d94fcf more lint 2020-09-30 01:22:46 -04:00
Hubert Chathi b181c83b93 lint 2020-09-30 01:16:57 -04:00
Hubert Chathi cf7c84c4ba more linting, because GitHub is showing me outdated CI info 2020-09-30 01:10:33 -04:00
Hubert Chathi 6e5230f9f9 fix lint again 2020-09-30 01:07:35 -04:00
Hubert Chathi f03f7c0acb fix lint 2020-09-30 00:58:37 -04:00
Hubert Chathi aa9b807b82 update to latest dehydration endpoints, and simplify API 2020-09-30 00:48:07 -04:00
Travis Ralston fa9921e091 Merge pull request #1462 from orangecms/develop
extend method redactEvent with reason
2020-09-28 15:05:00 -06:00
RiotRobot a9a6b2de48 Merge branch 'master' into develop 2020-09-28 16:09:28 +01:00
David Baker 3e978c64e4 Merge pull request #1484 from matrix-org/dbkr/catch_exceptions_from_call_event_handler
Catch exception from call event handler
2020-09-28 15:56:29 +01:00
David Baker 9b9a9642ee Merge pull request #1483 from matrix-org/dbkr/ignore_invalid_candidates
Ignore invalid candidates
2020-09-28 15:41:32 +01:00
David Baker 639d2317ed Catch exception from call event handler
Otherwise they leak out ot the sync loop. It's unfortunate that
exceptions from event handlers leak out to the emitter, and this
seems a bit clumsy, but having to wrap eveery event emit in a try
block seems worse.
2020-09-28 15:39:21 +01:00
David Baker 52379d7655 Ignore invalid candidates 2020-09-28 15:22:01 +01:00
Michael Telatynski 29827362d6 Merge pull request #1478 from matrix-org/t3chguy-patch-1
Always push docs if they are generated
2020-09-28 14:32:07 +01:00
RiotRobot f33315f610 Merge branch 'master' into develop 2020-09-28 14:23:19 +01:00
J. Ryan Stinnett f3a8306107 Merge pull request #1481 from matrix-org/jryans/guard-backup-sig
Only sign key backup with cross-signing keys when available
2020-09-28 10:21:34 +01:00
J. Ryan Stinnett 2b29e9934c Fix default key cache check 2020-09-28 10:16:20 +01:00
J. Ryan Stinnett 4b1104e463 Only sign key backup with cross-signing keys when available
This changes the key backup setup step to only sign with cross-signing keys when
both the public and private keys are already available without prompting. In
many cases down these paths, the cross-signing keys either may not exist or may
not be accessible. We always sign the key backup with your device key as well,
so there is always a route to trust the key backup even if this is skipped.

Fixes https://github.com/vector-im/element-web/issues/15230
2020-09-25 17:36:47 +01:00
Hubert Chathi 60b9ef959d don't include an empty signatures field when calculating the signature 2020-09-24 18:46:17 -04:00
Hubert Chathi 0074c2cf57 mark fallback keys as fallback 2020-09-24 18:09:07 -04:00
Hubert Chathi b0204ab54e use unstable prefix 2020-09-24 17:41:08 -04:00
J. Ryan Stinnett bc5b07aa75 Merge pull request #1479 from matrix-org/jryans/upgrade-deps-2020-09-23
Upgrade dependencies
2020-09-24 16:40:36 +01:00
Michael Telatynski cfe90dbed5 Always push docs if they are generated 2020-09-24 12:47:23 +01:00
J. Ryan Stinnett fa913655bc Upgrade dependencies 2020-09-23 17:35:24 +01:00
Daniel Maslowski aad7484c8d Add optional reason to be passed to redactEvent
Signed-off-by: Daniel Maslowski <info@orangecms.org>
2020-09-21 21:50:38 +02:00
Hubert Chathi 16dccd75c1 fix string 2020-09-18 19:20:09 -04:00
Hubert Chathi 5151b52688 Merge remote-tracking branch 'origin/develop' into dehydration 2020-09-18 19:13:22 -04:00
Hubert Chathi 492c1d77d8 style fixes 2020-09-16 17:09:33 -04:00
Hubert Chathi 3952768667 Merge remote-tracking branch 'origin/develop' into fallback_keys 2020-09-15 12:04:30 -04:00
Hubert Chathi 6331b34cf7 lint 2020-09-14 23:00:07 -04:00
Hubert Chathi 2ebe1dfa16 support olm fallback keys 2020-09-14 22:10:16 -04:00
Hubert Chathi 19f3996e09 only cross-sign dehydrated device if cross-signing is available 2020-09-03 16:06:06 -04:00
Hubert Chathi 25c2cc1768 various fixes 2020-09-03 11:45:37 -04:00
Hubert Chathi 438861ae5e separate dehydration functions to its own file and cache the key 2020-08-31 15:44:29 -04:00
Hubert Chathi ad85740ae2 allow other login functions to be used 2020-08-19 18:01:31 -04:00
Will Hunt 6ec7e3a0b7 UserId must be sane 2020-08-18 18:42:55 +01:00
Will Hunt 1a1fe759c3 fix encoding bug 2020-08-18 18:41:45 +01:00
Will Hunt a919c798f8 Update _unstable_getSharedRooms to match spec 2020-08-18 18:34:44 +01:00
Will Hunt 80fe66c481 Add /uk.half-shot.msc2666/ prefix 2020-08-18 18:34:44 +01:00
Will Hunt 4577dc8f44 s/msc2644/msc2666/ 2020-08-18 18:34:44 +01:00
Will Hunt cf0a5305e0 Throw if server does not support getSharedRooms 2020-08-18 18:34:44 +01:00
Will Hunt e69e6e1981 Update _unstable_getSharedRooms response format 2020-08-18 18:34:44 +01:00
Will Hunt 0febd99fbe Add _unstable_getSharedRooms 2020-08-18 18:34:43 +01:00
Hubert Chathi 6e8e3e4150 use newer version of dehydration proposal and add some comments 2020-08-17 18:09:23 -04:00
Hubert Chathi 8a4440c314 initial work on dehydration 2020-08-04 16:06:19 -04:00
51 changed files with 6818 additions and 4152 deletions
+4
View File
@@ -28,11 +28,15 @@ module.exports = {
"indent": "off",
"no-constant-condition": "off",
"no-async-promise-executor": "off",
// We use a `logger` intermediary module
"no-console": "error",
},
overrides: [{
"files": ["src/**/*.ts"],
"extends": ["matrix-org/ts"],
"rules": {
// We're okay being explicit at the moment
"@typescript-eslint/no-empty-interface": "off",
// While we're converting to ts we make heavy use of this
"@typescript-eslint/no-explicit-any": "off",
"quotes": "off",
-1
View File
@@ -12,7 +12,6 @@ lib-cov
out
/dist
/lib
/specbuild
# version file and tarball created by `npm pack` / `yarn pack`
/git-revision.txt
+316
View File
@@ -1,3 +1,319 @@
Changes in [9.7.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.7.0) (2021-02-16)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.7.0-rc.1...v9.7.0)
* No changes since rc.1
Changes in [9.7.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.7.0-rc.1) (2021-02-10)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.6.0...v9.7.0-rc.1)
* Handle undefined peerconn
[\#1600](https://github.com/matrix-org/matrix-js-sdk/pull/1600)
* ReEmitter: Don't throw if no error handler is attached
[\#1599](https://github.com/matrix-org/matrix-js-sdk/pull/1599)
* Convert ReEmitter to TS
[\#1598](https://github.com/matrix-org/matrix-js-sdk/pull/1598)
* Fix typo in main readme
[\#1597](https://github.com/matrix-org/matrix-js-sdk/pull/1597)
* Remove rogue plus character
[\#1596](https://github.com/matrix-org/matrix-js-sdk/pull/1596)
* Fix call ID NaN
[\#1595](https://github.com/matrix-org/matrix-js-sdk/pull/1595)
* Fix Electron type merging
[\#1594](https://github.com/matrix-org/matrix-js-sdk/pull/1594)
* Fix browser screen share
[\#1593](https://github.com/matrix-org/matrix-js-sdk/pull/1593)
* Fix desktop Matrix screen sharing
[\#1570](https://github.com/matrix-org/matrix-js-sdk/pull/1570)
* Guard against confused server retry times
[\#1591](https://github.com/matrix-org/matrix-js-sdk/pull/1591)
* Decrypt redaction events
[\#1589](https://github.com/matrix-org/matrix-js-sdk/pull/1589)
* Fix edge cases with peeking where a room is re-peeked
[\#1587](https://github.com/matrix-org/matrix-js-sdk/pull/1587)
Changes in [9.6.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.6.0) (2021-02-03)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.6.0-rc.1...v9.6.0)
* [Release] Fix edge cases with peeking where a room is re-peeked
[\#1588](https://github.com/matrix-org/matrix-js-sdk/pull/1588)
Changes in [9.6.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.6.0-rc.1) (2021-01-29)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.5.1...v9.6.0-rc.1)
* Add support for getting call stats
[\#1584](https://github.com/matrix-org/matrix-js-sdk/pull/1584)
* Fix compatibility with v0 calls
[\#1583](https://github.com/matrix-org/matrix-js-sdk/pull/1583)
* Upgrade deps 2021-01
[\#1582](https://github.com/matrix-org/matrix-js-sdk/pull/1582)
* Log the call ID when logging that we've received VoIP events
[\#1581](https://github.com/matrix-org/matrix-js-sdk/pull/1581)
* Fix extra negotiate message in Firefox
[\#1579](https://github.com/matrix-org/matrix-js-sdk/pull/1579)
* Add debug logs to encryption prep
[\#1580](https://github.com/matrix-org/matrix-js-sdk/pull/1580)
* Expose getPresence endpoint
[\#1578](https://github.com/matrix-org/matrix-js-sdk/pull/1578)
* Queue keys for backup even if backup isn't enabled yet
[\#1577](https://github.com/matrix-org/matrix-js-sdk/pull/1577)
* Stop retrying TURN access when forbidden
[\#1576](https://github.com/matrix-org/matrix-js-sdk/pull/1576)
* Add DTMF sending support
[\#1573](https://github.com/matrix-org/matrix-js-sdk/pull/1573)
Changes in [9.5.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.5.1) (2021-01-26)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.5.0...v9.5.1)
* [Release] Fix compatibility with v0 calls
[\#1585](https://github.com/matrix-org/matrix-js-sdk/pull/1585)
Changes in [9.5.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.5.0) (2021-01-18)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.5.0-rc.1...v9.5.0)
* No changes since rc.1
Changes in [9.5.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.5.0-rc.1) (2021-01-13)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.1...v9.5.0-rc.1)
* Don't log if no WebRTC
[\#1574](https://github.com/matrix-org/matrix-js-sdk/pull/1574)
* Add _unstable_getSharedRooms
[\#1417](https://github.com/matrix-org/matrix-js-sdk/pull/1417)
* Bump node-notifier from 8.0.0 to 8.0.1
[\#1568](https://github.com/matrix-org/matrix-js-sdk/pull/1568)
* Ignore party ID if opponent is v0
[\#1567](https://github.com/matrix-org/matrix-js-sdk/pull/1567)
* Basic call transfer initiation support
[\#1566](https://github.com/matrix-org/matrix-js-sdk/pull/1566)
* Room version 6 is now a thing
[\#1572](https://github.com/matrix-org/matrix-js-sdk/pull/1572)
* Store keys with same index but better trust level
[\#1571](https://github.com/matrix-org/matrix-js-sdk/pull/1571)
* Use TypeScript source for development, swap to build during release
[\#1561](https://github.com/matrix-org/matrix-js-sdk/pull/1561)
* Revert "Ignore party ID if opponent is v0"
[\#1565](https://github.com/matrix-org/matrix-js-sdk/pull/1565)
* Basic call transfer initiation support
[\#1558](https://github.com/matrix-org/matrix-js-sdk/pull/1558)
* Ignore party ID if opponent is v0
[\#1559](https://github.com/matrix-org/matrix-js-sdk/pull/1559)
* Honour a call reject event from another of our own devices
[\#1562](https://github.com/matrix-org/matrix-js-sdk/pull/1562)
Changes in [9.4.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.1) (2020-12-21)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.0...v9.4.1)
* Further script tweaks to get all layers building again
Changes in [9.4.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.0) (2020-12-21)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.0-rc.2...v9.4.0)
* Revert `postinstall` script change, causes issues for other layers
Changes in [9.4.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.0-rc.2) (2020-12-16)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.0-rc.1...v9.4.0-rc.2)
* Remove `postinstall` script which also runs as a dependency
[\#1560](https://github.com/matrix-org/matrix-js-sdk/pull/1560)
Changes in [9.4.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.0-rc.1) (2020-12-16)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.3.0...v9.4.0-rc.1)
* Fixes to support line 1 / 2
[\#1553](https://github.com/matrix-org/matrix-js-sdk/pull/1553)
* Add API for listening to remote hold status, advertise VoIP V1
[\#1549](https://github.com/matrix-org/matrix-js-sdk/pull/1549)
* A hangup from another client is still valid
[\#1555](https://github.com/matrix-org/matrix-js-sdk/pull/1555)
* Remove temporary build step for tests
[\#1554](https://github.com/matrix-org/matrix-js-sdk/pull/1554)
* Move browser build steps to prepublish only
[\#1552](https://github.com/matrix-org/matrix-js-sdk/pull/1552)
* Extend getSsoLoginUrl for MSC2858
[\#1541](https://github.com/matrix-org/matrix-js-sdk/pull/1541)
Changes in [9.3.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.3.0) (2020-12-07)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.3.0-rc.1...v9.3.0)
* No changes since rc.1
Changes in [9.3.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.3.0-rc.1) (2020-12-02)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.2.0...v9.3.0-rc.1)
* Export CallError
[\#1551](https://github.com/matrix-org/matrix-js-sdk/pull/1551)
* Upgrade dependencies
[\#1550](https://github.com/matrix-org/matrix-js-sdk/pull/1550)
* Don't log error when environment does not support WebRTC
[\#1547](https://github.com/matrix-org/matrix-js-sdk/pull/1547)
* Fix dehydration method name
[\#1544](https://github.com/matrix-org/matrix-js-sdk/pull/1544)
Changes in [9.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.2.0) (2020-11-23)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.2.0-rc.1...v9.2.0)
* [Release] Fix dehydration method name
[\#1545](https://github.com/matrix-org/matrix-js-sdk/pull/1545)
Changes in [9.2.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.2.0-rc.1) (2020-11-18)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.1.0...v9.2.0-rc.1)
* Implement call holding functionality
[\#1532](https://github.com/matrix-org/matrix-js-sdk/pull/1532)
* Support awaitable one-time dehydration
[\#1537](https://github.com/matrix-org/matrix-js-sdk/pull/1537)
* Client set profile methods update own user
[\#1534](https://github.com/matrix-org/matrix-js-sdk/pull/1534)
Changes in [9.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.1.0) (2020-11-09)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.1.0-rc.1...v9.1.0)
* No changes since rc.1
Changes in [9.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.1.0-rc.1) (2020-11-04)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.0.1...v9.1.0-rc.1)
* Fix spelling error in the server ACL event type
[\#1535](https://github.com/matrix-org/matrix-js-sdk/pull/1535)
* await idb operations from crypto store for dehydration
[\#1533](https://github.com/matrix-org/matrix-js-sdk/pull/1533)
* Fix stuck never-sending messages
[\#1531](https://github.com/matrix-org/matrix-js-sdk/pull/1531)
* Await key cache check to avoid prompts
[\#1529](https://github.com/matrix-org/matrix-js-sdk/pull/1529)
* Improve ICE candidate batching
[\#1524](https://github.com/matrix-org/matrix-js-sdk/pull/1524)
* Convert logger to typescript
[\#1527](https://github.com/matrix-org/matrix-js-sdk/pull/1527)
* Fix logger typo
[\#1525](https://github.com/matrix-org/matrix-js-sdk/pull/1525)
* bind online listener to window instead of document
[\#1523](https://github.com/matrix-org/matrix-js-sdk/pull/1523)
* Support m.call.select_answer
[\#1522](https://github.com/matrix-org/matrix-js-sdk/pull/1522)
Changes in [9.0.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.0.1) (2020-10-28)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.0.0...v9.0.1)
* [Release] Await key cache check to avoid prompts
[\#1530](https://github.com/matrix-org/matrix-js-sdk/pull/1530)
Changes in [9.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.0.0) (2020-10-26)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.0.0-rc.1...v9.0.0)
* Fix logger typo
[\#1528](https://github.com/matrix-org/matrix-js-sdk/pull/1528)
Changes in [9.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.0.0-rc.1) (2020-10-21)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v8.5.0...v9.0.0-rc.1)
BREAKING CHANGES
---
* `hasPendingEvent` now returns false instead of throwing when pending ordering mode is not `detached`
All changes
---
* Don't cache failures when fetching /versions
[\#1521](https://github.com/matrix-org/matrix-js-sdk/pull/1521)
* Install deps first as part of release
[\#1518](https://github.com/matrix-org/matrix-js-sdk/pull/1518)
* [Breaking] Change hasPendingEvent to return false if pending ordering
!detached
[\#1517](https://github.com/matrix-org/matrix-js-sdk/pull/1517)
* Skip editor prompts for merges
[\#1519](https://github.com/matrix-org/matrix-js-sdk/pull/1519)
* Convert call test to TypeScript
[\#1516](https://github.com/matrix-org/matrix-js-sdk/pull/1516)
* Support party_id
[\#1512](https://github.com/matrix-org/matrix-js-sdk/pull/1512)
* Support m.call.reject
[\#1510](https://github.com/matrix-org/matrix-js-sdk/pull/1510)
* Remove specbuild from .gitignore
[\#1515](https://github.com/matrix-org/matrix-js-sdk/pull/1515)
* Log the error when we failed to send candidates
[\#1514](https://github.com/matrix-org/matrix-js-sdk/pull/1514)
* Fixes for call state machine
[\#1503](https://github.com/matrix-org/matrix-js-sdk/pull/1503)
* Fix call event handler listener removing
[\#1506](https://github.com/matrix-org/matrix-js-sdk/pull/1506)
* Set the type of the call based on the tracks
[\#1501](https://github.com/matrix-org/matrix-js-sdk/pull/1501)
* Use new local timestamp for calls
[\#1499](https://github.com/matrix-org/matrix-js-sdk/pull/1499)
* Adjust types and APIs to match React SDK
[\#1502](https://github.com/matrix-org/matrix-js-sdk/pull/1502)
* Make an accurate version of 'age' for events
[\#1495](https://github.com/matrix-org/matrix-js-sdk/pull/1495)
* Make 'options' parameter optional
[\#1498](https://github.com/matrix-org/matrix-js-sdk/pull/1498)
* Create a giant event type enum
[\#1497](https://github.com/matrix-org/matrix-js-sdk/pull/1497)
* Convert call.js to Typescript & update WebRTC APIs (re-apply)
[\#1494](https://github.com/matrix-org/matrix-js-sdk/pull/1494)
Changes in [8.5.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v8.5.0) (2020-10-12)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v8.5.0-rc.1...v8.5.0)
* No changes since rc.1
Changes in [8.5.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v8.5.0-rc.1) (2020-10-07)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v8.4.1...v8.5.0-rc.1)
* Add support for olm fallback keys
[\#1467](https://github.com/matrix-org/matrix-js-sdk/pull/1467)
* Fix editing local echoes not updating them in real time
[\#1492](https://github.com/matrix-org/matrix-js-sdk/pull/1492)
* Fix re-emit of Event.replaced to be on client and not room
[\#1491](https://github.com/matrix-org/matrix-js-sdk/pull/1491)
* Add space to log line
[\#1496](https://github.com/matrix-org/matrix-js-sdk/pull/1496)
* Revert "Convert call.js to Typescript & update WebRTC APIs"
[\#1493](https://github.com/matrix-org/matrix-js-sdk/pull/1493)
* Convert call.js to Typescript & update WebRTC APIs
[\#1487](https://github.com/matrix-org/matrix-js-sdk/pull/1487)
* Dehydrate and rehydrate devices
[\#1436](https://github.com/matrix-org/matrix-js-sdk/pull/1436)
* Keep local device after processing device list sync
[\#1490](https://github.com/matrix-org/matrix-js-sdk/pull/1490)
* Enforce logger module via lint rules
[\#1489](https://github.com/matrix-org/matrix-js-sdk/pull/1489)
* Extend method redactEvent with reason
[\#1462](https://github.com/matrix-org/matrix-js-sdk/pull/1462)
* Catch exception from call event handler
[\#1484](https://github.com/matrix-org/matrix-js-sdk/pull/1484)
* Ignore invalid candidates
[\#1483](https://github.com/matrix-org/matrix-js-sdk/pull/1483)
* Always push docs if they are generated
[\#1478](https://github.com/matrix-org/matrix-js-sdk/pull/1478)
* Only sign key backup with cross-signing keys when available
[\#1481](https://github.com/matrix-org/matrix-js-sdk/pull/1481)
* Upgrade dependencies
[\#1479](https://github.com/matrix-org/matrix-js-sdk/pull/1479)
Changes in [8.4.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v8.4.1) (2020-09-28)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v8.4.0...v8.4.1)
+1 -1
View File
@@ -307,7 +307,7 @@ The SDK supports end-to-end encryption via the Olm and Megolm protocols, using
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
application to make libolm available, via the ``Olm`` global.
It is also necessry to call ``matrixClient.initCrypto()`` after creating a new
It is also necessary to call ``matrixClient.initCrypto()`` after creating a new
``MatrixClient`` (but **before** calling ``matrixClient.startClient()``) to
initialise the crypto layer.
+42 -38
View File
@@ -1,20 +1,21 @@
{
"name": "matrix-js-sdk",
"version": "8.4.1",
"version": "9.7.0",
"description": "Matrix Client-Server SDK for Javascript",
"scripts": {
"prepare": "yarn build",
"prepublishOnly": "yarn build",
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
"dist": "echo 'This is for the release script so it can make assets (browser bundle).' && yarn build",
"clean": "rimraf lib dist",
"build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:compile-browser && yarn build:minify-browser && yarn build:types",
"build": "yarn build:dev && yarn build:compile-browser && yarn build:minify-browser",
"build:dev": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types",
"build:types": "tsc --emitDeclarationOnly",
"build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src",
"build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
"build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js",
"gendoc": "jsdoc -c jsdoc.json -P package.json",
"lint": "yarn lint:types && yarn lint:js",
"lint:js": "eslint --max-warnings 76 src spec",
"lint:js": "eslint --max-warnings 73 src spec",
"lint:types": "tsc --noEmit",
"test": "jest spec/ --coverage --testEnvironment node",
"test:watch": "jest spec/ --coverage --testEnvironment node --watch"
@@ -27,10 +28,11 @@
"matrix-org"
],
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
"browser": "./lib/browser-index.js",
"matrix_src_main": "./src/index.ts",
"matrix_src_browser": "./src/browser-index.js",
"matrix_lib_main": "./lib/index.js",
"matrix_lib_typings": "./lib/index.d.ts",
"author": "matrix.org",
"license": "Apache-2.0",
"files": [
@@ -46,51 +48,53 @@
"release.sh"
],
"dependencies": {
"@babel/runtime": "^7.8.3",
"@babel/runtime": "^7.12.5",
"another-json": "^0.2.0",
"browser-request": "^0.3.3",
"bs58": "^4.0.1",
"content-type": "^1.0.2",
"loglevel": "^1.6.4",
"qs": "^6.5.2",
"request": "^2.88.0",
"unhomoglyph": "^1.0.2"
"content-type": "^1.0.4",
"loglevel": "^1.7.1",
"qs": "^6.9.6",
"request": "^2.88.2",
"unhomoglyph": "^1.0.6"
},
"devDependencies": {
"@babel/cli": "^7.7.5",
"@babel/core": "^7.7.5",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-proposal-numeric-separator": "^7.7.4",
"@babel/plugin-proposal-object-rest-spread": "^7.7.4",
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/plugin-transform-runtime": "^7.8.3",
"@babel/preset-env": "^7.7.6",
"@babel/preset-typescript": "^7.7.4",
"@babel/register": "^7.7.4",
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-numeric-separator": "^7.12.7",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-typescript": "^7.12.7",
"@babel/register": "^7.12.10",
"@types/jest": "^26.0.20",
"@types/node": "12",
"@types/request": "^2.48.4",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"@types/request": "^2.48.5",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.3",
"babelify": "^10.0.0",
"better-docs": "^2.3.2",
"browserify": "^16.5.0",
"browserify": "^17.0.0",
"docdash": "^1.2.0",
"eslint": "7.3.1",
"eslint-config-matrix-org": "^0.1.2",
"eslint-plugin-babel": "^5.3.0",
"eslint": "7.18.0",
"eslint-config-matrix-org": "^0.2.0",
"eslint-plugin-babel": "^5.3.1",
"exorcist": "^1.0.1",
"fake-indexeddb": "^3.0.0",
"jest": "^24.9.0",
"jest-localstorage-mock": "^2.4.0",
"jsdoc": "^3.6.5",
"fake-indexeddb": "^3.1.2",
"jest": "^26.6.3",
"jest-localstorage-mock": "^2.4.6",
"jsdoc": "^3.6.6",
"matrix-mock-request": "^1.2.3",
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
"rimraf": "^3.0.0",
"terser": "^4.4.3",
"tsify": "^4.0.1",
"typescript": "^3.7.3"
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"rimraf": "^3.0.2",
"terser": "^5.5.1",
"tsify": "^5.0.2",
"typescript": "^4.1.3"
},
"jest": {
"testEnvironment": "node"
}
},
"typings": "./lib/index.d.ts"
}
+50 -11
View File
@@ -94,6 +94,14 @@ if [ $# -ne 1 ]; then
exit 1
fi
# We use Git branch / commit dependencies for some packages, and Yarn seems
# to have a hard time getting that right. See also
# https://github.com/yarnpkg/yarn/issues/4734. As a workaround, we clean the
# global cache here to ensure we get the right thing.
yarn cache clean
# Ensure all dependencies are updated
yarn install --ignore-scripts
if [ -z "$skip_changelog" ]; then
# update_changelog doesn't have a --version flag
update_changelog -h > /dev/null || (echo "github-changelog-generator is required: please install it"; exit)
@@ -170,6 +178,19 @@ echo "yarn version"
# manually commit the result.
yarn version --no-git-tag-version --new-version "$release"
# For the published and dist versions of the package, we copy the
# `matrix_lib_main` and `matrix_lib_typings` fields to `main` and `typings` (if
# they exist). This small bit of gymnastics allows us to use the TypeScript
# source directly for development without needing to build before linting or
# testing.
for i in main typings
do
lib_value=$(jq -r ".matrix_lib_$i" package.json)
if [ "$lib_value" != "null" ]; then
jq ".$i = .matrix_lib_$i" package.json > package.json.new && mv package.json.new package.json
fi
done
# commit yarn.lock if it exists, is versioned, and is modified
if [[ -f yarn.lock && `git status --porcelain yarn.lock | grep '^ M'` ]];
then
@@ -204,11 +225,6 @@ if [ $dodist -eq 0 ]; then
pushd "$builddir"
git clone "$projdir" .
git checkout "$rel_branch"
# We use Git branch / commit dependencies for some packages, and Yarn seems
# to have a hard time getting that right. See also
# https://github.com/yarnpkg/yarn/issues/4734. As a workaround, we clean the
# global cache here to ensure we get the right thing.
yarn cache clean
yarn install
# We haven't tagged yet, so tell the dist script what version
# it's building
@@ -327,6 +343,7 @@ if [ -z "$skip_jsdoc" ]; then
$release index.html
git add "$release"
git commit --no-verify -m "Add jsdoc for $release" index.html "$release"
git push origin gh-pages
fi
# if it is a pre-release, leave it on the release branch for now.
@@ -339,18 +356,40 @@ fi
echo "updating master branch"
git checkout master
git pull
git merge "$rel_branch"
git merge "$rel_branch" --no-edit
# push master and docs (if generated) to github
# push master to github
git push origin master
if [ -z "$skip_jsdoc" ]; then
git push origin gh-pages
fi
# finally, merge master back onto develop (if it exists)
if [ $(git branch -lr | grep origin/develop -c) -ge 1 ]; then
git checkout develop
git pull
git merge master
git merge master --no-edit
# When merging to develop, we need revert the `main` and `typings` fields if
# we adjusted them previously.
for i in main typings
do
# If a `lib` prefixed value is present, it means we adjusted the field
# earlier at publish time, so we should revert it now.
if [ "$(jq -r ".matrix_lib_$i" package.json)" != "null" ]; then
# If there's a `src` prefixed value, use that, otherwise delete.
# This is used to delete the `typings` field and reset `main` back
# to the TypeScript source.
src_value=$(jq -r ".matrix_src_$i" package.json)
if [ "$src_value" != "null" ]; then
jq ".$i = .matrix_src_$i" package.json > package.json.new && mv package.json.new package.json
else
jq "del(.$i)" package.json > package.json.new && mv package.json.new package.json
fi
fi
done
if [ -n "$(git ls-files --modified package.json)" ]; then
echo "Committing develop package.json"
git commit package.json -m "Resetting package fields for development"
fi
git push origin develop
fi
+7
View File
@@ -69,6 +69,9 @@ export function TestClient(
this.deviceKeys = null;
this.oneTimeKeys = {};
this._callEventHandler = {
calls: new Map(),
};
}
TestClient.prototype.toString = function() {
@@ -230,3 +233,7 @@ TestClient.prototype.flushSync = function() {
logger.log(`${this}: flushSync completed`);
});
};
TestClient.prototype.isFallbackICEServerAllowed = function() {
return true;
};
+1 -1
View File
@@ -99,5 +99,5 @@ describe("Browserify Test", function() {
client.once("sync.unexpectedError", reject);
}),
]);
}, 10000);
}, 20000); // additional timeout as this test can take quite a while
});
+56
View File
@@ -971,4 +971,60 @@ describe("megolm", function() {
expect(event.getContent().body).toEqual('42');
});
});
it("Alice receives an untrusted megolm key, only to receive the trusted one shortly after", function() {
const testClient = new TestClient(
"@alice:localhost", "device2", "access_token2",
);
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const inboundGroupSession = new Olm.InboundGroupSession();
inboundGroupSession.create(groupSession.session_key());
const rawEvent = encryptMegolmEvent({
senderKey: testSenderKey,
groupSession: groupSession,
room_id: ROOM_ID,
});
return testClient.client.initCrypto().then(() => {
const keys = [{
room_id: ROOM_ID,
algorithm: 'm.megolm.v1.aes-sha2',
session_id: groupSession.session_id(),
session_key: inboundGroupSession.export_session(0),
sender_key: testSenderKey,
}];
return testClient.client.importRoomKeys(keys, { untrusted: true });
}).then(() => {
const event = testUtils.mkEvent({
event: true,
...rawEvent,
room: ROOM_ID,
});
return event.attemptDecryption(testClient.client._crypto, true).then(() => {
expect(event.isKeySourceUntrusted()).toBeTruthy();
});
}).then(() => {
const event = testUtils.mkEvent({
type: 'm.room_key',
content: {
room_id: ROOM_ID,
algorithm: 'm.megolm.v1.aes-sha2',
session_id: groupSession.session_id(),
session_key: groupSession.session_key(),
},
event: true,
});
event._senderCurve25519Key = testSenderKey;
return testClient.client._crypto._onRoomKeyEvent(event);
}).then(() => {
const event = testUtils.mkEvent({
event: true,
...rawEvent,
room: ROOM_ID,
});
return event.attemptDecryption(testClient.client._crypto, true).then(() => {
expect(event.isKeySourceUntrusted()).toBeFalsy();
});
});
});
});
+1 -1
View File
@@ -31,5 +31,5 @@ try {
const crypto = require('crypto');
utils.setCrypto(crypto);
} catch (err) {
console.log('nodejs was compiled without crypto support: some tests will fail');
logger.log('nodejs was compiled without crypto support: some tests will fail');
}
+72
View File
@@ -0,0 +1,72 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventEmitter } from "events";
import { ReEmitter } from "../../src/ReEmitter";
const EVENTNAME = "UnknownEntry";
class EventSource extends EventEmitter {
doTheThing() {
this.emit(EVENTNAME, "foo", "bar");
}
doAnError() {
this.emit('error');
}
}
class EventTarget extends EventEmitter {
}
describe("ReEmitter", function() {
it("Re-Emits events with the same args", function() {
const src = new EventSource();
const tgt = new EventTarget();
const handler = jest.fn();
tgt.on(EVENTNAME, handler);
const reEmitter = new ReEmitter(tgt);
reEmitter.reEmit(src, [EVENTNAME]);
src.doTheThing();
// Args should be the args passed to 'emit' after the event name, and
// also the source object of the event which re-emitter adds
expect(handler).toHaveBeenCalledWith("foo", "bar", src);
});
it("Doesn't throw if no handler for 'error' event", function() {
const src = new EventSource();
const tgt = new EventTarget();
const reEmitter = new ReEmitter(tgt);
reEmitter.reEmit(src, ['error']);
// without the workaround in ReEmitter, this would throw
src.doAnError();
const handler = jest.fn();
tgt.on('error', handler);
src.doAnError();
// Now we've attached an error handler, it should be called
expect(handler).toHaveBeenCalled();
});
});
+11 -3
View File
@@ -26,6 +26,7 @@ import {MemoryCryptoStore} from '../../../src/crypto/store/memory-crypto-store';
import 'fake-indexeddb/auto';
import 'jest-localstorage-mock';
import {OlmDevice} from "../../../src/crypto/OlmDevice";
import {logger} from '../../../src/logger';
const userId = "@alice:example.com";
@@ -51,7 +52,7 @@ const masterKeyPub = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk";
describe("CrossSigningInfo.getCrossSigningKey", 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;
}
@@ -83,9 +84,16 @@ describe("CrossSigningInfo.getCrossSigningKey", function() {
const info = new CrossSigningInfo(userId, {
getCrossSigningKey: () => testKey,
});
const [pubKey, ab] = await info.getCrossSigningKey("master", masterKeyPub);
const [pubKey, pkSigning] = await info.getCrossSigningKey("master", masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(ab).toEqual({a: 106712, b: 106712});
// check that the pkSigning object corresponds to the pubKey
const signature = pkSigning.sign("message");
const util = new global.Olm.Utility();
try {
util.ed25519_verify(pubKey, "message", signature);
} finally {
util.free();
}
});
it.each(types)("should request a key from the cache callback (if set)" +
+2
View File
@@ -72,6 +72,8 @@ describe('DeviceList', function() {
function createTestDeviceList() {
const baseApis = {
downloadKeysForUsers: downloadSpy,
getUserId: () => '@test1:sw1v.org',
deviceId: 'HGKAWHRVJQ',
};
const mockOlm = {
verifySignature: function(key, message, signature) {},
+2 -1
View File
@@ -22,6 +22,7 @@ import {TestClient} from '../../TestClient';
import {HttpResponse, setHttpResponses} from '../../test-utils';
import { resetCrossSigningKeys } from "./crypto-utils";
import { MatrixError } from '../../../src/http-api';
import {logger} from '../../../src/logger';
async function makeTestClient(userInfo, options, keys) {
if (!keys) keys = {};
@@ -49,7 +50,7 @@ async function makeTestClient(userInfo, options, keys) {
describe("Cross Signing", 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;
}
+6 -5
View File
@@ -22,6 +22,7 @@ import {TestClient} from '../../TestClient';
import {makeTestClients} from './verification/util';
import {encryptAES} from "../../../src/crypto/aes";
import {resetCrossSigningKeys, createSecretStorageKey} from "./crypto-utils";
import {logger} from '../../../src/logger';
import * as utils from "../../../src/utils";
@@ -29,7 +30,7 @@ try {
const crypto = require('crypto');
utils.setCrypto(crypto);
} catch (err) {
console.log('nodejs was compiled without crypto support');
logger.log('nodejs was compiled without crypto support');
}
async function makeTestClient(userInfo, options) {
@@ -60,7 +61,7 @@ function sign(obj, key, userId) {
describe("Secrets", 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;
}
@@ -193,7 +194,7 @@ describe("Secrets", function() {
};
resetCrossSigningKeys(alice);
const newKeyId = await alice.addSecretStorageKey(
const { keyId: newKeyId } = await alice.addSecretStorageKey(
SECRET_STORAGE_ALGORITHM_V1_AES,
);
// we don't await on this because it waits for the event to come down the sync
@@ -225,8 +226,8 @@ describe("Secrets", function() {
],
{
cryptoCallbacks: {
onSecretRequested: e => {
expect(e.name).toBe("foo");
onSecretRequested: (userId, deviceId, requestId, secretName, deviceTrust) => {
expect(secretName).toBe("foo");
return "bar";
},
},
+3 -2
View File
@@ -18,12 +18,13 @@ limitations under the License.
import {TestClient} from '../../../TestClient';
import {MatrixEvent} from "../../../../src/models/event";
import nodeCrypto from "crypto";
import {logger} from '../../../../src/logger';
export async function makeTestClients(userInfos, options) {
const clients = [];
const clientMap = {};
const sendToDevice = function(type, map) {
// console.log(this.getUserId(), "sends", type, map);
// logger.log(this.getUserId(), "sends", type, map);
for (const [userId, devMap] of Object.entries(map)) {
if (userId in clientMap) {
for (const [deviceId, msg] of Object.entries(devMap)) {
@@ -67,7 +68,7 @@ export async function makeTestClients(userInfos, options) {
setImmediate(() => {
for (const tc of clients) {
if (tc.client === this) { // eslint-disable-line babel/no-invalid-this
console.log("sending remote echo!!");
logger.log("sending remote echo!!");
tc.client.emit("Room.timeline", remoteEcho);
} else {
tc.client.emit("Room.timeline", event);
+15
View File
@@ -521,4 +521,19 @@ describe("MatrixClient", function() {
xit("should be able to peek into a room using peekInRoom", function(done) {
});
});
describe("getPresence", function() {
it("should send a presence HTTP GET", function() {
httpLookups = [{
method: "GET",
path: `/presence/${encodeURIComponent(userId)}/status`,
data: {
"presence": "unavailable",
"last_active_ago": 420845,
},
}];
client.getPresence(userId);
expect(httpLookups.length).toEqual(0);
});
});
});
+66
View File
@@ -17,6 +17,39 @@ limitations under the License.
import {SyncAccumulator} from "../../src/sync-accumulator";
// The event body & unsigned object get frozen to assert that they don't get altered
// by the impl
const RES_WITH_AGE = {
next_batch: "abc",
rooms: {
invite: {},
leave: {},
join: {
"!foo:bar": {
account_data: { events: [] },
ephemeral: { events: [] },
unread_notifications: {},
timeline: {
events: [
Object.freeze({
content: {
body: "This thing is happening right now!",
},
origin_server_ts: 123456789,
sender: "@alice:localhost",
type: "m.room.message",
unsigned: Object.freeze({
age: 50,
}),
}),
],
prev_batch: "something",
},
},
},
},
};
describe("SyncAccumulator", function() {
let sa;
@@ -368,6 +401,39 @@ describe("SyncAccumulator", function() {
expect(summary["m.joined_member_count"]).toEqual(5);
expect(summary["m.heroes"]).toEqual(["@bob:bar"]);
});
it("should return correctly adjusted age attributes", () => {
const delta = 1000;
const startingTs = 1000;
const oldDateNow = Date.now;
try {
Date.now = jest.fn();
Date.now.mockReturnValue(startingTs);
sa.accumulate(RES_WITH_AGE);
Date.now.mockReturnValue(startingTs + delta);
const output = sa.getJSON();
expect(output.roomsData.join["!foo:bar"].timeline.events[0].unsigned.age).toEqual(
RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0].unsigned.age + delta,
);
expect(Object.keys(output.roomsData.join["!foo:bar"].timeline.events[0])).toEqual(
Object.keys(RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0]),
);
} finally {
Date.now = oldDateNow;
}
});
it("should mangle age without adding extra keys", () => {
sa.accumulate(RES_WITH_AGE);
const output = sa.getJSON();
expect(Object.keys(output.roomsData.join["!foo:bar"].timeline.events[0])).toEqual(
Object.keys(RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0]),
);
});
});
});
+193
View File
@@ -0,0 +1,193 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {TestClient} from '../../TestClient';
import {MatrixCall, CallErrorCode} from '../../../src/webrtc/call';
const DUMMY_SDP = (
"v=0\r\n" +
"o=- 5022425983810148698 2 IN IP4 127.0.0.1\r\n" +
"s=-\r\nt=0 0\r\na=group:BUNDLE 0\r\n" +
"a=msid-semantic: WMS h3wAi7s8QpiQMH14WG3BnDbmlOqo9I5ezGZA\r\n" +
"m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n" +
"c=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:hLDR\r\n" +
"a=ice-pwd:bMGD9aOldHWiI+6nAq/IIlRw\r\n" +
"a=ice-options:trickle\r\n" +
"a=fingerprint:sha-256 E4:94:84:F9:4A:98:8A:56:F5:5F:FD:AF:72:B9:32:89:49:5C:4B:9A:" +
"4A:15:8E:41:8A:F3:69:E4:39:52:DC:D6\r\n" +
"a=setup:active\r\n" +
"a=mid:0\r\n" +
"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" +
"a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" +
"a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n" +
"a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n" +
"a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n" +
"a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n" +
"a=sendrecv\r\n" +
"a=msid:h3wAi7s8QpiQMH14WG3BnDbmlOqo9I5ezGZA 4357098f-3795-4131-bff4-9ba9c0348c49\r\n" +
"a=rtcp-mux\r\n" +
"a=rtpmap:111 opus/48000/2\r\n" +
"a=rtcp-fb:111 transport-cc\r\n" +
"a=fmtp:111 minptime=10;useinbandfec=1\r\n" +
"a=rtpmap:103 ISAC/16000\r\n" +
"a=rtpmap:104 ISAC/32000\r\n" +
"a=rtpmap:9 G722/8000\r\n" +
"a=rtpmap:0 PCMU/8000\r\n" +
"a=rtpmap:8 PCMA/8000\r\n" +
"a=rtpmap:106 CN/32000\r\n" +
"a=rtpmap:105 CN/16000\r\n" +
"a=rtpmap:13 CN/8000\r\n" +
"a=rtpmap:110 telephone-event/48000\r\n" +
"a=rtpmap:112 telephone-event/32000\r\n" +
"a=rtpmap:113 telephone-event/16000\r\n" +
"a=rtpmap:126 telephone-event/8000\r\n" +
"a=ssrc:3619738545 cname:2RWtmqhXLdoF4sOi\r\n"
);
class MockRTCPeerConnection {
localDescription: RTCSessionDescription;
constructor() {
this.localDescription = {
sdp: DUMMY_SDP,
type: 'offer',
toJSON: function() {},
};
}
addEventListener() {}
createOffer() {
return Promise.resolve({});
}
setRemoteDescription() {
return Promise.resolve();
}
setLocalDescription() {
return Promise.resolve();
}
close() {}
}
describe('Call', function() {
let client;
let call;
let prevNavigator;
let prevDocument;
let prevWindow;
beforeEach(function() {
prevNavigator = global.navigator;
prevDocument = global.document;
prevWindow = global.window;
global.navigator = {
mediaDevices: {
// @ts-ignore Mock
getUserMedia: () => {
return {
getTracks: () => [],
getAudioTracks: () => [],
getVideoTracks: () => [],
};
},
},
};
global.window = {
// @ts-ignore Mock
RTCPeerConnection: MockRTCPeerConnection,
// @ts-ignore Mock
RTCSessionDescription: {},
// @ts-ignore Mock
RTCIceCandidate: {},
getUserMedia: {},
};
// @ts-ignore Mock
global.document = {};
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
// We just stub out sendEvent: we're not interested in testing the client's
// event sending code here
client.client.sendEvent = () => {};
call = new MatrixCall({
client: client.client,
roomId: '!foo:bar',
});
// call checks one of these is wired up
call.on('error', () => {});
});
afterEach(function() {
client.stop();
global.navigator = prevNavigator;
global.window = prevWindow;
global.document = prevDocument;
});
it('should ignore candidate events from non-matching party ID', async function() {
await call.placeVoiceCall();
await call.onAnswerReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'the_correct_party_id',
answer: {
sdp: DUMMY_SDP,
},
};
},
});
call.peerConn.addIceCandidate = jest.fn();
call.onRemoteIceCandidatesReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'the_correct_party_id',
candidates: [
{
candidate: '',
sdpMid: '',
},
],
};
},
});
expect(call.peerConn.addIceCandidate.mock.calls.length).toBe(1);
call.onRemoteIceCandidatesReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'some_other_party_id',
candidates: [
{
candidate: '',
sdpMid: '',
},
],
};
},
});
expect(call.peerConn.addIceCandidate.mock.calls.length).toBe(1);
// Hangup to stop timers
call.hangup(CallErrorCode.UserHangup, true);
});
});
+89
View File
@@ -0,0 +1,89 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export enum EventType {
// Room state events
RoomCanonicalAlias = "m.room.canonical_alias",
RoomCreate = "m.room.create",
RoomJoinRules = "m.room.join_rules",
RoomMember = "m.room.member",
RoomThirdPartyInvite = "m.room.third_party_invite",
RoomPowerLevels = "m.room.power_levels",
RoomName = "m.room.name",
RoomTopic = "m.room.topic",
RoomAvatar = "m.room.avatar",
RoomPinnedEvents = "m.room.pinned_events",
RoomEncryption = "m.room.encryption",
RoomHistoryVisibility = "m.room.history_visibility",
RoomGuestAccess = "m.room.guest_access",
RoomServerAcl = "m.room.server_acl",
RoomTombstone = "m.room.tombstone",
/**
* @deprecated Should not be used.
*/
RoomAliases = "m.room.aliases", // deprecated https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
// Room timeline events
RoomRedaction = "m.room.redaction",
RoomMessage = "m.room.message",
RoomMessageEncrypted = "m.room.encrypted",
Sticker = "m.sticker",
CallInvite = "m.call.invite",
CallCandidates = "m.call.candidates",
CallAnswer = "m.call.answer",
CallHangup = "m.call.hangup",
CallReject = "m.call.reject",
CallSelectAnswer = "m.call.select_answer",
CallNegotiate = "m.call.negotiate",
CallReplaces = "m.call.replaces",
KeyVerificationRequest = "m.key.verification.request",
KeyVerificationStart = "m.key.verification.start",
KeyVerificationCancel = "m.key.verification.cancel",
KeyVerificationMac = "m.key.verification.mac",
// use of this is discouraged https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-feedback
RoomMessageFeedback = "m.room.message.feedback",
// Room ephemeral events
Typing = "m.typing",
Receipt = "m.receipt",
Presence = "m.presence",
// Room account_data events
FullyRead = "m.fully_read",
Tag = "m.tag",
// User account_data events
PushRules = "m.push_rules",
Direct = "m.direct",
IgnoredUserList = "m.ignored_user_list",
// to_device events
RoomKey = "m.room_key",
RoomKeyRequest = "m.room_key_request",
ForwardedRoomKey = "m.forwarded_room_key",
Dummy = "m.dummy",
}
export enum MsgType {
Text = "m.text",
Emote = "m.emote",
Notice = "m.notice",
Image = "m.image",
File = "m.file",
Audio = "m.audio",
Location = "m.location",
Video = "m.video",
}
+60 -4
View File
@@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as Olm from "olm";
// this is needed to tell TS about global.Olm
import * as Olm from "olm"; // eslint-disable-line @typescript-eslint/no-unused-vars
export {};
@@ -22,10 +23,65 @@ declare global {
namespace NodeJS {
interface Global {
localStorage: Storage;
Olm: Olm;
}
}
interface Global {
Olm: Olm;
interface Window {
electron?: Electron;
}
interface Electron {
getDesktopCapturerSources(options: GetSourcesOptions): Promise<Array<DesktopCapturerSource>>;
}
interface MediaDevices {
// This is experimental and types don't know about it yet
// https://github.com/microsoft/TypeScript/issues/33232
getDisplayMedia(constraints: MediaStreamConstraints | DesktopCapturerConstraints): Promise<MediaStream>;
getUserMedia(constraints: MediaStreamConstraints | DesktopCapturerConstraints): Promise<MediaStream>;
}
export interface DesktopCapturerConstraints {
audio: boolean | {
mandatory: {
chromeMediaSource: string;
chromeMediaSourceId: string;
};
};
video: boolean | {
mandatory: {
chromeMediaSource: string;
chromeMediaSourceId: string;
};
};
}
export interface DesktopCapturerSource {
id: string;
name: string;
thumbnailURL: string;
}
interface GetSourcesOptions {
types: Array<string>;
thumbnailSize?: {
height: number;
width: number;
};
fetchWindowIcons?: boolean;
}
interface HTMLAudioElement {
// sinkId & setSinkId are experimental and typescript doesn't know about them
sinkId: string;
setSinkId(outputId: string);
}
interface DummyInterfaceWeShouldntBeUsingThis {}
interface Navigator {
// We check for the webkit-prefixed getUserMedia to detect if we're
// on webkit: we should check if we still need to do this
webkitGetUserMedia: DummyInterfaceWeShouldntBeUsingThis;
}
}
-52
View File
@@ -1,52 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* @module
*/
export class ReEmitter {
constructor(target) {
this.target = target;
// We keep one bound event handler for each event name so we know
// what event is arriving
this.boundHandlers = {};
}
_handleEvent(eventName, ...args) {
this.target.emit(eventName, ...args);
}
reEmit(source, eventNames) {
// We include the source as the last argument for event handlers which may need it,
// such as read receipt listeners on the client class which won't have the context
// of the room.
const forSource = (handler, ...args) => {
handler(...args, source);
};
for (const eventName of eventNames) {
if (this.boundHandlers[eventName] === undefined) {
this.boundHandlers[eventName] = this._handleEvent.bind(this, eventName);
}
const boundHandler = forSource.bind(this, this.boundHandlers[eventName]);
source.on(eventName, boundHandler);
}
}
}
+50
View File
@@ -0,0 +1,50 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventEmitter } from "events";
export class ReEmitter {
private target: EventEmitter;
constructor(target: EventEmitter) {
this.target = target;
}
reEmit(source: EventEmitter, eventNames: string[]) {
for (const eventName of eventNames) {
// We include the source as the last argument for event handlers which may need it,
// such as read receipt listeners on the client class which won't have the context
// of the room.
const forSource = (...args) => {
// EventEmitter special cases 'error' to make the emit function throw if no
// handler is attached, which sort of makes sense for making sure that something
// handles an error, but for re-emitting, there could be a listener on the original
// source object so the test doesn't really work. We *could* try to replicate the
// same logic and throw if there is no listener on either the source or the target,
// but this behaviour is fairly undesireable for us anyway: the main place we throw
// 'error' events is for calls, where error events are usually emitted some time
// later by a different part of the code where 'emit' throwing because the app hasn't
// added an error handler isn't terribly helpful. (A better fix in retrospect may
// have been to just avoid using the event name 'error', but backwards compat...)
if (eventName === 'error' && this.target.listenerCount('error') === 0) return;
this.target.emit(eventName, ...args, source);
};
source.on(eventName, forSource);
}
}
}
+11 -4
View File
@@ -356,15 +356,22 @@ MatrixBaseApis.prototype.getCasLoginUrl = function(redirectUrl) {
* authenticates with the SSO.
* @param {string} loginType The type of SSO login we are doing (sso or cas).
* Defaults to 'sso'.
* @param {string} idpId The ID of the Identity Provider being targeted, optional.
* @return {string} The HS URL to hit to begin the SSO login process.
*/
MatrixBaseApis.prototype.getSsoLoginUrl = function(redirectUrl, loginType) {
MatrixBaseApis.prototype.getSsoLoginUrl = function(redirectUrl, loginType, idpId) {
if (loginType === undefined) {
loginType = "sso";
}
return this._http.getUrl("/login/"+loginType+"/redirect", {
"redirectUrl": redirectUrl,
}, PREFIX_R0);
let prefix = PREFIX_R0;
let url = "/login/" + loginType + "/redirect";
if (idpId) {
url += "/" + idpId;
prefix = "/_matrix/client/unstable/org.matrix.msc2858";
}
return this._http.getUrl(url, { redirectUrl }, prefix);
};
/**
+249 -245
View File
@@ -33,6 +33,7 @@ import {EventTimeline} from "./models/event-timeline";
import {SearchResult} from "./models/search-result";
import {StubStore} from "./store/stub";
import {createNewMatrixCall} from "./webrtc/call";
import {CallEventHandler} from './webrtc/callEventHandler';
import * as utils from './utils';
import {sleep} from './utils';
import {
@@ -55,6 +56,7 @@ import {PushProcessor} from "./pushprocessor";
import {encodeBase64, decodeBase64} from "./crypto/olmlib";
import { User } from "./models/user";
import {AutoDiscovery} from "./autodiscovery";
import {DEHYDRATION_ALGORITHM} from "./crypto/dehydration";
const SCROLLBACK_DELAY_MS = 3000;
export const CRYPTO_ENABLED = isCryptoAvailable();
@@ -178,6 +180,9 @@ function keyFromRecoverySession(session, decryptionKey) {
* @param {boolean} [opts.forceTURN]
* Optional. Whether relaying calls through a TURN server should be forced.
*
* @param {boolean} [opts.supportsCallTransfer]
* Optional. True to advertise support for call transfers to other parties on Matrix calls.
*
* @param {boolean} [opts.fallbackICEServerAllowed]
* Optional. Whether to allow a fallback ICE server should be used for negotiating a
* WebRTC connection if the homeserver doesn't provide any servers. Defaults to false.
@@ -234,7 +239,14 @@ function keyFromRecoverySession(session, decryptionKey) {
* {
* keys: {
* <key name>: {
* pubkey: {UInt8Array}
* "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
* "passphrase": {
* "algorithm": "m.pbkdf2",
* "iterations": 500000,
* "salt": "..."
* },
* "iv": "...",
* "mac": "..."
* }, ...
* }
* }
@@ -246,6 +258,7 @@ function keyFromRecoverySession(session, decryptionKey) {
* desired to avoid user prompts.
* Args:
* {string} keyId the ID of the new key
* {object} keyInfo Infomation about the key as above for `getSecretStorageKey`
* {Uint8Array} key the new private key
*
* @param {function} [opts.cryptoCallbacks.onSecretRequested]
@@ -327,17 +340,15 @@ export function MatrixClient(opts) {
}
this.clientRunning = false;
this.callList = {
// callId: MatrixCall
};
// try constructing a MatrixCall to see if we are running in an environment
// which has WebRTC. If we are, listen for and handle m.call.* events.
const call = createNewMatrixCall(this);
this._supportsVoip = false;
if (call) {
setupCallEventHandler(this);
this._callEventHandler = new CallEventHandler(this);
this._supportsVoip = true;
} else {
this._callEventHandler = null;
}
this._syncingRetry = null;
this._syncApi = null;
@@ -356,6 +367,7 @@ export function MatrixClient(opts) {
this._cryptoCallbacks = opts.cryptoCallbacks || {};
this._forceTURN = opts.forceTURN || false;
this._supportsCallTransfer = opts.supportsCallTransfer || false;
this._fallbackICEServerAllowed = opts.fallbackICEServerAllowed || false;
// List of which rooms have encryption enabled: separate from crypto because
@@ -459,6 +471,143 @@ export function MatrixClient(opts) {
utils.inherits(MatrixClient, EventEmitter);
utils.extend(MatrixClient.prototype, MatrixBaseApis.prototype);
/**
* Try to rehydrate a device if available. The client must have been
* initialized with a `cryptoCallback.getDehydrationKey` option, and this
* function must be called before initCrypto and startClient are called.
*
* @return {Promise} Resolves to undefined if a device could not be dehydrated, or
* to the new device ID if the dehydration was successful.
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixClient.prototype.rehydrateDevice = async function() {
if (this._crypto) {
throw new Error("Cannot rehydrate device after crypto is initialized");
}
if (!this._cryptoCallbacks.getDehydrationKey) {
return;
}
let getDeviceResult;
try {
getDeviceResult = await this._http.authedRequest(
undefined,
"GET",
"/dehydrated_device",
undefined, undefined,
{
prefix: "/_matrix/client/unstable/org.matrix.msc2697.v2",
},
);
} catch (e) {
logger.info("could not get dehydrated device", e.toString());
return;
}
if (!getDeviceResult.device_data || !getDeviceResult.device_id) {
logger.info("no dehydrated device found");
return;
}
const account = new global.Olm.Account();
try {
const deviceData = getDeviceResult.device_data;
if (deviceData.algorithm !== DEHYDRATION_ALGORITHM) {
logger.warn("Wrong algorithm for dehydrated device");
return;
}
logger.log("unpickling dehydrated device");
const key = await this._cryptoCallbacks.getDehydrationKey(
deviceData,
(k) => {
// copy the key so that it doesn't get clobbered
account.unpickle(new Uint8Array(k), deviceData.account);
},
);
account.unpickle(key, deviceData.account);
logger.log("unpickled device");
const rehydrateResult = await this._http.authedRequest(
undefined,
"POST",
"/dehydrated_device/claim",
undefined,
{
device_id: getDeviceResult.device_id,
},
{
prefix: "/_matrix/client/unstable/org.matrix.msc2697.v2",
},
);
if (rehydrateResult.success === true) {
this.deviceId = getDeviceResult.device_id;
logger.info("using dehydrated device");
const pickleKey = this.pickleKey || "DEFAULT_KEY";
this._exportedOlmDeviceToImport = {
pickledAccount: account.pickle(pickleKey),
sessions: [],
pickleKey: pickleKey,
};
account.free();
return this.deviceId;
} else {
account.free();
logger.info("not using dehydrated device");
return;
}
} catch (e) {
account.free();
logger.warn("could not unpickle", e);
}
};
/**
* Set the dehydration key. This will also periodically dehydrate devices to
* the server.
*
* @param {Uint8Array} key the dehydration key
* @param {object} [keyInfo] Information about the key. Primarily for
* information about how to generate the key from a passphrase.
* @param {string} [deviceDisplayName] The device display name for the
* dehydrated device.
* @return {Promise} A promise that resolves when the dehydrated device is stored.
*/
MatrixClient.prototype.setDehydrationKey = async function(
key, keyInfo = {}, deviceDisplayName = undefined,
) {
if (!(this._crypto)) {
logger.warn('not dehydrating device if crypto is not enabled');
return;
}
return await this._crypto._dehydrationManager.setKeyAndQueueDehydration(
key, keyInfo, deviceDisplayName,
);
};
/**
* Creates a new dehydrated device (without queuing periodic dehydration)
* @param {Uint8Array} key the dehydration key
* @param {object} [keyInfo] Information about the key. Primarily for
* information about how to generate the key from a passphrase.
* @param {string} [deviceDisplayName] The device display name for the
* dehydrated device.
* @return {Promise<String>} the device id of the newly created dehydrated device
*/
MatrixClient.prototype.createDehydratedDevice = async function(
key, keyInfo = {}, deviceDisplayName = undefined,
) {
if (!(this._crypto)) {
logger.warn('not dehydrating device if crypto is not enabled');
return;
}
await this._crypto._dehydrationManager.setKey(
key, keyInfo, deviceDisplayName,
);
return await this._crypto._dehydrationManager.dehydrateDevice();
};
MatrixClient.prototype.exportDevice = async function() {
if (!(this._crypto)) {
logger.warn('not exporting device if crypto is not enabled');
@@ -551,6 +700,14 @@ MatrixClient.prototype.setForceTURN = function(forceTURN) {
this._forceTURN = forceTURN;
};
/**
* Set whether to advertise transfer support to other parties on Matrix calls.
* @param {bool} supportsCallTransfer True to advertise the 'm.call.transferee' capability
*/
MatrixClient.prototype.setSupportsCallTransfer = function(supportsCallTransfer) {
this._supportsCallTransfer = supportsCallTransfer;
};
/**
* Get the current sync state.
* @return {?string} the sync state, which may be null.
@@ -1318,7 +1475,7 @@ wrapCryptoFuncs(MatrixClient, [
* containing the key, or rejects if the key cannot be obtained.
* Returns:
* {Promise} A promise which resolves to key creation data for
* SecretStorage#addKey: an object with `passphrase` and/or `pubkey` fields.
* SecretStorage#addKey: an object with `passphrase` etc fields.
*/
/**
@@ -1333,7 +1490,9 @@ wrapCryptoFuncs(MatrixClient, [
* @param {string} [keyName] the name of the key. If not given, a random
* name will be generated.
*
* @return {string} the name of the key
* @return {object} An object with:
* keyId: {string} the ID of the key
* keyInfo: {object} details about the key (iv, mac, passphrase)
*/
/**
@@ -1893,7 +2052,7 @@ MatrixClient.prototype.isValidRecoveryKey = function(recoveryKey) {
*
* @param {string} password Passphrase
* @param {object} backupInfo Backup metadata from `checkKeyBackup`
* @return {Promise<Buffer>} key backup key
* @return {Promise<Uint8Array>} key backup key
*/
MatrixClient.prototype.keyBackupKeyFromPassword = function(
password, backupInfo,
@@ -1908,7 +2067,7 @@ MatrixClient.prototype.keyBackupKeyFromPassword = function(
* The cross-signing API is currently UNSTABLE and may change without notice.
*
* @param {string} recoveryKey The recovery key
* @return {Buffer} key backup key
* @return {Uint8Array} key backup key
*/
MatrixClient.prototype.keyBackupKeyFromRecoveryKey = function(recoveryKey) {
return decodeRecoveryKey(recoveryKey);
@@ -2054,7 +2213,7 @@ MatrixClient.prototype._restoreKeyBackup = function(
// This is async.
this._crypto.storeSessionBackupPrivateKey(privKey)
.catch((e) => {
console.warn("Error caching session backup key:", e);
logger.warn("Error caching session backup key:", e);
}).then(cacheCompleteCallback);
if (progressCallback) {
@@ -2858,14 +3017,19 @@ function _sendEventHttpRequest(client, event) {
* @param {string} eventId
* @param {string} [txnId] transaction id. One will be made up if not
* supplied.
* @param {module:client.callback} callback Optional.
* @param {object|module:client.callback} callbackOrOpts
* Options to pass on, may contain `reason`.
* Can be callback for backwards compatibility.
* @return {Promise} Resolves: TODO
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixClient.prototype.redactEvent = function(roomId, eventId, txnId, callback) {
MatrixClient.prototype.redactEvent = function(roomId, eventId, txnId, callbackOrOpts) {
const opts = typeof(callbackOrOpts) === 'object' ? callbackOrOpts : {};
const reason = opts.reason;
const callback = typeof(callbackOrOpts) === 'function' ? callbackOrOpts : undefined;
return this._sendCompleteEvent(roomId, {
type: "m.room.redaction",
content: {},
content: { reason: reason },
redacts: eventId,
}, txnId, callback);
};
@@ -3569,25 +3733,39 @@ MatrixClient.prototype.setProfileInfo = function(info, data, callback) {
/**
* @param {string} name
* @param {module:client.callback} callback Optional.
* @return {Promise} Resolves: TODO
* @return {Promise} Resolves: {} an empty object.
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixClient.prototype.setDisplayName = function(name, callback) {
return this.setProfileInfo(
MatrixClient.prototype.setDisplayName = async function(name, callback) {
const prom = await this.setProfileInfo(
"displayname", { displayname: name }, callback,
);
// XXX: synthesise a profile update for ourselves because Synapse is broken and won't
const user = this.getUser(this.getUserId());
if (user) {
user.displayName = name;
user.emit("User.displayName", user.events.presence, user);
}
return prom;
};
/**
* @param {string} url
* @param {module:client.callback} callback Optional.
* @return {Promise} Resolves: TODO
* @return {Promise} Resolves: {} an empty object.
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixClient.prototype.setAvatarUrl = function(url, callback) {
return this.setProfileInfo(
MatrixClient.prototype.setAvatarUrl = async function(url, callback) {
const prom = await this.setProfileInfo(
"avatar_url", { avatar_url: url }, callback,
);
// XXX: synthesise a profile update for ourselves because Synapse is broken and won't
const user = this.getUser(this.getUserId());
if (user) {
user.avatarUrl = url;
user.emit("User.avatarUrl", user.events.presence, user);
}
return prom;
};
/**
@@ -3663,6 +3841,19 @@ MatrixClient.prototype.setPresence = function(opts, callback) {
);
};
/**
* @param {string} userId The user to get presence for
* @param {module:client.callback} callback Optional.
* @return {Promise} Resolves: The presence state for this user.
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixClient.prototype.getPresence = function(userId, callback) {
const path = utils.encodeUri("/presence/$userId/status", {
$userId: userId,
});
return this._http.authedRequest(callback, "GET", path, undefined, undefined);
};
/**
* Retrieve older messages from the given room and put them in the timeline.
@@ -4965,6 +5156,26 @@ MatrixClient.prototype._storeClientOptions = function() {
return this.store.storeClientOptions(serializableOpts);
};
/**
* Gets a set of room IDs in common with another user
* @param {string} userId The userId to check.
* @return {Promise<string[]>} Resolves to a set of rooms
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixClient.prototype._unstable_getSharedRooms = async function(userId) {
if (!(await this.doesServerSupportUnstableFeature("uk.half-shot.msc2666"))) {
throw Error('Server does not support shared_rooms API');
}
const path = utils.encodeUri("/uk.half-shot.msc2666/user/shared_rooms/$userId", {
$userId: userId,
});
const res = await this._http.authedRequest(
undefined, "GET", path, undefined, undefined,
{prefix: PREFIX_UNSTABLE},
);
return res.joined;
};
/**
* High level helper method to stop the client from polling and allow a
* clean shutdown.
@@ -4984,6 +5195,11 @@ MatrixClient.prototype.stopClient = function() {
if (this._peekSync) {
this._peekSync.stopPeeking();
}
if (this._callEventHandler) {
this._callEventHandler.stop();
this._callEventHandler = null;
}
global.clearTimeout(this._checkTurnServersTimeoutID);
if (this._clientWellKnownIntervalID !== undefined) {
global.clearInterval(this._clientWellKnownIntervalID);
@@ -5008,7 +5224,12 @@ MatrixClient.prototype.getVersions = function() {
{
prefix: '',
},
);
).catch((e) => {
// Need to unset this if it fails, otherwise we'll never retry
this._serverVersionsPromise = null;
// but rethrow the exception to anything that was waiting
throw e;
});
return this._serverVersionsPromise;
};
@@ -5193,227 +5414,6 @@ async function(roomId, eventId, relationType, eventType, opts = {}) {
};
};
function setupCallEventHandler(client) {
const candidatesByCall = {
// callId: [Candidate]
};
// The sync code always emits one event at a time, so it will patiently
// wait for us to finish processing a call invite before delivering the
// next event, even if that next event is a hangup. We therefore accumulate
// all our call events and then process them on the 'sync' event, ie.
// each time a sync has completed. This way, we can avoid emitting incoming
// call events if we get both the invite and answer/hangup in the same sync.
// This happens quite often, eg. replaying sync from storage, catchup sync
// after loading and after we've been offline for a bit.
let callEventBuffer = [];
function evaluateEventBuffer() {
if (client.getSyncState() === "SYNCING") {
// don't process any events until they are all decrypted
if (callEventBuffer.some((e) => e.isBeingDecrypted())) return;
const ignoreCallIds = {}; // Set<String>
// inspect the buffer and mark all calls which have been answered
// or hung up before passing them to the call event handler.
for (let i = callEventBuffer.length - 1; i >= 0; i--) {
const ev = callEventBuffer[i];
if (ev.getType() === "m.call.answer" ||
ev.getType() === "m.call.hangup") {
ignoreCallIds[ev.getContent().call_id] = "yep";
}
}
// now loop through the buffer chronologically and inject them
callEventBuffer.forEach(function(e) {
if (
e.getType() === "m.call.invite" &&
ignoreCallIds[e.getContent().call_id]
) {
// This call has previously been answered or hung up: ignore it
return;
}
try {
callEventHandler(e);
} catch (e) {
logger.error("Caught exception handling call event", e);
}
});
callEventBuffer = [];
}
}
client.on("sync", evaluateEventBuffer);
function onEvent(event) {
// any call events or ones that might be once they're decrypted
if (event.getType().indexOf("m.call.") === 0 || event.isBeingDecrypted()) {
// queue up for processing once all events from this sync have been
// processed (see above).
callEventBuffer.push(event);
}
if (event.isBeingDecrypted() || event.isDecryptionFailure()) {
// add an event listener for once the event is decrypted.
event.once("Event.decrypted", () => {
if (event.getType().indexOf("m.call.") === -1) return;
if (callEventBuffer.includes(event)) {
// we were waiting for that event to decrypt, so recheck the buffer
evaluateEventBuffer();
} else {
// This one wasn't buffered so just run the event handler for it
// straight away
try {
callEventHandler(event);
} catch (e) {
logger.error("Caught exception handling call event", e);
}
}
});
}
}
client.on("event", onEvent);
function callEventHandler(event) {
const content = event.getContent();
let call = content.call_id ? client.callList[content.call_id] : undefined;
let i;
//console.info("RECV %s content=%s", event.getType(), JSON.stringify(content));
if (event.getType() === "m.call.invite") {
if (event.getSender() === client.credentials.userId) {
return; // ignore invites you send
}
if (event.getAge() > content.lifetime) {
return; // expired call
}
if (call && call.state === "ended") {
return; // stale/old invite event
}
if (call) {
logger.log(
"WARN: Already have a MatrixCall with id %s but got an " +
"invite. Clobbering.",
content.call_id,
);
}
call = createNewMatrixCall(client, event.getRoomId(), {
forceTURN: client._forceTURN,
});
if (!call) {
logger.log(
"Incoming call ID " + content.call_id + " but this client " +
"doesn't support WebRTC",
);
// don't hang up the call: there could be other clients
// connected that do support WebRTC and declining the
// the call on their behalf would be really annoying.
return;
}
call.callId = content.call_id;
call._initWithInvite(event);
client.callList[call.callId] = call;
// if we stashed candidate events for that call ID, play them back now
if (candidatesByCall[call.callId]) {
for (i = 0; i < candidatesByCall[call.callId].length; i++) {
call._gotRemoteIceCandidate(
candidatesByCall[call.callId][i],
);
}
}
// Were we trying to call that user (room)?
let existingCall;
const existingCalls = utils.values(client.callList);
for (i = 0; i < existingCalls.length; ++i) {
const thisCall = existingCalls[i];
if (call.roomId === thisCall.roomId &&
thisCall.direction === 'outbound' &&
(["wait_local_media", "create_offer", "invite_sent"].indexOf(
thisCall.state) !== -1)) {
existingCall = thisCall;
break;
}
}
if (existingCall) {
// If we've only got to wait_local_media or create_offer and
// we've got an invite, pick the incoming call because we know
// we haven't sent our invite yet otherwise, pick whichever
// call has the lowest call ID (by string comparison)
if (existingCall.state === 'wait_local_media' ||
existingCall.state === 'create_offer' ||
existingCall.callId > call.callId) {
logger.log(
"Glare detected: answering incoming call " + call.callId +
" and canceling outgoing call " + existingCall.callId,
);
existingCall._replacedBy(call);
call.answer();
} else {
logger.log(
"Glare detected: rejecting incoming call " + call.callId +
" and keeping outgoing call " + existingCall.callId,
);
call.hangup();
}
} else {
client.emit("Call.incoming", call);
}
} else if (event.getType() === 'm.call.answer') {
if (!call) {
return;
}
if (event.getSender() === client.credentials.userId) {
if (call.state === 'ringing') {
call._onAnsweredElsewhere(content);
}
} else {
call._receivedAnswer(content);
}
} else if (event.getType() === 'm.call.candidates') {
if (event.getSender() === client.credentials.userId) {
return;
}
if (!call) {
// store the candidates; we may get a call eventually.
if (!candidatesByCall[content.call_id]) {
candidatesByCall[content.call_id] = [];
}
candidatesByCall[content.call_id] = candidatesByCall[
content.call_id
].concat(content.candidates);
} else {
for (i = 0; i < content.candidates.length; i++) {
call._gotRemoteIceCandidate(content.candidates[i]);
}
}
} else if (event.getType() === 'm.call.hangup') {
// Note that we also observe our own hangups here so we can see
// if we've already rejected a call that would otherwise be valid
if (!call) {
// if not live, store the fact that the call has ended because
// we're probably getting events backwards so
// the hangup will come before the invite
call = createNewMatrixCall(client, event.getRoomId());
if (call) {
call.callId = content.call_id;
call._initWithHangup(event);
client.callList[content.call_id] = call;
}
} else {
if (call.state !== 'ended') {
call._onHangupReceived(content);
delete client.callList[content.call_id];
}
}
}
}
}
function checkTurnServers(client) {
if (!client._supportsVoip) {
return;
@@ -5438,6 +5438,11 @@ function checkTurnServers(client) {
}
}, function(err) {
logger.error("Failed to get TURN URIs");
// If we get a 403, there's no point in looping forever.
if (err.httpStatus === 403) {
logger.info("TURN access unavailable for this account");
return;
}
client._checkTurnServersTimeoutID = setTimeout(function() {
checkTurnServers(client);
}, 60000);
@@ -5470,9 +5475,8 @@ function _PojoToMatrixEventMapper(client, options) {
}
event.attemptDecryption(client._crypto);
}
const room = client.getRoom(event.getRoomId());
if (room && !preventReEmit) {
room.reEmitter.reEmit(event, ["Event.replaced"]);
if (!preventReEmit) {
client.reEmitter.reEmit(event, ["Event.replaced"]);
}
return event;
}
+3 -3
View File
@@ -734,7 +734,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
if (baseApis.getUserId() !== userId) {
return;
}
console.log("Cross-signing: Self-verification done; requesting keys");
logger.log("Cross-signing: Self-verification done; requesting keys");
// This happens asynchronously, and we're not concerned about waiting for
// it. We return here in order to test.
return new Promise((resolve, reject) => {
@@ -748,7 +748,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
const crossSigning = new CrossSigningInfo(
original.userId,
{ getCrossSigningKey: async (type) => {
console.debug("Cross-signing: requesting secret",
logger.debug("Cross-signing: requesting secret",
type, deviceId);
const { promise } = client.requestSecret(
`m.cross_signing.${type}`, [deviceId],
@@ -811,6 +811,6 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
timeout,
]).then(resolve, reject);
}).catch((e) => {
console.warn("Cross-signing: failure while requesting keys:", e);
logger.warn("Cross-signing: failure while requesting keys:", e);
});
}
+11 -2
View File
@@ -848,6 +848,7 @@ class DeviceListUpdateSerialiser {
await _updateStoredDeviceKeysForUser(
this._olmDevice, userId, userStore, dkResponse || {},
this._baseApis.getUserId(), this._baseApis.deviceId,
);
// put the updates into the object that will be returned as our results
@@ -885,8 +886,9 @@ class DeviceListUpdateSerialiser {
}
async function _updateStoredDeviceKeysForUser(_olmDevice, userId, userStore,
userResult) {
async function _updateStoredDeviceKeysForUser(
_olmDevice, userId, userStore, userResult, localUserId, localDeviceId,
) {
let updated = false;
// remove any devices in the store which aren't in the response
@@ -896,6 +898,13 @@ async function _updateStoredDeviceKeysForUser(_olmDevice, userId, userStore,
}
if (!(deviceId in userResult)) {
if (userId === localUserId && deviceId === localDeviceId) {
logger.warn(
`Local device ${deviceId} missing from sync, skipping removal`,
);
continue;
}
logger.log("Device " + userId + ":" + deviceId +
" has been removed");
delete userStore[deviceId];
+2 -2
View File
@@ -344,14 +344,14 @@ class SSSSCryptoCallbacks {
}
}
addPrivateKey(keyId, privKey) {
addPrivateKey(keyId, keyInfo, privKey) {
this._privateKeys.set(keyId, privKey);
// Also pass along to application to cache if it wishes
if (
this._delegateCryptoCallbacks &&
this._delegateCryptoCallbacks.cacheSecretStorageKey
) {
this._delegateCryptoCallbacks.cacheSecretStorageKey(keyId, privKey);
this._delegateCryptoCallbacks.cacheSecretStorageKey(keyId, keyInfo, privKey);
}
}
}
+38 -3
View File
@@ -144,7 +144,7 @@ OlmDevice.prototype.init = async function(opts = {}) {
try {
if (fromExportedDevice) {
if (pickleKey) {
console.warn(
logger.warn(
'ignoring opts.pickleKey'
+ ' because opts.fromExportedDevice is present.',
);
@@ -477,6 +477,36 @@ OlmDevice.prototype.generateOneTimeKeys = function(numKeys) {
);
};
/**
* Generate a new fallback keys
*
* @return {Promise} Resolved once the account is saved back having generated the key
*/
OlmDevice.prototype.generateFallbackKey = async function() {
await this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
this._getAccount(txn, (account) => {
account.generate_fallback_key();
this._storeAccount(txn, account);
});
},
);
};
OlmDevice.prototype.getFallbackKey = async function() {
let result;
await this._cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
this._getAccount(txn, (account) => {
result = JSON.parse(account.fallback_key());
});
},
);
return result;
};
/**
* Generate a new outbound session
*
@@ -1029,9 +1059,14 @@ OlmDevice.prototype.addInboundGroupSession = async function(
+ senderKey + "/" + sessionId,
);
if (existingSession.first_known_index()
<= session.first_known_index()) {
<= session.first_known_index()
&& !(existingSession.first_known_index() == session.first_known_index()
&& !extraSessionData.untrusted
&& existingSessionData.untrusted)) {
// existing session has lower index (i.e. can
// decrypt more), so keep it
// decrypt more), or they have the same index and
// the new sessions trust does not win over the old
// sessions trust, so keep it
logger.log(
`Keeping existing megolm session ${sessionId}`,
);
+21 -16
View File
@@ -81,25 +81,27 @@ export class SecretStorage extends EventEmitter {
* @param {string} [keyId] the ID of the key. If not given, a random
* ID will be generated.
*
* @return {string} the ID of the key
* @return {object} An object with:
* keyId: {string} the ID of the key
* keyInfo: {object} details about the key (iv, mac, passphrase)
*/
async addKey(algorithm, opts, keyId) {
const keyData = {algorithm};
const keyInfo = {algorithm};
if (!opts) opts = {};
if (opts.name) {
keyData.name = opts.name;
keyInfo.name = opts.name;
}
if (algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (opts.passphrase) {
keyData.passphrase = opts.passphrase;
keyInfo.passphrase = opts.passphrase;
}
if (opts.key) {
const {iv, mac} = await SecretStorage._calculateKeyCheck(opts.key);
keyData.iv = iv;
keyData.mac = mac;
keyInfo.iv = iv;
keyInfo.mac = mac;
}
} else {
throw new Error(`Unknown key algorithm ${opts.algorithm}`);
@@ -116,10 +118,13 @@ export class SecretStorage extends EventEmitter {
}
await this._baseApis.setAccountData(
`m.secret_storage.key.${keyId}`, keyData,
`m.secret_storage.key.${keyId}`, keyInfo,
);
return keyId;
return {
keyId,
keyInfo,
};
}
/**
@@ -247,7 +252,7 @@ export class SecretStorage extends EventEmitter {
) {
const hasKey = await this.hasKey(keys[0]);
if (hasKey) {
console.log("Fixing up passthrough secret: " + name);
logger.log("Fixing up passthrough secret: " + name);
await this.storePassthrough(name, keys[0]);
const newData = await this._baseApis.getAccountDataFromServer(name);
return newData;
@@ -457,13 +462,13 @@ export class SecretStorage extends EventEmitter {
if (!this._cryptoCallbacks.onSecretRequested) {
return;
}
const secret = await this._cryptoCallbacks.onSecretRequested({
user_id: sender,
device_id: deviceId,
request_id: content.request_id,
name: content.name,
device_trust: this._baseApis.checkDeviceTrust(sender, deviceId),
});
const secret = await this._cryptoCallbacks.onSecretRequested(
sender,
deviceId,
content.request_id,
content.name,
this._baseApis.checkDeviceTrust(sender, deviceId),
);
if (secret) {
logger.info(`Preparing ${content.name} secret for ${deviceId}`);
const payload = {
+50 -34
View File
@@ -264,11 +264,14 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
await Promise.all([
(async () => {
// share keys with devices that we already have a session for
logger.debug(`Sharing keys with existing Olm sessions in ${this._roomId}`);
await this._shareKeyWithOlmSessions(
session, key, payload, olmSessions,
);
logger.debug(`Shared keys with existing Olm sessions in ${this._roomId}`);
})(),
(async () => {
logger.debug(`Sharing keys with new Olm sessions in ${this._roomId}`);
const errorDevices = [];
// meanwhile, establish olm sessions for devices that we don't
@@ -319,8 +322,10 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
} else {
await this._notifyFailedOlmDevices(session, key, errorDevices);
}
logger.debug(`Shared keys with new Olm sessions in ${this._roomId}`);
})(),
(async () => {
logger.debug(`Notifying blocked devices in ${this._roomId}`);
// also, notify blocked devices that they're blocked
const blockedMap = {};
for (const [userId, userBlockedDevices] of Object.entries(blocked)) {
@@ -336,6 +341,7 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
}
await this._notifyBlockedDevices(session, blockedMap);
logger.debug(`Notified blocked devices in ${this._roomId}`);
})(),
]);
};
@@ -348,6 +354,11 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
// first wait for the previous share to complete
const prom = this._setupPromise.then(prepareSession);
// Ensure any failures are logged for debugging
prom.catch(e => {
logger.error(`Failed to ensure outbound session in ${this._roomId}`, e);
});
// _setupPromise resolves to `session` whether or not the share succeeds
this._setupPromise = prom.then(returnSession, returnSession);
@@ -369,17 +380,11 @@ MegolmEncryption.prototype._prepareNewSession = async function() {
key.key, {ed25519: this._olmDevice.deviceEd25519Key},
);
if (this._crypto.backupInfo) {
// don't wait for it to complete
this._crypto.backupGroupSession(
this._roomId, this._olmDevice.deviceCurve25519Key, [],
sessionId, key.key,
).catch((e) => {
// This throws if the upload failed, but this is fine
// since it will have written it to the db and will retry.
logger.log("Failed to back up megolm session", e);
});
}
// don't wait for it to complete
this._crypto.backupGroupSession(
this._roomId, this._olmDevice.deviceCurve25519Key, [],
sessionId, key.key,
);
return new OutboundSessionInfo(sessionId);
};
@@ -846,24 +851,41 @@ MegolmEncryption.prototype.prepareToEncrypt = function(room) {
// We're already preparing something, so don't do anything else.
// FIXME: check if we need to restart
// (https://github.com/matrix-org/matrix-js-sdk/issues/1255)
const elapsedTime = Date.now() - this.encryptionPreparationMetadata.startTime;
logger.debug(
`Already started preparing to encrypt for ${this._roomId} ` +
`${elapsedTime} ms ago, skipping`,
);
return;
}
logger.debug(`Preparing to encrypt events for ${this._roomId}`);
this.encryptionPreparationMetadata = {
startTime: Date.now(),
};
this.encryptionPreparation = (async () => {
const [devicesInRoom, blocked] = await this._getDevicesInRoom(room);
try {
logger.debug(`Getting devices in ${this._roomId}`);
const [devicesInRoom, blocked] = await this._getDevicesInRoom(room);
if (this._crypto.getGlobalErrorOnUnknownDevices()) {
// Drop unknown devices for now. When the message gets sent, we'll
// throw an error, but we'll still be prepared to send to the known
// devices.
this._removeUnknownDevices(devicesInRoom);
if (this._crypto.getGlobalErrorOnUnknownDevices()) {
// Drop unknown devices for now. When the message gets sent, we'll
// throw an error, but we'll still be prepared to send to the known
// devices.
this._removeUnknownDevices(devicesInRoom);
}
logger.debug(`Ensuring outbound session in ${this._roomId}`);
await this._ensureOutboundSession(devicesInRoom, blocked, true);
logger.debug(`Ready to encrypt events for ${this._roomId}`);
} catch (e) {
logger.error(`Failed to prepare to encrypt events for ${this._roomId}`, e);
} finally {
delete this.encryptionPreparationMetadata;
delete this.encryptionPreparation;
}
await this._ensureOutboundSession(devicesInRoom, blocked, true);
delete this.encryptionPreparation;
})();
};
@@ -1347,18 +1369,12 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
}
});
}).then(() => {
if (this._crypto.backupInfo) {
// don't wait for the keys to be backed up for the server
this._crypto.backupGroupSession(
content.room_id, senderKey, forwardingKeyChain,
content.session_id, content.session_key, keysClaimed,
exportFormat,
).catch((e) => {
// This throws if the upload failed, but this is fine
// since it will have written it to the db and will retry.
logger.log("Failed to back up megolm session", e);
});
}
// don't wait for the keys to be backed up for the server
this._crypto.backupGroupSession(
content.room_id, senderKey, forwardingKeyChain,
content.session_id, content.session_key, keysClaimed,
exportFormat,
);
}).catch((e) => {
logger.error(`Error handling m.room_key_event: ${e}`);
});
@@ -1564,7 +1580,7 @@ MegolmDecryption.prototype.importRoomKey = function(session, opts = {}) {
true,
opts.untrusted ? { untrusted: opts.untrusted } : {},
).then(() => {
if (this._crypto.backupInfo && opts.source !== "backup") {
if (opts.source !== "backup") {
// don't wait for it to complete
this._crypto.backupGroupSession(
session.room_id,
+288
View File
@@ -0,0 +1,288 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {decodeBase64, encodeBase64} from './olmlib';
import {IndexedDBCryptoStore} from '../crypto/store/indexeddb-crypto-store';
import {decryptAES, encryptAES} from './aes';
import anotherjson from "another-json";
import {logger} from '../logger';
// FIXME: these types should eventually go in a different file
type Signatures = Record<string, Record<string, string>>;
interface DeviceKeys {
algorithms: Array<string>;
device_id: string; // eslint-disable-line camelcase
user_id: string; // eslint-disable-line camelcase
keys: Record<string, string>;
signatures?: Signatures;
}
interface OneTimeKey {
key: string;
fallback?: boolean;
signatures?: Signatures;
}
export const DEHYDRATION_ALGORITHM = "org.matrix.msc2697.v1.olm.libolm_pickle";
const oneweek = 7 * 24 * 60 * 60 * 1000;
export class DehydrationManager {
private inProgress = false;
private timeoutId: any;
private key: Uint8Array;
private keyInfo: {[props: string]: any};
private deviceDisplayName: string;
constructor(private crypto) {
this.getDehydrationKeyFromCache();
}
async getDehydrationKeyFromCache(): Promise<void> {
return await this.crypto._cryptoStore.doTxn(
'readonly',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
this.crypto._cryptoStore.getSecretStorePrivateKey(
txn,
async (result) => {
if (result) {
const {key, keyInfo, deviceDisplayName, time} = result;
const pickleKey = Buffer.from(this.crypto._olmDevice._pickleKey);
const decrypted = await decryptAES(key, pickleKey, DEHYDRATION_ALGORITHM);
this.key = decodeBase64(decrypted);
this.keyInfo = keyInfo;
this.deviceDisplayName = deviceDisplayName;
const now = Date.now();
const delay = Math.max(1, time + oneweek - now);
this.timeoutId = global.setTimeout(
this.dehydrateDevice.bind(this), delay,
);
}
},
"dehydration",
);
},
);
}
/** set the key, and queue periodic dehydration to the server in the background */
async setKeyAndQueueDehydration(
key: Uint8Array, keyInfo: {[props: string]: any} = {},
deviceDisplayName: string = undefined,
): Promise<void> {
const matches = await this.setKey(key, keyInfo, deviceDisplayName);
if (!matches) {
// start dehydration in the background
this.dehydrateDevice();
}
}
async setKey(
key: Uint8Array, keyInfo: {[props: string]: any} = {},
deviceDisplayName: string = undefined,
): Promise<boolean> {
if (!key) {
// unsetting the key -- cancel any pending dehydration task
if (this.timeoutId) {
global.clearTimeout(this.timeoutId);
this.timeoutId = undefined;
}
// clear storage
await this.crypto._cryptoStore.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
this.crypto._cryptoStore.storeSecretStorePrivateKey(
txn, "dehydration", null,
);
},
);
this.key = undefined;
this.keyInfo = undefined;
return;
}
// Check to see if it's the same key as before. If it's different,
// dehydrate a new device. If it's the same, we can keep the same
// device. (Assume that keyInfo and deviceDisplayName will be the
// same if the key is the same.)
let matches: boolean = this.key && key.length == this.key.length;
for (let i = 0; matches && i < key.length; i++) {
if (key[i] != this.key[i]) {
matches = false;
}
}
if (!matches) {
this.key = key;
this.keyInfo = keyInfo;
this.deviceDisplayName = deviceDisplayName;
}
return matches;
}
/** returns the device id of the newly created dehydrated device */
async dehydrateDevice(): Promise<string> {
if (this.inProgress) {
logger.log("Dehydration already in progress -- not starting new dehydration");
return;
}
this.inProgress = true;
if (this.timeoutId) {
global.clearTimeout(this.timeoutId);
this.timeoutId = undefined;
}
try {
const pickleKey = Buffer.from(this.crypto._olmDevice._pickleKey);
// update the crypto store with the timestamp
const key = await encryptAES(encodeBase64(this.key), pickleKey, DEHYDRATION_ALGORITHM);
await this.crypto._cryptoStore.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
this.crypto._cryptoStore.storeSecretStorePrivateKey(
txn, "dehydration",
{
keyInfo: this.keyInfo,
key,
deviceDisplayName: this.deviceDisplayName,
time: Date.now(),
},
);
},
);
logger.log("Attempting to dehydrate device");
logger.log("Creating account");
// create the account and all the necessary keys
const account = new global.Olm.Account();
account.create();
const e2eKeys = JSON.parse(account.identity_keys());
const maxKeys = account.max_number_of_one_time_keys();
// FIXME: generate in small batches?
account.generate_one_time_keys(maxKeys / 2);
account.generate_fallback_key();
const otks: Record<string, string> = JSON.parse(account.one_time_keys());
const fallbacks: Record<string, string> = JSON.parse(account.fallback_key());
account.mark_keys_as_published();
// dehydrate the account and store it on the server
const pickledAccount = account.pickle(new Uint8Array(this.key));
const deviceData: {[props: string]: any} = {
algorithm: DEHYDRATION_ALGORITHM,
account: pickledAccount,
};
if (this.keyInfo.passphrase) {
deviceData.passphrase = this.keyInfo.passphrase;
}
logger.log("Uploading account to server");
const dehydrateResult = await this.crypto._baseApis._http.authedRequest(
undefined,
"PUT",
"/dehydrated_device",
undefined,
{
device_data: deviceData,
initial_device_display_name: this.deviceDisplayName,
},
{
prefix: "/_matrix/client/unstable/org.matrix.msc2697.v2",
},
);
// send the keys to the server
const deviceId = dehydrateResult.device_id;
logger.log("Preparing device keys", deviceId);
const deviceKeys: DeviceKeys = {
algorithms: this.crypto._supportedAlgorithms,
device_id: deviceId,
user_id: this.crypto._userId,
keys: {
[`ed25519:${deviceId}`]: e2eKeys.ed25519,
[`curve25519:${deviceId}`]: e2eKeys.curve25519,
},
};
const deviceSignature = account.sign(anotherjson.stringify(deviceKeys));
deviceKeys.signatures = {
[this.crypto._userId]: {
[`ed25519:${deviceId}`]: deviceSignature,
},
};
if (this.crypto._crossSigningInfo.getId("self_signing")) {
await this.crypto._crossSigningInfo.signObject(deviceKeys, "self_signing");
}
logger.log("Preparing one-time keys");
const oneTimeKeys = {};
for (const [keyId, key] of Object.entries(otks.curve25519)) {
const k: OneTimeKey = {key};
const signature = account.sign(anotherjson.stringify(k));
k.signatures = {
[this.crypto._userId]: {
[`ed25519:${deviceId}`]: signature,
},
};
oneTimeKeys[`signed_curve25519:${keyId}`] = k;
}
logger.log("Preparing fallback keys");
const fallbackKeys = {};
for (const [keyId, key] of Object.entries(fallbacks.curve25519)) {
const k: OneTimeKey = {key, fallback: true};
const signature = account.sign(anotherjson.stringify(k));
k.signatures = {
[this.crypto._userId]: {
[`ed25519:${deviceId}`]: signature,
},
};
fallbackKeys[`signed_curve25519:${keyId}`] = k;
}
logger.log("Uploading keys to server");
await this.crypto._baseApis._http.authedRequest(
undefined,
"POST",
"/keys/upload/" + encodeURI(deviceId),
undefined,
{
"device_keys": deviceKeys,
"one_time_keys": oneTimeKeys,
"org.matrix.msc2732.fallback_keys": fallbackKeys,
},
);
logger.log("Done dehydrating");
// dehydrate again in a week
this.timeoutId = global.setTimeout(
this.dehydrateDevice.bind(this), oneweek,
);
return deviceId;
} finally {
this.inProgress = false;
}
}
private stop() {
if (this.timeoutId) {
global.clearTimeout(this.timeoutId);
this.timeoutId = undefined;
}
}
}
+101 -64
View File
@@ -56,6 +56,8 @@ import {ToDeviceChannel, ToDeviceRequests} from "./verification/request/ToDevice
import {IllegalMethod} from "./verification/IllegalMethod";
import {KeySignatureUploadError} from "../errors";
import {decryptAES, encryptAES} from './aes';
import {DehydrationManager} from './dehydration';
import { MatrixEvent } from "../models/event";
const DeviceVerification = DeviceInfo.DeviceVerification;
@@ -146,7 +148,7 @@ export function Crypto(baseApis, sessionStore, userId, deviceId,
method,
);
} else {
console.warn(`Excluding unknown verification method ${method}`);
logger.warn(`Excluding unknown verification method ${method}`);
}
}
} else {
@@ -243,6 +245,8 @@ export function Crypto(baseApis, sessionStore, userId, deviceId,
baseApis, cryptoCallbacks,
);
this._dehydrationManager = new DehydrationManager(this);
// Assuming no app-supplied callback, default to getting from SSSS.
if (!cryptoCallbacks.getCrossSigningKey && cryptoCallbacks.getSecretStorageKey) {
cryptoCallbacks.getCrossSigningKey = async (type) => {
@@ -630,7 +634,7 @@ Crypto.prototype.bootstrapCrossSigning = async function({
* containing the key, or rejects if the key cannot be obtained.
* Returns:
* {Promise} A promise which resolves to key creation data for
* SecretStorage#addKey: an object with `passphrase` and/or `pubkey` fields.
* SecretStorage#addKey: an object with `passphrase` etc fields.
*/
Crypto.prototype.bootstrapSecretStorage = async function({
createSecretStorageKey = async () => ({ }),
@@ -660,13 +664,13 @@ Crypto.prototype.bootstrapSecretStorage = async function({
opts.key = privateKey;
}
const keyId = await secretStorage.addKey(
const { keyId, keyInfo } = await secretStorage.addKey(
SECRET_STORAGE_ALGORITHM_V1_AES, opts,
);
if (privateKey) {
// make the private key available to encrypt 4S secrets
builder.ssssCryptoCallbacks.addPrivateKey(keyId, privateKey);
builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyInfo, privateKey);
}
await secretStorage.setDefaultKeyId(keyId);
@@ -679,9 +683,9 @@ Crypto.prototype.bootstrapSecretStorage = async function({
{keys: {[keyId]: keyInfo}}, "",
);
if (key) {
const keyData = key[1];
builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyData);
const {iv, mac} = await SecretStorage._calculateKeyCheck(keyData);
const privateKey = key[1];
builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyInfo, privateKey);
const {iv, mac} = await SecretStorage._calculateKeyCheck(privateKey);
keyInfo.iv = iv;
keyInfo.mac = mac;
@@ -692,6 +696,26 @@ Crypto.prototype.bootstrapSecretStorage = async function({
}
};
const signKeyBackupWithCrossSigning = async (keyBackupAuthData) => {
if (
this._crossSigningInfo.getId() &&
await this._crossSigningInfo.isStoredInKeyCache("master")
) {
try {
logger.log("Adding cross-signing signature to key backup");
await this._crossSigningInfo.signObject(keyBackupAuthData, "master");
} catch (e) {
// This step is not critical (just helpful), so we catch here
// and continue if it fails.
logger.error("Signing key backup with cross-signing keys failed", e);
}
} else {
logger.warn(
"Cross-signing keys not available, skipping signature on key backup",
);
}
};
const oldSSSSKey = await this.getSecretStorageKey();
const [oldKeyId, oldKeyInfo] = oldSSSSKey || [null, null];
const storageExists = (
@@ -758,19 +782,7 @@ Crypto.prototype.bootstrapSecretStorage = async function({
// The backup is trusted because the user provided the private key.
// Sign the backup with the cross-signing key so the key backup can
// be trusted via cross-signing.
if (
this._crossSigningInfo.getId() &&
this._crossSigningInfo.isStoredInKeyCache("master")
) {
logger.log("Adding cross-signing signature to key backup");
await this._crossSigningInfo.signObject(
keyBackupInfo.auth_data, "master",
);
} else {
logger.warn(
"Cross-signing keys not available, skipping signature on key backup",
);
}
await signKeyBackupWithCrossSigning(keyBackupInfo.auth_data);
builder.addSessionBackup(keyBackupInfo);
} else {
@@ -821,18 +833,8 @@ Crypto.prototype.bootstrapSecretStorage = async function({
auth_data: info.auth_data,
};
if (
this._crossSigningInfo.getId() &&
this._crossSigningInfo.isStoredInKeyCache("master")
) {
// sign with cross-sign master key
logger.log("Adding cross-signing signature to key backup");
await this._crossSigningInfo.signObject(data.auth_data, "master");
} else {
logger.warn(
"Cross-signing keys not available, skipping signature on key backup",
);
}
// Sign with cross-signing master key
await signKeyBackupWithCrossSigning(data.auth_data);
// sign with the device fingerprint
await this._signObject(data.auth_data);
@@ -1751,6 +1753,7 @@ Crypto.prototype.start = function() {
Crypto.prototype.stop = function() {
this._outgoingRoomKeyRequestManager.stop();
this._deviceList.stop();
this._dehydrationManager.stop();
};
/**
@@ -1856,6 +1859,14 @@ Crypto.prototype.updateOneTimeKeyCount = function(currentCount) {
}
};
Crypto.prototype.setNeedsNewFallback = function(needsNewFallback) {
this._needsNewFallback = !!needsNewFallback;
};
Crypto.prototype.getNeedsNewFallback = function() {
return this._needsNewFallback;
};
// check if it's time to upload one-time keys, and do so if so.
function _maybeUploadOneTimeKeys(crypto) {
// frequency with which to check & upload one-time keys
@@ -1903,27 +1914,31 @@ function _maybeUploadOneTimeKeys(crypto) {
// out stale private keys that won't receive a message.
const keyLimit = Math.floor(maxOneTimeKeys / 2);
function uploadLoop(keyCount) {
if (keyLimit <= keyCount) {
// If we don't need to generate any more keys then we are done.
return Promise.resolve();
}
async function uploadLoop(keyCount) {
while (keyLimit > keyCount || crypto.getNeedsNewFallback()) {
// Ask olm to generate new one time keys, then upload them to synapse.
if (keyLimit > keyCount) {
logger.info("generating oneTimeKeys");
const keysThisLoop = Math.min(keyLimit - keyCount, maxKeysPerCycle);
await crypto._olmDevice.generateOneTimeKeys(keysThisLoop);
}
const keysThisLoop = Math.min(keyLimit - keyCount, maxKeysPerCycle);
if (crypto.getNeedsNewFallback()) {
logger.info("generating fallback key");
await crypto._olmDevice.generateFallbackKey();
}
// Ask olm to generate new one time keys, then upload them to synapse.
return crypto._olmDevice.generateOneTimeKeys(keysThisLoop).then(() => {
return _uploadOneTimeKeys(crypto);
}).then((res) => {
logger.info("calling _uploadOneTimeKeys");
const res = await _uploadOneTimeKeys(crypto);
if (res.one_time_key_counts && res.one_time_key_counts.signed_curve25519) {
// if the response contains a more up to date value use this
// for the next loop
return uploadLoop(res.one_time_key_counts.signed_curve25519);
keyCount = res.one_time_key_counts.signed_curve25519;
} else {
throw new Error("response for uploading keys does not contain "
+ "one_time_key_counts.signed_curve25519");
throw new Error("response for uploading keys does not contain " +
"one_time_key_counts.signed_curve25519");
}
});
}
}
crypto._oneTimeKeyCheckInProgress = true;
@@ -1955,11 +1970,22 @@ function _maybeUploadOneTimeKeys(crypto) {
// returns a promise which resolves to the response
async function _uploadOneTimeKeys(crypto) {
const promises = [];
const fallbackJson = {};
if (crypto.getNeedsNewFallback()) {
const fallbackKeys = await crypto._olmDevice.getFallbackKey();
for (const [keyId, key] of Object.entries(fallbackKeys.curve25519)) {
const k = { key, fallback: true };
fallbackJson["signed_curve25519:" + keyId] = k;
promises.push(crypto._signObject(k));
}
crypto.setNeedsNewFallback(false);
}
const oneTimeKeys = await crypto._olmDevice.getOneTimeKeys();
const oneTimeJson = {};
const promises = [];
for (const keyId in oneTimeKeys.curve25519) {
if (oneTimeKeys.curve25519.hasOwnProperty(keyId)) {
const k = {
@@ -1973,7 +1999,8 @@ async function _uploadOneTimeKeys(crypto) {
await Promise.all(promises);
const res = await crypto._baseApis.uploadKeysRequest({
one_time_keys: oneTimeJson,
"one_time_keys": oneTimeJson,
"org.matrix.msc2732.fallback_keys": fallbackJson,
});
await crypto._olmDevice.markKeysAsPublished();
@@ -2400,7 +2427,7 @@ Crypto.prototype.getEventSenderDeviceInfo = function(event) {
if (claimedKey !== device.getFingerprint()) {
logger.warn(
"Event " + event.getId() + " claims ed25519 key " + claimedKey +
"but sender device has key " + device.getFingerprint());
" but sender device has key " + device.getFingerprint());
return null;
}
@@ -2613,7 +2640,10 @@ Crypto.prototype.trackRoomDevices = function(roomId) {
let promise = this._roomDeviceTrackingState[roomId];
if (!promise) {
promise = trackMembers();
this._roomDeviceTrackingState[roomId] = promise;
this._roomDeviceTrackingState[roomId] = promise.catch(err => {
this._roomDeviceTrackingState[roomId] = null;
throw err;
});
}
return promise;
};
@@ -2855,18 +2885,18 @@ Crypto.prototype.backupGroupSession = async function(
sessionId, sessionKey, keysClaimed,
exportFormat,
) {
if (!this.backupInfo) {
throw new Error("Key backups are not enabled");
}
await this._cryptoStore.markSessionsNeedingBackup([{
senderKey: senderKey,
sessionId: sessionId,
}]);
// don't wait for this to complete: it will delay so
// happens in the background
this.scheduleKeyBackupSend();
if (this.backupInfo) {
// don't wait for this to complete: it will delay so
// happens in the background
this.scheduleKeyBackupSend();
}
// if this.backupInfo is not set, then the keys will be backed up when
// client.enableKeyBackup is called
};
/**
@@ -2999,19 +3029,26 @@ Crypto.prototype.encryptEvent = async function(event, room) {
* finished decrypting. Rejects with an `algorithms.DecryptionError` if there
* is a problem decrypting the event.
*/
Crypto.prototype.decryptEvent = function(event) {
Crypto.prototype.decryptEvent = async function(event) {
if (event.isRedacted()) {
return Promise.resolve({
const redactionEvent = new MatrixEvent(event.getUnsigned().redacted_because);
const decryptedEvent = await this.decryptEvent(redactionEvent);
return {
clearEvent: {
room_id: event.getRoomId(),
type: "m.room.message",
content: {},
unsigned: {
redacted_because: decryptedEvent.clearEvent,
},
},
});
};
} else {
const content = event.getWireContent();
const alg = this._getRoomDecryptor(event.getRoomId(), content.algorithm);
return await alg.decryptEvent(event);
}
const content = event.getWireContent();
const alg = this._getRoomDecryptor(event.getRoomId(), content.algorithm);
return alg.decryptEvent(event);
};
/**
+3 -2
View File
@@ -26,6 +26,7 @@ import {
newUserCancelledError,
} from './Error';
import {encodeUnpaddedBase64, decodeBase64} from "../olmlib";
import {logger} from '../../logger';
export const SHOW_QR_CODE_METHOD = "m.qr_code.show.v1";
export const SCAN_QR_CODE_METHOD = "m.qr_code.scan.v1";
@@ -94,7 +95,7 @@ export class ReciprocateQRCode extends Base {
if (!targetKey) throw newKeyMismatchError();
if (keyInfo !== targetKey) {
console.error("key ID from key info does not match");
logger.error("key ID from key info does not match");
throw newKeyMismatchError();
}
for (const deviceKeyId in device.keys) {
@@ -102,7 +103,7 @@ export class ReciprocateQRCode extends Base {
const deviceTargetKey = keys[deviceKeyId];
if (!deviceTargetKey) throw newKeyMismatchError();
if (device.keys[deviceKeyId] !== deviceTargetKey) {
console.error("master key does not match");
logger.error("master key does not match");
throw newKeyMismatchError();
}
}
+1 -1
View File
@@ -961,7 +961,7 @@ export async function retryNetworkOperation(maxAttempts, callback) {
try {
if (attempts > 0) {
const timeout = 1000 * Math.pow(2, attempts);
console.log(`network operation failed ${attempts} times,` +
logger.log(`network operation failed ${attempts} times,` +
` retrying in ${timeout}ms...`);
await new Promise(r => setTimeout(r, timeout));
}
+2
View File
@@ -40,11 +40,13 @@ log.methodFactory = function(methodName, logLevel, loggerName) {
methodName === "warn" ||
methodName === "trace" ||
methodName === "info";
/* eslint-disable no-console */
if (supportedByConsole) {
return console[methodName](...args);
} else {
return console.log(...args);
}
/* eslint-enable no-console */
};
};
+46 -16
View File
@@ -1,7 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -113,7 +113,7 @@ export function setCryptoStoreFactory(fac) {
cryptoStoreFactory = fac;
}
interface ICreateClientOpts {
export interface ICreateClientOpts {
baseUrl: string;
idBaseUrl?: string;
store?: Store;
@@ -126,6 +126,7 @@ interface ICreateClientOpts {
identityServer?: any;
localTimeoutMs?: number;
useAuthorizationHeader?: boolean;
timelineSupport?: boolean;
queryParams?: Record<string, unknown>;
deviceToImport?: {
olmDevice: {
@@ -136,26 +137,55 @@ interface ICreateClientOpts {
userId: string;
deviceId: string;
};
pickleKey?: string;
sessionStore?: any;
unstableClientRelationAggregation?: boolean;
verificationMethods?: Array<any>;
forceTURN?: boolean;
supportsCallTransfer?: boolean,
fallbackICEServerAllowed?: boolean;
cryptoCallbacks?: {
getCrossSigningKey?: (keyType: string, pubKey: Uint8Array) => Promise<Uint8Array>;
saveCrossSigningKeys?: (keys: Record<string, Uint8Array>) => unknown;
shouldUpgradeDeviceVerifications?: (
users: Record<string, any>
) => Promise<Array<string>>;
getSecretStorageKey?: (
keys: {keys: Record<string, {pubkey: Uint8Array}>}, name: string
) => Promise<[string, Uint8Array] | null>;
cacheSecretStorageKey?: (keyId: string, key: Uint8Array) => unknown;
onSecretRequested?: (
name: string, userId: string, deviceId: string,
requestId: string, deviceTrust: any
) => Promise<string>;
cryptoCallbacks?: ICryptoCallbacks;
}
export interface ICryptoCallbacks {
getCrossSigningKey?: (keyType: string, pubKey: Uint8Array) => Promise<Uint8Array>;
saveCrossSigningKeys?: (keys: Record<string, Uint8Array>) => void;
shouldUpgradeDeviceVerifications?: (
users: Record<string, any>
) => Promise<string[]>;
getSecretStorageKey?: (
keys: {keys: Record<string, ISecretStorageKeyInfo>}, name: string
) => Promise<[string, Uint8Array] | null>;
cacheSecretStorageKey?: (
keyId: string, keyInfo: ISecretStorageKeyInfo, key: Uint8Array
) => void;
onSecretRequested?: (
userId: string, deviceId: string,
requestId: string, secretName: string, deviceTrust: IDeviceTrustLevel
) => Promise<string>;
getDehydrationKey?: (
keyInfo: ISecretStorageKeyInfo,
checkFunc: (Uint8Array) => void,
) => Promise<Uint8Array>;
}
// TODO: Move this to `SecretStorage` once converted
export interface ISecretStorageKeyInfo {
passphrase?: {
algorithm: "m.pbkdf2";
iterations: number;
salt: string;
};
iv?: string;
mac?: string;
}
// TODO: Move this to `CrossSigning` once converted
export interface IDeviceTrustLevel {
isVerified(): boolean;
isCrossSigningVerified(): boolean;
isLocallyVerified(): boolean;
isTofu(): boolean;
}
/**
+1 -1
View File
@@ -806,7 +806,7 @@ EventTimelineSet.prototype.aggregateRelations = function(event) {
this.room,
);
isNewRelations = true;
relatesToEvent = this.findEventById(relatesToEventId);
relatesToEvent = this.findEventById(relatesToEventId) || this.room.getPendingEvent(relatesToEventId);
if (relatesToEvent) {
relationsWithEventType.setTargetEvent(relatesToEvent);
}
+31 -2
View File
@@ -169,6 +169,14 @@ export const MatrixEvent = function(
allows for a unique ID which does not change when the event comes back down sync.
*/
this._txnId = null;
/* Set an approximate timestamp for the event relative the local clock.
* This will inherently be approximate because it doesn't take into account
* the time between the server putting the 'age' field on the event as it sent
* it to us and the time we're now constructing this event, but that's better
* than assuming the local clock is in sync with the origin HS's clock.
*/
this._localTimestamp = Date.now() - this.getAge();
};
utils.inherits(MatrixEvent, EventEmitter);
@@ -311,11 +319,12 @@ utils.extend(MatrixEvent.prototype, {
/**
* 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.
* This is the 'age' field adjusted according to how long this client has
* had the event.
* @return {Number} The age of this event in milliseconds.
*/
getLocalAge: function() {
return Date.now() - this.getTs();
return Date.now() - this._localTimestamp;
},
/**
@@ -802,6 +811,24 @@ utils.extend(MatrixEvent.prototype, {
return this.getType() === "m.room.redaction";
},
/**
* Get the (decrypted, if necessary) redaction event JSON
* if event was redacted
*
* @returns {object} The redaction event JSON, or an empty object
*/
getRedactionEvent: function() {
if (!this.isRedacted()) return null;
if (this._clearEvent.unsigned) {
return this._clearEvent.unsigned.redacted_because;
} else if (this.event.unsigned.redacted_because) {
return this.event.unsigned.redacted_because;
} else {
return {};
}
},
/**
* Get the push actions, if known, for this event
*
@@ -904,6 +931,8 @@ utils.extend(MatrixEvent.prototype, {
/**
* Set an event that replaces the content of this event, through an m.replace relation.
*
* @fires module:models/event.MatrixEvent#"Event.replaced"
*
* @param {MatrixEvent?} newEvent the event with the replacing content, if any.
*/
makeReplaced(newEvent) {
+5 -4
View File
@@ -16,6 +16,7 @@ limitations under the License.
import {EventEmitter} from 'events';
import {EventStatus} from '../models/event';
import {logger} from '../logger';
/**
* A container for relation events that supports easy access to common ways of
@@ -60,7 +61,7 @@ export class Relations extends EventEmitter {
const relation = event.getRelation();
if (!relation) {
console.error("Event must have relation info");
logger.error("Event must have relation info");
return;
}
@@ -68,7 +69,7 @@ export class Relations extends EventEmitter {
const eventType = event.getType();
if (this.relationType !== relationType || this.eventType !== eventType) {
console.error("Event relation info doesn't match this container");
logger.error("Event relation info doesn't match this container");
return;
}
@@ -104,7 +105,7 @@ export class Relations extends EventEmitter {
const relation = event.getRelation();
if (!relation) {
console.error("Event must have relation info");
logger.error("Event must have relation info");
return;
}
@@ -112,7 +113,7 @@ export class Relations extends EventEmitter {
const eventType = event.getType();
if (this.relationType !== relationType || this.eventType !== eventType) {
console.error("Event relation info doesn't match this container");
logger.error("Event relation info doesn't match this container");
return;
}
+18 -6
View File
@@ -37,8 +37,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 = '5';
const SAFE_ROOM_VERSIONS = ['1', '2', '3', '4', '5'];
const KNOWN_SAFE_ROOM_VERSION = '6';
const SAFE_ROOM_VERSIONS = ['1', '2', '3', '4', '5', '6'];
function synthesizeReceipt(userId, event, receiptType) {
// console.log("synthesizing receipt for "+event.getId());
@@ -358,21 +358,33 @@ Room.prototype.getPendingEvents = function() {
/**
* Check whether the pending event list contains a given event by ID.
* If pending event ordering is not "detached" then this returns false.
*
* @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 false;
}
return this._pendingEventList.some(event => event.getId() === eventId);
};
/**
* Get a specific event from the pending event list, if configured, null otherwise.
*
* @param {string} eventId The event ID to check for.
* @return {MatrixEvent}
*/
Room.prototype.getPendingEvent = function(eventId) {
if (this._opts.pendingEventOrdering !== "detached") {
return null;
}
return this._pendingEventList.find(event => event.getId() === eventId);
};
/**
* Get the live unfiltered timeline for this room.
*
+2 -1
View File
@@ -16,6 +16,7 @@ limitations under the License.
*/
import {escapeRegExp, globToRegexp, isNullOrUndefined} from "./utils";
import {logger} from './logger';
/**
* @module pushprocessor
@@ -444,7 +445,7 @@ PushProcessor.rewriteDefaultRules = function(incomingRules) {
} else {
// Add the rule
const ruleId = override.rule_id;
console.warn(`Adding default global override for ${ruleId}`);
logger.warn(`Adding default global override for ${ruleId}`);
globalOverrides.push(override);
}
}
+1 -1
View File
@@ -164,7 +164,7 @@ MatrixScheduler.RETRY_BACKOFF_RATELIMIT = function(event, attempts, err) {
if (err.name === "M_LIMIT_EXCEEDED") {
const waitTime = err.data.retry_after_ms;
if (waitTime) {
if (waitTime > 0) {
return waitTime;
}
}
+2 -2
View File
@@ -226,7 +226,7 @@ LocalIndexedDBStoreBackend.prototype = {
account_data: {
events: accountData,
},
});
}, true);
});
},
@@ -416,7 +416,7 @@ LocalIndexedDBStoreBackend.prototype = {
},
syncToDatabase: function(userTuples) {
const syncData = this._syncAccumulator.getJSON();
const syncData = this._syncAccumulator.getJSON(true);
return Promise.all([
this._persistUserPresenceEvents(userTuples),
+47 -12
View File
@@ -87,8 +87,8 @@ export class SyncAccumulator {
};
}
accumulate(syncResponse) {
this._accumulateRooms(syncResponse);
accumulate(syncResponse, fromDatabase) {
this._accumulateRooms(syncResponse, fromDatabase);
this._accumulateGroups(syncResponse);
this._accumulateAccountData(syncResponse);
this.nextBatch = syncResponse.next_batch;
@@ -107,35 +107,36 @@ export class SyncAccumulator {
/**
* Accumulate incremental /sync room data.
* @param {Object} syncResponse the complete /sync JSON
* @param {boolean} fromDatabase True if the sync response is one saved to the database
*/
_accumulateRooms(syncResponse) {
_accumulateRooms(syncResponse, fromDatabase) {
if (!syncResponse.rooms) {
return;
}
if (syncResponse.rooms.invite) {
Object.keys(syncResponse.rooms.invite).forEach((roomId) => {
this._accumulateRoom(
roomId, "invite", syncResponse.rooms.invite[roomId],
roomId, "invite", syncResponse.rooms.invite[roomId], fromDatabase,
);
});
}
if (syncResponse.rooms.join) {
Object.keys(syncResponse.rooms.join).forEach((roomId) => {
this._accumulateRoom(
roomId, "join", syncResponse.rooms.join[roomId],
roomId, "join", syncResponse.rooms.join[roomId], fromDatabase,
);
});
}
if (syncResponse.rooms.leave) {
Object.keys(syncResponse.rooms.leave).forEach((roomId) => {
this._accumulateRoom(
roomId, "leave", syncResponse.rooms.leave[roomId],
roomId, "leave", syncResponse.rooms.leave[roomId], fromDatabase,
);
});
}
}
_accumulateRoom(roomId, category, data) {
_accumulateRoom(roomId, category, data, fromDatabase) {
// Valid /sync state transitions
// +--------+ <======+ 1: Accept an invite
// +== | INVITE | | (5) 2: Leave a room
@@ -159,7 +160,7 @@ export class SyncAccumulator {
delete this.inviteRooms[roomId];
}
// (3)
this._accumulateJoinState(roomId, data);
this._accumulateJoinState(roomId, data, fromDatabase);
break;
case "leave":
if (this.inviteRooms[roomId]) { // (4)
@@ -203,7 +204,7 @@ export class SyncAccumulator {
}
// Accumulate timeline and state events in a room.
_accumulateJoinState(roomId, data) {
_accumulateJoinState(roomId, data, fromDatabase) {
// We expect this function to be called a lot (every /sync) so we want
// this to be fast. /sync stores events in an array but we often want
// to clobber based on type/state_key. Rather than convert arrays to
@@ -337,8 +338,20 @@ export class SyncAccumulator {
setState(currentData._currentState, e);
// append the event to the timeline. The back-pagination token
// corresponds to the first event in the timeline
let transformedEvent;
if (!fromDatabase) {
transformedEvent = Object.assign({}, e);
if (transformedEvent.unsigned !== undefined) {
transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned);
}
const age = e.unsigned ? e.unsigned.age : e.age;
if (age !== undefined) transformedEvent._localTs = Date.now() - age;
} else {
transformedEvent = e;
}
currentData._timeline.push({
event: e,
event: transformedEvent,
token: index === 0 ? data.timeline.prev_batch : null,
});
});
@@ -405,6 +418,7 @@ export class SyncAccumulator {
* represents all room data that should be stored. This should be paired
* with the sync token which represents the most recent /sync response
* provided to accumulate().
* @param {boolean} forDatabase True to generate a sync to be saved to storage
* @return {Object} An object with a "nextBatch", "roomsData" and "accountData"
* keys.
* The "nextBatch" key is a string which represents at what point in the
@@ -414,7 +428,7 @@ export class SyncAccumulator {
* /sync response from the 'rooms' key onwards. The "accountData" key is
* a list of raw events which represent global account data.
*/
getJSON() {
getJSON(forDatabase) {
const data = {
join: {},
invite: {},
@@ -486,7 +500,28 @@ export class SyncAccumulator {
}
roomJson.timeline.prev_batch = msgData.token;
}
roomJson.timeline.events.push(msgData.event);
let transformedEvent;
if (!forDatabase && msgData.event._localTs) {
// This means we have to copy each event so we can fix it up to
// set a correct 'age' parameter whilst keeping the local timestamp
// on our stored event. If this turns out to be a bottleneck, it could
// be optimised either by doing this in the main process after the data
// has been structured-cloned to go between the worker & main process,
// or special-casing data from saved syncs to read the local timstamp
// directly rather than turning it into age to then immediately be
// transformed back again into a local timestamp.
transformedEvent = Object.assign({}, msgData.event);
if (transformedEvent.unsigned !== undefined) {
transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned);
}
delete transformedEvent._localTs;
transformedEvent.unsigned = transformedEvent.unsigned || {};
transformedEvent.unsigned.age = Date.now() - msgData.event._localTs;
} else {
transformedEvent = msgData.event;
}
roomJson.timeline.events.push(transformedEvent);
});
// Add state data: roll back current state to the start of timeline,
+36 -25
View File
@@ -93,7 +93,7 @@ export function SyncApi(client, opts) {
};
}
this.opts = opts;
this._peekRoomId = null;
this._peekRoom = null;
this._currentSyncRequest = null;
this._syncState = null;
this._syncStateData = null; // additional data (eg. error object for failed sync)
@@ -264,17 +264,18 @@ SyncApi.prototype.syncLeftRooms = function() {
* store.
*/
SyncApi.prototype.peek = function(roomId) {
const self = this;
if (this._peekRoom && this._peekRoom.roomId === roomId) {
return Promise.resolve(this._peekRoom);
}
const client = this.client;
this._peekRoomId = roomId;
return this.client.roomInitialSync(roomId, 20).then(function(response) {
this._peekRoom = this.createRoom(roomId);
return this.client.roomInitialSync(roomId, 20).then((response) => {
// make sure things are init'd
response.messages = response.messages || {};
response.messages.chunk = response.messages.chunk || [];
response.state = response.state || [];
const peekRoom = self.createRoom(roomId);
// FIXME: Mostly duplicated from _processRoomEvents but not entirely
// because "state" in this API is at the BEGINNING of the chunk
const oldStateEvents = utils.map(
@@ -309,28 +310,28 @@ SyncApi.prototype.peek = function(roomId) {
// fire off pagination requests in response to the Room.timeline
// events.
if (response.messages.start) {
peekRoom.oldState.paginationToken = response.messages.start;
this._peekRoom.oldState.paginationToken = response.messages.start;
}
// set the state of the room to as it was after the timeline executes
peekRoom.oldState.setStateEvents(oldStateEvents);
peekRoom.currentState.setStateEvents(stateEvents);
this._peekRoom.oldState.setStateEvents(oldStateEvents);
this._peekRoom.currentState.setStateEvents(stateEvents);
self._resolveInvites(peekRoom);
peekRoom.recalculate();
this._resolveInvites(this._peekRoom);
this._peekRoom.recalculate();
// roll backwards to diverge old state. addEventsToTimeline
// will overwrite the pagination token, so make sure it overwrites
// it with the right thing.
peekRoom.addEventsToTimeline(messages.reverse(), true,
peekRoom.getLiveTimeline(),
this._peekRoom.addEventsToTimeline(messages.reverse(), true,
this._peekRoom.getLiveTimeline(),
response.messages.start);
client.store.storeRoom(peekRoom);
client.emit("Room", peekRoom);
client.store.storeRoom(this._peekRoom);
client.emit("Room", this._peekRoom);
self._peekPoll(peekRoom);
return peekRoom;
this._peekPoll(this._peekRoom);
return this._peekRoom;
});
};
@@ -339,16 +340,16 @@ SyncApi.prototype.peek = function(roomId) {
* peeked.
*/
SyncApi.prototype.stopPeeking = function() {
this._peekRoomId = null;
this._peekRoom = null;
};
/**
* Do a peek room poll.
* @param {Room} peekRoom
* @param {string} token from= token
* @param {string?} token from= token
*/
SyncApi.prototype._peekPoll = function(peekRoom, token) {
if (this._peekRoomId !== peekRoom.roomId) {
if (this._peekRoom !== peekRoom) {
debuglog("Stopped peeking in room %s", peekRoom.roomId);
return;
}
@@ -360,7 +361,7 @@ SyncApi.prototype._peekPoll = function(peekRoom, token) {
timeout: 30 * 1000,
from: token,
}, undefined, 50 * 1000).then(function(res) {
if (self._peekRoomId !== peekRoom.roomId) {
if (self._peekRoom !== peekRoom) {
debuglog("Stopped peeking in room %s", peekRoom.roomId);
return;
}
@@ -475,9 +476,9 @@ SyncApi.prototype.sync = function() {
this._running = true;
if (global.document) {
if (global.window) {
this._onOnlineBound = this._onOnline.bind(this);
global.document.addEventListener("online", this._onOnlineBound, false);
global.window.addEventListener("online", this._onOnlineBound, false);
}
let savedSyncPromise = Promise.resolve();
@@ -643,8 +644,8 @@ SyncApi.prototype.sync = function() {
*/
SyncApi.prototype.stop = function() {
debuglog("SyncApi.stop");
if (global.document) {
global.document.removeEventListener("online", this._onOnlineBound, false);
if (global.window) {
global.window.removeEventListener("online", this._onOnlineBound, false);
this._onOnlineBound = undefined;
}
this._running = false;
@@ -1361,6 +1362,16 @@ SyncApi.prototype._processSyncResponse = async function(
const currentCount = data.device_one_time_keys_count.signed_curve25519 || 0;
this.opts.crypto.updateOneTimeKeyCount(currentCount);
}
if (this.opts.crypto && data["org.matrix.msc2732.device_unused_fallback_key_types"]) {
// The presence of device_unused_fallback_key_types indicates that the
// server supports fallback keys. If there's no unused
// signed_curve25519 fallback key we need a new one.
const unusedFallbackKeys = data["org.matrix.msc2732.device_unused_fallback_key_types"];
this.opts.crypto.setNeedsNewFallback(
unusedFallbackKeys instanceof Array &&
!unusedFallbackKeys.includes("signed_curve25519"),
);
}
};
/**
-1434
View File
File diff suppressed because it is too large Load Diff
+1816
View File
File diff suppressed because it is too large Load Diff
+274
View File
@@ -0,0 +1,274 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import MatrixEvent from '../models/event';
import {logger} from '../logger';
import { createNewMatrixCall, MatrixCall, CallErrorCode, CallState, CallDirection } from './call';
import { EventType } from '../@types/event';
import { MatrixClient } from '../client';
// Don't ring unless we'd be ringing for at least 3 seconds: the user needs some
// time to press the 'accept' button
const RING_GRACE_PERIOD = 3000;
export class CallEventHandler {
client: MatrixClient;
calls: Map<string, MatrixCall>;
callEventBuffer: MatrixEvent[];
candidateEventsByCall: Map<string, Array<MatrixEvent>>;
constructor(client: MatrixClient) {
this.client = client;
this.calls = new Map<string, MatrixCall>();
// The sync code always emits one event at a time, so it will patiently
// wait for us to finish processing a call invite before delivering the
// next event, even if that next event is a hangup. We therefore accumulate
// all our call events and then process them on the 'sync' event, ie.
// each time a sync has completed. This way, we can avoid emitting incoming
// call events if we get both the invite and answer/hangup in the same sync.
// This happens quite often, eg. replaying sync from storage, catchup sync
// after loading and after we've been offline for a bit.
this.callEventBuffer = [];
this.candidateEventsByCall = new Map<string, Array<MatrixEvent>>();
this.client.on("sync", this.evaluateEventBuffer);
this.client.on("event", this.onEvent);
}
public stop() {
this.client.removeListener("sync", this.evaluateEventBuffer);
this.client.removeListener("event", this.onEvent);
}
private evaluateEventBuffer = () => {
if (this.client.getSyncState() === "SYNCING") {
// don't process any events until they are all decrypted
if (this.callEventBuffer.some((e) => e.isBeingDecrypted())) return;
const ignoreCallIds = new Set<String>();
// inspect the buffer and mark all calls which have been answered
// or hung up before passing them to the call event handler.
for (const ev of this.callEventBuffer) {
if (ev.getType() === EventType.CallAnswer ||
ev.getType() === EventType.CallHangup) {
ignoreCallIds.add(ev.getContent().call_id);
}
}
// now loop through the buffer chronologically and inject them
for (const e of this.callEventBuffer) {
if (
e.getType() === EventType.CallInvite &&
ignoreCallIds.has(e.getContent().call_id)
) {
// This call has previously been answered or hung up: ignore it
continue;
}
try {
this.handleCallEvent(e);
} catch (e) {
logger.error("Caught exception handling call event", e);
}
}
this.callEventBuffer = [];
}
}
private onEvent = (event: MatrixEvent) => {
// any call events or ones that might be once they're decrypted
if (event.getType().indexOf("m.call.") === 0 || event.isBeingDecrypted()) {
// queue up for processing once all events from this sync have been
// processed (see above).
this.callEventBuffer.push(event);
}
if (event.isBeingDecrypted() || event.isDecryptionFailure()) {
// add an event listener for once the event is decrypted.
event.once("Event.decrypted", () => {
if (event.getType().indexOf("m.call.") === -1) return;
if (this.callEventBuffer.includes(event)) {
// we were waiting for that event to decrypt, so recheck the buffer
this.evaluateEventBuffer();
} else {
// This one wasn't buffered so just run the event handler for it
// straight away
try {
this.handleCallEvent(event);
} catch (e) {
logger.error("Caught exception handling call event", e);
}
}
});
}
}
private handleCallEvent(event: MatrixEvent) {
const content = event.getContent();
let call = content.call_id ? this.calls.get(content.call_id) : undefined;
//console.info("RECV %s content=%s", event.getType(), JSON.stringify(content));
if (event.getType() === EventType.CallInvite) {
if (event.getSender() === this.client.credentials.userId) {
return; // ignore invites you send
}
if (event.getLocalAge() > content.lifetime - RING_GRACE_PERIOD) {
return; // expired call
}
if (call && call.state === CallState.Ended) {
return; // stale/old invite event
}
if (call) {
logger.log(
`WARN: Already have a MatrixCall with id ${content.call_id} but got an ` +
`invite. Clobbering.`,
);
}
call = createNewMatrixCall(this.client, event.getRoomId(), {
forceTURN: this.client._forceTURN,
});
if (!call) {
logger.log(
"Incoming call ID " + content.call_id + " but this client " +
"doesn't support WebRTC",
);
// don't hang up the call: there could be other clients
// connected that do support WebRTC and declining the
// the call on their behalf would be really annoying.
return;
}
call.callId = content.call_id;
call.initWithInvite(event);
this.calls.set(call.callId, call);
// if we stashed candidate events for that call ID, play them back now
if (this.candidateEventsByCall.get(call.callId)) {
for (const ev of this.candidateEventsByCall.get(call.callId)) {
call.onRemoteIceCandidatesReceived(ev);
}
}
// Were we trying to call that user (room)?
let existingCall;
for (const thisCall of this.calls.values()) {
const isCalling = [CallState.WaitLocalMedia, CallState.CreateOffer, CallState.InviteSent].includes(
thisCall.state,
);
if (
call.roomId === thisCall.roomId &&
thisCall.direction === CallDirection.Outbound &&
isCalling
) {
existingCall = thisCall;
break;
}
}
if (existingCall) {
// If we've only got to wait_local_media or create_offer and
// we've got an invite, pick the incoming call because we know
// we haven't sent our invite yet otherwise, pick whichever
// call has the lowest call ID (by string comparison)
if (existingCall.state === CallState.WaitLocalMedia ||
existingCall.state === CallState.CreateOffer ||
existingCall.callId > call.callId) {
logger.log(
"Glare detected: answering incoming call " + call.callId +
" and canceling outgoing call " + existingCall.callId,
);
existingCall.replacedBy(call);
call.answer();
} else {
logger.log(
"Glare detected: rejecting incoming call " + call.callId +
" and keeping outgoing call " + existingCall.callId,
);
call.hangup(CallErrorCode.Replaced, true);
}
} else {
this.client.emit("Call.incoming", call);
}
} else if (event.getType() === EventType.CallAnswer) {
if (!call) {
return;
}
if (event.getSender() === this.client.credentials.userId) {
if (call.state === CallState.Ringing) {
call.onAnsweredElsewhere(content);
}
} else {
call.onAnswerReceived(event);
}
} else if (event.getType() === EventType.CallCandidates) {
if (event.getSender() === this.client.credentials.userId) {
return;
}
if (!call) {
// store the candidates; we may get a call eventually.
if (!this.candidateEventsByCall.has(content.call_id)) {
this.candidateEventsByCall.set(content.call_id, []);
}
this.candidateEventsByCall.get(content.call_id).push(event);
} else {
call.onRemoteIceCandidatesReceived(event);
}
} else if ([EventType.CallHangup, EventType.CallReject].includes(event.getType())) {
// Note that we also observe our own hangups here so we can see
// if we've already rejected a call that would otherwise be valid
if (!call) {
// if not live, store the fact that the call has ended because
// we're probably getting events backwards so
// the hangup will come before the invite
call = createNewMatrixCall(this.client, event.getRoomId());
if (call) {
call.callId = content.call_id;
call.initWithHangup(event);
this.calls.set(content.call_id, call);
}
} else {
if (call.state !== CallState.Ended) {
if (event.getType() === EventType.CallHangup) {
call.onHangupReceived(content);
} else {
call.onRejectReceived(content);
}
this.calls.delete(content.call_id);
}
}
} else if (event.getType() === EventType.CallSelectAnswer) {
if (!call) return;
if (event.getContent().party_id === call.ourPartyId) {
// Ignore remote echo
return;
}
call.onSelectAnswerReceived(event);
} else if (event.getType() === EventType.CallNegotiate) {
if (!call) return;
if (event.getContent().party_id === call.ourPartyId) {
// Ignore remote echo
return;
}
call.onNegotiateReceived(event);
}
}
}
+37
View File
@@ -0,0 +1,37 @@
// allow camelcase as these are events type that go onto the wire
/* eslint-disable camelcase */
interface CallOfferAnswer {
type: string;
sdp: string;
}
export interface CallCapabilities {
'm.call.transferee': boolean;
}
export interface MCallAnswer {
answer: CallOfferAnswer;
capabilities: CallCapabilities;
}
export interface MCallOfferNegotiate {
offer: CallOfferAnswer;
description: CallOfferAnswer;
lifetime: number;
capabilities: CallCapabilities;
}
export interface MCallReplacesTarget {
id: string;
display_name: string;
avatar_url: string;
}
export interface MCallReplacesEvent {
replacement_id: string;
target_user: MCallReplacesTarget;
create_call: string;
target_room: string;
}
/* eslint-enable camelcase */
+2675 -2152
View File
File diff suppressed because it is too large Load Diff