Compare commits

...

160 Commits

Author SHA1 Message Date
RiotRobot 5dd60de57d v9.8.0 2021-03-01 12:44:55 +00:00
RiotRobot 5efbfc2dba Prepare changelog for v9.8.0 2021-03-01 12:44:54 +00:00
RiotRobot 6a7d58e22e v9.8.0-rc.1 2021-02-24 17:24:17 +00:00
RiotRobot 203829c1cd Prepare changelog for v9.8.0-rc.1 2021-02-24 17:24:16 +00:00
J. Ryan Stinnett b55e6c4ef0 Merge pull request #1615 from matrix-org/jryans/megolm-logs-2021-02-22
Optimise prefixed logger
2021-02-23 17:46:57 +00:00
J. Ryan Stinnett 8d779e8aec Optimise prefixed logger
Tweak the prefixed logger to only do the setup work the first time.
2021-02-23 16:32:10 +00:00
J. Ryan Stinnett dd1d48f688 Merge pull request #1614 from matrix-org/jryans/megolm-logs-2021-02-22
Add debug logs to encryption prep, take 3
2021-02-23 16:07:39 +00:00
J. Ryan Stinnett 8d14dc9ee3 Add debug logs to encryption prep, take 3
This continues adding more logs to work out the root cause of
https://github.com/vector-im/element-web/issues/16194.

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-21 16:54:57 +00:00
RiotRobot 5b1e3537cc Merge branch 'master' into develop 2020-12-21 16:53:35 +00:00
RiotRobot 44843d418d v9.4.0 2020-12-21 16:50:47 +00:00
RiotRobot a103ffa038 Prepare changelog for v9.4.0 2020-12-21 16:50:46 +00:00
David Baker 712335789e Yes, thank you test, you've made your point 2020-12-21 16:30:16 +00:00
David Baker cdf8186f44 and also in the other direction 2020-12-21 16:30:08 +00:00
David Baker d06942d602 Ignore party ID if opponent is v0 2020-12-21 16:30:00 +00:00
RiotRobot 45ac3a60dc Revert "Remove postinstall script which also runs as a dependency"
This temporarily reverts commit 853363fdf5, which
will cause trouble for downstream layers without additional changes.
2020-12-21 16:23:16 +00:00
David Baker 4f244da3ec Makean interface for the replaces event 2020-12-21 15:42:34 +00:00
David Baker 4eefa05d3f Another typo 2020-12-21 15:20:45 +00:00
David Baker 40fb31099a Typo 2020-12-21 15:18:21 +00:00
David Baker bcd85f5397 Rename to supportsCallTransfer
to be less ambiguous
2020-12-21 15:14:59 +00:00
David Baker 89aeda45c6 Basic call transfer initiation support
Re-commit of f3ee164a7d after I accidentally
merged to develop
2020-12-21 13:54:16 +00:00
David Baker 7581e5ffdc Merge pull request #1565 from matrix-org/revert-1559-dbkr/ignore_party_id_v0
Revert "Ignore party ID if opponent is v0"
2020-12-21 13:48:40 +00:00
David Baker 7046fa3224 Revert "Ignore party ID if opponent is v0" 2020-12-21 13:48:06 +00:00
David Baker ef392785e8 Merge pull request #1559 from matrix-org/dbkr/ignore_party_id_v0
Ignore party ID if opponent is v0
2020-12-21 13:47:33 +00:00
David Baker 5bd029115c Merge pull request #1562 from matrix-org/dbkr/honour_reject_from_self
Honour a call reject event from another of our own devices
2020-12-18 17:24:32 +00:00
David Baker b6f42b25dd Honour a call reject event from another of our own devices
Fixes a bug where the call would ring again when you refreshed,
even though you'd previously rejected it.
2020-12-18 13:45:13 +00:00
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
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
29 changed files with 1635 additions and 636 deletions
+2
View File
@@ -35,6 +35,8 @@ module.exports = {
"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",
+148
View File
@@ -1,3 +1,151 @@
Changes in [9.8.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.8.0) (2021-03-01)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.8.0-rc.1...v9.8.0)
* No changes since rc.1
Changes in [9.8.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.8.0-rc.1) (2021-02-24)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.7.0...v9.8.0-rc.1)
* Optimise prefixed logger
[\#1615](https://github.com/matrix-org/matrix-js-sdk/pull/1615)
* Add debug logs to encryption prep, take 3
[\#1614](https://github.com/matrix-org/matrix-js-sdk/pull/1614)
* Add functions for upper & lowercase random strings
[\#1612](https://github.com/matrix-org/matrix-js-sdk/pull/1612)
* Room helpers for invite permissions and join rules
[\#1609](https://github.com/matrix-org/matrix-js-sdk/pull/1609)
* Fixed wording in "Adding video track with id" log
[\#1606](https://github.com/matrix-org/matrix-js-sdk/pull/1606)
* Add more debug logs to encryption prep
[\#1605](https://github.com/matrix-org/matrix-js-sdk/pull/1605)
* Add option to set ice candidate pool size
[\#1604](https://github.com/matrix-org/matrix-js-sdk/pull/1604)
* Cancel call if no source was selected
[\#1601](https://github.com/matrix-org/matrix-js-sdk/pull/1601)
Changes in [9.7.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.7.0) (2021-02-16)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.7.0-rc.1...v9.7.0)
* No changes since rc.1
Changes in [9.7.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.7.0-rc.1) (2021-02-10)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.6.0...v9.7.0-rc.1)
* Handle undefined peerconn
[\#1600](https://github.com/matrix-org/matrix-js-sdk/pull/1600)
* ReEmitter: Don't throw if no error handler is attached
[\#1599](https://github.com/matrix-org/matrix-js-sdk/pull/1599)
* Convert ReEmitter to TS
[\#1598](https://github.com/matrix-org/matrix-js-sdk/pull/1598)
* Fix typo in main readme
[\#1597](https://github.com/matrix-org/matrix-js-sdk/pull/1597)
* Remove rogue plus character
[\#1596](https://github.com/matrix-org/matrix-js-sdk/pull/1596)
* Fix call ID NaN
[\#1595](https://github.com/matrix-org/matrix-js-sdk/pull/1595)
* Fix Electron type merging
[\#1594](https://github.com/matrix-org/matrix-js-sdk/pull/1594)
* Fix browser screen share
[\#1593](https://github.com/matrix-org/matrix-js-sdk/pull/1593)
* Fix desktop Matrix screen sharing
[\#1570](https://github.com/matrix-org/matrix-js-sdk/pull/1570)
* Guard against confused server retry times
[\#1591](https://github.com/matrix-org/matrix-js-sdk/pull/1591)
* Decrypt redaction events
[\#1589](https://github.com/matrix-org/matrix-js-sdk/pull/1589)
* Fix edge cases with peeking where a room is re-peeked
[\#1587](https://github.com/matrix-org/matrix-js-sdk/pull/1587)
Changes in [9.6.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.6.0) (2021-02-03)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.6.0-rc.1...v9.6.0)
* [Release] Fix edge cases with peeking where a room is re-peeked
[\#1588](https://github.com/matrix-org/matrix-js-sdk/pull/1588)
Changes in [9.6.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.6.0-rc.1) (2021-01-29)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.5.1...v9.6.0-rc.1)
* Add support for getting call stats
[\#1584](https://github.com/matrix-org/matrix-js-sdk/pull/1584)
* Fix compatibility with v0 calls
[\#1583](https://github.com/matrix-org/matrix-js-sdk/pull/1583)
* Upgrade deps 2021-01
[\#1582](https://github.com/matrix-org/matrix-js-sdk/pull/1582)
* Log the call ID when logging that we've received VoIP events
[\#1581](https://github.com/matrix-org/matrix-js-sdk/pull/1581)
* Fix extra negotiate message in Firefox
[\#1579](https://github.com/matrix-org/matrix-js-sdk/pull/1579)
* Add debug logs to encryption prep
[\#1580](https://github.com/matrix-org/matrix-js-sdk/pull/1580)
* Expose getPresence endpoint
[\#1578](https://github.com/matrix-org/matrix-js-sdk/pull/1578)
* Queue keys for backup even if backup isn't enabled yet
[\#1577](https://github.com/matrix-org/matrix-js-sdk/pull/1577)
* Stop retrying TURN access when forbidden
[\#1576](https://github.com/matrix-org/matrix-js-sdk/pull/1576)
* Add DTMF sending support
[\#1573](https://github.com/matrix-org/matrix-js-sdk/pull/1573)
Changes in [9.5.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.5.1) (2021-01-26)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.5.0...v9.5.1)
* [Release] Fix compatibility with v0 calls
[\#1585](https://github.com/matrix-org/matrix-js-sdk/pull/1585)
Changes in [9.5.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.5.0) (2021-01-18)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.5.0-rc.1...v9.5.0)
* No changes since rc.1
Changes in [9.5.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.5.0-rc.1) (2021-01-13)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.1...v9.5.0-rc.1)
* Don't log if no WebRTC
[\#1574](https://github.com/matrix-org/matrix-js-sdk/pull/1574)
* Add _unstable_getSharedRooms
[\#1417](https://github.com/matrix-org/matrix-js-sdk/pull/1417)
* Bump node-notifier from 8.0.0 to 8.0.1
[\#1568](https://github.com/matrix-org/matrix-js-sdk/pull/1568)
* Ignore party ID if opponent is v0
[\#1567](https://github.com/matrix-org/matrix-js-sdk/pull/1567)
* Basic call transfer initiation support
[\#1566](https://github.com/matrix-org/matrix-js-sdk/pull/1566)
* Room version 6 is now a thing
[\#1572](https://github.com/matrix-org/matrix-js-sdk/pull/1572)
* Store keys with same index but better trust level
[\#1571](https://github.com/matrix-org/matrix-js-sdk/pull/1571)
* Use TypeScript source for development, swap to build during release
[\#1561](https://github.com/matrix-org/matrix-js-sdk/pull/1561)
* Revert "Ignore party ID if opponent is v0"
[\#1565](https://github.com/matrix-org/matrix-js-sdk/pull/1565)
* Basic call transfer initiation support
[\#1558](https://github.com/matrix-org/matrix-js-sdk/pull/1558)
* Ignore party ID if opponent is v0
[\#1559](https://github.com/matrix-org/matrix-js-sdk/pull/1559)
* Honour a call reject event from another of our own devices
[\#1562](https://github.com/matrix-org/matrix-js-sdk/pull/1562)
Changes in [9.4.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.1) (2020-12-21)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.0...v9.4.1)
* Further script tweaks to get all layers building again
Changes in [9.4.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.0) (2020-12-21)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.0-rc.2...v9.4.0)
* Revert `postinstall` script change, causes issues for other layers
Changes in [9.4.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.0-rc.2) (2020-12-16)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.0-rc.1...v9.4.0-rc.2)
+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.
+17 -15
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "9.4.0-rc.2",
"version": "9.8.0",
"description": "Matrix Client-Server SDK for Javascript",
"scripts": {
"prepublishOnly": "yarn build",
@@ -28,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": [
@@ -53,22 +54,22 @@
"bs58": "^4.0.1",
"content-type": "^1.0.4",
"loglevel": "^1.7.1",
"qs": "^6.9.4",
"qs": "^6.9.6",
"request": "^2.88.2",
"unhomoglyph": "^1.0.6"
},
"devDependencies": {
"@babel/cli": "^7.12.8",
"@babel/core": "^7.12.9",
"@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.1",
"@babel/preset-env": "^7.12.7",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-typescript": "^7.12.7",
"@babel/register": "^7.12.1",
"@types/jest": "^26.0.15",
"@babel/register": "^7.12.10",
"@types/jest": "^26.0.20",
"@types/node": "12",
"@types/request": "^2.48.5",
"babel-eslint": "^10.1.0",
@@ -77,22 +78,23 @@
"better-docs": "^2.3.2",
"browserify": "^17.0.0",
"docdash": "^1.2.0",
"eslint": "7.14.0",
"eslint-config-matrix-org": "^0.1.2",
"eslint": "7.18.0",
"eslint-config-matrix-org": "^0.2.0",
"eslint-plugin-babel": "^5.3.1",
"exorcist": "^1.0.1",
"fake-indexeddb": "^3.1.2",
"jest": "^26.6.3",
"jest-localstorage-mock": "^2.4.3",
"jest-localstorage-mock": "^2.4.6",
"jsdoc": "^3.6.6",
"matrix-mock-request": "^1.2.3",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"rimraf": "^3.0.2",
"terser": "^5.5.0",
"terser": "^5.5.1",
"tsify": "^5.0.2",
"typescript": "^4.1.2"
"typescript": "^4.1.3"
},
"jest": {
"testEnvironment": "node"
}
},
"typings": "./lib/index.d.ts"
}
+38
View File
@@ -178,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
@@ -353,5 +366,30 @@ if [ $(git branch -lr | grep origin/develop -c) -ge 1 ]; then
git checkout develop
git pull
git merge master --no-edit
# When merging to develop, we need revert the `main` and `typings` fields if
# we adjusted them previously.
for i in main typings
do
# If a `lib` prefixed value is present, it means we adjusted the field
# earlier at publish time, so we should revert it now.
if [ "$(jq -r ".matrix_lib_$i" package.json)" != "null" ]; then
# If there's a `src` prefixed value, use that, otherwise delete.
# This is used to delete the `typings` field and reset `main` back
# to the TypeScript source.
src_value=$(jq -r ".matrix_src_$i" package.json)
if [ "$src_value" != "null" ]; then
jq ".$i = .matrix_src_$i" package.json > package.json.new && mv package.json.new package.json
else
jq "del(.$i)" package.json > package.json.new && mv package.json.new package.json
fi
fi
done
if [ -n "$(git ls-files --modified package.json)" ]; then
echo "Committing develop package.json"
git commit package.json -m "Resetting package fields for development"
fi
git push origin develop
fi
+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();
});
});
});
});
+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();
});
});
+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);
});
});
});
+3 -3
View File
@@ -142,7 +142,7 @@ describe('Call', function() {
await call.onAnswerReceived({
getContent: () => {
return {
version: 0,
version: 1,
call_id: call.callId,
party_id: 'the_correct_party_id',
answer: {
@@ -156,7 +156,7 @@ describe('Call', function() {
call.onRemoteIceCandidatesReceived({
getContent: () => {
return {
version: 0,
version: 1,
call_id: call.callId,
party_id: 'the_correct_party_id',
candidates: [
@@ -173,7 +173,7 @@ describe('Call', function() {
call.onRemoteIceCandidatesReceived({
getContent: () => {
return {
version: 0,
version: 1,
call_id: call.callId,
party_id: 'some_other_party_id',
candidates: [
+1
View File
@@ -48,6 +48,7 @@ export enum EventType {
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",
+40 -1
View File
@@ -26,10 +26,49 @@ declare global {
}
}
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): Promise<MediaStream>;
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 {
-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);
}
}
}
+56
View File
@@ -180,6 +180,14 @@ function keyFromRecoverySession(session, decryptionKey) {
* @param {boolean} [opts.forceTURN]
* Optional. Whether relaying calls through a TURN server should be forced.
*
* * @param {boolean} [opts.iceCandidatePoolSize]
* Optional. Up to this many ICE candidates will be gathered when an incoming call arrives.
* Gathering does not send data to the caller, but will communicate with the configured TURN
* server. Default 0.
*
* @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.
@@ -364,6 +372,8 @@ export function MatrixClient(opts) {
this._cryptoCallbacks = opts.cryptoCallbacks || {};
this._forceTURN = opts.forceTURN || false;
this._iceCandidatePoolSize = opts.iceCandidatePoolSize === undefined ? 0 : opts.iceCandidatePoolSize;
this._supportsCallTransfer = opts.supportsCallTransfer || false;
this._fallbackICEServerAllowed = opts.fallbackICEServerAllowed || false;
// List of which rooms have encryption enabled: separate from crypto because
@@ -696,6 +706,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.
@@ -3829,6 +3847,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.
@@ -5131,6 +5162,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.
@@ -5393,6 +5444,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);
+7 -2
View File
@@ -1059,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}`,
);
+81 -41
View File
@@ -22,7 +22,7 @@ limitations under the License.
* @module crypto/algorithms/megolm
*/
import {logger} from '../../logger';
import {getPrefixedLogger, logger} from '../../logger';
import * as utils from "../../utils";
import {polyfillSuper} from "../../utils";
import * as olmlib from "../olmlib";
@@ -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 (start phase 1) with new Olm sessions in ${this._roomId}`);
const errorDevices = [];
// meanwhile, establish olm sessions for devices that we don't
@@ -282,6 +285,7 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
session, key, payload, devicesWithoutSession, errorDevices,
singleOlmCreationPhase ? 10000 : 2000, failedServers,
);
logger.debug(`Shared keys (end phase 1) with new Olm sessions in ${this._roomId}`);
if (!singleOlmCreationPhase && (Date.now() - start < 10000)) {
// perform the second phase of olm session creation if requested,
@@ -310,19 +314,24 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
}
}
logger.debug(`Sharing keys (start phase 2) with new Olm sessions in ${this._roomId}`);
await this._shareKeyWithDevices(
session, key, payload, retryDevices, failedDevices, 30000,
);
logger.debug(`Shared keys (end phase 2) with new Olm sessions in ${this._roomId}`);
await this._notifyFailedOlmDevices(session, key, failedDevices);
})();
} else {
await this._notifyFailedOlmDevices(session, key, errorDevices);
}
logger.debug(`Shared keys (all phases done) with new Olm sessions in ${this._roomId}`);
})(),
(async () => {
logger.debug(`Notifying blocked devices in ${this._roomId}`);
// also, notify blocked devices that they're blocked
const blockedMap = {};
let blockedCount = 0;
for (const [userId, userBlockedDevices] of Object.entries(blocked)) {
for (const [deviceId, device] of Object.entries(userBlockedDevices)) {
if (
@@ -330,12 +339,14 @@ MegolmEncryption.prototype._ensureOutboundSession = async function(
session.blockedDevicesNotified[userId][deviceId] === undefined
) {
blockedMap[userId] = blockedMap[userId] || {};
blockedMap[userId][deviceId] = {device};
blockedMap[userId][deviceId] = { device };
blockedCount++;
}
}
}
await this._notifyBlockedDevices(session, blockedMap);
logger.debug(`Notified ${blockedCount} blocked devices in ${this._roomId}`);
})(),
]);
};
@@ -348,6 +359,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 +385,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);
};
@@ -723,13 +733,18 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
MegolmEncryption.prototype._shareKeyWithDevices = async function(
session, key, payload, devicesByUser, errorDevices, otkTimeout, failedServers,
) {
logger.debug(`Ensuring Olm sessions for devices in ${this._roomId}`);
const devicemap = await olmlib.ensureOlmSessionsForDevices(
this._olmDevice, this._baseApis, devicesByUser, otkTimeout, failedServers,
getPrefixedLogger(`[${this._roomId}]`),
);
logger.debug(`Ensured Olm sessions for devices in ${this._roomId}`);
this._getDevicesWithoutSessions(devicemap, devicesByUser, errorDevices);
logger.debug(`Sharing keys with Olm sessions in ${this._roomId}`);
await this._shareKeyWithOlmSessions(session, key, payload, devicemap);
logger.debug(`Shared keys with Olm sessions in ${this._roomId}`);
};
MegolmEncryption.prototype._shareKeyWithOlmSessions = async function(
@@ -738,16 +753,17 @@ MegolmEncryption.prototype._shareKeyWithOlmSessions = async function(
const userDeviceMaps = this._splitDevices(devicemap);
for (let i = 0; i < userDeviceMaps.length; i++) {
const taskDetail =
`megolm keys for ${session.sessionId} ` +
`in ${this._roomId} (slice ${i + 1}/${userDeviceMaps.length})`;
try {
logger.debug(`Sharing ${taskDetail}`);
await this._encryptAndSendKeysToDevices(
session, key.chain_index, userDeviceMaps[i], payload,
);
logger.log(`Completed megolm keyshare for ${session.sessionId} `
+ `in ${this._roomId} (slice ${i + 1}/${userDeviceMaps.length})`);
logger.debug(`Shared ${taskDetail}`);
} catch (e) {
logger.log(`megolm keyshare for ${session.sessionId} in ${this._roomId} `
+ `(slice ${i + 1}/${userDeviceMaps.length}) failed`);
logger.error(`Failed to share ${taskDetail}`);
throw e;
}
}
@@ -766,6 +782,11 @@ MegolmEncryption.prototype._shareKeyWithOlmSessions = async function(
MegolmEncryption.prototype._notifyFailedOlmDevices = async function(
session, key, failedDevices,
) {
logger.debug(
`Notifying ${failedDevices.length} devices we failed to ` +
`create Olm sessions in ${this._roomId}`,
);
// mark the devices that failed as "handled" because we don't want to try
// to claim a one-time-key for dead devices on every message.
for (const {userId, deviceInfo} of failedDevices) {
@@ -780,6 +801,10 @@ MegolmEncryption.prototype._notifyFailedOlmDevices = async function(
await this._olmDevice.filterOutNotifiedErrorDevices(
failedDevices,
);
logger.debug(
`Filtered down to ${filteredFailedDevices.length} error devices ` +
`in ${this._roomId}`,
);
const blockedMap = {};
for (const {userId, deviceInfo} of filteredFailedDevices) {
blockedMap[userId] = blockedMap[userId] || {};
@@ -797,6 +822,10 @@ MegolmEncryption.prototype._notifyFailedOlmDevices = async function(
// send the notifications
await this._notifyBlockedDevices(session, blockedMap);
logger.debug(
`Notified ${filteredFailedDevices.length} devices we failed to ` +
`create Olm sessions in ${this._roomId}`,
);
};
/**
@@ -846,24 +875,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 +1393,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 +1604,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,
+21 -13
View File
@@ -57,6 +57,7 @@ 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;
@@ -2884,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
};
/**
@@ -3028,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);
};
/**
+31 -8
View File
@@ -183,18 +183,24 @@ export async function getExistingOlmSessions(
* @param {Array} [failedServers] An array to fill with remote servers that
* failed to respond to one-time-key requests.
*
* @param {Object} [log] A possibly customised log
*
* @return {Promise} resolves once the sessions are complete, to
* an Object mapping from userId to deviceId to
* {@link module:crypto~OlmSessionResult}
*/
export async function ensureOlmSessionsForDevices(
olmDevice, baseApis, devicesByUser, force, otkTimeout, failedServers,
olmDevice, baseApis, devicesByUser, force, otkTimeout, failedServers, log,
) {
if (typeof force === "number") {
log = failedServers;
failedServers = otkTimeout;
otkTimeout = force;
force = false;
}
if (!log) {
log = logger;
}
const devicesWithoutSession = [
// [userId, deviceId], ...
@@ -216,7 +222,7 @@ export async function ensureOlmSessionsForDevices(
// new chain when this side has an active sender chain.
// If you see this message being logged in the wild, we should find
// the thing that is trying to send Olm messages to itself and fix it.
logger.info("Attempted to start session with ourself! Ignoring");
log.info("Attempted to start session with ourself! Ignoring");
// We must fill in the section in the return value though, as callers
// expect it to be there.
result[userId][deviceId] = {
@@ -258,9 +264,9 @@ export async function ensureOlmSessionsForDevices(
}
if (sessionId === null || force) {
if (force) {
logger.info("Forcing new Olm session for " + userId + ":" + deviceId);
log.info(`Forcing new Olm session for ${userId}:${deviceId}`);
} else {
logger.info("Making new Olm session for " + userId + ":" + deviceId);
log.info(`Making new Olm session for ${userId}:${deviceId}`);
}
devicesWithoutSession.push([userId, deviceId]);
}
@@ -277,16 +283,29 @@ export async function ensureOlmSessionsForDevices(
const oneTimeKeyAlgorithm = "signed_curve25519";
let res;
let taskDetail = `one-time keys for ${devicesWithoutSession.length} devices`;
// If your homeserver takes a nap here and never replies, this process
// would hang indefinitely. While that's easily fixed by setting a
// timeout on this request, let's first log whether that's the root
// cause we're seeing in practice.
// See also https://github.com/vector-im/element-web/issues/16194
const otkTimeoutLogger = setTimeout(() => {
log.error(`Homeserver never replied while claiming ${taskDetail}`);
}, otkTimeout);
try {
log.debug(`Claiming ${taskDetail}`);
res = await baseApis.claimOneTimeKeys(
devicesWithoutSession, oneTimeKeyAlgorithm, otkTimeout,
);
log.debug(`Claimed ${taskDetail}`);
} catch (e) {
for (const resolver of Object.values(resolveSession)) {
resolver.resolve();
}
logger.log("failed to claim one-time keys", e, devicesWithoutSession);
log.log(`Failed to claim ${taskDetail}`, e, devicesWithoutSession);
throw e;
} finally {
clearTimeout(otkTimeoutLogger);
}
if (failedServers && "failures" in res) {
@@ -323,9 +342,10 @@ export async function ensureOlmSessionsForDevices(
}
if (!oneTimeKey) {
const msg = "No one-time keys (alg=" + oneTimeKeyAlgorithm +
") for device " + userId + ":" + deviceId;
logger.warn(msg);
log.warn(
`No one-time keys (alg=${oneTimeKeyAlgorithm}) ` +
`for device ${userId}:${deviceId}`,
);
if (resolveSession[key]) {
resolveSession[key].resolve();
}
@@ -350,7 +370,10 @@ export async function ensureOlmSessionsForDevices(
}
}
taskDetail = `Olm sessions for ${promises.length} devices`;
log.debug(`Starting ${taskDetail}`);
await Promise.all(promises);
log.debug(`Started ${taskDetail}`);
return result;
}
+20 -2
View File
@@ -1,6 +1,6 @@
/*
Copyright 2018 André Jaenisch
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019, 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.
@@ -19,7 +19,7 @@ limitations under the License.
* @module logger
*/
import log from "loglevel";
import log, { Logger } from "loglevel";
// This is to demonstrate, that you can use any namespace you want.
// Namespaces allow you to turn on/off the logging for specific parts of the
@@ -36,6 +36,11 @@ const DEFAULT_NAMESPACE = "matrix";
// when logging so we always get the current value of console methods.
log.methodFactory = function(methodName, logLevel, loggerName) {
return function(...args) {
/* eslint-disable babel/no-invalid-this */
if (this.prefix) {
args.unshift(this.prefix);
}
/* eslint-enable babel/no-invalid-this */
const supportedByConsole = methodName === "error" ||
methodName === "warn" ||
methodName === "trace" ||
@@ -57,3 +62,16 @@ log.methodFactory = function(methodName, logLevel, loggerName) {
export const logger = log.getLogger(DEFAULT_NAMESPACE);
logger.setLevel(log.levels.DEBUG);
interface PrefixedLogger extends Logger {
prefix?: any;
}
export function getPrefixedLogger(prefix): PrefixedLogger {
const prefixLogger: PrefixedLogger = log.getLogger(`${DEFAULT_NAMESPACE}-${prefix}`);
if (prefixLogger.prefix !== prefix) {
// Only do this setup work the first time through, as loggers are saved by name.
prefixLogger.prefix = prefix;
prefixLogger.setLevel(log.levels.DEBUG);
}
return prefixLogger;
}
+2
View File
@@ -142,6 +142,8 @@ export interface ICreateClientOpts {
unstableClientRelationAggregation?: boolean;
verificationMethods?: Array<any>;
forceTURN?: boolean;
iceCandidatePoolSize?: number,
supportsCallTransfer?: boolean,
fallbackICEServerAllowed?: boolean;
cryptoCallbacks?: ICryptoCallbacks;
}
+18
View File
@@ -811,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
*
+11
View File
@@ -23,6 +23,7 @@ import {EventEmitter} from "events";
import {RoomMember} from "./room-member";
import {logger} from '../logger';
import * as utils from "../utils";
import {EventType} from "../@types/event";
// possible statuses for out-of-band member loading
const OOB_STATUS_NOTSTARTED = 1;
@@ -718,6 +719,16 @@ RoomState.prototype.mayTriggerNotifOfType = function(notifLevelKey, userId) {
return member.powerLevel >= notifLevel;
};
/**
* Returns the join rule based on the m.room.join_rule state event, defaulting to `invite`.
* @returns {string} the join_rule applied to this room
*/
RoomState.prototype.getJoinRule = function() {
const joinRuleEvent = this.getStateEvents(EventType.RoomJoinRules, "");
const joinRuleContent = joinRuleEvent ? joinRuleEvent.getContent() : {};
return joinRuleContent["join_rule"] || "invite";
};
function _updateThirdPartyTokenCache(roomState, memberEvent) {
if (!memberEvent.getContent().third_party_invite) {
+37 -3
View File
@@ -30,6 +30,7 @@ import {RoomMember} from "./room-member";
import {RoomSummary} from "./room-summary";
import {logger} from '../logger';
import {ReEmitter} from '../ReEmitter';
import {EventType} from "../@types/event";
// These constants are used as sane defaults when the homeserver doesn't support
// the m.room_versions capability. In practice, KNOWN_SAFE_ROOM_VERSION should be
@@ -37,8 +38,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());
@@ -1822,7 +1823,7 @@ Room.prototype.getAccountData = function(type) {
/**
* Returns wheter the syncing user has permission to send a message in the room
* Returns whether the syncing user has permission to send a message in the room
* @return {boolean} true if the user should be permitted to send
* message events into the room.
*/
@@ -1831,6 +1832,30 @@ Room.prototype.maySendMessage = function() {
this.currentState.maySendEvent('m.room.message', this.myUserId);
};
/**
* Returns whether the given user has permissions to issue an invite for this room.
* @param {string} userId the ID of the Matrix user to check permissions for
* @returns {boolean} true if the user should be permitted to issue invites for this room.
*/
Room.prototype.canInvite = function(userId) {
let canInvite = this.getMyMembership() === "join";
const powerLevelsEvent = this.currentState.getStateEvents(EventType.RoomPowerLevels, "");
const powerLevels = powerLevelsEvent && powerLevelsEvent.getContent();
const me = this.getMember(userId);
if (powerLevels && me && powerLevels.invite > me.powerLevel) {
canInvite = false;
}
return canInvite;
};
/**
* Returns the join rule based on the m.room.join_rule state event, defaulting to `invite`.
* @returns {string} the join_rule applied to this room
*/
Room.prototype.getJoinRule = function() {
return this.currentState.getJoinRule();
};
/**
* This is an internal method. Calculates the name of the room from the current
* room state.
@@ -2048,3 +2073,12 @@ function memberNamesToRoomName(names, count = (names.length + 1)) {
*
* @param {EventStatus} oldStatus The previous event status.
*/
/**
* Fires when the logged in user's membership in the room is updated.
*
* @event module:models/room~Room#"Room.myMembership"
* @param {Room} room The room in which the membership has been updated
* @param {string} membership The new membership value
* @param {string} prevMembership The previous membership value
*/
+16 -1
View File
@@ -15,9 +15,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
const LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const DIGITS = "0123456789";
export function randomString(len: number): string {
return randomStringFrom(len, UPPERCASE + LOWERCASE + DIGITS);
}
export function randomLowercaseString(len: number): string {
return randomStringFrom(len, LOWERCASE);
}
export function randomUppercaseString(len: number): string {
return randomStringFrom(len, UPPERCASE);
}
function randomStringFrom(len: number, chars: string): string {
let ret = "";
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < len; ++i) {
ret += chars.charAt(Math.floor(Math.random() * chars.length));
+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;
}
}
+22 -21
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;
}
+221 -37
View File
@@ -27,6 +27,8 @@ import * as utils from '../utils';
import MatrixEvent from '../models/event';
import {EventType} from '../@types/event';
import { RoomMember } from '../models/room-member';
import { randomString } from '../randomstring';
import { MCallReplacesEvent, MCallAnswer, MCallOfferNegotiate, CallCapabilities } from './callEventTypes';
// events: hangup, error(err), replaced(call), state(state, oldState)
@@ -183,6 +185,21 @@ const FALLBACK_ICE_SERVER = 'stun:turn.matrix.org';
/** The length of time a call can be ringing for. */
const CALL_TIMEOUT_MS = 60000;
/** Retrieves sources from desktopCapturer */
export function getDesktopCapturerSources(): Promise<Array<DesktopCapturerSource>> {
const options: GetSourcesOptions = {
thumbnailSize: {
height: 176,
width: 312,
},
types: [
"screen",
"window",
],
};
return window.electron.getDesktopCapturerSources(options);
}
export class CallError extends Error {
code : string;
@@ -194,6 +211,10 @@ export class CallError extends Error {
}
}
function genCallID(): string {
return Date.now().toString() + randomString(16);
}
/**
* Construct a new Matrix Call.
* @constructor
@@ -240,6 +261,7 @@ export class MatrixCall extends EventEmitter {
// The party ID of the other side: undefined if we haven't chosen a partner
// yet, null if we have but they didn't send a party ID.
private opponentPartyId: string;
private opponentCaps: CallCapabilities;
private inviteTimeout: NodeJS.Timeout; // in the browser it's 'number'
// The logic of when & if a call is on hold is nontrivial and explained in is*OnHold
@@ -253,6 +275,11 @@ export class MatrixCall extends EventEmitter {
private micMuted;
private vidMuted;
// the stats for the call at the point it ended. We can't get these after we
// tear the call down, so we just grab a snapshot before we stop the call.
// The typescript definitions have this type as 'any' :(
private callStatsAtEnd: any[];
// Perfect negotiation state: https://www.w3.org/TR/webrtc/#perfect-negotiation-example
private makingOffer: boolean;
private ignoreOffer: boolean;
@@ -264,6 +291,9 @@ export class MatrixCall extends EventEmitter {
this.type = null;
this.forceTURN = opts.forceTURN;
this.ourPartyId = this.client.deviceId;
// We compare this to null to checks the presence of a party ID:
// make sure it's null, not undefined
this.opponentPartyId = null;
// Array of Objects with urls, username, credential keys
this.turnServers = opts.turnServers || [];
if (this.turnServers.length === 0 && this.client.isFallbackICEServerAllowed()) {
@@ -275,7 +305,7 @@ export class MatrixCall extends EventEmitter {
utils.checkObjectHasKeys(server, ["urls"]);
}
this.callId = "c" + new Date().getTime() + Math.random();
this.callId = genCallID();
this.state = CallState.Fledgling;
// A queue for candidates waiting to go out.
@@ -332,37 +362,80 @@ export class MatrixCall extends EventEmitter {
* to render the local camera preview.
* @throws If you have not specified a listener for 'error' events.
*/
async placeScreenSharingCall(remoteVideoElement: HTMLVideoElement, localVideoElement: HTMLVideoElement) {
async placeScreenSharingCall(
remoteVideoElement: HTMLVideoElement,
localVideoElement: HTMLVideoElement,
selectDesktopCapturerSource: () => Promise<DesktopCapturerSource>,
) {
logger.debug("placeScreenSharingCall");
this.checkForErrorListener();
this.localVideoElement = localVideoElement;
this.remoteVideoElement = remoteVideoElement;
try {
this.screenSharingStream = await navigator.mediaDevices.getDisplayMedia({'audio': false});
logger.debug("Got screen stream, requesting audio stream...");
const audioConstraints = getUserMediaVideoContraints(CallType.Voice);
this.placeCallWithConstraints(audioConstraints);
} catch (err) {
this.emit(CallEvent.Error,
new CallError(
CallErrorCode.NoUserMedia,
"Failed to get screen-sharing stream: ", err,
),
);
if (window.electron?.getDesktopCapturerSources) {
// We have access to getDesktopCapturerSources()
logger.debug("Electron getDesktopCapturerSources() is available...");
try {
const selectedSource = await selectDesktopCapturerSource();
// If no source was selected cancel call
if (!selectedSource) return;
const getUserMediaOptions: MediaStreamConstraints | DesktopCapturerConstraints = {
audio: false,
video: {
mandatory: {
chromeMediaSource: "desktop",
chromeMediaSourceId: selectedSource.id,
},
},
}
this.screenSharingStream = await window.navigator.mediaDevices.getUserMedia(getUserMediaOptions);
logger.debug("Got screen stream, requesting audio stream...");
const audioConstraints = getUserMediaVideoContraints(CallType.Voice);
this.placeCallWithConstraints(audioConstraints);
} catch (err) {
this.emit(CallEvent.Error,
new CallError(
CallErrorCode.NoUserMedia,
"Failed to get screen-sharing stream: ", err,
),
);
}
} else {
/* We do not have access to the Electron desktop capturer,
* therefore we can assume we are on the web */
logger.debug("Electron desktopCapturer is not available...");
try {
this.screenSharingStream = await navigator.mediaDevices.getDisplayMedia({'audio': false});
logger.debug("Got screen stream, requesting audio stream...");
const audioConstraints = getUserMediaVideoContraints(CallType.Voice);
this.placeCallWithConstraints(audioConstraints);
} catch (err) {
this.emit(CallEvent.Error,
new CallError(
CallErrorCode.NoUserMedia,
"Failed to get screen-sharing stream: ", err,
),
);
}
}
this.type = CallType.Video;
}
getOpponentMember() {
public getOpponentMember() {
return this.opponentMember;
}
public opponentCanBeTransferred() {
return Boolean(this.opponentCaps && this.opponentCaps["m.call.transferee"]);
}
/**
* Retrieve the local <code>&lt;video&gt;</code> DOM element.
* @return {Element} The dom element
*/
getLocalVideoElement(): HTMLVideoElement {
public getLocalVideoElement(): HTMLVideoElement {
return this.localVideoElement;
}
@@ -371,7 +444,7 @@ export class MatrixCall extends EventEmitter {
* used for playing back video capable streams.
* @return {Element} The dom element
*/
getRemoteVideoElement(): HTMLVideoElement {
public getRemoteVideoElement(): HTMLVideoElement {
return this.remoteVideoElement;
}
@@ -380,7 +453,7 @@ export class MatrixCall extends EventEmitter {
* used for playing back audio only streams.
* @return {Element} The dom element
*/
getRemoteAudioElement(): HTMLAudioElement {
public getRemoteAudioElement(): HTMLAudioElement {
return this.remoteAudioElement;
}
@@ -389,7 +462,7 @@ export class MatrixCall extends EventEmitter {
* video will be rendered to it immediately.
* @param {Element} element The <code>&lt;video&gt;</code> DOM element.
*/
async setLocalVideoElement(element : HTMLVideoElement) {
public async setLocalVideoElement(element : HTMLVideoElement) {
this.localVideoElement = element;
if (element && this.localAVStream && this.type === CallType.Video) {
@@ -410,7 +483,7 @@ export class MatrixCall extends EventEmitter {
* the first received video-capable stream will be rendered to it immediately.
* @param {Element} element The <code>&lt;video&gt;</code> DOM element.
*/
setRemoteVideoElement(element : HTMLVideoElement) {
public setRemoteVideoElement(element : HTMLVideoElement) {
if (element === this.remoteVideoElement) return;
element.autoplay = true;
@@ -432,7 +505,7 @@ export class MatrixCall extends EventEmitter {
* The audio will *not* be rendered from the remoteVideoElement.
* @param {Element} element The <code>&lt;video&gt;</code> DOM element.
*/
async setRemoteAudioElement(element: HTMLAudioElement) {
public async setRemoteAudioElement(element: HTMLAudioElement) {
if (element === this.remoteAudioElement) return;
this.remoteAudioElement = element;
@@ -440,12 +513,36 @@ export class MatrixCall extends EventEmitter {
if (this.remoteStream) this.playRemoteAudio();
}
// The typescript definitions have this type as 'any' :(
public async getCurrentCallStats(): Promise<any[]> {
if (this.callHasEnded()) {
return this.callStatsAtEnd;
}
return this.collectCallStats();
}
private async collectCallStats(): Promise<any[]> {
// This happens when the call fails before it starts.
// For example when we fail to get capture sources
if (!this.peerConn) return;
const statsReport = await this.peerConn.getStats();
const stats = [];
for (const item of statsReport) {
stats.push(item[1]);
}
return stats;
}
/**
* Configure this call from an invite event. Used by MatrixClient.
* @param {MatrixEvent} event The m.call.invite event
*/
async initWithInvite(event: MatrixEvent) {
this.msg = event.getContent();
this.direction = CallDirection.Inbound;
this.peerConn = this.createPeerConnection();
try {
await this.peerConn.setRemoteDescription(this.msg.offer);
@@ -467,9 +564,12 @@ export class MatrixCall extends EventEmitter {
this.type = this.remoteStream.getTracks().some(t => t.kind === 'video') ? CallType.Video : CallType.Voice;
this.setState(CallState.Ringing);
this.direction = CallDirection.Inbound;
this.opponentVersion = this.msg.version;
this.opponentPartyId = this.msg.party_id || null;
if (this.opponentVersion !== 0) {
// ignore party ID in v0 calls: party ID isn't a thing until v1
this.opponentPartyId = this.msg.party_id || null;
}
this.opponentCaps = this.msg.capabilities || {};
this.opponentMember = event.sender;
if (event.getLocalAge()) {
@@ -694,6 +794,21 @@ export class MatrixCall extends EventEmitter {
return callOnHold;
}
/**
* Sends a DTMF digit to the other party
* @param digit The digit (nb. string - '#' and '*' are dtmf too)
*/
sendDtmfDigit(digit: string) {
for (const sender of this.peerConn.getSenders()) {
if (sender.track.kind === 'audio' && sender.dtmf) {
sender.dtmf.insertDTMF(digit);
return;
}
}
throw new Error("Unable to find a track to send DTMF on");
}
private updateMuteStatus() {
if (!this.localAVStream) {
return;
@@ -764,7 +879,7 @@ export class MatrixCall extends EventEmitter {
this.peerConn.addTrack(audioTrack, stream);
}
for (const videoTrack of (this.screenSharingStream || stream).getVideoTracks()) {
logger.info("Adding audio track with id " + videoTrack.id);
logger.info("Adding video track with id " + videoTrack.id);
this.peerConn.addTrack(videoTrack, stream);
}
@@ -779,7 +894,14 @@ export class MatrixCall extends EventEmitter {
// required to still be sent for backwards compat
type: this.peerConn.localDescription.type,
},
};
} as MCallAnswer;
if (this.client._supportsCallTransfer) {
answerContent.capabilities = {
'm.call.transferee': true,
}
}
// We have just taken the local description from the peerconnection which will
// contain all the local candidates added so far, so we can discard any candidates
// we had queued up because they'll be in the answer.
@@ -950,7 +1072,7 @@ export class MatrixCall extends EventEmitter {
return;
}
if (this.opponentPartyId !== undefined) {
if (this.opponentPartyId !== null) {
logger.info(
`Ignoring answer from party ID ${event.getContent().party_id}: ` +
`we already have an answer/reject from ${this.opponentPartyId}`,
@@ -959,7 +1081,10 @@ export class MatrixCall extends EventEmitter {
}
this.opponentVersion = event.getContent().version;
this.opponentPartyId = event.getContent().party_id || null;
if (this.opponentVersion !== 0) {
this.opponentPartyId = event.getContent().party_id || null;
}
this.opponentCaps = event.getContent().capabilities || {};
this.opponentMember = event.sender;
this.setState(CallState.Connecting);
@@ -1044,8 +1169,21 @@ export class MatrixCall extends EventEmitter {
await this.peerConn.setRemoteDescription(description);
if (description.type === 'offer') {
// First we sent the direction of the tranciever to what we'd like it to be,
// irresepective of whether the other side has us on hold - so just whether we
// want the call to be on hold or not. This is necessary because in a few lines,
// we'll adjust the direction and unless we do this too, we'll never come off hold.
for (const tranceiver of this.peerConn.getTransceivers()) {
tranceiver.direction = this.isRemoteOnHold() ? 'inactive' : 'sendrecv';
}
const localDescription = await this.peerConn.createAnswer();
await this.peerConn.setLocalDescription(localDescription);
// Now we've got our answer, set the direction to the outcome of the negotiation.
// We need to do this otherwise Firefox will notice that the direction is not the
// currentDirection and try to negotiate itself off hold again.
for (const tranceiver of this.peerConn.getTransceivers()) {
tranceiver.direction = tranceiver.currentDirection;
}
this.sendVoipEvent(EventType.CallNegotiate, {
description: this.peerConn.localDescription,
@@ -1096,13 +1234,24 @@ export class MatrixCall extends EventEmitter {
if (this.callHasEnded()) return;
const keyName = this.state === CallState.CreateOffer ? 'offer' : 'description';
const eventType = this.state === CallState.CreateOffer ? EventType.CallInvite : EventType.CallNegotiate;
const content = {
[keyName]: this.peerConn.localDescription,
lifetime: CALL_TIMEOUT_MS,
};
} as MCallOfferNegotiate;
// clunky because TypeScript can't folow the types through if we use an expression as the key
if (this.state === CallState.CreateOffer) {
content.offer = this.peerConn.localDescription;
} else {
content.description = this.peerConn.localDescription;
}
if (this.client._supportsCallTransfer) {
content.capabilities = {
'm.call.transferee': true,
}
}
// Get rid of any candidates waiting to be sent: they'll be included in the local
// description we just got and will send in the offer.
@@ -1178,7 +1327,7 @@ export class MatrixCall extends EventEmitter {
return; // because ICE can still complete as we're ending the call
}
logger.debug(
"ICE connection state changed to: " + this.peerConn.iceConnectionState,
"Call ID " + this.callId + ": ICE connection state changed to: " + this.peerConn.iceConnectionState,
);
// ideally we'd consider the call to be connected when we get media but
// chrome doesn't implement any of the 'onstarted' events yet
@@ -1296,11 +1445,11 @@ export class MatrixCall extends EventEmitter {
}
onHangupReceived = (msg) => {
logger.debug("Hangup received");
logger.debug("Hangup received for call ID " + this.callId);
// party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen
// a partner yet but we're treating the hangup as a reject as per VoIP v0)
if (this.partyIdMatches(msg) || this.opponentPartyId === undefined || this.state === CallState.Ringing) {
if (this.partyIdMatches(msg) || this.state === CallState.Ringing) {
// default reason is user_hangup
this.terminate(CallParty.Remote, msg.reason || CallErrorCode.UserHangup, true);
} else {
@@ -1309,12 +1458,21 @@ export class MatrixCall extends EventEmitter {
};
onRejectReceived = (msg) => {
logger.debug("Reject received");
logger.debug("Reject received for call ID " + this.callId);
// No need to check party_id for reject because if we'd received either
// an answer or reject, we wouldn't be in state InviteSent
if (this.state === CallState.InviteSent) {
const shouldTerminate = (
// reject events also end the call if it's ringing: it's another of
// our devices rejecting the call.
([CallState.InviteSent, CallState.Ringing].includes(this.state)) ||
// also if we're in the init state and it's an inbound call, since
// this means we just haven't entered the ringing state yet
this.state === CallState.Fledgling && this.direction === CallDirection.Inbound
);
if (shouldTerminate) {
this.terminate(CallParty.Remote, CallErrorCode.UserHangup, true);
} else {
logger.debug(`Call is in state: ${this.state}: ignoring reject`);
@@ -1322,7 +1480,7 @@ export class MatrixCall extends EventEmitter {
};
onAnsweredElsewhere = (msg) => {
logger.debug("Answered elsewhere");
logger.debug("Call ID " + this.callId + " answered elsewhere");
this.terminate(CallParty.Remote, CallErrorCode.AnsweredElsewhere, true);
};
@@ -1370,9 +1528,33 @@ export class MatrixCall extends EventEmitter {
}
}
async transfer(targetUserId: string, targetRoomId?: string) {
// Fetch the target user's global profile info: their room avatar / displayname
// could be different in whatever room we shae with them.
const profileInfo = await this.client.getProfileInfo(targetUserId);
const replacementId = genCallID();
const body = {
replacement_id: genCallID(),
target_user: {
id: targetUserId,
display_name: profileInfo.display_name,
avatar_url: profileInfo.avatar_url,
},
create_call: replacementId,
} as MCallReplacesEvent;
if (targetRoomId) body.target_room = targetRoomId;
return this.sendVoipEvent(EventType.CallReplaces, body);
}
private async terminate(hangupParty: CallParty, hangupReason: CallErrorCode, shouldEmit: boolean) {
if (this.callHasEnded()) return;
this.callStatsAtEnd = await this.collectCallStats();
if (this.inviteTimeout) {
clearTimeout(this.inviteTimeout);
this.inviteTimeout = null;
@@ -1504,6 +1686,7 @@ export class MatrixCall extends EventEmitter {
const pc = new window.RTCPeerConnection({
iceTransportPolicy: this.forceTURN ? 'relay' : undefined,
iceServers: this.turnServers,
iceCandidatePoolSize: this.client._iceCandidatePoolSize,
});
// 'connectionstatechange' would be better, but firefox doesn't implement that.
@@ -1596,7 +1779,8 @@ export function setVideoInput(deviceId: string) { videoInput = deviceId; }
export function createNewMatrixCall(client: any, roomId: string, options?: CallOpts) {
// typeof prevents Node from erroring on an undefined reference
if (typeof(window) === 'undefined' || typeof(document) === 'undefined') {
logger.info("No window or document object: WebRTC is not supported in this environment");
// NB. We don't log here as apps try to create a call object as a test for
// whether calls are supported, so we shouldn't fill the logs up.
return null;
}
+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 */
+611 -435
View File
File diff suppressed because it is too large Load Diff