Compare commits

..

1145 Commits

Author SHA1 Message Date
RiotRobot fe36dafcc7 v3.42.2-rc.3 2022-04-08 10:40:56 +01:00
RiotRobot 04a6dbbedf Prepare changelog for v3.42.2-rc.3 2022-04-08 10:40:56 +01:00
RiotRobot 29b427aab7 v17.0.0-rc.1 2022-04-08 10:36:14 +01:00
RiotRobot 643b783dec Prepare changelog for v17.0.0-rc.1 2022-04-08 10:36:13 +01:00
Germain 872033a552 Port #2283 to release (#2284) 2022-04-08 10:34:09 +01:00
RiotRobot d457fd6db0 v16.0.2-rc.1 2022-04-05 18:07:38 +01:00
RiotRobot 03f4700bd7 Prepare changelog for v16.0.2-rc.1 2022-04-05 18:07:37 +01:00
Robin b8321290f8 Add Element video room type (#2273) 2022-04-04 10:29:35 -04:00
Kerry 71b7521f42 Live location sharing - handle redacted beacons (#2269)
* emit beacon destroy event on destroy

Signed-off-by: Kerry Archibald <kerrya@element.io>

* handle redacted beacon events in room-state

Signed-off-by: Kerry Archibald <kerrya@element.io>

* empty line

Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-04-04 10:17:49 +02:00
adamvy 106f7beb48 Fix getSessionsNeedingBackup() limit support (#2270) 2022-04-01 19:56:07 -06:00
Michael Telatynski d6f1c6cfdc Fix thread & main timeline partitioning logic (#2264) 2022-03-31 13:57:37 +01:00
Robin 4360ae7ff8 Fix coverage diffs for PRs that aren't up to date (#2263) 2022-03-30 08:02:14 -04:00
Michael Telatynski 26cbe02a7f Instantiate Thread objects when running fetchRoomThreads (#2262) 2022-03-29 15:28:08 +01:00
Michael Telatynski 85b8d4f83a Fix issues with /search and /context API handling for threads (#2261) 2022-03-29 09:24:45 +01:00
RiotRobot bdc3da1fac Resetting package fields for development 2022-03-28 14:38:44 +01:00
RiotRobot 95fcd5aa26 Merge branch 'master' into develop 2022-03-28 14:38:43 +01:00
RiotRobot 0ad83c43e4 v16.0.1 2022-03-28 14:35:08 +01:00
RiotRobot d6e4de4761 Prepare changelog for v16.0.1 2022-03-28 14:35:07 +01:00
Andy Balaam f03a391f80 Prevent exception 'Unable to set up secret storage' (#2260) 2022-03-28 11:48:34 +01:00
Andy Balaam e90f12ee32 Port codecov improvements matrix-react-sdk -> matrix-js-sdk (#2258) 2022-03-25 09:09:35 +00:00
Michael Telatynski c541b3f1ce Fix issues with duplicated MatrixEvent objects around threads (#2256) 2022-03-24 12:24:19 +00:00
Germain 6192325fe0 Thread list ordering by last reply (#2253) 2022-03-23 14:43:30 +00:00
Travis Ralston d0b964837f Remove groups (#2234)
This API is due for removal in Synapse and has been deprecated for a very long time. People should move away from it soon, but just in case we'll declare this as a breaking change.

There is no impact on sync storage here: we happen to store the data in a way that is backwards-compatible for group-supporting clients, and the code guards against missing data from the stores. So, if someone were to revert, they'd be "safe" (probably lose all their group info, but the app wouldn't crash).
2022-03-22 22:20:32 +00:00
Robin 65316ffb5c Voice rooms prototype (#2249)
* Support call room type from MSC3417

Signed-off-by: Robin Townsend <robin@robin.town>

* Make it more clear that call room type is unstable

Signed-off-by: Robin Townsend <robin@robin.town>
2022-03-22 16:14:23 -06:00
RiotRobot dadc19897c v16.0.1-rc.1 2022-03-22 21:40:04 +00:00
RiotRobot bd2f1858f4 Prepare changelog for v16.0.1-rc.1 2022-03-22 21:40:03 +00:00
Germain e32b8a75ee Lazy load thread list timeline set (#2254) 2022-03-22 21:34:20 +00:00
Kerry a6fe8797f0 Use beacon info event type as beacon identifier (#2251)
* use beacon info event type as beacon identifier

Signed-off-by: Kerry Archibald <kerrya@element.io>

* test cases

Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-03-22 11:14:43 +01:00
Michael Telatynski 29e54806a4 Improve typing for MatrixEvent::getContent generic (#2252) 2022-03-21 17:22:21 +01:00
Germain 75674d961a Create threads event timeline set in the room model (#2244) 2022-03-18 11:58:59 +00:00
Germain 779afbcb39 Apply redaction logic to threaded events (#2246) 2022-03-18 09:30:08 +00:00
Kerry a3f5ec1ba2 reemit beacon events (#2245)
* reemit beacon events

Signed-off-by: Kerry Archibald <kerrya@element.io>

* use specific imports

Signed-off-by: Kerry Archibald <kerrya@element.io>

* Update src/models/room-state.ts

Co-authored-by: Travis Ralston <travisr@matrix.org>

Co-authored-by: Travis Ralston <travisr@matrix.org>
2022-03-18 08:56:53 +00:00
Kerry 524322280b add upsert function for updating beacon events (#2247)
* add upsert function for updating beacon events

Signed-off-by: Kerry Archibald <kerrya@element.io>

* expose event type on beacon model

Signed-off-by: Kerry Archibald <kerrya@element.io>

* allow setting timestamp in beaconinfo content helper

Signed-off-by: Kerry Archibald <kerrya@element.io>

* expose parsed beacon info

Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-03-18 09:52:27 +01:00
James Salter 905a884f72 Formalise guidelines around writing tests (#2179) 2022-03-17 09:10:12 +00:00
Kerry 157635476b fix missed types for event emitter (#2243)
Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-03-16 13:50:07 +01:00
Kerry 18943d6519 emit aggregate room beacon liveness (#2241)
* emit aggregate room beacon liveness

Signed-off-by: Kerry Archibald <kerrya@element.io>

* tidy and comment

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add export for models/beacon

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add owner and roomId

Signed-off-by: Kerry Archibald <kerrya@element.io>

* copyright

Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-03-16 08:54:13 +00:00
Germain 42b3b73551 Fix threads reply count sometimes off by one (#2240) 2022-03-15 18:59:12 -06:00
RiotRobot ee9eccb85a Resetting package fields for development 2022-03-15 14:22:43 +00:00
RiotRobot 03a8d9edb6 Merge branch 'master' into develop 2022-03-15 14:22:42 +00:00
RiotRobot f1db4dc668 v16.0.0 2022-03-15 14:17:54 +00:00
RiotRobot 98c1710ac1 Prepare changelog for v16.0.0 2022-03-15 14:17:54 +00:00
Germain 510833b2f2 Use stable value for fallback (#2239) 2022-03-15 13:52:33 +00:00
Kerry c2fdb4478d Live location sharing - create m.beacon_info events (#2238)
* add content helpers

Signed-off-by: Kerry Archibald <kerrya@element.io>

* stubbed Beacon class

Signed-off-by: Kerry Archibald <kerrya@element.io>

* beacon test utils

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add beacon test utils

Signed-off-by: Kerry Archibald <kerrya@element.io>

* copyrights

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add beacons to room state

Signed-off-by: Kerry Archibald <kerrya@element.io>

* tidy comments

Signed-off-by: Kerry Archibald <kerrya@element.io>

* unit test RoomState.setBeacon

Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-03-15 10:31:32 +01:00
Kerry 57d71ccd0f Move test-utils into a directory (#2236)
* move test-utils.js into directory

Signed-off-by: Kerry Archibald <kerrya@element.io>

* fix imports

Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-03-14 14:55:11 +01:00
Kerry d064d82fcc Beacon event types from MSC3489 (#2230)
* ASSET_NODE_TYPE -> M_ASSET

Signed-off-by: Kerry Archibald <kerrya@element.io>

* export const M_TIMESTAMP = new UnstableValue("m.ts", "org.matrix.msc3488.ts");

Signed-off-by: Kerry Archibald <kerrya@element.io>

* LOCATION_EVENT_TYPE -> M_LOCATION

Signed-off-by: Kerry Archibald <kerrya@element.io>

* extensible event types for location

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add locationevent parsing helpers

Signed-off-by: Kerry Archibald <kerrya@element.io>

* rename

Signed-off-by: Kerry Archibald <kerrya@element.io>

* comment

Signed-off-by: Kerry Archibald <kerrya@element.io>

* revert makelocationcontent signature

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add beacon event types

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add variable* to type and comment

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add content helper functions for beacon_info and beacon

Signed-off-by: Kerry Archibald <kerrya@element.io>

* copyright

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add m.beacon_info.live from msc3672

Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-03-14 12:13:28 +00:00
Hubert Chathi 17f3920ddd Send and handle stable name for withheld codes (#2232)
since MSC2399 is finished FCP and it's in the spec, we can use the stable name
now
2022-03-11 16:21:06 -05:00
Germain 9fc8048c30 Fix incorrect toJSON for filter-component (#2231) 2022-03-11 10:46:18 +00:00
Germain 9058dbf289 Switch to using stable values for Threads (#2228) 2022-03-11 09:04:17 +00:00
Kerry e16e7bc098 Location event helper functions (#2229)
* ASSET_NODE_TYPE -> M_ASSET

Signed-off-by: Kerry Archibald <kerrya@element.io>

* export const M_TIMESTAMP = new UnstableValue("m.ts", "org.matrix.msc3488.ts");

Signed-off-by: Kerry Archibald <kerrya@element.io>

* LOCATION_EVENT_TYPE -> M_LOCATION

Signed-off-by: Kerry Archibald <kerrya@element.io>

* extensible event types for location

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add locationevent parsing helpers

Signed-off-by: Kerry Archibald <kerrya@element.io>

* rename

Signed-off-by: Kerry Archibald <kerrya@element.io>

* comment

Signed-off-by: Kerry Archibald <kerrya@element.io>

* revert makelocationcontent signature

Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-03-10 18:40:13 +01:00
Germain dbcd01bb43 Fix missing threads in thread list (#2226)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2022-03-10 16:44:42 +00:00
Michael Telatynski 40d1674a5c Fix incorrect usage of unstable variant of is_falling_back (#2227) 2022-03-10 15:31:57 +00:00
Michael Telatynski 35a375e3d2 Update thread relation fields to match MSC3440 changes (#2218) 2022-03-09 16:15:44 +00:00
Germain 460f4f9254 fix thread fallback targeting only thread relations (#2224) 2022-03-09 11:25:13 +00:00
J. Ryan Stinnett dbd6af745c Add lint for unused locals (#2223) 2022-03-09 10:09:00 +00:00
Germain 2ac9448646 Disable pending events for thread list when server supports threads (#2222) 2022-03-08 15:51:19 +00:00
RiotRobot 3141a7d7c1 v16.0.0-rc.1 2022-03-08 14:43:46 +00:00
RiotRobot 1304e811d0 Prepare changelog for v16.0.0-rc.1 2022-03-08 13:31:50 +00:00
Andy Balaam 2ce1e7e6ef Move codecov into the .github dir (#2220) 2022-03-04 15:56:14 +00:00
Michael Telatynski 70efed1a58 Add test coverage around push rules with no conditions (#2219) 2022-03-04 14:03:35 +00:00
Michael Telatynski 9e4f109e80 Fix wrongly asserting that PushRule::conditions is non-null (#2217) 2022-03-04 08:36:00 +00:00
Michael Telatynski 5d54bf558c Fix defer not supporting resolving with a Promise<T> (#2216) 2022-03-03 21:41:23 +00:00
Germain fc5f0e8047 Fix message ordering in threads (#2215) 2022-03-03 15:21:17 +00:00
Kerry 6bc584ba8b add LocationAssetType enum (#2214)
Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-03-03 10:49:16 +01:00
Germain ac4e504b8d Make eventType optional for relations (#2212) 2022-03-02 14:37:15 +00:00
Michael Telatynski 0abee2a0bf Fix wrong event_id being sent for m.in_reply_to of threads (#2213) 2022-03-02 14:27:08 +00:00
Germain 4e4afdb795 Update thread info after MSC3440 updates (#2209) 2022-03-02 10:52:08 +00:00
Michael Telatynski 9b429c1902 Throw error if both browser-index and (node) index are loaded (#2211) 2022-03-01 20:42:13 +00:00
Germain b782dee2ef Partition root event in thread and room timeline (#2210) 2022-03-01 13:04:24 +00:00
RiotRobot 54e815085f Fix main field 2022-02-28 17:38:59 +00:00
RiotRobot 0739da4ef4 Resetting package fields for development 2022-02-28 16:25:58 +00:00
RiotRobot c300a6bdd6 Merge branch 'master' into develop 2022-02-28 16:24:54 +00:00
RiotRobot 901d53eb38 v15.6.0 2022-02-28 16:16:51 +00:00
RiotRobot a54471f737 Prepare changelog for v15.6.0 2022-02-28 11:53:35 +00:00
Germain 1fae9cb3ef Update versions response type (#2208) 2022-02-28 10:44:38 +00:00
Germain 124bfc9328 Make createThread more resilient when missing rootEvent (#2207) 2022-02-28 10:02:09 +00:00
Šimon Brandner 53aa34fba5 Support for mid-call devices changes (#2154)
* Push to `usermediaSenders` in `upgradeCall()`

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Make sure to enable tracks after a call upgrade

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Simplify `updateMuteStatus()`

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Add copyright for 2022

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Add `updateLocalUsermediaStream()`

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Support mid-call device changes

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Use `updateLocalUsermediaStream()` for call upgrades

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Improve mock classes

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Add new tests

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2022-02-25 14:52:05 +00:00
Andy Balaam 58756a1973 Provide settings to control the comments codecov adds to PRs (#2206) 2022-02-25 14:22:09 +00:00
Michael Telatynski 7d9800b817 Stop using development /spaces API in favour of v1 /hierarchy API (#2204) 2022-02-25 10:43:24 +00:00
Michael Telatynski e408590c21 [Release] Fix bug with the /hierarchy API sending invalid requests (#2202) 2022-02-24 14:49:51 +00:00
Michael Telatynski 735ccca18b Fix bug with the /hierarchy API sending invalid requests (#2201) 2022-02-24 14:49:48 +00:00
Andy Balaam 946f47e037 Workflow for uploading coverage to codecov (#2200) 2022-02-24 14:40:20 +00:00
Michael Telatynski 4de1699c49 Add new room state emit RoomStateEvent.Update for lower-frequency hits (#2192)
Co-authored-by: Travis Ralston <travisr@matrix.org>
2022-02-24 13:16:59 +00:00
Andy Balaam 3d9221f054 Generate a JSON coverage file when requested to generate coverage (#2199) 2022-02-24 13:05:02 +00:00
Kerry 1d1d59c757 eslint to 8.9.0 (#2198)
Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-02-24 11:42:31 +01:00
Stanislav Demydiuk af43687354 Export additional types (#2195) 2022-02-24 09:35:05 +00:00
Kerry 2ec5acb55d fix relation sender filter (#2196)
* fix relation sender filter

Signed-off-by: Kerry Archibald <kerrya@element.io>

* lint

Signed-off-by: Kerry Archibald <kerrya@element.io>
2022-02-24 09:44:18 +01:00
David Baker 2128f67dc3 Fix bug with ine-way audio after a transfer (#2193)
Seems chrome at least will give you a disabled audio track if you
already had another user media audio track and disabled it, so make
sure our tracks are enabled when we add them. We already did this
on one code path but it didn't get moved over when a new code path
was added.

On the plus side, we now know the reason for the ancient code that
had the comment asking what it was for, so update that.
2022-02-23 09:54:14 +00:00
Germain 55dda8420c Update return type for client.getRoom (#2190) 2022-02-22 15:50:23 +00:00
RiotRobot d595717e60 v15.6.0-rc.1 2022-02-22 13:30:52 +00:00
RiotRobot 4603d4e578 Prepare changelog for v15.6.0-rc.1 2022-02-22 13:30:51 +00:00
Michael Telatynski 12e525b664 Improve typing around event emitter handlers (#2180) 2022-02-22 12:18:07 +00:00
Andy Balaam 1ac4cc4b11 Bump matrix-events-sdk to 0.0.1-beta.7 (#2184)
* Bump matrix-events-sdk to 0.0.1-beta.7

* Update lockfile for matrix-events-sdk-0.0.1-beta.7
2022-02-22 11:30:50 +00:00
David Baker 7b218905fb Fix bug where calls could break if rejected from somewhere else (#2189)
* Fix bug where calls could ignore new events of rejected from somewhere else

When callEventHandler passed a reject event to the call object, it assumed
that always caused the call to end and deleted it from the list, so it
never got any more events. The point of a reject is that it doesn't
end the call if it's already been picked up though. This only removes
the call if it's actually ended.

* Use ts-expect-error
2022-02-21 18:52:24 +00:00
David Baker 080426dfdd Fix camera stuck on after call transfer (#2188)
The 'Replaced' error code is handled specially (for better or worse)
so was leaving the capture feeds open. Use the 'transfer' hangup
reason which is what we should be using anyway (and were, on the line
below for the other call...)
2022-02-21 17:11:14 +00:00
Hugh Nimmo-Smith a89c1990d6 Return send event response from MSC3089Branch.createNewVersion() (#2186)
* Return send event response from MSC3089Branch.createNewVersion()

* docs: update JSDoc to match
2022-02-21 15:45:43 +00:00
Germain 4e72290d53 Null-guard for undefined rootEvent when creating a thread (#2187) 2022-02-21 15:06:01 +00:00
Patrick Cloke 7a7318b636 Fix incorrect type referenced in receiptCacheByEventId. (#2185) 2022-02-17 17:39:17 +00:00
RiotRobot 74d24f38f7 Merge branch 'master' into develop 2022-02-17 11:48:50 +00:00
RiotRobot 78dcae9143 v15.5.2 2022-02-17 11:45:23 +00:00
RiotRobot 0db640a679 Prepare changelog for v15.5.2 2022-02-17 11:45:22 +00:00
David Baker a3ddfd519b Fix synthetic read receipt handling (#2174) (#2183)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2022-02-17 11:29:28 +00:00
Travis Ralston e86d8861b9 Add functions to support refresh tokens (#2178)
* Add functions for refreshing access tokens

* Add function to change the client's access token in flight

* Appease the linter

* Use sensible code style
2022-02-15 20:07:36 +00:00
Michael Telatynski 2910e62bb6 Fix synthetic read receipt handling (#2174) 2022-02-14 18:45:02 -07:00
Michael Telatynski 2bb14698a5 Revert "Sign backup with cross-signing key when we reset it." (#2175) 2022-02-14 19:11:06 +00:00
RiotRobot 2fd08d72dc Resetting package fields for development 2022-02-14 15:17:32 +00:00
RiotRobot 222f62164c Merge branch 'master' into develop 2022-02-14 15:17:32 +00:00
RiotRobot bf8ee39a3f v15.5.1 2022-02-14 15:14:04 +00:00
RiotRobot cc1d1d76d0 Prepare changelog for v15.5.1 2022-02-14 15:14:04 +00:00
David Baker 30fdd96168 Revert event-mapper optimisations (#2171) (#2172)
Co-authored-by: Germain <germains@element.io>
2022-02-14 13:53:12 +00:00
Hubert Chathi cfad8d3614 Enable key backup after we reset it (#2170)
This ensures that we remember it if bootstrapCrossSigning gets called, so that
the authData gets signed by the master key, if a new key is created.
2022-02-11 08:42:49 -05:00
Germain d9c3b880fc Revert event-mapper optimisations (#2171) 2022-02-11 12:55:44 +00:00
Hugh Nimmo-Smith 25c115739c Fix error in uploadContent() when file is empty under Node.js (#2155)
* Fix error in uploadContent() when file is empty under Node.js

* Make type safe check work

* Make comment actually make sense
2022-02-11 11:16:10 +00:00
David Baker d97c514b8d Log when member event membership is undefined (#2169)
* Log when member event membership is undefined

To diagnose https://github.com/vector-im/element-web/issues/20962

* May as well have a stack trace too
2022-02-10 22:33:49 +00:00
Germain 6b822ccd61 Improve thread partitioning for 2nd degree relations (#2165) 2022-02-10 15:09:46 +00:00
Hubert Chathi 47c5c4645e Check the backup info against the stored private key when determining trust. (#2167) 2022-02-10 08:34:21 -05:00
Hubert Chathi ea0eaff212 Back up keys before logging out (#2158) 2022-02-09 08:36:43 -05:00
David Baker 41bf8c2d5f Yarn upgrade (#2164) 2022-02-08 19:17:28 +00:00
RiotRobot d473a2e095 v15.5.1-rc.1 2022-02-08 15:32:28 +00:00
RiotRobot d398ffd6df Prepare changelog for v15.5.1-rc.1 2022-02-08 15:32:27 +00:00
Michael Telatynski ffab55452a Fix issue with rooms not getting marked as unread (#2163) 2022-02-08 12:32:14 +00:00
Germain 9120b1dfd9 Expose room state helper (#2162) 2022-02-08 12:08:43 +00:00
Šimon Brandner 5d4e3183aa Don't store streams that are only used once (#2157)
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2022-02-08 10:13:58 +00:00
Michael Telatynski 07171a95c4 Fix edge cases around RR calculations (#2160) 2022-02-07 17:49:14 +00:00
Šimon Brandner 12afcd3850 Account for encryption in maySendMessage() (#2159) 2022-02-07 15:32:21 +00:00
Andy Balaam 2d23330b74 Send references to thread root to threads, even out of order (#2156)
Co-authored-by: Germain <germains@element.io>
2022-02-04 15:50:06 +00:00
Germain 7faff66006 Out of sync thread summary in main timeline (#2153) 2022-02-04 11:57:42 +00:00
Michael Telatynski b07457726b Avoid re-doing as much work when processing m.receipt event (#2151) 2022-02-03 11:06:19 +00:00
Germain 6bf8142ff6 Fix initial sync fail when event fetching unsuccessful (#2150) 2022-02-02 17:56:43 +00:00
Christian Paul cf0ccaf93d TypeScript: opts parameter of client.startClient is optional (#2106) 2022-02-02 15:29:58 +00:00
Christian Paul 34a3955b60 MatrixEvent.getRoomId can return undefined (#2036) 2022-02-02 16:22:57 +01:00
Germain 82122872bf Add thread relation in sendEvent if it's missing (#2149) 2022-02-02 14:44:38 +00:00
Germain 9c242a9ce6 Keep a reference to the latest thread created (#2148) 2022-02-01 14:50:42 +00:00
Germain 51f3fac87b Add thread relation to sticker message (#2147) 2022-02-01 10:36:04 +00:00
Germain 66b98844a2 Refactor thread model to be created from the root event (#2142) 2022-02-01 08:58:39 +00:00
David Baker d03db00e4c Yarn upgrade (#2146)
Apologies if this means everyone has to `rm -rf node_modules`: react-ace
decided to turn some paths that were regular files into directories
and it turns out this really confuses yarn: https://github.com/securingsincity/react-ace/issues/1048
2022-01-31 16:45:42 +00:00
RiotRobot cf8d2bf6ef Resetting package fields for development 2022-01-31 14:44:13 +00:00
RiotRobot 913a0b51a6 Merge branch 'master' into develop 2022-01-31 14:44:13 +00:00
RiotRobot ede0f696ee v15.5.0 2022-01-31 14:41:10 +00:00
RiotRobot d05214a169 Prepare changelog for v15.5.0 2022-01-31 14:41:09 +00:00
David Baker 1e93d0b19f Bump node-fetch from 2.6.6 to 2.6.7 (#2140) (#2145)
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.6 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.6...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-31 14:24:05 +00:00
Šimon Brandner 5e125e646c Don't decrypt redacted messages (#2143) 2022-01-31 11:21:12 +00:00
dependabot[bot] 1643201363 Bump node-fetch from 2.6.6 to 2.6.7 (#2140)
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.6 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.6...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-27 21:59:03 +00:00
dependabot[bot] 8e99e084a8 Bump cached-path-relative from 1.0.2 to 1.1.0 (#2138)
Bumps [cached-path-relative](https://github.com/ashaffer/cached-path-relative) from 1.0.2 to 1.1.0.
- [Release notes](https://github.com/ashaffer/cached-path-relative/releases)
- [Commits](https://github.com/ashaffer/cached-path-relative/commits)

---
updated-dependencies:
- dependency-name: cached-path-relative
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-27 14:52:23 -07:00
Germain 67d362fdd9 Implement local relation types filter (#2139) 2022-01-27 17:47:05 +00:00
RiotRobot e66430ada6 v15.5.0-rc.1 2022-01-26 16:56:34 +00:00
RiotRobot 52ca60142a Prepare changelog for v15.5.0-rc.1 2022-01-26 16:56:33 +00:00
Michael Telatynski 7d2a5afa6d Fix http-api butchering idServer requests (#2134) 2022-01-26 15:43:24 +00:00
Germain 02ba233644 Improved threads reliability with/without server side support (#2132) 2022-01-26 13:54:00 +00:00
Michael Telatynski 50493a3330 Improve typing for Room.timeline data parameter (#2131) 2022-01-26 13:24:10 +00:00
Germain 8434f29c54 Make threads reply chain filter out local event ID (#2129) 2022-01-25 10:51:12 +00:00
Michael Telatynski a34426a7f6 Improve signature of MatrixClient::isUsernameAvailable to not rely on throwing (#2130) 2022-01-25 10:45:39 +00:00
Germain 033693283d Fix local echo for reactions in threads (#2128) 2022-01-24 17:53:05 +00:00
Germain fab7b7f26a Add default IContent value in makeContentExtensible (#2126) 2022-01-24 09:28:58 +00:00
David Baker 230e3b4ace Add eslint camelcase ignores & remove unnecessary casts (#2110)
Presumbaly casts left over from typescript migration.
2022-01-19 18:19:08 +00:00
Andy Balaam a50a627300 Support m.asset in m.location event content (#2109) 2022-01-19 09:08:41 +00:00
Germain 80930f6690 Extend IEventRelation to define m.in_reply_to (#2108)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2022-01-19 08:57:32 +00:00
Michael Telatynski 652b3a9208 Revert "Remove getCapabilities call for guest users (#2100)" (#2107) 2022-01-18 09:55:54 +00:00
Travis Ralston 0a8b825702 Move polls to events-sdk, and invent a RelatedRelations type (#2102)
* Move polls handling to events-sdk & invent a multi-relation handler

Polls are now fully handled by the events-sdk instead of by the js-sdk.

The multi-relation handler (RelatedRelations) is primarily meant to handle unstable & stable support in polls when it becomes stable.

* update events-sdk for polls

* Update events-sdk for polls bugfix

* Update events-sdk for maintenance
2022-01-17 10:06:42 -07:00
RiotRobot f4e2a38f4b Resetting package fields for development 2022-01-17 14:10:20 +00:00
RiotRobot b3474531c9 Merge branch 'master' into develop 2022-01-17 14:10:20 +00:00
RiotRobot f0597e0124 v15.4.0 2022-01-17 14:06:06 +00:00
RiotRobot 2fdc91bd04 Prepare changelog for v15.4.0 2022-01-17 14:06:05 +00:00
David Baker 16ca09eed8 Don't remove streams that still have tracks (#2104)
If a renogotiation ends up with one track being removed, we removed
the whole stream, which would cause us to lose, for example, audio
rather than just video.
2022-01-14 09:43:50 +00:00
Travis Ralston 129fb7f10f Keep unknown fields when adding extensible formats (#2105)
* Keep unknown fields when adding extensible formats

This also properly handles `m.new_content` to also be extensible.

* appease the linter
2022-01-13 20:17:46 +00:00
Travis Ralston 016e24472a Send extensible events structure and support on-demand parsing (#2091)
* Parse extensible events on demand

* Decorate messages with MSC1767 when appropriate

We do this automatically to force a pool of messages in the wild we can use for testing.

* Include the SDK

* Appease linter and tests

* Change property name to appease linter

* Update SDK
2022-01-13 09:56:11 -07:00
David Teller 4acb98c496 [Fix] Nullcheck redactedEvent (#2103)
We should only check whether a redacted event is a visibility event *if the redacted event is not null*.
2022-01-12 13:27:03 +01:00
David Teller 96d1b30012 MSC3531: Hiding messages during moderation (#2041) 2022-01-12 11:27:33 +01:00
David Baker 6fc586598a Yarn upgrade (#2101) 2022-01-11 17:17:01 +00:00
Michael Telatynski 2d9c938765 Support cancelling events whilst they are in status = ENCRYPTING (#2095) 2022-01-11 15:03:33 +00:00
RiotRobot 83d1a6c046 v15.4.0-rc.1 2022-01-11 14:56:46 +00:00
RiotRobot 87393c7db8 Prepare changelog for v15.4.0-rc.1 2022-01-11 14:56:45 +00:00
Germain bd47667e63 Remove getCapabilities call for guest users (#2100) 2022-01-11 12:50:10 +00:00
Germain cfd865bf8b Fetch server capabilities during client initialisation (#2093) 2022-01-11 11:53:30 +00:00
Michael Telatynski 9b54df7b2b Don't consider alt_aliases when calculating room name (#2094) 2022-01-10 13:42:10 -07:00
Michael Telatynski 5da562fa6f Stop encrypting redactions as it isn't spec compliant (#2098) 2022-01-10 17:02:11 +00:00
J. Ryan Stinnett 83d952eae8 Fix spacing errors (#2096) 2022-01-10 12:57:11 +00:00
Hugh Nimmo-Smith f30be87879 Fix more function typings relating to key backup (#2086)
* Fix more function typings relating to key backup

* Use function overloads to specify allowed params
2021-12-23 17:23:31 +00:00
Hugh Nimmo-Smith ab19480cc5 Fix timeline search in MSC3089 getFileEvent() (#2085) 2021-12-23 17:23:19 +00:00
Šimon Brandner 99451698a4 Set a deviceId for VoIP example and use const/let (#2090) 2021-12-23 13:37:55 +00:00
Germain 48dbed8001 Make threads events indexed by the index manager (#2089) 2021-12-23 09:58:11 +00:00
Germain bae883a891 Update filters to reflect MSC3440 (threads) (#2065) 2021-12-22 13:58:21 +00:00
Hugh Nimmo-Smith 2aae2362e3 Fix incorrect TS return type for secret storage and key backup functions (#2082) 2021-12-20 16:52:50 +00:00
RiotRobot f780e1dbc3 Resetting package fields for development 2021-12-20 14:01:19 +00:00
RiotRobot fdc11b5e53 Merge branch 'master' into develop 2021-12-20 14:01:19 +00:00
RiotRobot 1a0bdb0f98 v15.3.0 2021-12-20 13:58:07 +00:00
RiotRobot e870bebd3d Prepare changelog for v15.3.0 2021-12-20 13:58:07 +00:00
Michael Telatynski dd0ca91aa9 Stop using v1-prefixed /hierarchy API due to Synapse bugs (#2080) 2021-12-17 15:33:55 +00:00
Hugh Nimmo-Smith 36906e2ddb Load room history if necessary when searching for MSC3089 getFileEvent() (#2066) 2021-12-16 21:50:33 -07:00
Michael Telatynski 3eaed30446 Iterate typing around sync, usage limits and errors (#2077) 2021-12-16 09:57:07 +00:00
Andy Balaam 72643026a3 Remove now-unused makePollContent from its old location (#2078) 2021-12-15 17:08:21 +00:00
Andy Balaam d6980cb8fa Provide a makeLocationContent to create a location content event (#2076) 2021-12-15 16:32:24 +00:00
Andy Balaam dcd24fd516 Add consts for location and extensible events types (#2074) 2021-12-15 13:49:26 +00:00
Andy Balaam 75cc873d77 Copy the polls constants to matrix-js-sdk from matrix-react-sdk (#2073) 2021-12-15 12:15:38 +00:00
Eric Eastwood dd23a1a401 Add support for MSC3030 /timestamp_to_event (#2072)
- `/jumptodate` slash command is being worked on in https://github.com/matrix-org/matrix-react-sdk/pull/7372
 - Jump to date headers are being worked on in https://github.com/matrix-org/matrix-react-sdk/pull/7339

Related to https://github.com/vector-im/element-web/issues/7677

Part of MSC3030: https://github.com/matrix-org/matrix-doc/pull/3030

Experimental Synapse implementation added in https://github.com/matrix-org/synapse/pull/9445
2021-12-15 05:06:26 -06:00
Michael Telatynski 6ac84a2465 Use v1 prefix for /hierarchy API, falling back to both previous variants (#2022) 2021-12-15 09:56:07 +00:00
Hugh Nimmo-Smith e4d3124e72 Move Room properties JSDoc into more usable comments on actual props (#2067) 2021-12-15 08:57:54 +00:00
Michael Telatynski df11a9e832 Fix sending undefined query args to Synapse (#2070) 2021-12-14 10:38:28 -07:00
David Baker e4703989fe Add git archive learnings to release script comment (#2069) 2021-12-14 15:46:34 +00:00
Michael Telatynski feb83ba161 Convert http-api to Typescript (#2063) 2021-12-14 15:34:50 +00:00
RiotRobot 22a98b3e4f v15.3.0-rc.1 2021-12-14 14:37:58 +00:00
RiotRobot 934a0e8943 Prepare changelog for v15.3.0-rc.1 2021-12-14 14:37:58 +00:00
Michael Telatynski 963c7690b6 Iterate typing to work towards noImplicitAny (#2061) 2021-12-14 14:32:35 +00:00
Michael Telatynski aeec4aa4a8 Update typescript-eslint plugin & parser (#2064) 2021-12-13 23:28:54 +00:00
Michael Telatynski 169b6b5572 Filter out falsey opts in /relations API hits (#2059) 2021-12-13 15:38:03 +00:00
RiotRobot 6244d77d44 Merge branch 'master' into develop 2021-12-13 15:22:07 +00:00
RiotRobot f4839a3b4f v15.2.1 2021-12-13 15:17:37 +00:00
RiotRobot 4b21b67a45 Prepare changelog for v15.2.1 2021-12-13 15:17:37 +00:00
David Baker 799a83a115 Update olm to 3.2.8 (#2062) 2021-12-13 14:59:03 +00:00
Michael Telatynski f3ebb2a314 Update Typescript to 4.5 (#2060) 2021-12-13 11:22:50 +00:00
Michael Telatynski c0fbed914e Update olm in yarn.lock (#2058) 2021-12-10 10:49:35 +00:00
Hubert Chathi dd8c157bb9 Improve fallback key behaviour (#2037) 2021-12-09 19:00:23 -05:00
Travis Ralston 8bd2d640f2 Update eslint 2021-12-09 16:01:07 -07:00
Aaron R 80aaa6c32b Fix imports to work with new lint rules (#2003)
* Add eslint-plugin-import

Signed-off-by: Aaron Raimist <aaron@raim.ist>

* Autofix

Signed-off-by: Aaron Raimist <aaron@raim.ist>

* Manual fix

Signed-off-by: Aaron Raimist <aaron@raim.ist>
2021-12-09 15:57:43 -07:00
Michael Telatynski 4e9fe8f53f Fix types (#2056) 2021-12-09 15:44:07 +00:00
Ajay Bura f926a2f777 Fix paginateEventTimeline resolve to boolean (#2054)
* Fix paginateEventTimeline resolve to boolean

Earlier paginateEventTimeline used to resolve to  chunk instead of returning boolean. Now it return boolean as specified in doc.

* added type
2021-12-09 20:00:55 +05:30
Michael Telatynski f8097221e6 Improve typing (#2055) 2021-12-09 14:22:58 +00:00
Callum Brown 95e7a76ba9 Finish adding registration token UIAA type (#2048)
Signed-off-by: Callum Brown <callum@calcuode.com>
2021-12-08 19:38:22 -07:00
Germain 72bc13eb39 Allow to omit optional params for /relations calls (#2053) 2021-12-08 08:23:53 +00:00
Germain 2206b80e65 Threads notifications after app startup (#2043) 2021-12-07 10:58:34 +00:00
Germain 39a8399977 Add new room event filter fields (#2051) 2021-12-07 09:42:52 +00:00
RiotRobot b7247fcedc Resetting package fields for development 2021-12-06 15:14:16 +00:00
RiotRobot e4494707fa Merge branch 'master' into develop 2021-12-06 15:14:16 +00:00
RiotRobot 45b7cab203 v15.2.0 2021-12-06 15:10:51 +00:00
RiotRobot 4dfc3b9dbb Prepare changelog for v15.2.0 2021-12-06 15:10:50 +00:00
Hugh Nimmo-Smith 15d05cd92f Fix incorrect MSC3089 typings and add null checks (#2049)
* Fix MSC3089TreeSpace typings to match actual code

* Implement strict null checks to resolve TS strictNullChecks=true issues
2021-12-06 10:54:43 +00:00
skyka13711 ba1b1cc6fa fix exports (#2047) 2021-12-02 19:40:10 -07:00
Michael Telatynski b716cde9b5 Add method to fetch /account/whoami (#2046) 2021-12-02 13:43:15 +00:00
RiotRobot 46a0645865 v15.2.0-rc.1 2021-11-30 18:16:24 +00:00
RiotRobot 2b30f45f60 Prepare changelog for v15.2.0-rc.1 2021-11-30 18:16:23 +00:00
J. Ryan Stinnett 87b920698f Upgrade allchange to 1.0.6 (#2042) 2021-11-30 17:55:38 +00:00
Germain 4f4811e1d9 Emit thread update after event decryption (#2040) 2021-11-30 14:40:59 +00:00
Hugh Nimmo-Smith db9936e07c Standardise content type handling in MSC3089 createFile() and createNewVersion() (#2014)
* Provide cross platform compatible versions of createFile() and createNewVersion()

The exist implementations are deprecated as they only work in a browser and support a different type of contents from MatrixClient.uploadContent()

* Fix MSC3089 content upload meta data in NodeJS runtime

* Break unstable createFile() and createNewVersion() instead of deprecating

Test using NodeJS types instead of mocked browser Blob

* chore: remove incorrect comment
2021-11-30 09:02:41 +00:00
Hugh Nimmo-Smith 7dcee2eb40 Implementation of deriveKey() for NodeJS (#2021)
Based on approach used by aes.ts
2021-11-30 09:02:26 +00:00
skyka13711 6b34ea6fe5 Exporting missing types (#2023)
* fix types export

* fix lint

* index.ts fix exports

* revert yarn.lock

Co-authored-by: Alexey Avramenko <2822698@gmail.com>
Co-authored-by: nikita <nikita@42px.ru>
2021-11-29 19:54:08 -07:00
Germain b33b01df0f Handle remote echo when creating a thread (#2038) 2021-11-25 08:27:13 +00:00
Germain 1c895432ed Fix backwards compatibility for redactEvent (#2039) 2021-11-24 15:42:10 +00:00
Germain ddd6a05198 Make local echo work for threads (#2026) 2021-11-24 08:19:17 +00:00
Germain 0ccde7807f Comprehensive events emitter typing (#2034) 2021-11-23 15:16:35 +00:00
Will Hunt 9f97992196 getStateEvent should return any (#2032)
* getStateEvent returns any

getStateEvent returns the content of an event, not the whole state event as per https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey

* Make the return type Record<string, any>

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-11-23 13:54:54 +00:00
Germain 67b8da719f Add a flag to track user participation in a thread (#2030) 2021-11-23 08:20:27 +00:00
Šimon Brandner 04fad564f7 Fix call upgrades (#2024)
* Init call feeds with non-muted state

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Improve CallFeed docs

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-11-22 18:17:35 -07:00
Germain aee698c285 Add KeyVerificationRequest msgType (#2033) 2021-11-22 17:08:24 +00:00
RiotRobot 55b489b17a Resetting package fields for development 2021-11-22 13:32:08 +00:00
RiotRobot c5d188c30a Merge branch 'master' into develop 2021-11-22 13:32:08 +00:00
RiotRobot 6fff3d6db5 v15.1.1 2021-11-22 13:28:39 +00:00
RiotRobot 8043d83921 Prepare changelog for v15.1.1 2021-11-22 13:28:39 +00:00
Michael Telatynski b2d83c1f80 Iterate type definitions (#2029) 2021-11-19 17:35:09 +00:00
Travis Ralston 1b91cd7037 Add optional force parameter when ensuring Olm sessions (#2027)
This is useful for debugging crypto where Element's `/discardsession` doesn't reset the Olm session.
2021-11-19 07:33:45 -07:00
skyka13711 0baf926c84 TimelineWindow fix load function signature (#2028) 2021-11-19 09:00:08 +00:00
Robert Long 763e25e031 Update sync state to error when aborting (#2025) 2021-11-18 12:16:57 -08:00
RiotRobot 861023b3f6 v15.1.1-rc.1 2021-11-17 13:52:47 +00:00
RiotRobot 1f5a83994b Prepare changelog for v15.1.1-rc.1 2021-11-17 13:52:47 +00:00
Dariusz Niemczyk af523522de Add LocalStorageErrorEventListener (#2019)
Add a way to listen to LocalStorage error events from element-web
and matrix-react-sdk in the same manner.

Related: https://github.com/vector-im/element-web/issues/18423
2021-11-15 18:08:20 +00:00
Dariusz Niemczyk c3a266b3e7 Implement TypedEventEmitter for better TS support (#2018)
We're using stringly typed events everywhere, this is the first step for
better typescript support with our event emitters before we replace it
with something much better for React support.
2021-11-15 14:47:21 +00:00
Callum Brown f41d815aa6 Add registration token UIA type (#2020) 2021-11-15 09:16:23 +00:00
Germain ad8a93dde8 Fix check supportExperimentalThreads (#2017) 2021-11-10 10:54:13 +00:00
Germain b07d44a6c0 Getter for last thread reply (#2015) 2021-11-09 14:46:48 +00:00
RiotRobot 1dc899ba6e Resetting package fields for development 2021-11-08 17:37:26 +00:00
RiotRobot 0b1e3edaff Merge branch 'master' into develop 2021-11-08 17:37:25 +00:00
RiotRobot 9fb4fed044 v15.1.0 2021-11-08 17:34:08 +00:00
RiotRobot 730b0b121d Prepare changelog for v15.1.0 2021-11-08 17:34:07 +00:00
Aaron R 9d9d9e2cfa Fix edit history being broken after editing an unencrypted event with an encrypted event (#2013) 2021-11-08 08:55:41 +00:00
Germain 43bc09f392 Copy relations to thread root in the thread timeline (#2012) 2021-11-05 14:16:45 +00:00
Germain 195498e9db Make events pagination responses parse threads (#2011) 2021-11-04 11:29:14 +00:00
Faye Duxovni 50332c4999 End authentication attempt immediately if we couldn't find an appropriate flow (#2008)
If `doRequest()` in `interactive-auth.ts` fails to obtain an appropriate authentication flow, it should return immediately after rejecting the promise.  If it continues, it'll attempt to check `chosenFlow.stages`, which will cause an error because `chosenFlow` is `null`.  This was breaking the interactive auth spec tests with Node 16.
2021-11-02 10:40:46 -04:00
RiotRobot 9e46646b16 v15.1.0-rc.1 2021-11-02 14:09:01 +00:00
RiotRobot a74a03e414 Prepare changelog for v15.1.0-rc.1 2021-11-02 14:09:00 +00:00
David Baker fe8b9eca8e Update allchange to 1.0.5 (#2010) 2021-11-02 13:30:49 +00:00
Germain a7cc1ae630 Fetch thread root event if it's missing from the sync (#2009) 2021-11-02 11:56:43 +00:00
Šimon Brandner 14e008bbad Don't emit CallError when we fail to get screen-sharing stream (#2005)
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-10-28 16:23:06 +01:00
Travis Ralston 6a12c265cf PSFD-455: Try to set a sender on search result events if possible (#2004)
This is to ensure that search results have aesthetic information such as display name and avatar. Though the membership event won't be in context for when the event was sent, it'll at least be something better than a bare user ID.
2021-10-28 07:48:27 -06:00
Šimon Brandner c35cb57a79 Port some changes from group calls branch to develop (#2001)
* Add some useful getters

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

Co-authored-by: Robert Long <robert@robertlong.me>

* Add removeLocalFeed()

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

Co-authored-by: Robert Long <robert@robertlong.me>

* Don't updateStream() if they're the same

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

Co-authored-by: Robert Long <robert@robertlong.me>

* Add isSpeaking()

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

Co-authored-by: Robert Long <robert@robertlong.me>

* Improve speaking detection using history

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

Co-authored-by: Robert Long <robert@robertlong.me>

* Make code for placing and answering calls more flexible

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

Co-authored-by: Robert Long <robert@robertlong.me>

* Correctly log stream id

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Remove mistaken parameter

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Add a unit

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

Co-authored-by: Robert Long <robert@robertlong.me>
2021-10-26 11:59:15 -07:00
Christian Paul 51c776f51e Correct TypeScript signature of client.sendCompleteEvent (#1997)
* Correct TypeScript signature of client.sendCompleteEvent

* Revert renaming txnId to txnIdOrCallback
2021-10-25 14:25:38 -06:00
Hubert Chathi 7ce0eb26d4 Fetch room membership from server rather than relying on stored data (#1998)
To work around the possibility that the persisted sync data may be out of date.
2021-10-25 10:29:46 -04:00
RiotRobot f0091fa811 Resetting package fields for development 2021-10-25 11:25:30 +01:00
RiotRobot 25c88bc986 Merge branch 'master' into develop 2021-10-25 11:25:29 +01:00
RiotRobot ab06f57965 v15.0.0 2021-10-25 11:22:22 +01:00
RiotRobot c46209a159 Prepare changelog for v15.0.0 2021-10-25 11:22:21 +01:00
Germain 0c47e27f9f Add explicit options to disable pending events (#1993) 2021-10-22 09:23:12 +01:00
Hubert Chathi 0385f265e8 Mark old verification methods as deprecated (#1994)
The old verification methods start a verification by sending an
`m.key.verification.start` message, rather than an `m.key.verification.request`
message.  This behaviour is deprecated in the
spec (https://github.com/matrix-org/matrix-doc/pull/3122), so these functions
should be deprecated too.
2021-10-21 16:10:13 -04:00
David Baker 59b3960a42 Strip direction override characters from display names (#1992)
Strip RLO and LRO characters from name and rawDisplayName so they
can safely be embedded into other text without messing up the text
ordering.

Fixes https://github.com/vector-im/element-web/issues/1712
2021-10-20 17:08:40 +01:00
Germain d5ab4ba2ef Update thread replies count to only include m.thread (#1991) 2021-10-20 16:49:54 +01:00
Travis Ralston f59b81f46b Update allchange (#1989) 2021-10-19 18:21:52 +01:00
Michael Telatynski 4c39ce8c96 Add method to fetch the MSC3266 Room Summary of a Room (#1988) 2021-10-19 16:13:29 +01:00
Germain Souquet 94ff0ea4cd Upgrade yarn dependencies 2021-10-19 11:24:53 +01:00
RiotRobot 526758a07f v15.0.0-rc.1 2021-10-19 10:50:42 +01:00
RiotRobot 9686fba81e Prepare changelog for v15.0.0-rc.1 2021-10-19 10:50:41 +01:00
Germain bbc547fd7f Merge pull request #1982 from matrix-org/gsouquet/thread-event-new 2021-10-15 14:32:19 +01:00
Germain 7d5dbeaf2d Merge pull request #1981 from matrix-org/gsouquet/threads-root-guard 2021-10-15 14:32:13 +01:00
Germain Souquet 8c19cf45e2 Dispatch a Thread.New event 2021-10-15 11:09:14 +01:00
Germain Souquet c9277de8ee Remove console statement 2021-10-15 10:31:43 +01:00
Germain Souquet b5baca51a6 Null-guard for thread root event not loaded yet 2021-10-15 10:22:32 +01:00
Germain fc800821f9 Merge pull request #1980 from matrix-org/gsouquet/threads-relations 2021-10-15 08:53:42 +01:00
Germain Souquet efbc46937f Null-guard for event that do not have a thread 2021-10-14 17:37:33 +01:00
Germain Souquet 9ceeb2d220 Delete UnstableValue and use hardcoded value for m.thread 2021-10-14 17:09:00 +01:00
Germain Souquet 7b89862ed0 Make threads use 'm.thread' relation 2021-10-14 16:56:48 +01:00
David Baker 6804e4290a Merge pull request #1972 from SimonBrandner/feature/answer-no-cam
Try to answer a call without video if we can't access the camera
2021-10-14 12:15:35 +01:00
David Baker 0a3d820fda Merge pull request #1943 from freaktechnik/develop
Fix `requestVerificationDM` with chronological `pendingEventOrdering`
2021-10-14 12:14:20 +01:00
Michael Telatynski e2a9c95ea8 Merge pull request #1974 from SimonBrandner/task/security-ts 2021-10-14 10:02:14 +01:00
Šimon Brandner 3aefc9f02e Add tests for falling back to answering with no video
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-10-14 08:46:36 +02:00
Šimon Brandner 0042cb5292 Fix importRoomKeys() definition
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-10-13 16:50:27 +02:00
Denis Kasak b288c97372 Merge pull request #1975 from matrix-org/dkasak/more-detailed-room-key-sharing-logging
More detailed logging when sharing room keys
2021-10-12 13:26:43 +00:00
Denis Kasak a0de1740a4 Add comma per review
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-10-12 13:23:22 +00:00
Denis Kasak 8e2e3018d8 Log device objects for existing/new devices when sharing room keys. 2021-10-12 13:48:41 +02:00
Denis Kasak 60e93024e7 Fix comment regarding marking blocked devices as notified. 2021-10-12 13:43:28 +02:00
Denis Kasak d89aa365f9 More detailed logging of blocked devices.
Separately log how many blocked devices there are in total and list them
out.

Then, log how many devices were notified because they were newly
blocked and list those out too.
2021-10-12 13:43:28 +02:00
Denis Kasak 4241263f2c Make log message regarding notifying unnotified failed devices clearer. 2021-10-12 13:43:26 +02:00
Denis Kasak b009f9c21b Log more clearly when we're sharing room keys with *new* Olm sessions. 2021-10-12 13:41:04 +02:00
Denis Kasak 04ccec6de3 Log which devices room keys were shared with. 2021-10-12 13:41:04 +02:00
RiotRobot e7b41fadd0 Merge branch 'master' into develop 2021-10-12 08:50:43 +01:00
RiotRobot 2dc1acef2e Resetting package fields for development 2021-10-12 08:36:13 +01:00
RiotRobot b977b629f4 Merge branch 'release-v14.0.1' 2021-10-12 08:35:42 +01:00
RiotRobot 6bbd493e20 v14.0.1 2021-10-12 08:29:30 +01:00
RiotRobot 38ad435af5 Prepare changelog for v14.0.1 2021-10-12 08:29:30 +01:00
RiotRobot 8f245af317 Merge branch 'master' into develop 2021-10-11 11:48:22 +01:00
RiotRobot 9b2bfbe7d0 v14.0.0 2021-10-11 11:44:54 +01:00
RiotRobot f69ea85e55 Prepare changelog for v14.0.0 2021-10-11 11:44:53 +01:00
Šimon Brandner 7559a10526 Make opts in importRoomKeys optional
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-10-10 08:36:11 +02:00
Šimon Brandner 68dbe959bb Add a logline
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-10-10 08:19:29 +02:00
Šimon Brandner 74a875aa23 Add MockMediaDeviceInfo
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-10-10 08:19:29 +02:00
Šimon Brandner 4b679c5056 Try to answer without video if we can't access it
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-10-10 08:19:22 +02:00
Šimon Brandner cb8a260b9c Improve handling when we don't have a camera
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-10-09 08:36:19 +02:00
Robert Long 7d7975b8bb Fix connecting to a call without a webcam
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-10-09 08:05:59 +02:00
Travis Ralston f12513da96 Merge pull request #1971 from matrix-org/travis/spam-log
Remove excess logging from getRoomUpgradeHistory()
2021-10-08 10:33:07 -06:00
Travis Ralston 785a3e3381 Remove excess logging from getRoomUpgradeHistory()
On large accounts this can now cause a massive amount of spam. It's not really providing any value in day-to-day debugging either.
2021-10-08 10:28:20 -06:00
RiotRobot f5fcc20840 v14.0.0-rc.2 2021-10-07 17:42:40 +01:00
RiotRobot 17e20c17b3 Prepare changelog for v14.0.0-rc.2 2021-10-07 17:42:40 +01:00
Michael Telatynski 39fd350ffe Merge pull request #1969 from matrix-org/t3chguy/cp/space-hierarchy 2021-10-06 16:51:32 +01:00
Michael Telatynski b4d100baff Tweak Room Hierarchy root visibility and add loading getter 2021-10-06 13:29:13 +01:00
Michael Telatynski 29bc83f4a7 Merge pull request #1968 from matrix-org/t3chguy/fix/19276 2021-10-06 13:27:12 +01:00
Michael Telatynski 13c8774d45 Tweak Room Hierarchy root visibility and add loading getter 2021-10-06 10:25:27 +01:00
Travis Ralston c9212e770d Merge pull request #1966 from Alexendoo/declaration-map
Enable TypeScript declaration maps
2021-10-05 13:39:47 -06:00
Germain 08aa7803e3 Merge pull request #1964 from SimonBrandner/feature/call-feed-opts 2021-10-04 15:02:49 +01:00
Alex Macleod 770858c255 Enable TypeScript declaration maps
This allows go-to-definition to reach the original .ts files for
consumers of the package. It emits .d.ts.map files alongside the regular
.d.ts files

e.g. in sdk.createClient go-to-definition on createClient will place the
cursor at the function in src/matrix.ts rather than inside a definition
file lib/matrix.d.ts

Signed-off-by: Alex Macleod <alex@macleod.io>
2021-10-04 14:36:50 +01:00
Germain Souquet c73bb7d408 Update dependencies 2021-10-04 12:30:14 +01:00
RiotRobot 15cd675354 v14.0.0-rc.1 2021-10-04 11:55:19 +01:00
RiotRobot e8137d3f88 Prepare changelog for v14.0.0-rc.1 2021-10-04 11:55:19 +01:00
Šimon Brandner c63abe9988 Use ICallFeedOpts in the CallFeed constructor
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-10-02 08:12:43 +02:00
Germain a7a08fd760 Merge pull request #1962 from matrix-org/gsouquet/fix-19229 2021-10-01 15:46:30 +01:00
Germain df51f95fbd Merge pull request #1963 from matrix-org/gsouquet/fix-19225 2021-10-01 12:11:35 +01:00
Germain Souquet a14cf1a339 Remove unused DuplicateStrategy 2021-10-01 12:07:16 +01:00
Germain Souquet 30758600f0 Prepend events to thread when fetching the reply chain 2021-10-01 10:49:46 +01:00
Germain 823242a93c Merge pull request #1959 from psrpinto/fix/joined-rooms-response 2021-09-30 17:54:53 +01:00
Martin Giger 482e3c4cb7 Skip waiting if already sent and abort on cancel 2021-09-30 16:58:08 +02:00
Germain Souquet 1dbd7158ad Prevent redactions to be sent as pending events
Co-authored-by: Dariusz Niemczyk <Palid@users.noreply.github.com>
2021-09-30 13:56:52 +01:00
Michael Telatynski 46b3f0babd Merge pull request #1958 from SimonBrandner/fix/call-length 2021-09-29 15:04:02 +01:00
Šimon Brandner b4bc554d7a Make sure to callLengthInterval only once
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-29 15:46:22 +02:00
Paulo Pinto 1485969493 Return IJoinedRoomsResponse from getJoinedRooms(), instead of string[]
Signed-off-by: Paulo Pinto <paulo.pinto@automattic.com>
2021-09-29 11:45:01 +01:00
Travis Ralston 92f7ddcf3e Merge pull request #1954 from SimonBrandner/task/src-ts
Changes for TS migration
2021-09-27 13:45:06 -06:00
Travis Ralston 90d480b4cf Merge pull request #1952 from matrix-org/travis/fsdk/versions
Implement file versioning for tree spaces
2021-09-27 10:04:22 -06:00
RiotRobot 2515d07c8f Resetting package fields for development 2021-09-27 14:23:47 +01:00
RiotRobot 7acb9416c2 Merge branch 'master' into develop 2021-09-27 14:23:47 +01:00
RiotRobot 095e3a8a88 v13.0.0 2021-09-27 14:20:00 +01:00
RiotRobot 92623a246a Prepare changelog for v13.0.0 2021-09-27 14:20:00 +01:00
Michael Telatynski 0c4212ca90 Merge pull request #1935 from matrix-org/t3chguy/fix/18969 2021-09-27 11:08:07 +01:00
Germain 9a06a05a75 Merge pull request #1953 from SimonBrandner/fix/audio-upgrade 2021-09-27 10:18:13 +01:00
Šimon Brandner f05a0615f7 Export IRequestMsisdnTokenResponse
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-27 09:45:07 +02:00
Šimon Brandner 6b93687ff0 Pass correct params to upgradeCall() to upgrade audio
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-26 13:10:00 +02:00
Travis Ralston 8d910d5e26 Merge pull request #1950 from SimonBrandner/feature/flexible-answer
Allow answering calls without audio/video
2021-09-25 23:39:20 -06:00
Šimon Brandner 9264050f41 Update src/webrtc/call.ts
Co-authored-by: Travis Ralston <travpc@gmail.com>
2021-09-26 07:26:08 +02:00
Travis Ralston eab0c54663 Appease the linter 2021-09-25 20:47:59 -06:00
Travis Ralston 82a254b7bd Implement file versioning for tree spaces
The diff isn't super clear on how this works, but the general idea is that the MSC3089Branches (files) now know which directory they came from, and the directories (MSC3089TreeSpace) can tell when files are triggering uploads referencing themselves so it can add all the replacement metadata onto the event.

There's a few challenges with how relations work in the js-sdk which has shaped the API surface exposed by this change. Specifically, there is a `getVersionHistory()` function instead of a function to get a certain prior version: the js-sdk doesn't track parent events in the relation structures so cannot determine what the event could have replaced. 

In order to trigger the built-in relations structures, we must trigger decryption with `emit: true`. This is so an internal listener in the relations system can pick up `Event.decrypted`.
2021-09-25 20:44:17 -06:00
Šimon Brandner 0f6a59ed98 Allow answering calls without audio/video
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-25 10:00:51 +02:00
Travis Ralston afb4682987 Merge pull request #1949 from SimonBrandner/fix/mic-state
Fix not returning new mic mute state
2021-09-25 01:14:11 -06:00
Šimon Brandner a18326519b Add missing return statement
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-25 08:26:13 +02:00
Germain b033540f95 Merge pull request #1948 from matrix-org/gsouquet/fix-sync-partitioning 2021-09-24 15:43:27 +01:00
Germain Souquet ff529d733d Fix event partitioning from non threading ready clients 2021-09-24 14:54:40 +01:00
Germain 5951958c05 Merge pull request #1945 from matrix-org/gsouquet/thread-serialise-18720 2021-09-24 11:17:39 +01:00
David Baker 886f2fb95d Merge pull request #1947 from matrix-org/dbkr/we_like_tests
Add tests requirements to CONTRIBUTING
2021-09-23 18:15:32 +01:00
David Baker f6ce0c581e Add tests requirements to CONTRIBUTING
Fixes https://github.com/vector-im/element-web/issues/19136
2021-09-23 14:09:01 +01:00
Germain 5ce68b6c12 Merge pull request #1944 from matrix-org/gsouquet/thread-state-events-18719 2021-09-23 09:17:02 +01:00
Germain Souquet c748aebfc5 Add context to explain why we fetch roomstate in threads 2021-09-22 17:36:12 +01:00
Germain Souquet f8ec445ff0 Fix addLiveEvent with 'replace' strategy 2021-09-22 17:14:18 +01:00
Germain Souquet 5d4f347eaa Add thread serialisation helper 2021-09-22 11:35:19 +01:00
Germain Souquet 1359c0574e Share room state events with thread's timelines 2021-09-22 11:19:08 +01:00
Travis Ralston b1d239b292 Merge pull request #1934 from aaronraimist/kick-api
Switch to /kick API
2021-09-21 23:30:23 -06:00
Travis Ralston 70031415e8 Merge pull request #1938 from matrix-org/travis/fix-widgets-relations
Ensure unencrypted fields get exposed by getEffectiveEvent()
2021-09-21 23:16:18 -06:00
Martin Giger 3c97d17770 Fix requestVerificationDM with chronological pendingEventOrdering
Signed-off-by: Martin Giger <martin@humanoids.be>
2021-09-21 20:04:07 +02:00
Travis Ralston 5a73a9758a Merge pull request #1941 from SimonBrandner/feature/bound
Add `bound` to `IThreepid`
2021-09-21 11:36:36 -06:00
Travis Ralston 6848e96244 Merge pull request #1942 from SimonBrandner/feature/trusted_locally
Add `trusted_locally` to `TrustInfo`
2021-09-21 11:26:35 -06:00
Travis Ralston b53566e074 Merge pull request #1827 from SimonBrandner/feature/call-upgrades
Support for call upgrades
2021-09-21 09:10:02 -06:00
Šimon Brandner 012b914a97 Add trusted_locally to TrustInfo
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-21 16:20:39 +02:00
Šimon Brandner 3c6eed0135 Add bound to IThreepid
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-21 14:56:33 +02:00
RiotRobot f2a2e7f5dd v13.0.0-rc.1 2021-09-21 09:24:22 +01:00
RiotRobot 15b6a9a257 Prepare changelog for v13.0.0-rc.1 2021-09-21 09:24:21 +01:00
Šimon Brandner b83977e72f Add @param docs
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-20 16:40:46 +02:00
Travis Ralston 8f5082cd2f Merge pull request #1940 from SimonBrandner/feature/getHistoryVisibility
Add `getHistoryVisibility()` and `getGuestAccess()`
2021-09-20 08:34:14 -06:00
David Baker 50c877cb60 Merge pull request #1939 from SimonBrandner/fix/stream-stopping
Fix stream stopping and improve logging
2021-09-20 11:56:36 +01:00
Šimon Brandner e3e7fd5900 Add getGuestAccess()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-19 10:47:18 +02:00
Šimon Brandner 69823f9cae Add getHistoryVisibility()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-19 10:36:45 +02:00
Šimon Brandner e5dcdf109a Fix stream stopping and improve logging
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-19 09:29:16 +02:00
Šimon Brandner 5c6a643436 Merge branch 'feature/call-upgrades' of https://github.com/SimonBrandner/matrix-js-sdk into feature/call-upgrades 2021-09-19 07:44:40 +02:00
Šimon Brandner 1ca1a69eb8 Fix docs
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-19 07:44:28 +02:00
Šimon Brandner 76ea1ba192 Improve log
Co-authored-by: Travis Ralston <travpc@gmail.com>
2021-09-19 07:43:09 +02:00
Šimon Brandner 8cd8ec134e Merge remote-tracking branch 'upstream/develop' into feature/call-upgrades 2021-09-19 07:42:04 +02:00
Travis Ralston 6c649e2bfe Merge pull request #1937 from SimonBrandner/fix/getUserMediaStream
Fix `getUserMediaStream()` not being able to handle streams of different track combinations
2021-09-18 16:29:15 -06:00
Travis Ralston 9f42647a75 Ensure unencrypted fields get exposed by getEffectiveEvent()
Fixes https://github.com/vector-im/element-web/issues/19062
2021-09-18 16:26:08 -06:00
Šimon Brandner 9747e5e0d5 Fix getUserMediaStream() not being able to handle streams of different track combinations
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-18 13:18:28 +02:00
Šimon Brandner a4cacbc73a Add call upgrade support
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-18 12:32:13 +02:00
Šimon Brandner f24840d09c Make CallFeed handle addTrack events
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-18 12:31:54 +02:00
Šimon Brandner acb7991cc5 Fix getUserMediaStream() not being able to handle streams of different track combinations
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-18 12:27:24 +02:00
Šimon Brandner 6addc7dd3d Add MockMediaStream
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-18 12:27:08 +02:00
Eric Eastwood 2bd60f4160 Merge pull request #1933 from matrix-org/madlittlemods/more-better-pr-descriptions
Update PR guidelines for easier review
2021-09-17 11:54:13 -05:00
David Baker e71362a6df Merge pull request #1936 from matrix-org/dbkr/await_save_backup_key
Fix race on automatic backup restore
2021-09-17 17:06:43 +01:00
David Baker 673c09cd77 Fix race on automatic backup restore
We forgot to await the saving of the backup key, so there was a race
where it would fail to get the backup key because it hadn't been saved
yet, so the backup wouldn't be restored automatically.

Fixes https://github.com/vector-im/element-web/issues/17781
2021-09-17 15:53:14 +01:00
Michael Telatynski 4606422e57 Make RoomState::hasSufficientPowerLevelFor public 2021-09-17 15:35:03 +01:00
Dariusz Niemczyk 4bc8f968e0 Merge pull request #1932 from matrix-org/palid/threads-typescriptify
Fix PR comments
2021-09-17 08:11:36 +02:00
Aaron Raimist 35ee58dfe1 Switch to /kick API
Signed-off-by: Aaron Raimist <aaron@raim.ist>
2021-09-17 01:05:21 -05:00
Eric Eastwood 3705426b6d Make PR's easier to review 2021-09-16 16:05:49 -05:00
Dariusz Niemczyk 5813aea27f Properly rename ThreadEvents to ThreadEvent 2021-09-16 17:49:31 +02:00
Dariusz Niemczyk 5ba836a31b Fix PR comments 2021-09-16 17:12:57 +02:00
Dariusz Niemczyk 4f387d15b9 Merge pull request #1930 from matrix-org/palid/threads-typescriptify
Typescriptify thread events a bit
2021-09-16 17:12:02 +02:00
Dariusz Niemczyk 8ab0246ca2 Fix PR comments 2021-09-16 16:03:47 +02:00
Dariusz Niemczyk ed68bd4108 Typescriptify thread events a bit 2021-09-16 15:59:41 +02:00
Dariusz Niemczyk 3d39e8ec26 Merge pull request #1880 from matrix-org/palid/fix/dont-persist-log-level
Palid/fix/dont persist log level
2021-09-16 14:52:52 +02:00
Robert Long 53bdab7cc8 Merge pull request #1929 from matrix-org/robertlong/datachannels
Add WebRTC DataChannels to MatrixCall
2021-09-15 14:25:13 -07:00
Robert Long fafa55ba1d Merge pull request #1928 from matrix-org/robertlong/clone-streams
Clone and stop local media streams from mediaHandler
2021-09-15 14:25:00 -07:00
Travis Ralston 3a818c1419 Merge pull request #1927 from SimonBrandner/feature/getBuffer
Add `getBuffer()` to `QRCodeData`
2021-09-15 14:42:11 -06:00
Robert Long 422853298c Reset media stream arrays 2021-09-15 13:20:52 -07:00
Robert Long f3a2bd6b40 Clean up stopAllMedia 2021-09-15 12:38:12 -07:00
Robert Long 6834137f50 Add datachannels to MatrixCall 2021-09-15 12:28:09 -07:00
Robert Long c4d7fef0cd Clone and stop local media streams from mediaHandler 2021-09-15 12:08:28 -07:00
Šimon Brandner 556b5ff628 Add getBuffer() to QRCodeData
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-15 20:27:13 +02:00
David Baker 2daa1ec4ce Merge pull request #1916 from matrix-org/dbkr/verify_key_share_tests
Tests for key sharing security fix
2021-09-15 18:38:03 +01:00
Travis Ralston 348ea72b70 Merge pull request #1925 from SimonBrandner/task/setCodecPreferences
Add `setCodecPreferences()` to `global.d.ts`
2021-09-15 09:48:27 -06:00
Šimon Brandner 4e14d810c5 Add setCodecPreferences()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-15 17:44:16 +02:00
Travis Ralston f84905b003 Merge pull request #1924 from SimonBrandner/revert-1923-task/die-rtx-hack
Revert "Remove RTX codec hack "
2021-09-15 09:23:27 -06:00
Šimon Brandner 9bb87145f6 Revert "Remove RTX codec hack " 2021-09-15 17:17:56 +02:00
Travis Ralston c7675570aa Merge pull request #1923 from SimonBrandner/task/die-rtx-hack
Remove RTX codec hack
2021-09-15 09:16:55 -06:00
Šimon Brandner be942fd66a Remove RTX codec hack as the issue is no longer present on Chromium 90
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-15 16:38:59 +02:00
James Salter 8544aca362 Merge pull request #1922 from matrix-org/revert-errors-ts
Revert "Migrate errors to TypeScript"
2021-09-15 12:08:32 +01:00
James Salter c3308e1de9 Revert "Migrate errors to TypeScript"
This reverts commit ab906faa17.
2021-09-15 12:01:34 +01:00
David Baker 047d7125a1 Merge pull request #1920 from matrix-org/dbkr/fix_autodiscovery_types
Fix types
2021-09-15 11:29:10 +01:00
David Baker cf4d3117c1 Fix types
This is 'any' (defined as any by IWellKnownConfig.raw) rather than
string (which the jsdoc claimed). We were calling this method ourselves
but with an object that was also 'any', so it was fine, but react-sdk
calls it with an object literal, which typescript knows it not a string.
2021-09-15 10:29:14 +01:00
David Baker 88c2844e48 Merge pull request #1919 from matrix-org/dbkr/yarnupgrade_20210914
yarn upgrade
2021-09-15 10:01:18 +01:00
Germain 6ca4694fe6 Merge pull request #1907 from matrix-org/gsouquet/ts-last-push 2021-09-15 08:30:52 +01:00
Germain 062287e270 Merge pull request #1913 from matrix-org/gsouquet/ts-security 2021-09-15 08:27:29 +01:00
Germain Souquet 50e0d76f10 remove erroneous comment related to typings 2021-09-15 09:26:25 +02:00
Travis Ralston df039d3d8c Merge pull request #1911 from SimonBrandner/feature/media-handling
Improve `MatrixCall` media handling and internally remove `CallType`s
2021-09-14 14:40:45 -06:00
Travis Ralston 799606b73c Merge pull request #1909 from matrix-org/travis/fsdk/locking
Add file locking to MSC3089 branches
2021-09-14 12:39:41 -06:00
Travis Ralston c048f5d357 Merge pull request #1910 from matrix-org/travis/fsdk/tests
Add missing tests for MSC3089 implementation
2021-09-14 12:37:54 -06:00
Robert Long f712a8cecb Merge pull request #1903 from matrix-org/robertlong/fix-register-types
Add types for the MatrixClient register method
2021-09-14 10:56:12 -07:00
David Baker e1d8d6bf4e Not typescript again
because this time they just removed setCodecPreferences from webrtc?
2021-09-14 18:52:35 +01:00
Robert Long 14ed8a110c Update src/client.ts
Co-authored-by: Travis Ralston <travisr@matrix.org>
2021-09-14 10:50:50 -07:00
David Baker cb6700f21b yarn upgrade 2021-09-14 18:41:14 +01:00
Travis Ralston d4f35bf07a fix main package? 2021-09-14 10:03:23 -06:00
RiotRobot b4eb29138b Resetting package fields for development 2021-09-14 15:52:38 +01:00
RiotRobot 8ca3a071f9 Merge branch 'master' into develop 2021-09-14 15:51:45 +01:00
RiotRobot 5abd211436 Merge branch 'release-v12.5.0' 2021-09-14 15:51:13 +01:00
RiotRobot 99f428e091 v12.5.0 2021-09-14 15:46:09 +01:00
RiotRobot a734fdf9b0 Prepare changelog for v12.5.0 2021-09-14 15:46:08 +01:00
David Baker 6f91b2bc80 Merge pull request #1917 from matrix-org/dbkr/verify_keyshare_tests_125rel
Tests for key sharing security fix
2021-09-14 15:33:05 +01:00
David Baker 32d7272939 Tests for key sharing security fix
Tests for 894c24880d
2021-09-14 15:24:15 +01:00
David Baker c8d85452bf Merge remote-tracking branch 'origin/develop' into dbkr/verify_key_share_tests 2021-09-14 15:16:49 +01:00
RiotRobot 557f800919 Verify target device key on reshare 2021-09-14 15:14:46 +01:00
David Baker 2119e88c78 Tests for key sharing security fix
Tests for 894c24880d
2021-09-14 13:11:18 +01:00
Travis Ralston 24e7a7e6bf Merge pull request #1915 from SimonBrandner/feature/has-been-cancelled
Add `hasBeenCancelled` to `VerificationBase`
2021-09-13 20:54:03 -06:00
Šimon Brandner 4e5847f356 Add hasBeenCancelled to VerificationBase
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-13 18:01:24 +02:00
RiotRobot 72d580edfd Merge branch 'master' into develop 2021-09-13 12:50:02 +01:00
RiotRobot e878b7683b v12.4.1 2021-09-13 12:47:04 +01:00
RiotRobot fac700bf4d Changelog for 12.4.1 2021-09-13 12:43:13 +01:00
RiotRobot 894c24880d Verify target device key on reshare 2021-09-13 12:34:48 +01:00
Germain d742249fd1 Merge pull request #1914 from matrix-org/gsouquet/ts-misc
Types adjustment for TS migration
2021-09-13 10:00:26 +01:00
Germain Souquet 9c100fea48 Types update for UserView TS migration 2021-09-12 10:12:59 +02:00
Germain Souquet 7daab62850 Types fixes for CreateKeyBackupDialog TS migration 2021-09-12 09:21:04 +02:00
Germain Souquet fed0ced89d Type fixes for CreateSecretStorageDialog TS migration 2021-09-12 08:56:31 +02:00
Germain Souquet df9cfaed1d Merge branch 'develop' into gsouquet/ts-last-push 2021-09-11 16:18:25 +01:00
Germain Souquet edd614dd0c fix cyclic dependencies for AutoDiscoveryActionEnum 2021-09-11 16:17:40 +01:00
Germain Souquet 375b228bb2 fix missing types and linting errors 2021-09-11 16:06:29 +01:00
Šimon Brandner 7143ef8a32 Internally remove call types
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-11 08:21:58 +02:00
Šimon Brandner 1bd7de5a18 Use MediaHandler
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-11 08:21:57 +02:00
Šimon Brandner a1f73eee86 Add MediaHandler
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-11 08:21:51 +02:00
Travis Ralston d9f8710758 Add missing tests for MSC3089 implementation
Fixes https://github.com/vector-im/element-web/issues/18461
2021-09-10 14:44:50 -06:00
Travis Ralston 3a9d5439a2 Add tests 2021-09-10 14:18:11 -06:00
Travis Ralston 5d1be6e8be Add file locking to MSC3089 branches
This isn't hooked up to versioning, yet, but will be eventually.
2021-09-10 14:15:32 -06:00
Germain 775e85a2e8 Merge pull request #1906 from matrix-org/gsouquet/threads-relations-18721 2021-09-10 19:22:41 +01:00
Travis Ralston dc8f403c44 Merge pull request #1908 from SimonBrandner/task/dialogs-ts
Add `ISasEvent`
2021-09-10 12:17:41 -06:00
Šimon Brandner cf9fa991d1 Add ISasEvent
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-10 19:18:19 +02:00
Robert Long d26dc49755 Merge pull request #1904 from matrix-org/robertlong/callfeed-dispose
Properly dispose of CallFeeds
2021-09-10 10:07:10 -07:00
Germain be04559a66 Merge pull request #1878 from SimonBrandner/task/i-hate-my-code 2021-09-10 16:16:51 +01:00
Germain Souquet 0722dafe58 Migrate AutoDiscovery to TypeScript 2021-09-10 16:04:55 +01:00
Germain Souquet ab906faa17 Migrate errors to TypeScript 2021-09-10 15:39:36 +01:00
Germain Souquet 9d63a12469 Migrate realtime-callbacks to TypeScript 2021-09-10 15:31:21 +01:00
Germain Souquet f40d0d24c7 Move relations and redactions to thread timeline 2021-09-10 09:21:25 +01:00
Germain Souquet 2cdea36abf Merge branch 'develop' into gsouquet/threads-relations-18721 2021-09-10 08:18:03 +01:00
Travis Ralston 8d4f4c8832 Merge pull request #1901 from matrix-org/travis/rl/perf-metrics
[Release] Exclude opt-in Element performance metrics from encryption
2021-09-09 18:27:25 -06:00
Robert Long 3b4dcbb01d Properly dispose of CallFeeds 2021-09-09 17:15:41 -07:00
Robert Long b52f3b1c57 Add types for the MatrixClient register method 2021-09-09 17:00:45 -07:00
Germain 2cdf5629e5 Merge pull request #1900 from matrix-org/gsouquet/thread-disable-echo 2021-09-09 18:56:02 +01:00
Germain Souquet 97b4f0e369 restore correct local echo 2021-09-09 18:51:12 +01:00
Germain Souquet a508c8d236 Make supportsExperimentalThreads calls more resilient 2021-09-09 18:40:15 +01:00
Travis Ralston 7911e6371a Exclude opt-in Element performance metrics from encryption
This is needed for the system which is meant to be monitoring this metadata. See https://github.com/matrix-org/matrix-react-sdk/pull/6766
2021-09-09 10:41:09 -06:00
Travis Ralston bbace01e9a Merge pull request #1897 from matrix-org/travis/measure-encryption-perf
Exclude opt-in Element performance metrics from encryption
2021-09-09 10:38:18 -06:00
Germain Souquet 87469fea63 Disable pending events for threads
There's a fair few things to update to make them work with Threads
Will get back to it when the plan is to build a more polished UI ready for production
2021-09-09 17:24:55 +01:00
Germain Souquet 48a9db0315 Add helper to check threads support 2021-09-09 17:24:34 +01:00
Germain Souquet 33c9471112 Make timelineSet public readonly 2021-09-09 17:23:45 +01:00
Travis Ralston 04fdcbb672 Exclude opt-in Element performance metrics from encryption
This is needed for the system which is meant to be monitoring this metadata. See https://github.com/matrix-org/matrix-react-sdk/pull/6766
2021-09-08 11:34:01 -06:00
Michael Telatynski 4d2286c5be Merge pull request #1888 from matrix-org/t3chguy/ts1234 2021-09-08 16:59:17 +01:00
Michael Telatynski 620e45e4dc fix typing mishap 2021-09-08 16:56:19 +01:00
Michael Telatynski fb97e74344 iterate PR based on feedback 2021-09-08 16:49:17 +01:00
Michael Telatynski 3bbb7df72d fix tests private field access 2021-09-08 12:57:12 +01:00
Michael Telatynski 58b08821bf Fix bugs identified by TS 2021-09-08 12:49:30 +01:00
Michael Telatynski 19bdfd5b84 Add missing interface 2021-09-08 12:49:22 +01:00
Michael Telatynski b6c857e2a2 Convert crypto/verification/* to Typescript 2021-09-08 12:34:44 +01:00
Michael Telatynski 0991626e85 Merge pull request #1884 from matrix-org/t3chguy/ts/123 2021-09-08 10:03:25 +01:00
Germain 7b10d4fac0 Merge pull request #1886 from janpawellek/janpawellek-fix-removeEventListener
Fix calling removeEventListener if it does not exist
2021-09-08 09:13:27 +01:00
Jan Pawellek 980dacaa87 Add explaination why the check for global.window.removeEventListener is necessary 2021-09-08 09:59:09 +02:00
Travis Ralston 3790e8abb4 Merge pull request #1887 from matrix-org/revert-1872-fix/call-notifs
Revert "Count notifications in encrypted rooms client-side"
2021-09-07 14:15:15 -06:00
Travis Ralston 14ba851daf Revert "Count notifications in encrypted rooms client-side" 2021-09-07 14:15:01 -06:00
Travis Ralston 95d80772d7 Merge pull request #1872 from SimonBrandner/fix/call-notifs
Count notifications in encrypted rooms client-side
2021-09-07 12:31:49 -06:00
RiotRobot 341e936082 v12.5.0-rc.1 2021-09-07 18:20:07 +01:00
RiotRobot e87399c4a1 Prepare changelog for v12.5.0-rc.1 2021-09-07 18:20:06 +01:00
Jan Pawellek a7678ea1e0 Fix calling removeEventListener if it does not exist 2021-09-07 17:12:21 +02:00
Germain 0388bd3b48 Merge pull request #1885 from matrix-org/gsouquet/improve-threads-dedupe 2021-09-07 15:31:56 +01:00
Germain Souquet f27327164b Merge branch 'develop' into gsouquet/improve-threads-dedupe 2021-09-07 15:28:05 +01:00
Germain a0f85075c1 Merge pull request #1882 from matrix-org/gsouquet/fix-replyinthread 2021-09-07 15:26:35 +01:00
Germain Souquet e97d18a03b Improve thread deduplication thread process
Some threads were still holding a reference to the original thread models they were assigned to, leading to some unexpected timeline rendering
2021-09-07 15:05:45 +01:00
Michael Telatynski f07cdb43b1 Fix dehydration Crypto type 2021-09-07 14:39:49 +01:00
Michael Telatynski da8c8f166d Improve jsdoc 2021-09-07 14:36:20 +01:00
Michael Telatynski 0830a30498 Fix tests 2021-09-07 14:36:10 +01:00
Michael Telatynski 68a2762b75 fix forwarding_curve25519_key_chain in OlmDevice::exportInboundGroupSession 2021-09-07 13:29:42 +01:00
Michael Telatynski 666e471369 Convert OlmDevice to Typescript 2021-09-07 13:22:27 +01:00
Germain Souquet d89f4a74f9 Fix replyInThread getter to match event m.in_reply_to structure 2021-09-07 10:20:01 +01:00
Germain Souquet 191535d01a Fix replyInThread getter to match event m.in_reply_to structure 2021-09-07 10:17:24 +01:00
Šimon Brandner 241203f804 Merge remote-tracking branch 'upstream/develop' into fix/call-notifs 2021-09-03 18:28:34 +02:00
Šimon Brandner 26cc7a0f64 Return a TODO
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-03 18:24:37 +02:00
Travis Ralston 324f9e58ea Merge pull request #1873 from SimonBrandner/feature/call-timer/18566
Give `MatrixCall` the capability to emit `LengthChanged` events
2021-09-02 22:12:31 -06:00
Dariusz Niemczyk e71519c638 Remove persisted log levels 2021-09-02 20:17:32 +02:00
Šimon Brandner 9b6cee0cab Merge remote-tracking branch 'upstream/develop' into fix/call-notifs 2021-09-02 16:32:26 +02:00
Germain Souquet 2a5f6a7b3d Move all relations event in thread timeline
If an event is related to another event who's in a thread, we want to make sure to move that event to the thread's timeline
2021-09-02 14:14:30 +01:00
Šimon Brandner cae03817cb Use source id directly
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-02 14:10:43 +02:00
Germain 37736184a0 Merge pull request #1876 from matrix-org/gsouquet/replies-fix-18717
Add reply events rendering hint
2021-09-02 08:37:11 +01:00
Šimon Brandner 9431a52abe Move DesktopCapturerSource out of global.d.ts
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-01 18:22:51 +02:00
Šimon Brandner 13c664ad34 Remove Element-specifc screen-sharing code out of the js-sdk
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-01 18:22:45 +02:00
Šimon Brandner d7640d9e15 Don't use a callback in setScreensharingEnabled()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-09-01 18:08:59 +02:00
Michael Telatynski 3c36be9839 Merge pull request #1877 from matrix-org/t3chguy/fix/18798 2021-09-01 16:08:37 +01:00
Michael Telatynski 9dce7bbb97 Improve typing around join rules 2021-09-01 15:54:04 +01:00
Germain Souquet fa44300abc Add reply events rendering hint
Adds an unstable hint for clients to know whether to render an event in the room timeline or in a thread

There are currently no MSC associated with that unstable "io.element.in_thread" property as I am only experimenting before settling on a specific approach
2021-09-01 12:12:01 +01:00
RiotRobot 2783d162b7 Resetting package fields for development 2021-08-31 13:32:29 +01:00
RiotRobot 39566dbd4f Merge branch 'master' into develop 2021-08-31 13:32:28 +01:00
RiotRobot f8186add92 v12.4.0 2021-08-31 13:29:19 +01:00
RiotRobot c15f22e81d Prepare changelog for v12.4.0 2021-08-31 13:29:18 +01:00
Michael Telatynski 052ac8f95b Merge pull request #1874 from SimonBrandner/task/call-types
Improve TypeScript in `MatrixCall`
2021-08-31 13:06:23 +01:00
Šimon Brandner c37f8ba4c7 Make placeCalls public
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-31 14:00:37 +02:00
Germain 7e2458d5b5 Merge pull request #1828 from matrix-org/gsouquet/threaded-messaging-2349 2021-08-31 09:19:48 +01:00
Germain fe989177bb Merge pull request #1875 from psrpinto/fix/browser-example 2021-08-31 09:15:39 +01:00
Paulo Pinto 412645f025 Use HTTPS
Signed-off-by: Paulo Pinto <paulo.pinto@automattic.com>
2021-08-30 13:32:30 +01:00
Paulo Pinto e527c46f34 Add empty favicon
Signed-off-by: Paulo Pinto <paulo.pinto@automattic.com>
2021-08-30 13:32:30 +01:00
Paulo Pinto da471b57ad Define document encoding
Signed-off-by: Paulo Pinto <paulo.pinto@automattic.com>
2021-08-30 13:32:30 +01:00
Paulo Pinto a4ff97c8df Add required lang attribute
Signed-off-by: Paulo Pinto <paulo.pinto@automattic.com>
2021-08-30 13:32:30 +01:00
Šimon Brandner 411fc47f28 Improve TypeScript in MatrixCall
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-28 09:27:47 +02:00
Šimon Brandner b97e3113d1 Add/improve call event content types
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-28 09:27:34 +02:00
Šimon Brandner f107d63fab Give MatrixCall the capability to emit LengthChanged events
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-27 15:06:55 +02:00
Šimon Brandner a65ac5a82b Count notifications in encrypted rooms client-side
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-27 08:38:59 +02:00
Šimon Brandner c7c1db00e8 Use getWirePushActions()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-26 20:01:40 +02:00
Šimon Brandner 824f93daa3 Add wirePushRules
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-26 20:01:39 +02:00
Šimon Brandner 9fec2f8c58 Simplifie some code
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-26 20:01:39 +02:00
Šimon Brandner 0b46389176 currentCount -> currentHighlightCount
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-26 20:01:38 +02:00
Šimon Brandner db03f16b8e Add a type
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-26 20:01:37 +02:00
David Baker f83fcb1a18 Merge pull request #1870 from matrix-org/dbkr/allchange102
Update changelog generator
2021-08-26 09:42:37 +01:00
David Baker ba0f1f86b9 Merge pull request #1871 from matrix-org/dbkr/verification_request_cancel
Fix verification request cancellation
2021-08-26 09:40:53 +01:00
David Baker 81d349d993 Clarify operator precedence 2021-08-25 20:55:03 +01:00
David Baker d65d2b94b9 Fix verification request cancellation
If you started trying to verify against your other devices and
then cancelled the dialog, the device destination in the cancellation
to-device message would be 'null', so the cancellation wouldn't go
anywhere.

If we haven't yet picked a specific device to talk to, send the
cancellation to all devices (like we did the request).
2021-08-25 18:37:45 +01:00
David Baker acd52bc070 Update changelog generator 2021-08-25 17:19:25 +01:00
David Baker a64339763f Merge pull request #1869 from matrix-org/dbkr/yarn_upgrade_20210824
yarn upgrade
2021-08-25 16:39:45 +01:00
David Baker 4f21b95bbf Merge pull request #1865 from SimonBrandner/feature/voice-activity
Give `CallFeed` the capability to emit on volume changes
2021-08-25 14:40:24 +01:00
David Baker 03e4dcf1ed yarn upgrade 2021-08-25 10:57:37 +01:00
Germain Souquet 3c56d1372d Appease the linter 2021-08-25 09:15:07 +01:00
Germain Souquet 27ecdef09b Merge branch 'develop' into gsouquet/threaded-messaging-2349 2021-08-25 08:53:29 +01:00
RiotRobot 121e8a51c1 v12.4.0-rc.1 2021-08-24 17:04:11 +01:00
RiotRobot 88e3ada701 Prepare changelog for v12.4.0-rc.1 2021-08-24 17:04:10 +01:00
Dariusz Niemczyk 92d822d494 Merge pull request #1847 from matrix-org/jaller94-patch-2
SSSSCryptoCallbacks.getSecretStorageKey: Test if delegateCryptoCallbacks has getSecretStorageKey
2021-08-24 15:24:54 +02:00
Travis Ralston 62c93f54cc Merge pull request #1818 from matrix-org/t3chguy/fix/18089
Add support for /hierarchy API and fall back to /spaces API if needed
2021-08-23 15:51:28 -06:00
Germain Souquet 500c2ed4f2 Remove thread spec 2021-08-23 15:03:10 +01:00
Šimon Brandner 425362edfe Emit Speaking events from CallFeed
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-19 14:48:26 +02:00
Germain Souquet 9c554f9b3d Add experimentalThreadSupport flag 2021-08-19 11:18:46 +01:00
Germain Souquet b7a5f81f95 Add experimental indicators for all threading related features 2021-08-19 11:10:20 +01:00
Šimon Brandner 0746a64c83 Add some docs
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-19 12:03:57 +02:00
Šimon Brandner 22cd475d22 Add missing types
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-19 11:49:26 +02:00
Šimon Brandner 6904f6b36f Emit volume changes
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-19 11:49:19 +02:00
Šimon Brandner e25a649e1a Add webkitAudioContext to window
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-19 11:49:18 +02:00
Germain Souquet 0eb8cbee4d Merge branch 'develop' into gsouquet/threaded-messaging-2349 2021-08-19 10:45:23 +01:00
Dariusz Niemczyk 96e8f65af7 Merge pull request #1864 from matrix-org/palid/dx/typescriptify-password-reset
Add necessary changes for PasswordReset migration
2021-08-19 10:42:59 +02:00
Dariusz Niemczyk 8fb036ba2d Make eslint happy 2021-08-19 09:46:54 +02:00
Dariusz Niemczyk 0a10fa12ef Add necessary changes for PasswordReset migration 2021-08-19 08:59:55 +02:00
David Baker e30dad7913 Merge pull request #1859 from SimonBrandner/task/revert-quick-voip-fix
Revert quick VoIP fix
2021-08-18 18:50:37 +01:00
Travis Ralston c2a0c02898 Merge pull request #1850 from matrix-org/travis/fsdk/filemeta
Add method for including extra fields when uploading to a tree space
2021-08-18 07:29:49 -06:00
Dariusz Niemczyk 7c1e89f86a Merge pull request #1849 from matrix-org/jaller94-patch-3
Make delegateCryptoCallbacks an optional parameter of EncryptionSetupBuilder and SSSSCryptoCallbacks
2021-08-18 14:01:44 +02:00
Germain 29f8a4cba2 Merge pull request #1853 from matrix-org/gsouquet/ts-components-migration
Expose IMyDevice interface
2021-08-18 12:35:57 +01:00
Germain Souquet b884accc99 Dispatch thread events 2021-08-17 17:43:00 +01:00
Germain Souquet d56540320e Merge branch 'develop' into gsouquet/threaded-messaging-2349 2021-08-17 11:14:10 +01:00
Germain Souquet 526fe7e9a4 use timeline set instead of an array of events 2021-08-17 11:10:20 +01:00
Germain Souquet aa696c4c15 Move threading logic to the Sync API 2021-08-17 10:37:34 +01:00
RiotRobot 3216d7e5a7 Merge branch 'master' into develop 2021-08-17 09:27:42 +01:00
RiotRobot b68f649414 v12.3.1 2021-08-17 09:24:43 +01:00
RiotRobot 543f35d9a8 Prepare changelog for v12.3.1 2021-08-17 09:24:42 +01:00
David Baker 03d42a98cd Merge pull request #1862 from matrix-org/dbkr/release_script_handle_no_signing_id_rel_take_2
Fix release script to handle no signing ID
2021-08-17 09:19:30 +01:00
David Baker 4f48554cd2 Merge pull request #1861 from matrix-org/dbkr/release_script_handle_no_signing_id_rel
Fix release script to handle no signing ID
2021-08-17 09:13:59 +01:00
David Baker 85d9c0f403 Merge pull request #1860 from matrix-org/dbkr/1858_rel
Fix multiple VoIP regressions
2021-08-17 09:11:36 +01:00
David Baker 66b4279dd8 Fix release script to handle no signing ID
We now use the file for stuff other than just the signing ID, so
the file may be present but with no signing ID: handle this case.
2021-08-17 09:10:11 +01:00
Šimon Brandner 1ee84fbeca Fix regressions
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-17 09:06:53 +01:00
Šimon Brandner c0e15b206a Revert "Fix regressions"
This reverts commit bd8690de57.
2021-08-17 10:03:46 +02:00
David Baker 290575ba65 Merge pull request #1858 from SimonBrandner/fix/voip
Fix broken voice calls, no ringing and broken call notifications
2021-08-17 08:54:11 +01:00
Šimon Brandner bd8690de57 Fix regressions
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-17 08:09:14 +02:00
Šimon Brandner 0d09f87777 Revert "Revert "Fix glare related regressions""
This reverts commit 4c552cc350.
2021-08-17 07:33:40 +02:00
David Baker 0f45b7e516 Merge pull request #1857 from matrix-org/revert-1851-fix/glare/18538
Revert "Fix glare related regressions"
2021-08-16 23:18:13 +01:00
David Baker 4c552cc350 Revert "Fix glare related regressions" 2021-08-16 23:06:57 +01:00
David Baker 6a87f54292 Merge pull request #1856 from matrix-org/dbkr/release_script_handle_no_signing_id
Fix release script to handle no signing ID
2021-08-16 15:32:09 +01:00
David Baker bfb2c5aad0 Fix release script to handle no signing ID
We now use the file for stuff other than just the signing ID, so
the file may be present but with no signing ID: handle this case.
2021-08-16 14:38:42 +01:00
RiotRobot db898e7bcf Resetting package fields for development 2021-08-16 13:55:37 +01:00
RiotRobot dbebbe3a19 Merge branch 'master' into develop 2021-08-16 13:55:37 +01:00
RiotRobot 10e8aff46a v12.3.0 2021-08-16 13:52:36 +01:00
RiotRobot 81e2ea6a61 Prepare changelog for v12.3.0 2021-08-16 13:52:35 +01:00
David Baker 2594c59250 Merge pull request #1855 from matrix-org/dbkr/update_allchange
Update changelog generator
2021-08-16 13:31:28 +01:00
David Baker b56a4ee6ec Fix dates 2021-08-16 13:24:11 +01:00
David Baker ab524ad3b4 Update changelog generator 2021-08-16 13:18:06 +01:00
David Baker 83bf80feec Merge pull request #1854 from SimonBrandner/release/fix/glare/18538
[Release] Fix glare related regressions
2021-08-16 12:40:08 +01:00
Šimon Brandner b9e5417218 Await handleCallEvent()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-16 13:30:22 +02:00
Šimon Brandner 082169a756 Fix glare
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-16 13:30:21 +02:00
Šimon Brandner cdc87d371c Reformat some code
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-16 13:30:21 +02:00
Šimon Brandner f8ec3bc591 Remove weird method call
If we can get localUsermediaStream gotUserMediaForAnswer() has alredy been called before

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-16 13:30:21 +02:00
Šimon Brandner 85f4651e9b Simplifie some code
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-16 13:30:20 +02:00
Šimon Brandner da53241c48 Remove unnecessary logs
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-16 13:30:20 +02:00
Šimon Brandner fa265296e8 Add a comment
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-16 13:30:19 +02:00
David Baker ccf296ea92 Merge pull request #1851 from SimonBrandner/fix/glare/18538
Fix glare related regressions
2021-08-16 12:27:51 +01:00
Šimon Brandner d65b48204b Await handleCallEvent()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-16 13:14:04 +02:00
J. Ryan Stinnett 977720cee4 Merge pull request #1852 from matrix-org/jryans/comment-to-json
Clarify usages of event's toJSON
2021-08-16 10:30:50 +01:00
Christian Paul 3c7cdb1da8 Update src/crypto/EncryptionSetup.ts 2021-08-16 10:36:21 +02:00
Germain Souquet dd80e744ff Expose IMyDevice interface 2021-08-16 09:06:52 +01:00
J. Ryan Stinnett 0b12e37459 Clarify usages of event's toJSON 2021-08-16 09:04:14 +01:00
Šimon Brandner 51e815f0cb Fix glare
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-15 13:03:18 +02:00
Šimon Brandner f3856d569d Reformat some code
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-15 13:03:08 +02:00
Šimon Brandner e6d1909f0b Remove weird method call
If we can get localUsermediaStream gotUserMediaForAnswer() has alredy been called before

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-15 13:02:39 +02:00
Šimon Brandner 408976a199 Simplifie some code
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-15 12:58:38 +02:00
Šimon Brandner 4da49d926b Remove unnecessary logs
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-15 12:57:27 +02:00
Šimon Brandner 75750ed760 Add a comment
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-15 12:56:48 +02:00
Travis Ralston 2f74779bf1 Merge pull request #1772 from matrix-org/t3chguy/ts/12
Convert SearchResult, InteractiveAuth, PushProcessor and Scheduler to TS
2021-08-13 10:08:54 -06:00
Michael Telatynski 7b038393b1 Merge pull request #1792 from matrix-org/t3chguy/kill-groups
Deprecate groups APIs
2021-08-13 16:39:47 +01:00
Travis Ralston 768c0e7f77 Add method for including extra fields when uploading to a tree space
We ultimately need this to include things like `info` and other metadata in a specific environment.
2021-08-13 09:25:19 -06:00
Christian Paul c6009b1056 Make delegateCryptoCallbacks parameter optional on EncryptionSetupBuilder 2021-08-13 16:04:47 +02:00
Christian Paul 2a280afa88 Make SSSSCryptoCallbacks.delegateCryptoCallbacks optional 2021-08-13 15:58:44 +02:00
Christian Paul be980f4bc9 Update src/crypto/EncryptionSetup.ts 2021-08-13 15:38:22 +02:00
Dariusz Niemczyk 78118e9442 Merge pull request #1846 from matrix-org/jaller94-patch-1
SSSSCryptoCallbacks.getSecretStorageKey return Promise<null> if no key was found
2021-08-13 15:36:53 +02:00
Christian Paul a2c10a7913 SSSSCryptoCallbacks.getSecretStorageKey: Test if delegateCryptoCallbacks has getSecretStorageKey 2021-08-13 15:28:01 +02:00
Christian Paul 7c4f7c9dad SSSSCryptoCallbacks.getSecretStorageKey: Return null, if no key found 2021-08-13 15:23:47 +02:00
Christian Paul 62058e6d48 SSSSCryptoCallbacks.getSecretStorageKey may return Promise<void>
If no result is found, the function may reach the last line without `return` ever being called.
2021-08-13 15:15:20 +02:00
Dariusz Niemczyk a2f514b544 Merge pull request #1834 from matrix-org/palid/fix/signaling-state-errors
Fix temporary call messages being handled without call
2021-08-13 14:17:30 +02:00
RiotRobot 67434bc5d4 v12.3.0-rc.2 2021-08-12 15:11:35 +01:00
RiotRobot 167183775e Prepare changelog for v12.3.0-rc.2 2021-08-12 15:11:35 +01:00
David Baker 4ce6313126 Merge pull request #1840 from matrix-org/dbkr/allchange_npm
Use changelog generator from npm
2021-08-12 15:02:12 +01:00
David Baker 1298ed61b6 Merge pull request #1842 from matrix-org/dbkr/fix_types_build
Fix the types in shipped package
2021-08-12 14:33:01 +01:00
Michael Telatynski 14b424ee94 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/18089 2021-08-12 11:55:04 +01:00
David Baker 2bc1f2fe21 Apparently target matters when not emitting 2021-08-12 11:54:19 +01:00
David Baker b392dbf6f8 Fix the types building
1829 added the spec files to 'src', presumably because they just
were't type checked otherwise, but this chaqnged the implicit rootDir
so the lib directory we shipped has types in src/ and spec/ which
meant lib/index.d.ts was at lib/src/index.d.ts.

Specify the rootDir on the build tsconfig to put it back in the
right place, and also try to make the base tsconfig never emit
to avoid it ever generating .d.ts files in silly places. Move all
the optoins related to emitting to -build since they aren't relevant
in the main one.

Is this a sensible way to do this? It doesn't feel terribly elegant.

Fixes https://github.com/vector-im/element-web/issues/18503
Regressed in https://github.com/matrix-org/matrix-js-sdk/pull/1829
2021-08-12 11:46:49 +01:00
Travis Ralston 696b3ef1ce Merge pull request #1841 from matrix-org/travis/fsdk/fix-delete
Fix conditional on returning file tree spaces
2021-08-11 15:21:20 -06:00
Travis Ralston 4e2ee3b3a8 It helps to fix the other tests too 2021-08-11 15:18:24 -06:00
Travis Ralston 1f9fab9a0c Fix conditional on returning file tree spaces
If a tree space was deleted then it'll still hold a Room object for us, which is not much help if we're effectively trying to get the joined trees.
2021-08-11 15:11:13 -06:00
David Baker 6170796ed8 Use changelog generator from npm 2021-08-11 21:50:13 +01:00
Michael Telatynski d91d1cea34 Apply suggestions from code review
Co-authored-by: Travis Ralston <travisr@matrix.org>
2021-08-11 21:47:51 +01:00
Michael Telatynski 69ba32683c Iterate PR based on feedback 2021-08-11 21:45:51 +01:00
Michael Telatynski fc78e63ad1 Fix RoomHierarchy comments 2021-08-11 20:57:40 +01:00
Michael Telatynski db212a555d Update to latest version of MSC2946 API s/next_token/next_batch/ 2021-08-11 20:49:23 +01:00
RiotRobot 429f32dacb v12.3.0-rc.1 2021-08-11 15:41:40 +01:00
David Baker ad036104e6 Changelog 2021-08-11 15:41:00 +01:00
Dariusz Niemczyk 4607f0177c Fix invalid string quotes 2021-08-11 14:39:00 +02:00
Dariusz Niemczyk d7dbaeba46 Update src/webrtc/callEventHandler.ts
Co-authored-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-11 14:38:20 +02:00
Dariusz Niemczyk 3e94db1837 Update src/webrtc/call.ts
Co-authored-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-11 14:38:13 +02:00
Dariusz Niemczyk 4fd77c2f05 Update src/webrtc/callEventHandler.ts
Co-authored-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-11 14:38:08 +02:00
Dariusz Niemczyk 9fe05e7d40 Fix temporary call messages being handled without call
In cases where a rogue client, or just a simple bug, sends a temporary
call message, like CallNegotiate the messages should just be discarded
if there's no actual p2p connection created.
2021-08-11 14:28:10 +02:00
Matthew Hodgson 946dcd0301 Merge pull request #1838 from matrix-org/matthew/fix-turn
fix TURN by fixing regression preventing multiple ICE candidates from sending.
2021-08-11 11:35:36 +01:00
Matthew Hodgson 97d718e850 tab->space 2021-08-11 11:31:40 +01:00
Matthew Hodgson 913b2d148c clarify blank candidate comment 2021-08-11 11:30:42 +01:00
Matthew Hodgson 916f6a6126 fix TURN by fixing regression preventing multiple candidates from sending
In https://github.com/matrix-org/matrix-js-sdk/commit/eafecd36bc860e79d9aa280a7e60455dbd7c6f67#diff-df33fb6c18a9b5896cd500875824d6c10980d42c92b21cedac6d8f570a8d52b7L1639
the candidateSendTries reset was accidentally lost, meaning that we never send more than one m.call.candidates, breaking trickle ICE.

Also, we leak blank candidates out in m.call.candidates on Safari and FF, so fix that too.
2021-08-11 04:42:32 +01:00
David Baker 7dbee19456 Merge pull request #1837 from matrix-org/dbkr/fix_blank_membership_events
Fix blank profile in join events
2021-08-10 22:13:16 +01:00
David Baker 2375b00b52 more null checking 2021-08-10 22:09:01 +01:00
David Baker 13cdca028f Fix blank profile in join events
This definitely needs explanation, but hopefully the comment is sufficient.

Type: defect
Fixes https://github.com/vector-im/element-web/issues/18321
Regressed in https://github.com/matrix-org/matrix-js-sdk/pull/1765
2021-08-10 21:59:40 +01:00
Germain Souquet e5a67500e4 Stop checking for event type as it is sometimes encrypted 2021-08-10 16:21:59 +02:00
David Baker 264636c879 Merge pull request #1835 from matrix-org/travis/fsdk/get-perm
Add support for reading permissions from tree spaces
2021-08-10 11:08:51 +01:00
Michael Telatynski 9b50455049 Iterate PR, merge types with @types/PushRules 2021-08-10 11:03:05 +01:00
Michael Telatynski a531446396 tidy a bit more 2021-08-10 10:46:13 +01:00
Michael Telatynski f876c35283 Fix some bad conversion artifacts 2021-08-10 10:34:05 +01:00
Michael Telatynski 1bcec53c6b delint 2021-08-10 10:22:40 +01:00
Michael Telatynski 0ad5ef4e9b Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/ts/12 2021-08-10 10:21:13 +01:00
David Baker 94726aa1d5 Merge pull request #1832 from matrix-org/dbkr/preview_changelog
Add changelog preview action
2021-08-09 20:21:22 +01:00
Travis Ralston 8da2f91e22 Add support for reading permissions from tree spaces
This was originally thought to be able to be performed in another way, but unfortunately isn't as easy as originally predicted.
2021-08-09 08:43:46 -06:00
Dariusz Niemczyk b03555a50b Merge pull request #1833 from SimonBrandner/fix/screensharingc-crash
Fix error on turning off screensharing
2021-08-08 21:08:52 +02:00
Šimon Brandner b61312feca Fix screensharing crash
We have to first stop the track and only then delete the feed as stopping the tracks depends on the feed being defined

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-08 11:10:08 +02:00
David Baker ea9459d330 Add changelog preview action 2021-08-06 23:05:00 +01:00
Travis Ralston 3edfe70a7b Merge pull request #1831 from matrix-org/travis/fsdk/expose-get-file
Expose a getFileEvent() function off a MSC3089Branch
2021-08-06 09:15:24 -06:00
Travis Ralston 5482a19979 Expose a getFileEvent() function off a MSC3089Branch 2021-08-06 09:09:03 -06:00
Michael Telatynski e1678aaf11 Merge pull request #1830 from matrix-org/t3chguy/js-docfix
Fix type documentation on MatrixClient::setRoomMutePushRule
2021-08-06 14:48:05 +01:00
Michael Telatynski 2bdb570f3c Fix type documentation on MatrixClient::setRoomMutePushRule 2021-08-06 09:38:50 +01:00
Germain 3723b2da39 Merge pull request #1829 from matrix-org/gsouquet/jest-typescript 2021-08-05 11:00:40 +01:00
Germain Souquet b63af00579 Exclude tests when building browserify package 2021-08-05 11:56:26 +02:00
David Baker 4ed6fbe5fe Merge pull request #1820 from SimonBrandner/fix/hangup-reason/18219
Send `user_hangup` reason if the opponent supports it
2021-08-05 10:41:28 +01:00
Germain Souquet 026260502b Fix linting issues in TypeScript test files 2021-08-05 11:37:27 +02:00
Germain Souquet c45bc91389 Include all TypeScript @types listed in project dependency
Fixes vector-im/element-web#17743

The package @types/jest was not discovered because if types is specified, only packages listed will be included in the global scope.
2021-08-05 11:16:28 +02:00
Germain Souquet 18113900fe Implementation of event threading 2021-08-04 17:28:08 +02:00
David Baker 69358f8372 Merge pull request #1825 from matrix-org/dbkr/codeowners
Add CODEOWNERS
2021-08-04 13:21:37 +01:00
David Baker 8800c6d2e0 Add CODEOWNERS
This should make github auto review request from the element-web team
2021-08-04 13:16:15 +01:00
David Baker b63de6a902 Merge pull request #1823 from matrix-org/dbkr/argh_the_linting
Update eslint plugin & fix silly indenting
2021-08-04 10:03:20 +01:00
David Baker b1cec7d6a5 Merge pull request #1824 from matrix-org/dbkr/allchange
Switch to new changelog generator
2021-08-04 10:03:08 +01:00
David Baker 08821e499c Switch to new changelog generator 2021-08-03 17:51:42 +01:00
David Baker 3ca84cfc49 Update eslint plugin & fix silly indenting
As per matrix-org/eslint-plugin-matrix-org#15
this caused a bunch of silly indenting to creep in, so this fixes it
back to the previous style.
2021-08-03 17:15:25 +01:00
David Baker be8ecce995 Merge pull request #1821 from SimonBrandner/task/cleanup
Clean-up some VoIP code
2021-08-03 13:18:47 +01:00
Šimon Brandner 775c3465bb Rename so that VS Code spell checker shutsup
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-03 10:05:05 +02:00
Šimon Brandner 7484e3c032 Improve wording
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-03 09:48:23 +02:00
Šimon Brandner b93e79274f Move for better organization
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-03 09:45:27 +02:00
Šimon Brandner fee6a96350 Rename for consistency
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-03 09:44:44 +02:00
Šimon Brandner ac2c09108f Improve styling a bit
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-03 09:42:20 +02:00
Šimon Brandner 5b13785115 Make a few things into one liners
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-03 09:40:04 +02:00
Šimon Brandner dd3c53edac Extract weSentTheEvent into a variable
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-03 09:37:41 +02:00
Šimon Brandner 904071ebe6 Extract type into a variable
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-03 09:33:40 +02:00
Šimon Brandner 7e0aeb425d Move things we can into a switch statement
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-03 09:31:06 +02:00
Šimon Brandner f4e985b5cc Fix typos
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-02 21:38:05 +02:00
Šimon Brandner 03134832e9 Standardise screensharing casing
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-02 21:30:33 +02:00
Šimon Brandner dbb0f66094 Remove unused mute state stuff
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-02 21:29:09 +02:00
Šimon Brandner 4631cd1fe3 Make localUsermediaStream into a getter
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-02 21:28:49 +02:00
Šimon Brandner bc07ad5909 Make localScreenSharingStream into a getter
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-02 21:25:55 +02:00
Šimon Brandner 8b0c4a0efb Merge remote-tracking branch 'upstream/develop' into task/cleanup
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-02 21:17:14 +02:00
Šimon Brandner eac77a6695 Remove config as it wasn't used
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-02 21:15:36 +02:00
Šimon Brandner aae8f1c706 screenSharingStream -> localScreenSharingStream
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-02 21:14:59 +02:00
Šimon Brandner 1e281f6838 localAVStream -> localUsermediaStream
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-08-02 21:02:09 +02:00
David Baker aef89e4a26 Merge pull request #1812 from SimonBrandner/feature/muting
Support for MSC3291: Muting in VoIP calls
2021-08-02 17:20:42 +01:00
RiotRobot 8a1bc98d4e Resetting package fields for development 2021-08-02 13:03:59 +01:00
RiotRobot 8b2d5959ec Merge branch 'master' into develop 2021-08-02 13:03:59 +01:00
RiotRobot 4891446dc3 v12.2.0 2021-08-02 13:00:06 +01:00
David Baker cace8f232f Changelog for v12.2.0 2021-08-02 12:45:25 +01:00
Michael Telatynski 88b8b24629 Merge room hierarchy pages 2021-07-30 11:01:08 +01:00
Šimon Brandner b15ba8c5e3 Send hangup reason if the opponent supports it
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-30 08:54:39 +02:00
Michael Telatynski 58c60d85e7 Refactor Space Hierarchy stuff in preparation for pagination 2021-07-29 17:35:17 +01:00
Hubert Chathi 529fe93ab1 Merge pull request #1819 from uhoreg/fix_aes_iv
Only clear bit 63 when we create the IV
2021-07-29 12:07:54 -04:00
Hubert Chathi 600438d02e only clear bit 63 when we create the IV
If we got the IV from somewhere else, we should just use it as-is, rather than
trying to re-clear the bit.
2021-07-29 11:58:08 -04:00
Michael Telatynski 21a357c433 fix typo 2021-07-29 15:00:39 +01:00
Michael Telatynski 9c05077c26 Merge pull request #1816 from matrix-org/t3chguy/hidden-names 2021-07-29 12:55:40 +01:00
Michael Telatynski d49d6936a5 Add support for /children API and fall back to /spaces API if M_UNRECOGNIZED 2021-07-29 12:16:34 +01:00
Šimon Brandner e6696f78f4 Allow recieving unprefixed version of m.call.sdp_stream_metadata_changed
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-29 13:00:48 +02:00
Michael Telatynski f2da3ba6ae Apply hidden char check to rawDisplayName too 2021-07-29 11:00:30 +01:00
Šimon Brandner db5de4c6e4 Merge remote-tracking branch 'upstream/develop' into feature/muting 2021-07-29 09:10:38 +02:00
Šimon Brandner ca042b3647 Fix handling when remoteSDPStreamMetadata is null
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 20:12:21 +02:00
Šimon Brandner 87fd3521ea Fix types
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 20:11:51 +02:00
Šimon Brandner b3c66848e2 Handle missing params
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 19:06:24 +02:00
Šimon Brandner 606aa29381 Use recursivelyAssign
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 18:37:48 +02:00
Šimon Brandner 2d25150a21 Write tests for recursivelyAssign()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 17:57:16 +02:00
Šimon Brandner ca7a457094 Add recursivelyAssign()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 17:56:51 +02:00
Šimon Brandner 23ab3e3ec0 Test mute metadata gets mapped onto feeds
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 15:29:28 +02:00
Šimon Brandner b11a8459d8 Add a method for setting remoteSDPStreamMetadata
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 15:16:24 +02:00
Šimon Brandner 057eb0f2a5 Make mute state props mandetory
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 15:00:21 +02:00
Šimon Brandner 8de6c5aad1 Fix typo which caused all feeds to be muted by default
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 14:59:57 +02:00
Šimon Brandner 6fda2a0c57 Handle purpose changes
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 14:15:00 +02:00
David Baker efeeba265f Merge pull request #1813 from SimonBrandner/fix/call-view/18221
Handle DTMF support
2021-07-28 11:27:09 +01:00
Šimon Brandner 35ca7fdc48 Fix call transfer support
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-28 08:15:50 +02:00
Šimon Brandner 959e3f68a4 Handle DTMF support
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-27 17:51:13 +02:00
RiotRobot f20b3211e6 v12.2.0-rc.1 2021-07-27 15:43:44 +01:00
RiotRobot 9b5302ebf2 Prepare changelog for v12.2.0-rc.1 2021-07-27 15:43:43 +01:00
Šimon Brandner 19f90f56be Merge remote-tracking branch 'upstream/develop' into feature/muting 2021-07-27 16:40:07 +02:00
David Baker a69d9a9f20 Merge pull request #1685 from SimonBrandner/fix/12652/screen-share
Support for screen-sharing using multi-stream VoIP (MSC3077)
2021-07-27 15:36:40 +01:00
Michael Telatynski c5e113d0ca Merge pull request #1808 from matrix-org/t3chguy/ts/13 2021-07-27 15:36:21 +01:00
Šimon Brandner 1df90f8fc7 Basic implementation of MSC3291
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-27 14:31:13 +02:00
Šimon Brandner 8e6040ad6f Give CallFeed a mute state
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-27 14:23:58 +02:00
Šimon Brandner 21f107d734 Add types for MSC3291
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-27 14:19:54 +02:00
Dariusz Niemczyk e6d40f0c17 Merge pull request #1810 from matrix-org/fix/update-eslint-config
Update ESLint config
2021-07-27 09:51:58 +02:00
Dariusz Niemczyk 6b8bb46790 Update ESLint config 2021-07-26 16:37:47 +02:00
Michael Telatynski 05c3236e50 Merge pull request #1807 from bradtgmurray/improve-event-clear-event-state 2021-07-26 12:55:49 +01:00
Brad Murray 4f8c20ddae Fix reattempting decryption check 2021-07-26 07:49:42 -04:00
Brad Murray 8aa283d994 Fix lint in tests 2021-07-25 11:45:09 -04:00
Brad Murray e85b3b6a8d Fix the tests and add a test 2021-07-25 11:41:08 -04:00
Brad Murray 04cd9b55f7 Merge remote-tracking branch 'upstream/develop' into improve-event-clear-event-state 2021-07-25 11:23:11 -04:00
Brad Murray faf237e588 Respect the comment in getClearContent 2021-07-25 11:19:36 -04:00
Šimon Brandner 019abc7ad8 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-07-25 08:13:23 +02:00
Michael Telatynski db4b6fe2af Update to beta better-docs to fix the gendocs issue 2021-07-24 12:10:45 +01:00
Michael Telatynski c340e6f1ba delint and fix tests 2021-07-24 12:01:23 +01:00
Michael Telatynski f227a307c1 Fix some confused signatures/types 2021-07-24 11:40:30 +01:00
Michael Telatynski 011a2acfd2 Convert crypto/store/* to Typescript 2021-07-24 11:35:30 +01:00
Michael Telatynski c2b5b14d26 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/ts/12
 Conflicts:
	src/client.ts
	src/interactive-auth.ts
	src/models/search-result.ts
2021-07-23 23:46:15 +01:00
Brad Murray ad79a64f1c Address review feedback 2021-07-23 16:25:39 -04:00
Brad Murray 91f5df1e48 Fix incorrect indentation 2021-07-23 16:16:39 -04:00
Brad Murray 980d6fc2ae Differentiate between an undefined clearEvent and a falsey property of the clearEvent 2021-07-23 16:13:46 -04:00
Brad Murray 5aa60b8056 Clean up Event.clearEvent handling 2021-07-23 15:56:24 -04:00
Michael Telatynski dc154a00a8 Merge pull request #1806 from matrix-org/t3chguy/eslint1 2021-07-23 16:24:58 +01:00
Michael Telatynski a087acf65d Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/eslint1 2021-07-23 16:08:31 +01:00
Michael Telatynski 05d0755b6a Update matrix-org-eslint-plugin and tighten max warning limit
Whilst it is down, make the most of it!
2021-07-23 16:07:49 +01:00
Michael Telatynski cd86aa7d9e Merge pull request #1745 from matrix-org/t3chguy/fix/17686 2021-07-23 09:09:15 +01:00
Šimon Brandner fcbbcc0398 Always return true for voice calls
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-22 09:24:59 +02:00
Travis Ralston 2bd99b5cb0 Merge pull request #1771 from matrix-org/j94/functional-members
Functional members
2021-07-21 18:25:14 -06:00
David Baker 82d248cb8e Merge pull request #1802 from matrix-org/dbkr/yarn_upgrade_210721
yarn upgrade
2021-07-21 16:58:49 +01:00
David Baker ce2803ebaa yarn upgrade 2021-07-21 15:37:19 +01:00
Germain e85038e888 Merge pull request #1801 from matrix-org/gsouquet/localecompare-perf
Improve calculateRoomName performances by using Intl.Collator
2021-07-21 15:28:22 +01:00
Šimon Brandner ceafb1c5d0 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-07-21 15:58:46 +02:00
Michael Telatynski a87858840b Merge pull request #1798 from matrix-org/t3chguy/eslint 2021-07-21 14:53:12 +01:00
Germain Souquet f708c47385 Improve calculateRoomName performances by using Intl.Collator 2021-07-21 14:18:22 +02:00
Michael Telatynski 9eb918f701 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17686 2021-07-21 10:56:21 +01:00
Christian Paul b4bffd2700 Merge remote-tracking branch 'origin/develop' into j94/functional-members 2021-07-21 11:23:22 +02:00
Šimon Brandner 80d7c88b40 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-07-20 15:47:36 +02:00
Travis Ralston fb315ac75b Merge pull request #1799 from matrix-org/travis/error1
Actually print IndexedDB command error if there is one
2021-07-20 07:38:43 -06:00
Šimon Brandner bee840ce16 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-07-20 13:30:01 +02:00
Christian Paul afa67688f8 Add test for service member who is not a room member 2021-07-20 12:51:08 +02:00
Michael Telatynski 0c06e47782 Update eslint-plugin-matrix-org 2021-07-20 09:15:26 +01:00
Travis Ralston ea646c673b Actually print idb command error if there is one 2021-07-19 23:31:39 -06:00
Michael Telatynski 5cf6684129 Conform to new typescript eslint rules 2021-07-19 22:47:32 +01:00
Christian Paul cb2b9619ab Replace .indexOf() !== -1 with .includes() for readability 2021-07-19 18:22:29 +02:00
Christian Paul b8c2a57829 Add test with multiple service_members 2021-07-19 18:17:39 +02:00
Christian Paul 718a22f574 Merge branch 'j94/functional-members' of github.com:matrix-org/matrix-js-sdk into j94/functional-members 2021-07-19 18:01:05 +02:00
Christian Paul 324cd886a2 Merge remote-tracking branch 'origin/develop' into j94/functional-members 2021-07-19 18:00:59 +02:00
Christian Paul 4cf655785e Add tests 2021-07-19 17:59:11 +02:00
Michael Telatynski f686d70157 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17686 2021-07-19 16:47:39 +01:00
RiotRobot b936b4a2ce Resetting package fields for development 2021-07-19 15:52:47 +01:00
RiotRobot 026839d7e7 Merge branch 'master' into develop 2021-07-19 15:52:46 +01:00
RiotRobot 830a4a741e v12.1.0 2021-07-19 15:49:55 +01:00
RiotRobot a3d9ed19a4 Prepare changelog for v12.1.0 2021-07-19 15:49:54 +01:00
David Baker 8f9f5fd5b0 Merge pull request #1796 from matrix-org/dbkr/upgrade_eslint_plugin_matrix_org
Update eslint-plugin-matrix-org
2021-07-19 15:33:45 +01:00
David Baker 805267f97f Update eslint-plugin-matrix-org 2021-07-19 15:23:20 +01:00
Christian Paul 4a1cd159a4 Update event.ts 2021-07-19 11:53:43 +02:00
Christian Paul 7129c7c7af Update src/models/room.ts
Co-authored-by: Travis Ralston <travisr@matrix.org>
2021-07-19 10:56:15 +02:00
Travis Ralston ecc9fda28b Merge pull request #1778 from matrix-org/travis/notifications-2
Add minimal types for "notification settings" UI
2021-07-16 23:53:13 -06:00
Travis Ralston 7f7c3cbea5 Fix exclusion types 2021-07-16 23:48:41 -06:00
Travis Ralston fc8cd39d1a Optionalize opts 2021-07-16 16:32:49 -06:00
Travis Ralston 589c66bb12 Re-appease linter 2021-07-16 16:30:06 -06:00
Travis Ralston 16fe5a91d5 Consolidate types 2021-07-16 16:27:50 -06:00
Travis Ralston b57b7a65ee Merge branch 'develop' into travis/notifications-2 2021-07-16 16:26:05 -06:00
Šimon Brandner 019e93e76c Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-07-16 21:38:10 +02:00
Michael Telatynski 69415ba2c3 Merge pull request #1791 from matrix-org/t3chguy/querystring 2021-07-16 19:57:22 +01:00
Šimon Brandner 1607ad2111 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-07-16 20:11:39 +02:00
Michael Telatynski f1a0c46a29 Deprecate groups APIs 2021-07-16 17:45:07 +01:00
Michael Telatynski 9b81b805be create utility to decode qs into objects 2021-07-16 13:07:01 +01:00
Michael Telatynski 8379bb818e Switch from url to URL constructor 2021-07-16 13:01:27 +01:00
Michael Telatynski c62f2a0965 Merge pull request #1789 from matrix-org/t3chguy/fix/18008 2021-07-16 11:06:56 +01:00
Michael Telatynski d383e71aca Switch callEventHandler from listening on event to Room.timeline
so that it doesn't end up processing ephemerals, account data, remote echoes, etc etc
2021-07-15 17:44:17 +01:00
David Baker dbe93f598a Merge pull request #1787 from matrix-org/dbkr/new_changelogs
Contributing guidelines for new changelog generation
2021-07-15 14:12:20 +01:00
David Baker 2bc5f7132d Merge remote-tracking branch 'origin/develop' into dbkr/new_changelogs 2021-07-15 14:07:51 +01:00
David Baker b17f339011 Merge pull request #1786 from matrix-org/dbkr/fix_contributing_link
Fix link to CONTRIBUTING.md
2021-07-15 14:06:20 +01:00
Michael Telatynski e2b04d543a Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17686
 Conflicts:
	src/@types/partials.ts
	src/client.ts
2021-07-15 10:03:05 +01:00
Michael Telatynski 0601f98265 Merge pull request #1788 from SimonBrandner/feature/hidden-rrs
Use an unstable prefix for MSC2885: Hidden read receipts
2021-07-15 08:46:54 +01:00
Šimon Brandner bae5650e7a Fix MSC number
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-15 09:41:40 +02:00
Šimon Brandner ff40b4dc4a Fix MSC number
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-15 09:41:35 +02:00
Germain 6098f74d8f Merge pull request #1782 from psrpinto/fix/naming 2021-07-15 08:07:05 +01:00
David Baker 45c153321c Use X-Breaking-Change
Apparently we already have this label in react SDK (for some reason)
so let's use it. Plus, it's clearer that it's for changes rather than
issues that cause things to break.
2021-07-14 22:46:22 +01:00
David Baker 19aedebff1 Update other label mentions 2021-07-14 22:16:00 +01:00
David Baker 5392f616b4 Use existing issues labels / scheme
Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
2021-07-14 22:15:18 +01:00
David Baker c4ccb9d493 Use a real-life example which should hopefully be clearer 2021-07-14 22:00:42 +01:00
David Baker b0726b5008 On brand ourself
Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
2021-07-14 21:57:48 +01:00
David Baker 9c221419ef On-brand
Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
2021-07-14 21:57:29 +01:00
David Baker fddf0c47fb Fix formatting
Co-authored-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-14 21:57:04 +01:00
David Baker b17862e212 s/tag/label/
Also make breaking change notation clearer.
2021-07-14 19:55:58 +01:00
David Baker e133898554 Fix link to CONTRIBUTING.md 2021-07-14 19:48:47 +01:00
David Baker ee5a0e7edf Internal PRs shouldn't have changelog entries by default 2021-07-14 19:47:46 +01:00
David Baker b538c06fcb Update contributing guidelines to for new changelog stuff 2021-07-14 19:45:42 +01:00
David Baker 49cbaa579f Merge pull request #1785 from matrix-org/dbkr/contributing-to-md
Convert CONTRIBUTING to markdown
2021-07-14 19:05:26 +01:00
David Baker 20bd9b525b Convert CONTRIBUTING to markdown
Because everything else is
2021-07-14 19:02:03 +01:00
Michael Telatynski 3038fc523c Merge pull request #1780 from matrix-org/t3chguy/ts/c3
Use webpack worker-loader instead of homegrown hack
2021-07-14 18:25:21 +01:00
Michael Telatynski 52a85e3f82 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/ts/c3 2021-07-14 17:52:42 +01:00
Michael Telatynski 3df1c4b507 Merge pull request #1779 from matrix-org/t3chguy/ts/c2 2021-07-14 17:44:35 +01:00
Travis Ralston 6ada02e245 Merge pull request #1784 from matrix-org/travis/widgets/fix-enc
Expose MatrixEvent's internal clearEvent as a function
2021-07-14 10:33:45 -06:00
Šimon Brandner 9184a0d902 Use MSC2885 unstable prefix
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-14 18:27:38 +02:00
Travis Ralston 4bc4e1b76f remove useless code 2021-07-14 10:22:36 -06:00
Michael Telatynski cb192d3c74 Merge pull request #1777 from matrix-org/t3chguy/ts/c1 2021-07-14 17:19:03 +01:00
Travis Ralston 11dabf9a68 rename 2021-07-14 10:18:12 -06:00
Travis Ralston ea4dfd003f It's not a partial anymore 2021-07-14 10:09:23 -06:00
Travis Ralston 326b14402f Return a properly shaped event 2021-07-14 10:07:43 -06:00
RiotRobot d71ca8a9e0 v12.1.0-rc.1 2021-07-14 16:16:04 +01:00
RiotRobot 45259f0773 Prepare changelog for v12.1.0-rc.1 2021-07-14 16:16:03 +01:00
Travis Ralston 83337eee57 Expose MatrixEvent's internal clearEvent as a function 2021-07-13 23:09:37 -06:00
Michael Telatynski aed861036b Merge pull request #1783 from SimonBrandner/ignore-vscode 2021-07-13 19:19:58 +01:00
Šimon Brandner 3ff05c0947 Ignore vscode
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-13 20:11:58 +02:00
Paulo Pinto 5b2a53a2b4 Undo changes to the CHANGELOG
Signed-off-by: Paulo Pinto <paulo.pinto@automattic.com>
2021-07-13 18:30:57 +01:00
Paulo Pinto 3f6aee1443 Use correct term for identity server
Signed-off-by: Paulo Pinto <paulo.pinto@automattic.com>
2021-07-13 16:27:40 +01:00
Paulo Pinto dba70d4ce2 Standardise casing of integration manager
Signed-off-by: Paulo Pinto <paulo.pinto@automattic.com>
2021-07-13 16:27:40 +01:00
Paulo Pinto ab3dfd48ae Standardise spelling of identity server
Signed-off-by: Paulo Pinto <paulo.pinto@automattic.com>
2021-07-13 16:27:40 +01:00
Paulo Pinto ab59e9134b Standardise casing of identity server
Signed-off-by: Paulo Pinto <paulo.pinto@automattic.com>
2021-07-13 16:27:40 +01:00
Paulo Pinto 56921f0961 Standardise spelling and casing of homeserver
Signed-off-by: Paulo Pinto <paulo.pinto@automattic.com>
2021-07-13 16:27:40 +01:00
Michael Telatynski f9005c33e9 Fix casing of IIndexedDBBackend 2021-07-13 11:29:32 +01:00
Michael Telatynski b7c5b36556 Merge pull request #1781 from matrix-org/t3chguy/dt1
Make `Crypto::inRoomVerificationRequests` public
2021-07-13 11:12:23 +01:00
Michael Telatynski 33deef9f2c Rework devtools and get rid of flippy tgl button 2021-07-13 10:33:33 +01:00
Travis Ralston d5c3cb1b7f More minimal types for pushers & push rules 2021-07-12 21:50:27 -06:00
Michael Telatynski 19d8f2bbac Use webpack worker-loader to load the IndexedDB worker instead of homegrown hack 2021-07-12 18:43:21 +01:00
Michael Telatynski 1aa103d8d2 Fix spurious argument appearing on the interface and TODO 2021-07-12 18:41:26 +01:00
Michael Telatynski 75f630e45d Add missing interface 2021-07-12 18:39:29 +01:00
Michael Telatynski 1defde0f5e Convert IndexedDB store & worker to Typescript 2021-07-12 18:36:47 +01:00
Michael Telatynski e38595e735 remove unused import 2021-07-12 09:14:44 +01:00
Michael Telatynski c0e16ac98c Update some more 2021-07-12 09:10:27 +01:00
Michael Telatynski b33429317c Fix setTimeout/setInterval typing 2021-07-12 09:02:47 +01:00
Michael Telatynski e3f7c848d7 iterate types based on PR review 2021-07-12 08:44:34 +01:00
Travis Ralston 5f2ed4450e -- 2021-07-11 21:04:10 -06:00
Travis Ralston 11e25ea9a2 Appease linter 2021-07-11 21:02:33 -06:00
Travis Ralston fb7184e6fb Reorganize types a bit 2021-07-11 20:56:52 -06:00
Travis Ralston 3c77a8772a Add minimal types for "notification settings" UI
For https://github.com/vector-im/element-web/issues/17782
2021-07-11 20:54:51 -06:00
Travis Ralston 5ce787c6ea Merge pull request #1765 from SimonBrandner/fix/add-event-meta-for-filtered/5692
Call `setEventMetadata()` for filtered `timelineSet`s
2021-07-11 02:51:51 -06:00
Šimon Brandner 67189ad323 Use an enum
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-11 10:47:42 +02:00
Michael Telatynski 3bff5430f6 delint 2021-07-10 15:55:19 +01:00
Michael Telatynski 17efcad6fe Improve and consolidate typing 2021-07-10 15:50:40 +01:00
Christian Paul d7e6cee19c Update room.ts 2021-07-10 03:44:03 +02:00
Christian Paul 116085e927 Fix linter error 2021-07-10 03:08:51 +02:00
Christian Paul 96e6e7b1e8 Update event.ts 2021-07-09 16:44:39 +02:00
Hubert Chathi 061199ec3c Merge pull request #1775 from uhoreg/symmetric_backup
Symmetric backup
2021-07-09 10:31:43 -04:00
David Baker 2d67a35af5 Merge pull request #1776 from matrix-org/dbkr/rescue_lost_megolm_key
Attempt to fix megolm key not being in SSSS
2021-07-09 14:30:55 +01:00
Michael Telatynski 4cb6ad9e70 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17686 2021-07-09 08:53:24 +01:00
Hubert Chathi d639a29501 more type improvements 2021-07-08 18:47:22 -04:00
Hubert Chathi ac02f30dc8 improve types 2021-07-08 18:10:37 -04:00
David Baker 05a20ab56f Fix up keys even if key backup isn't enabled
Mostly because that's what the test tests, so let's keep that
behaviour the same.
2021-07-08 21:30:50 +01:00
David Baker 964aa6d94a Update comment 2021-07-08 21:20:34 +01:00
David Baker 1a17b138ce Attempt to fix megolm key not being in SSSS
If the account has both a key backup and SSSS enabled but the key
backup key isn't stored in SSSS, try to save it there by getting
it from either the cache or the user (if perhaps it's the same as
their security passphrase but lost a passthrough entry).

Fixes https://github.com/vector-im/element-web/issues/17886
2021-07-08 21:12:45 +01:00
Hubert Chathi 2e27e247f3 Merge branch 'develop' into symmetric_backup 2021-07-08 15:16:07 -04:00
Hubert Chathi ef8e4d6d35 fix the tests that got broken by the last commit 2021-07-08 11:19:46 -04:00
Hubert Chathi 8bee38367a use the MSC number 2021-07-08 10:14:53 -04:00
Šimon Brandner 0318c65847 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-07-08 13:22:34 +02:00
David Baker ab5b69bbdb Merge pull request #1774 from matrix-org/dbkr/tsify_secretstorage
Convert SecretStorage to TypeScript
2021-07-08 09:18:23 +01:00
Hubert Chathi 9f4c5af665 fix test 2021-07-07 20:19:23 -04:00
Hubert Chathi e275301e8b lint 2021-07-07 19:50:16 -04:00
Hubert Chathi c8a7820cb3 fix types 2021-07-07 19:46:01 -04:00
Hubert Chathi 17dc1bda19 Merge branch 'develop' into symmetric_backup 2021-07-07 19:19:56 -04:00
David Baker 8d9de37099 type for reason 2021-07-07 21:21:22 +01:00
David Baker 29b403da45 visibility 2021-07-07 21:20:48 +01:00
David Baker 1fd1ba2ada de-underscore 2021-07-07 21:19:51 +01:00
David Baker 5fbce3b928 Consistent values between various account data interfaces 2021-07-07 21:18:14 +01:00
Hubert Chathi 9019bce843 mark session as untrusted when the session data has the untrusted flag 2021-07-07 16:16:07 -04:00
David Baker fd2d106f2a extract SecretStorageKeyObject 2021-07-07 21:14:34 +01:00
David Baker 64a1c83acc param names 2021-07-07 21:11:11 +01:00
David Baker 8cc250e19d param names 2021-07-07 21:10:51 +01:00
David Baker dbd737c87d param names & comment any 2021-07-07 21:10:13 +01:00
David Baker 33398e78cd Fix duplicate inlined / explicit members 2021-07-07 21:05:45 +01:00
David Baker 79c6d0180c remove TODO 2021-07-07 21:04:07 +01:00
David Baker e7a198dc0c Line breaks
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-07 21:00:02 +01:00
David Baker ef08c35c6f Line break
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-07 20:59:32 +01:00
David Baker 41b816538e Line break
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-07 20:58:47 +01:00
David Baker 87d9fe24f5 Shorthand
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-07 20:58:08 +01:00
David Baker d11c95a23f Add type
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-07 20:57:40 +01:00
David Baker 3c9b846116 Shorthand
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-07 20:56:55 +01:00
David Baker c701e2e6d3 Inline members in ctor
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-07 20:55:23 +01:00
David Baker 11b490c77f Line breaks
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-07 20:54:48 +01:00
David Baker 0544ab2617 Add return type
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-07 20:52:07 +01:00
David Baker 5c6d3724b0 Move some interfaces around 2021-07-07 20:21:44 +01:00
David Baker c34d4e777f Convert SecretStorage to TypeScript 2021-07-07 19:37:22 +01:00
Michael Telatynski 588d783a55 Merge pull request #1721 from matrix-org/t3chguy/fix/17494 2021-07-07 18:10:35 +01:00
Michael Telatynski 79f74fdff4 Improve return type for getUrlPreview 2021-07-07 17:54:46 +01:00
Germain 6f5dbb782e Merge pull request #1773 from matrix-org/gsouquet/ci-pure-lockfile
Do not generate a lockfile when running in CI
2021-07-07 15:23:25 +01:00
Germain Souquet f0ae9b0100 Do not generate a lockfile when running in CI 2021-07-07 16:11:14 +02:00
Michael Telatynski fd260a023c Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17494
 Conflicts:
	src/client.js
2021-07-07 14:03:12 +01:00
Michael Telatynski e775bcac3c Convert SearchResult, InteractiveAuth, PushProcessor and Scheduler to Typescript 2021-07-07 11:08:54 +01:00
Šimon Brandner 641c852ee2 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-07-07 10:43:23 +02:00
Christian Paul ab679c2216 Update src/@types/event.ts 2021-07-07 00:39:26 +02:00
Christian Paul 96420a75ab room.ts: Use UNSTABLE_ELEMENT_FUNCTIONAL_USERS 2021-07-06 19:35:56 +02:00
Christian Paul c4263692f0 Update event.ts 2021-07-06 19:34:44 +02:00
Christian Paul 0074b71cbf Fix object-curly-spacing linting errors 2021-07-06 19:10:25 +02:00
Christian Paul d219c2f462 Fix mistakes 2021-07-06 19:07:20 +02:00
Christian Paul 4700dc0375 Remove trailing spaces 2021-07-06 18:59:02 +02:00
Christian Paul a248bbc46b Update src/models/room.ts
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2021-07-06 18:58:07 +02:00
Christian Paul d60affc1b8 room.calculateRoomName: Exclude service members 2021-07-06 18:35:34 +02:00
Christian Paul 8c0f5b4e31 Update event.ts: Add io.element.functional_members 2021-07-06 16:48:57 +02:00
Michael Telatynski a617f6cc55 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17686 2021-07-06 10:12:18 +01:00
Michael Telatynski 9e66026bdc Export MSC3244 type 2021-07-06 10:08:40 +01:00
RiotRobot dc90115a1b Resetting package fields for development 2021-07-05 14:57:15 +01:00
RiotRobot 13b250d291 Merge branch 'master' into develop 2021-07-05 14:57:15 +01:00
RiotRobot 834ab22923 v12.0.1 2021-07-05 14:54:21 +01:00
RiotRobot 17be68a39d Prepare changelog for v12.0.1 2021-07-05 14:54:21 +01:00
Michael Telatynski 48ee03e278 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17686 2021-07-05 13:45:56 +01:00
David Baker 5b86d8b837 Merge pull request #1766 from matrix-org/dbkr/secretrequest_nopromise
Tidy up secret requesting code
2021-07-02 14:50:50 +01:00
David Baker 521fce59ea Tidy up secret requesting code
Use a plain async function rather than a promise, so we don't
have to squelch the lint warning.
2021-07-02 14:46:30 +01:00
Michael Telatynski bbe1ba7328 Improve typing 2021-07-02 14:38:17 +01:00
Michael Telatynski ce7f20219f Merge pull request #1763 from matrix-org/t3chguy/ts/11
Convert Sync and SyncAccumulator to Typescript
2021-07-02 11:03:27 +01:00
Šimon Brandner aad33bd1b7 Formatting
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-02 11:04:54 +02:00
Šimon Brandner 9923c7e577 Merge remote-tracking branch 'upstream/develop' into fix/add-event-meta-for-filtered/5692
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-02 11:03:54 +02:00
Michael Telatynski ae870b1cc1 Merge pull request #1762 from matrix-org/t3chguy/ts/10 2021-07-02 09:51:56 +01:00
Michael Telatynski e3cb199540 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17686 2021-07-02 09:06:15 +01:00
Šimon Brandner d9d19fdc63 delint
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-02 09:51:26 +02:00
Šimon Brandner f506882bf8 Call setEventMetadata() for filtered timelineSets
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-07-02 09:47:47 +02:00
David Baker e782183a49 Merge pull request #1764 from matrix-org/dbkr/ts_semicolons
Comply with new member-delimiter-style rule
2021-07-01 23:54:07 +01:00
David Baker 6699c4d8af Revert e0012d9b81
Accidentally comitted to develop
2021-07-01 23:49:13 +01:00
David Baker f027cbf170 Bump eslint plugin version
Also yarn has decided that other line is going away too
2021-07-01 23:48:56 +01:00
David Baker e0012d9b81 Bump eslint plugin version
Also yarn has decided that other line is going away too
2021-07-01 23:46:13 +01:00
David Baker b2ad957d29 Comply with new member-delimiter-style rule
Just `eslint --fix` with rule from
https://github.com/matrix-org/eslint-plugin-matrix-org/pull/9 in place
2021-07-01 23:28:18 +01:00
Michael Telatynski cab334ed73 Convert Sync and SyncAccumulator to Typescript 2021-07-01 22:47:50 +01:00
Michael Telatynski a99c1e96d6 fix field accesses in tests and default params 2021-07-01 21:05:58 +01:00
Michael Telatynski 261ac8b2d6 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17686 2021-07-01 20:49:20 +01:00
Michael Telatynski 399237e781 use better types 2021-07-01 10:01:49 +01:00
Michael Telatynski 4b29f02f1c Convert EventTimeline, EventTimelineSet and TimelineWindow to TS 2021-07-01 09:53:55 +01:00
Hubert Chathi 558da5528b fix validity checks for backup info 2021-06-30 21:53:11 -04:00
J. Ryan Stinnett e3a00c2cb4 Merge pull request #1754 from aaronraimist/string-pl
Do not honor string power levels
2021-06-30 16:39:44 +01:00
Michael Telatynski 12deaa80a6 Merge pull request #1508 from matrix-org/t3chguy/ts/4 2021-06-29 22:24:18 +01:00
Germain a6507768bc Merge pull request #1760 from matrix-org/gsouquet/filepanel-typescript
Make filterId read/write and optional
2021-06-29 15:15:10 +01:00
RiotRobot 8f19ab066c v12.0.1-rc.1 2021-06-29 14:30:59 +01:00
RiotRobot 17decea576 Prepare changelog for v12.0.1-rc.1 2021-06-29 14:30:58 +01:00
Germain Souquet a2442add5b Make filterId read/write and optional 2021-06-29 13:46:42 +01:00
Michael Telatynski 7dbe95fa92 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17686
 Conflicts:
	src/@types/partials.ts
	src/client.ts
2021-06-29 12:28:38 +01:00
Michael Telatynski 3339a2874d Merge pull request #1759 from matrix-org/t3chguy/fix/17753
Fix broken /messages filtering due to internal field changes in FilterComponent
2021-06-29 11:47:23 +01:00
Michael Telatynski 393047dec5 Fix broken /messages filtering due to internal field changes in FilterComponent 2021-06-29 11:34:59 +01:00
Aaron Raimist 55fb3d4e8e Also prevent sending unless safe integer
Synapse enforces this but I guess it doesn't hurt to also check here

Signed-off-by: Aaron Raimist <aaron@raim.ist>
2021-06-28 22:32:27 -05:00
Aaron Raimist 047180edc9 Merge branch 'develop' into string-pl 2021-06-28 22:17:55 -05:00
Michael Telatynski 1b0a388eb3 delint import 2021-06-25 10:27:55 +01:00
Michael Telatynski d50e559f97 MegolmDecryption::deviceId is not a valid field, sub it out for undefined as it isn't used meaningfully anyhow 2021-06-25 10:21:28 +01:00
Michael Telatynski 835aafcb17 Type the rest of algorithms because tests are unhappy otherwise 2021-06-25 10:18:46 +01:00
Michael Telatynski 48ad9ba3d7 Some more types 2021-06-24 21:48:55 +01:00
Michael Telatynski 3675e95970 fix the upset CI 2021-06-24 21:22:56 +01:00
Michael Telatynski 1ca13f4ce9 delint 2021-06-24 19:28:04 +01:00
Michael Telatynski 40aa6ba96a Even moar typescriptification 2021-06-24 19:19:41 +01:00
Michael Telatynski b4dc1e1555 Moar typescriptification 2021-06-24 17:41:52 +01:00
Michael Telatynski 3effeb7dc4 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/ts/4
 Conflicts:
	package.json
	src/@types/global.d.ts
	src/crypto/key_passphrase.ts
	src/crypto/olmlib.ts
	src/utils.ts
2021-06-24 17:04:45 +01:00
Michael Telatynski 8c11839e4d Merge pull request #1749 from matrix-org/t3chguy/ts/5.1 2021-06-24 15:23:55 +01:00
Michael Telatynski 41817995bd Merge pull request #1739 from matrix-org/t3chguy/fix/15051 2021-06-24 11:20:10 +01:00
Hubert Chathi 8c9799d64c add tests and some fixes 2021-06-23 18:20:00 -04:00
Michael Telatynski 3a5e4ffa91 Fix yet more underscored accesses 2021-06-23 15:54:48 +01:00
Michael Telatynski 02afcc7d4b delint 2021-06-23 15:48:21 +01:00
Michael Telatynski e9007429dd fix more underscored accesses 2021-06-23 15:02:01 +01:00
Michael Telatynski 664d920dd1 Fix issues identified by Typescriptification 2021-06-23 14:51:40 +01:00
Michael Telatynski 5a8299f1a5 Convert more of js-sdk crypto and fix underscored field accesses 2021-06-23 14:47:25 +01:00
Michael Telatynski 6017fead19 Fix imports 2021-06-23 13:33:56 +01:00
Michael Telatynski 69050ed338 Fix import cycle 2021-06-23 13:12:26 +01:00
Michael Telatynski 2664848545 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/ts/5.1 2021-06-23 13:05:34 +01:00
Aaron Raimist b18f0ff738 Do not honor string power levels
Signed-off-by: Aaron Raimist <aaron@raim.ist>
2021-06-22 22:34:34 -05:00
Michael Telatynski 1171c33f7a Merge pull request #1753 from matrix-org/t3chguy/ts/8 2021-06-22 22:04:22 +01:00
Michael Telatynski 6deb2bc7a9 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/15051
 Conflicts:
	src/models/MSC3089TreeSpace.ts
2021-06-22 22:03:44 +01:00
Michael Telatynski 7bda13aba6 Fix types of MatrixEvent sender & target 2021-06-22 20:37:50 +01:00
Travis Ralston 75719c3e0f Merge pull request #1744 from matrix-org/travis/keyshare-file-trees
Add keysharing on invites to File Tree Spaces
2021-06-22 10:48:57 -06:00
Michael Telatynski 2fb033b5ba Merge pull request #1746 from matrix-org/t3chguy/ts/5 2021-06-22 17:41:26 +01:00
Michael Telatynski 9f1db7a707 Merge pull request #1752 from matrix-org/t3chguy/ts/7
Improve type of IContent msgtype
2021-06-22 17:36:53 +01:00
Michael Telatynski a93de99d42 Improve type of IContent msgtype 2021-06-22 17:18:54 +01:00
Michael Telatynski 3d5774ac9e Update early MSC3083 support 2021-06-22 16:07:05 +01:00
Travis Ralston 1d8e0398e9 Merge pull request #1747 from SimonBrandner/feature/pr-template
Add PR template
2021-06-21 14:06:50 -06:00
Michael Telatynski cf76375ce0 Move Promise::allSettled typing from react-sdk to js-sdk 2021-06-21 21:04:29 +01:00
RiotRobot 284e2bb911 Resetting package fields for development 2021-06-21 16:28:40 +01:00
RiotRobot ecb790c179 Merge branch 'master' into develop 2021-06-21 16:28:40 +01:00
Michael Telatynski 66b17aa019 Add type for another-json dep 2021-06-19 19:45:29 +01:00
Michael Telatynski 27a6d1f878 Fix missing await identified by TS conversion 2021-06-19 19:44:47 +01:00
Michael Telatynski fc67dc6497 Convert crypto index to TS 2021-06-19 19:41:45 +01:00
Travis Ralston fedd9a981a Merge pull request #1738 from matrix-org/travis/event-fixes
Add functions to assist in immutability of Event objects
2021-06-18 11:23:08 -06:00
Travis Ralston 47e972a66c Incorporate merge conflict resolution 2021-06-18 11:18:29 -06:00
Travis Ralston 36bd4b5408 Merge remote-tracking branch 'origin/develop' into travis/event-fixes 2021-06-18 11:15:00 -06:00
Travis Ralston f502233ddc try to add more docs 2021-06-18 11:14:43 -06:00
Michael Telatynski 59c1edb623 fix tests 2021-06-18 17:56:51 +01:00
Michael Telatynski 9dd00c7731 Fix tests and tweak some optional types 2021-06-18 17:40:04 +01:00
Šimon Brandner 311edb4e4c Add PR template
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-06-18 17:30:50 +02:00
Michael Telatynski 924b8629d8 Updates around the use of private fields out of class 2021-06-18 16:29:10 +01:00
Michael Telatynski 9e11da1fa5 Fix typing 2021-06-18 16:11:11 +01:00
Michael Telatynski b760fa0ff5 Switch typescript target to es2020 as we use new things like Promise.allSettled and it needs it 2021-06-18 15:30:53 +01:00
Michael Telatynski cbce2f46c3 Fix position of invite_room_state on the event 2021-06-18 15:30:30 +01:00
Michael Telatynski 7aa5a79c86 Convert Room and RoomState to Typescript 2021-06-18 15:29:45 +01:00
Michael Telatynski fc029a9b9e Add MSC3244 types 2021-06-18 11:39:43 +01:00
Michael Telatynski 86ef80ae9a Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into t3chguy/fix/17686 2021-06-18 10:06:05 +01:00
Michael Telatynski e5b475ad89 Create Typescript types for JoinRule, GuestAccess, HistoryVisibility, including join_rule=restricted 2021-06-18 10:05:29 +01:00
Michael Telatynski 2c6858f149 Merge pull request #1742 from matrix-org/t3chguy/ts/5 2021-06-18 09:49:00 +01:00
Travis Ralston c1bff0b2ea delint 2021-06-17 17:38:57 -06:00
Travis Ralston 39892c98f9 Add keysharing on invites to File Tree Spaces 2021-06-17 17:36:49 -06:00
Travis Ralston b15487ec03 Misc lint 2021-06-17 14:24:39 -06:00
Travis Ralston 303119e113 Merge pull request #1743 from matrix-org/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.20 to 4.17.21
2021-06-17 11:08:33 -06:00
dependabot[bot] 09b729beb5 Bump lodash from 4.17.20 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

---
updated-dependencies:
- dependency-name: lodash
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-17 17:07:56 +00:00
Travis Ralston 6b73a77b2d Merge pull request #1740 from matrix-org/travis/invite-retry
Add invite retries to file trees
2021-06-17 11:07:06 -06:00
Michael Telatynski a2449ff6a7 Fix typos 2021-06-17 15:23:40 +01:00
Michael Telatynski b1b7522b80 Fix tests by updating private field names and spies 2021-06-17 15:18:52 +01:00
Michael Telatynski 608b0e7b93 Fix up some more type defs 2021-06-17 14:49:27 +01:00
Michael Telatynski 7c61b9cf7e Fix more type definitions 2021-06-17 14:24:53 +01:00
Michael Telatynski 50a973409a Typescript fixes due to MatrixEvent being TSified 2021-06-17 14:06:03 +01:00
Michael Telatynski bfea882416 Convert MatrixEvent to TS 2021-06-17 14:04:04 +01:00
Michael Telatynski f5e8fe836e Convert Room Member and User to TS 2021-06-17 14:03:52 +01:00
Michael Telatynski c4664a185f Convert Event Context to TS 2021-06-17 14:03:34 +01:00
Michael Telatynski c5f093c42f Merge pull request #1741 from matrix-org/t3chguy/ts/5
Convert IndexedDBStore to TS
2021-06-17 12:15:10 +01:00
Michael Telatynski 64f369b5de Fix IndexedDBStore ts-ification 2021-06-17 12:10:48 +01:00
Michael Telatynski 4b5653c09b Convert IndexedDBStore to TS 2021-06-17 11:37:41 +01:00
Michael Telatynski 837e739ec6 Merge pull request #1736 from matrix-org/t3chguy/ts4 2021-06-17 10:51:11 +01:00
Michael Telatynski b780ee8373 Update src/models/relations.ts
Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
2021-06-17 10:23:09 +01:00
Travis Ralston fe5bfbf76f The linter needed appeasing 2021-06-16 20:27:03 -06:00
Travis Ralston d924617672 Add invite retries to file trees 2021-06-16 20:24:48 -06:00
Michael Telatynski c545c7ca70 fix MSC3089TreeSpace incorrect type assumption 2021-06-16 20:32:46 +01:00
Michael Telatynski 9eb9f3a117 also do same update on the 3pid invite paths 2021-06-16 20:26:50 +01:00
Michael Telatynski 7862fd9679 just update jsdoc/ts return types due to contention. 2021-06-16 20:24:57 +01:00
Travis Ralston 17402e8475 Work around docgen 2021-06-15 14:32:53 -06:00
Travis Ralston aac77440db work around docgen? 2021-06-15 14:29:26 -06:00
Travis Ralston 13c9c4bea5 Add functions to assist in immutability of Event objects 2021-06-15 14:23:27 -06:00
Michael Telatynski eb5908d5d2 fix tests 2021-06-15 10:15:43 +01:00
Michael Telatynski e0c36498e6 delint 2021-06-15 10:06:30 +01:00
Michael Telatynski 913710dd99 Convert filter classes to typescript 2021-06-15 10:02:05 +01:00
Michael Telatynski 2f0d96d030 Convert some utils to typescript 2021-06-15 10:01:51 +01:00
Michael Telatynski 265802acb1 Convert some models to typescript 2021-06-15 10:01:38 +01:00
Michael Telatynski 045f31a0dc Convert some stores to typescript 2021-06-15 10:01:22 +01:00
Šimon Brandner ca37ae6794 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-06-09 19:20:21 +02:00
Hubert Chathi cf21d64aa8 allow backup algorithm to determine whether restored keys should be trusted 2021-06-09 13:02:47 -04:00
Hubert Chathi c04d79d9a0 initial work on symmetric algorithm for key backups 2021-06-08 21:39:37 -04:00
Šimon Brandner c1625e5c27 More styling :(
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-06-04 11:44:31 +02:00
Šimon Brandner 0e05f9fd73 Styling
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-06-04 11:41:35 +02:00
Šimon Brandner ef7595bb06 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-06-04 11:38:13 +02:00
Šimon Brandner 090c2c0891 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-06-04 08:27:46 +02:00
Šimon Brandner d8fd12103a Merge branch 'develop' into fix/12652/screen-share
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-06-02 16:09:11 +02:00
Michael Telatynski bcb4071993 Strip hash from urls being previewed to de-duplicate 2021-06-01 11:17:30 +01:00
Šimon Brandner 5724462c2c Delint
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-28 17:45:52 +02:00
Šimon Brandner 0a0489750c Add missing space
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-28 17:37:55 +02:00
Šimon Brandner f46190509a Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-28 17:37:17 +02:00
Šimon Brandner 75321220fd Simplifie code - don't be an idiot
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-27 18:49:02 +02:00
Šimon Brandner 43198b0425 Disable RTX only for screen-sharing transceivers
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-27 18:41:48 +02:00
Šimon Brandner 4aa2c03ff0 Merge branch 'develop' into fix/12652/screen-share 2021-05-27 18:23:22 +02:00
Šimon Brandner db4c6af472 Fix an odd mistake
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-20 15:54:08 +02:00
Šimon Brandner 87689ca733 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-05-20 15:50:15 +02:00
Šimon Brandner a1a5d85979 Stop tracks only if disabling screen-sharing
The other thing lead to usermedia tracks being stopped when on hold

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-20 15:49:56 +02:00
Šimon Brandner b1459a43ef Also put getRidOfRTXCodecs() before createAnswer()
Just to be sure

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-20 15:11:23 +02:00
Šimon Brandner 8797381f44 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-05-19 21:07:04 +02:00
Šimon Brandner 92e89ffbcf Add getRidOfRTXCodecs() method
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-19 21:06:35 +02:00
Šimon Brandner c2da4376e0 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-05-19 15:21:00 +02:00
Šimon Brandner 78ebf8f117 Merge remote-tracking branch 'upstream/develop' into fix/12652/screen-share 2021-05-19 08:46:54 +02:00
Šimon Brandner 5de189bfa3 Improve logging in pushRemoteFeed()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-15 11:06:04 +02:00
Šimon Brandner 44fc820f99 Make feed pushing methods more verbose
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-13 18:12:48 +02:00
Šimon Brandner 5a8e5a9785 Log local SDPStreamMetadata
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-13 13:43:44 +02:00
Šimon Brandner 960c5da3b2 Merge branch 'develop' into fix/12652/screen-share 2021-05-13 13:39:51 +02:00
Šimon Brandner 00a28e743d Move track.stop() to deleteFeedByStream()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-10 19:18:39 +02:00
Šimon Brandner 2596c25ccc Add missing semicolon
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-10 19:17:07 +02:00
Šimon Brandner 582aafa552 Simpler naming
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-10 19:12:33 +02:00
Šimon Brandner 61e7d4f807 Remove some leftovers from placeScreensharingCall()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-10 18:22:06 +02:00
Šimon Brandner f2c215311f If we can't get constraints don't error
We do this because it could mean the user just hasn't selected a window/screen

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-10 13:01:06 +02:00
Šimon Brandner a13cf0e1e0 Remove placeScreenSharingCall()
This method is quite problematic and doesn't have any benefits

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-10 12:27:21 +02:00
Šimon Brandner ff60bbac9d Remove import that was a mistake
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-10 07:35:25 +02:00
Šimon Brandner 16f569136b Simplifie and avoid repetation
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-09 18:48:08 +02:00
Šimon Brandner 27c172361f Add a log line to pushLocalFeed()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 20:03:47 +02:00
Šimon Brandner 1e0d6b9d4a Jest: should fallback to replaceTrack() if the other side doesn't support SPDStreamMetadata
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 15:50:20 +02:00
Šimon Brandner b67cd94ee2 Jest: should map SDPStreamMetadata to feeds
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 15:39:11 +02:00
Šimon Brandner c6764490c6 Use for loop
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 14:17:10 +02:00
Šimon Brandner 18580624e6 Use opponentSupportsSDPStreamMetadata()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 14:16:43 +02:00
Šimon Brandner 7b333a34b5 Warn level
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 14:16:30 +02:00
Šimon Brandner d0707e183d Make shift-click work again
This is VERY ugly but it works

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 10:02:27 +02:00
Šimon Brandner fa3b246de5 Add addToPeerConnection param
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 10:00:59 +02:00
Šimon Brandner df28d87d25 Remove log line
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 09:29:41 +02:00
Šimon Brandner fc68bb3ae0 Add ()!!!
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 09:29:31 +02:00
Šimon Brandner 82c530da95 Add setScreensharingEnabledWithoutMetadataSupport as a fallback()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 09:08:06 +02:00
Šimon Brandner d250e7387c Merge screenshare track into usermedia stream
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 09:02:01 +02:00
Šimon Brandner cbc74815d8 Use getScreensharingStream()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 08:46:29 +02:00
Šimon Brandner e9b802deb3 Use getScreensharingStream() in setScreensharingEnabled()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 08:03:45 +02:00
Šimon Brandner 377ca0c678 Add getScreensharingStream()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 08:01:56 +02:00
Šimon Brandner 972aef7a9d Merge feed delete methods and add sender arrays
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-08 07:40:46 +02:00
Šimon Brandner a35559be65 Add a method to start screensharing
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 21:33:05 +02:00
Šimon Brandner 0e6b43a769 Hook up methods to delete feeds
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 21:02:56 +02:00
Šimon Brandner 8c8a68d3ae Add methods to delete feeds
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 21:01:56 +02:00
Šimon Brandner 4d74b5cdad Send SDPStreamMetadata in negotiation response
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 20:57:45 +02:00
Šimon Brandner b1ace49f9a Add methods useful for screensharing
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 20:53:19 +02:00
Šimon Brandner 640d13af99 Set remoteSDPStreamMetadata in onNegotiateReceived()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 13:20:36 +02:00
Šimon Brandner 50e0f6353a Move adding tracks into pushLocalFeed()
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 13:15:09 +02:00
Šimon Brandner 30f2263443 Rework pushing of remote feeds for MSC3077
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 11:18:25 +02:00
Šimon Brandner 25eb6de220 Send SDPStreamMetadata in answer
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 11:17:08 +02:00
Šimon Brandner cebdc44689 Set remoteSDPStreamMetadata from answer
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 11:11:31 +02:00
Šimon Brandner 23f5c2e03f Add a separate method to push local feed
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 10:43:36 +02:00
Šimon Brandner 4d4a6ede21 Use somicolons instead
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 10:41:59 +02:00
Šimon Brandner 6a920fe623 Get sdpStreamMetadata from invite
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-07 07:56:27 +02:00
Šimon Brandner 631faa2046 Send SDPStreamMetadata
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-06 17:18:30 +02:00
Šimon Brandner a4e853e1d4 Add types for MSC3077
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-05-06 17:15:04 +02:00
Michael Telatynski 54a03b234a Merge branch 'develop' into t3chguy/ts/4 2020-10-22 16:46:06 +01:00
Michael Telatynski ad2f537887 Merge remote-tracking branch 'origin/t3chguy/ts/4' into t3chguy/ts/4 2020-10-13 22:34:22 +01:00
Michael Telatynski df36f0dab4 fix couple more errors
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-13 22:34:06 +01:00
Hubert Chathi 608d7faa29 fix some typescript errors 2020-10-13 17:25:58 -04:00
Michael Telatynski 7845957d0d fix Olm types import
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-13 19:43:31 +01:00
Michael Telatynski 13c5c4e4f5 Use utf8 instead of utf-8
as per https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-13 19:36:03 +01:00
Michael Telatynski 62114028d5 Convert aes, key_passphrase, olmlib, and recoverykey to TS
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2020-10-13 19:28:16 +01:00
204 changed files with 39228 additions and 26406 deletions
+3
View File
@@ -31,6 +31,9 @@ module.exports = {
"no-async-promise-executor": "off",
// We use a `logger` intermediary module
"no-console": "error",
// restrict EventEmitters to force callers to use TypedEventEmitter
"no-restricted-imports": ["error", "events"],
},
overrides: [{
files: [
+1
View File
@@ -0,0 +1 @@
* @matrix-org/element-web
+7
View File
@@ -0,0 +1,7 @@
<!-- Please read https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.md before submitting your pull request -->
<!-- Include a Sign-Off as described in https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.md#sign-off -->
<!-- To specify text for the changelog entry (otherwise the PR title will be used):
Notes:
-->
+12
View File
@@ -0,0 +1,12 @@
codecov:
allow_coverage_offsets: True
coverage:
status:
project: off
patch: off
comment:
layout: "diff, files"
behavior: default
require_changes: false
require_base: no
require_head: no
+12
View File
@@ -0,0 +1,12 @@
name: Preview Changelog
on:
pull_request_target:
types: [ opened, edited, labeled ]
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- name: Preview Changelog
uses: matrix-org/allchange@main
with:
ghToken: ${{ secrets.GITHUB_TOKEN }}
+31
View File
@@ -0,0 +1,31 @@
name: Test coverage
on:
pull_request: {}
push:
branches: [develop, main, master]
jobs:
test-coverage:
runs-on: ubuntu-latest
env:
# This must be set for fetchdep.sh to get the right branch
PR_NUMBER: ${{github.event.number}}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
# If this is a pull request, make sure we check out its head rather than the
# automatically generated merge commit, so that the coverage diff excludes
# unrelated changes in the base branch
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
- name: Yarn cache
uses: c-hive/gha-yarn-cache@v2
- name: Run tests with coverage
run: "yarn install && yarn build && yarn coverage"
- name: Upload coverage
uses: codecov/codecov-action@v2
with:
fail_ci_if_error: false
verbose: true
+3
View File
@@ -16,3 +16,6 @@ out
# version file and tarball created by `npm pack` / `yarn pack`
/git-revision.txt
/matrix-js-sdk-*.tgz
.vscode
.vscode/
+552
View File
@@ -1,3 +1,549 @@
Changes in [17.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v17.0.0-rc.1) (2022-04-08)
============================================================================================================
Changes in [16.0.2-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v16.0.2-rc.1) (2022-04-05)
============================================================================================================
## 🚨 BREAKING CHANGES
* Remove groups and groups-related APIs ([\#2234](https://github.com/matrix-org/matrix-js-sdk/pull/2234)).
## ✨ Features
* Add Element video room type ([\#2273](https://github.com/matrix-org/matrix-js-sdk/pull/2273)).
* Live location sharing - handle redacted beacons ([\#2269](https://github.com/matrix-org/matrix-js-sdk/pull/2269)).
## 🐛 Bug Fixes
* Fix getSessionsNeedingBackup() limit support ([\#2270](https://github.com/matrix-org/matrix-js-sdk/pull/2270)). Contributed by @adamvy.
* Fix issues with /search and /context API handling for threads ([\#2261](https://github.com/matrix-org/matrix-js-sdk/pull/2261)). Fixes vector-im/element-web#21543.
* Prevent exception 'Unable to set up secret storage' ([\#2260](https://github.com/matrix-org/matrix-js-sdk/pull/2260)).
Changes in [16.0.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v16.0.1) (2022-03-28)
==================================================================================================
## ✨ Features
* emit aggregate room beacon liveness ([\#2241](https://github.com/matrix-org/matrix-js-sdk/pull/2241)).
* Live location sharing - create m.beacon_info events ([\#2238](https://github.com/matrix-org/matrix-js-sdk/pull/2238)).
* Beacon event types from MSC3489 ([\#2230](https://github.com/matrix-org/matrix-js-sdk/pull/2230)).
## 🐛 Bug Fixes
* Fix incorrect usage of unstable variant of `is_falling_back` ([\#2227](https://github.com/matrix-org/matrix-js-sdk/pull/2227)).
Changes in [16.0.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v16.0.1-rc.1) (2022-03-22)
============================================================================================================
Changes in [16.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v16.0.0) (2022-03-15)
==================================================================================================
## 🚨 BREAKING CHANGES
* Improve typing around event emitter handlers ([\#2180](https://github.com/matrix-org/matrix-js-sdk/pull/2180)).
## ✨ Features
* Fix defer not supporting resolving with a Promise<T> ([\#2216](https://github.com/matrix-org/matrix-js-sdk/pull/2216)).
* add LocationAssetType enum ([\#2214](https://github.com/matrix-org/matrix-js-sdk/pull/2214)).
* Support for mid-call devices changes ([\#2154](https://github.com/matrix-org/matrix-js-sdk/pull/2154)). Contributed by @SimonBrandner.
* Add new room state emit RoomStateEvent.Update for lower-frequency hits ([\#2192](https://github.com/matrix-org/matrix-js-sdk/pull/2192)).
## 🐛 Bug Fixes
* Fix wrong event_id being sent for m.in_reply_to of threads ([\#2213](https://github.com/matrix-org/matrix-js-sdk/pull/2213)).
* Fix wrongly asserting that PushRule::conditions is non-null ([\#2217](https://github.com/matrix-org/matrix-js-sdk/pull/2217)).
* Make createThread more resilient when missing rootEvent ([\#2207](https://github.com/matrix-org/matrix-js-sdk/pull/2207)). Fixes vector-im/element-web#21130.
* Fix bug with the /hierarchy API sending invalid requests ([\#2201](https://github.com/matrix-org/matrix-js-sdk/pull/2201)). Fixes vector-im/element-web#21170.
* fix relation sender filter ([\#2196](https://github.com/matrix-org/matrix-js-sdk/pull/2196)). Fixes vector-im/element-web#20877.
* Fix bug with one-way audio after a transfer ([\#2193](https://github.com/matrix-org/matrix-js-sdk/pull/2193)).
Changes in [16.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v16.0.0-rc.1) (2022-03-08)
============================================================================================================
## 🚨 BREAKING CHANGES
* Improve typing around event emitter handlers ([\#2180](https://github.com/matrix-org/matrix-js-sdk/pull/2180)).
## ✨ Features
* Fix defer not supporting resolving with a Promise<T> ([\#2216](https://github.com/matrix-org/matrix-js-sdk/pull/2216)).
* add LocationAssetType enum ([\#2214](https://github.com/matrix-org/matrix-js-sdk/pull/2214)).
* Support for mid-call devices changes ([\#2154](https://github.com/matrix-org/matrix-js-sdk/pull/2154)). Contributed by @SimonBrandner.
* Add new room state emit RoomStateEvent.Update for lower-frequency hits ([\#2192](https://github.com/matrix-org/matrix-js-sdk/pull/2192)).
## 🐛 Bug Fixes
* Fix wrong event_id being sent for m.in_reply_to of threads ([\#2213](https://github.com/matrix-org/matrix-js-sdk/pull/2213)).
* Fix wrongly asserting that PushRule::conditions is non-null ([\#2217](https://github.com/matrix-org/matrix-js-sdk/pull/2217)).
* Make createThread more resilient when missing rootEvent ([\#2207](https://github.com/matrix-org/matrix-js-sdk/pull/2207)). Fixes vector-im/element-web#21130.
* Fix bug with the /hierarchy API sending invalid requests ([\#2201](https://github.com/matrix-org/matrix-js-sdk/pull/2201)). Fixes vector-im/element-web#21170.
* fix relation sender filter ([\#2196](https://github.com/matrix-org/matrix-js-sdk/pull/2196)). Fixes vector-im/element-web#20877.
* Fix bug with one-way audio after a transfer ([\#2193](https://github.com/matrix-org/matrix-js-sdk/pull/2193)).
Changes in [15.6.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.6.0) (2022-02-28)
==================================================================================================
## ✨ Features
* Return send event response from MSC3089Branch.createNewVersion() ([\#2186](https://github.com/matrix-org/matrix-js-sdk/pull/2186)).
* Add functions to support refresh tokens ([\#2178](https://github.com/matrix-org/matrix-js-sdk/pull/2178)).
## 🐛 Bug Fixes
* [Release] Fix bug with the /hierarchy API sending invalid requests ([\#2202](https://github.com/matrix-org/matrix-js-sdk/pull/2202)).
* Fix bug where calls could break if rejected from somewhere else ([\#2189](https://github.com/matrix-org/matrix-js-sdk/pull/2189)).
* Fix camera stuck on after call transfer ([\#2188](https://github.com/matrix-org/matrix-js-sdk/pull/2188)).
* Fix synthetic read receipt handling ([\#2174](https://github.com/matrix-org/matrix-js-sdk/pull/2174)). Fixes vector-im/element-web#21016.
* Revert "Sign backup with cross-signing key when we reset it." ([\#2175](https://github.com/matrix-org/matrix-js-sdk/pull/2175)).
* Sign backup with cross-signing key when we reset it. ([\#2170](https://github.com/matrix-org/matrix-js-sdk/pull/2170)).
* Fix error in uploadContent() when file is empty under Node.js ([\#2155](https://github.com/matrix-org/matrix-js-sdk/pull/2155)).
* Check the backup info against the stored private key when determining trust. ([\#2167](https://github.com/matrix-org/matrix-js-sdk/pull/2167)).
* Back up keys before logging out ([\#2158](https://github.com/matrix-org/matrix-js-sdk/pull/2158)). Fixes vector-im/element-web#13151.
Changes in [15.6.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.6.0-rc.1) (2022-02-22)
============================================================================================================
## ✨ Features
* Return send event response from MSC3089Branch.createNewVersion() ([\#2186](https://github.com/matrix-org/matrix-js-sdk/pull/2186)).
* Add functions to support refresh tokens ([\#2178](https://github.com/matrix-org/matrix-js-sdk/pull/2178)).
## 🐛 Bug Fixes
* Fix bug where calls could break if rejected from somewhere else ([\#2189](https://github.com/matrix-org/matrix-js-sdk/pull/2189)).
* Fix camera stuck on after call transfer ([\#2188](https://github.com/matrix-org/matrix-js-sdk/pull/2188)).
* Fix synthetic read receipt handling ([\#2174](https://github.com/matrix-org/matrix-js-sdk/pull/2174)). Fixes vector-im/element-web#21016.
* Revert "Sign backup with cross-signing key when we reset it." ([\#2175](https://github.com/matrix-org/matrix-js-sdk/pull/2175)).
* Sign backup with cross-signing key when we reset it. ([\#2170](https://github.com/matrix-org/matrix-js-sdk/pull/2170)).
* Fix error in uploadContent() when file is empty under Node.js ([\#2155](https://github.com/matrix-org/matrix-js-sdk/pull/2155)).
* Check the backup info against the stored private key when determining trust. ([\#2167](https://github.com/matrix-org/matrix-js-sdk/pull/2167)).
* Back up keys before logging out ([\#2158](https://github.com/matrix-org/matrix-js-sdk/pull/2158)). Fixes vector-im/element-web#13151.
Changes in [15.5.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.5.2) (2022-02-17)
==================================================================================================
## 🐛 Bug Fixes
* Fix synthetic read receipt handling
Changes in [15.5.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.5.1) (2022-02-14)
==================================================================================================
## 🐛 Bug Fixes
* Fix issue with rooms not getting marked as unread ([\#2163](https://github.com/matrix-org/matrix-js-sdk/pull/2163)). Fixes vector-im/element-web#20971.
* Don't store streams that are only used once ([\#2157](https://github.com/matrix-org/matrix-js-sdk/pull/2157)). Fixes vector-im/element-web#20932. Contributed by @SimonBrandner.
* Fix edge cases around RR calculations ([\#2160](https://github.com/matrix-org/matrix-js-sdk/pull/2160)). Fixes vector-im/element-web#20922.
* Account for encryption in `maySendMessage()` ([\#2159](https://github.com/matrix-org/matrix-js-sdk/pull/2159)). Contributed by @SimonBrandner.
* Send references to thread root to threads, even out of order ([\#2156](https://github.com/matrix-org/matrix-js-sdk/pull/2156)).
* Fix initial sync fail when event fetching unsuccessful ([\#2150](https://github.com/matrix-org/matrix-js-sdk/pull/2150)). Fixes vector-im/element-web#20862.
* Don't decrypt redacted messages ([\#2143](https://github.com/matrix-org/matrix-js-sdk/pull/2143)). Contributed by @SimonBrandner.
Changes in [15.5.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.5.1-rc.1) (2022-02-08)
============================================================================================================
## 🐛 Bug Fixes
* Fix issue with rooms not getting marked as unread ([\#2163](https://github.com/matrix-org/matrix-js-sdk/pull/2163)). Fixes vector-im/element-web#20971.
* Don't store streams that are only used once ([\#2157](https://github.com/matrix-org/matrix-js-sdk/pull/2157)). Fixes vector-im/element-web#20932. Contributed by @SimonBrandner.
* Fix edge cases around RR calculations ([\#2160](https://github.com/matrix-org/matrix-js-sdk/pull/2160)). Fixes vector-im/element-web#20922.
* Account for encryption in `maySendMessage()` ([\#2159](https://github.com/matrix-org/matrix-js-sdk/pull/2159)). Contributed by @SimonBrandner.
* Send references to thread root to threads, even out of order ([\#2156](https://github.com/matrix-org/matrix-js-sdk/pull/2156)).
* Fix initial sync fail when event fetching unsuccessful ([\#2150](https://github.com/matrix-org/matrix-js-sdk/pull/2150)). Fixes vector-im/element-web#20862.
* Don't decrypt redacted messages ([\#2143](https://github.com/matrix-org/matrix-js-sdk/pull/2143)). Contributed by @SimonBrandner.
Changes in [15.5.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.5.0) (2022-01-31)
==================================================================================================
## ✨ Features
* Support m.asset in m.location event content ([\#2109](https://github.com/matrix-org/matrix-js-sdk/pull/2109)).
* Send extensible events structure and support on-demand parsing ([\#2091](https://github.com/matrix-org/matrix-js-sdk/pull/2091)).
* Support cancelling events whilst they are in status = ENCRYPTING ([\#2095](https://github.com/matrix-org/matrix-js-sdk/pull/2095)).
## 🐛 Bug Fixes
* Fix http-api butchering idServer requests ([\#2134](https://github.com/matrix-org/matrix-js-sdk/pull/2134)). Fixes vector-im/element-web#20680.
* Don't remove streams that still have tracks ([\#2104](https://github.com/matrix-org/matrix-js-sdk/pull/2104)).
Changes in [15.5.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.5.0-rc.1) (2022-01-26)
============================================================================================================
## ✨ Features
* Support m.asset in m.location event content ([\#2109](https://github.com/matrix-org/matrix-js-sdk/pull/2109)).
* Send extensible events structure and support on-demand parsing ([\#2091](https://github.com/matrix-org/matrix-js-sdk/pull/2091)).
* Support cancelling events whilst they are in status = ENCRYPTING ([\#2095](https://github.com/matrix-org/matrix-js-sdk/pull/2095)).
## 🐛 Bug Fixes
* Fix http-api butchering idServer requests ([\#2134](https://github.com/matrix-org/matrix-js-sdk/pull/2134)). Fixes vector-im/element-web#20680.
* Don't remove streams that still have tracks ([\#2104](https://github.com/matrix-org/matrix-js-sdk/pull/2104)).
Changes in [15.4.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.4.0) (2022-01-17)
==================================================================================================
## ✨ Features
* Don't consider alt_aliases when calculating room name ([\#2094](https://github.com/matrix-org/matrix-js-sdk/pull/2094)). Fixes vector-im/element-web#13887.
* Load room history if necessary when searching for MSC3089 getFileEvent() ([\#2066](https://github.com/matrix-org/matrix-js-sdk/pull/2066)).
* Add support for MSC3030 `/timestamp_to_event` ([\#2072](https://github.com/matrix-org/matrix-js-sdk/pull/2072)).
## 🐛 Bug Fixes
* Stop encrypting redactions as it isn't spec compliant ([\#2098](https://github.com/matrix-org/matrix-js-sdk/pull/2098)). Fixes vector-im/element-web#20460.
* Fix more function typings relating to key backup ([\#2086](https://github.com/matrix-org/matrix-js-sdk/pull/2086)).
* Fix timeline search in MSC3089 getFileEvent() ([\#2085](https://github.com/matrix-org/matrix-js-sdk/pull/2085)).
* Set a `deviceId` for VoIP example and use `const`/`let` ([\#2090](https://github.com/matrix-org/matrix-js-sdk/pull/2090)). Fixes #2083. Contributed by @SimonBrandner.
* Fix incorrect TS return type for secret storage and key backup functions ([\#2082](https://github.com/matrix-org/matrix-js-sdk/pull/2082)).
Changes in [15.4.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.4.0-rc.1) (2022-01-11)
============================================================================================================
## ✨ Features
* Don't consider alt_aliases when calculating room name ([\#2094](https://github.com/matrix-org/matrix-js-sdk/pull/2094)). Fixes vector-im/element-web#13887.
* Load room history if necessary when searching for MSC3089 getFileEvent() ([\#2066](https://github.com/matrix-org/matrix-js-sdk/pull/2066)).
* Add support for MSC3030 `/timestamp_to_event` ([\#2072](https://github.com/matrix-org/matrix-js-sdk/pull/2072)).
## 🐛 Bug Fixes
* Stop encrypting redactions as it isn't spec compliant ([\#2098](https://github.com/matrix-org/matrix-js-sdk/pull/2098)). Fixes vector-im/element-web#20460.
* Fix more function typings relating to key backup ([\#2086](https://github.com/matrix-org/matrix-js-sdk/pull/2086)).
* Fix timeline search in MSC3089 getFileEvent() ([\#2085](https://github.com/matrix-org/matrix-js-sdk/pull/2085)).
* Set a `deviceId` for VoIP example and use `const`/`let` ([\#2090](https://github.com/matrix-org/matrix-js-sdk/pull/2090)). Fixes #2083. Contributed by @SimonBrandner.
* Fix incorrect TS return type for secret storage and key backup functions ([\#2082](https://github.com/matrix-org/matrix-js-sdk/pull/2082)).
Changes in [15.3.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.3.0) (2021-12-20)
==================================================================================================
## ✨ Features
* Improve fallback key behaviour ([\#2037](https://github.com/matrix-org/matrix-js-sdk/pull/2037)).
* Add new room event filter fields ([\#2051](https://github.com/matrix-org/matrix-js-sdk/pull/2051)).
* Add method to fetch /account/whoami ([\#2046](https://github.com/matrix-org/matrix-js-sdk/pull/2046)).
## 🐛 Bug Fixes
* Filter out falsey opts in /relations API hits ([\#2059](https://github.com/matrix-org/matrix-js-sdk/pull/2059)). Fixes vector-im/element-web#20137.
* Fix paginateEventTimeline resolve to boolean ([\#2054](https://github.com/matrix-org/matrix-js-sdk/pull/2054)).
* Fix incorrect MSC3089 typings and add null checks ([\#2049](https://github.com/matrix-org/matrix-js-sdk/pull/2049)).
Changes in [15.3.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.3.0-rc.1) (2021-12-14)
============================================================================================================
## ✨ Features
* Improve fallback key behaviour ([\#2037](https://github.com/matrix-org/matrix-js-sdk/pull/2037)).
* Add new room event filter fields ([\#2051](https://github.com/matrix-org/matrix-js-sdk/pull/2051)).
* Add method to fetch /account/whoami ([\#2046](https://github.com/matrix-org/matrix-js-sdk/pull/2046)).
## 🐛 Bug Fixes
* Filter out falsey opts in /relations API hits ([\#2059](https://github.com/matrix-org/matrix-js-sdk/pull/2059)). Fixes vector-im/element-web#20137.
* Fix paginateEventTimeline resolve to boolean ([\#2054](https://github.com/matrix-org/matrix-js-sdk/pull/2054)).
* Fix incorrect MSC3089 typings and add null checks ([\#2049](https://github.com/matrix-org/matrix-js-sdk/pull/2049)).
Changes in [15.2.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.2.1) (2021-12-13)
==================================================================================================
* Security release with updated version of Olm to fix https://matrix.org/blog/2021/12/03/pre-disclosure-upcoming-security-release-of-libolm-and-matrix-js-sdk
Changes in [15.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.2.0) (2021-12-06)
==================================================================================================
## ✨ Features
* Remove support for `ArrayBuffer` in unstable MSC3089 `createFile()` and `createNewVersion()` and instead use same content types as handled by `MatrixClient.uploadContent()`. This enables support for Node.js. ([\#2014](https://github.com/matrix-org/matrix-js-sdk/pull/2014)).
* Support for password-based backup on Node.js ([\#2021](https://github.com/matrix-org/matrix-js-sdk/pull/2021)).
* Add optional force parameter when ensuring Olm sessions ([\#2027](https://github.com/matrix-org/matrix-js-sdk/pull/2027)).
## 🐛 Bug Fixes
* Fix call upgrades ([\#2024](https://github.com/matrix-org/matrix-js-sdk/pull/2024)). Contributed by @SimonBrandner.
Changes in [15.2.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.2.0-rc.1) (2021-11-30)
============================================================================================================
## ✨ Features
* Remove support for `ArrayBuffer` in unstable MSC3089 `createFile()` and `createNewVersion()` and instead use same content types as handled by `MatrixClient.uploadContent()`. This enables support for Node.js. ([\#2014](https://github.com/matrix-org/matrix-js-sdk/pull/2014)).
* Support for password-based backup on Node.js ([\#2021](https://github.com/matrix-org/matrix-js-sdk/pull/2021)).
* Add optional force parameter when ensuring Olm sessions ([\#2027](https://github.com/matrix-org/matrix-js-sdk/pull/2027)).
## 🐛 Bug Fixes
* Fix call upgrades ([\#2024](https://github.com/matrix-org/matrix-js-sdk/pull/2024)). Contributed by @SimonBrandner.
Changes in [15.1.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.1.1) (2021-11-22)
==================================================================================================
## 🐛 Bug Fixes
* Fix edit history being broken after editing an unencrypted event with an encrypted event ([\#2013](https://github.com/matrix-org/matrix-js-sdk/pull/2013)). Fixes vector-im/element-web#19651 and vector-im/element-web#19651. Contributed by @aaronraimist.
* Make events pagination responses parse threads ([\#2011](https://github.com/matrix-org/matrix-js-sdk/pull/2011)). Fixes vector-im/element-web#19587 and vector-im/element-web#19587.
Changes in [15.1.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.1.1-rc.1) (2021-11-17)
============================================================================================================
## 🐛 Bug Fixes
* Fix edit history being broken after editing an unencrypted event with an encrypted event ([\#2013](https://github.com/matrix-org/matrix-js-sdk/pull/2013)). Fixes vector-im/element-web#19651 and vector-im/element-web#19651. Contributed by @aaronraimist.
* Make events pagination responses parse threads ([\#2011](https://github.com/matrix-org/matrix-js-sdk/pull/2011)). Fixes vector-im/element-web#19587 and vector-im/element-web#19587.
Changes in [15.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.1.0) (2021-11-08)
==================================================================================================
## 🦖 Deprecations
* Mark old verification methods as deprecated ([\#1994](https://github.com/matrix-org/matrix-js-sdk/pull/1994)).
## ✨ Features
* Try to set a sender on search result events if possible ([\#2004](https://github.com/matrix-org/matrix-js-sdk/pull/2004)).
* Port some changes from group calls branch to develop ([\#2001](https://github.com/matrix-org/matrix-js-sdk/pull/2001)). Contributed by @SimonBrandner.
* Fetch room membership from server rather than relying on stored data ([\#1998](https://github.com/matrix-org/matrix-js-sdk/pull/1998)).
* Add method to fetch the MSC3266 Room Summary of a Room ([\#1988](https://github.com/matrix-org/matrix-js-sdk/pull/1988)).
## 🐛 Bug Fixes
* Don't show `Unable to access microphone` when cancelling screensharing dialog ([\#2005](https://github.com/matrix-org/matrix-js-sdk/pull/2005)). Fixes vector-im/element-web#19533 and vector-im/element-web#19533. Contributed by @SimonBrandner.
* Strip direction override characters from display names ([\#1992](https://github.com/matrix-org/matrix-js-sdk/pull/1992)). Fixes vector-im/element-web#1712 and vector-im/element-web#1712.
Changes in [15.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v15.1.0-rc.1) (2021-11-02)
============================================================================================================
## 🦖 Deprecations
* Mark old verification methods as deprecated ([\#1994](https://github.com/matrix-org/matrix-js-sdk/pull/1994)).
## ✨ Features
* Try to set a sender on search result events if possible ([\#2004](https://github.com/matrix-org/matrix-js-sdk/pull/2004)).
* Port some changes from group calls branch to develop ([\#2001](https://github.com/matrix-org/matrix-js-sdk/pull/2001)). Contributed by @SimonBrandner.
* Fetch room membership from server rather than relying on stored data ([\#1998](https://github.com/matrix-org/matrix-js-sdk/pull/1998)).
* Add method to fetch the MSC3266 Room Summary of a Room ([\#1988](https://github.com/matrix-org/matrix-js-sdk/pull/1988)).
## 🐛 Bug Fixes
* Don't show `Unable to access microphone` when cancelling screensharing dialog ([\#2005](https://github.com/matrix-org/matrix-js-sdk/pull/2005)). Fixes vector-im/element-web#19533 and vector-im/element-web#19533. Contributed by @SimonBrandner.
* Strip direction override characters from display names ([\#1992](https://github.com/matrix-org/matrix-js-sdk/pull/1992)). Fixes vector-im/element-web#1712 and vector-im/element-web#1712.
Changes in [15.0.0](https://github.com/vector-im/element-desktop/releases/tag/v15.0.0) (2021-10-25)
===================================================================================================
## 🚨 BREAKING CHANGES
* Use `ICallFeedOpts` in the `CallFeed` constructor. To construct a new `CallFeed` object you have to pass `ICallFeedOpts` e.g. `const callFeed = new CallFeed({client ([\#1964](https://github.com/matrix-org/matrix-js-sdk/pull/1964)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
## ✨ Features
* Make threads use 'm.thread' relation ([\#1980](https://github.com/matrix-org/matrix-js-sdk/pull/1980)).
* Try to answer a call without video if we can't access the camera ([\#1972](https://github.com/matrix-org/matrix-js-sdk/pull/1972)). Fixes vector-im/element-web#17975 and vector-im/element-web#17975. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Make `opts` in `importRoomKeys()` optional ([\#1974](https://github.com/matrix-org/matrix-js-sdk/pull/1974)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Enable TypeScript declaration maps ([\#1966](https://github.com/matrix-org/matrix-js-sdk/pull/1966)). Contributed by [Alexendoo](https://github.com/Alexendoo).
## 🐛 Bug Fixes
* Fix `requestVerificationDM` with chronological `pendingEventOrdering` ([\#1943](https://github.com/matrix-org/matrix-js-sdk/pull/1943)). Contributed by [freaktechnik](https://github.com/freaktechnik).
Changes in [15.0.0-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v15.0.0-rc.1) (2021-10-19)
=============================================================================================================
## 🚨 BREAKING CHANGES
* Use `ICallFeedOpts` in the `CallFeed` constructor. To construct a new `CallFeed` object you have to pass `ICallFeedOpts` e.g. `const callFeed = new CallFeed({client ([\#1964](https://github.com/matrix-org/matrix-js-sdk/pull/1964)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
## ✨ Features
* Make threads use 'm.thread' relation ([\#1980](https://github.com/matrix-org/matrix-js-sdk/pull/1980)).
* Try to answer a call without video if we can't access the camera ([\#1972](https://github.com/matrix-org/matrix-js-sdk/pull/1972)). Fixes vector-im/element-web#17975 and vector-im/element-web#17975. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Make `opts` in `importRoomKeys()` optional ([\#1974](https://github.com/matrix-org/matrix-js-sdk/pull/1974)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Enable TypeScript declaration maps ([\#1966](https://github.com/matrix-org/matrix-js-sdk/pull/1966)). Contributed by [Alexendoo](https://github.com/Alexendoo).
## 🐛 Bug Fixes
* Fix `requestVerificationDM` with chronological `pendingEventOrdering` ([\#1943](https://github.com/matrix-org/matrix-js-sdk/pull/1943)). Contributed by [freaktechnik](https://github.com/freaktechnik).
Changes in [14.0.1](https://github.com/vector-im/element-desktop/releases/tag/v14.0.1) (2021-10-12)
===================================================================================================
## 🚨 BREAKING CHANGES
* Support for call upgrades. `setLocalVideoMuted()` and `setMicrophoneMuted()` are now `async` and return the new mute state ([\#1827](https://github.com/matrix-org/matrix-js-sdk/pull/1827)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
## ✨ Features
* Implement file versioning for tree spaces ([\#1952](https://github.com/matrix-org/matrix-js-sdk/pull/1952)).
* Allow answering calls without audio/video ([\#1950](https://github.com/matrix-org/matrix-js-sdk/pull/1950)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Add `bound` to `IThreepid` ([\#1941](https://github.com/matrix-org/matrix-js-sdk/pull/1941)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Add `trusted_locally` to `TrustInfo` ([\#1942](https://github.com/matrix-org/matrix-js-sdk/pull/1942)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
## 🐛 Bug Fixes
* Fix incorrect return value type in getJoinedRooms() ([\#1959](https://github.com/matrix-org/matrix-js-sdk/pull/1959)). Contributed by [psrpinto](https://github.com/psrpinto).
* Make sure to set `callLengthInterval` only once ([\#1958](https://github.com/matrix-org/matrix-js-sdk/pull/1958)). Fixes vector-im/element-web#19221 and vector-im/element-web#19221. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Fix event partitioning from non threading ready clients ([\#1948](https://github.com/matrix-org/matrix-js-sdk/pull/1948)).
* Ensure unencrypted fields get exposed by getEffectiveEvent() ([\#1938](https://github.com/matrix-org/matrix-js-sdk/pull/1938)). Fixes vector-im/element-web#19062 and vector-im/element-web#19062.
Changes in [14.0.0-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v14.0.0-rc.1) (2021-10-04)
=============================================================================================================
## 🚨 BREAKING CHANGES
* Support for call upgrades. `setLocalVideoMuted()` and `setMicrophoneMuted()` are now `async` and return the new mute state ([\#1827](https://github.com/matrix-org/matrix-js-sdk/pull/1827)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
## ✨ Features
* Implement file versioning for tree spaces ([\#1952](https://github.com/matrix-org/matrix-js-sdk/pull/1952)).
* Allow answering calls without audio/video ([\#1950](https://github.com/matrix-org/matrix-js-sdk/pull/1950)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Add `bound` to `IThreepid` ([\#1941](https://github.com/matrix-org/matrix-js-sdk/pull/1941)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Add `trusted_locally` to `TrustInfo` ([\#1942](https://github.com/matrix-org/matrix-js-sdk/pull/1942)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
## 🐛 Bug Fixes
* Fix incorrect return value type in getJoinedRooms() ([\#1959](https://github.com/matrix-org/matrix-js-sdk/pull/1959)). Contributed by [psrpinto](https://github.com/psrpinto).
* Make sure to set `callLengthInterval` only once ([\#1958](https://github.com/matrix-org/matrix-js-sdk/pull/1958)). Fixes vector-im/element-web#19221 and vector-im/element-web#19221. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Fix event partitioning from non threading ready clients ([\#1948](https://github.com/matrix-org/matrix-js-sdk/pull/1948)).
* Ensure unencrypted fields get exposed by getEffectiveEvent() ([\#1938](https://github.com/matrix-org/matrix-js-sdk/pull/1938)). Fixes vector-im/element-web#19062 and vector-im/element-web#19062.
Changes in [13.0.0](https://github.com/vector-im/element-desktop/releases/tag/v13.0.0) (2021-09-27)
===================================================================================================
## ✨ Features
* Add `getHistoryVisibility()` and `getGuestAccess()` ([\#1940](https://github.com/matrix-org/matrix-js-sdk/pull/1940)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Add `getBuffer()` to `QRCodeData` ([\#1927](https://github.com/matrix-org/matrix-js-sdk/pull/1927)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Added `createDataChannel()` and `CallEvent.DataChannel` to `MatrixCall` for creating and listening for WebRTC datachannels. ([\#1929](https://github.com/matrix-org/matrix-js-sdk/pull/1929)). Contributed by [robertlong](https://github.com/robertlong).
* Add file locking to MSC3089 branches ([\#1909](https://github.com/matrix-org/matrix-js-sdk/pull/1909)).
* Add `hasBeenCancelled` to `VerificationBase` ([\#1915](https://github.com/matrix-org/matrix-js-sdk/pull/1915)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Add `ISasEvent` ([\#1908](https://github.com/matrix-org/matrix-js-sdk/pull/1908)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Count notifications in encrypted rooms client-side ([\#1872](https://github.com/matrix-org/matrix-js-sdk/pull/1872)). Fixes vector-im/element-web#15393 and vector-im/element-web#15393. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Exclude opt-in Element performance metrics from encryption ([\#1897](https://github.com/matrix-org/matrix-js-sdk/pull/1897)).
## 🐛 Bug Fixes
* Fix race on automatic backup restore ([\#1936](https://github.com/matrix-org/matrix-js-sdk/pull/1936)). Fixes vector-im/element-web#17781 and vector-im/element-web#17781.
Changes in [13.0.0-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v13.0.0-rc.1) (2021-09-21)
=============================================================================================================
## ✨ Features
* Add `getHistoryVisibility()` and `getGuestAccess()` ([\#1940](https://github.com/matrix-org/matrix-js-sdk/pull/1940)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Add `getBuffer()` to `QRCodeData` ([\#1927](https://github.com/matrix-org/matrix-js-sdk/pull/1927)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Added `createDataChannel()` and `CallEvent.DataChannel` to `MatrixCall` for creating and listening for WebRTC datachannels. ([\#1929](https://github.com/matrix-org/matrix-js-sdk/pull/1929)). Contributed by [robertlong](https://github.com/robertlong).
* Add file locking to MSC3089 branches ([\#1909](https://github.com/matrix-org/matrix-js-sdk/pull/1909)).
* Add `hasBeenCancelled` to `VerificationBase` ([\#1915](https://github.com/matrix-org/matrix-js-sdk/pull/1915)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Add `ISasEvent` ([\#1908](https://github.com/matrix-org/matrix-js-sdk/pull/1908)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Count notifications in encrypted rooms client-side ([\#1872](https://github.com/matrix-org/matrix-js-sdk/pull/1872)). Fixes vector-im/element-web#15393 and vector-im/element-web#15393. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Exclude opt-in Element performance metrics from encryption ([\#1897](https://github.com/matrix-org/matrix-js-sdk/pull/1897)).
## 🐛 Bug Fixes
* Fix race on automatic backup restore ([\#1936](https://github.com/matrix-org/matrix-js-sdk/pull/1936)). Fixes vector-im/element-web#17781 and vector-im/element-web#17781.
Changes in [12.5.0](https://github.com/vector-im/element-desktop/releases/tag/v12.5.0) (2021-09-14)
===================================================================================================
## ✨ Features
* [Release] Exclude opt-in Element performance metrics from encryption ([\#1901](https://github.com/matrix-org/matrix-js-sdk/pull/1901)).
* Give `MatrixCall` the capability to emit `LengthChanged` events ([\#1873](https://github.com/matrix-org/matrix-js-sdk/pull/1873)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Improve browser example ([\#1875](https://github.com/matrix-org/matrix-js-sdk/pull/1875)). Contributed by [psrpinto](https://github.com/psrpinto).
* Give `CallFeed` the capability to emit on volume changes ([\#1865](https://github.com/matrix-org/matrix-js-sdk/pull/1865)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
## 🐛 Bug Fixes
* Fix verification request cancellation ([\#1871](https://github.com/matrix-org/matrix-js-sdk/pull/1871)).
Changes in [12.4.1](https://github.com/vector-im/element-desktop/releases/tag/v12.4.1) (2021-09-13)
===================================================================================================
## 🔒 SECURITY FIXES
* Fix a security issue with message key sharing. See https://matrix.org/blog/2021/09/13/vulnerability-disclosure-key-sharing
for details.
Changes in [12.4.0](https://github.com/vector-im/element-desktop/releases/tag/v12.4.0) (2021-08-31)
===================================================================================================
## 🦖 Deprecations
* Deprecate groups APIs. Groups are no longer supported, only Synapse has support. They are being replaced by Spaces which build off of Rooms and are far more flexible. ([\#1792](https://github.com/matrix-org/matrix-js-sdk/pull/1792)).
## ✨ Features
* Add method for including extra fields when uploading to a tree space ([\#1850](https://github.com/matrix-org/matrix-js-sdk/pull/1850)).
## 🐛 Bug Fixes
* Fix broken voice calls, no ringing and broken call notifications ([\#1858](https://github.com/matrix-org/matrix-js-sdk/pull/1858)). Fixes vector-im/element-web#18578 vector-im/element-web#18538 and vector-im/element-web#18578. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Revert "Fix glare related regressions" ([\#1857](https://github.com/matrix-org/matrix-js-sdk/pull/1857)).
* Fix glare related regressions ([\#1851](https://github.com/matrix-org/matrix-js-sdk/pull/1851)). Fixes vector-im/element-web#18538 and vector-im/element-web#18538. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Fix temporary call messages being handled without call ([\#1834](https://github.com/matrix-org/matrix-js-sdk/pull/1834)). Contributed by [Palid](https://github.com/Palid).
* Fix conditional on returning file tree spaces ([\#1841](https://github.com/matrix-org/matrix-js-sdk/pull/1841)).
Changes in [12.3.1](https://github.com/vector-im/element-desktop/releases/tag/v12.3.1) (2021-08-17)
===================================================================================================
## 🐛 Bug Fixes
* Fix multiple VoIP regressions ([\#1860](https://github.com/matrix-org/matrix-js-sdk/pull/1860)).
Changes in [12.3.0](https://github.com/vector-im/element-desktop/releases/tag/v12.3.0) (2021-08-16)
===================================================================================================
## ✨ Features
* Support for MSC3291: Muting in VoIP calls ([\#1812](https://github.com/matrix-org/matrix-js-sdk/pull/1812)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Support for screen-sharing using multi-stream VoIP (MSC3077) ([\#1685](https://github.com/matrix-org/matrix-js-sdk/pull/1685)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Handle DTMF support ([\#1813](https://github.com/matrix-org/matrix-js-sdk/pull/1813)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
## 🐛 Bug Fixes
* [Release] Fix glare related regressions ([\#1854](https://github.com/matrix-org/matrix-js-sdk/pull/1854)). Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Fix the types in shipped package ([\#1842](https://github.com/matrix-org/matrix-js-sdk/pull/1842)). Fixes vector-im/element-web#18503 and vector-im/element-web#18503.
* Fix error on turning off screensharing ([\#1833](https://github.com/matrix-org/matrix-js-sdk/pull/1833)). Fixes vector-im/element-web#18449. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Fix blank profile in join events ([\#1837](https://github.com/matrix-org/matrix-js-sdk/pull/1837)). Fixes vector-im/element-web#18321.
* fix TURN by fixing regression preventing multiple ICE candidates from sending. ([\#1838](https://github.com/matrix-org/matrix-js-sdk/pull/1838)).
* Send `user_hangup` reason if the opponent supports it ([\#1820](https://github.com/matrix-org/matrix-js-sdk/pull/1820)). Fixes vector-im/element-web#18219. Contributed by [SimonBrandner](https://github.com/SimonBrandner).
* Apply hidden char check to rawDisplayName too ([\#1816](https://github.com/matrix-org/matrix-js-sdk/pull/1816)).
* Only clear bit 63 when we create the IV ([\#1819](https://github.com/matrix-org/matrix-js-sdk/pull/1819)).
Changes in [12.2.0](https://github.com/vector-im/element-desktop/releases/tag/v12.2.0) (2021-08-02)
===================================================================================================
## ✨ Features
* Improve calculateRoomName performances by using Intl.Collator
[\#1801](https://github.com/matrix-org/matrix-js-sdk/pull/1801)
* Switch callEventHandler from listening on `event` to `Room.timeline`
[\#1789](https://github.com/matrix-org/matrix-js-sdk/pull/1789)
* Expose MatrixEvent's internal clearEvent as a function
[\#1784](https://github.com/matrix-org/matrix-js-sdk/pull/1784)
## 🐛 Bug Fixes
* Clean up Event.clearEvent handling to fix a bug where malformed events with falsey content wouldn't be considered decrypted
[\#1807](https://github.com/matrix-org/matrix-js-sdk/pull/1807)
* Standardise spelling and casing of homeserver, identity server, and integration manager
[\#1782](https://github.com/matrix-org/matrix-js-sdk/pull/1782)
Changes in [12.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v12.1.0) (2021-07-19)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v12.1.0-rc.1...v12.1.0)
* No changes from rc.1
Changes in [12.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v12.1.0-rc.1) (2021-07-14)
============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v12.0.1...v12.1.0-rc.1)
* Add VS Code to gitignore
[\#1783](https://github.com/matrix-org/matrix-js-sdk/pull/1783)
* Make `Crypto::inRoomVerificationRequests` public
[\#1781](https://github.com/matrix-org/matrix-js-sdk/pull/1781)
* Call `setEventMetadata()` for filtered `timelineSet`s
[\#1765](https://github.com/matrix-org/matrix-js-sdk/pull/1765)
* Symmetric backup
[\#1775](https://github.com/matrix-org/matrix-js-sdk/pull/1775)
* Attempt to fix megolm key not being in SSSS
[\#1776](https://github.com/matrix-org/matrix-js-sdk/pull/1776)
* Convert SecretStorage to TypeScript
[\#1774](https://github.com/matrix-org/matrix-js-sdk/pull/1774)
* Strip hash from urls being previewed to de-duplicate
[\#1721](https://github.com/matrix-org/matrix-js-sdk/pull/1721)
* Do not generate a lockfile when running in CI
[\#1773](https://github.com/matrix-org/matrix-js-sdk/pull/1773)
* Tidy up secret requesting code
[\#1766](https://github.com/matrix-org/matrix-js-sdk/pull/1766)
* Convert Sync and SyncAccumulator to Typescript
[\#1763](https://github.com/matrix-org/matrix-js-sdk/pull/1763)
* Convert EventTimeline, EventTimelineSet and TimelineWindow to TS
[\#1762](https://github.com/matrix-org/matrix-js-sdk/pull/1762)
* Comply with new member-delimiter-style rule
[\#1764](https://github.com/matrix-org/matrix-js-sdk/pull/1764)
* Do not honor string power levels
[\#1754](https://github.com/matrix-org/matrix-js-sdk/pull/1754)
* Typescriptify some crypto stuffs
[\#1508](https://github.com/matrix-org/matrix-js-sdk/pull/1508)
* Make filterId read/write and optional
[\#1760](https://github.com/matrix-org/matrix-js-sdk/pull/1760)
Changes in [12.0.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v12.0.1) (2021-07-05)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v12.0.1-rc.1...v12.0.1)
* No changes from rc.1
Changes in [12.0.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v12.0.1-rc.1) (2021-06-29)
============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v12.0.0...v12.0.1-rc.1)
* Fix broken /messages filtering due to internal field changes in
FilterComponent
[\#1759](https://github.com/matrix-org/matrix-js-sdk/pull/1759)
* Convert crypto index to TS
[\#1749](https://github.com/matrix-org/matrix-js-sdk/pull/1749)
* Fix typescript return types for membership update events
[\#1739](https://github.com/matrix-org/matrix-js-sdk/pull/1739)
* Fix types of MatrixEvent sender & target
[\#1753](https://github.com/matrix-org/matrix-js-sdk/pull/1753)
* Add keysharing on invites to File Tree Spaces
[\#1744](https://github.com/matrix-org/matrix-js-sdk/pull/1744)
* Convert Room and RoomState to Typescript
[\#1746](https://github.com/matrix-org/matrix-js-sdk/pull/1746)
* Improve type of IContent msgtype
[\#1752](https://github.com/matrix-org/matrix-js-sdk/pull/1752)
* Add PR template
[\#1747](https://github.com/matrix-org/matrix-js-sdk/pull/1747)
* Add functions to assist in immutability of Event objects
[\#1738](https://github.com/matrix-org/matrix-js-sdk/pull/1738)
* Convert Event Context to TS
[\#1742](https://github.com/matrix-org/matrix-js-sdk/pull/1742)
* Bump lodash from 4.17.20 to 4.17.21
[\#1743](https://github.com/matrix-org/matrix-js-sdk/pull/1743)
* Add invite retries to file trees
[\#1740](https://github.com/matrix-org/matrix-js-sdk/pull/1740)
* Convert IndexedDBStore to TS
[\#1741](https://github.com/matrix-org/matrix-js-sdk/pull/1741)
* Convert additional files to typescript
[\#1736](https://github.com/matrix-org/matrix-js-sdk/pull/1736)
Changes in [12.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v12.0.0) (2021-06-21)
==================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v12.0.0-rc.1...v12.0.0)
@@ -1460,6 +2006,12 @@ All Changes
* [BREAKING] Refactor the entire build process
[\#1113](https://github.com/matrix-org/matrix-js-sdk/pull/1113)
Changes in [3.42.2-rc.3](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v3.42.2-rc.3) (2022-04-08)
============================================================================================================
## 🐛 Bug Fixes
* Make self membership less prone to races ([\#2277](https://github.com/matrix-org/matrix-js-sdk/pull/2277)). Fixes vector-im/element-web#21661.
Changes in [3.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v3.0.0) (2020-01-13)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v3.0.0-rc.1...v3.0.0)
+245
View File
@@ -0,0 +1,245 @@
Contributing code to matrix-js-sdk
==================================
Everyone is welcome to contribute code to matrix-js-sdk, provided that they are
willing to license their contributions under the same license as the project
itself. We follow a simple 'inbound=outbound' model for contributions: the act
of submitting an 'inbound' contribution means that the contributor agrees to
license the code under the same terms as the project's overall 'outbound'
license - in this case, Apache Software License v2 (see
[LICENSE](LICENSE)).
How to contribute
-----------------
The preferred and easiest way to contribute changes to the project is to fork
it on github, and then create a pull request to ask us to pull your changes
into our repo (https://help.github.com/articles/using-pull-requests/)
We use GitHub's pull request workflow to review the contribution, and either
ask you to make any refinements needed or merge it and make them ourselves.
Things that should go into your PR description:
* A changelog entry in the `Notes` section (see below)
* References to any bugs fixed by the change (in GitHub's `Fixes` notation)
* Describe the why and what is changing in the PR description so it's easy for
onlookers and reviewers to onboard and context switch.
* Include both **before** and **after** screenshots to easily compare and discuss
what's changing.
* Include a step-by-step testing strategy so that a reviewer can check out the
code locally and easily get to the point of testing your change.
* Add comments to the diff for the reviewer that might help them to understand
why the change is necessary or how they might better understand and review it.
Things that should *not* go into your PR description:
* Any information on how the code works or why you chose to do it the way
you did. If this isn't obvious from your code, you haven't written enough
comments.
We rely on information in pull request to populate the information that goes
into the changelogs our users see, both for the JS SDK itself and also for some
projects based on it. This is picked up from both labels on the pull request and
the `Notes:` annotation in the description. By default, the PR title will be
used for the changelog entry, but you can specify more options, as follows.
To add a longer, more detailed description of the change for the changelog:
*Fix llama herding bug*
```
Notes: Fix a bug (https://github.com/matrix-org/notaproject/issues/123) where the 'Herd' button would not herd more than 8 Llamas if the moon was in the waxing gibbous phase
```
For some PRs, it's not useful to have an entry in the user-facing changelog (this is
the default for PRs labelled with `T-Task`):
*Remove outdated comment from `Ungulates.ts`*
```
Notes: none
```
Sometimes, you're fixing a bug in a downstream project, in which case you want
an entry in that project's changelog. You can do that too:
*Fix another herding bug*
```
Notes: Fix a bug where the `herd()` function would only work on Tuesdays
element-web notes: Fix a bug where the 'Herd' button only worked on Tuesdays
```
This example is for Element Web. You can specify:
* matrix-react-sdk
* element-web
* element-desktop
If your PR introduces a breaking change, use the `Notes` section in the same
way, additionally adding the `X-Breaking-Change` label (see below). There's no need
to specify in the notes that it's a breaking change - this will be added
automatically based on the label - but remember to tell the developer how to
migrate:
*Remove legacy class*
```
Notes: Remove legacy `Camelopard` class. `Giraffe` should be used instead.
```
Other metadata can be added using labels.
* `X-Breaking-Change`: A breaking change - adding this label will mean the change causes a *major* version bump.
* `T-Enhancement`: A new feature - adding this label will mean the change causes a *minor* version bump.
* `T-Defect`: A bug fix (in either code or docs).
* `T-Task`: No user-facing changes, eg. code comments, CI fixes, refactors or tests. Won't have a changelog entry unless you specify one.
If you don't have permission to add labels, your PR reviewer(s) can work with you
to add them: ask in the PR description or comments.
We use continuous integration, and all pull requests get automatically tested:
if your change breaks the build, then the PR will show that there are failed
checks, so please check back after a few minutes.
Tests
-----
Your PR should include tests.
For new user facing features in `matrix-react-sdk` or `element-web`, you
must include:
1. Comprehensive unit tests written in Jest. These are located in `/test`.
2. "happy path" end-to-end tests.
These are located in `/test/end-to-end-tests` in `matrix-react-sdk`, and
are run using `element-web`. Ideally, you would also include tests for edge
and error cases.
Unit tests are expected even when the feature is in labs. It's good practice
to write tests alongside the code as it ensures the code is testable from
the start, and gives you a fast feedback loop while you're developing the
functionality. End-to-end tests should be added prior to the feature
leaving labs, but don't have to be present from the start (although it might
be beneficial to have some running early, so you can test things faster).
For bugs in those repos, your change must include at least one unit test or
end-to-end test; which is best depends on what sort of test most concisely
exercises the area.
Changes to `matrix-js-sdk` must be accompanied by unit tests written in Jest.
These are located in `/spec/`.
When writing unit tests, please aim for a high level of test coverage
for new code - 80% or greater. If you cannot achieve that, please document
why it's not possible in your PR.
Tests validate that your change works as intended and also document
concisely what is being changed. Ideally, your new tests fail
prior to your change, and succeed once it has been applied. You may
find this simpler to achieve if you write the tests first.
If you're spiking some code that's experimental and not being used to support
production features, exceptions can be made to requirements for tests.
Note that tests will still be required in order to ship the feature, and it's
strongly encouraged to think about tests early in the process, as adding
tests later will become progressively more difficult.
If you're not sure how to approach writing tests for your change, ask for help
in [#element-dev](https://matrix.to/#/#element-dev:matrix.org).
Code style
----------
The js-sdk aims to target TypeScript/ES6. All new files should be written in
TypeScript and existing files should use ES6 principles where possible.
Members should not be exported as a default export in general - it causes problems
with the architecture of the SDK (index file becomes less clear) and could
introduce naming problems (as default exports get aliased upon import). In
general, avoid using `export default`.
The remaining code-style for matrix-js-sdk is not formally documented, but
contributors are encouraged to read the
[code style document for matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md)
and follow the principles set out there.
Please ensure your changes match the cosmetic style of the existing project,
and ***never*** mix cosmetic and functional changes in the same commit, as it
makes it horribly hard to review otherwise.
Attribution
-----------
Everyone who contributes anything to Matrix is welcome to be listed in the
AUTHORS.rst file for the project in question. Please feel free to include a
change to AUTHORS.rst in your pull request to list yourself and a short
description of the area(s) you've worked on. Also, we sometimes have swag to
give away to contributors - if you feel that Matrix-branded apparel is missing
from your life, please mail us your shipping address to matrix at matrix.org
and we'll try to fix it :)
Sign off
--------
In order to have a concrete record that your contribution is intentional
and you agree to license it under the same terms as the project's license, we've
adopted the same lightweight approach that the Linux Kernel
(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker
(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
projects use: the DCO (Developer Certificate of Origin:
http://developercertificate.org/). This is a simple declaration that you wrote
the contribution or otherwise have the right to contribute it to Matrix:
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
If you agree to this for your contribution, then all that's needed is to
include the line in your commit or pull request comment:
```
Signed-off-by: Your Name <your@email.example.org>
```
We accept contributions under a legally identifiable name, such as your name on
government documentation or common-law names (names claimed by legitimate usage
or repute). Unfortunately, we cannot accept anonymous contributions at this
time.
Git allows you to add this signoff automatically when using the `-s` flag to
`git commit`, which uses the name and email set in your `user.name` and
`user.email` git configs.
If you forgot to sign off your commits before making your pull request and are
on Git 2.17+ you can mass signoff using rebase:
```
git rebase --signoff origin/develop
```
-131
View File
@@ -1,131 +0,0 @@
Contributing code to matrix-js-sdk
==================================
Everyone is welcome to contribute code to matrix-js-sdk, provided that they are
willing to license their contributions under the same license as the project
itself. We follow a simple 'inbound=outbound' model for contributions: the act
of submitting an 'inbound' contribution means that the contributor agrees to
license the code under the same terms as the project's overall 'outbound'
license - in this case, Apache Software License v2 (see `<LICENSE>`_).
How to contribute
~~~~~~~~~~~~~~~~~
The preferred and easiest way to contribute changes to the project is to fork
it on github, and then create a pull request to ask us to pull your changes
into our repo (https://help.github.com/articles/using-pull-requests/)
**The single biggest thing you need to know is: please base your changes on
the develop branch - /not/ master.**
We use the master branch to track the most recent release, so that folks who
blindly clone the repo and automatically check out master get something that
works. Develop is the unstable branch where all the development actually
happens: the workflow is that contributors should fork the develop branch to
make a 'feature' branch for a particular contribution, and then make a pull
request to merge this back into the matrix.org 'official' develop branch. We
use GitHub's pull request workflow to review the contribution, and either ask
you to make any refinements needed or merge it and make them ourselves. The
changes will then land on master when we next do a release.
We use continuous integration, and all pull requests get automatically tested:
if your change breaks the build, then the PR will show that there are failed
checks, so please check back after a few minutes.
Code style
~~~~~~~~~~
The js-sdk aims to target TypeScript/ES6. All new files should be written in
TypeScript and existing files should use ES6 principles where possible.
Members should not be exported as a default export in general - it causes problems
with the architecture of the SDK (index file becomes less clear) and could
introduce naming problems (as default exports get aliased upon import). In
general, avoid using `export default`.
The remaining code-style for matrix-js-sdk is not formally documented, but
contributors are encouraged to read the code style document for matrix-react-sdk
(`<https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md>`_)
and follow the principles set out there.
Please ensure your changes match the cosmetic style of the existing project,
and **never** mix cosmetic and functional changes in the same commit, as it
makes it horribly hard to review otherwise.
Attribution
~~~~~~~~~~~
Everyone who contributes anything to Matrix is welcome to be listed in the
AUTHORS.rst file for the project in question. Please feel free to include a
change to AUTHORS.rst in your pull request to list yourself and a short
description of the area(s) you've worked on. Also, we sometimes have swag to
give away to contributors - if you feel that Matrix-branded apparel is missing
from your life, please mail us your shipping address to matrix at matrix.org
and we'll try to fix it :)
Sign off
~~~~~~~~
In order to have a concrete record that your contribution is intentional
and you agree to license it under the same terms as the project's license, we've
adopted the same lightweight approach that the Linux Kernel
(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker
(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
projects use: the DCO (Developer Certificate of Origin:
http://developercertificate.org/). This is a simple declaration that you wrote
the contribution or otherwise have the right to contribute it to Matrix::
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
If you agree to this for your contribution, then all that's needed is to
include the line in your commit or pull request comment::
Signed-off-by: Your Name <your@email.example.org>
We accept contributions under a legally identifiable name, such as your name on
government documentation or common-law names (names claimed by legitimate usage
or repute). Unfortunately, we cannot accept anonymous contributions at this
time.
Git allows you to add this signoff automatically when using the ``-s`` flag to
``git commit``, which uses the name and email set in your ``user.name`` and
``user.email`` git configs.
If you forgot to sign off your commits before making your pull request and are
on Git 2.17+ you can mass signoff using rebase::
git rebase --signoff origin/develop
+1 -1
View File
@@ -1,6 +1,6 @@
console.log("Loading browser sdk");
var client = matrixcs.createClient("http://matrix.org");
var client = matrixcs.createClient("https://matrix.org");
client.publicRooms(function (err, data) {
if (err) {
console.error("err %s", JSON.stringify(err));
+3 -1
View File
@@ -1,6 +1,8 @@
<html>
<html lang="en">
<head>
<title>Test</title>
<meta charset="utf-8"/>
<link rel="icon" href="data:,">
<script src="lib/matrix.js"></script>
<script src="browserTest.js"></script>
</head>
+10 -9
View File
@@ -1,16 +1,17 @@
console.log("Loading browser sdk");
var BASE_URL = "https://matrix.org";
var TOKEN = "accesstokengoeshere";
var USER_ID = "@username:localhost";
var ROOM_ID = "!room:id";
const BASE_URL = "https://matrix.org";
const TOKEN = "accesstokengoeshere";
const USER_ID = "@username:localhost";
const ROOM_ID = "!room:id";
const DEVICE_ID = "some_device_id";
var client = matrixcs.createClient({
const client = matrixcs.createClient({
baseUrl: BASE_URL,
accessToken: TOKEN,
userId: USER_ID
userId: USER_ID,
deviceId: DEVICE_ID
});
var call;
let call;
function disableButtons(place, answer, hangup) {
document.getElementById("hangup").disabled = hangup;
@@ -19,7 +20,7 @@ function disableButtons(place, answer, hangup) {
}
function addListeners(call) {
var lastError = "";
let lastError = "";
call.on("hangup", function() {
disableButtons(false, true, true);
document.getElementById("result").innerHTML = (
+20 -12
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "12.0.0",
"version": "3.42.2-rc.3",
"description": "Matrix Client-Server SDK for Javascript",
"scripts": {
"prepublishOnly": "yarn build",
@@ -9,13 +9,14 @@
"clean": "rimraf lib dist",
"build": "yarn build:dev && yarn build:compile-browser && yarn build:minify-browser",
"build:dev": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types",
"build:types": "tsc --emitDeclarationOnly",
"build:types": "tsc -p tsconfig-build.json --emitDeclarationOnly",
"build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src",
"build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
"build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig-build.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
"build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js",
"gendoc": "jsdoc -c jsdoc.json -P package.json",
"lint": "yarn lint:types && yarn lint:js",
"lint:js": "eslint --max-warnings 57 src spec",
"lint:js": "eslint --max-warnings 0 src spec",
"lint:js-fix": "eslint --fix src spec",
"lint:types": "tsc --noEmit",
"test": "jest",
"test:watch": "jest --watch",
@@ -55,6 +56,8 @@
"bs58": "^4.0.1",
"content-type": "^1.0.4",
"loglevel": "^1.7.1",
"matrix-events-sdk": "^0.0.1-beta.7",
"p-retry": "^4.5.0",
"qs": "^6.9.6",
"request": "^2.88.2",
"unhomoglyph": "^1.0.6"
@@ -72,20 +75,24 @@
"@babel/preset-env": "^7.12.11",
"@babel/preset-typescript": "^7.12.7",
"@babel/register": "^7.12.10",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
"@types/bs58": "^4.0.1",
"@types/content-type": "^1.1.5",
"@types/jest": "^26.0.20",
"@types/node": "12",
"@types/request": "^2.48.5",
"@typescript-eslint/eslint-plugin": "^4.17.0",
"@typescript-eslint/parser": "^4.17.0",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"allchange": "^1.0.6",
"babel-jest": "^26.6.3",
"babelify": "^10.0.0",
"better-docs": "^2.3.2",
"better-docs": "^2.4.0-beta.9",
"browserify": "^17.0.0",
"docdash": "^1.2.0",
"eslint": "7.18.0",
"eslint": "8.9.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#main",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-matrix-org": "^0.4.0",
"exorcist": "^1.0.1",
"fake-indexeddb": "^3.1.2",
"jest": "^26.6.3",
@@ -95,7 +102,7 @@
"rimraf": "^3.0.2",
"terser": "^5.5.1",
"tsify": "^5.0.2",
"typescript": "^4.1.3"
"typescript": "^4.5.3"
},
"jest": {
"testEnvironment": "node",
@@ -106,7 +113,8 @@
"<rootDir>/src/**/*.{js,ts}"
],
"coverageReporters": [
"text"
"text",
"json"
]
},
"typings": "./lib/index.d.ts"
+13 -17
View File
@@ -100,12 +100,7 @@ fi
# global cache here to ensure we get the right thing.
yarn cache clean
# Ensure all dependencies are updated
yarn install --ignore-scripts
if [ -z "$skip_changelog" ]; then
# update_changelog doesn't have a --version flag
update_changelog -h > /dev/null || (echo "github-changelog-generator is required: please install it"; exit)
fi
yarn install --ignore-scripts --pure-lockfile
# Login and publish continues to use `npm`, as it seems to have more clearly
# defined options and semantics than `yarn` for writing to the registry.
@@ -133,14 +128,6 @@ if [ $prerelease -eq 1 ]; then
echo Making a PRE-RELEASE
fi
if [ -z "$skip_changelog" ]; then
if ! command -v update_changelog >/dev/null 2>&1; then
echo "release.sh requires github-changelog-generator. Try:" >&2
echo " pip install git+https://github.com/matrix-org/github-changelog-generator.git" >&2
exit 1
fi
fi
# we might already be on the release branch, in which case, yay
# If we're on any branch starting with 'release', we don't create
# a separate release branch (this allows us to use the same
@@ -156,7 +143,7 @@ fi
if [ -z "$skip_changelog" ]; then
echo "Generating changelog"
update_changelog -f "$changelog_file" "$release"
yarn run allchange "$release"
read -p "Edit $changelog_file manually, or press enter to continue " REPLY
if [ -n "$(git ls-files --modified $changelog_file)" ]; then
@@ -204,7 +191,10 @@ git commit package.json $pkglock -m "$tag"
# figure out if we should be signing this release
signing_id=
if [ -f release_config.yaml ]; then
signing_id=`cat release_config.yaml | python -c "import yaml; import sys; print yaml.load(sys.stdin)['signing_id']"`
result=`cat release_config.yaml | python -c "import yaml; import sys; print yaml.load(sys.stdin)['signing_id']" 2> /dev/null || true`
if [ "$?" -eq 0 ]; then
signing_id=$result
fi
fi
@@ -225,7 +215,7 @@ if [ $dodist -eq 0 ]; then
pushd "$builddir"
git clone "$projdir" .
git checkout "$rel_branch"
yarn install
yarn install --pure-lockfile
# We haven't tagged yet, so tell the dist script what version
# it's building
DIST_VERSION="$tag" yarn dist
@@ -265,6 +255,12 @@ if [ -n "$signing_id" ]; then
# the easiest way to check the validity of the tarball from git is to unzip
# it and compare it with our own idea of what the tar should look like.
# This uses git archive which seems to be what github uses. Specifically,
# the header fields are set in the same way: same file mode, uid & gid
# both zero and mtime set to the timestamp of the commit that the tag
# references. Also note that this puts the commit into the tar headers
# and can be extracted with gunzip -c foo.tar.gz | git get-tar-commit-id
# the name of the sig file we want to create
source_sigfile="${tag}-src.tar.gz.asc"
+3 -1
View File
@@ -20,10 +20,11 @@ limitations under the License.
import './olm-loader';
import MockHttpBackend from 'matrix-mock-request';
import { LocalStorageCryptoStore } from '../src/crypto/store/localStorage-crypto-store';
import { logger } from '../src/logger';
import { WebStorageSessionStore } from "../src/store/session/webstorage";
import { syncPromise } from "./test-utils";
import { syncPromise } from "./test-utils/test-utils";
import { createClient } from "../src/matrix";
import { MockStorageApi } from "./MockStorageApi";
@@ -85,6 +86,7 @@ TestClient.prototype.toString = function() {
*/
TestClient.prototype.start = function() {
logger.log(this + ': starting');
this.httpBackend.when("GET", "/versions").respond(200, {});
this.httpBackend.when("GET", "/pushrules").respond(200, {});
this.httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
this.expectDeviceKeyUpload();
+11 -33
View File
@@ -17,54 +17,34 @@ limitations under the License.
// load XmlHttpRequest mock
import "./setupTests";
import "../../dist/browser-matrix"; // uses browser-matrix instead of the src
import { MockStorageApi } from "../MockStorageApi";
import { WebStorageSessionStore } from "../../src/store/session/webstorage";
import MockHttpBackend from "matrix-mock-request";
import { LocalStorageCryptoStore } from "../../src/crypto/store/localStorage-crypto-store";
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
import { TestClient } from "../TestClient";
const USER_ID = "@user:test.server";
const DEVICE_ID = "device_id";
const ACCESS_TOKEN = "access_token";
const ROOM_ID = "!room_id:server.test";
/* global matrixcs */
describe("Browserify Test", function() {
let client;
let httpBackend;
async function createTestClient() {
const sessionStoreBackend = new MockStorageApi();
const sessionStore = new WebStorageSessionStore(sessionStoreBackend);
const httpBackend = new MockHttpBackend();
beforeEach(() => {
const testClient = new TestClient(USER_ID, DEVICE_ID, ACCESS_TOKEN);
const options = {
baseUrl: "http://" + USER_ID + ".test.server",
userId: USER_ID,
accessToken: ACCESS_TOKEN,
deviceId: DEVICE_ID,
sessionStore: sessionStore,
request: httpBackend.requestFn,
cryptoStore: new LocalStorageCryptoStore(sessionStoreBackend),
};
const client = matrixcs.createClient(options);
client = testClient.client;
httpBackend = testClient.httpBackend;
httpBackend.when("GET", "/versions").respond(200, {});
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
return { client, httpBackend };
}
beforeEach(async () => {
({ client, httpBackend } = await createTestClient());
await client.startClient();
client.startClient();
});
afterEach(async () => {
client.stopClient();
await httpBackend.stop();
httpBackend.stop();
});
it("Sync", async function() {
@@ -91,10 +71,8 @@ describe("Browserify Test", function() {
};
httpBackend.when("GET", "/sync").respond(200, syncData);
await Promise.race([
Promise.all([
httpBackend.flushAllExpected(),
]),
return await Promise.race([
httpBackend.flushAllExpected(),
new Promise((_, reject) => {
client.once("sync.unexpectedError", reject);
}),
+9 -9
View File
@@ -17,7 +17,7 @@ limitations under the License.
*/
import { TestClient } from '../TestClient';
import * as testUtils from '../test-utils';
import * as testUtils from '../test-utils/test-utils';
import { logger } from '../../src/logger';
const ROOM_ID = "!room:id";
@@ -165,7 +165,7 @@ describe("DeviceList management:", function() {
aliceTestClient.httpBackend.flush('/keys/query', 1).then(
() => aliceTestClient.httpBackend.flush('/send/', 1),
),
aliceTestClient.client.crypto._deviceList.saveIfDirty(),
aliceTestClient.client.crypto.deviceList.saveIfDirty(),
]);
}).then(() => {
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
@@ -202,7 +202,7 @@ describe("DeviceList management:", function() {
return aliceTestClient.httpBackend.flush('/keys/query', 1);
}).then((flushed) => {
expect(flushed).toEqual(0);
return aliceTestClient.client.crypto._deviceList.saveIfDirty();
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -235,7 +235,7 @@ describe("DeviceList management:", function() {
// wait for the client to stop processing the response
return aliceTestClient.client.downloadKeys(['@bob:xyz']);
}).then(() => {
return aliceTestClient.client.crypto._deviceList.saveIfDirty();
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -256,7 +256,7 @@ describe("DeviceList management:", function() {
// wait for the client to stop processing the response
return aliceTestClient.client.downloadKeys(['@chris:abc']);
}).then(() => {
return aliceTestClient.client.crypto._deviceList.saveIfDirty();
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -286,7 +286,7 @@ describe("DeviceList management:", function() {
},
);
await aliceTestClient.httpBackend.flush('/keys/query', 1);
await aliceTestClient.client.crypto._deviceList.saveIfDirty();
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -322,7 +322,7 @@ describe("DeviceList management:", function() {
);
await aliceTestClient.flushSync();
await aliceTestClient.client.crypto._deviceList.saveIfDirty();
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -358,7 +358,7 @@ describe("DeviceList management:", function() {
);
await aliceTestClient.flushSync();
await aliceTestClient.client.crypto._deviceList.saveIfDirty();
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
@@ -379,7 +379,7 @@ describe("DeviceList management:", function() {
anotherTestClient.httpBackend.when('GET', '/sync').respond(
200, getSyncResponse([]));
await anotherTestClient.flushSync();
await anotherTestClient.client.crypto._deviceList.saveIfDirty();
await anotherTestClient.client.crypto.deviceList.saveIfDirty();
anotherTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
+4 -3
View File
@@ -29,7 +29,7 @@ limitations under the License.
import '../olm-loader';
import { logger } from '../../src/logger';
import * as testUtils from "../test-utils";
import * as testUtils from "../test-utils/test-utils";
import { TestClient } from "../TestClient";
import { CRYPTO_ENABLED } from "../../src/client";
@@ -159,7 +159,7 @@ function aliDownloadsKeys() {
// check that the localStorage is updated as we expect (not sure this is
// an integration test, but meh)
return Promise.all([p1, p2]).then(() => {
return aliTestClient.client.crypto._deviceList.saveIfDirty();
return aliTestClient.client.crypto.deviceList.saveIfDirty();
}).then(() => {
aliTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
const devices = data.devices[bobUserId];
@@ -348,7 +348,7 @@ function recvMessage(httpBackend, client, sender, message) {
return testUtils.awaitDecryption(event);
}).then((event) => {
expect(event.getType()).toEqual("m.room.message");
expect(event.getContent()).toEqual({
expect(event.getContent()).toMatchObject({
msgtype: "m.text",
body: "Hello, World",
});
@@ -722,6 +722,7 @@ describe("MatrixClient crypto", function() {
return Promise.resolve()
.then(() => {
logger.log(aliTestClient + ': starting');
httpBackend.when("GET", "/versions").respond(200, {});
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
aliTestClient.expectDeviceKeyUpload();
@@ -1,4 +1,4 @@
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
import { TestClient } from "../TestClient";
describe("MatrixClient events", function() {
@@ -11,6 +11,7 @@ describe("MatrixClient events", function() {
const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken);
client = testClient.client;
httpBackend = testClient.httpBackend;
httpBackend.when("GET", "/versions").respond(200, {});
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
});
+75 -14
View File
@@ -1,7 +1,8 @@
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
import { EventTimeline } from "../../src/matrix";
import { logger } from "../../src/logger";
import { TestClient } from "../TestClient";
import { Thread, THREAD_RELATION_TYPE } from "../../src/models/thread";
const userId = "@alice:localhost";
const userName = "Alice";
@@ -69,8 +70,30 @@ const EVENTS = [
}),
];
const THREAD_ROOT = utils.mkMessage({
room: roomId,
user: userId,
msg: "thread root",
});
const THREAD_REPLY = utils.mkEvent({
room: roomId,
user: userId,
type: "m.room.message",
content: {
"body": "thread reply",
"msgtype": "m.text",
"m.relates_to": {
// We can't use the const here because we change server support mode for test
rel_type: "io.element.thread",
event_id: THREAD_ROOT.event_id,
},
},
});
// start the client, and wait for it to initialise
function startClient(httpBackend, client) {
httpBackend.when("GET", "/versions").respond(200, {});
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
httpBackend.when("GET", "/sync").respond(200, INITIAL_SYNC_DATA);
@@ -115,9 +138,7 @@ describe("getEventTimeline support", function() {
return startClient(httpBackend, client).then(function() {
const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0];
expect(function() {
client.getEventTimeline(timelineSet, "event");
}).toThrow();
expect(client.getEventTimeline(timelineSet, "event")).rejects.toBeTruthy();
});
});
@@ -135,16 +156,12 @@ describe("getEventTimeline support", function() {
return startClient(httpBackend, client).then(() => {
const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0];
expect(function() {
client.getEventTimeline(timelineSet, "event");
}).not.toThrow();
expect(client.getEventTimeline(timelineSet, "event")).rejects.toBeFalsy();
});
});
it("scrollback should be able to scroll back to before a gappy /sync",
function() {
it("scrollback should be able to scroll back to before a gappy /sync", function() {
// need a client with timelineSupport disabled to make this work
let room;
return startClient(httpBackend, client).then(function() {
@@ -228,6 +245,7 @@ describe("MatrixClient event timelines", function() {
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
client.stopClient();
Thread.setServerSideSupport(false);
});
describe("getEventTimeline", function() {
@@ -354,8 +372,7 @@ describe("MatrixClient event timelines", function() {
]);
});
it("should join timelines where they overlap a previous /context",
function() {
it("should join timelines where they overlap a previous /context", function() {
const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0];
@@ -477,6 +494,50 @@ describe("MatrixClient event timelines", function() {
httpBackend.flushAllExpected(),
]);
});
it("should handle thread replies with server support by fetching a contiguous thread timeline", async () => {
Thread.setServerSideSupport(true);
client.stopClient(); // we don't need the client to be syncing at this time
const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0];
httpBackend.when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(THREAD_REPLY.event_id))
.respond(200, function() {
return {
start: "start_token0",
events_before: [],
event: THREAD_REPLY,
events_after: [],
end: "end_token0",
state: [],
};
});
httpBackend.when("GET", "/rooms/!foo%3Abar/event/" + encodeURIComponent(THREAD_ROOT.event_id))
.respond(200, function() {
return THREAD_ROOT;
});
httpBackend.when("GET", "/rooms/!foo%3Abar/relations/" +
encodeURIComponent(THREAD_ROOT.event_id) + "/" +
encodeURIComponent(THREAD_RELATION_TYPE.name) + "?limit=20")
.respond(200, function() {
return {
original_event: THREAD_ROOT,
chunk: [THREAD_REPLY],
next_batch: "next_batch_token0",
prev_batch: "prev_batch_token0",
};
});
const timelinePromise = client.getEventTimeline(timelineSet, THREAD_REPLY.event_id);
await httpBackend.flushAllExpected();
const timeline = await timelinePromise;
expect(timeline.getEvents().find(e => e.getId() === THREAD_ROOT.event_id));
expect(timeline.getEvents().find(e => e.getId() === THREAD_REPLY.event_id));
});
});
describe("paginateEventTimeline", function() {
@@ -502,7 +563,7 @@ describe("MatrixClient event timelines", function() {
const params = req.queryParams;
expect(params.dir).toEqual("b");
expect(params.from).toEqual("start_token0");
expect(params.limit).toEqual(30);
expect(params.limit).toEqual("30");
}).respond(200, function() {
return {
chunk: [EVENTS[1], EVENTS[2]],
@@ -553,7 +614,7 @@ describe("MatrixClient event timelines", function() {
const params = req.queryParams;
expect(params.dir).toEqual("f");
expect(params.from).toEqual("end_token0");
expect(params.limit).toEqual(20);
expect(params.limit).toEqual("20");
}).respond(200, function() {
return {
chunk: [EVENTS[1], EVENTS[2]],
+715 -17
View File
@@ -1,7 +1,9 @@
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
import { CRYPTO_ENABLED } from "../../src/client";
import { MatrixEvent } from "../../src/models/event";
import { Filter, MemoryStore, Room } from "../../src/matrix";
import { TestClient } from "../TestClient";
import { THREAD_RELATION_TYPE } from "../../src/models/thread";
describe("MatrixClient", function() {
let client = null;
@@ -13,9 +15,7 @@ describe("MatrixClient", function() {
beforeEach(function() {
store = new MemoryStore();
const testClient = new TestClient(userId, "aliceDevice", accessToken, undefined, {
store: store,
});
const testClient = new TestClient(userId, "aliceDevice", accessToken, undefined, { store });
httpBackend = testClient.httpBackend;
client = testClient.client;
});
@@ -243,14 +243,15 @@ describe("MatrixClient", function() {
});
describe("searching", function() {
const response = {
search_categories: {
room_events: {
count: 24,
results: {
"$flibble:localhost": {
it("searchMessageText should perform a /search for room_events", function() {
const response = {
search_categories: {
room_events: {
count: 24,
results: [{
rank: 0.1,
result: {
event_id: "$flibble:localhost",
type: "m.room.message",
user_id: "@alice:localhost",
room_id: "!feuiwhf:localhost",
@@ -259,13 +260,11 @@ describe("MatrixClient", function() {
msgtype: "m.text",
},
},
},
}],
},
},
},
};
};
it("searchMessageText should perform a /search for room_events", function(done) {
client.searchMessageText({
query: "monkeys",
});
@@ -279,8 +278,171 @@ describe("MatrixClient", function() {
});
}).respond(200, response);
httpBackend.flush().then(function() {
done();
return httpBackend.flush();
});
describe("should filter out context from different timelines (threads)", () => {
it("filters out thread replies when result is in the main timeline", async () => {
const response = {
search_categories: {
room_events: {
count: 24,
results: [{
rank: 0.1,
result: {
event_id: "$flibble:localhost",
type: "m.room.message",
user_id: "@alice:localhost",
room_id: "!feuiwhf:localhost",
content: {
body: "main timeline",
msgtype: "m.text",
},
},
context: {
events_after: [{
event_id: "$ev-after:server",
type: "m.room.message",
user_id: "@alice:localhost",
room_id: "!feuiwhf:localhost",
content: {
"body": "thread reply",
"msgtype": "m.text",
"m.relates_to": {
"event_id": "$some-thread:server",
"rel_type": THREAD_RELATION_TYPE.name,
},
},
}],
events_before: [{
event_id: "$ev-before:server",
type: "m.room.message",
user_id: "@alice:localhost",
room_id: "!feuiwhf:localhost",
content: {
body: "main timeline again",
msgtype: "m.text",
},
}],
},
}],
},
},
};
const data = {
results: [],
highlights: [],
};
client.processRoomEventsSearch(data, response);
expect(data.results).toHaveLength(1);
expect(data.results[0].context.timeline).toHaveLength(2);
expect(data.results[0].context.timeline.find(e => e.getId() === "$ev-after:server")).toBeFalsy();
});
it("filters out thread replies from threads other than the thread the result replied to", () => {
const response = {
search_categories: {
room_events: {
count: 24,
results: [{
rank: 0.1,
result: {
event_id: "$flibble:localhost",
type: "m.room.message",
user_id: "@alice:localhost",
room_id: "!feuiwhf:localhost",
content: {
"body": "thread 1 reply 1",
"msgtype": "m.text",
"m.relates_to": {
"event_id": "$thread1:server",
"rel_type": THREAD_RELATION_TYPE.name,
},
},
},
context: {
events_after: [{
event_id: "$ev-after:server",
type: "m.room.message",
user_id: "@alice:localhost",
room_id: "!feuiwhf:localhost",
content: {
"body": "thread 2 reply 2",
"msgtype": "m.text",
"m.relates_to": {
"event_id": "$thread2:server",
"rel_type": THREAD_RELATION_TYPE.name,
},
},
}],
events_before: [],
},
}],
},
},
};
const data = {
results: [],
highlights: [],
};
client.processRoomEventsSearch(data, response);
expect(data.results).toHaveLength(1);
expect(data.results[0].context.timeline).toHaveLength(1);
expect(data.results[0].context.timeline.find(e => e.getId() === "$flibble:localhost")).toBeTruthy();
});
it("filters out main timeline events when result is a thread reply", () => {
const response = {
search_categories: {
room_events: {
count: 24,
results: [{
rank: 0.1,
result: {
event_id: "$flibble:localhost",
type: "m.room.message",
user_id: "@alice:localhost",
room_id: "!feuiwhf:localhost",
content: {
"body": "thread 1 reply 1",
"msgtype": "m.text",
"m.relates_to": {
"event_id": "$thread1:server",
"rel_type": THREAD_RELATION_TYPE.name,
},
},
},
context: {
events_after: [{
event_id: "$ev-after:server",
type: "m.room.message",
user_id: "@alice:localhost",
room_id: "!feuiwhf:localhost",
content: {
"body": "main timeline",
"msgtype": "m.text",
},
}],
events_before: [],
},
}],
},
},
};
const data = {
results: [],
highlights: [],
};
client.processRoomEventsSearch(data, response);
expect(data.results).toHaveLength(1);
expect(data.results[0].context.timeline).toHaveLength(1);
expect(data.results[0].context.timeline.find(e => e.getId() === "$flibble:localhost")).toBeTruthy();
});
});
});
@@ -336,7 +498,7 @@ describe("MatrixClient", function() {
var b = JSON.parse(JSON.stringify(o));
delete(b.signatures);
delete(b.unsigned);
return client.crypto._olmDevice.sign(anotherjson.stringify(b));
return client.crypto.olmDevice.sign(anotherjson.stringify(b));
};
logger.log("Ed25519: " + ed25519key);
@@ -392,6 +554,542 @@ describe("MatrixClient", function() {
return prom;
});
});
describe("partitionThreadedEvents", function() {
const room = new Room("!STrMRsukXHtqQdSeHa:matrix.org", client, userId);
it("returns empty arrays when given an empty arrays", function() {
const events = [];
const [timeline, threaded] = client.partitionThreadedEvents(room, events);
expect(timeline).toEqual([]);
expect(threaded).toEqual([]);
});
it("copies pre-thread in-timeline vote events onto both timelines", function() {
client.clientOpts = { experimentalThreadSupport: true };
const eventPollResponseReference = buildEventPollResponseReference();
const eventPollStartThreadRoot = buildEventPollStartThreadRoot();
const eventMessageInThread = buildEventMessageInThread(eventPollStartThreadRoot);
const events = [
eventPollStartThreadRoot,
eventMessageInThread,
eventPollResponseReference,
];
// Vote has no threadId yet
expect(eventPollResponseReference.threadId).toBeFalsy();
const [timeline, threaded] = client.partitionThreadedEvents(room, events);
expect(timeline).toEqual([
// The message that was sent in a thread is missing
eventPollStartThreadRoot,
eventPollResponseReference,
]);
// The vote event has been copied into the thread
const eventRefWithThreadId = withThreadId(
eventPollResponseReference, eventPollStartThreadRoot.getId());
expect(eventRefWithThreadId.threadId).toBeTruthy();
expect(threaded).toEqual([
eventPollStartThreadRoot,
eventMessageInThread,
eventRefWithThreadId,
]);
});
it("copies pre-thread in-timeline reactions onto both timelines", function() {
client.clientOpts = { experimentalThreadSupport: true };
const eventPollStartThreadRoot = buildEventPollStartThreadRoot();
const eventMessageInThread = buildEventMessageInThread(eventPollStartThreadRoot);
const eventReaction = buildEventReaction(eventPollStartThreadRoot);
const events = [
eventPollStartThreadRoot,
eventMessageInThread,
eventReaction,
];
const [timeline, threaded] = client.partitionThreadedEvents(room, events);
expect(timeline).toEqual([
eventPollStartThreadRoot,
eventReaction,
]);
expect(threaded).toEqual([
eventPollStartThreadRoot,
eventMessageInThread,
withThreadId(eventReaction, eventPollStartThreadRoot.getId()),
]);
});
it("copies post-thread in-timeline vote events onto both timelines", function() {
client.clientOpts = { experimentalThreadSupport: true };
const eventPollResponseReference = buildEventPollResponseReference();
const eventPollStartThreadRoot = buildEventPollStartThreadRoot();
const eventMessageInThread = buildEventMessageInThread(eventPollStartThreadRoot);
const events = [
eventPollStartThreadRoot,
eventPollResponseReference,
eventMessageInThread,
];
const [timeline, threaded] = client.partitionThreadedEvents(room, events);
expect(timeline).toEqual([
eventPollStartThreadRoot,
eventPollResponseReference,
]);
expect(threaded).toEqual([
eventPollStartThreadRoot,
withThreadId(eventPollResponseReference, eventPollStartThreadRoot.getId()),
eventMessageInThread,
]);
});
it("copies post-thread in-timeline reactions onto both timelines", function() {
client.clientOpts = { experimentalThreadSupport: true };
const eventPollStartThreadRoot = buildEventPollStartThreadRoot();
const eventMessageInThread = buildEventMessageInThread(eventPollStartThreadRoot);
const eventReaction = buildEventReaction(eventPollStartThreadRoot);
const events = [
eventPollStartThreadRoot,
eventMessageInThread,
eventReaction,
];
const [timeline, threaded] = client.partitionThreadedEvents(room, events);
expect(timeline).toEqual([
eventPollStartThreadRoot,
eventReaction,
]);
expect(threaded).toEqual([
eventPollStartThreadRoot,
eventMessageInThread,
withThreadId(eventReaction, eventPollStartThreadRoot.getId()),
]);
});
it("sends room state events to the main timeline only", function() {
client.clientOpts = { experimentalThreadSupport: true };
// This is based on recording the events in a real room:
const eventPollStartThreadRoot = buildEventPollStartThreadRoot();
const eventPollResponseReference = buildEventPollResponseReference();
const eventMessageInThread = buildEventMessageInThread(eventPollStartThreadRoot);
const eventRoomName = buildEventRoomName();
const eventEncryption = buildEventEncryption();
const eventGuestAccess = buildEventGuestAccess();
const eventHistoryVisibility = buildEventHistoryVisibility();
const eventJoinRules = buildEventJoinRules();
const eventPowerLevels = buildEventPowerLevels();
const eventMember = buildEventMember();
const eventCreate = buildEventCreate();
const events = [
eventPollStartThreadRoot,
eventPollResponseReference,
eventMessageInThread,
eventRoomName,
eventEncryption,
eventGuestAccess,
eventHistoryVisibility,
eventJoinRules,
eventPowerLevels,
eventMember,
eventCreate,
];
const [timeline, threaded] = client.partitionThreadedEvents(room, events);
expect(timeline).toEqual([
// The message that was sent in a thread is missing
eventPollStartThreadRoot,
eventPollResponseReference,
eventRoomName,
eventEncryption,
eventGuestAccess,
eventHistoryVisibility,
eventJoinRules,
eventPowerLevels,
eventMember,
eventCreate,
]);
// Thread should contain only stuff that happened in the thread - no room state events
expect(threaded).toEqual([
eventPollStartThreadRoot,
withThreadId(eventPollResponseReference, eventPollStartThreadRoot.getId()),
eventMessageInThread,
]);
});
it("sends redactions of reactions to thread responses to thread timeline only", () => {
client.clientOpts = { experimentalThreadSupport: true };
const threadRootEvent = buildEventPollStartThreadRoot();
const eventMessageInThread = buildEventMessageInThread(threadRootEvent);
const threadedReaction = buildEventReaction(eventMessageInThread);
const threadedReactionRedaction = buildEventRedaction(threadedReaction);
const events = [
threadRootEvent,
eventMessageInThread,
threadedReaction,
threadedReactionRedaction,
];
const [timeline, threaded] = client.partitionThreadedEvents(room, events);
expect(timeline).toEqual([
threadRootEvent,
]);
expect(threaded).toEqual([
threadRootEvent,
eventMessageInThread,
threadedReaction,
threadedReactionRedaction,
]);
});
it("sends reply to reply to thread root outside of thread to main timeline only", () => {
client.clientOpts = { experimentalThreadSupport: true };
const threadRootEvent = buildEventPollStartThreadRoot();
const eventMessageInThread = buildEventMessageInThread(threadRootEvent);
const directReplyToThreadRoot = buildEventReply(threadRootEvent);
const replyToReply = buildEventReply(directReplyToThreadRoot);
const events = [
threadRootEvent,
eventMessageInThread,
directReplyToThreadRoot,
replyToReply,
];
const [timeline, threaded] = client.partitionThreadedEvents(room, events);
expect(timeline).toEqual([
threadRootEvent,
directReplyToThreadRoot,
replyToReply,
]);
expect(threaded).toEqual([
threadRootEvent,
eventMessageInThread,
]);
});
it("sends reply to thread responses to thread timeline only", () => {
client.clientOpts = { experimentalThreadSupport: true };
const threadRootEvent = buildEventPollStartThreadRoot();
const eventMessageInThread = buildEventMessageInThread(threadRootEvent);
const replyToThreadResponse = buildEventReply(eventMessageInThread);
const events = [
threadRootEvent,
eventMessageInThread,
replyToThreadResponse,
];
const [timeline, threaded] = client.partitionThreadedEvents(room, events);
expect(timeline).toEqual([
threadRootEvent,
]);
expect(threaded).toEqual([
threadRootEvent,
eventMessageInThread,
replyToThreadResponse,
]);
});
});
});
function withThreadId(event, newThreadId) {
const ret = event.toSnapshot();
ret.setThreadId(newThreadId);
return ret;
}
const buildEventMessageInThread = (root) => new MatrixEvent({
"age": 80098509,
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"ciphertext": "ENCRYPTEDSTUFF",
"device_id": "XISFUZSKHH",
"m.relates_to": {
"event_id": root.getId(),
"m.in_reply_to": {
"event_id": root.getId(),
},
"rel_type": "m.thread",
},
"sender_key": "i3N3CtG/CD2bGB8rA9fW6adLYSDvlUhf2iuU73L65Vg",
"session_id": "Ja11R/KG6ua0wdk8zAzognrxjio1Gm/RK2Gn6lFL804",
},
"event_id": "$W4chKIGYowtBblVLkRimeIg8TcdjETnxhDPGfi6NpDg",
"origin_server_ts": 1643815466378,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"type": "m.room.encrypted",
"unsigned": { "age": 80098509 },
"user_id": "@andybalaam-test1:matrix.org",
});
const buildEventPollResponseReference = () => new MatrixEvent({
"age": 80098509,
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"ciphertext": "ENCRYPTEDSTUFF",
"device_id": "XISFUZSKHH",
"m.relates_to": {
"event_id": "$VLS2ojbPmxb6x8ECetn45hmND6cRDcjgv-j-to9m7Vo",
"rel_type": "m.reference",
},
"sender_key": "i3N3CtG/CD2bGB8rA9fW6adLYSDvlUhf2iuU73L65Vg",
"session_id": "Ja11R/KG6ua0wdk8zAzognrxjio1Gm/RK2Gn6lFL804",
},
"event_id": "$91JvpezvsF0cKgav3g8W-uEVS4WkDHgxbJZvL3uMR1g",
"origin_server_ts": 1643815458650,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"type": "m.room.encrypted",
"unsigned": { "age": 80106237 },
"user_id": "@andybalaam-test1:matrix.org",
});
const buildEventReaction = (event) => new MatrixEvent({
"content": {
"m.relates_to": {
"event_id": event.getId(),
"key": "🤗",
"rel_type": "m.annotation",
},
},
"origin_server_ts": 1643977249238,
"sender": "@andybalaam-test1:matrix.org",
"type": "m.reaction",
"unsigned": {
"age": 22598,
"transaction_id": "m1643977249073.16",
},
"event_id": "$86B2b-x3LgE4DlV4y24b7UHnt72LIA3rzjvMysTtAfA",
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
});
const buildEventRedaction = (event) => new MatrixEvent({
"content": {
},
"origin_server_ts": 1643977249239,
"sender": "@andybalaam-test1:matrix.org",
"redacts": event.getId(),
"type": "m.room.redaction",
"unsigned": {
"age": 22597,
"transaction_id": "m1643977249073.17",
},
"event_id": "$86B2b-x3LgE4DlV4y24b7UHnt72LIA3rzjvMysTtAfB",
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
});
const buildEventPollStartThreadRoot = () => new MatrixEvent({
"age": 80108647,
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"ciphertext": "ENCRYPTEDSTUFF",
"device_id": "XISFUZSKHH",
"sender_key": "i3N3CtG/CD2bGB8rA9fW6adLYSDvlUhf2iuU73L65Vg",
"session_id": "Ja11R/KG6ua0wdk8zAzognrxjio1Gm/RK2Gn6lFL804",
},
"event_id": "$VLS2ojbPmxb6x8ECetn45hmND6cRDcjgv-j-to9m7Vo",
"origin_server_ts": 1643815456240,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"type": "m.room.encrypted",
"unsigned": { "age": 80108647 },
"user_id": "@andybalaam-test1:matrix.org",
});
const buildEventReply = (target) => new MatrixEvent({
"age": 80098509,
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"ciphertext": "ENCRYPTEDSTUFF",
"device_id": "XISFUZSKHH",
"m.relates_to": {
"m.in_reply_to": {
"event_id": target.getId(),
},
},
"sender_key": "i3N3CtG/CD2bGB8rA9fW6adLYSDvlUhf2iuU73L65Vg",
"session_id": "Ja11R/KG6ua0wdk8zAzognrxjio1Gm/RK2Gn6lFL804",
},
"event_id": target.getId() + Math.random(),
"origin_server_ts": 1643815466378,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"type": "m.room.encrypted",
"unsigned": { "age": 80098509 },
"user_id": "@andybalaam-test1:matrix.org",
});
const buildEventRoomName = () => new MatrixEvent({
"age": 80123249,
"content": {
"name": "1 poll, 1 vote, 1 thread",
},
"event_id": "$QAdyNJtKnl1j7or2yMycbOCvb6bCgvHs5lg3ZMd5xWk",
"origin_server_ts": 1643815441638,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"state_key": "",
"type": "m.room.name",
"unsigned": { "age": 80123249 },
"user_id": "@andybalaam-test1:matrix.org",
});
const buildEventEncryption = () => new MatrixEvent({
"age": 80123383,
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
},
"event_id": "$1hGykogKQkXbHw8bVuyE3BjHnFBEJBcUWnakd0ck2K0",
"origin_server_ts": 1643815441504,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"state_key": "",
"type": "m.room.encryption",
"unsigned": { "age": 80123383 },
"user_id": "@andybalaam-test1:matrix.org",
});
const buildEventGuestAccess = () => new MatrixEvent({
"age": 80123473,
"content": {
"guest_access": "can_join",
},
"event_id": "$4_2n-H6K9-0nPbnjjtIue2SU44tGJsnuTmi6UuSrh-U",
"origin_server_ts": 1643815441414,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"state_key": "",
"type": "m.room.guest_access",
"unsigned": { "age": 80123473 },
"user_id": "@andybalaam-test1:matrix.org",
});
const buildEventHistoryVisibility = () => new MatrixEvent({
"age": 80123556,
"content": {
"history_visibility": "shared",
},
"event_id": "$W6kp44CTnvciOiHSPyhp8dh4n2v1_9kclUPddeaQj0E",
"origin_server_ts": 1643815441331,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"state_key": "",
"type": "m.room.history_visibility",
"unsigned": { "age": 80123556 },
"user_id": "@andybalaam-test1:matrix.org",
});
const buildEventJoinRules = () => new MatrixEvent({
"age": 80123696,
"content": {
"join_rule": "invite",
},
"event_id": "$6JDDeDp7fEc0F6YnTWMruNcKWFltR3e9wk7wWDDJrAU",
"origin_server_ts": 1643815441191,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"state_key": "",
"type": "m.room.join_rules",
"unsigned": { "age": 80123696 },
"user_id": "@andybalaam-test1:matrix.org",
});
const buildEventPowerLevels = () => new MatrixEvent({
"age": 80124105,
"content": {
"ban": 50,
"events": {
"m.room.avatar": 50,
"m.room.canonical_alias": 50,
"m.room.encryption": 100,
"m.room.history_visibility": 100,
"m.room.name": 50,
"m.room.power_levels": 100,
"m.room.server_acl": 100,
"m.room.tombstone": 100,
},
"events_default": 0,
"historical": 100,
"invite": 0,
"kick": 50,
"redact": 50,
"state_default": 50,
"users": {
"@andybalaam-test1:matrix.org": 100,
},
"users_default": 0,
},
"event_id": "$XZY2YgQhXskpc7gmJJG3S0VmS9_QjjCUVeeFTfgfC2E",
"origin_server_ts": 1643815440782,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"state_key": "",
"type": "m.room.power_levels",
"unsigned": { "age": 80124105 },
"user_id": "@andybalaam-test1:matrix.org",
});
const buildEventMember = () => new MatrixEvent({
"age": 80125279,
"content": {
"avatar_url": "mxc://matrix.org/aNtbVcFfwotudypZcHsIcPOc",
"displayname": "andybalaam-test1",
"membership": "join",
},
"event_id": "$Ex5eVmMs_ti784mo8bgddynbwLvy6231lCycJr7Cl9M",
"origin_server_ts": 1643815439608,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"state_key": "@andybalaam-test1:matrix.org",
"type": "m.room.member",
"unsigned": { "age": 80125279 },
"user_id": "@andybalaam-test1:matrix.org",
});
const buildEventCreate = () => new MatrixEvent({
"age": 80126105,
"content": {
"creator": "@andybalaam-test1:matrix.org",
"room_version": "6",
},
"event_id": "$e7j2Gt37k5NPwB6lz2N3V9lO5pUdNK8Ai7i2FPEK-oI",
"origin_server_ts": 1643815438782,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",
"state_key": "",
"type": "m.room.create",
"unsigned": { "age": 80126105 },
"user_id": "@andybalaam-test1:matrix.org",
});
function assertObjectContains(obj, expected) {
+5 -2
View File
@@ -1,5 +1,6 @@
import * as utils from "../test-utils";
import HttpBackend from "matrix-mock-request";
import * as utils from "../test-utils/test-utils";
import { MatrixClient } from "../../src/matrix";
import { MatrixScheduler } from "../../src/scheduler";
import { MemoryStore } from "../../src/store/memory";
@@ -104,10 +105,12 @@ describe("MatrixClient opts", function() {
expectedEventTypes.indexOf(event.getType()), 1,
);
});
httpBackend.when("GET", "/versions").respond(200, {});
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "foo" });
httpBackend.when("GET", "/sync").respond(200, syncData);
await client.startClient();
client.startClient();
await httpBackend.flush("/versions", 1);
await httpBackend.flush("/pushrules", 1);
await httpBackend.flush("/filter", 1);
await Promise.all([
@@ -1,16 +1,16 @@
import { EventStatus } from "../../src/matrix";
import { EventStatus, RoomEvent } from "../../src/matrix";
import { MatrixScheduler } from "../../src/scheduler";
import { Room } from "../../src/models/room";
import { TestClient } from "../TestClient";
describe("MatrixClient retrying", function() {
let client = null;
let httpBackend = null;
let client: TestClient = null;
let httpBackend: TestClient["httpBackend"] = null;
let scheduler;
const userId = "@alice:localhost";
const accessToken = "aseukfgwef";
const roomId = "!room:here";
let room;
let room: Room;
beforeEach(function() {
scheduler = new MatrixScheduler();
@@ -23,7 +23,7 @@ describe("MatrixClient retrying", function() {
);
httpBackend = testClient.httpBackend;
client = testClient.client;
room = new Room(roomId);
room = new Room(roomId, client, userId);
client.store.storeRoom(room);
});
@@ -50,17 +50,23 @@ describe("MatrixClient retrying", function() {
it("should mark events as EventStatus.CANCELLED when cancelled", function() {
// send a couple of events; the second will be queued
const p1 = client.sendMessage(roomId, "m1").then(function(ev) {
const p1 = client.sendMessage(roomId, {
"msgtype": "m.text",
"body": "m1",
}).then(function() {
// we expect the first message to fail
throw new Error('Message 1 unexpectedly sent successfully');
}, (e) => {
}, () => {
// this is expected
});
// XXX: it turns out that the promise returned by this message
// never gets resolved.
// https://github.com/matrix-org/matrix-js-sdk/issues/496
client.sendMessage(roomId, "m2");
client.sendMessage(roomId, {
"msgtype": "m.text",
"body": "m2",
});
// both events should be in the timeline at this point
const tl = room.getLiveTimeline().getEvents();
@@ -72,7 +78,7 @@ describe("MatrixClient retrying", function() {
expect(ev2.status).toEqual(EventStatus.SENDING);
// the first message should get sent, and the second should get queued
httpBackend.when("PUT", "/send/m.room.message/").check(function(rq) {
httpBackend.when("PUT", "/send/m.room.message/").check(function() {
// ev2 should now have been queued
expect(ev2.status).toEqual(EventStatus.QUEUED);
@@ -88,8 +94,8 @@ describe("MatrixClient retrying", function() {
}).respond(400); // fail the first message
// wait for the localecho of ev1 to be updated
const p3 = new Promise((resolve, reject) => {
room.on("Room.localEchoUpdated", (ev0) => {
const p3 = new Promise<void>((resolve, reject) => {
room.on(RoomEvent.LocalEchoUpdated, (ev0) => {
if (ev0 === ev1) {
resolve();
}
@@ -1,4 +1,4 @@
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
import { EventStatus } from "../../src/models/event";
import { TestClient } from "../TestClient";
@@ -96,7 +96,7 @@ describe("MatrixClient room timelines", function() {
});
}
beforeEach(function() {
beforeEach(async function() {
// these tests should work with or without timelineSupport
const testClient = new TestClient(
userId,
@@ -109,6 +109,7 @@ describe("MatrixClient room timelines", function() {
client = testClient.client;
setNextSyncData();
httpBackend.when("GET", "/versions").respond(200, {});
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
httpBackend.when("GET", "/sync").respond(200, SYNC_DATA);
@@ -116,9 +117,10 @@ describe("MatrixClient room timelines", function() {
return NEXT_SYNC_DATA;
});
client.startClient();
return httpBackend.flush("/pushrules").then(function() {
return httpBackend.flush("/filter");
});
await httpBackend.flush("/versions");
await httpBackend.flush("/pushrules");
await httpBackend.flush("/filter");
});
afterEach(function() {
@@ -551,6 +553,7 @@ describe("MatrixClient room timelines", function() {
NEXT_SYNC_DATA.rooms.join[roomId].timeline.limited = true;
return Promise.all([
httpBackend.flush("/versions", 1),
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(() => {
+3 -3
View File
@@ -1,6 +1,6 @@
import { MatrixEvent } from "../../src/models/event";
import { EventTimeline } from "../../src/models/event-timeline";
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
import { TestClient } from "../TestClient";
describe("MatrixClient syncing", function() {
@@ -19,6 +19,7 @@ describe("MatrixClient syncing", function() {
const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken);
httpBackend = testClient.httpBackend;
client = testClient.client;
httpBackend.when("GET", "/versions").respond(200, {});
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
});
@@ -734,8 +735,7 @@ describe("MatrixClient syncing", function() {
expect(tok).toEqual("pagTok");
}),
// first flush the filter request; this will make syncLeftRooms
// make its /sync call
// first flush the filter request; this will make syncLeftRooms make its /sync call
httpBackend.flush("/filter").then(function() {
return httpBackend.flushAllExpected();
}),
+74 -3
View File
@@ -16,7 +16,8 @@ limitations under the License.
*/
import anotherjson from "another-json";
import * as testUtils from "../test-utils";
import * as testUtils from "../test-utils/test-utils";
import { TestClient } from "../TestClient";
import { logger } from "../../src/logger";
@@ -617,6 +618,9 @@ describe("megolm", function() {
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
).respond(200, {});
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room_key.withheld/',
).respond(200, {});
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
@@ -717,6 +721,9 @@ describe("megolm", function() {
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
).respond(200, {});
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/m.room_key.withheld/',
).respond(200, {});
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test2'),
@@ -1012,8 +1019,8 @@ describe("megolm", function() {
},
event: true,
});
event._senderCurve25519Key = testSenderKey;
return testClient.client.crypto._onRoomKeyEvent(event);
event.senderCurve25519Key = testSenderKey;
return testClient.client.crypto.onRoomKeyEvent(event);
}).then(() => {
const event = testUtils.mkEvent({
event: true,
@@ -1025,4 +1032,68 @@ describe("megolm", function() {
});
});
});
it("Alice can decrypt a message with falsey content", function() {
return aliceTestClient.start().then(() => {
return createOlmSession(testOlmAccount, aliceTestClient);
}).then((p2pSession) => {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
// make the room_key event
const roomKeyEncrypted = encryptGroupSessionKey({
senderKey: testSenderKey,
recipient: aliceTestClient,
p2pSession: p2pSession,
groupSession: groupSession,
room_id: ROOM_ID,
});
const plaintext = {
type: "m.room.message",
content: undefined,
room_id: ROOM_ID,
};
const messageEncrypted = {
event_id: 'test_megolm_event',
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: groupSession.encrypt(JSON.stringify(plaintext)),
device_id: "testDevice",
sender_key: testSenderKey,
session_id: groupSession.session_id(),
},
type: "m.room.encrypted",
};
// Alice gets both the events in a single sync
const syncResponse = {
next_batch: 1,
to_device: {
events: [roomKeyEncrypted],
},
rooms: {
join: {},
},
};
syncResponse.rooms.join[ROOM_ID] = {
timeline: {
events: [messageEncrypted],
},
};
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
return aliceTestClient.flushSync();
}).then(function() {
const room = aliceTestClient.client.getRoom(ROOM_ID);
const event = room.getLiveTimeline().getEvents()[0];
expect(event.isEncrypted()).toBe(true);
return testUtils.awaitDecryption(event);
}).then((event) => {
expect(event.getRoomId()).toEqual(ROOM_ID);
expect(event.getContent()).toEqual({});
expect(event.getClearContent()).toBeUndefined();
});
});
});
-368
View File
@@ -1,368 +0,0 @@
// load olm before the sdk if possible
import './olm-loader';
import { logger } from '../src/logger';
import { MatrixEvent } from "../src/models/event";
/**
* Return a promise that is resolved when the client next emits a
* SYNCING event.
* @param {Object} client The client
* @param {Number=} count Number of syncs to wait for (default 1)
* @return {Promise} Resolves once the client has emitted a SYNCING event
*/
export function syncPromise(client, count) {
if (count === undefined) {
count = 1;
}
if (count <= 0) {
return Promise.resolve();
}
const p = new Promise((resolve, reject) => {
const cb = (state) => {
logger.log(`${Date.now()} syncPromise(${count}): ${state}`);
if (state === 'SYNCING') {
resolve();
} else {
client.once('sync', cb);
}
};
client.once('sync', cb);
});
return p.then(() => {
return syncPromise(client, count-1);
});
}
/**
* Create a spy for an object and automatically spy its methods.
* @param {*} constr The class constructor (used with 'new')
* @param {string} name The name of the class
* @return {Object} An instantiated object with spied methods/properties.
*/
export function mock(constr, name) {
// Based on
// http://eclipsesource.com/blogs/2014/03/27/mocks-in-jasmine-tests/
const HelperConstr = new Function(); // jshint ignore:line
HelperConstr.prototype = constr.prototype;
const result = new HelperConstr();
result.toString = function() {
return "mock" + (name ? " of " + name : "");
};
for (const key in constr.prototype) { // eslint-disable-line guard-for-in
try {
if (constr.prototype[key] instanceof Function) {
result[key] = jest.fn();
}
} catch (ex) {
// Direct access to some non-function fields of DOM prototypes may
// cause exceptions.
// Overwriting will not work either in that case.
}
}
return result;
}
/**
* Create an Event.
* @param {Object} opts Values for the event.
* @param {string} opts.type The event.type
* @param {string} opts.room The event.room_id
* @param {string} opts.sender The event.sender
* @param {string} opts.skey Optional. The state key (auto inserts empty string)
* @param {Object} opts.content The event.content
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object} a JSON object representing this event.
*/
export function mkEvent(opts) {
if (!opts.type || !opts.content) {
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
}
const event = {
type: opts.type,
room_id: opts.room,
sender: opts.sender || opts.user, // opts.user for backwards-compat
content: opts.content,
event_id: "$" + Math.random() + "-" + Math.random(),
};
if (opts.skey !== undefined) {
event.state_key = opts.skey;
} else if (["m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
"m.room.power_levels", "m.room.topic",
"com.example.state"].indexOf(opts.type) !== -1) {
event.state_key = "";
}
return opts.event ? new MatrixEvent(event) : event;
}
/**
* Create an m.presence event.
* @param {Object} opts Values for the presence.
* @return {Object|MatrixEvent} The event
*/
export function mkPresence(opts) {
if (!opts.user) {
throw new Error("Missing user");
}
const event = {
event_id: "$" + Math.random() + "-" + Math.random(),
type: "m.presence",
sender: opts.sender || opts.user, // opts.user for backwards-compat
content: {
avatar_url: opts.url,
displayname: opts.name,
last_active_ago: opts.ago,
presence: opts.presence || "offline",
},
};
return opts.event ? new MatrixEvent(event) : event;
}
/**
* Create an m.room.member event.
* @param {Object} opts Values for the membership.
* @param {string} opts.room The room ID for the event.
* @param {string} opts.mship The content.membership for the event.
* @param {string} opts.sender The sender user ID for the event.
* @param {string} opts.skey The target user ID for the event if applicable
* e.g. for invites/bans.
* @param {string} opts.name The content.displayname for the event.
* @param {string} opts.url The content.avatar_url for the event.
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event
*/
export function mkMembership(opts) {
opts.type = "m.room.member";
if (!opts.skey) {
opts.skey = opts.sender || opts.user;
}
if (!opts.mship) {
throw new Error("Missing .mship => " + JSON.stringify(opts));
}
opts.content = {
membership: opts.mship,
};
if (opts.name) {
opts.content.displayname = opts.name;
}
if (opts.url) {
opts.content.avatar_url = opts.url;
}
return mkEvent(opts);
}
/**
* Create an m.room.message event.
* @param {Object} opts Values for the message
* @param {string} opts.room The room ID for the event.
* @param {string} opts.user The user ID for the event.
* @param {string} opts.msg Optional. The content.body for the event.
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event
*/
export function mkMessage(opts) {
opts.type = "m.room.message";
if (!opts.msg) {
opts.msg = "Random->" + Math.random();
}
if (!opts.room || !opts.user) {
throw new Error("Missing .room or .user from %s", opts);
}
opts.content = {
msgtype: "m.text",
body: opts.msg,
};
return mkEvent(opts);
}
/**
* A mock implementation of webstorage
*
* @constructor
*/
export function MockStorageApi() {
this.data = {};
}
MockStorageApi.prototype = {
get length() {
return Object.keys(this.data).length;
},
key: function(i) {
return Object.keys(this.data)[i];
},
setItem: function(k, v) {
this.data[k] = v;
},
getItem: function(k) {
return this.data[k] || null;
},
removeItem: function(k) {
delete this.data[k];
},
};
/**
* If an event is being decrypted, wait for it to finish being decrypted.
*
* @param {MatrixEvent} event
* @returns {Promise} promise which resolves (to `event`) when the event has been decrypted
*/
export function awaitDecryption(event) {
// An event is not always decrypted ahead of time
// getClearContent is a good signal to know whether an event has been decrypted
// already
if (event.getClearContent() !== null) {
return event;
} else {
logger.log(`${Date.now()} event ${event.getId()} is being decrypted; waiting`);
return new Promise((resolve, reject) => {
event.once('Event.decrypted', (ev) => {
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev);
});
});
}
}
export function HttpResponse(
httpLookups, acceptKeepalives, ignoreUnhandledSync,
) {
this.httpLookups = httpLookups;
this.acceptKeepalives = acceptKeepalives === undefined ? true : acceptKeepalives;
this.ignoreUnhandledSync = ignoreUnhandledSync;
this.pendingLookup = null;
}
HttpResponse.prototype.request = function(
cb, method, path, qp, data, prefix,
) {
if (path === HttpResponse.KEEP_ALIVE_PATH && this.acceptKeepalives) {
return Promise.resolve();
}
const next = this.httpLookups.shift();
const logLine = (
"MatrixClient[UT] RECV " + method + " " + path + " " +
"EXPECT " + (next ? next.method : next) + " " + (next ? next.path : next)
);
logger.log(logLine);
if (!next) { // no more things to return
if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
logger.log("MatrixClient[UT] Ignoring.");
return new Promise(() => {});
}
if (this.pendingLookup) {
if (this.pendingLookup.method === method
&& this.pendingLookup.path === path) {
return this.pendingLookup.promise;
}
// >1 pending thing, and they are different, whine.
expect(false).toBe(
true, ">1 pending request. You should probably handle them. " +
"PENDING: " + JSON.stringify(this.pendingLookup) + " JUST GOT: " +
method + " " + path,
);
}
this.pendingLookup = {
promise: new Promise(() => {}),
method: method,
path: path,
};
return this.pendingLookup.promise;
}
if (next.path === path && next.method === method) {
logger.log(
"MatrixClient[UT] Matched. Returning " +
(next.error ? "BAD" : "GOOD") + " response",
);
if (next.expectBody) {
expect(next.expectBody).toEqual(data);
}
if (next.expectQueryParams) {
Object.keys(next.expectQueryParams).forEach(function(k) {
expect(qp[k]).toEqual(next.expectQueryParams[k]);
});
}
if (next.thenCall) {
process.nextTick(next.thenCall, 0); // next tick so we return first.
}
if (next.error) {
return Promise.reject({
errcode: next.error.errcode,
httpStatus: next.error.httpStatus,
name: next.error.errcode,
message: "Expected testing error",
data: next.error,
});
}
return Promise.resolve(next.data);
} else if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
logger.log("MatrixClient[UT] Ignoring.");
this.httpLookups.unshift(next);
return new Promise(() => {});
}
expect(true).toBe(false, "Expected different request. " + logLine);
return new Promise(() => {});
};
HttpResponse.KEEP_ALIVE_PATH = "/_matrix/client/versions";
HttpResponse.PUSH_RULES_RESPONSE = {
method: "GET",
path: "/pushrules/",
data: {},
};
HttpResponse.USER_ID = "@alice:bar";
HttpResponse.filterResponse = function(userId) {
const filterPath = "/user/" + encodeURIComponent(userId) + "/filter";
return {
method: "POST",
path: filterPath,
data: { filter_id: "f1lt3r" },
};
};
HttpResponse.SYNC_DATA = {
next_batch: "s_5_3",
presence: { events: [] },
rooms: {},
};
HttpResponse.SYNC_RESPONSE = {
method: "GET",
path: "/sync",
data: HttpResponse.SYNC_DATA,
};
HttpResponse.defaultResponses = function(userId) {
return [
HttpResponse.PUSH_RULES_RESPONSE,
HttpResponse.filterResponse(userId),
HttpResponse.SYNC_RESPONSE,
];
};
export function setHttpResponses(
client, responses, acceptKeepalives, ignoreUnhandledSyncs,
) {
const httpResponseObj = new HttpResponse(
responses, acceptKeepalives, ignoreUnhandledSyncs,
);
const httpReq = httpResponseObj.request.bind(httpResponseObj);
client.http = [
"authedRequest", "authedRequestWithPrefix", "getContentUri",
"request", "requestWithPrefix", "uploadContent",
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {});
client.http.authedRequest.mockImplementation(httpReq);
client.http.authedRequestWithPrefix.mockImplementation(httpReq);
client.http.requestWithPrefix.mockImplementation(httpReq);
client.http.request.mockImplementation(httpReq);
}
+119
View File
@@ -0,0 +1,119 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from "../../src";
import { M_BEACON, M_BEACON_INFO } from "../../src/@types/beacon";
import { LocationAssetType } from "../../src/@types/location";
import {
makeBeaconContent,
makeBeaconInfoContent,
} from "../../src/content-helpers";
type InfoContentProps = {
timeout: number;
isLive?: boolean;
assetType?: LocationAssetType;
description?: string;
};
const DEFAULT_INFO_CONTENT_PROPS: InfoContentProps = {
timeout: 3600000,
};
/**
* Create an m.beacon_info event
* all required properties are mocked
* override with contentProps
*/
export const makeBeaconInfoEvent = (
sender: string,
roomId: string,
contentProps: Partial<InfoContentProps> = {},
eventId?: string,
eventTypeSuffix?: string,
): MatrixEvent => {
const {
timeout, isLive, description, assetType,
} = {
...DEFAULT_INFO_CONTENT_PROPS,
...contentProps,
};
const event = new MatrixEvent({
type: `${M_BEACON_INFO.name}.${sender}.${eventTypeSuffix || Date.now()}`,
room_id: roomId,
state_key: sender,
content: makeBeaconInfoContent(timeout, isLive, description, assetType),
});
// live beacons use the beacon_info event id
// set or default this
event.replaceLocalEventId(eventId || `$${Math.random()}-${Math.random()}`);
return event;
};
type ContentProps = {
uri: string;
timestamp: number;
beaconInfoId: string;
description?: string;
};
const DEFAULT_CONTENT_PROPS: ContentProps = {
uri: 'geo:-36.24484561954707,175.46884959563613;u=10',
timestamp: 123,
beaconInfoId: '$123',
};
/**
* Create an m.beacon event
* all required properties are mocked
* override with contentProps
*/
export const makeBeaconEvent = (
sender: string,
contentProps: Partial<ContentProps> = {},
): MatrixEvent => {
const { uri, timestamp, beaconInfoId, description } = {
...DEFAULT_CONTENT_PROPS,
...contentProps,
};
return new MatrixEvent({
type: M_BEACON.name,
sender,
content: makeBeaconContent(uri, timestamp, beaconInfoId, description),
});
};
/**
* Create a mock geolocation position
* defaults all required properties
*/
export const makeGeolocationPosition = (
{ timestamp, coords }:
{ timestamp?: number, coords: Partial<GeolocationCoordinates> },
): GeolocationPosition => ({
timestamp: timestamp ?? 1647256791840,
coords: {
accuracy: 1,
latitude: 54.001927,
longitude: -8.253491,
altitude: null,
altitudeAccuracy: null,
heading: null,
speed: null,
...coords,
},
});
+28
View File
@@ -0,0 +1,28 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* Filter emitter.emit mock calls to find relevant events
* eg:
* ```
* const emitSpy = jest.spyOn(state, 'emit');
* << actions >>
* const beaconLivenessEmits = emitCallsByEventType(BeaconEvent.New, emitSpy);
* expect(beaconLivenessEmits.length).toBe(1);
* ```
*/
export const filterEmitCallsByEventType = (eventType: string, spy: jest.SpyInstance<any, unknown[]>) =>
spy.mock.calls.filter((args) => args[0] === eventType);
+282
View File
@@ -0,0 +1,282 @@
// eslint-disable-next-line no-restricted-imports
import EventEmitter from "events";
// load olm before the sdk if possible
import '../olm-loader';
import { logger } from '../../src/logger';
import { IContent, IEvent, IUnsigned, MatrixEvent, MatrixEventEvent } from "../../src/models/event";
import { ClientEvent, EventType, MatrixClient } from "../../src";
import { SyncState } from "../../src/sync";
/**
* Return a promise that is resolved when the client next emits a
* SYNCING event.
* @param {Object} client The client
* @param {Number=} count Number of syncs to wait for (default 1)
* @return {Promise} Resolves once the client has emitted a SYNCING event
*/
export function syncPromise(client: MatrixClient, count = 1): Promise<void> {
if (count <= 0) {
return Promise.resolve();
}
const p = new Promise<void>((resolve) => {
const cb = (state: SyncState) => {
logger.log(`${Date.now()} syncPromise(${count}): ${state}`);
if (state === SyncState.Syncing) {
resolve();
} else {
client.once(ClientEvent.Sync, cb);
}
};
client.once(ClientEvent.Sync, cb);
});
return p.then(() => {
return syncPromise(client, count - 1);
});
}
/**
* Create a spy for an object and automatically spy its methods.
* @param {*} constr The class constructor (used with 'new')
* @param {string} name The name of the class
* @return {Object} An instantiated object with spied methods/properties.
*/
export function mock<T>(constr: { new(...args: any[]): T }, name: string): T {
// Based on http://eclipsesource.com/blogs/2014/03/27/mocks-in-jasmine-tests/
const HelperConstr = new Function(); // jshint ignore:line
HelperConstr.prototype = constr.prototype;
// @ts-ignore
const result = new HelperConstr();
result.toString = function() {
return "mock" + (name ? " of " + name : "");
};
for (const key of Object.getOwnPropertyNames(constr.prototype)) { // eslint-disable-line guard-for-in
try {
if (constr.prototype[key] instanceof Function) {
result[key] = jest.fn();
}
} catch (ex) {
// Direct access to some non-function fields of DOM prototypes may
// cause exceptions.
// Overwriting will not work either in that case.
}
}
return result;
}
interface IEventOpts {
type: EventType | string;
room: string;
sender?: string;
skey?: string;
content: IContent;
event?: boolean;
user?: string;
unsigned?: IUnsigned;
redacts?: string;
}
/**
* Create an Event.
* @param {Object} opts Values for the event.
* @param {string} opts.type The event.type
* @param {string} opts.room The event.room_id
* @param {string} opts.sender The event.sender
* @param {string} opts.skey Optional. The state key (auto inserts empty string)
* @param {Object} opts.content The event.content
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object} a JSON object representing this event.
*/
export function mkEvent(opts: IEventOpts): object | MatrixEvent {
if (!opts.type || !opts.content) {
throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
}
const event: Partial<IEvent> = {
type: opts.type as string,
room_id: opts.room,
sender: opts.sender || opts.user, // opts.user for backwards-compat
content: opts.content,
unsigned: opts.unsigned || {},
event_id: "$" + Math.random() + "-" + Math.random(),
redacts: opts.redacts,
};
if (opts.skey !== undefined) {
event.state_key = opts.skey;
} else if ([
EventType.RoomName,
EventType.RoomTopic,
EventType.RoomCreate,
EventType.RoomJoinRules,
EventType.RoomPowerLevels,
EventType.RoomTopic,
"com.example.state",
].includes(opts.type)) {
event.state_key = "";
}
return opts.event ? new MatrixEvent(event) : event;
}
interface IPresenceOpts {
user?: string;
sender?: string;
url: string;
name: string;
ago: number;
presence?: string;
event?: boolean;
}
/**
* Create an m.presence event.
* @param {Object} opts Values for the presence.
* @return {Object|MatrixEvent} The event
*/
export function mkPresence(opts: IPresenceOpts): object | MatrixEvent {
const event = {
event_id: "$" + Math.random() + "-" + Math.random(),
type: "m.presence",
sender: opts.sender || opts.user, // opts.user for backwards-compat
content: {
avatar_url: opts.url,
displayname: opts.name,
last_active_ago: opts.ago,
presence: opts.presence || "offline",
},
};
return opts.event ? new MatrixEvent(event) : event;
}
interface IMembershipOpts {
room: string;
mship: string;
sender?: string;
user?: string;
skey?: string;
name?: string;
url?: string;
event?: boolean;
}
/**
* Create an m.room.member event.
* @param {Object} opts Values for the membership.
* @param {string} opts.room The room ID for the event.
* @param {string} opts.mship The content.membership for the event.
* @param {string} opts.sender The sender user ID for the event.
* @param {string} opts.skey The target user ID for the event if applicable
* e.g. for invites/bans.
* @param {string} opts.name The content.displayname for the event.
* @param {string} opts.url The content.avatar_url for the event.
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event
*/
export function mkMembership(opts: IMembershipOpts): object | MatrixEvent {
const eventOpts: IEventOpts = {
...opts,
type: EventType.RoomMember,
content: {
membership: opts.mship,
},
};
if (!opts.skey) {
eventOpts.skey = opts.sender || opts.user;
}
if (opts.name) {
eventOpts.content.displayname = opts.name;
}
if (opts.url) {
eventOpts.content.avatar_url = opts.url;
}
return mkEvent(eventOpts);
}
interface IMessageOpts {
room: string;
user: string;
msg?: string;
event?: boolean;
}
/**
* Create an m.room.message event.
* @param {Object} opts Values for the message
* @param {string} opts.room The room ID for the event.
* @param {string} opts.user The user ID for the event.
* @param {string} opts.msg Optional. The content.body for the event.
* @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event
*/
export function mkMessage(opts: IMessageOpts): object | MatrixEvent {
const eventOpts: IEventOpts = {
...opts,
type: EventType.RoomMessage,
content: {
msgtype: "m.text",
body: opts.msg,
},
};
if (!eventOpts.content.body) {
eventOpts.content.body = "Random->" + Math.random();
}
return mkEvent(eventOpts);
}
/**
* A mock implementation of webstorage
*
* @constructor
*/
export class MockStorageApi {
private data: Record<string, any> = {};
public get length() {
return Object.keys(this.data).length;
}
public key(i: number): any {
return Object.keys(this.data)[i];
}
public setItem(k: string, v: any): void {
this.data[k] = v;
}
public getItem(k: string): any {
return this.data[k] || null;
}
public removeItem(k: string): void {
delete this.data[k];
}
}
/**
* If an event is being decrypted, wait for it to finish being decrypted.
*
* @param {MatrixEvent} event
* @returns {Promise} promise which resolves (to `event`) when the event has been decrypted
*/
export async function awaitDecryption(event: MatrixEvent): Promise<MatrixEvent> {
// An event is not always decrypted ahead of time
// getClearContent is a good signal to know whether an event has been decrypted
// already
if (event.getClearContent() !== null) {
return event;
} else {
logger.log(`${Date.now()} event ${event.getId()} is being decrypted; waiting`);
return new Promise((resolve) => {
event.once(MatrixEventEvent.Decrypted, (ev) => {
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev);
});
});
}
}
export const emitPromise = (e: EventEmitter, k: string): Promise<any> => new Promise(r => e.once(k, r));
+2
View File
@@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
import { ReEmitter } from "../../src/ReEmitter";
const EVENTNAME = "UnknownEntry";
+1
View File
@@ -16,6 +16,7 @@ limitations under the License.
*/
import MockHttpBackend from "matrix-mock-request";
import * as sdk from "../../src";
import { AutoDiscovery } from "../../src/autodiscovery";
+127
View File
@@ -0,0 +1,127 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { REFERENCE_RELATION } from "matrix-events-sdk";
import { M_BEACON_INFO } from "../../src/@types/beacon";
import { LocationAssetType, M_ASSET, M_LOCATION, M_TIMESTAMP } from "../../src/@types/location";
import { makeBeaconContent, makeBeaconInfoContent } from "../../src/content-helpers";
describe('Beacon content helpers', () => {
describe('makeBeaconInfoContent()', () => {
const mockDateNow = 123456789;
beforeEach(() => {
jest.spyOn(global.Date, 'now').mockReturnValue(mockDateNow);
});
afterAll(() => {
jest.spyOn(global.Date, 'now').mockRestore();
});
it('create fully defined event content', () => {
expect(makeBeaconInfoContent(
1234,
true,
'nice beacon_info',
LocationAssetType.Pin,
)).toEqual({
[M_BEACON_INFO.name]: {
description: 'nice beacon_info',
timeout: 1234,
live: true,
},
[M_TIMESTAMP.name]: mockDateNow,
[M_ASSET.name]: {
type: LocationAssetType.Pin,
},
});
});
it('defaults timestamp to current time', () => {
expect(makeBeaconInfoContent(
1234,
true,
'nice beacon_info',
LocationAssetType.Pin,
)).toEqual(expect.objectContaining({
[M_TIMESTAMP.name]: mockDateNow,
}));
});
it('uses timestamp when provided', () => {
expect(makeBeaconInfoContent(
1234,
true,
'nice beacon_info',
LocationAssetType.Pin,
99999,
)).toEqual(expect.objectContaining({
[M_TIMESTAMP.name]: 99999,
}));
});
it('defaults asset type to self when not set', () => {
expect(makeBeaconInfoContent(
1234,
true,
'nice beacon_info',
// no assetType passed
)).toEqual(expect.objectContaining({
[M_ASSET.name]: {
type: LocationAssetType.Self,
},
}));
});
});
describe('makeBeaconContent()', () => {
it('creates event content without description', () => {
expect(makeBeaconContent(
'geo:foo',
123,
'$1234',
// no description
)).toEqual({
[M_LOCATION.name]: {
description: undefined,
uri: 'geo:foo',
},
[M_TIMESTAMP.name]: 123,
"m.relates_to": {
rel_type: REFERENCE_RELATION.name,
event_id: '$1234',
},
});
});
it('creates event content with description', () => {
expect(makeBeaconContent(
'geo:foo',
123,
'$1234',
'test description',
)).toEqual({
[M_LOCATION.name]: {
description: 'test description',
uri: 'geo:foo',
},
[M_TIMESTAMP.name]: 123,
"m.relates_to": {
rel_type: REFERENCE_RELATION.name,
event_id: '$1234',
},
});
});
});
});
+36 -9
View File
@@ -1,4 +1,7 @@
import '../olm-loader';
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
import { Crypto } from "../../src/crypto";
import { WebStorageSessionStore } from "../../src/store/session/webstorage";
import { MemoryCryptoStore } from "../../src/crypto/store/memory-crypto-store";
@@ -8,9 +11,9 @@ import { MatrixEvent } from "../../src/models/event";
import { Room } from "../../src/models/room";
import * as olmlib from "../../src/crypto/olmlib";
import { sleep } from "../../src/utils";
import { EventEmitter } from "events";
import { CRYPTO_ENABLED } from "../../src/client";
import { DeviceInfo } from "../../src/crypto/deviceinfo";
import { logger } from '../../src/logger';
const Olm = global.Olm;
@@ -65,7 +68,7 @@ describe("Crypto", function() {
'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI';
device.keys["ed25519:FLIBBLE"] =
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
client.crypto._deviceList.getDeviceByIdentityKey = () => device;
client.crypto.deviceList.getDeviceByIdentityKey = () => device;
encryptionInfo = client.getEventEncryptionInfo(event);
expect(encryptionInfo.encrypted).toBeTruthy();
@@ -213,7 +216,7 @@ describe("Crypto", function() {
async function keyshareEventForEvent(event, index) {
const eventContent = event.getWireContent();
const key = await aliceClient.crypto._olmDevice
const key = await aliceClient.crypto.olmDevice
.getInboundGroupSessionKey(
roomId, eventContent.sender_key, eventContent.session_id,
index,
@@ -234,7 +237,7 @@ describe("Crypto", function() {
},
});
// make onRoomKeyEvent think this was an encrypted event
ksEvent._senderCurve25519Key = "akey";
ksEvent.senderCurve25519Key = "akey";
return ksEvent;
}
@@ -274,9 +277,9 @@ describe("Crypto", function() {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto.encryptEvent(event, aliceRoom);
event._clearEvent = {};
event._senderCurve25519Key = null;
event._claimedEd25519Key = null;
event.clearEvent = undefined;
event.senderCurve25519Key = null;
event.claimedEd25519Key = null;
try {
await bobClient.crypto.decryptEvent(event);
} catch (e) {
@@ -285,7 +288,7 @@ describe("Crypto", function() {
}
}));
const bobDecryptor = bobClient.crypto._getRoomDecryptor(
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
@@ -377,7 +380,7 @@ describe("Crypto", function() {
// key requests get queued until the sync has finished, but we don't
// let the client set up enough for that to happen, so gut-wrench a bit
// to force it to send now.
aliceClient.crypto._outgoingRoomKeyRequestManager.sendQueuedRequests();
aliceClient.crypto.outgoingRoomKeyRequestManager.sendQueuedRequests();
jest.runAllTimers();
await Promise.resolve();
expect(aliceClient.sendToDevice).toBeCalledTimes(1);
@@ -398,4 +401,28 @@ describe("Crypto", function() {
expect(aliceClient.sendToDevice.mock.calls[2][2]).not.toBe(txnId);
});
});
describe('Secret storage', function() {
it("creates secret storage even if there is no keyInfo", async function() {
jest.spyOn(logger, 'log').mockImplementation(() => {});
jest.setTimeout(10000);
const client = (new TestClient("@a:example.com", "dev")).client;
await client.initCrypto();
client.crypto.getSecretStorageKey = async () => null;
client.crypto.isCrossSigningReady = async () => false;
client.crypto.baseApis.uploadDeviceSigningKeys = () => null;
client.crypto.baseApis.setAccountData = () => null;
client.crypto.baseApis.uploadKeySignatures = () => null;
client.crypto.baseApis.http.authedRequest = () => null;
const createSecretStorageKey = async () => {
return {
keyInfo: undefined, // Returning undefined here used to cause a crash
privateKey: Uint8Array.of(32, 33),
};
};
await client.crypto.bootstrapSecretStorage({
createSecretStorageKey,
});
});
});
});
+204 -97
View File
@@ -2,7 +2,7 @@ import '../../../olm-loader';
import * as algorithms from "../../../../src/crypto/algorithms";
import { MemoryCryptoStore } from "../../../../src/crypto/store/memory-crypto-store";
import { MockStorageApi } from "../../../MockStorageApi";
import * as testUtils from "../../../test-utils";
import * as testUtils from "../../../test-utils/test-utils";
import { OlmDevice } from "../../../../src/crypto/OlmDevice";
import { Crypto } from "../../../../src/crypto";
import { logger } from "../../../../src/logger";
@@ -256,97 +256,145 @@ describe("MegolmDecryption", function() {
});
});
it("re-uses sessions for sequential messages", async function() {
mockCrypto._backupManager = {
backupGroupSession: () => {},
};
const mockStorage = new MockStorageApi();
const cryptoStore = new MemoryCryptoStore(mockStorage);
describe("session reuse and key reshares", () => {
let megolmEncryption;
let aliceDeviceInfo;
let mockRoom;
let olmDevice;
const olmDevice = new OlmDevice(cryptoStore);
olmDevice.verifySignature = jest.fn();
await olmDevice.init();
beforeEach(async () => {
mockCrypto.backupManager = {
backupGroupSession: () => {},
};
const mockStorage = new MockStorageApi();
const cryptoStore = new MemoryCryptoStore(mockStorage);
mockBaseApis.claimOneTimeKeys = jest.fn().mockReturnValue(Promise.resolve({
one_time_keys: {
'@alice:home.server': {
aliceDevice: {
'signed_curve25519:flooble': {
key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
signatures: {
'@alice:home.server': {
'ed25519:aliceDevice': 'totally valid',
olmDevice = new OlmDevice(cryptoStore);
olmDevice.verifySignature = jest.fn();
await olmDevice.init();
mockBaseApis.claimOneTimeKeys = jest.fn().mockReturnValue(Promise.resolve({
one_time_keys: {
'@alice:home.server': {
aliceDevice: {
'signed_curve25519:flooble': {
key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
signatures: {
'@alice:home.server': {
'ed25519:aliceDevice': 'totally valid',
},
},
},
},
},
},
},
}));
mockBaseApis.sendToDevice = jest.fn().mockResolvedValue(undefined);
}));
mockBaseApis.sendToDevice = jest.fn().mockResolvedValue(undefined);
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
'@alice:home.server': {
aliceDevice: {
deviceId: 'aliceDevice',
isBlocked: jest.fn().mockReturnValue(false),
isUnverified: jest.fn().mockReturnValue(false),
getIdentityKey: jest.fn().mockReturnValue(
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE',
),
getFingerprint: jest.fn().mockReturnValue(''),
aliceDeviceInfo = {
deviceId: 'aliceDevice',
isBlocked: jest.fn().mockReturnValue(false),
isUnverified: jest.fn().mockReturnValue(false),
getIdentityKey: jest.fn().mockReturnValue(
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE',
),
getFingerprint: jest.fn().mockReturnValue(''),
};
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
'@alice:home.server': {
aliceDevice: aliceDeviceInfo,
},
},
}));
}));
mockCrypto.checkDeviceTrust.mockReturnValue({
isVerified: () => false,
mockCrypto.checkDeviceTrust.mockReturnValue({
isVerified: () => false,
});
megolmEncryption = new MegolmEncryption({
userId: '@user:id',
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: mockBaseApis,
roomId: ROOM_ID,
config: {
rotation_period_ms: 9999999999999,
},
});
mockRoom = {
getEncryptionTargetMembers: jest.fn().mockReturnValue(
[{ userId: "@alice:home.server" }],
),
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(false),
};
});
const megolmEncryption = new MegolmEncryption({
userId: '@user:id',
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: mockBaseApis,
roomId: ROOM_ID,
config: {
rotation_period_ms: 9999999999999,
},
});
const mockRoom = {
getEncryptionTargetMembers: jest.fn().mockReturnValue(
[{ userId: "@alice:home.server" }],
),
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(false),
};
const ct1 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some text",
});
expect(mockRoom.getEncryptionTargetMembers).toHaveBeenCalled();
it("re-uses sessions for sequential messages", async function() {
const ct1 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some text",
});
expect(mockRoom.getEncryptionTargetMembers).toHaveBeenCalled();
// this should have claimed a key for alice as it's starting a new session
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519', 2000,
);
expect(mockCrypto.downloadKeys).toHaveBeenCalledWith(
['@alice:home.server'], false,
);
expect(mockBaseApis.sendToDevice).toHaveBeenCalled();
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519', 2000,
);
// this should have claimed a key for alice as it's starting a new session
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519', 2000,
);
expect(mockCrypto.downloadKeys).toHaveBeenCalledWith(
['@alice:home.server'], false,
);
expect(mockBaseApis.sendToDevice).toHaveBeenCalled();
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519', 2000,
);
mockBaseApis.claimOneTimeKeys.mockReset();
mockBaseApis.claimOneTimeKeys.mockReset();
const ct2 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some more text",
const ct2 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some more text",
});
// this should *not* have claimed a key as it should be using the same session
expect(mockBaseApis.claimOneTimeKeys).not.toHaveBeenCalled();
// likewise they should show the same session ID
expect(ct2.session_id).toEqual(ct1.session_id);
});
// this should *not* have claimed a key as it should be using the same session
expect(mockBaseApis.claimOneTimeKeys).not.toHaveBeenCalled();
it("re-shares keys to devices it's already sent to", async function() {
const ct1 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some text",
});
// likewise they should show the same session ID
expect(ct2.session_id).toEqual(ct1.session_id);
mockBaseApis.sendToDevice.mockClear();
await megolmEncryption.reshareKeyWithDevice(
olmDevice.deviceCurve25519Key,
ct1.session_id,
'@alice:home.server',
aliceDeviceInfo,
);
expect(mockBaseApis.sendToDevice).toHaveBeenCalled();
});
it("does not re-share keys to devices whose keys have changed", async function() {
const ct1 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some text",
});
aliceDeviceInfo.getIdentityKey = jest.fn().mockReturnValue(
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWI',
);
mockBaseApis.sendToDevice.mockClear();
await megolmEncryption.reshareKeyWithDevice(
olmDevice.deviceCurve25519Key,
ct1.session_id,
'@alice:home.server',
aliceDeviceInfo,
);
expect(mockBaseApis.sendToDevice).not.toHaveBeenCalled();
});
});
});
@@ -365,9 +413,9 @@ describe("MegolmDecryption", function() {
bobClient1.initCrypto(),
bobClient2.initCrypto(),
]);
const aliceDevice = aliceClient.crypto._olmDevice;
const bobDevice1 = bobClient1.crypto._olmDevice;
const bobDevice2 = bobClient2.crypto._olmDevice;
const aliceDevice = aliceClient.crypto.olmDevice;
const bobDevice1 = bobClient1.crypto.olmDevice;
const bobDevice2 = bobClient2.crypto.olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
@@ -404,17 +452,17 @@ describe("MegolmDecryption", function() {
},
};
aliceClient.crypto._deviceList.storeDevicesForUser(
aliceClient.crypto.deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient.crypto._deviceList.downloadKeys = async function(userIds) {
return this._getDevicesFromStore(userIds);
aliceClient.crypto.deviceList.downloadKeys = async function(userIds) {
return this.getDevicesFromStore(userIds);
};
let run = false;
aliceClient.sendToDevice = async (msgtype, contentMap) => {
run = true;
expect(msgtype).toBe("org.matrix.room_key.withheld");
expect(msgtype).toMatch(/^(org.matrix|m).room_key.withheld$/);
delete contentMap["@bob:example.com"].bobdevice1.session_id;
delete contentMap["@bob:example.com"].bobdevice2.session_id;
expect(contentMap).toStrictEqual({
@@ -468,8 +516,8 @@ describe("MegolmDecryption", function() {
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const aliceDevice = aliceClient.crypto._olmDevice;
const bobDevice = bobClient.crypto._olmDevice;
const aliceDevice = aliceClient.crypto.olmDevice;
const bobDevice = bobClient.crypto.olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
@@ -508,11 +556,11 @@ describe("MegolmDecryption", function() {
},
};
aliceClient.crypto._deviceList.storeDevicesForUser(
aliceClient.crypto.deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient.crypto._deviceList.downloadKeys = async function(userIds) {
return this._getDevicesFromStore(userIds);
aliceClient.crypto.deviceList.downloadKeys = async function(userIds) {
return this.getDevicesFromStore(userIds);
};
aliceClient.claimOneTimeKeys = async () => {
@@ -524,7 +572,7 @@ describe("MegolmDecryption", function() {
const sendPromise = new Promise((resolve, reject) => {
aliceClient.sendToDevice = async (msgtype, contentMap) => {
expect(msgtype).toBe("org.matrix.room_key.withheld");
expect(msgtype).toMatch(/^(org.matrix|m).room_key.withheld$/);
expect(contentMap).toStrictEqual({
'@bob:example.com': {
bobdevice: {
@@ -561,17 +609,17 @@ describe("MegolmDecryption", function() {
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const bobDevice = bobClient.crypto._olmDevice;
const bobDevice = bobClient.crypto.olmDevice;
const roomId = "!someroom";
aliceClient.crypto._onToDeviceEvent(new MatrixEvent({
aliceClient.crypto.onToDeviceEvent(new MatrixEvent({
type: "org.matrix.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
session_id: "session_id",
session_id: "session_id1",
sender_key: bobDevice.deviceCurve25519Key,
code: "m.blacklisted",
reason: "You have been blocked",
@@ -588,7 +636,34 @@ describe("MegolmDecryption", function() {
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id",
session_id: "session_id1",
},
}))).rejects.toThrow("The sender has blocked you.");
aliceClient.crypto.onToDeviceEvent(new MatrixEvent({
type: "m.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
session_id: "session_id2",
sender_key: bobDevice.deviceCurve25519Key,
code: "m.blacklisted",
reason: "You have been blocked",
},
}));
await expect(aliceClient.crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
room_id: roomId,
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id2",
},
}))).rejects.toThrow("The sender has blocked you.");
});
@@ -605,19 +680,19 @@ describe("MegolmDecryption", function() {
bobClient.initCrypto(),
]);
aliceClient.crypto.downloadKeys = async () => {};
const bobDevice = bobClient.crypto._olmDevice;
const bobDevice = bobClient.crypto.olmDevice;
const roomId = "!someroom";
const now = Date.now();
aliceClient.crypto._onToDeviceEvent(new MatrixEvent({
aliceClient.crypto.onToDeviceEvent(new MatrixEvent({
type: "org.matrix.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
session_id: "session_id",
session_id: "session_id1",
sender_key: bobDevice.deviceCurve25519Key,
code: "m.no_olm",
reason: "Unable to establish a secure channel.",
@@ -638,7 +713,39 @@ describe("MegolmDecryption", function() {
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id",
session_id: "session_id1",
},
origin_server_ts: now,
}))).rejects.toThrow("The sender was unable to establish a secure channel.");
aliceClient.crypto.onToDeviceEvent(new MatrixEvent({
type: "m.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
session_id: "session_id2",
sender_key: bobDevice.deviceCurve25519Key,
code: "m.no_olm",
reason: "Unable to establish a secure channel.",
},
}));
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
await expect(aliceClient.crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
room_id: roomId,
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id2",
},
origin_server_ts: now,
}))).rejects.toThrow("The sender was unable to establish a secure channel.");
@@ -655,7 +762,7 @@ describe("MegolmDecryption", function() {
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const bobDevice = bobClient.crypto._olmDevice;
const bobDevice = bobClient.crypto.olmDevice;
aliceClient.crypto.downloadKeys = async () => {};
const roomId = "!someroom";
@@ -663,7 +770,7 @@ describe("MegolmDecryption", function() {
const now = Date.now();
// pretend we got an event that we can't decrypt
aliceClient.crypto._onToDeviceEvent(new MatrixEvent({
aliceClient.crypto.onToDeviceEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
content: {
+2 -2
View File
@@ -246,7 +246,7 @@ describe("OlmDevice", function() {
// After a single tick through the first task, it should have
// claimed ownership of all devices to avoid deadlocking others.
expect(Object.keys(aliceOlmDevice._sessionsInProgress).length).toBe(2);
expect(Object.keys(aliceOlmDevice.sessionsInProgress).length).toBe(2);
const task2 = alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
aliceOlmDevice, baseApis, devicesByUserBA,
@@ -254,7 +254,7 @@ describe("OlmDevice", function() {
// The second task should not have changed the ownership count, as
// it's waiting on the first task.
expect(Object.keys(aliceOlmDevice._sessionsInProgress).length).toBe(2);
expect(Object.keys(aliceOlmDevice.sessionsInProgress).length).toBe(2);
// Track the tasks, but don't await them yet.
const promises = Promise.all([
+149 -23
View File
@@ -24,7 +24,7 @@ import * as algorithms from "../../../src/crypto/algorithms";
import { WebStorageSessionStore } from "../../../src/store/session/webstorage";
import { MemoryCryptoStore } from "../../../src/crypto/store/memory-crypto-store";
import { MockStorageApi } from "../../MockStorageApi";
import * as testUtils from "../../test-utils";
import * as testUtils from "../../test-utils/test-utils";
import { OlmDevice } from "../../../src/crypto/OlmDevice";
import { Crypto } from "../../../src/crypto";
import { resetCrossSigningKeys } from "./crypto-utils";
@@ -52,7 +52,7 @@ const ENCRYPTED_EVENT = new MatrixEvent({
origin_server_ts: 1507753886000,
});
const KEY_BACKUP_DATA = {
const CURVE25519_KEY_BACKUP_DATA = {
first_message_index: 0,
forwarded_count: 0,
is_verified: false,
@@ -73,7 +73,26 @@ const KEY_BACKUP_DATA = {
},
};
const BACKUP_INFO = {
const AES256_KEY_BACKUP_DATA = {
first_message_index: 0,
forwarded_count: 0,
is_verified: false,
session_data: {
iv: 'b3Jqqvm5S9QdmXrzssspLQ',
ciphertext: 'GOOASO3E9ThogkG0zMjEduGLM3u9jHZTkS7AvNNbNj3q1znwk4OlaVKXce'
+ '7ynofiiYIiS865VlOqrKEEXv96XzRyUpgn68e3WsicwYl96EtjIEh/iY003PG2Qd'
+ 'EluT899Ax7PydpUHxEktbWckMppYomUR5q8x1KI1SsOQIiJaIGThmIMPANRCFiK0'
+ 'WQj+q+dnhzx4lt9AFqU5bKov8qKnw2qGYP7/+6RmJ0Kpvs8tG6lrcNDEHtFc2r0r'
+ 'KKubDypo0Vc8EWSwsAHdKa36ewRavpreOuE8Z9RLfY0QIR1ecXrMqW0CdGFr7H3P'
+ 'vcjF8sjwvQAavzxEKT1WMGizSMLeKWo2mgZ5cKnwV5HGUAw596JQvKs9laG2U89K'
+ 'YrT0sH30vi62HKzcBLcDkWkUSNYPz7UiZ1MM0L380UA+1ZOXSOmtBA9xxzzbc8Xd'
+ 'fRimVgklGdxrxjzuNLYhL2BvVH4oPWonD9j0bvRwE6XkimdbGQA8HB7UmXXjE8WA'
+ 'RgaDHkfzoA3g3aeQ',
mac: 'uR988UYgGL99jrvLLPX3V1ows+UYbktTmMxPAo2kxnU',
},
};
const CURVE25519_BACKUP_INFO = {
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
version: 1,
auth_data: {
@@ -81,6 +100,14 @@ const BACKUP_INFO = {
},
};
const AES256_BACKUP_INFO = {
algorithm: "org.matrix.msc3270.v1.aes-hmac-sha2",
version: 1,
auth_data: {
// FIXME: add iv and mac
},
};
const keys = {};
function getCrossSigningKey(type) {
@@ -139,12 +166,12 @@ describe("MegolmBackup", function() {
let megolmDecryption;
beforeEach(async function() {
mockCrypto = testUtils.mock(Crypto, 'Crypto');
mockCrypto._backupManager = testUtils.mock(BackupManager, "BackupManager");
mockCrypto.backupManager = testUtils.mock(BackupManager, "BackupManager");
mockCrypto.backupKey = new Olm.PkEncryption();
mockCrypto.backupKey.set_recipient_key(
"hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
);
mockCrypto.backupInfo = BACKUP_INFO;
mockCrypto.backupInfo = CURVE25519_BACKUP_INFO;
mockStorage = new MockStorageApi();
sessionStore = new WebStorageSessionStore(mockStorage);
@@ -217,18 +244,18 @@ describe("MegolmBackup", function() {
};
mockCrypto.cancelRoomKeyRequest = function() {};
mockCrypto._backupManager = {
mockCrypto.backupManager = {
backupGroupSession: jest.fn(),
};
return event.attemptDecryption(mockCrypto).then(() => {
return megolmDecryption.onRoomKeyEvent(event);
}).then(() => {
expect(mockCrypto._backupManager.backupGroupSession).toHaveBeenCalled();
expect(mockCrypto.backupManager.backupGroupSession).toHaveBeenCalled();
});
});
it('sends backups to the server', function() {
it('sends backups to the server (Curve25519 version)', function() {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const ibGroupSession = new Olm.InboundGroupSession();
@@ -261,7 +288,7 @@ describe("MegolmBackup", function() {
ed25519: "SENDER_ED25519",
},
room_id: ROOM_ID,
session: ibGroupSession.pickle(olmDevice._pickleKey),
session: ibGroupSession.pickle(olmDevice.pickleKey),
},
txn);
});
@@ -296,7 +323,89 @@ describe("MegolmBackup", function() {
resolve();
return Promise.resolve({});
};
client.crypto._backupManager.backupGroupSession(
client.crypto.backupManager.backupGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
);
}).then(() => {
expect(numCalls).toBe(1);
});
});
});
it('sends backups to the server (AES-256 version)', function() {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const ibGroupSession = new Olm.InboundGroupSession();
ibGroupSession.create(groupSession.session_key());
const client = makeTestClient(sessionStore, cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
megolmDecryption.olmlib = mockOlmLib;
return client.initCrypto()
.then(() => {
return client.crypto.storeSessionBackupPrivateKey(new Uint8Array(32));
})
.then(() => {
return cryptoStore.doTxn(
"readwrite",
[cryptoStore.STORE_SESSION],
(txn) => {
cryptoStore.addEndToEndInboundGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
{
forwardingCurve25519KeyChain: undefined,
keysClaimed: {
ed25519: "SENDER_ED25519",
},
room_id: ROOM_ID,
session: ibGroupSession.pickle(olmDevice.pickleKey),
},
txn);
});
})
.then(() => {
client.enableKeyBackup({
algorithm: "org.matrix.msc3270.v1.aes-hmac-sha2",
version: 1,
auth_data: {
iv: "PsCAtR7gMc4xBd9YS3A9Ow",
mac: "ZSDsTFEZK7QzlauCLMleUcX96GQZZM7UNtk4sripSqQ",
},
});
let numCalls = 0;
return new Promise((resolve, reject) => {
client.http.authedRequest = function(
callback, method, path, queryParams, data, opts,
) {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(1);
if (numCalls >= 2) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
return Promise.resolve({});
}
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams.version).toBe(1);
expect(data.rooms[ROOM_ID].sessions).toBeDefined();
expect(data.rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
);
resolve();
return Promise.resolve({});
};
client.crypto.backupManager.backupGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
);
@@ -437,7 +546,7 @@ describe("MegolmBackup", function() {
ed25519: "SENDER_ED25519",
},
room_id: ROOM_ID,
session: ibGroupSession.pickle(olmDevice._pickleKey),
session: ibGroupSession.pickle(olmDevice.pickleKey),
},
txn);
});
@@ -478,7 +587,7 @@ describe("MegolmBackup", function() {
);
}
};
client.crypto._backupManager.backupGroupSession(
client.crypto.backupManager.backupGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
);
@@ -512,30 +621,47 @@ describe("MegolmBackup", function() {
client.stopClient();
});
it('can restore from backup', function() {
it('can restore from backup (Curve25519 version)', function() {
client.http.authedRequest = function() {
return Promise.resolve(KEY_BACKUP_DATA);
return Promise.resolve(CURVE25519_KEY_BACKUP_DATA);
};
return client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
ROOM_ID,
SESSION_ID,
BACKUP_INFO,
CURVE25519_BACKUP_INFO,
).then(() => {
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
}).then((res) => {
expect(res.clearEvent.content).toEqual('testytest');
expect(res.untrusted).toBeTruthy(); // keys from backup are untrusted
expect(res.untrusted).toBeTruthy(); // keys from Curve25519 backup are untrusted
});
});
it('can restore backup by room', function() {
it('can restore from backup (AES-256 version)', function() {
client.http.authedRequest = function() {
return Promise.resolve(AES256_KEY_BACKUP_DATA);
};
return client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
ROOM_ID,
SESSION_ID,
AES256_BACKUP_INFO,
).then(() => {
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
}).then((res) => {
expect(res.clearEvent.content).toEqual('testytest');
expect(res.untrusted).toBeFalsy(); // keys from AES backup are trusted
});
});
it('can restore backup by room (Curve25519 version)', function() {
client.http.authedRequest = function() {
return Promise.resolve({
rooms: {
[ROOM_ID]: {
sessions: {
[SESSION_ID]: KEY_BACKUP_DATA,
[SESSION_ID]: CURVE25519_KEY_BACKUP_DATA,
},
},
},
@@ -543,7 +669,7 @@ describe("MegolmBackup", function() {
};
return client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
null, null, BACKUP_INFO,
null, null, CURVE25519_BACKUP_INFO,
).then(() => {
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
}).then((res) => {
@@ -562,14 +688,14 @@ describe("MegolmBackup", function() {
const cachedNull = await client.crypto.getSessionBackupPrivateKey();
expect(cachedNull).toBeNull();
client.http.authedRequest = function() {
return Promise.resolve(KEY_BACKUP_DATA);
return Promise.resolve(CURVE25519_KEY_BACKUP_DATA);
};
await new Promise((resolve) => {
client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
ROOM_ID,
SESSION_ID,
BACKUP_INFO,
CURVE25519_BACKUP_INFO,
{ cacheCompleteCallback: resolve },
);
});
@@ -578,11 +704,11 @@ describe("MegolmBackup", function() {
});
it("fails if an known algorithm is used", async function() {
const BAD_BACKUP_INFO = Object.assign({}, BACKUP_INFO, {
const BAD_BACKUP_INFO = Object.assign({}, CURVE25519_BACKUP_INFO, {
algorithm: "this.algorithm.does.not.exist",
});
client.http.authedRequest = function() {
return Promise.resolve(KEY_BACKUP_DATA);
return Promise.resolve(CURVE25519_KEY_BACKUP_DATA);
};
await expect(client.restoreKeyBackupWithRecoveryKey(
+77 -52
View File
@@ -17,13 +17,36 @@ limitations under the License.
import '../../olm-loader';
import anotherjson from 'another-json';
import * as olmlib from "../../../src/crypto/olmlib";
import { TestClient } from '../../TestClient';
import { HttpResponse, setHttpResponses } from '../../test-utils';
import { resetCrossSigningKeys } from "./crypto-utils";
import { MatrixError } from '../../../src/http-api';
import { logger } from '../../../src/logger';
const PUSH_RULES_RESPONSE = {
method: "GET",
path: "/pushrules/",
data: {},
};
const filterResponse = function(userId) {
const filterPath = "/user/" + encodeURIComponent(userId) + "/filter";
return {
method: "POST",
path: filterPath,
data: { filter_id: "f1lt3r" },
};
};
function setHttpResponses(httpBackend, responses) {
responses.forEach(response => {
httpBackend
.when(response.method, response.path)
.respond(200, response.data);
});
}
async function makeTestClient(userInfo, options, keys) {
if (!keys) keys = {};
@@ -39,13 +62,14 @@ async function makeTestClient(userInfo, options, keys) {
options.cryptoCallbacks = Object.assign(
{}, { getCrossSigningKey, saveCrossSigningKeys }, options.cryptoCallbacks || {},
);
const client = (new TestClient(
const testClient = new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
)).client;
);
const client = testClient.client;
await client.initCrypto();
return client;
return { client, httpBackend: testClient.httpBackend };
}
describe("Cross Signing", function() {
@@ -59,13 +83,13 @@ describe("Cross Signing", function() {
});
it("should sign the master key with the device key", async function() {
const alice = await makeTestClient(
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = jest.fn(async (auth, keys) => {
await olmlib.verifySignature(
alice.crypto._olmDevice, keys.master_key, "@alice:example.com",
"Osborne2", alice.crypto._olmDevice.deviceEd25519Key,
alice.crypto.olmDevice, keys.master_key, "@alice:example.com",
"Osborne2", alice.crypto.olmDevice.deviceEd25519Key,
);
});
alice.uploadKeySignatures = async () => {};
@@ -79,7 +103,7 @@ describe("Cross Signing", function() {
});
it("should abort bootstrap if device signing auth fails", async function() {
const alice = await makeTestClient(
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = async (auth, keys) => {
@@ -130,7 +154,7 @@ describe("Cross Signing", function() {
});
it("should upload a signature when a user is verified", async function() {
const alice = await makeTestClient(
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = async () => {};
@@ -138,7 +162,7 @@ describe("Cross Signing", function() {
// set Alice's cross-signing key
await resetCrossSigningKeys(alice);
// Alice downloads Bob's device key
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@@ -160,7 +184,7 @@ describe("Cross Signing", function() {
await promise;
});
it("should get cross-signing keys from sync", async function() {
it.skip("should get cross-signing keys from sync", async function() {
const masterKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
@@ -174,7 +198,7 @@ describe("Cross Signing", function() {
0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
]);
const alice = await makeTestClient(
const { client: alice, httpBackend } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
@@ -203,12 +227,12 @@ describe("Cross Signing", function() {
alice.uploadKeySignatures = jest.fn(async (content) => {
try {
await olmlib.verifySignature(
alice.crypto._olmDevice,
alice.crypto.olmDevice,
content["@alice:example.com"][
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
],
"@alice:example.com",
"Osborne2", alice.crypto._olmDevice.deviceEd25519Key,
"Osborne2", alice.crypto.olmDevice.deviceEd25519Key,
);
olmlib.pkVerify(
content["@alice:example.com"]["Osborne2"],
@@ -222,7 +246,7 @@ describe("Cross Signing", function() {
});
});
const deviceInfo = alice.crypto._deviceList._devices["@alice:example.com"]
const deviceInfo = alice.crypto.deviceList.devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
@@ -230,12 +254,12 @@ describe("Cross Signing", function() {
};
aliceDevice.keys = deviceInfo.keys;
aliceDevice.algorithms = deviceInfo.algorithms;
await alice.crypto._signObject(aliceDevice);
await alice.crypto.signObject(aliceDevice);
olmlib.pkSign(aliceDevice, selfSigningKey, "@alice:example.com");
// feed sync result that includes master key, ssk, device key
const responses = [
HttpResponse.PUSH_RULES_RESPONSE,
PUSH_RULES_RESPONSE,
{
method: "POST",
path: "/keys/upload",
@@ -246,7 +270,7 @@ describe("Cross Signing", function() {
},
},
},
HttpResponse.filterResponse("@alice:example.com"),
filterResponse("@alice:example.com"),
{
method: "GET",
path: "/sync",
@@ -310,9 +334,10 @@ describe("Cross Signing", function() {
},
},
];
setHttpResponses(alice, responses, true, true);
setHttpResponses(httpBackend, responses);
await alice.startClient();
alice.startClient();
httpBackend.flushAllExpected();
// once ssk is confirmed, device key should be trusted
await keyChangePromise;
@@ -331,7 +356,7 @@ describe("Cross Signing", function() {
});
it("should use trust chain to determine device verification", async function() {
const alice = await makeTestClient(
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = async () => {};
@@ -358,7 +383,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@@ -387,7 +412,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobPubkey]: sig,
},
};
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device key should be TOFU
@@ -414,15 +439,15 @@ describe("Cross Signing", function() {
expect(bobDeviceTrust2.isTofu()).toBeTruthy();
});
it("should trust signatures received from other devices", async function() {
it.skip("should trust signatures received from other devices", async function() {
const aliceKeys = {};
const alice = await makeTestClient(
const { client: alice, httpBackend } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
null,
aliceKeys,
);
alice.crypto._deviceList.startTrackingDeviceList("@bob:example.com");
alice.crypto._deviceList.stopTrackingAllDeviceLists = () => {};
alice.crypto.deviceList.startTrackingDeviceList("@bob:example.com");
alice.crypto.deviceList.stopTrackingAllDeviceLists = () => {};
alice.uploadDeviceSigningKeys = async () => {};
alice.uploadKeySignatures = async () => {};
@@ -437,14 +462,14 @@ describe("Cross Signing", function() {
]);
const keyChangePromise = new Promise((resolve, reject) => {
alice.crypto._deviceList.once("userCrossSigningUpdated", (userId) => {
alice.crypto.deviceList.once("userCrossSigningUpdated", (userId) => {
if (userId === "@bob:example.com") {
resolve();
}
});
});
const deviceInfo = alice.crypto._deviceList._devices["@alice:example.com"]
const deviceInfo = alice.crypto.deviceList.devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
@@ -452,7 +477,7 @@ describe("Cross Signing", function() {
};
aliceDevice.keys = deviceInfo.keys;
aliceDevice.algorithms = deviceInfo.algorithms;
await alice.crypto._signObject(aliceDevice);
await alice.crypto.signObject(aliceDevice);
const bobOlmAccount = new global.Olm.Account();
bobOlmAccount.create();
@@ -490,7 +515,7 @@ describe("Cross Signing", function() {
// - master key signed by her usk (pretend that it was signed by another
// of Alice's devices)
const responses = [
HttpResponse.PUSH_RULES_RESPONSE,
PUSH_RULES_RESPONSE,
{
method: "POST",
path: "/keys/upload",
@@ -501,7 +526,7 @@ describe("Cross Signing", function() {
},
},
},
HttpResponse.filterResponse("@alice:example.com"),
filterResponse("@alice:example.com"),
{
method: "GET",
path: "/sync",
@@ -560,10 +585,10 @@ describe("Cross Signing", function() {
},
},
];
setHttpResponses(alice, responses);
await alice.startClient();
setHttpResponses(httpBackend, responses);
alice.startClient();
httpBackend.flushAllExpected();
await keyChangePromise;
// Bob's device key should be trusted
@@ -578,7 +603,7 @@ describe("Cross Signing", function() {
});
it("should dis-trust an unsigned device", async function() {
const alice = await makeTestClient(
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = async () => {};
@@ -606,7 +631,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@@ -629,7 +654,7 @@ describe("Cross Signing", function() {
"ed25519:Dynabook": "someOtherPubkey",
},
};
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device key should be untrusted
@@ -647,7 +672,7 @@ describe("Cross Signing", function() {
});
it("should dis-trust a user when their ssk changes", async function() {
const alice = await makeTestClient(
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = async () => {};
@@ -673,7 +698,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@@ -701,7 +726,7 @@ describe("Cross Signing", function() {
bobDevice.signatures = {};
bobDevice.signatures["@bob:example.com"] = {};
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey] = sig;
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Alice verifies Bob's SSK
@@ -733,7 +758,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey2]: sskSig2,
},
};
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@@ -770,7 +795,7 @@ describe("Cross Signing", function() {
// Alice gets new signature for device
const sig2 = bobSigning2.sign(bobDeviceString);
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey2] = sig2;
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
@@ -785,7 +810,7 @@ describe("Cross Signing", function() {
it("should offer to upgrade device verifications to cross-signing", async function() {
let upgradeResolveFunc;
const alice = await makeTestClient(
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
@@ -797,7 +822,7 @@ describe("Cross Signing", function() {
},
},
);
const bob = await makeTestClient(
const { client: bob } = await makeTestClient(
{ userId: "@bob:example.com", deviceId: "Dynabook" },
);
@@ -805,20 +830,20 @@ describe("Cross Signing", function() {
bob.uploadKeySignatures = async () => {};
// set Bob's cross-signing key
await resetCrossSigningKeys(bob);
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: {
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": bob.crypto._olmDevice.deviceCurve25519Key,
"ed25519:Dynabook": bob.crypto._olmDevice.deviceEd25519Key,
"curve25519:Dynabook": bob.crypto.olmDevice.deviceCurve25519Key,
"ed25519:Dynabook": bob.crypto.olmDevice.deviceEd25519Key,
},
verified: 1,
known: true,
},
});
alice.crypto._deviceList.storeCrossSigningForUser(
alice.crypto.deviceList.storeCrossSigningForUser(
"@bob:example.com",
bob.crypto._crossSigningInfo.toStorage(),
bob.crypto.crossSigningInfo.toStorage(),
);
alice.uploadDeviceSigningKeys = async () => {};
@@ -838,7 +863,7 @@ describe("Cross Signing", function() {
expect(bobTrust.isTofu()).toBeTruthy();
// "forget" that Bob is trusted
delete alice.crypto._deviceList._crossSigningInfo["@bob:example.com"]
delete alice.crypto.deviceList.crossSigningInfo["@bob:example.com"]
.keys.master.signatures["@alice:example.com"];
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
@@ -848,7 +873,7 @@ describe("Cross Signing", function() {
upgradePromise = new Promise((resolve) => {
upgradeResolveFunc = resolve;
});
alice.crypto._deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
alice.crypto.deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
await new Promise((resolve) => {
alice.crypto.on("userTrustStatusChanged", resolve);
});
+9 -9
View File
@@ -8,26 +8,26 @@ export async function resetCrossSigningKeys(client, {
} = {}) {
const crypto = client.crypto;
const oldKeys = Object.assign({}, crypto._crossSigningInfo.keys);
const oldKeys = Object.assign({}, crypto.crossSigningInfo.keys);
try {
await crypto._crossSigningInfo.resetKeys(level);
await crypto._signObject(crypto._crossSigningInfo.keys.master);
await crypto.crossSigningInfo.resetKeys(level);
await crypto.signObject(crypto.crossSigningInfo.keys.master);
// write a copy locally so we know these are trusted keys
await crypto._cryptoStore.doTxn(
await crypto.cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
crypto._cryptoStore.storeCrossSigningKeys(
txn, crypto._crossSigningInfo.keys);
crypto.cryptoStore.storeCrossSigningKeys(
txn, crypto.crossSigningInfo.keys);
},
);
} catch (e) {
// If anything failed here, revert the keys so we know to try again from the start
// next time.
crypto._crossSigningInfo.keys = oldKeys;
crypto.crossSigningInfo.keys = oldKeys;
throw e;
}
crypto._baseApis.emit("crossSigning.keysChanged", {});
await crypto._afterCrossSigningLocalKeyChange();
crypto.emit("crossSigning.keysChanged", {});
await crypto.afterCrossSigningLocalKeyChange();
}
export async function createSecretStorageKey() {
@@ -18,28 +18,26 @@ import {
IndexedDBCryptoStore,
} from '../../../src/crypto/store/indexeddb-crypto-store';
import { MemoryCryptoStore } from '../../../src/crypto/store/memory-crypto-store';
import { RoomKeyRequestState } from '../../../src/crypto/OutgoingRoomKeyRequestManager';
import 'fake-indexeddb/auto';
import 'jest-localstorage-mock';
import {
ROOM_KEY_REQUEST_STATES,
} from '../../../src/crypto/OutgoingRoomKeyRequestManager';
const requests = [
{
requestId: "A",
requestBody: { session_id: "A", room_id: "A" },
state: ROOM_KEY_REQUEST_STATES.SENT,
state: RoomKeyRequestState.Sent,
},
{
requestId: "B",
requestBody: { session_id: "B", room_id: "B" },
state: ROOM_KEY_REQUEST_STATES.SENT,
state: RoomKeyRequestState.Sent,
},
{
requestId: "C",
requestBody: { session_id: "C", room_id: "C" },
state: ROOM_KEY_REQUEST_STATES.UNSENT,
state: RoomKeyRequestState.Unsent,
},
];
@@ -68,9 +66,9 @@ describe.each([
it("getAllOutgoingRoomKeyRequestsByState retrieves all entries in a given state",
async () => {
const r = await
store.getAllOutgoingRoomKeyRequestsByState(ROOM_KEY_REQUEST_STATES.SENT);
store.getAllOutgoingRoomKeyRequestsByState(RoomKeyRequestState.Sent);
expect(r).toHaveLength(2);
requests.filter((e) => e.state == ROOM_KEY_REQUEST_STATES.SENT).forEach((e) => {
requests.filter((e) => e.state === RoomKeyRequestState.Sent).forEach((e) => {
expect(r).toContainEqual(e);
});
});
@@ -78,10 +76,10 @@ describe.each([
test("getOutgoingRoomKeyRequestByState retrieves any entry in a given state",
async () => {
const r =
await store.getOutgoingRoomKeyRequestByState([ROOM_KEY_REQUEST_STATES.SENT]);
await store.getOutgoingRoomKeyRequestByState([RoomKeyRequestState.Sent]);
expect(r).not.toBeNull();
expect(r).not.toBeUndefined();
expect(r.state).toEqual(ROOM_KEY_REQUEST_STATES.SENT);
expect(r.state).toEqual(RoomKeyRequestState.Sent);
expect(requests).toContainEqual(r);
});
});
+17 -18
View File
@@ -23,7 +23,6 @@ import { makeTestClients } from './verification/util';
import { encryptAES } from "../../../src/crypto/aes";
import { resetCrossSigningKeys, createSecretStorageKey } from "./crypto-utils";
import { logger } from '../../../src/logger';
import * as utils from "../../../src/utils";
try {
@@ -99,11 +98,11 @@ describe("Secrets", function() {
},
},
);
alice.crypto._crossSigningInfo.setKeys({
alice.crypto.crossSigningInfo.setKeys({
master: signingkeyInfo,
});
const secretStorage = alice.crypto._secretStorage;
const secretStorage = alice.crypto.secretStorage;
alice.setAccountData = async function(eventType, contents, callback) {
alice.store.storeAccountDataEvents([
@@ -120,7 +119,7 @@ describe("Secrets", function() {
const keyAccountData = {
algorithm: SECRET_STORAGE_ALGORITHM_V1_AES,
};
await alice.crypto._crossSigningInfo.signObject(keyAccountData, 'master');
await alice.crypto.crossSigningInfo.signObject(keyAccountData, 'master');
alice.store.storeAccountDataEvents([
new MatrixEvent({
@@ -234,11 +233,11 @@ describe("Secrets", function() {
},
);
const vaxDevice = vax.client.crypto._olmDevice;
const osborne2Device = osborne2.client.crypto._olmDevice;
const secretStorage = osborne2.client.crypto._secretStorage;
const vaxDevice = vax.client.crypto.olmDevice;
const osborne2Device = osborne2.client.crypto.olmDevice;
const secretStorage = osborne2.client.crypto.secretStorage;
osborne2.client.crypto._deviceList.storeDevicesForUser("@alice:example.com", {
osborne2.client.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
"VAX": {
user_id: "@alice:example.com",
device_id: "VAX",
@@ -249,7 +248,7 @@ describe("Secrets", function() {
},
},
});
vax.client.crypto._deviceList.storeDevicesForUser("@alice:example.com", {
vax.client.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
"Osborne2": {
user_id: "@alice:example.com",
device_id: "Osborne2",
@@ -265,7 +264,7 @@ describe("Secrets", function() {
const otks = (await osborne2Device.getOneTimeKeys()).curve25519;
await osborne2Device.markKeysAsPublished();
await vax.client.crypto._olmDevice.createOutboundSession(
await vax.client.crypto.olmDevice.createOutboundSession(
osborne2Device.deviceCurve25519Key,
Object.values(otks)[0],
);
@@ -334,8 +333,8 @@ describe("Secrets", function() {
createSecretStorageKey,
});
const crossSigning = bob.crypto._crossSigningInfo;
const secretStorage = bob.crypto._secretStorage;
const crossSigning = bob.crypto.crossSigningInfo;
const secretStorage = bob.crypto.secretStorage;
expect(crossSigning.getId()).toBeTruthy();
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
@@ -376,10 +375,10 @@ describe("Secrets", function() {
]);
this.emit("accountData", event);
};
bob.crypto._backupManager.checkKeyBackup = async () => {};
bob.crypto.backupManager.checkKeyBackup = async () => {};
const crossSigning = bob.crypto._crossSigningInfo;
const secretStorage = bob.crypto._secretStorage;
const crossSigning = bob.crypto.crossSigningInfo;
const secretStorage = bob.crypto.secretStorage;
// Set up cross-signing keys from scratch with specific storage key
await bob.bootstrapCrossSigning({
@@ -394,7 +393,7 @@ describe("Secrets", function() {
});
// Clear local cross-signing keys and read from secret storage
bob.crypto._deviceList.storeCrossSigningForUser(
bob.crypto.deviceList.storeCrossSigningForUser(
"@bob:example.com",
crossSigning.toStorage(),
);
@@ -479,7 +478,7 @@ describe("Secrets", function() {
},
}),
]);
alice.crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: {
master: {
user_id: "@alice:example.com",
@@ -619,7 +618,7 @@ describe("Secrets", function() {
},
}),
]);
alice.crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: {
master: {
user_id: "@alice:example.com",
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { InRoomChannel } from "../../../../src/crypto/verification/request/InRoomChannel";
"../../../../src/crypto/verification/request/ToDeviceChannel";
import { MatrixEvent } from "../../../../src/models/event";
"../../../../src/crypto/verification/request/ToDeviceChannel";
describe("InRoomChannel tests", function() {
const ALICE = "@alice:hs.tld";
@@ -49,7 +49,7 @@ describe("verification request integration tests with crypto layer", function()
verificationMethods: [verificationMethods.SAS],
},
);
alice.client.crypto._deviceList.getRawStoredDevicesForUser = function() {
alice.client.crypto.deviceList.getRawStoredDevicesForUser = function() {
return {
Dynabook: {
keys: {
@@ -69,7 +69,7 @@ describe("verification request integration tests with crypto layer", function()
bobVerifier.verify();
// XXX: Private function access (but it's a test, so we're okay)
bobVerifier._endTimer();
bobVerifier.endTimer();
});
const aliceRequest = await alice.client.requestVerification("@bob:example.com");
await aliceRequest.waitFor(r => r.started);
@@ -77,6 +77,6 @@ describe("verification request integration tests with crypto layer", function()
expect(aliceVerifier).toBeInstanceOf(SAS);
// XXX: Private function access (but it's a test, so we're okay)
aliceVerifier._endTimer();
aliceVerifier.endTimer();
});
});
+6 -6
View File
@@ -87,8 +87,8 @@ describe("SAS verification", function() {
},
);
const aliceDevice = alice.client.crypto._olmDevice;
const bobDevice = bob.client.crypto._olmDevice;
const aliceDevice = alice.client.crypto.olmDevice;
const bobDevice = bob.client.crypto.olmDevice;
ALICE_DEVICES = {
Osborne2: {
@@ -114,14 +114,14 @@ describe("SAS verification", function() {
},
};
alice.client.crypto._deviceList.storeDevicesForUser(
alice.client.crypto.deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
alice.client.downloadKeys = () => {
return Promise.resolve();
};
bob.client.crypto._deviceList.storeDevicesForUser(
bob.client.crypto.deviceList.storeDevicesForUser(
"@alice:example.com", ALICE_DEVICES,
);
bob.client.downloadKeys = () => {
@@ -296,9 +296,9 @@ describe("SAS verification", function() {
await resetCrossSigningKeys(bob.client);
bob.client.crypto._deviceList.storeCrossSigningForUser(
bob.client.crypto.deviceList.storeCrossSigningForUser(
"@alice:example.com", {
keys: alice.client.crypto._crossSigningInfo.keys,
keys: alice.client.crypto.crossSigningInfo.keys,
},
);
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { VerificationBase } from '../../../../src/crypto/verification/Base';
import { CrossSigningInfo } from '../../../../src/crypto/CrossSigning';
import { encodeBase64 } from "../../../../src/crypto/olmlib";
import { setupWebcrypto, teardownWebcrypto } from './util';
import { VerificationBase } from '../../../../src/crypto/verification/Base';
jest.useFakeTimers();
@@ -48,18 +48,18 @@ describe("self-verifications", () => {
storeCrossSigningKeyCache: jest.fn(),
};
const _crossSigningInfo = new CrossSigningInfo(
const crossSigningInfo = new CrossSigningInfo(
userId,
{},
cacheCallbacks,
);
_crossSigningInfo.keys = {
crossSigningInfo.keys = {
master: { keys: { X: testKeyPub } },
self_signing: { keys: { X: testKeyPub } },
user_signing: { keys: { X: testKeyPub } },
};
const _secretStorage = {
const secretStorage = {
request: jest.fn().mockReturnValue({
promise: Promise.resolve(encodeBase64(testKey)),
}),
@@ -70,12 +70,12 @@ describe("self-verifications", () => {
const client = {
crypto: {
_crossSigningInfo,
_secretStorage,
crossSigningInfo,
secretStorage,
storeSessionBackupPrivateKey,
getSessionBackupPrivateKey: () => null,
},
requestSecret: _secretStorage.request.bind(_secretStorage),
requestSecret: secretStorage.request.bind(secretStorage),
getUserId: () => userId,
getKeyBackupVersion: () => Promise.resolve({}),
restoreKeyBackupWithCache,
@@ -93,13 +93,13 @@ describe("self-verifications", () => {
undefined, // startEvent
request,
);
verification._resolve = () => undefined;
verification.resolve = () => undefined;
const result = await verification.done();
/* We should request, and store, 3 cross signing keys and the key backup key */
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls.length).toBe(3);
expect(_secretStorage.request.mock.calls.length).toBe(4);
expect(secretStorage.request.mock.calls.length).toBe(4);
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls[0][1])
.toEqual(testKey);
+2 -1
View File
@@ -15,9 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import nodeCrypto from "crypto";
import { TestClient } from '../../../TestClient';
import { MatrixEvent } from "../../../../src/models/event";
import nodeCrypto from "crypto";
import { logger } from '../../../../src/logger';
export async function makeTestClients(userInfos, options) {
+180
View File
@@ -0,0 +1,180 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient, MatrixEvent, MatrixEventEvent, MatrixScheduler, Room } from "../../src";
import { eventMapperFor } from "../../src/event-mapper";
import { IStore } from "../../src/store";
describe("eventMapperFor", function() {
let rooms: Room[] = [];
const userId = "@test:example.org";
let client: MatrixClient;
beforeEach(() => {
client = new MatrixClient({
baseUrl: "https://my.home.server",
accessToken: "my.access.token",
request: function() {} as any, // NOP
store: {
getRoom(roomId: string): Room | null {
return rooms.find(r => r.roomId === roomId);
},
} as IStore,
scheduler: {
setProcessFunction: jest.fn(),
} as unknown as MatrixScheduler,
userId: userId,
});
rooms = [];
});
it("should de-duplicate MatrixEvent instances by means of findEventById on the room object", async () => {
const roomId = "!room:example.org";
const room = new Room(roomId, client, userId);
rooms.push(room);
const mapper = eventMapperFor(client, {
preventReEmit: true,
decrypt: false,
});
const eventId = "$event1:server";
const eventDefinition = {
type: "m.room.message",
room_id: roomId,
sender: userId,
content: {
body: "body",
},
unsigned: {},
event_id: eventId,
};
const event = mapper(eventDefinition);
expect(event).toBeInstanceOf(MatrixEvent);
room.addLiveEvents([event]);
expect(room.findEventById(eventId)).toBe(event);
const event2 = mapper(eventDefinition);
expect(event).toBe(event2);
});
it("should not de-duplicate state events due to directionality of sentinel members", async () => {
const roomId = "!room:example.org";
const room = new Room(roomId, client, userId);
rooms.push(room);
const mapper = eventMapperFor(client, {
preventReEmit: true,
decrypt: false,
});
const eventId = "$event1:server";
const eventDefinition = {
type: "m.room.name",
room_id: roomId,
sender: userId,
content: {
name: "Room name",
},
unsigned: {},
event_id: eventId,
state_key: "",
};
const event = mapper(eventDefinition);
expect(event).toBeInstanceOf(MatrixEvent);
room.oldState.setStateEvents([event]);
room.currentState.setStateEvents([event]);
room.addLiveEvents([event]);
expect(room.findEventById(eventId)).toBe(event);
const event2 = mapper(eventDefinition);
expect(event).not.toBe(event2);
});
it("should decrypt appropriately", async () => {
const roomId = "!room:example.org";
const room = new Room(roomId, client, userId);
rooms.push(room);
const eventId = "$event1:server";
const eventDefinition = {
type: "m.room.encrypted",
room_id: roomId,
sender: userId,
content: {
ciphertext: "",
},
unsigned: {},
event_id: eventId,
};
const decryptEventIfNeededSpy = jest.spyOn(client, "decryptEventIfNeeded");
decryptEventIfNeededSpy.mockResolvedValue(); // stub it out
const mapper = eventMapperFor(client, {
decrypt: true,
});
const event = mapper(eventDefinition);
expect(event).toBeInstanceOf(MatrixEvent);
expect(decryptEventIfNeededSpy).toHaveBeenCalledWith(event);
});
it("should configure re-emitter appropriately", async () => {
const roomId = "!room:example.org";
const room = new Room(roomId, client, userId);
rooms.push(room);
const eventId = "$event1:server";
const eventDefinition = {
type: "m.room.message",
room_id: roomId,
sender: userId,
content: {
body: "body",
},
unsigned: {},
event_id: eventId,
};
const evListener = jest.fn();
client.on(MatrixEventEvent.Replaced, evListener);
const noReEmitMapper = eventMapperFor(client, {
preventReEmit: true,
});
const event1 = noReEmitMapper(eventDefinition);
expect(event1).toBeInstanceOf(MatrixEvent);
event1.emit(MatrixEventEvent.Replaced, event1);
expect(evListener).not.toHaveBeenCalled();
const reEmitMapper = eventMapperFor(client, {
preventReEmit: false,
});
const event2 = reEmitMapper(eventDefinition);
expect(event2).toBeInstanceOf(MatrixEvent);
event2.emit(MatrixEventEvent.Replaced, event2);
expect(evListener.mock.calls[0][0]).toEqual(event2);
expect(event1).not.toBe(event2); // the event wasn't added to a room so de-duplication wouldn't occur
});
});
+5 -5
View File
@@ -1,10 +1,10 @@
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
import { EventTimeline } from "../../src/models/event-timeline";
import { RoomState } from "../../src/models/room-state";
function mockRoomStates(timeline) {
timeline._startState = utils.mock(RoomState, "startState");
timeline._endState = utils.mock(RoomState, "endState");
timeline.startState = utils.mock(RoomState, "startState");
timeline.endState = utils.mock(RoomState, "endState");
}
describe("EventTimeline", function() {
@@ -48,10 +48,10 @@ describe("EventTimeline", function() {
}),
];
timeline.initialiseState(events);
expect(timeline._startState.setStateEvents).toHaveBeenCalledWith(
expect(timeline.startState.setStateEvents).toHaveBeenCalledWith(
events,
);
expect(timeline._endState.setStateEvents).toHaveBeenCalledWith(
expect(timeline.endState.setStateEvents).toHaveBeenCalledWith(
events,
);
});
+1 -1
View File
@@ -1,6 +1,6 @@
/*
Copyright 2017 New Vector Ltd
Copyright 2019 The Matrix.org Foundaction C.I.C.
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
-34
View File
@@ -1,34 +0,0 @@
import { FilterComponent } from "../../src/filter-component";
import { mkEvent } from '../test-utils';
describe("Filter Component", function() {
describe("types", function() {
it("should filter out events with other types", function() {
const filter = new FilterComponent({ types: ['m.room.message'] });
const event = mkEvent({
type: 'm.room.member',
content: { },
room: 'roomId',
event: true,
});
const checkResult = filter.check(event);
expect(checkResult).toBe(false);
});
it("should validate events with the same type", function() {
const filter = new FilterComponent({ types: ['m.room.message'] });
const event = mkEvent({
type: 'm.room.message',
content: { },
room: 'roomId',
event: true,
});
const checkResult = filter.check(event);
expect(checkResult).toBe(true);
});
});
});
+170
View File
@@ -0,0 +1,170 @@
import {
MatrixEvent,
RelationType,
} from "../../src";
import { FilterComponent } from "../../src/filter-component";
import { mkEvent } from '../test-utils/test-utils';
describe("Filter Component", function() {
describe("types", function() {
it("should filter out events with other types", function() {
const filter = new FilterComponent({ types: ['m.room.message'] });
const event = mkEvent({
type: 'm.room.member',
content: { },
room: 'roomId',
event: true,
}) as MatrixEvent;
const checkResult = filter.check(event);
expect(checkResult).toBe(false);
});
it("should validate events with the same type", function() {
const filter = new FilterComponent({ types: ['m.room.message'] });
const event = mkEvent({
type: 'm.room.message',
content: { },
room: 'roomId',
event: true,
}) as MatrixEvent;
const checkResult = filter.check(event);
expect(checkResult).toBe(true);
});
it("should filter out events by relation participation", function() {
const currentUserId = '@me:server.org';
const filter = new FilterComponent({
related_by_senders: [currentUserId],
}, currentUserId);
const threadRootNotParticipated = mkEvent({
type: 'm.room.message',
content: {},
room: 'roomId',
user: '@someone-else:server.org',
event: true,
unsigned: {
"m.relations": {
"m.thread": {
count: 2,
current_user_participated: false,
},
},
},
}) as MatrixEvent;
expect(filter.check(threadRootNotParticipated)).toBe(false);
});
it("should keep events by relation participation", function() {
const currentUserId = '@me:server.org';
const filter = new FilterComponent({
related_by_senders: [currentUserId],
}, currentUserId);
const threadRootParticipated = mkEvent({
type: 'm.room.message',
content: {},
unsigned: {
"m.relations": {
"m.thread": {
count: 2,
current_user_participated: true,
},
},
},
user: '@someone-else:server.org',
room: 'roomId',
event: true,
}) as MatrixEvent;
expect(filter.check(threadRootParticipated)).toBe(true);
});
it("should filter out events by relation type", function() {
const filter = new FilterComponent({
related_by_rel_types: ["m.thread"],
});
const referenceRelationEvent = mkEvent({
type: 'm.room.message',
content: {},
room: 'roomId',
event: true,
unsigned: {
"m.relations": {
[RelationType.Reference]: {},
},
},
}) as MatrixEvent;
expect(filter.check(referenceRelationEvent)).toBe(false);
});
it("should keep events by relation type", function() {
const filter = new FilterComponent({
related_by_rel_types: ["m.thread"],
});
const threadRootEvent = mkEvent({
type: 'm.room.message',
content: {},
unsigned: {
"m.relations": {
"m.thread": {
count: 2,
current_user_participated: true,
},
},
},
room: 'roomId',
event: true,
}) as MatrixEvent;
const eventWithMultipleRelations = mkEvent({
"type": "m.room.message",
"content": {},
"unsigned": {
"m.relations": {
"testtesttest": {},
"m.annotation": {
"chunk": [
{
"type": "m.reaction",
"key": "🤫",
"count": 1,
},
],
},
"m.thread": {
count: 2,
current_user_participated: true,
},
},
},
"room": 'roomId',
"event": true,
}) as MatrixEvent;
const noMatchEvent = mkEvent({
"type": "m.room.message",
"content": {},
"unsigned": {
"m.relations": {
"testtesttest": {},
},
},
"room": 'roomId',
"event": true,
}) as MatrixEvent;
expect(filter.check(threadRootEvent)).toBe(true);
expect(filter.check(eventWithMultipleRelations)).toBe(true);
expect(filter.check(noMatchEvent)).toBe(false);
});
});
});
+111
View File
@@ -0,0 +1,111 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { makeLocationContent, parseLocationEvent } from "../../src/content-helpers";
import {
M_ASSET,
LocationAssetType,
M_LOCATION,
M_TIMESTAMP,
LocationEventWireContent,
} from "../../src/@types/location";
import { TEXT_NODE_TYPE } from "../../src/@types/extensible_events";
import { MsgType } from "../../src/@types/event";
describe("Location", function() {
const defaultContent = {
"body": "Location geo:-36.24484561954707,175.46884959563613;u=10 at 2022-03-09T11:01:52.443Z",
"msgtype": "m.location",
"geo_uri": "geo:-36.24484561954707,175.46884959563613;u=10",
[M_LOCATION.name]: { "uri": "geo:-36.24484561954707,175.46884959563613;u=10", "description": null },
[M_ASSET.name]: { "type": "m.self" },
[TEXT_NODE_TYPE.name]: "Location geo:-36.24484561954707,175.46884959563613;u=10 at 2022-03-09T11:01:52.443Z",
[M_TIMESTAMP.name]: 1646823712443,
} as any;
const backwardsCompatibleEventContent = { ...defaultContent };
// eslint-disable-next-line camelcase
const { body, msgtype, geo_uri, ...modernProperties } = defaultContent;
const modernEventContent = { ...modernProperties };
const legacyEventContent = {
// eslint-disable-next-line camelcase
body, msgtype, geo_uri,
} as LocationEventWireContent;
it("should create a valid location with defaults", function() {
const loc = makeLocationContent(undefined, "geo:foo", 134235435);
expect(loc.body).toEqual('User Location geo:foo at 1970-01-02T13:17:15.435Z');
expect(loc.msgtype).toEqual(MsgType.Location);
expect(loc.geo_uri).toEqual("geo:foo");
expect(M_LOCATION.findIn(loc)).toEqual({
uri: "geo:foo",
description: undefined,
});
expect(M_ASSET.findIn(loc)).toEqual({ type: LocationAssetType.Self });
expect(TEXT_NODE_TYPE.findIn(loc)).toEqual('User Location geo:foo at 1970-01-02T13:17:15.435Z');
expect(M_TIMESTAMP.findIn(loc)).toEqual(134235435);
});
it("should create a valid location with explicit properties", function() {
const loc = makeLocationContent(
undefined, "geo:bar", 134235436, "desc", LocationAssetType.Pin);
expect(loc.body).toEqual('Location "desc" geo:bar at 1970-01-02T13:17:15.436Z');
expect(loc.msgtype).toEqual(MsgType.Location);
expect(loc.geo_uri).toEqual("geo:bar");
expect(M_LOCATION.findIn(loc)).toEqual({
uri: "geo:bar",
description: "desc",
});
expect(M_ASSET.findIn(loc)).toEqual({ type: LocationAssetType.Pin });
expect(TEXT_NODE_TYPE.findIn(loc)).toEqual('Location "desc" geo:bar at 1970-01-02T13:17:15.436Z');
expect(M_TIMESTAMP.findIn(loc)).toEqual(134235436);
});
it('parses backwards compatible event correctly', () => {
const eventContent = parseLocationEvent(backwardsCompatibleEventContent);
expect(eventContent).toEqual(backwardsCompatibleEventContent);
});
it('parses modern correctly', () => {
const eventContent = parseLocationEvent(modernEventContent);
expect(eventContent).toEqual(backwardsCompatibleEventContent);
});
it('parses legacy event correctly', () => {
const eventContent = parseLocationEvent(legacyEventContent);
const {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[M_TIMESTAMP.name]: timestamp,
...expectedResult
} = defaultContent;
expect(eventContent).toEqual({
...expectedResult,
[M_LOCATION.name]: {
...expectedResult[M_LOCATION.name],
description: undefined,
},
});
// don't infer timestamp from legacy event
expect(M_TIMESTAMP.findIn(eventContent)).toBeFalsy();
});
});
@@ -1,3 +1,19 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from "../../src/logger";
import { MatrixClient } from "../../src/client";
import { Filter } from "../../src/filter";
@@ -11,8 +27,12 @@ import {
UNSTABLE_MSC3089_TREE_SUBTYPE,
} from "../../src/@types/event";
import { MEGOLM_ALGORITHM } from "../../src/crypto/olmlib";
import { MatrixEvent } from "../../src/models/event";
import { EventStatus, MatrixEvent } from "../../src/models/event";
import { Preset } from "../../src/@types/partials";
import * as testUtils from "../test-utils/test-utils";
import { makeBeaconInfoContent } from "../../src/content-helpers";
import { M_BEACON_INFO } from "../../src/@types/beacon";
import { Room } from "../../src";
jest.useFakeTimers();
@@ -84,11 +104,7 @@ describe("MatrixClient", function() {
return pendingLookup.promise;
}
// >1 pending thing, and they are different, whine.
expect(false).toBe(
true, ">1 pending request. You should probably handle them. " +
"PENDING: " + JSON.stringify(pendingLookup) + " JUST GOT: " +
method + " " + path,
);
expect(false).toBe(true);
}
pendingLookup = {
promise: new Promise(() => {}),
@@ -116,6 +132,7 @@ describe("MatrixClient", function() {
}
if (next.error) {
// eslint-disable-next-line
return Promise.reject({
errcode: next.error.errcode,
httpStatus: next.error.httpStatus,
@@ -126,7 +143,7 @@ describe("MatrixClient", function() {
}
return Promise.resolve(next.data);
}
expect(true).toBe(false, "Expected different request. " + logLine);
expect(true).toBe(false);
return new Promise(() => {});
}
@@ -151,7 +168,7 @@ describe("MatrixClient", function() {
baseUrl: "https://my.home.server",
idBaseUrl: identityServerUrl,
accessToken: "my.access.token",
request: function() {}, // NOP
request: function() {} as any, // NOP
store: store,
scheduler: scheduler,
userId: userId,
@@ -237,6 +254,7 @@ describe("MatrixClient", function() {
it("should get (unstable) file trees with valid state", async () => {
const roomId = "!room:example.org";
const mockRoom = {
getMyMembership: () => "join",
currentState: {
getStateEvents: (eventType, stateKey) => {
if (eventType === EventType.RoomCreate) {
@@ -270,9 +288,33 @@ describe("MatrixClient", function() {
expect(tree.room).toBe(mockRoom);
});
it("should not get (unstable) file trees if not joined", async () => {
const roomId = "!room:example.org";
const mockRoom = {
getMyMembership: () => "leave", // "not join"
};
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return mockRoom;
};
const tree = client.unstableGetFileTreeSpace(roomId);
expect(tree).toBeFalsy();
});
it("should not get (unstable) file trees for unknown rooms", async () => {
const roomId = "!room:example.org";
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return null; // imply unknown
};
const tree = client.unstableGetFileTreeSpace(roomId);
expect(tree).toBeFalsy();
});
it("should not get (unstable) file trees with invalid create contents", async () => {
const roomId = "!room:example.org";
const mockRoom = {
getMyMembership: () => "join",
currentState: {
getStateEvents: (eventType, stateKey) => {
if (eventType === EventType.RoomCreate) {
@@ -307,6 +349,7 @@ describe("MatrixClient", function() {
it("should not get (unstable) file trees with invalid purpose/subtype contents", async () => {
const roomId = "!room:example.org";
const mockRoom = {
getMyMembership: () => "join",
currentState: {
getStateEvents: (eventType, stateKey) => {
if (eventType === EventType.RoomCreate) {
@@ -338,15 +381,16 @@ describe("MatrixClient", function() {
});
it("should not POST /filter if a matching filter already exists", async function() {
httpLookups = [];
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push(SYNC_RESPONSE);
httpLookups = [
PUSH_RULES_RESPONSE,
SYNC_RESPONSE,
];
const filterId = "ehfewf";
store.getFilterIdByName.mockReturnValue(filterId);
const filter = new Filter(0, filterId);
const filter = new Filter("0", filterId);
filter.setDefinition({ "room": { "timeline": { "limit": 8 } } });
store.getFilter.mockReturnValue(filter);
const syncPromise = new Promise((resolve, reject) => {
const syncPromise = new Promise<void>((resolve, reject) => {
client.on("sync", function syncListener(state) {
if (state === "SYNCING") {
expect(httpLookups.length).toEqual(0);
@@ -367,7 +411,7 @@ describe("MatrixClient", function() {
});
it("should return the same sync state as emitted sync events", async function() {
const syncingPromise = new Promise((resolve) => {
const syncingPromise = new Promise<void>((resolve) => {
client.on("sync", function syncListener(state) {
expect(state).toEqual(client.getSyncState());
if (state === "SYNCING") {
@@ -387,7 +431,7 @@ describe("MatrixClient", function() {
it("should use an existing filter if id is present in localStorage", function() {
});
it("should handle localStorage filterId missing from the server", function(done) {
function getFilterName(userId, suffix) {
function getFilterName(userId, suffix?: string) {
// scope this on the user ID because people may login on many accounts
// and they all need to be stored!
return "FILTER_SYNC_" + userId + (suffix ? "_" + suffix : "");
@@ -421,6 +465,7 @@ describe("MatrixClient", function() {
describe("retryImmediately", function() {
it("should return false if there is no request waiting", async function() {
httpLookups = [];
await client.startClient();
expect(client.retryImmediately()).toBe(false);
});
@@ -462,7 +507,7 @@ describe("MatrixClient", function() {
if (state === "ERROR" && httpLookups.length > 0) {
expect(httpLookups.length).toEqual(1);
expect(client.retryImmediately()).toBe(
true, "retryImmediately returned false",
true,
);
jest.advanceTimersByTime(1);
} else if (state === "RECONNECTING" && httpLookups.length > 0) {
@@ -542,33 +587,36 @@ describe("MatrixClient", function() {
client.startClient();
});
it("should transition ERROR -> CATCHUP after /sync if prev failed",
function(done) {
const expectedStates = [];
acceptKeepalives = false;
httpLookups = [];
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push(FILTER_RESPONSE);
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH,
error: { errcode: "KEEPALIVE_FAIL" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH, data: {},
});
httpLookups.push({
method: "GET", path: "/sync", data: SYNC_DATA,
});
// Disabled because now `startClient` makes a legit call to `/versions`
// And those tests are really unhappy about it... Not possible to figure
// out what a good resolution would look like
xit("should transition ERROR -> CATCHUP after /sync if prev failed",
function(done) {
const expectedStates = [];
acceptKeepalives = false;
httpLookups = [];
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push(FILTER_RESPONSE);
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH,
error: { errcode: "KEEPALIVE_FAIL" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH, data: {},
});
httpLookups.push({
method: "GET", path: "/sync", data: SYNC_DATA,
});
expectedStates.push(["RECONNECTING", null]);
expectedStates.push(["ERROR", "RECONNECTING"]);
expectedStates.push(["CATCHUP", "ERROR"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
expectedStates.push(["RECONNECTING", null]);
expectedStates.push(["ERROR", "RECONNECTING"]);
expectedStates.push(["CATCHUP", "ERROR"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
it("should transition PREPARED -> SYNCING after /sync", function(done) {
const expectedStates = [];
@@ -578,7 +626,7 @@ describe("MatrixClient", function() {
client.startClient();
});
it("should transition SYNCING -> ERROR after a failed /sync", function(done) {
xit("should transition SYNCING -> ERROR after a failed /sync", function(done) {
acceptKeepalives = false;
const expectedStates = [];
httpLookups.push({
@@ -598,34 +646,34 @@ describe("MatrixClient", function() {
});
xit("should transition ERROR -> SYNCING after /sync if prev failed",
function(done) {
const expectedStates = [];
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
});
httpLookups.push(SYNC_RESPONSE);
function(done) {
const expectedStates = [];
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
});
httpLookups.push(SYNC_RESPONSE);
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
expectedStates.push(["ERROR", "SYNCING"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
expectedStates.push(["ERROR", "SYNCING"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
it("should transition SYNCING -> SYNCING on subsequent /sync successes",
function(done) {
const expectedStates = [];
httpLookups.push(SYNC_RESPONSE);
httpLookups.push(SYNC_RESPONSE);
function(done) {
const expectedStates = [];
httpLookups.push(SYNC_RESPONSE);
httpLookups.push(SYNC_RESPONSE);
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
expectedStates.push(["SYNCING", "SYNCING"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
expectedStates.push(["SYNCING", "SYNCING"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
it("should transition ERROR -> ERROR if keepalive keeps failing", function(done) {
xit("should transition ERROR -> ERROR if keepalive keeps failing", function(done) {
acceptKeepalives = false;
const expectedStates = [];
httpLookups.push({
@@ -671,7 +719,7 @@ describe("MatrixClient", function() {
describe("guest rooms", function() {
it("should only do /sync calls (without filter/pushrules)", function(done) {
httpLookups = []; // no /pushrules or /filter
httpLookups = []; // no /pushrules or /filterw
httpLookups.push({
method: "GET",
path: "/sync",
@@ -702,4 +750,282 @@ describe("MatrixClient", function() {
expect(httpLookups.length).toEqual(0);
});
});
describe("sendEvent", () => {
const roomId = "!room:example.org";
const body = "This is the body";
const content = { body };
it("overload without threadId works", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
httpLookups = [{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`,
data: { event_id: eventId },
expectBody: content,
}];
await client.sendEvent(roomId, EventType.RoomMessage, content, txnId);
});
it("overload with null threadId works", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
httpLookups = [{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`,
data: { event_id: eventId },
expectBody: content,
}];
await client.sendEvent(roomId, null, EventType.RoomMessage, content, txnId);
});
it("overload with threadId works", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
httpLookups = [{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`,
data: { event_id: eventId },
expectBody: content,
}];
await client.sendEvent(roomId, "$threadId:server", EventType.RoomMessage, content, txnId);
});
});
describe("redactEvent", () => {
const roomId = "!room:example.org";
const mockRoom = {
getMyMembership: () => "join",
currentState: {
getStateEvents: (eventType, stateKey) => {
if (eventType === EventType.RoomEncryption) {
expect(stateKey).toEqual("");
return new MatrixEvent({ content: {} });
} else {
throw new Error("Unexpected event type or state key");
}
},
},
threads: {
get: jest.fn(),
},
addPendingEvent: jest.fn(),
updatePendingEvent: jest.fn(),
};
beforeEach(() => {
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return mockRoom;
};
});
it("overload without threadId works", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
httpLookups = [{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`,
data: { event_id: eventId },
}];
await client.redactEvent(roomId, eventId, txnId);
});
it("overload with null threadId works", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
httpLookups = [{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`,
data: { event_id: eventId },
}];
await client.redactEvent(roomId, null, eventId, txnId);
});
it("overload with threadId works", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
httpLookups = [{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`,
data: { event_id: eventId },
}];
await client.redactEvent(roomId, "$threadId:server", eventId, txnId);
});
it("does not get wrongly encrypted", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
const reason = "This is the redaction reason";
httpLookups = [{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`,
expectBody: { reason }, // NOT ENCRYPTED
data: { event_id: eventId },
}];
await client.redactEvent(roomId, eventId, txnId, { reason });
});
});
describe("cancelPendingEvent", () => {
const roomId = "!room:server";
const txnId = "m12345";
const mockRoom = {
getMyMembership: () => "join",
updatePendingEvent: (event, status) => event.setStatus(status),
currentState: {
getStateEvents: (eventType, stateKey) => {
if (eventType === EventType.RoomCreate) {
expect(stateKey).toEqual("");
return new MatrixEvent({
content: {
[RoomCreateTypeField]: RoomType.Space,
},
});
} else if (eventType === EventType.RoomEncryption) {
expect(stateKey).toEqual("");
return new MatrixEvent({ content: {} });
} else {
throw new Error("Unexpected event type or state key");
}
},
},
};
let event;
beforeEach(async () => {
event = new MatrixEvent({
event_id: "~" + roomId + ":" + txnId,
user_id: client.credentials.userId,
sender: client.credentials.userId,
room_id: roomId,
origin_server_ts: new Date().getTime(),
});
event.setTxnId(txnId);
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return mockRoom;
};
client.crypto = { // mock crypto
encryptEvent: (event, room) => new Promise(() => {}),
};
});
function assertCancelled() {
expect(event.status).toBe(EventStatus.CANCELLED);
expect(client.scheduler.removeEventFromQueue(event)).toBeFalsy();
expect(httpLookups.filter(h => h.path.includes("/send/")).length).toBe(0);
}
it("should cancel an event which is queued", () => {
event.setStatus(EventStatus.QUEUED);
client.scheduler.queueEvent(event);
client.cancelPendingEvent(event);
assertCancelled();
});
it("should cancel an event which is encrypting", async () => {
client.encryptAndSendEvent(null, event);
await testUtils.emitPromise(event, "Event.status");
client.cancelPendingEvent(event);
assertCancelled();
});
it("should cancel an event which is not sent", () => {
event.setStatus(EventStatus.NOT_SENT);
client.cancelPendingEvent(event);
assertCancelled();
});
it("should error when given any other event status", () => {
event.setStatus(EventStatus.SENDING);
expect(() => client.cancelPendingEvent(event)).toThrow("cannot cancel an event with status sending");
expect(event.status).toBe(EventStatus.SENDING);
});
});
describe("threads", () => {
it("partitions root events to room timeline and thread timeline", () => {
const supportsExperimentalThreads = client.supportsExperimentalThreads;
client.supportsExperimentalThreads = () => true;
const room = new Room("!room1:matrix.org", client, userId);
const rootEvent = new MatrixEvent({
"content": {},
"origin_server_ts": 1,
"room_id": "!room1:matrix.org",
"sender": "@alice:matrix.org",
"type": "m.room.message",
"unsigned": {
"m.relations": {
"m.thread": {
"latest_event": {},
"count": 33,
"current_user_participated": false,
},
},
},
"event_id": "$ev1",
"user_id": "@alice:matrix.org",
});
expect(rootEvent.isThreadRoot).toBe(true);
const [roomEvents, threadEvents] = client.partitionThreadedEvents(room, [rootEvent]);
expect(roomEvents).toHaveLength(1);
expect(threadEvents).toHaveLength(1);
// Restore method
client.supportsExperimentalThreads = supportsExperimentalThreads;
});
});
describe("beacons", () => {
const roomId = '!room:server.org';
const content = makeBeaconInfoContent(100, true);
beforeEach(() => {
client.http.authedRequest.mockClear().mockResolvedValue({});
});
it("creates new beacon info", async () => {
await client.unstable_createLiveBeacon(roomId, content, '123');
// event type combined
const expectedEventType = `${M_BEACON_INFO.name}.${userId}.123`;
const [callback, method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
expect(callback).toBeFalsy();
expect(method).toBe('PUT');
expect(path).toEqual(
`/rooms/${encodeURIComponent(roomId)}/state/` +
`${encodeURIComponent(expectedEventType)}/${encodeURIComponent(userId)}`,
);
expect(queryParams).toBeFalsy();
expect(requestContent).toEqual(content);
});
it("updates beacon info with specific event type", async () => {
const eventType = `${M_BEACON_INFO.name}.${userId}.456`;
await client.unstable_setLiveBeacon(roomId, eventType, content);
// event type combined
const [, , path, , requestContent] = client.http.authedRequest.mock.calls[0];
expect(path).toEqual(
`/rooms/${encodeURIComponent(roomId)}/state/` +
`${encodeURIComponent(eventType)}/${encodeURIComponent(userId)}`,
);
expect(requestContent).toEqual(content);
});
});
});
+194 -18
View File
@@ -14,22 +14,25 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient } from "../../../src";
import { IContent, MatrixClient, MatrixEvent } from "../../../src";
import { Room } from "../../../src/models/room";
import { MatrixEvent } from "../../../src/models/event";
import { UNSTABLE_MSC3089_BRANCH } from "../../../src/@types/event";
import { IEncryptedFile, RelationType, UNSTABLE_MSC3089_BRANCH } from "../../../src/@types/event";
import { EventTimelineSet } from "../../../src/models/event-timeline-set";
import { EventTimeline } from "../../../src/models/event-timeline";
import { MSC3089Branch } from "../../../src/models/MSC3089Branch";
import { MSC3089TreeSpace } from "../../../src/models/MSC3089TreeSpace";
describe("MSC3089Branch", () => {
let client: MatrixClient;
// @ts-ignore - TS doesn't know that this is a type
let indexEvent: MatrixEvent;
let indexEvent: any;
let directory: MSC3089TreeSpace;
let branch: MSC3089Branch;
let branch2: MSC3089Branch;
const branchRoomId = "!room:example.org";
const fileEventId = "$file";
const fileEventId2 = "$second_file";
const staticTimelineSets = {} as EventTimelineSet;
const staticRoom = {
@@ -47,11 +50,16 @@ describe("MSC3089Branch", () => {
}
},
};
indexEvent = {
indexEvent = ({
getRoomId: () => branchRoomId,
getStateKey: () => fileEventId,
};
branch = new MSC3089Branch(client, indexEvent);
});
directory = new MSC3089TreeSpace(client, branchRoomId);
branch = new MSC3089Branch(client, indexEvent, directory);
branch2 = new MSC3089Branch(client, {
getRoomId: () => branchRoomId,
getStateKey: () => fileEventId2,
} as MatrixEvent, directory);
});
it('should know the file event ID', () => {
@@ -70,13 +78,15 @@ describe("MSC3089Branch", () => {
});
it('should be able to delete the file', async () => {
const eventIdOrder = [fileEventId, fileEventId2];
const stateFn = jest.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(branchRoomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test that we're definitely using the unstable value
expect(content).toMatchObject({});
expect(content['active']).toBeUndefined();
expect(stateKey).toEqual(fileEventId);
expect(stateKey).toEqual(eventIdOrder[stateFn.mock.calls.length - 1]);
return Promise.resolve(); // return value not used
});
@@ -84,16 +94,19 @@ describe("MSC3089Branch", () => {
const redactFn = jest.fn().mockImplementation((roomId: string, eventId: string) => {
expect(roomId).toEqual(branchRoomId);
expect(eventId).toEqual(fileEventId);
expect(eventId).toEqual(eventIdOrder[stateFn.mock.calls.length - 1]);
return Promise.resolve(); // return value not used
});
client.redactEvent = redactFn;
branch.getVersionHistory = () => Promise.resolve([branch, branch2]);
branch2.getVersionHistory = () => Promise.resolve([branch2]);
await branch.delete();
expect(stateFn).toHaveBeenCalledTimes(1);
expect(redactFn).toHaveBeenCalledTimes(1);
expect(stateFn).toHaveBeenCalledTimes(2);
expect(redactFn).toHaveBeenCalledTimes(2);
});
it('should know its name', async () => {
@@ -128,16 +141,71 @@ describe("MSC3089Branch", () => {
expect(stateFn).toHaveBeenCalledTimes(1);
});
it('should be v1 by default', () => {
indexEvent.getContent = () => ({ active: true });
const res = branch.version;
expect(res).toEqual(1);
});
it('should be vN when set', () => {
indexEvent.getContent = () => ({ active: true, version: 3 });
const res = branch.version;
expect(res).toEqual(3);
});
it('should be unlocked by default', async () => {
indexEvent.getContent = () => ({ active: true });
const res = branch.isLocked();
expect(res).toEqual(false);
});
it('should use lock status from index event', async () => {
indexEvent.getContent = () => ({ active: true, locked: true });
const res = branch.isLocked();
expect(res).toEqual(true);
});
it('should be able to change its locked status', async () => {
const locked = true;
indexEvent.getContent = () => ({ active: true, retained: true });
const stateFn = jest.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(branchRoomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test that we're definitely using the unstable value
expect(content).toMatchObject({
retained: true, // canary for copying state
active: true,
locked: locked,
});
expect(stateKey).toEqual(fileEventId);
return Promise.resolve(); // return value not used
});
client.sendStateEvent = stateFn;
await branch.setLocked(locked);
expect(stateFn).toHaveBeenCalledTimes(1);
});
it('should be able to return event information', async () => {
const mxcLatter = "example.org/file";
const fileContent = { isFile: "not quite", url: "mxc://" + mxcLatter };
const eventsArr = [
{ getId: () => "$not-file", getContent: () => ({}) },
{ getId: () => fileEventId, getContent: () => ({ file: fileContent }) },
];
client.getEventTimeline = () => Promise.resolve({
getEvents: () => eventsArr,
}) as any as Promise<EventTimeline>; // partial
const fileEvent = { getId: () => fileEventId, getOriginalContent: () => ({ file: fileContent }) };
staticRoom.getUnfilteredTimelineSet = () => ({
findEventById: (eventId) => {
expect(eventId).toEqual(fileEventId);
return fileEvent;
},
}) as EventTimelineSet;
client.mxcUrlToHttp = (mxc: string) => {
expect(mxc).toEqual("mxc://" + mxcLatter);
return `https://example.org/_matrix/media/v1/download/${mxcLatter}`;
@@ -152,4 +220,112 @@ describe("MSC3089Branch", () => {
httpUrl: expect.stringMatching(`.+${mxcLatter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`),
});
});
it('should be able to return the event object', async () => {
const mxcLatter = "example.org/file";
const fileContent = { isFile: "not quite", url: "mxc://" + mxcLatter };
const fileEvent = { getId: () => fileEventId, getOriginalContent: () => ({ file: fileContent }) };
staticRoom.getUnfilteredTimelineSet = () => ({
findEventById: (eventId) => {
expect(eventId).toEqual(fileEventId);
return fileEvent;
},
}) as EventTimelineSet;
client.mxcUrlToHttp = (mxc: string) => {
expect(mxc).toEqual("mxc://" + mxcLatter);
return `https://example.org/_matrix/media/v1/download/${mxcLatter}`;
};
client.decryptEventIfNeeded = () => Promise.resolve();
const res = await branch.getFileEvent();
expect(res).toBeDefined();
expect(res).toBe(fileEvent);
});
it('should create new versions of itself', async () => {
const canaryName = "canary";
const canaryContents = "contents go here";
const canaryFile = {} as IEncryptedFile;
const canaryAddl = { canary: true };
indexEvent.getContent = () => ({ active: true, retained: true });
const stateKeyOrder = [fileEventId2, fileEventId];
const stateFn = jest.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(branchRoomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test that we're definitely using the unstable value
expect(stateKey).toEqual(stateKeyOrder[stateFn.mock.calls.length - 1]);
if (stateKey === fileEventId) {
expect(content).toMatchObject({
retained: true, // canary for copying state
active: false,
});
} else if (stateKey === fileEventId2) {
expect(content).toMatchObject({
active: true,
version: 2,
name: canaryName,
});
} else {
throw new Error("Unexpected state key: " + stateKey);
}
return Promise.resolve(); // return value not used
});
client.sendStateEvent = stateFn;
const createFn = jest.fn().mockImplementation((
name: string,
contents: ArrayBuffer,
info: Partial<IEncryptedFile>,
addl: IContent,
) => {
expect(name).toEqual(canaryName);
expect(contents).toBe(canaryContents);
expect(info).toBe(canaryFile);
expect(addl).toMatchObject({
...canaryAddl,
"m.new_content": true,
"m.relates_to": {
"rel_type": RelationType.Replace,
"event_id": fileEventId,
},
});
return Promise.resolve({ event_id: fileEventId2 });
});
directory.createFile = createFn;
await branch.createNewVersion(canaryName, canaryContents, canaryFile, canaryAddl);
expect(stateFn).toHaveBeenCalledTimes(2);
expect(createFn).toHaveBeenCalledTimes(1);
});
it('should fetch file history', async () => {
branch2.getFileEvent = () => Promise.resolve({
replacingEventId: () => undefined,
getId: () => fileEventId2,
} as MatrixEvent);
branch.getFileEvent = () => Promise.resolve({
replacingEventId: () => fileEventId2,
getId: () => fileEventId,
} as MatrixEvent);
const events = [await branch.getFileEvent(), await branch2.getFileEvent(), {
replacingEventId: (): string => null,
getId: () => "$unknown",
}];
staticRoom.getLiveTimeline = () => ({ getEvents: () => events }) as EventTimeline;
directory.getFile = (evId: string) => {
expect(evId).toEqual(fileEventId);
return branch;
};
const results = await branch2.getVersionHistory();
expect(results).toMatchObject([
branch2,
branch,
]);
});
});
+232 -23
View File
@@ -24,11 +24,11 @@ import {
TreePermissions,
} from "../../../src/models/MSC3089TreeSpace";
import { DEFAULT_ALPHABET } from "../../../src/utils";
import { MockBlob } from "../../MockBlob";
import { MatrixError } from "../../../src/http-api";
describe("MSC3089TreeSpace", () => {
let client: MatrixClient;
let room: Room;
let room: any;
let tree: MSC3089TreeSpace;
const roomId = "!tree:localhost";
const targetUser = "@target:example.org";
@@ -93,7 +93,7 @@ describe("MSC3089TreeSpace", () => {
});
client.sendStateEvent = fn;
await tree.setName(newName);
expect(fn.mock.calls.length).toBe(1);
expect(fn).toHaveBeenCalledTimes(1);
});
it('should support inviting users to the space', async () => {
@@ -104,8 +104,113 @@ describe("MSC3089TreeSpace", () => {
return Promise.resolve();
});
client.invite = fn;
await tree.invite(target);
expect(fn.mock.calls.length).toBe(1);
await tree.invite(target, false, false);
expect(fn).toHaveBeenCalledTimes(1);
});
it('should retry invites to the space', async () => {
const target = targetUser;
const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
expect(inviteRoomId).toEqual(roomId);
expect(userId).toEqual(target);
if (fn.mock.calls.length === 1) return Promise.reject(new Error("Sample Failure"));
return Promise.resolve();
});
client.invite = fn;
await tree.invite(target, false, false);
expect(fn).toHaveBeenCalledTimes(2);
});
it('should not retry invite permission errors', async () => {
const target = targetUser;
const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
expect(inviteRoomId).toEqual(roomId);
expect(userId).toEqual(target);
return Promise.reject(new MatrixError({ errcode: "M_FORBIDDEN", error: "Sample Failure" }));
});
client.invite = fn;
try {
await tree.invite(target, false, false);
// noinspection ExceptionCaughtLocallyJS
throw new Error("Failed to fail");
} catch (e) {
expect(e.errcode).toEqual("M_FORBIDDEN");
}
expect(fn).toHaveBeenCalledTimes(1);
});
it('should invite to subspaces', async () => {
const target = targetUser;
const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
expect(inviteRoomId).toEqual(roomId);
expect(userId).toEqual(target);
return Promise.resolve();
});
client.invite = fn;
tree.getDirectories = () => [
// Bare minimum overrides. We proxy to our mock function manually so we can
// count the calls, not to ensure accuracy. The invite function behaving correctly
// is covered by another test.
{ invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
{ invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
{ invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace,
];
await tree.invite(target, true, false);
expect(fn).toHaveBeenCalledTimes(4);
});
it('should share keys with invitees', async () => {
const target = targetUser;
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
expect(inviteRoomId).toEqual(roomId);
expect(userIds).toMatchObject([target]);
return Promise.resolve();
});
client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
client.sendSharedHistoryKeys = sendKeysFn;
// Mock the history check as best as possible
const historyVis = "shared";
const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
// We're not expecting a super rigid test: the function that calls this internally isn't
// really being tested here.
expect(eventType).toEqual(EventType.RoomHistoryVisibility);
expect(stateKey).toEqual("");
return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
});
room.currentState.getStateEvents = historyFn;
// Note: inverse test is implicit from other tests, which disable the call stack of this
// test in order to pass.
await tree.invite(target, false, true);
expect(sendKeysFn).toHaveBeenCalledTimes(1);
expect(historyFn).toHaveBeenCalledTimes(1);
});
it('should not share keys with invitees if inappropriate history visibility', async () => {
const target = targetUser;
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
expect(inviteRoomId).toEqual(roomId);
expect(userIds).toMatchObject([target]);
return Promise.resolve();
});
client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests
client.sendSharedHistoryKeys = sendKeysFn;
const historyVis = "joined"; // NOTE: Changed.
const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => {
expect(eventType).toEqual(EventType.RoomHistoryVisibility);
expect(stateKey).toEqual("");
return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase
});
room.currentState.getStateEvents = historyFn;
await tree.invite(target, false, true);
expect(sendKeysFn).toHaveBeenCalledTimes(0);
expect(historyFn).toHaveBeenCalledTimes(1);
});
async function evaluatePowerLevels(pls: any, role: TreePermissions, expectedPl: number) {
@@ -121,40 +226,61 @@ describe("MSC3089TreeSpace", () => {
[targetUser]: expectedPl,
},
});
// Store new power levels so the `getPermissions()` test passes
makePowerLevels(content);
return Promise.resolve();
});
client.sendStateEvent = fn;
await tree.setPermissions(targetUser, role);
expect(fn.mock.calls.length).toBe(1);
const finalPermissions = tree.getPermissions(targetUser);
expect(finalPermissions).toEqual(role);
}
it('should support setting Viewer permissions', () => {
return evaluatePowerLevels({
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
users_default: 1024,
events_default: 1025,
events: {
[EventType.RoomPowerLevels]: 1026,
},
}, TreePermissions.Viewer, 1024);
});
it('should support setting Editor permissions', () => {
return evaluatePowerLevels({
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
events_default: 1024,
}, TreePermissions.Editor, 1024);
users_default: 1024,
events_default: 1025,
events: {
[EventType.RoomPowerLevels]: 1026,
},
}, TreePermissions.Editor, 1025);
});
it('should support setting Owner permissions', () => {
return evaluatePowerLevels({
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
users_default: 1024,
events_default: 1025,
events: {
[EventType.RoomPowerLevels]: 1024,
[EventType.RoomPowerLevels]: 1026,
},
}, TreePermissions.Owner, 1024);
}, TreePermissions.Owner, 1026);
});
it('should support demoting permissions', () => {
return evaluatePowerLevels({
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
users_default: 1024,
events_default: 1025,
events: {
[EventType.RoomPowerLevels]: 1026,
},
users: {
[targetUser]: 2222,
},
@@ -164,11 +290,15 @@ describe("MSC3089TreeSpace", () => {
it('should support promoting permissions', () => {
return evaluatePowerLevels({
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
events_default: 1024,
users_default: 1024,
events_default: 1025,
events: {
[EventType.RoomPowerLevels]: 1026,
},
users: {
[targetUser]: 5,
},
}, TreePermissions.Editor, 1024);
}, TreePermissions.Editor, 1025);
});
it('should support defaults: Viewer', () => {
@@ -340,9 +470,9 @@ describe("MSC3089TreeSpace", () => {
// Danger: these are partial implementations for testing purposes only
// @ts-ignore - "MatrixEvent is a value but used as a type", which is true but not important
let childState: { [roomId: string]: MatrixEvent[] } = {};
let childState: { [roomId: string]: any[] } = {};
// @ts-ignore - "MatrixEvent is a value but used as a type", which is true but not important
let parentState: MatrixEvent[] = [];
let parentState: any[] = [];
let parentRoom: Room;
let childTrees: MSC3089TreeSpace[];
let rooms: { [roomId: string]: Room };
@@ -756,12 +886,8 @@ describe("MSC3089TreeSpace", () => {
const fileName = "My File.txt";
const fileContents = "This is a test file";
// Mock out Blob for the test environment
(<any>global).Blob = MockBlob;
const uploadFn = jest.fn().mockImplementation((contents: Blob, opts: any) => {
expect(contents).toBeInstanceOf(Blob);
expect(contents.size).toEqual(fileContents.length);
const uploadFn = jest.fn().mockImplementation((contents: Buffer, opts: any) => {
expect(contents.length).toEqual(fileContents.length);
expect(opts).toMatchObject({
includeFilename: false,
onlyContentUri: true, // because the tests rely on this - we shouldn't really be testing for this.
@@ -777,6 +903,7 @@ describe("MSC3089TreeSpace", () => {
body: fileName,
url: mxc,
file: fileInfo,
metadata: true, // additional content from test
[UNSTABLE_MSC3089_LEAF.unstable]: {}, // test to ensure we're definitely using unstable
});
@@ -794,14 +921,78 @@ describe("MSC3089TreeSpace", () => {
name: fileName,
});
return Promise.resolve(); // return value not used.
return Promise.resolve({ event_id: "wrong" }); // return value shouldn't be used
});
client.sendStateEvent = sendStateFn;
const buf = Uint8Array.from(Array.from(fileContents).map((_, i) => fileContents.charCodeAt(i)));
const buf = Buffer.from(fileContents);
// We clone the file info just to make sure it doesn't get mutated for the test.
await tree.createFile(fileName, buf, Object.assign({}, fileInfo));
const result = await tree.createFile(fileName, buf, Object.assign({}, fileInfo), { metadata: true });
expect(result).toMatchObject({ event_id: fileEventId });
expect(uploadFn).toHaveBeenCalledTimes(1);
expect(sendMsgFn).toHaveBeenCalledTimes(1);
expect(sendStateFn).toHaveBeenCalledTimes(1);
});
it('should upload file versions', async () => {
const mxc = "mxc://example.org/file";
const fileInfo = {
mimetype: "text/plain",
// other fields as required by encryption, but ignored here
};
const fileEventId = "$file";
const fileName = "My File.txt";
const fileContents = "This is a test file";
const uploadFn = jest.fn().mockImplementation((contents: Buffer, opts: any) => {
expect(contents.length).toEqual(fileContents.length);
expect(opts).toMatchObject({
includeFilename: false,
onlyContentUri: true, // because the tests rely on this - we shouldn't really be testing for this.
});
return Promise.resolve(mxc);
});
client.uploadContent = uploadFn;
const sendMsgFn = jest.fn().mockImplementation((roomId: string, contents: any) => {
expect(roomId).toEqual(tree.roomId);
const content = {
msgtype: MsgType.File,
body: fileName,
url: mxc,
file: fileInfo,
};
expect(contents).toMatchObject({
...content,
"m.new_content": content,
[UNSTABLE_MSC3089_LEAF.unstable]: {}, // test to ensure we're definitely using unstable
});
return Promise.resolve({ event_id: fileEventId }); // eslint-disable-line camelcase
});
client.sendMessage = sendMsgFn;
const sendStateFn = jest.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(tree.roomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
expect(stateKey).toEqual(fileEventId);
expect(content).toMatchObject({
active: true,
name: fileName,
});
return Promise.resolve({ event_id: "wrong" }); // return value shouldn't be used
});
client.sendStateEvent = sendStateFn;
const buf = Buffer.from(fileContents);
// We clone the file info just to make sure it doesn't get mutated for the test.
const result = await tree.createFile(fileName, buf, Object.assign({}, fileInfo), { "m.new_content": true });
expect(result).toMatchObject({ event_id: fileEventId });
expect(uploadFn).toHaveBeenCalledTimes(1);
expect(sendMsgFn).toHaveBeenCalledTimes(1);
@@ -827,7 +1018,7 @@ describe("MSC3089TreeSpace", () => {
it('should return falsy for unknown files', () => {
const fileEventId = "$file";
room.currentState = {
getStateEvents: (eventType: string, stateKey?: string) => {
getStateEvents: (eventType: string, stateKey?: string): MatrixEvent[] | MatrixEvent | null => {
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
expect(stateKey).toEqual(fileEventId);
return null;
@@ -854,4 +1045,22 @@ describe("MSC3089TreeSpace", () => {
expect(files.length).toEqual(1);
expect(files[0].indexEvent).toBe(firstFile);
});
it('should list all files', () => {
const firstFile = { getContent: () => ({ active: true }) };
const secondFile = { getContent: () => ({ active: false }) }; // deliberately inactive
room.currentState = {
getStateEvents: (eventType: string, stateKey?: string) => {
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
expect(stateKey).toBeUndefined();
return [firstFile, secondFile];
},
};
const files = tree.listAllFiles();
expect(files).toBeDefined();
expect(files.length).toEqual(2);
expect(files[0].indexEvent).toBe(firstFile);
expect(files[1].indexEvent).toBe(secondFile);
});
});
+281
View File
@@ -0,0 +1,281 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventType } from "../../../src";
import { M_BEACON_INFO } from "../../../src/@types/beacon";
import {
isTimestampInDuration,
isBeaconInfoEventType,
Beacon,
BeaconEvent,
} from "../../../src/models/beacon";
import { makeBeaconInfoEvent } from "../../test-utils/beacon";
jest.useFakeTimers();
describe('Beacon', () => {
describe('isTimestampInDuration()', () => {
const startTs = new Date('2022-03-11T12:07:47.592Z').getTime();
const HOUR_MS = 3600000;
it('returns false when timestamp is before start time', () => {
// day before
const timestamp = new Date('2022-03-10T12:07:47.592Z').getTime();
expect(isTimestampInDuration(startTs, HOUR_MS, timestamp)).toBe(false);
});
it('returns false when timestamp is after start time + duration', () => {
// 1 second later
const timestamp = new Date('2022-03-10T12:07:48.592Z').getTime();
expect(isTimestampInDuration(startTs, HOUR_MS, timestamp)).toBe(false);
});
it('returns true when timestamp is exactly start time', () => {
expect(isTimestampInDuration(startTs, HOUR_MS, startTs)).toBe(true);
});
it('returns true when timestamp is exactly the end of the duration', () => {
expect(isTimestampInDuration(startTs, HOUR_MS, startTs + HOUR_MS)).toBe(true);
});
it('returns true when timestamp is within the duration', () => {
const twoHourDuration = HOUR_MS * 2;
const now = startTs + HOUR_MS;
expect(isTimestampInDuration(startTs, twoHourDuration, now)).toBe(true);
});
});
describe('isBeaconInfoEventType', () => {
it.each([
EventType.CallAnswer,
`prefix.${M_BEACON_INFO.name}`,
`prefix.${M_BEACON_INFO.altName}`,
])('returns false for %s', (type) => {
expect(isBeaconInfoEventType(type)).toBe(false);
});
it.each([
M_BEACON_INFO.name,
M_BEACON_INFO.altName,
`${M_BEACON_INFO.name}.@test:server.org.12345`,
`${M_BEACON_INFO.altName}.@test:server.org.12345`,
])('returns true for %s', (type) => {
expect(isBeaconInfoEventType(type)).toBe(true);
});
});
describe('Beacon', () => {
const userId = '@user:server.org';
const roomId = '$room:server.org';
// 14.03.2022 16:15
const now = 1647270879403;
const HOUR_MS = 3600000;
// beacon_info events
// created 'an hour ago'
// without timeout of 3 hours
let liveBeaconEvent;
let notLiveBeaconEvent;
const advanceDateAndTime = (ms: number) => {
// bc liveness check uses Date.now we have to advance this mock
jest.spyOn(global.Date, 'now').mockReturnValue(now + ms);
// then advance time for the interval by the same amount
jest.advanceTimersByTime(ms);
};
beforeEach(() => {
// go back in time to create the beacon
jest.spyOn(global.Date, 'now').mockReturnValue(now - HOUR_MS);
liveBeaconEvent = makeBeaconInfoEvent(
userId,
roomId,
{
timeout: HOUR_MS * 3,
isLive: true,
},
'$live123',
'$live123',
);
notLiveBeaconEvent = makeBeaconInfoEvent(
userId,
roomId,
{ timeout: HOUR_MS * 3, isLive: false },
'$dead123',
'$dead123',
);
// back to now
jest.spyOn(global.Date, 'now').mockReturnValue(now);
});
afterAll(() => {
jest.spyOn(global.Date, 'now').mockRestore();
});
it('creates beacon from event', () => {
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
expect(beacon.roomId).toEqual(roomId);
expect(beacon.isLive).toEqual(true);
expect(beacon.beaconInfoOwner).toEqual(userId);
expect(beacon.beaconInfoEventType).toEqual(liveBeaconEvent.getType());
expect(beacon.identifier).toEqual(liveBeaconEvent.getType());
expect(beacon.beaconInfo).toBeTruthy();
});
describe('isLive()', () => {
it('returns false when beacon is explicitly set to not live', () => {
const beacon = new Beacon(notLiveBeaconEvent);
expect(beacon.isLive).toEqual(false);
});
it('returns false when beacon is expired', () => {
// time travel to beacon creation + 3 hours
jest.spyOn(global.Date, 'now').mockReturnValue(now - 3 * HOUR_MS);
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.isLive).toEqual(false);
});
it('returns false when beacon timestamp is in future', () => {
// time travel to before beacon events timestamp
// event was created now - 1 hour
jest.spyOn(global.Date, 'now').mockReturnValue(now - HOUR_MS - HOUR_MS);
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.isLive).toEqual(false);
});
it('returns true when beacon was created in past and not yet expired', () => {
// liveBeaconEvent was created 1 hour ago
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.isLive).toEqual(true);
});
});
describe('update()', () => {
it('does not update with different event', () => {
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
expect(() => beacon.update(notLiveBeaconEvent)).toThrow();
expect(beacon.isLive).toEqual(true);
});
it('updates event', () => {
const beacon = new Beacon(liveBeaconEvent);
const emitSpy = jest.spyOn(beacon, 'emit');
expect(beacon.isLive).toEqual(true);
const updatedBeaconEvent = makeBeaconInfoEvent(
userId, roomId, { timeout: HOUR_MS * 3, isLive: false }, '$live123', '$live123');
beacon.update(updatedBeaconEvent);
expect(beacon.isLive).toEqual(false);
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.Update, updatedBeaconEvent, beacon);
});
it('emits livenesschange event when beacon liveness changes', () => {
const beacon = new Beacon(liveBeaconEvent);
const emitSpy = jest.spyOn(beacon, 'emit');
expect(beacon.isLive).toEqual(true);
const updatedBeaconEvent = makeBeaconInfoEvent(
userId,
roomId,
{ timeout: HOUR_MS * 3, isLive: false },
beacon.beaconInfoId,
'$live123',
);
beacon.update(updatedBeaconEvent);
expect(beacon.isLive).toEqual(false);
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.LivenessChange, false, beacon);
});
});
describe('monitorLiveness()', () => {
it('does not set a monitor interval when beacon is not live', () => {
// beacon was created an hour ago
// and has a 3hr duration
const beacon = new Beacon(notLiveBeaconEvent);
const emitSpy = jest.spyOn(beacon, 'emit');
beacon.monitorLiveness();
// @ts-ignore
expect(beacon.livenessWatchInterval).toBeFalsy();
advanceDateAndTime(HOUR_MS * 2 + 1);
// no emit
expect(emitSpy).not.toHaveBeenCalled();
});
it('checks liveness of beacon at expected expiry time', () => {
// live beacon was created an hour ago
// and has a 3hr duration
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.isLive).toBeTruthy();
const emitSpy = jest.spyOn(beacon, 'emit');
beacon.monitorLiveness();
advanceDateAndTime(HOUR_MS * 2 + 1);
expect(emitSpy).toHaveBeenCalledTimes(1);
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.LivenessChange, false, beacon);
});
it('clears monitor interval when re-monitoring liveness', () => {
// live beacon was created an hour ago
// and has a 3hr duration
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.isLive).toBeTruthy();
beacon.monitorLiveness();
// @ts-ignore
const oldMonitor = beacon.livenessWatchInterval;
beacon.monitorLiveness();
// @ts-ignore
expect(beacon.livenessWatchInterval).not.toEqual(oldMonitor);
});
it('destroy kills liveness monitor and emits', () => {
// live beacon was created an hour ago
// and has a 3hr duration
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.isLive).toBeTruthy();
const emitSpy = jest.spyOn(beacon, 'emit');
beacon.monitorLiveness();
// destroy the beacon
beacon.destroy();
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.Destroy, beacon.identifier);
// live forced to false
expect(beacon.isLive).toBe(false);
advanceDateAndTime(HOUR_MS * 2 + 1);
// no additional calls
expect(emitSpy).toHaveBeenCalledTimes(1);
});
});
});
});
+60
View File
@@ -0,0 +1,60 @@
/*
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 { MatrixEvent } from "../../../src/models/event";
describe('MatrixEvent', () => {
it('should create copies of itself', () => {
const a = new MatrixEvent({
type: "com.example.test",
content: {
isTest: true,
num: 42,
},
});
const clone = a.toSnapshot();
expect(clone).toBeDefined();
expect(clone).not.toBe(a);
expect(clone.event).not.toBe(a.event);
expect(clone.event).toMatchObject(a.event);
// The other properties we're not super interested in, honestly.
});
it('should compare itself to other events using json', () => {
const a = new MatrixEvent({
type: "com.example.test",
content: {
isTest: true,
num: 42,
},
});
const b = new MatrixEvent({
type: "com.example.test______B",
content: {
isTest: true,
num: 42,
},
});
expect(a.isEquivalentTo(b)).toBe(false);
expect(a.isEquivalentTo(a)).toBe(true);
expect(b.isEquivalentTo(a)).toBe(false);
expect(b.isEquivalentTo(b)).toBe(true);
expect(a.toSnapshot().isEquivalentTo(a)).toBe(true);
expect(a.toSnapshot().isEquivalentTo(b)).toBe(false);
});
});
+17 -1
View File
@@ -1,4 +1,4 @@
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
import { PushProcessor } from "../../src/pushprocessor";
describe('NotificationService', function() {
@@ -302,4 +302,20 @@ describe('NotificationService', function() {
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(false);
});
it("a rule with no conditions matches every event.", function() {
expect(pushProcessor.ruleMatchesEvent({
rule_id: "rule1",
actions: [],
conditions: [],
default: false,
enabled: true,
}, testEvent)).toBe(true);
expect(pushProcessor.ruleMatchesEvent({
rule_id: "rule1",
actions: [],
default: false,
enabled: true,
}, testEvent)).toBe(true);
});
});
+8 -8
View File
@@ -15,12 +15,14 @@ limitations under the License.
*/
import { EventTimelineSet } from "../../src/models/event-timeline-set";
import { MatrixEvent } from "../../src/models/event";
import { MatrixEvent, MatrixEventEvent } from "../../src/models/event";
import { Room } from "../../src/models/room";
import { Relations } from "../../src/models/relations";
describe("Relations", function() {
it("should deduplicate annotations", function() {
const relations = new Relations("m.annotation", "m.reaction");
const room = new Room("room123", null, null);
const relations = new Relations("m.annotation", "m.reaction", room);
// Create an instance of an annotation
const eventData = {
@@ -95,15 +97,13 @@ describe("Relations", function() {
});
// Stub the room
const room = {
getPendingEvent() { return null; },
getUnfilteredTimelineSet() { return null; },
};
const room = new Room("room123", null, null);
// Add the target event first, then the relation event
{
const relationsCreated = new Promise(resolve => {
targetEvent.once("Event.relationsCreated", resolve);
targetEvent.once(MatrixEventEvent.RelationsCreated, resolve);
});
const timelineSet = new EventTimelineSet(room, {
@@ -118,7 +118,7 @@ describe("Relations", function() {
// Add the relation event first, then the target event
{
const relationsCreated = new Promise(resolve => {
targetEvent.once("Event.relationsCreated", resolve);
targetEvent.once(MatrixEventEvent.RelationsCreated, resolve);
});
const timelineSet = new EventTimelineSet(room, {
+29 -1
View File
@@ -1,4 +1,4 @@
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
import { RoomMember } from "../../src/models/room-member";
describe("RoomMember", function() {
@@ -124,6 +124,34 @@ describe("RoomMember", function() {
expect(member.powerLevel).toEqual(0);
expect(emitCount).toEqual(1);
});
it("should not honor string power levels.",
function() {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
},
event: true,
});
let emitCount = 0;
member.on("RoomMember.powerLevel", function(emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual('@alice:bar');
expect(emitMember.powerLevel).toEqual(20);
expect(emitEvent).toEqual(event);
});
member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(20);
expect(emitCount).toEqual(1);
});
});
describe("setTypingEvent", function() {
+108 -31
View File
@@ -1,6 +1,8 @@
import * as utils from "../test-utils";
import { RoomState } from "../../src/models/room-state";
import { RoomMember } from "../../src/models/room-member";
import * as utils from "../test-utils/test-utils";
import { makeBeaconInfoEvent } from "../test-utils/beacon";
import { filterEmitCallsByEventType } from "../test-utils/emitter";
import { RoomState, RoomStateEvent } from "../../src/models/room-state";
import { BeaconEvent } from "../../src/models/beacon";
describe("RoomState", function() {
const roomId = "!foo:bar";
@@ -121,7 +123,7 @@ describe("RoomState", function() {
it("should return a single MatrixEvent if a state_key was specified",
function() {
const event = state.getStateEvents("m.room.member", userA);
expect(event.getContent()).toEqual({
expect(event.getContent()).toMatchObject({
membership: "join",
});
});
@@ -193,12 +195,7 @@ describe("RoomState", function() {
expect(emitCount).toEqual(2);
});
it("should call setPowerLevelEvent on each RoomMember for m.room.power_levels",
function() {
// mock up the room members
state.members[userA] = utils.mock(RoomMember);
state.members[userB] = utils.mock(RoomMember);
it("should call setPowerLevelEvent on each RoomMember for m.room.power_levels", function() {
const powerLevelEvent = utils.mkEvent({
type: "m.room.power_levels", room: roomId, user: userA, event: true,
content: {
@@ -208,18 +205,16 @@ describe("RoomState", function() {
},
});
// spy on the room members
jest.spyOn(state.members[userA], "setPowerLevelEvent");
jest.spyOn(state.members[userB], "setPowerLevelEvent");
state.setStateEvents([powerLevelEvent]);
expect(state.members[userA].setPowerLevelEvent).toHaveBeenCalledWith(
powerLevelEvent,
);
expect(state.members[userB].setPowerLevelEvent).toHaveBeenCalledWith(
powerLevelEvent,
);
expect(state.members[userA].setPowerLevelEvent).toHaveBeenCalledWith(powerLevelEvent);
expect(state.members[userB].setPowerLevelEvent).toHaveBeenCalledWith(powerLevelEvent);
});
it("should call setPowerLevelEvent on a new RoomMember if power levels exist",
function() {
it("should call setPowerLevelEvent on a new RoomMember if power levels exist", function() {
const memberEvent = utils.mkMembership({
mship: "join", user: userC, room: roomId, event: true,
});
@@ -243,13 +238,12 @@ describe("RoomState", function() {
});
it("should call setMembershipEvent on the right RoomMember", function() {
// mock up the room members
state.members[userA] = utils.mock(RoomMember);
state.members[userB] = utils.mock(RoomMember);
const memberEvent = utils.mkMembership({
user: userB, mship: "leave", room: roomId, event: true,
});
// spy on the room members
jest.spyOn(state.members[userA], "setMembershipEvent");
jest.spyOn(state.members[userB], "setMembershipEvent");
state.setStateEvents([memberEvent]);
expect(state.members[userA].setMembershipEvent).not.toHaveBeenCalled();
@@ -257,6 +251,93 @@ describe("RoomState", function() {
memberEvent, state,
);
});
describe('beacon events', () => {
it('adds new beacon info events to state and emits', () => {
const beaconEvent = makeBeaconInfoEvent(userA, roomId);
const emitSpy = jest.spyOn(state, 'emit');
state.setStateEvents([beaconEvent]);
expect(state.beacons.size).toEqual(1);
const beaconInstance = state.beacons.get(beaconEvent.getType());
expect(beaconInstance).toBeTruthy();
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance);
});
it('does not add redacted beacon info events to state', () => {
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
const redactionEvent = { event: { type: 'm.room.redaction' } };
redactedBeaconEvent.makeRedacted(redactionEvent);
const emitSpy = jest.spyOn(state, 'emit');
state.setStateEvents([redactedBeaconEvent]);
// no beacon added
expect(state.beacons.size).toEqual(0);
expect(state.beacons.get(redactedBeaconEvent.getType)).toBeFalsy();
// no new beacon emit
expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy();
});
it('updates existing beacon info events in state', () => {
const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId);
const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId, beaconId);
state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(beaconEvent.getType());
expect(beaconInstance.isLive).toEqual(true);
state.setStateEvents([updatedBeaconEvent]);
// same Beacon
expect(state.beacons.get(beaconEvent.getType())).toBe(beaconInstance);
// updated liveness
expect(state.beacons.get(beaconEvent.getType()).isLive).toEqual(false);
});
it('destroys and removes redacted beacon events', () => {
const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId);
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId);
const redactionEvent = { event: { type: 'm.room.redaction' } };
redactedBeaconEvent.makeRedacted(redactionEvent);
state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(beaconEvent.getType());
const destroySpy = jest.spyOn(beaconInstance, 'destroy');
expect(beaconInstance.isLive).toEqual(true);
state.setStateEvents([redactedBeaconEvent]);
expect(destroySpy).toHaveBeenCalled();
expect(state.beacons.get(beaconEvent.getType())).toBe(undefined);
});
it('updates live beacon ids once after setting state events', () => {
const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1', '$beacon1');
const deadBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, '$beacon2', '$beacon2');
const emitSpy = jest.spyOn(state, 'emit');
state.setStateEvents([liveBeaconEvent, deadBeaconEvent]);
// called once
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(1);
// live beacon is now not live
const updatedLiveBeaconEvent = makeBeaconInfoEvent(
userA, roomId, { isLive: false }, liveBeaconEvent.getId(), '$beacon1',
);
state.setStateEvents([updatedLiveBeaconEvent]);
expect(state.hasLiveBeacons).toBe(false);
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(2);
expect(emitSpy).toHaveBeenCalledWith(RoomStateEvent.BeaconLiveness, state, false);
});
});
});
describe("setOutOfBandMembers", function() {
@@ -374,17 +455,13 @@ describe("RoomState", function() {
user_ids: [userA],
},
});
// mock up the room members
state.members[userA] = utils.mock(RoomMember);
state.members[userB] = utils.mock(RoomMember);
// spy on the room members
jest.spyOn(state.members[userA], "setTypingEvent");
jest.spyOn(state.members[userB], "setTypingEvent");
state.setTypingEvent(typingEvent);
expect(state.members[userA].setTypingEvent).toHaveBeenCalledWith(
typingEvent,
);
expect(state.members[userB].setTypingEvent).toHaveBeenCalledWith(
typingEvent,
);
expect(state.members[userA].setTypingEvent).toHaveBeenCalledWith(typingEvent);
expect(state.members[userB].setTypingEvent).toHaveBeenCalledWith(typingEvent);
});
});
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,7 +4,7 @@
import { defer } from '../../src/utils';
import { MatrixError } from "../../src/http-api";
import { MatrixScheduler } from "../../src/scheduler";
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
jest.useFakeTimers();
+1 -1
View File
@@ -1,6 +1,6 @@
import { EventTimeline } from "../../src/models/event-timeline";
import { TimelineIndex, TimelineWindow } from "../../src/timeline-window";
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
const ROOM_ID = "roomId";
const USER_ID = "userId";
+1 -1
View File
@@ -1,5 +1,5 @@
import { User } from "../../src/models/user";
import * as utils from "../test-utils";
import * as utils from "../test-utils/test-utils";
describe("User", function() {
const userId = "@alice:bar";
+141 -64
View File
@@ -3,10 +3,12 @@ import {
alphabetPad,
averageBetweenStrings,
baseToString,
deepSortedObjectEntries,
DEFAULT_ALPHABET,
lexicographicCompare,
nextString,
prevString,
simpleRetryOperation,
stringToBase,
} from "../../src/utils";
import { logger } from "../../src/logger";
@@ -24,6 +26,15 @@ describe("utils", function() {
"foo=bar&baz=beer%40",
);
});
it("should handle boolean and numeric values", function() {
const params = {
string: "foobar",
number: 12345,
boolean: false,
};
expect(utils.encodeParams(params)).toEqual("string=foobar&number=12345&boolean=false");
});
});
describe("encodeUri", function() {
@@ -109,10 +120,10 @@ describe("utils", function() {
describe("deepCompare", function() {
const assert = {
isTrue: function(x) {
isTrue: function(x: any) {
expect(x).toBe(true);
},
isFalse: function(x) {
isFalse: function(x: any) {
expect(x).toBe(false);
},
};
@@ -174,10 +185,10 @@ describe("utils", function() {
// no two different function is equal really, they capture their
// context variables so even if they have same toString(), they
// won't have same functionality
const func = function(x) {
const func = function() {
return true;
};
const func2 = function(x) {
const func2 = function() {
return true;
};
assert.isTrue(utils.deepCompare(func, func));
@@ -187,66 +198,6 @@ describe("utils", function() {
});
});
describe("extend", function() {
const SOURCE = { "prop2": 1, "string2": "x", "newprop": "new" };
it("should extend", function() {
const target = {
"prop1": 5, "prop2": 7, "string1": "baz", "string2": "foo",
};
const merged = {
"prop1": 5, "prop2": 1, "string1": "baz", "string2": "x",
"newprop": "new",
};
const sourceOrig = JSON.stringify(SOURCE);
utils.extend(target, SOURCE);
expect(JSON.stringify(target)).toEqual(JSON.stringify(merged));
// check the originial wasn't modified
expect(JSON.stringify(SOURCE)).toEqual(sourceOrig);
});
it("should ignore null", function() {
const target = {
"prop1": 5, "prop2": 7, "string1": "baz", "string2": "foo",
};
const merged = {
"prop1": 5, "prop2": 1, "string1": "baz", "string2": "x",
"newprop": "new",
};
const sourceOrig = JSON.stringify(SOURCE);
utils.extend(target, null, SOURCE);
expect(JSON.stringify(target)).toEqual(JSON.stringify(merged));
// check the originial wasn't modified
expect(JSON.stringify(SOURCE)).toEqual(sourceOrig);
});
it("should handle properties created with defineProperties", function() {
const source = Object.defineProperties({}, {
"enumerableProp": {
get: function() {
return true;
},
enumerable: true,
},
"nonenumerableProp": {
get: function() {
return true;
},
},
});
// TODO: Fix type
const target: any = {};
utils.extend(target, source);
expect(target.enumerableProp).toBe(true);
expect(target.nonenumerableProp).toBe(undefined);
});
});
describe("chunkPromises", function() {
it("should execute promises in chunks", async function() {
let promiseCount = 0;
@@ -267,6 +218,34 @@ describe("utils", function() {
});
});
describe('simpleRetryOperation', () => {
it('should retry', async () => {
let count = 0;
const val = {};
const fn = (attempt: any) => {
count++;
// If this expectation fails then it can appear as a Jest Timeout due to
// the retry running beyond the test limit.
expect(attempt).toEqual(count);
if (count > 1) {
return Promise.resolve(val);
} else {
return Promise.reject(new Error("Iterative failure"));
}
};
const ret = await simpleRetryOperation(fn);
expect(ret).toBe(val);
expect(count).toEqual(2);
});
// We don't test much else of the function because then we're just testing that the
// underlying library behaves, which should be tested on its own. Our API surface is
// all that concerns us.
});
describe('DEFAULT_ALPHABET', () => {
it('should be usefully printable ASCII in order', () => {
expect(DEFAULT_ALPHABET).toEqual(
@@ -429,4 +408,102 @@ describe("utils", function() {
expect(lexicographicCompare('a', 'A') > 0).toBe(true);
});
});
describe('deepSortedObjectEntries', () => {
it('should auto-return non-objects', () => {
expect(deepSortedObjectEntries(42)).toEqual(42);
expect(deepSortedObjectEntries("not object")).toEqual("not object");
expect(deepSortedObjectEntries(true)).toEqual(true);
expect(deepSortedObjectEntries([42])).toEqual([42]);
expect(deepSortedObjectEntries(null)).toEqual(null);
expect(deepSortedObjectEntries(undefined)).toEqual(undefined);
});
it('should sort objects appropriately', () => {
const input = {
a: 42,
b: {
d: {},
a: "test",
b: "alpha",
},
[72]: "test",
};
const output: any = [
["72", "test"],
["a", 42],
["b", [
["a", "test"],
["b", "alpha"],
["d", []],
]],
];
expect(deepSortedObjectEntries(input)).toMatchObject(output);
});
});
describe("recursivelyAssign", () => {
it("doesn't override with null/undefined", () => {
const result = utils.recursivelyAssign(
{
string: "Hello world",
object: {},
float: 0.1,
}, {
string: null,
object: undefined,
},
true,
);
expect(result).toStrictEqual({
string: "Hello world",
object: {},
float: 0.1,
});
});
it("assigns recursively", () => {
const result = utils.recursivelyAssign(
{
number: 42,
object: {
message: "Hello world",
day: "Monday",
langs: {
compiled: ["c++"],
},
},
thing: "string",
}, {
number: 2,
object: {
message: "How are you",
day: "Friday",
langs: {
compiled: ["c++", "c"],
},
},
thing: {
aSubThing: "something",
},
},
);
expect(result).toStrictEqual({
number: 2,
object: {
message: "How are you",
day: "Friday",
langs: {
compiled: ["c++", "c"],
},
},
thing: {
aSubThing: "something",
},
});
});
});
});
+215 -8
View File
@@ -16,6 +16,8 @@ limitations under the License.
import { TestClient } from '../../TestClient';
import { MatrixCall, CallErrorCode, CallEvent } from '../../../src/webrtc/call';
import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from '../../../src/webrtc/callEventTypes';
import { RoomMember } from "../../../src";
const DUMMY_SDP = (
"v=0\r\n" +
@@ -80,6 +82,51 @@ class MockRTCPeerConnection {
}
close() {}
getStats() { return []; }
addTrack(track: MockMediaStreamTrack) {return new MockRTCRtpSender(track);}
}
class MockRTCRtpSender {
constructor(public track: MockMediaStreamTrack) {}
replaceTrack(track: MockMediaStreamTrack) {this.track = track;}
}
class MockMediaStreamTrack {
constructor(public readonly id: string, public readonly kind: "audio" | "video", public enabled = true) {}
stop() {}
}
class MockMediaStream {
constructor(
public id: string,
private tracks: MockMediaStreamTrack[] = [],
) {}
getTracks() { return this.tracks; }
getAudioTracks() { return this.tracks.filter((track) => track.kind === "audio"); }
getVideoTracks() { return this.tracks.filter((track) => track.kind === "video"); }
addEventListener() {}
removeEventListener() { }
addTrack(track: MockMediaStreamTrack) {this.tracks.push(track);}
removeTrack(track: MockMediaStreamTrack) {this.tracks.splice(this.tracks.indexOf(track), 1);}
}
class MockMediaDeviceInfo {
constructor(
public kind: "audio" | "video",
) {}
}
class MockMediaHandler {
getUserMediaStream(audio: boolean, video: boolean) {
const tracks = [];
if (audio) tracks.push(new MockMediaStreamTrack("audio_track", "audio"));
if (video) tracks.push(new MockMediaStreamTrack("video_track", "video"));
return new MockMediaStream("mock_stream_from_media_handler", tracks);
}
stopUserMediaStream() {}
}
describe('Call', function() {
@@ -97,13 +144,9 @@ describe('Call', function() {
global.navigator = {
mediaDevices: {
// @ts-ignore Mock
getUserMedia: () => {
return {
getTracks: () => [],
getAudioTracks: () => [],
getVideoTracks: () => [],
};
},
getUserMedia: () => new MockMediaStream("local_stream"),
// @ts-ignore Mock
enumerateDevices: async () => [new MockMediaDeviceInfo("audio"), new MockMediaDeviceInfo("video")],
},
};
@@ -114,7 +157,7 @@ describe('Call', function() {
RTCSessionDescription: {},
// @ts-ignore Mock
RTCIceCandidate: {},
getUserMedia: {},
getUserMedia: () => new MockMediaStream("local_stream"),
};
// @ts-ignore Mock
global.document = {};
@@ -123,6 +166,8 @@ describe('Call', function() {
// We just stub out sendEvent: we're not interested in testing the client's
// event sending code here
client.client.sendEvent = () => {};
client.client.mediaHandler = new MockMediaHandler;
client.client.getMediaHandler = () => client.client.mediaHandler;
client.httpBackend.when("GET", "/voip/turnServer").respond(200, {});
call = new MatrixCall({
client: client.client,
@@ -298,4 +343,166 @@ describe('Call', function() {
// Hangup to stop timers
call.hangup(CallErrorCode.UserHangup, true);
});
it("should map SDPStreamMetadata to feeds", async () => {
const callPromise = call.placeVoiceCall();
await client.httpBackend.flush();
await callPromise;
call.getOpponentMember = () => {
return { userId: "@bob:bar.uk" };
};
await call.onAnswerReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'party_id',
answer: {
sdp: DUMMY_SDP,
},
[SDPStreamMetadataKey]: {
"remote_stream": {
purpose: SDPStreamMetadataPurpose.Usermedia,
audio_muted: true,
video_muted: false,
},
},
};
},
});
call.pushRemoteFeed(
new MockMediaStream(
"remote_stream",
[
new MockMediaStreamTrack("remote_audio_track", "audio"),
new MockMediaStreamTrack("remote_video_track", "video"),
],
),
);
const feed = call.getFeeds().find((feed) => feed.stream.id === "remote_stream");
expect(feed?.purpose).toBe(SDPStreamMetadataPurpose.Usermedia);
expect(feed?.isAudioMuted()).toBeTruthy();
expect(feed?.isVideoMuted()).not.toBeTruthy();
});
it("should fallback to replaceTrack() if the other side doesn't support SPDStreamMetadata", async () => {
const callPromise = call.placeVoiceCall();
await client.httpBackend.flush();
await callPromise;
call.getOpponentMember = () => {
return { userId: "@bob:bar.uk" } as RoomMember;
};
await call.onAnswerReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'party_id',
answer: {
sdp: DUMMY_SDP,
},
};
},
});
call.setScreensharingEnabledWithoutMetadataSupport = jest.fn();
call.setScreensharingEnabled(true);
expect(call.setScreensharingEnabledWithoutMetadataSupport).toHaveBeenCalled();
});
it("should fallback to answering with no video", async () => {
await client.httpBackend.flush();
call.shouldAnswerWithMediaType = (wantedValue: boolean) => wantedValue;
client.client.mediaHandler.getUserMediaStream = jest.fn().mockRejectedValue("reject");
await call.answer(true, true);
expect(client.client.mediaHandler.getUserMediaStream).toHaveBeenNthCalledWith(1, true, true);
expect(client.client.mediaHandler.getUserMediaStream).toHaveBeenNthCalledWith(2, true, false);
});
it("should handle mid-call device changes", async () => {
client.client.mediaHandler.getUserMediaStream = jest.fn().mockReturnValue(
new MockMediaStream(
"stream", [
new MockMediaStreamTrack("audio_track", "audio"),
new MockMediaStreamTrack("video_track", "video"),
],
),
);
const callPromise = call.placeVideoCall();
await client.httpBackend.flush();
await callPromise;
await call.onAnswerReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'party_id',
answer: {
sdp: DUMMY_SDP,
},
};
},
});
await call.updateLocalUsermediaStream(
new MockMediaStream(
"replacement_stream",
[
new MockMediaStreamTrack("new_audio_track", "audio"),
new MockMediaStreamTrack("video_track", "video"),
],
),
);
expect(call.localUsermediaStream.id).toBe("stream");
expect(call.localUsermediaStream.getAudioTracks()[0].id).toBe("new_audio_track");
expect(call.localUsermediaStream.getVideoTracks()[0].id).toBe("video_track");
expect(call.usermediaSenders.find((sender) => {
return sender?.track?.kind === "audio";
}).track.id).toBe("new_audio_track");
expect(call.usermediaSenders.find((sender) => {
return sender?.track?.kind === "video";
}).track.id).toBe("video_track");
});
it("should handle upgrade to video call", async () => {
const callPromise = call.placeVoiceCall();
await client.httpBackend.flush();
await callPromise;
await call.onAnswerReceived({
getContent: () => {
return {
version: 1,
call_id: call.callId,
party_id: 'party_id',
answer: {
sdp: DUMMY_SDP,
},
[SDPStreamMetadataKey]: {},
};
},
});
await call.upgradeCall(false, true);
expect(call.localUsermediaStream.getAudioTracks()[0].id).toBe("audio_track");
expect(call.localUsermediaStream.getVideoTracks()[0].id).toBe("video_track");
expect(call.usermediaSenders.find((sender) => {
return sender?.track?.kind === "audio";
}).track.id).toBe("audio_track");
expect(call.usermediaSenders.find((sender) => {
return sender?.track?.kind === "video";
}).track.id).toBe("video_track");
});
});
+165
View File
@@ -0,0 +1,165 @@
/*
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.
*/
// allow camelcase as these are things that go onto the wire
/* eslint-disable camelcase */
export enum PushRuleActionName {
DontNotify = "dont_notify",
Notify = "notify",
Coalesce = "coalesce",
}
export enum TweakName {
Highlight = "highlight",
Sound = "sound",
}
export type Tweak<N extends TweakName, V> = {
set_tweak: N;
value: V;
};
export type TweakHighlight = Tweak<TweakName.Highlight, boolean>;
export type TweakSound = Tweak<TweakName.Sound, string>;
export type Tweaks = TweakHighlight | TweakSound;
export enum ConditionOperator {
ExactEquals = "==",
LessThan = "<",
GreaterThan = ">",
GreaterThanOrEqual = ">=",
LessThanOrEqual = "<=",
}
export type PushRuleAction = Tweaks | PushRuleActionName;
export type MemberCountCondition
<N extends number, Op extends ConditionOperator = ConditionOperator.ExactEquals>
= `${Op}${N}` | (Op extends ConditionOperator.ExactEquals ? `${N}` : never);
export type AnyMemberCountCondition = MemberCountCondition<number, ConditionOperator>;
export const DMMemberCountCondition: MemberCountCondition<2> = "2";
export function isDmMemberCountCondition(condition: AnyMemberCountCondition): boolean {
return condition === "==2" || condition === "2";
}
export enum ConditionKind {
EventMatch = "event_match",
ContainsDisplayName = "contains_display_name",
RoomMemberCount = "room_member_count",
SenderNotificationPermission = "sender_notification_permission",
}
export interface IPushRuleCondition<N extends ConditionKind | string> {
[k: string]: any; // for custom conditions, there can be other fields here
kind: N;
}
export interface IEventMatchCondition extends IPushRuleCondition<ConditionKind.EventMatch> {
key: string;
pattern: string;
}
export interface IContainsDisplayNameCondition extends IPushRuleCondition<ConditionKind.ContainsDisplayName> {
// no additional fields
}
export interface IRoomMemberCountCondition extends IPushRuleCondition<ConditionKind.RoomMemberCount> {
is: AnyMemberCountCondition;
}
export interface ISenderNotificationPermissionCondition
extends IPushRuleCondition<ConditionKind.SenderNotificationPermission> {
key: string;
}
// XXX: custom conditions are possible but always fail, and break the typescript discriminated union so ignore them here
// IPushRuleCondition<Exclude<string, ConditionKind>> unfortunately does not resolve this at the time of writing.
export type PushRuleCondition = IEventMatchCondition
| IContainsDisplayNameCondition
| IRoomMemberCountCondition
| ISenderNotificationPermissionCondition;
export enum PushRuleKind {
Override = "override",
ContentSpecific = "content",
RoomSpecific = "room",
SenderSpecific = "sender",
Underride = "underride",
}
export enum RuleId {
Master = ".m.rule.master",
ContainsDisplayName = ".m.rule.contains_display_name",
ContainsUserName = ".m.rule.contains_user_name",
AtRoomNotification = ".m.rule.roomnotif",
DM = ".m.rule.room_one_to_one",
EncryptedDM = ".m.rule.encrypted_room_one_to_one",
Message = ".m.rule.message",
EncryptedMessage = ".m.rule.encrypted",
InviteToSelf = ".m.rule.invite_for_me",
MemberEvent = ".m.rule.member_event",
IncomingCall = ".m.rule.call",
SuppressNotices = ".m.rule.suppress_notices",
Tombstone = ".m.rule.tombstone",
}
export type PushRuleSet = {
[k in PushRuleKind]?: IPushRule[];
};
export interface IPushRule {
actions: PushRuleAction[];
conditions?: PushRuleCondition[];
default: boolean;
enabled: boolean;
pattern?: string;
rule_id: RuleId | string;
}
export interface IAnnotatedPushRule extends IPushRule {
kind: PushRuleKind;
}
export interface IPushRules {
global: PushRuleSet;
device?: PushRuleSet;
}
export interface IPusher {
app_display_name: string;
app_id: string;
data: {
format?: string;
url?: string; // TODO: Required if kind==http
brand?: string; // TODO: For email notifications only? Unspecced field
};
device_display_name: string;
kind: "http" | string;
lang: string;
profile_tag?: string;
pushkey: string;
}
export interface IPusherRequest extends IPusher {
append?: boolean;
}
/* eslint-enable camelcase */
@@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
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.
@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export const SERVICE_TYPES = Object.freeze({
IS: 'SERVICE_TYPE_IS', // An Identity Service
IM: 'SERVICE_TYPE_IM', // An Integration Manager
});
declare module "another-json" {
export function stringify(o: object): string;
}
+29
View File
@@ -0,0 +1,29 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// disable lint because these are wire responses
/* eslint-disable camelcase */
/**
* Represents a response to the CSAPI `/refresh` endpoint.
*/
export interface IRefreshTokenResponse {
access_token: string;
expires_in_ms: number;
refresh_token: string;
}
/* eslint-enable camelcase */
+147
View File
@@ -0,0 +1,147 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { EitherAnd, RELATES_TO_RELATIONSHIP, REFERENCE_RELATION } from "matrix-events-sdk";
import { UnstableValue } from "../NamespacedValue";
import { MAssetEvent, MLocationEvent, MTimestampEvent } from "./location";
/**
* Beacon info and beacon event types as described in MSC3489
* https://github.com/matrix-org/matrix-spec-proposals/pull/3489
*/
/**
* Beacon info events are state events.
* We have two requirements for these events:
* 1. they can only be written by their owner
* 2. a user can have an arbitrary number of beacon_info events
*
* 1. is achieved by setting the state_key to the owners mxid.
* Event keys in room state are a combination of `type` + `state_key`.
* To achieve an arbitrary number of only owner-writable state events
* we introduce a variable suffix to the event type
*
* Eg
* {
* "type": "m.beacon_info.@matthew:matrix.org.1",
* "state_key": "@matthew:matrix.org",
* "content": {
* "m.beacon_info": {
* "description": "The Matthew Tracker",
* "timeout": 86400000,
* },
* // more content as described below
* }
* },
* {
* "type": "m.beacon_info.@matthew:matrix.org.2",
* "state_key": "@matthew:matrix.org",
* "content": {
* "m.beacon_info": {
* "description": "Another different Matthew tracker",
* "timeout": 400000,
* },
* // more content as described below
* }
* }
*/
/**
* Variable event type for m.beacon_info
*/
export const M_BEACON_INFO_VARIABLE = new UnstableValue("m.beacon_info.*", "org.matrix.msc3489.beacon_info.*");
/**
* Non-variable type for m.beacon_info event content
*/
export const M_BEACON_INFO = new UnstableValue("m.beacon_info", "org.matrix.msc3489.beacon_info");
export const M_BEACON = new UnstableValue("m.beacon", "org.matrix.msc3489.beacon");
export type MBeaconInfoContent = {
description?: string;
// how long from the last event until we consider the beacon inactive in milliseconds
timeout: number;
// true when this is a live location beacon
// https://github.com/matrix-org/matrix-spec-proposals/pull/3672
live?: boolean;
};
export type MBeaconInfoEvent = EitherAnd<
{ [M_BEACON_INFO.name]: MBeaconInfoContent },
{ [M_BEACON_INFO.altName]: MBeaconInfoContent }
>;
/**
* m.beacon_info Event example from the spec
* https://github.com/matrix-org/matrix-spec-proposals/pull/3489
* {
"type": "m.beacon_info.@matthew:matrix.org.1",
"state_key": "@matthew:matrix.org",
"content": {
"m.beacon_info": {
"description": "The Matthew Tracker", // same as an `m.location` description
"timeout": 86400000, // how long from the last event until we consider the beacon inactive in milliseconds
},
"m.ts": 1436829458432, // creation timestamp of the beacon on the client
"m.asset": {
"type": "m.self" // the type of asset being tracked as per MSC3488
}
}
}
*/
/**
* m.beacon_info.* event content
*/
export type MBeaconInfoEventContent = &
MBeaconInfoEvent &
// creation timestamp of the beacon on the client
MTimestampEvent &
// the type of asset being tracked as per MSC3488
MAssetEvent;
/**
* m.beacon event example
* https://github.com/matrix-org/matrix-spec-proposals/pull/3489
*
* {
"type": "m.beacon",
"sender": "@matthew:matrix.org",
"content": {
"m.relates_to": { // from MSC2674: https://github.com/matrix-org/matrix-doc/pull/2674
"rel_type": "m.reference", // from MSC3267: https://github.com/matrix-org/matrix-doc/pull/3267
"event_id": "$beacon_info"
},
"m.location": {
"uri": "geo:51.5008,0.1247;u=35",
"description": "Arbitrary beacon information"
},
"m.ts": 1636829458432,
}
}
*/
/**
* Content of an m.beacon event
*/
export type MBeaconEventContent = &
MLocationEvent &
// timestamp when location was taken
MTimestampEvent &
// relates to a beacon_info event
RELATES_TO_RELATIONSHIP<typeof REFERENCE_RELATION>;
+44
View File
@@ -53,6 +53,8 @@ export enum EventType {
CallReject = "m.call.reject",
CallSelectAnswer = "m.call.select_answer",
CallNegotiate = "m.call.negotiate",
CallSDPStreamMetadataChanged = "m.call.sdp_stream_metadata_changed",
CallSDPStreamMetadataChangedPrefix = "org.matrix.call.sdp_stream_metadata_changed",
CallReplaces = "m.call.replaces",
CallAssertedIdentity = "m.call.asserted_identity",
CallAssertedIdentityPrefix = "org.matrix.call.asserted_identity",
@@ -87,6 +89,13 @@ export enum EventType {
Dummy = "m.dummy",
}
export enum RelationType {
Annotation = "m.annotation",
Replace = "m.replace",
Reference = "m.reference",
Thread = "m.thread",
}
export enum MsgType {
Text = "m.text",
Emote = "m.emote",
@@ -96,12 +105,15 @@ export enum MsgType {
Audio = "m.audio",
Location = "m.location",
Video = "m.video",
KeyVerificationRequest = "m.key.verification.request",
}
export const RoomCreateTypeField = "type";
export enum RoomType {
Space = "m.space",
UnstableCall = "org.matrix.msc3417.call",
ElementVideo = "io.element.video",
}
/**
@@ -139,6 +151,38 @@ export const UNSTABLE_MSC3089_LEAF = new UnstableValue("m.leaf", "org.matrix.msc
*/
export const UNSTABLE_MSC3089_BRANCH = new UnstableValue("m.branch", "org.matrix.msc3089.branch");
/**
* Functional members type for declaring a purpose of room members (e.g. helpful bots).
* Note that this reference is UNSTABLE and subject to breaking changes, including its
* eventual removal.
*
* Schema (TypeScript):
* {
* service_members?: string[]
* }
*
* Example:
* {
* "service_members": [
* "@helperbot:localhost",
* "@reminderbot:alice.tdl"
* ]
* }
*/
export const UNSTABLE_ELEMENT_FUNCTIONAL_USERS = new UnstableValue(
"io.element.functional_members",
"io.element.functional_members");
/**
* A type of message that affects visibility of a message,
* as per https://github.com/matrix-org/matrix-doc/pull/3531
*
* @experimental
*/
export const EVENT_VISIBILITY_CHANGE_TYPE = new UnstableValue(
"m.visibility",
"org.matrix.msc3531.visibility");
export interface IEncryptedFile {
url: string;
mimetype?: string;
@@ -14,13 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// TODO: Merge this with sync.js once converted
// Types for MSC1767: Extensible events in Matrix
export enum SyncState {
Error = "ERROR",
Prepared = "PREPARED",
Stopped = "STOPPED",
Syncing = "SYNCING",
Catchup = "CATCHUP",
Reconnecting = "RECONNECTING",
}
import { UnstableValue } from "../NamespacedValue";
export const TEXT_NODE_TYPE = new UnstableValue("m.text", "org.matrix.msc1767.text");
+30 -25
View File
@@ -15,11 +15,17 @@ limitations under the License.
*/
// this is needed to tell TS about global.Olm
import * as Olm from "@matrix-org/olm"; // eslint-disable-line @typescript-eslint/no-unused-vars
import "@matrix-org/olm";
export {};
declare global {
// use `number` as the return type in all cases for global.set{Interval,Timeout},
// so we don't accidentally use the methods on NodeJS.Timeout - they only exist in a subset of environments.
// The overload for clear{Interval,Timeout} is resolved as expected.
function setInterval(handler: TimerHandler, timeout: number, ...arguments: any[]): number;
function setTimeout(handler: TimerHandler, timeout: number, ...arguments: any[]): number;
namespace NodeJS {
interface Global {
localStorage: Storage;
@@ -27,11 +33,11 @@ declare global {
}
interface Window {
electron?: Electron;
webkitAudioContext: typeof AudioContext;
}
interface Electron {
getDesktopCapturerSources(options: GetSourcesOptions): Promise<Array<DesktopCapturerSource>>;
interface Crypto {
webkitSubtle?: Window["crypto"]["subtle"];
}
interface MediaDevices {
@@ -56,27 +62,6 @@ declare global {
};
}
interface DesktopCapturerSource {
id: string;
name: string;
thumbnailURL: string;
}
interface GetSourcesOptions {
types: Array<string>;
thumbnailSize?: {
height: number;
width: number;
};
fetchWindowIcons?: boolean;
}
interface HTMLAudioElement {
// sinkId & setSinkId are experimental and typescript doesn't know about them
sinkId: string;
setSinkId(outputId: string);
}
interface DummyInterfaceWeShouldntBeUsingThis {}
interface Navigator {
@@ -84,4 +69,24 @@ declare global {
// on webkit: we should check if we still need to do this
webkitGetUserMedia: DummyInterfaceWeShouldntBeUsingThis;
}
export interface ISettledFulfilled<T> {
status: "fulfilled";
value: T;
}
export interface ISettledRejected {
status: "rejected";
reason: any;
}
interface PromiseConstructor {
allSettled<T>(promises: Promise<T>[]): Promise<Array<ISettledFulfilled<T> | ISettledRejected>>;
}
interface RTCRtpTransceiver {
// This has been removed from TS
// (https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029),
// but we still need this for MatrixCall::getRidOfRTXCodecs()
setCodecPreferences(codecs: RTCRtpCodecCapability[]): void;
}
}
+97
View File
@@ -0,0 +1,97 @@
/*
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.
*/
// Types for MSC3488 - m.location: Extending events with location data
import { EitherAnd } from "matrix-events-sdk";
import { UnstableValue } from "../NamespacedValue";
import { TEXT_NODE_TYPE } from "./extensible_events";
export enum LocationAssetType {
Self = "m.self",
Pin = "m.pin",
}
export const M_ASSET = new UnstableValue("m.asset", "org.matrix.msc3488.asset");
export type MAssetContent = { type: LocationAssetType };
/**
* The event definition for an m.asset event (in content)
*/
export type MAssetEvent = EitherAnd<{ [M_ASSET.name]: MAssetContent }, { [M_ASSET.altName]: MAssetContent }>;
export const M_TIMESTAMP = new UnstableValue("m.ts", "org.matrix.msc3488.ts");
/**
* The event definition for an m.ts event (in content)
*/
export type MTimestampEvent = EitherAnd<{ [M_TIMESTAMP.name]: number }, { [M_TIMESTAMP.altName]: number }>;
export const M_LOCATION = new UnstableValue(
"m.location", "org.matrix.msc3488.location");
export type MLocationContent = {
uri: string;
description?: string | null;
};
export type MLocationEvent = EitherAnd<
{ [M_LOCATION.name]: MLocationContent },
{ [M_LOCATION.altName]: MLocationContent }
>;
export type MTextEvent = EitherAnd<{ [TEXT_NODE_TYPE.name]: string }, { [TEXT_NODE_TYPE.altName]: string }>;
/* From the spec at:
* https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md
{
"type": "m.room.message",
"content": {
"body": "Matthew was at geo:51.5008,0.1247;u=35 as of Sat Nov 13 18:50:58 2021",
"msgtype": "m.location",
"geo_uri": "geo:51.5008,0.1247;u=35",
"m.location": {
"uri": "geo:51.5008,0.1247;u=35",
"description": "Matthew's whereabouts",
},
"m.asset": {
"type": "m.self"
},
"m.text": "Matthew was at geo:51.5008,0.1247;u=35 as of Sat Nov 13 18:50:58 2021",
"m.ts": 1636829458432,
}
}
*/
type OptionalTimestampEvent = MTimestampEvent | undefined;
/**
* The content for an m.location event
*/
export type MLocationEventContent = &
MLocationEvent &
MAssetEvent &
MTextEvent &
OptionalTimestampEvent;
export type LegacyLocationEventContent = {
body: string;
msgtype: string;
geo_uri: string;
};
/**
* Possible content for location events as sent over the wire
*/
export type LocationEventWireContent = Partial<LegacyLocationEventContent & MLocationEventContent>;
export type ILocationContent = MLocationEventContent & LegacyLocationEventContent;
+54
View File
@@ -37,3 +37,57 @@ export enum Preset {
TrustedPrivateChat = "trusted_private_chat",
PublicChat = "public_chat",
}
export type ResizeMethod = "crop" | "scale";
// TODO move to http-api after TSification
export interface IAbortablePromise<T> extends Promise<T> {
abort(): void;
}
export type IdServerUnbindResult = "no-support" | "success";
// Knock and private are reserved keywords which are not yet implemented.
export enum JoinRule {
Public = "public",
Invite = "invite",
/**
* @deprecated Reserved keyword. Should not be used. Not yet implemented.
*/
Private = "private",
Knock = "knock",
Restricted = "restricted",
}
export enum RestrictedAllowType {
RoomMembership = "m.room_membership",
}
export interface IJoinRuleEventContent {
join_rule: JoinRule; // eslint-disable-line camelcase
allow?: {
type: RestrictedAllowType;
room_id: string; // eslint-disable-line camelcase
}[];
}
export enum GuestAccess {
CanJoin = "can_join",
Forbidden = "forbidden",
}
export enum HistoryVisibility {
Invited = "invited",
Joined = "joined",
Shared = "shared",
WorldReadable = "world_readable",
}
export interface IUsageLimit {
// "hs_disabled" is NOT a specced string, but is used in Synapse
// This is tracked over at https://github.com/matrix-org/synapse/issues/9237
// eslint-disable-next-line camelcase
limit_type: "monthly_active_user" | "hs_disabled" | string;
// eslint-disable-next-line camelcase
admin_contact?: string;
}
+43 -7
View File
@@ -15,9 +15,13 @@ limitations under the License.
*/
import { Callback } from "../client";
import { IContent, IEvent } from "../models/event";
import { Preset, Visibility } from "./partials";
import { SearchKey } from "./search";
import { IRoomEventFilter } from "../filter";
import { Direction } from "../models/event-timeline";
// allow camelcase as these are things go onto the wire
// allow camelcase as these are things that go onto the wire
/* eslint-disable camelcase */
export interface IJoinRoomOpts {
@@ -63,12 +67,12 @@ export interface IGuestAccessOpts {
}
export interface ISearchOpts {
keys?: string[];
keys?: SearchKey[];
query: string;
}
export interface IEventSearchOpts {
filter: any; // TODO: Types
filter?: IRoomEventFilter;
term: string;
}
@@ -82,7 +86,7 @@ export interface IInvite3PID {
export interface ICreateRoomStateEvent {
type: string;
state_key?: string; // defaults to an empty string
content: object;
content: IContent;
}
export interface ICreateRoomOpts {
@@ -104,9 +108,11 @@ export interface IRoomDirectoryOptions {
server?: string;
limit?: number;
since?: string;
// TODO: Proper types
filter?: any & {generic_search_term: string};
filter?: {
generic_search_term: string;
};
include_all_networks?: boolean;
third_party_instance_id?: string;
}
export interface IUploadOpts {
@@ -119,4 +125,34 @@ export interface IUploadOpts {
progressHandler?: (state: {loaded: number, total: number}) => void;
}
export interface IAddThreePidOnlyBody {
auth?: {
type: string;
session?: string;
};
client_secret: string;
sid: string;
}
export interface IBindThreePidBody {
client_secret: string;
id_server: string;
id_access_token: string;
sid: string;
}
export interface IRelationsRequestOpts {
from?: string;
to?: string;
limit?: number;
direction?: Direction;
}
export interface IRelationsResponse {
original_event: IEvent;
chunk: IEvent[];
next_batch?: string;
prev_batch?: string;
}
/* eslint-enable camelcase */
+118
View File
@@ -0,0 +1,118 @@
/*
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.
*/
// Types relating to the /search API
import { IRoomEvent, IStateEvent } from "../sync-accumulator";
import { IRoomEventFilter } from "../filter";
import { SearchResult } from "../models/search-result";
/* eslint-disable camelcase */
export interface IEventWithRoomId extends IRoomEvent {
room_id: string;
}
export interface IStateEventWithRoomId extends IStateEvent {
room_id: string;
}
export interface IMatrixProfile {
avatar_url?: string;
displayname?: string;
}
export interface IResultContext {
events_before: IEventWithRoomId[];
events_after: IEventWithRoomId[];
profile_info: Record<string, IMatrixProfile>;
start?: string;
end?: string;
}
export interface ISearchResult {
rank: number;
result: IEventWithRoomId;
context: IResultContext;
}
enum GroupKey {
RoomId = "room_id",
Sender = "sender",
}
export interface IResultRoomEvents {
count: number;
highlights: string[];
results: ISearchResult[];
state?: { [roomId: string]: IStateEventWithRoomId[] };
groups?: {
[groupKey in GroupKey]: {
[value: string]: {
next_batch?: string;
order: number;
results: string[];
};
};
};
next_batch?: string;
}
interface IResultCategories {
room_events: IResultRoomEvents;
}
export type SearchKey = "content.body" | "content.name" | "content.topic";
export enum SearchOrderBy {
Recent = "recent",
Rank = "rank",
}
export interface ISearchRequestBody {
search_categories: {
room_events: {
search_term: string;
keys?: SearchKey[];
filter?: IRoomEventFilter;
order_by?: SearchOrderBy;
event_context?: {
before_limit?: number;
after_limit?: number;
include_profile?: boolean;
};
include_state?: boolean;
groupings?: {
group_by: {
key: GroupKey;
}[];
};
};
};
}
export interface ISearchResponse {
search_categories: IResultCategories;
}
export interface ISearchResults {
_query?: ISearchRequestBody;
results: SearchResult[];
highlights: string[];
count?: number;
next_batch?: string;
pendingRequest?: Promise<ISearchResults>;
}
/* eslint-enable camelcase */
+4
View File
@@ -19,3 +19,7 @@ export interface ISignatures {
[keyId: string]: string;
};
}
export interface ISigned {
signatures?: ISignatures;
}
+37
View File
@@ -0,0 +1,37 @@
/*
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 { IPublicRoomsChunkRoom } from "../client";
import { RoomType } from "./event";
import { IStrippedState } from "../sync-accumulator";
// Types relating to Rooms of type `m.space` and related APIs
/* eslint-disable camelcase */
export interface IHierarchyRelation extends IStrippedState {
origin_server_ts: number;
content: {
order?: string;
suggested?: boolean;
via?: string[];
};
}
export interface IHierarchyRoom extends IPublicRoomsChunkRoom {
room_type?: RoomType | string;
children_state: IHierarchyRelation[];
}
/* eslint-enable camelcase */
+40
View File
@@ -0,0 +1,40 @@
/*
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 { IdServerUnbindResult } from "./partials";
// Types relating to Synapse Admin APIs
/* eslint-disable camelcase */
export interface ISynapseAdminWhoisResponse {
user_id: string;
devices: {
[deviceId: string]: {
sessions: {
connections: {
ip: string;
last_seen: number; // millis since epoch
user_agent: string;
}[];
}[];
};
};
}
export interface ISynapseAdminDeactivateResponse {
id_server_unbind_result: IdServerUnbindResult;
}
/* eslint-enable camelcase */
+12 -10
View File
@@ -14,14 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export class MockBlob {
private contents: number[] = [];
public constructor(private parts: ArrayLike<number>[]) {
parts.forEach(p => Array.from(p).forEach(e => this.contents.push(e)));
}
public get size(): number {
return this.contents.length;
}
export enum ThreepidMedium {
Email = "email",
Phone = "msisdn",
}
// TODO: Are these types universal, or specific to just /account/3pid?
export interface IThreepid {
medium: ThreepidMedium;
address: string;
validated_at: number; // eslint-disable-line camelcase
added_at: number; // eslint-disable-line camelcase
bound?: boolean;
}
+16
View File
@@ -70,6 +70,22 @@ export class NamespacedValue<S extends string, U extends string> {
}
}
export class ServerControlledNamespacedValue<S extends string, U extends string>
extends NamespacedValue<S, U> {
private preferUnstable = false;
public setPreferUnstable(preferUnstable: boolean): void {
this.preferUnstable = preferUnstable;
}
public get name(): U | S {
if (this.stable && !this.preferUnstable) {
return this.stable;
}
return this.unstable;
}
}
/**
* Represents a namespaced value which prioritizes the unstable value over the stable
* value.
+22 -7
View File
@@ -16,21 +16,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
import { ListenerMap, TypedEventEmitter } from "./models/typed-event-emitter";
export class ReEmitter {
private target: EventEmitter;
constructor(private readonly target: EventEmitter) {}
constructor(target: EventEmitter) {
this.target = target;
}
reEmit(source: EventEmitter, eventNames: string[]) {
public reEmit(source: EventEmitter, eventNames: string[]): void {
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) => {
const forSource = (...args: any[]) => {
// 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
@@ -48,3 +47,19 @@ export class ReEmitter {
}
}
}
export class TypedReEmitter<
Events extends string,
Arguments extends ListenerMap<Events>,
> extends ReEmitter {
constructor(target: TypedEventEmitter<Events, Arguments>) {
super(target);
}
public reEmit<ReEmittedEvents extends string, T extends Events & ReEmittedEvents>(
source: TypedEventEmitter<ReEmittedEvents, any>,
eventNames: T[],
): void {
super.reEmit(source, eventNames);
}
}
+56 -133
View File
@@ -17,79 +17,20 @@ limitations under the License.
/** @module auto-discovery */
import { logger } from './logger';
import { URL as NodeURL } from "url";
import { IClientWellKnown, IWellKnownConfig } from "./client";
import { logger } from './logger';
// Dev note: Auto discovery is part of the spec.
// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
/**
* Description for what an automatically discovered client configuration
* would look like. Although this is a class, it is recommended that it
* be treated as an interface definition rather than as a class.
*
* Additional properties than those defined here may be present, and
* should follow the Java package naming convention.
*/
class DiscoveredClientConfig { // eslint-disable-line no-unused-vars
// Dev note: this is basically a copy/paste of the .well-known response
// object as defined in the spec. It does have additional information,
// however. Overall, this exists to serve as a place for documentation
// and not functionality.
// See https://matrix.org/docs/spec/client_server/r0.4.0.html#get-well-known-matrix-client
constructor() {
/**
* The homeserver configuration the client should use. This will
* always be present on the object.
* @type {{state: string, base_url: string}} The configuration.
*/
this["m.homeserver"] = {
/**
* The lookup result state. If this is anything other than
* AutoDiscovery.SUCCESS then base_url may be falsey. Additionally,
* if this is not AutoDiscovery.SUCCESS then the client should
* assume the other properties in the client config (such as
* the identity server configuration) are not valid.
*/
state: AutoDiscovery.PROMPT,
/**
* If the state is AutoDiscovery.FAIL_ERROR or .FAIL_PROMPT
* then this will contain a human-readable (English) message
* for what went wrong. If the state is none of those previously
* mentioned, this will be falsey.
*/
error: "Something went wrong",
/**
* The base URL clients should use to talk to the homeserver,
* particularly for the login process. May be falsey if the
* state is not AutoDiscovery.SUCCESS.
*/
base_url: "https://matrix.org",
};
/**
* The identity server configuration the client should use. This
* will always be present on teh object.
* @type {{state: string, base_url: string}} The configuration.
*/
this["m.identity_server"] = {
/**
* The lookup result state. If this is anything other than
* AutoDiscovery.SUCCESS then base_url may be falsey.
*/
state: AutoDiscovery.PROMPT,
/**
* The base URL clients should use for interacting with the
* identity server. May be falsey if the state is not
* AutoDiscovery.SUCCESS.
*/
base_url: "https://vector.im",
};
}
export enum AutoDiscoveryAction {
SUCCESS = "SUCCESS",
IGNORE = "IGNORE",
PROMPT = "PROMPT",
FAIL_PROMPT = "FAIL_PROMPT",
FAIL_ERROR = "FAIL_ERROR",
}
/**
@@ -102,55 +43,36 @@ export class AutoDiscovery {
// translate the meaning of the states in the spec, but also
// support our own if needed.
static get ERROR_INVALID() {
return "Invalid homeserver discovery response";
}
public static readonly ERROR_INVALID = "Invalid homeserver discovery response";
static get ERROR_GENERIC_FAILURE() {
return "Failed to get autodiscovery configuration from server";
}
public static readonly ERROR_GENERIC_FAILURE = "Failed to get autodiscovery configuration from server";
static get ERROR_INVALID_HS_BASE_URL() {
return "Invalid base_url for m.homeserver";
}
public static readonly ERROR_INVALID_HS_BASE_URL = "Invalid base_url for m.homeserver";
static get ERROR_INVALID_HOMESERVER() {
return "Homeserver URL does not appear to be a valid Matrix homeserver";
}
public static readonly ERROR_INVALID_HOMESERVER = "Homeserver URL does not appear to be a valid Matrix homeserver";
static get ERROR_INVALID_IS_BASE_URL() {
return "Invalid base_url for m.identity_server";
}
public static readonly ERROR_INVALID_IS_BASE_URL = "Invalid base_url for m.identity_server";
static get ERROR_INVALID_IDENTITY_SERVER() {
return "Identity server URL does not appear to be a valid identity server";
}
// eslint-disable-next-line
public static readonly ERROR_INVALID_IDENTITY_SERVER = "Identity server URL does not appear to be a valid identity server";
static get ERROR_INVALID_IS() {
return "Invalid identity server discovery response";
}
public static readonly ERROR_INVALID_IS = "Invalid identity server discovery response";
static get ERROR_MISSING_WELLKNOWN() {
return "No .well-known JSON file found";
}
public static readonly ERROR_MISSING_WELLKNOWN = "No .well-known JSON file found";
static get ERROR_INVALID_JSON() {
return "Invalid JSON";
}
public static readonly ERROR_INVALID_JSON = "Invalid JSON";
static get ALL_ERRORS() {
return [
AutoDiscovery.ERROR_INVALID,
AutoDiscovery.ERROR_GENERIC_FAILURE,
AutoDiscovery.ERROR_INVALID_HS_BASE_URL,
AutoDiscovery.ERROR_INVALID_HOMESERVER,
AutoDiscovery.ERROR_INVALID_IS_BASE_URL,
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
AutoDiscovery.ERROR_INVALID_IS,
AutoDiscovery.ERROR_MISSING_WELLKNOWN,
AutoDiscovery.ERROR_INVALID_JSON,
];
}
public static readonly ALL_ERRORS = [
AutoDiscovery.ERROR_INVALID,
AutoDiscovery.ERROR_GENERIC_FAILURE,
AutoDiscovery.ERROR_INVALID_HS_BASE_URL,
AutoDiscovery.ERROR_INVALID_HOMESERVER,
AutoDiscovery.ERROR_INVALID_IS_BASE_URL,
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
AutoDiscovery.ERROR_INVALID_IS,
AutoDiscovery.ERROR_MISSING_WELLKNOWN,
AutoDiscovery.ERROR_INVALID_JSON,
];
/**
* The auto discovery failed. The client is expected to communicate
@@ -158,7 +80,7 @@ export class AutoDiscovery {
* @return {string}
* @constructor
*/
static get FAIL_ERROR() { return "FAIL_ERROR"; }
public static readonly FAIL_ERROR = AutoDiscoveryAction.FAIL_ERROR;
/**
* The auto discovery failed, however the client may still recover
@@ -169,7 +91,7 @@ export class AutoDiscovery {
* @return {string}
* @constructor
*/
static get FAIL_PROMPT() { return "FAIL_PROMPT"; }
public static readonly FAIL_PROMPT = AutoDiscoveryAction.FAIL_PROMPT;
/**
* The auto discovery didn't fail but did not find anything of
@@ -178,14 +100,14 @@ export class AutoDiscovery {
* @return {string}
* @constructor
*/
static get PROMPT() { return "PROMPT"; }
public static readonly PROMPT = AutoDiscoveryAction.PROMPT;
/**
* The auto discovery was successful.
* @return {string}
* @constructor
*/
static get SUCCESS() { return "SUCCESS"; }
public static readonly SUCCESS = AutoDiscoveryAction.SUCCESS;
/**
* Validates and verifies client configuration information for purposes
@@ -193,13 +115,13 @@ export class AutoDiscovery {
* and identity server URL the client would want. Additional details
* may also be included, and will be transparently brought into the
* response object unaltered.
* @param {string} wellknown The configuration object itself, as returned
* @param {object} wellknown The configuration object itself, as returned
* by the .well-known auto-discovery endpoint.
* @return {Promise<DiscoveredClientConfig>} Resolves to the verified
* configuration, which may include error states. Rejects on unexpected
* failure, not when verification fails.
*/
static async fromDiscoveryConfig(wellknown) {
public static async fromDiscoveryConfig(wellknown: any): Promise<IClientWellKnown> {
// Step 1 is to get the config, which is provided to us here.
// We default to an error state to make the first few checks easier to
@@ -240,7 +162,7 @@ export class AutoDiscovery {
// Step 2: Make sure the homeserver URL is valid *looking*. We'll make
// sure it points to a homeserver in Step 3.
const hsUrl = this._sanitizeWellKnownUrl(
const hsUrl = this.sanitizeWellKnownUrl(
wellknown["m.homeserver"]["base_url"],
);
if (!hsUrl) {
@@ -250,7 +172,7 @@ export class AutoDiscovery {
}
// Step 3: Make sure the homeserver URL points to a homeserver.
const hsVersions = await this._fetchWellKnownObject(
const hsVersions = await this.fetchWellKnownObject(
`${hsUrl}/_matrix/client/versions`,
);
if (!hsVersions || !hsVersions.raw["versions"]) {
@@ -272,7 +194,7 @@ export class AutoDiscovery {
};
// Step 5: Try to pull out the identity server configuration
let isUrl = "";
let isUrl: string | boolean = "";
if (wellknown["m.identity_server"]) {
// We prepare a failing identity server response to save lines later
// in this branch.
@@ -287,7 +209,7 @@ export class AutoDiscovery {
// Step 5a: Make sure the URL is valid *looking*. We'll make sure it
// points to an identity server in Step 5b.
isUrl = this._sanitizeWellKnownUrl(
isUrl = this.sanitizeWellKnownUrl(
wellknown["m.identity_server"]["base_url"],
);
if (!isUrl) {
@@ -299,10 +221,10 @@ export class AutoDiscovery {
// Step 5b: Verify there is an identity server listening on the provided
// URL.
const isResponse = await this._fetchWellKnownObject(
const isResponse = await this.fetchWellKnownObject(
`${isUrl}/_matrix/identity/api/v1`,
);
if (!isResponse || !isResponse.raw || isResponse.action !== "SUCCESS") {
if (!isResponse || !isResponse.raw || isResponse.action !== AutoDiscoveryAction.SUCCESS) {
logger.error("Invalid /api/v1 response");
failingClientConfig["m.identity_server"].error =
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
@@ -317,7 +239,7 @@ export class AutoDiscovery {
// Step 6: Now that the identity server is valid, or never existed,
// populate the IS section.
if (isUrl && isUrl.length > 0) {
if (isUrl && isUrl.toString().length > 0) {
clientConfig["m.identity_server"] = {
state: AutoDiscovery.SUCCESS,
error: null,
@@ -359,7 +281,7 @@ export class AutoDiscovery {
* configuration, which may include error states. Rejects on unexpected
* failure, not when discovery fails.
*/
static async findClientConfig(domain) {
public static async findClientConfig(domain: string): Promise<IClientWellKnown> {
if (!domain || typeof(domain) !== "string" || domain.length === 0) {
throw new Error("'domain' must be a string of non-zero length");
}
@@ -395,13 +317,13 @@ export class AutoDiscovery {
// Step 1: Actually request the .well-known JSON file and make sure it
// at least has a homeserver definition.
const wellknown = await this._fetchWellKnownObject(
const wellknown = await this.fetchWellKnownObject(
`https://${domain}/.well-known/matrix/client`,
);
if (!wellknown || wellknown.action !== "SUCCESS") {
if (!wellknown || wellknown.action !== AutoDiscoveryAction.SUCCESS) {
logger.error("No response or error when parsing .well-known");
if (wellknown.reason) logger.error(wellknown.reason);
if (wellknown.action === "IGNORE") {
if (wellknown.action === AutoDiscoveryAction.IGNORE) {
clientConfig["m.homeserver"] = {
state: AutoDiscovery.PROMPT,
error: null,
@@ -427,12 +349,12 @@ export class AutoDiscovery {
* @returns {Promise<object>} Resolves to the domain's client config. Can
* be an empty object.
*/
static async getRawClientConfig(domain) {
public static async getRawClientConfig(domain: string): Promise<IClientWellKnown> {
if (!domain || typeof(domain) !== "string" || domain.length === 0) {
throw new Error("'domain' must be a string of non-zero length");
}
const response = await this._fetchWellKnownObject(
const response = await this.fetchWellKnownObject(
`https://${domain}/.well-known/matrix/client`,
);
if (!response) return {};
@@ -447,7 +369,7 @@ export class AutoDiscovery {
* @return {string|boolean} The sanitized URL or a falsey value if the URL is invalid.
* @private
*/
static _sanitizeWellKnownUrl(url) {
private static sanitizeWellKnownUrl(url: string): string | boolean {
if (!url) return false;
try {
@@ -495,8 +417,9 @@ export class AutoDiscovery {
* @return {Promise<object>} Resolves to the returned state.
* @private
*/
static async _fetchWellKnownObject(url) {
private static async fetchWellKnownObject(url: string): Promise<IWellKnownConfig> {
return new Promise(function(resolve, reject) {
// eslint-disable-next-line
const request = require("./matrix").getRequest();
if (!request) throw new Error("No request library available");
request(
@@ -505,10 +428,10 @@ export class AutoDiscovery {
if (err || response &&
(response.statusCode < 200 || response.statusCode >= 300)
) {
let action = "FAIL_PROMPT";
let action = AutoDiscoveryAction.FAIL_PROMPT;
let reason = (err ? err.message : null) || "General failure";
if (response && response.statusCode === 404) {
action = "IGNORE";
action = AutoDiscoveryAction.IGNORE;
reason = AutoDiscovery.ERROR_MISSING_WELLKNOWN;
}
resolve({ raw: {}, action: action, reason: reason, error: err });
@@ -516,7 +439,7 @@ export class AutoDiscovery {
}
try {
resolve({ raw: JSON.parse(body), action: "SUCCESS" });
resolve({ raw: JSON.parse(body), action: AutoDiscoveryAction.SUCCESS });
} catch (e) {
let reason = AutoDiscovery.ERROR_INVALID;
if (e.name === "SyntaxError") {
@@ -524,7 +447,7 @@ export class AutoDiscovery {
}
resolve({
raw: {},
action: "FAIL_PROMPT",
action: AutoDiscoveryAction.FAIL_PROMPT,
reason: reason,
error: e,
});
+6 -1
View File
@@ -14,10 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as matrixcs from "./matrix";
import request from "browser-request";
import queryString from "qs";
import * as matrixcs from "./matrix";
if (matrixcs.getRequest()) {
throw new Error("Multiple matrix-js-sdk entrypoints detected!");
}
matrixcs.request(function(opts, fn) {
// We manually fix the query string for browser-request because
// it doesn't correctly handle cases like ?via=one&via=two. Instead
+2111 -1300
View File
File diff suppressed because it is too large Load Diff
-99
View File
@@ -1,99 +0,0 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 ContentHelpers */
/**
* Generates the content for a HTML Message event
* @param {string} body the plaintext body of the message
* @param {string} htmlBody the HTML representation of the message
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlMessage(body, htmlBody) {
return {
msgtype: "m.text",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a HTML Notice event
* @param {string} body the plaintext body of the notice
* @param {string} htmlBody the HTML representation of the notice
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlNotice(body, htmlBody) {
return {
msgtype: "m.notice",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a HTML Emote event
* @param {string} body the plaintext body of the emote
* @param {string} htmlBody the HTML representation of the emote
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlEmote(body, htmlBody) {
return {
msgtype: "m.emote",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a Plaintext Message event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
export function makeTextMessage(body) {
return {
msgtype: "m.text",
body: body,
};
}
/**
* Generates the content for a Plaintext Notice event
* @param {string} body the plaintext body of the notice
* @returns {{msgtype: string, body: string}}
*/
export function makeNotice(body) {
return {
msgtype: "m.notice",
body: body,
};
}
/**
* Generates the content for a Plaintext Emote event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
export function makeEmoteMessage(body) {
return {
msgtype: "m.emote",
body: body,
};
}
+265
View File
@@ -0,0 +1,265 @@
/*
Copyright 2018 - 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/** @module ContentHelpers */
import { REFERENCE_RELATION } from "matrix-events-sdk";
import { MBeaconEventContent, MBeaconInfoContent, MBeaconInfoEventContent, M_BEACON_INFO } from "./@types/beacon";
import { MsgType } from "./@types/event";
import { TEXT_NODE_TYPE } from "./@types/extensible_events";
import {
M_ASSET,
LocationAssetType,
M_LOCATION,
M_TIMESTAMP,
LocationEventWireContent,
MLocationEventContent,
MLocationContent,
MAssetContent,
LegacyLocationEventContent,
} from "./@types/location";
/**
* Generates the content for a HTML Message event
* @param {string} body the plaintext body of the message
* @param {string} htmlBody the HTML representation of the message
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlMessage(body: string, htmlBody: string) {
return {
msgtype: MsgType.Text,
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a HTML Notice event
* @param {string} body the plaintext body of the notice
* @param {string} htmlBody the HTML representation of the notice
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlNotice(body: string, htmlBody: string) {
return {
msgtype: MsgType.Notice,
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a HTML Emote event
* @param {string} body the plaintext body of the emote
* @param {string} htmlBody the HTML representation of the emote
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
export function makeHtmlEmote(body: string, htmlBody: string) {
return {
msgtype: MsgType.Emote,
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
}
/**
* Generates the content for a Plaintext Message event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
export function makeTextMessage(body: string) {
return {
msgtype: MsgType.Text,
body: body,
};
}
/**
* Generates the content for a Plaintext Notice event
* @param {string} body the plaintext body of the notice
* @returns {{msgtype: string, body: string}}
*/
export function makeNotice(body: string) {
return {
msgtype: MsgType.Notice,
body: body,
};
}
/**
* Generates the content for a Plaintext Emote event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
export function makeEmoteMessage(body: string) {
return {
msgtype: MsgType.Emote,
body: body,
};
}
/** Location content helpers */
export const getTextForLocationEvent = (
uri: string,
assetType: LocationAssetType,
timestamp: number,
description?: string,
): string => {
const date = `at ${new Date(timestamp).toISOString()}`;
const assetName = assetType === LocationAssetType.Self ? 'User' : undefined;
const quotedDescription = description ? `"${description}"` : undefined;
return [
assetName,
'Location',
quotedDescription,
uri,
date,
].filter(Boolean).join(' ');
};
/**
* Generates the content for a Location event
* @param uri a geo:// uri for the location
* @param ts the timestamp when the location was correct (milliseconds since
* the UNIX epoch)
* @param description the (optional) label for this location on the map
* @param asset_type the (optional) asset type of this location e.g. "m.self"
* @param text optional. A text for the location
*/
export const makeLocationContent = (
// this is first but optional
// to avoid a breaking change
text: string | undefined,
uri: string,
timestamp?: number,
description?: string,
assetType?: LocationAssetType,
): LegacyLocationEventContent & MLocationEventContent => {
const defaultedText = text ??
getTextForLocationEvent(uri, assetType || LocationAssetType.Self, timestamp, description);
const timestampEvent = timestamp ? { [M_TIMESTAMP.name]: timestamp } : {};
return {
msgtype: MsgType.Location,
body: defaultedText,
geo_uri: uri,
[M_LOCATION.name]: {
description,
uri,
},
[M_ASSET.name]: {
type: assetType || LocationAssetType.Self,
},
[TEXT_NODE_TYPE.name]: defaultedText,
...timestampEvent,
} as LegacyLocationEventContent & MLocationEventContent;
};
/**
* Parse location event content and transform to
* a backwards compatible modern m.location event format
*/
export const parseLocationEvent = (wireEventContent: LocationEventWireContent): MLocationEventContent => {
const location = M_LOCATION.findIn<MLocationContent>(wireEventContent);
const asset = M_ASSET.findIn<MAssetContent>(wireEventContent);
const timestamp = M_TIMESTAMP.findIn<number>(wireEventContent);
const text = TEXT_NODE_TYPE.findIn<string>(wireEventContent);
const geoUri = location?.uri ?? wireEventContent?.geo_uri;
const description = location?.description;
const assetType = asset?.type ?? LocationAssetType.Self;
const fallbackText = text ?? wireEventContent.body;
return makeLocationContent(fallbackText, geoUri, timestamp, description, assetType);
};
/**
* Beacon event helpers
*/
export type MakeBeaconInfoContent = (
timeout: number,
isLive?: boolean,
description?: string,
assetType?: LocationAssetType,
timestamp?: number
) => MBeaconInfoEventContent;
export const makeBeaconInfoContent: MakeBeaconInfoContent = (
timeout,
isLive,
description,
assetType,
timestamp,
) => ({
[M_BEACON_INFO.name]: {
description,
timeout,
live: isLive,
},
[M_TIMESTAMP.name]: timestamp || Date.now(),
[M_ASSET.name]: {
type: assetType ?? LocationAssetType.Self,
},
});
export type BeaconInfoState = MBeaconInfoContent & {
assetType: LocationAssetType;
timestamp: number;
};
/**
* Flatten beacon info event content
*/
export const parseBeaconInfoContent = (content: MBeaconInfoEventContent): BeaconInfoState => {
const { description, timeout, live } = M_BEACON_INFO.findIn<MBeaconInfoContent>(content);
const { type: assetType } = M_ASSET.findIn<MAssetContent>(content);
const timestamp = M_TIMESTAMP.findIn<number>(content);
return {
description,
timeout,
live,
assetType,
timestamp,
};
};
export type MakeBeaconContent = (
uri: string,
timestamp: number,
beaconInfoId: string,
description?: string,
) => MBeaconEventContent;
export const makeBeaconContent: MakeBeaconContent = (
uri,
timestamp,
beaconInfoId,
description,
) => ({
[M_LOCATION.name]: {
description,
uri,
},
[M_TIMESTAMP.name]: timestamp,
"m.relates_to": {
rel_type: REFERENCE_RELATION.name,
event_id: beaconInfoId,
},
});
+16 -11
View File
@@ -1,6 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2015 - 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.
@@ -34,8 +33,14 @@ import * as utils from "./utils";
* for such URLs.
* @return {string} The complete URL to the content.
*/
export function getHttpUriForMxc(baseUrl, mxc, width, height,
resizeMethod, allowDirectLinks) {
export function getHttpUriForMxc(
baseUrl: string,
mxc: string,
width: number,
height: number,
resizeMethod: string,
allowDirectLinks = false,
): string {
if (typeof mxc !== "string" || !mxc) {
return '';
}
@@ -48,16 +53,16 @@ export function getHttpUriForMxc(baseUrl, mxc, width, height,
}
let serverAndMediaId = mxc.slice(6); // strips mxc://
let prefix = "/_matrix/media/r0/download/";
const params = {};
const params: Record<string, string> = {};
if (width) {
params.width = Math.round(width);
params["width"] = Math.round(width).toString();
}
if (height) {
params.height = Math.round(height);
params["height"] = Math.round(height).toString();
}
if (resizeMethod) {
params.method = resizeMethod;
params["method"] = resizeMethod;
}
if (Object.keys(params).length > 0) {
// these are thumbnailing params so they probably want the
@@ -71,7 +76,7 @@ export function getHttpUriForMxc(baseUrl, mxc, width, height,
fragment = serverAndMediaId.substr(fragmentOffset);
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
}
return baseUrl + prefix + serverAndMediaId +
(Object.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params))) + fragment;
const urlParams = (Object.keys(params).length === 0 ? "" : ("?" + utils.encodeParams(params)));
return baseUrl + prefix + serverAndMediaId + urlParams + fragment;
}
@@ -1,6 +1,5 @@
/*
Copyright 2019 New Vector Ltd
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.
@@ -20,22 +19,51 @@ limitations under the License.
* @module crypto/CrossSigning
*/
import { PkSigning } from "@matrix-org/olm";
import { decodeBase64, encodeBase64, pkSign, pkVerify } from './olmlib';
import { EventEmitter } from 'events';
import { logger } from '../logger';
import { IndexedDBCryptoStore } from '../crypto/store/indexeddb-crypto-store';
import { decryptAES, encryptAES } from './aes';
import { DeviceInfo } from "./deviceinfo";
import { SecretStorage } from "./SecretStorage";
import { ICrossSigningKey, ISignedKey, MatrixClient } from "../client";
import { OlmDevice } from "./OlmDevice";
import { ICryptoCallbacks } from "../matrix";
import { ISignatures } from "../@types/signed";
import { CryptoStore } from "./store/base";
import { ISecretStorageKeyInfo } from "./api";
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
function publicKeyFromKeyInfo(keyInfo) {
function publicKeyFromKeyInfo(keyInfo: ICrossSigningKey): string {
// `keys` is an object with { [`ed25519:${pubKey}`]: pubKey }
// We assume only a single key, and we want the bare form without type
// prefix, so we select the values.
return Object.values(keyInfo.keys)[0];
}
export class CrossSigningInfo extends EventEmitter {
export interface ICacheCallbacks {
getCrossSigningKeyCache?(type: string, expectedPublicKey?: string): Promise<Uint8Array>;
storeCrossSigningKeyCache?(type: string, key: Uint8Array): Promise<void>;
}
export interface ICrossSigningInfo {
keys: Record<string, ICrossSigningKey>;
firstUse: boolean;
crossSigningVerifiedBefore: boolean;
}
export class CrossSigningInfo {
public keys: Record<string, ICrossSigningKey> = {};
public firstUse = true;
// This tracks whether we've ever verified this user with any identity.
// When you verify a user, any devices online at the time that receive
// the verifying signature via the homeserver will latch this to true
// and can use it in the future to detect cases where the user has
// become unverified later for any reason.
private crossSigningVerifiedBefore = false;
/**
* Information about a user's cross-signing keys
*
@@ -46,27 +74,13 @@ export class CrossSigningInfo extends EventEmitter {
* Requires getCrossSigningKey and saveCrossSigningKeys
* @param {object} cacheCallbacks Callbacks used to interact with the cache
*/
constructor(userId, callbacks, cacheCallbacks) {
super();
constructor(
public readonly userId: string,
private callbacks: ICryptoCallbacks = {},
private cacheCallbacks: ICacheCallbacks = {},
) {}
// you can't change the userId
Object.defineProperty(this, 'userId', {
enumerable: true,
value: userId,
});
this._callbacks = callbacks || {};
this._cacheCallbacks = cacheCallbacks || {};
this.keys = {};
this.firstUse = true;
// This tracks whether we've ever verified this user with any identity.
// When you verify a user, any devices online at the time that receive
// the verifying signature via the homeserver will latch this to true
// and can use it in the future to detect cases where the user has
// become unverifed later for any reason.
this.crossSigningVerifiedBefore = false;
}
static fromStorage(obj, userId) {
public static fromStorage(obj: ICrossSigningInfo, userId: string): CrossSigningInfo {
const res = new CrossSigningInfo(userId);
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
@@ -76,7 +90,7 @@ export class CrossSigningInfo extends EventEmitter {
return res;
}
toStorage() {
public toStorage(): ICrossSigningInfo {
return {
keys: this.keys,
firstUse: this.firstUse,
@@ -92,10 +106,10 @@ export class CrossSigningInfo extends EventEmitter {
* the stored public key for the given key type.
* @returns {Array} An array with [ public key, Olm.PkSigning ]
*/
async getCrossSigningKey(type, expectedPubkey) {
public async getCrossSigningKey(type: string, expectedPubkey?: string): Promise<[string, PkSigning]> {
const shouldCache = ["master", "self_signing", "user_signing"].indexOf(type) >= 0;
if (!this._callbacks.getCrossSigningKey) {
if (!this.callbacks.getCrossSigningKey) {
throw new Error("No getCrossSigningKey callback supplied");
}
@@ -103,7 +117,7 @@ export class CrossSigningInfo extends EventEmitter {
expectedPubkey = this.getId(type);
}
function validateKey(key) {
function validateKey(key: Uint8Array): [string, PkSigning] {
if (!key) return;
const signing = new global.Olm.PkSigning();
const gotPubkey = signing.init_with_seed(key);
@@ -114,9 +128,8 @@ export class CrossSigningInfo extends EventEmitter {
}
let privkey;
if (this._cacheCallbacks.getCrossSigningKeyCache && shouldCache) {
privkey = await this._cacheCallbacks
.getCrossSigningKeyCache(type, expectedPubkey);
if (this.cacheCallbacks.getCrossSigningKeyCache && shouldCache) {
privkey = await this.cacheCallbacks.getCrossSigningKeyCache(type, expectedPubkey);
}
const cacheresult = validateKey(privkey);
@@ -124,11 +137,11 @@ export class CrossSigningInfo extends EventEmitter {
return cacheresult;
}
privkey = await this._callbacks.getCrossSigningKey(type, expectedPubkey);
privkey = await this.callbacks.getCrossSigningKey(type, expectedPubkey);
const result = validateKey(privkey);
if (result) {
if (this._cacheCallbacks.storeCrossSigningKeyCache && shouldCache) {
await this._cacheCallbacks.storeCrossSigningKeyCache(type, privkey);
if (this.cacheCallbacks.storeCrossSigningKeyCache && shouldCache) {
await this.cacheCallbacks.storeCrossSigningKeyCache(type, privkey);
}
return result;
}
@@ -156,12 +169,11 @@ export class CrossSigningInfo extends EventEmitter {
* with, or null if it is not present or not encrypted with a trusted
* key
*/
async isStoredInSecretStorage(secretStorage) {
public async isStoredInSecretStorage(secretStorage: SecretStorage): Promise<Record<string, object>> {
// check what SSSS keys have encrypted the master key (if any)
const stored =
await secretStorage.isStored("m.cross_signing.master", false) || {};
const stored = await secretStorage.isStored("m.cross_signing.master", false) || {};
// then check which of those SSSS keys have also encrypted the SSK and USK
function intersect(s) {
function intersect(s: Record<string, ISecretStorageKeyInfo>) {
for (const k of Object.keys(stored)) {
if (!s[k]) {
delete stored[k];
@@ -169,9 +181,7 @@ export class CrossSigningInfo extends EventEmitter {
}
}
for (const type of ["self_signing", "user_signing"]) {
intersect(
await secretStorage.isStored(`m.cross_signing.${type}`, false) || {},
);
intersect(await secretStorage.isStored(`m.cross_signing.${type}`, false) || {});
}
return Object.keys(stored).length ? stored : null;
}
@@ -184,7 +194,10 @@ export class CrossSigningInfo extends EventEmitter {
* @param {Map} keys The keys to store
* @param {SecretStorage} secretStorage The secret store using account data
*/
static async storeInSecretStorage(keys, secretStorage) {
public static async storeInSecretStorage(
keys: Map<string, Uint8Array>,
secretStorage: SecretStorage,
): Promise<void> {
for (const [type, privateKey] of keys) {
const encodedKey = encodeBase64(privateKey);
await secretStorage.store(`m.cross_signing.${type}`, encodedKey);
@@ -200,7 +213,7 @@ export class CrossSigningInfo extends EventEmitter {
* @param {SecretStorage} secretStorage The secret store using account data
* @return {Uint8Array} The private key
*/
static async getFromSecretStorage(type, secretStorage) {
public static async getFromSecretStorage(type: string, secretStorage: SecretStorage): Promise<Uint8Array> {
const encodedKey = await secretStorage.get(`m.cross_signing.${type}`);
if (!encodedKey) {
return null;
@@ -215,8 +228,8 @@ export class CrossSigningInfo extends EventEmitter {
* "self_signing", or "user_signing". Optional, will check all by default.
* @returns {boolean} True if all keys are stored in the local cache.
*/
async isStoredInKeyCache(type) {
const cacheCallbacks = this._cacheCallbacks;
public async isStoredInKeyCache(type?: string): Promise<boolean> {
const cacheCallbacks = this.cacheCallbacks;
if (!cacheCallbacks) return false;
const types = type ? [type] : ["master", "self_signing", "user_signing"];
for (const t of types) {
@@ -232,9 +245,9 @@ export class CrossSigningInfo extends EventEmitter {
*
* @returns {Map} A map from key type (string) to private key (Uint8Array)
*/
async getCrossSigningKeysFromCache() {
public async getCrossSigningKeysFromCache(): Promise<Map<string, Uint8Array>> {
const keys = new Map();
const cacheCallbacks = this._cacheCallbacks;
const cacheCallbacks = this.cacheCallbacks;
if (!cacheCallbacks) return keys;
for (const type of ["master", "self_signing", "user_signing"]) {
const privKey = await cacheCallbacks.getCrossSigningKeyCache(type);
@@ -255,8 +268,7 @@ export class CrossSigningInfo extends EventEmitter {
*
* @return {string} the ID
*/
getId(type) {
type = type || "master";
public getId(type = "master"): string {
if (!this.keys[type]) return null;
const keyInfo = this.keys[type];
return publicKeyFromKeyInfo(keyInfo);
@@ -269,8 +281,8 @@ export class CrossSigningInfo extends EventEmitter {
*
* @param {CrossSigningLevel} level The key types to reset
*/
async resetKeys(level) {
if (!this._callbacks.saveCrossSigningKeys) {
public async resetKeys(level?: CrossSigningLevel): Promise<void> {
if (!this.callbacks.saveCrossSigningKeys) {
throw new Error("No saveCrossSigningKeys callback supplied");
}
@@ -285,12 +297,12 @@ export class CrossSigningInfo extends EventEmitter {
CrossSigningLevel.USER_SIGNING |
CrossSigningLevel.SELF_SIGNING
);
} else if (level === 0) {
} else if (level === 0 as CrossSigningLevel) {
return;
}
const privateKeys = {};
const keys = {};
const privateKeys: Record<string, Uint8Array> = {};
const keys: Record<string, ICrossSigningKey> = {};
let masterSigning;
let masterPub;
@@ -347,7 +359,7 @@ export class CrossSigningInfo extends EventEmitter {
}
Object.assign(this.keys, keys);
this._callbacks.saveCrossSigningKeys(privateKeys);
this.callbacks.saveCrossSigningKeys(privateKeys);
} finally {
if (masterSigning) {
masterSigning.free();
@@ -358,12 +370,12 @@ export class CrossSigningInfo extends EventEmitter {
/**
* unsets the keys, used when another session has reset the keys, to disable cross-signing
*/
clearKeys() {
public clearKeys(): void {
this.keys = {};
}
setKeys(keys) {
const signingKeys = {};
public setKeys(keys: Record<string, ICrossSigningKey>): void {
const signingKeys: Record<string, ICrossSigningKey> = {};
if (keys.master) {
if (keys.master.user_id !== this.userId) {
const error = "Mismatched user ID " + keys.master.user_id +
@@ -434,7 +446,7 @@ export class CrossSigningInfo extends EventEmitter {
}
}
updateCrossSigningVerifiedBefore(isCrossSigningVerified) {
public updateCrossSigningVerifiedBefore(isCrossSigningVerified: boolean): void {
// It is critical that this value latches forward from false to true but
// never back to false to avoid a downgrade attack.
if (!this.crossSigningVerifiedBefore && isCrossSigningVerified) {
@@ -442,7 +454,7 @@ export class CrossSigningInfo extends EventEmitter {
}
}
async signObject(data, type) {
public async signObject<T extends object>(data: T, type: string): Promise<T & { signatures: ISignatures }> {
if (!this.keys[type]) {
throw new Error(
"Attempted to sign with " + type + " key but no such key present",
@@ -451,13 +463,13 @@ export class CrossSigningInfo extends EventEmitter {
const [pubkey, signing] = await this.getCrossSigningKey(type);
try {
pkSign(data, signing, this.userId, pubkey);
return data;
return data as T & { signatures: ISignatures };
} finally {
signing.free();
}
}
async signUser(key) {
public async signUser(key: CrossSigningInfo): Promise<ICrossSigningKey> {
if (!this.keys.user_signing) {
logger.info("No user signing key: not signing user");
return;
@@ -465,7 +477,7 @@ export class CrossSigningInfo extends EventEmitter {
return this.signObject(key.keys.master, "user_signing");
}
async signDevice(userId, device) {
public async signDevice(userId: string, device: DeviceInfo): Promise<ISignedKey> {
if (userId !== this.userId) {
throw new Error(
`Trying to sign ${userId}'s device; can only sign our own device`,
@@ -475,7 +487,7 @@ export class CrossSigningInfo extends EventEmitter {
logger.info("No self signing key: not signing device");
return;
}
return this.signObject(
return this.signObject<Omit<ISignedKey, "signatures">>(
{
algorithms: device.algorithms,
keys: device.keys,
@@ -492,7 +504,7 @@ export class CrossSigningInfo extends EventEmitter {
*
* @returns {UserTrustLevel}
*/
checkUserTrust(userCrossSigning) {
public checkUserTrust(userCrossSigning: CrossSigningInfo): UserTrustLevel {
// if we're checking our own key, then it's trusted if the master key
// and self-signing key match
if (this.userId === userCrossSigning.userId
@@ -530,12 +542,17 @@ export class CrossSigningInfo extends EventEmitter {
*
* @param {CrossSigningInfo} userCrossSigning Cross signing info for user
* @param {module:crypto/deviceinfo} device The device to check
* @param {bool} localTrust Whether the device is trusted locally
* @param {bool} trustCrossSignedDevices Whether we trust cross signed devices
* @param {boolean} localTrust Whether the device is trusted locally
* @param {boolean} trustCrossSignedDevices Whether we trust cross signed devices
*
* @returns {DeviceTrustLevel}
*/
checkDeviceTrust(userCrossSigning, device, localTrust, trustCrossSignedDevices) {
public checkDeviceTrust(
userCrossSigning: CrossSigningInfo,
device: DeviceInfo,
localTrust: boolean,
trustCrossSignedDevices: boolean,
): DeviceTrustLevel {
const userTrust = this.checkUserTrust(userCrossSigning);
const userSSK = userCrossSigning.keys.self_signing;
@@ -552,29 +569,23 @@ export class CrossSigningInfo extends EventEmitter {
// if we can verify the user's SSK from their master key...
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
// ...and this device's key from their SSK...
pkVerify(
deviceObj, publicKeyFromKeyInfo(userSSK), userCrossSigning.userId,
);
pkVerify(deviceObj, publicKeyFromKeyInfo(userSSK), userCrossSigning.userId);
// ...then we trust this device as much as far as we trust the user
return DeviceTrustLevel.fromUserTrustLevel(
userTrust, localTrust, trustCrossSignedDevices,
);
return DeviceTrustLevel.fromUserTrustLevel(userTrust, localTrust, trustCrossSignedDevices);
} catch (e) {
return new DeviceTrustLevel(
false, false, localTrust, trustCrossSignedDevices,
);
return new DeviceTrustLevel(false, false, localTrust, trustCrossSignedDevices);
}
}
/**
* @returns {object} Cache callbacks
*/
getCacheCallbacks() {
return this._cacheCallbacks;
public getCacheCallbacks(): ICacheCallbacks {
return this.cacheCallbacks;
}
}
function deviceToObject(device, userId) {
function deviceToObject(device: DeviceInfo, userId: string) {
return {
algorithms: device.algorithms,
keys: device.keys,
@@ -584,49 +595,49 @@ function deviceToObject(device, userId) {
};
}
export const CrossSigningLevel = {
MASTER: 4,
USER_SIGNING: 2,
SELF_SIGNING: 1,
};
export enum CrossSigningLevel {
MASTER = 4,
USER_SIGNING = 2,
SELF_SIGNING = 1,
}
/**
* Represents the ways in which we trust a user
*/
export class UserTrustLevel {
constructor(crossSigningVerified, crossSigningVerifiedBefore, tofu) {
this._crossSigningVerified = crossSigningVerified;
this._crossSigningVerifiedBefore = crossSigningVerifiedBefore;
this._tofu = tofu;
}
constructor(
private readonly crossSigningVerified: boolean,
private readonly crossSigningVerifiedBefore: boolean,
private readonly tofu: boolean,
) {}
/**
* @returns {bool} true if this user is verified via any means
* @returns {boolean} true if this user is verified via any means
*/
isVerified() {
public isVerified(): boolean {
return this.isCrossSigningVerified();
}
/**
* @returns {bool} true if this user is verified via cross signing
* @returns {boolean} true if this user is verified via cross signing
*/
isCrossSigningVerified() {
return this._crossSigningVerified;
public isCrossSigningVerified(): boolean {
return this.crossSigningVerified;
}
/**
* @returns {bool} true if we ever verified this user before (at least for
* @returns {boolean} true if we ever verified this user before (at least for
* the history of verifications observed by this device).
*/
wasCrossSigningVerified() {
return this._crossSigningVerifiedBefore;
public wasCrossSigningVerified(): boolean {
return this.crossSigningVerifiedBefore;
}
/**
* @returns {bool} true if this user's key is trusted on first use
* @returns {boolean} true if this user's key is trusted on first use
*/
isTofu() {
return this._tofu;
public isTofu(): boolean {
return this.tofu;
}
}
@@ -634,58 +645,62 @@ export class UserTrustLevel {
* Represents the ways in which we trust a device
*/
export class DeviceTrustLevel {
constructor(crossSigningVerified, tofu, localVerified, trustCrossSignedDevices) {
this._crossSigningVerified = crossSigningVerified;
this._tofu = tofu;
this._localVerified = localVerified;
this._trustCrossSignedDevices = trustCrossSignedDevices;
}
constructor(
public readonly crossSigningVerified: boolean,
public readonly tofu: boolean,
private readonly localVerified: boolean,
private readonly trustCrossSignedDevices: boolean,
) {}
static fromUserTrustLevel(userTrustLevel, localVerified, trustCrossSignedDevices) {
public static fromUserTrustLevel(
userTrustLevel: UserTrustLevel,
localVerified: boolean,
trustCrossSignedDevices: boolean,
): DeviceTrustLevel {
return new DeviceTrustLevel(
userTrustLevel._crossSigningVerified,
userTrustLevel._tofu,
userTrustLevel.isCrossSigningVerified(),
userTrustLevel.isTofu(),
localVerified,
trustCrossSignedDevices,
);
}
/**
* @returns {bool} true if this device is verified via any means
* @returns {boolean} true if this device is verified via any means
*/
isVerified() {
public isVerified(): boolean {
return Boolean(this.isLocallyVerified() || (
this._trustCrossSignedDevices && this.isCrossSigningVerified()
this.trustCrossSignedDevices && this.isCrossSigningVerified()
));
}
/**
* @returns {bool} true if this device is verified via cross signing
* @returns {boolean} true if this device is verified via cross signing
*/
isCrossSigningVerified() {
return this._crossSigningVerified;
public isCrossSigningVerified(): boolean {
return this.crossSigningVerified;
}
/**
* @returns {bool} true if this device is verified locally
* @returns {boolean} true if this device is verified locally
*/
isLocallyVerified() {
return this._localVerified;
public isLocallyVerified(): boolean {
return this.localVerified;
}
/**
* @returns {bool} true if this device is trusted from a user's key
* @returns {boolean} true if this device is trusted from a user's key
* that is trusted on first use
*/
isTofu() {
return this._tofu;
public isTofu(): boolean {
return this.tofu;
}
}
export function createCryptoStoreCacheCallbacks(store, olmdevice) {
export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: OlmDevice): ICacheCallbacks {
return {
getCrossSigningKeyCache: async function(type, _expectedPublicKey) {
const key = await new Promise((resolve) => {
getCrossSigningKeyCache: async function(type: string, _expectedPublicKey: string): Promise<Uint8Array> {
const key = await new Promise<any>((resolve) => {
return store.doTxn(
'readonly',
[IndexedDBCryptoStore.STORE_ACCOUNT],
@@ -696,32 +711,34 @@ export function createCryptoStoreCacheCallbacks(store, olmdevice) {
});
if (key && key.ciphertext) {
const pickleKey = Buffer.from(olmdevice._pickleKey);
const pickleKey = Buffer.from(olmDevice.pickleKey);
const decrypted = await decryptAES(key, pickleKey, type);
return decodeBase64(decrypted);
} else {
return key;
}
},
storeCrossSigningKeyCache: async function(type, key) {
storeCrossSigningKeyCache: async function(type: string, key: Uint8Array): Promise<void> {
if (!(key instanceof Uint8Array)) {
throw new Error(
`storeCrossSigningKeyCache expects Uint8Array, got ${key}`,
);
}
const pickleKey = Buffer.from(olmdevice._pickleKey);
key = await encryptAES(encodeBase64(key), pickleKey, type);
const pickleKey = Buffer.from(olmDevice.pickleKey);
const encryptedKey = await encryptAES(encodeBase64(key), pickleKey, type);
return store.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
store.storeSecretStorePrivateKey(txn, type, key);
store.storeSecretStorePrivateKey(txn, type, encryptedKey);
},
);
},
};
}
export type KeysDuringVerification = [[string, PkSigning], [string, PkSigning], [string, PkSigning], void];
/**
* Request cross-signing keys from another device during verification.
*
@@ -729,17 +746,21 @@ export function createCryptoStoreCacheCallbacks(store, olmdevice) {
* @param {string} userId The user ID being verified
* @param {string} deviceId The device ID being verified
*/
export async function requestKeysDuringVerification(baseApis, userId, deviceId) {
export function requestKeysDuringVerification(
baseApis: MatrixClient,
userId: string,
deviceId: string,
): Promise<KeysDuringVerification | void> {
// If this is a self-verification, ask the other party for keys
if (baseApis.getUserId() !== userId) {
return;
}
logger.log("Cross-signing: Self-verification done; requesting keys");
// This happens asynchronously, and we're not concerned about waiting for
// it. We return here in order to test.
return new Promise((resolve, reject) => {
// it. We return here in order to test.
return new Promise<KeysDuringVerification | void>((resolve, reject) => {
const client = baseApis;
const original = client.crypto._crossSigningInfo;
const original = client.crypto.crossSigningInfo;
// We already have all of the infrastructure we need to validate and
// cache cross-signing keys, so instead of replicating that, here we set
@@ -748,8 +769,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
const crossSigning = new CrossSigningInfo(
original.userId,
{ getCrossSigningKey: async (type) => {
logger.debug("Cross-signing: requesting secret",
type, deviceId);
logger.debug("Cross-signing: requesting secret", type, deviceId);
const { promise } = client.requestSecret(
`m.cross_signing.${type}`, [deviceId],
);
@@ -757,7 +777,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
const decoded = decodeBase64(result);
return Uint8Array.from(decoded);
} },
original._cacheCallbacks,
original.getCacheCallbacks(),
);
crossSigning.keys = original.keys;
@@ -765,7 +785,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
// https://github.com/vector-im/element-web/issues/12604
// then change here to reject on the timeout
// Requests can be ignored, so don't wait around forever
const timeout = new Promise((resolve, reject) => {
const timeout = new Promise<void>((resolve) => {
setTimeout(
resolve,
KEY_REQUEST_TIMEOUT_MS,
@@ -774,7 +794,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
});
// also request and cache the key backup key
const backupKeyPromise = new Promise(async resolve => {
const backupKeyPromise = (async () => {
const cachedKey = await client.crypto.getSessionBackupPrivateKey();
if (!cachedKey) {
logger.info("No cached backup key found. Requesting...");
@@ -785,29 +805,26 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
logger.info("Got key backup key, decoding...");
const decodedKey = decodeBase64(base64Key);
logger.info("Decoded backup key, storing...");
client.crypto.storeSessionBackupPrivateKey(
await client.crypto.storeSessionBackupPrivateKey(
Uint8Array.from(decodedKey),
);
logger.info("Backup key stored. Starting backup restore...");
const backupInfo = await client.getKeyBackupVersion();
// no need to await for this - just let it go in the bg
client.restoreKeyBackupWithCache(
undefined, undefined, backupInfo,
).then(() => {
client.restoreKeyBackupWithCache(undefined, undefined, backupInfo).then(() => {
logger.info("Backup restored.");
});
}
resolve();
});
})();
// We call getCrossSigningKey() for its side-effects
return Promise.race([
return Promise.race<KeysDuringVerification | void>([
Promise.all([
crossSigning.getCrossSigningKey("master"),
crossSigning.getCrossSigningKey("self_signing"),
crossSigning.getCrossSigningKey("user_signing"),
backupKeyPromise,
]),
]) as Promise<KeysDuringVerification>,
timeout,
]).then(resolve, reject);
}).catch((e) => {
File diff suppressed because it is too large Load Diff
@@ -1,11 +1,43 @@
/*
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 { logger } from "../logger";
import { MatrixEvent } from "../models/event";
import { EventEmitter } from "events";
import { createCryptoStoreCacheCallbacks } from "./CrossSigning";
import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning";
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { Method, PREFIX_UNSTABLE } from "../http-api";
import { Crypto, IBootstrapCrossSigningOpts } from "./index";
import {
PREFIX_UNSTABLE,
} from "../http-api";
ClientEvent,
CrossSigningKeys,
ClientEventHandlerMap,
ICrossSigningKey,
ICryptoCallbacks,
ISignedKey,
KeySignatures,
} from "../matrix";
import { ISecretStorageKeyInfo } from "./api";
import { IKeyBackupInfo } from "./keybackup";
import { TypedEventEmitter } from "../models/typed-event-emitter";
import { IAccountDataClient } from "./SecretStorage";
interface ICrossSigningKeys {
authUpload: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"];
keys: Record<string, ICrossSigningKey>;
}
/**
* Builds an EncryptionSetupOperation by calling any of the add.. methods.
@@ -17,18 +49,23 @@ import {
* more than once.
*/
export class EncryptionSetupBuilder {
public readonly accountDataClientAdapter: AccountDataClientAdapter;
public readonly crossSigningCallbacks: CrossSigningCallbacks;
public readonly ssssCryptoCallbacks: SSSSCryptoCallbacks;
private crossSigningKeys: ICrossSigningKeys = null;
private keySignatures: KeySignatures = null;
private keyBackupInfo: IKeyBackupInfo = null;
private sessionBackupPrivateKey: Uint8Array;
/**
* @param {Object.<String, MatrixEvent>} accountData pre-existing account data, will only be read, not written.
* @param {CryptoCallbacks} delegateCryptoCallbacks crypto callbacks to delegate to if the key isn't in cache yet
*/
constructor(accountData, delegateCryptoCallbacks) {
constructor(accountData: Record<string, MatrixEvent>, delegateCryptoCallbacks?: ICryptoCallbacks) {
this.accountDataClientAdapter = new AccountDataClientAdapter(accountData);
this.crossSigningCallbacks = new CrossSigningCallbacks();
this.ssssCryptoCallbacks = new SSSSCryptoCallbacks(delegateCryptoCallbacks);
this._crossSigningKeys = null;
this._keySignatures = null;
this._keyBackupInfo = null;
}
/**
@@ -42,8 +79,8 @@ export class EncryptionSetupBuilder {
* an empty authDict, to obtain the flows.
* @param {Object} keys the new keys
*/
addCrossSigningKeys(authUpload, keys) {
this._crossSigningKeys = { authUpload, keys };
public addCrossSigningKeys(authUpload: ICrossSigningKeys["authUpload"], keys: ICrossSigningKeys["keys"]): void {
this.crossSigningKeys = { authUpload, keys };
}
/**
@@ -54,8 +91,8 @@ export class EncryptionSetupBuilder {
*
* @param {Object} keyBackupInfo as received from/sent to the server
*/
addSessionBackup(keyBackupInfo) {
this._keyBackupInfo = keyBackupInfo;
public addSessionBackup(keyBackupInfo: IKeyBackupInfo): void {
this.keyBackupInfo = keyBackupInfo;
}
/**
@@ -65,8 +102,8 @@ export class EncryptionSetupBuilder {
*
* @param {Uint8Array} privateKey
*/
addSessionBackupPrivateKeyToCache(privateKey) {
this._sessionBackupPrivateKey = privateKey;
public addSessionBackupPrivateKeyToCache(privateKey: Uint8Array): void {
this.sessionBackupPrivateKey = privateKey;
}
/**
@@ -75,14 +112,14 @@ export class EncryptionSetupBuilder {
*
* @param {String} userId
* @param {String} deviceId
* @param {String} signature
* @param {Object} signature
*/
addKeySignature(userId, deviceId, signature) {
if (!this._keySignatures) {
this._keySignatures = {};
public addKeySignature(userId: string, deviceId: string, signature: ISignedKey): void {
if (!this.keySignatures) {
this.keySignatures = {};
}
const userSignatures = this._keySignatures[userId] || {};
this._keySignatures[userId] = userSignatures;
const userSignatures = this.keySignatures[userId] || {};
this.keySignatures[userId] = userSignatures;
userSignatures[deviceId] = signature;
}
@@ -91,21 +128,21 @@ export class EncryptionSetupBuilder {
* @param {Object} content
* @return {Promise}
*/
setAccountData(type, content) {
return this.accountDataClientAdapter.setAccountData(type, content);
public async setAccountData(type: string, content: object): Promise<void> {
await this.accountDataClientAdapter.setAccountData(type, content);
}
/**
* builds the operation containing all the parts that have been added to the builder
* @return {EncryptionSetupOperation}
*/
buildOperation() {
const accountData = this.accountDataClientAdapter._values;
public buildOperation(): EncryptionSetupOperation {
const accountData = this.accountDataClientAdapter.values;
return new EncryptionSetupOperation(
accountData,
this._crossSigningKeys,
this._keyBackupInfo,
this._keySignatures,
this.crossSigningKeys,
this.keyBackupInfo,
this.keySignatures,
);
}
@@ -118,28 +155,27 @@ export class EncryptionSetupBuilder {
* @param {Crypto} crypto
* @return {Promise}
*/
async persist(crypto) {
public async persist(crypto: Crypto): Promise<void> {
// store private keys in cache
if (this._crossSigningKeys) {
const cacheCallbacks = createCryptoStoreCacheCallbacks(
crypto._cryptoStore, crypto._olmDevice);
if (this.crossSigningKeys) {
const cacheCallbacks = createCryptoStoreCacheCallbacks(crypto.cryptoStore, crypto.olmDevice);
for (const type of ["master", "self_signing", "user_signing"]) {
logger.log(`Cache ${type} cross-signing private key locally`);
const privateKey = this.crossSigningCallbacks.privateKeys.get(type);
await cacheCallbacks.storeCrossSigningKeyCache(type, privateKey);
}
// store own cross-sign pubkeys as trusted
await crypto._cryptoStore.doTxn(
await crypto.cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
crypto._cryptoStore.storeCrossSigningKeys(
txn, this._crossSigningKeys.keys);
crypto.cryptoStore.storeCrossSigningKeys(
txn, this.crossSigningKeys.keys);
},
);
}
// store session backup key in cache
if (this._sessionBackupPrivateKey) {
await crypto.storeSessionBackupPrivateKey(this._sessionBackupPrivateKey);
if (this.sessionBackupPrivateKey) {
await crypto.storeSessionBackupPrivateKey(this.sessionBackupPrivateKey);
}
}
}
@@ -157,66 +193,66 @@ export class EncryptionSetupOperation {
* @param {Object} keyBackupInfo
* @param {Object} keySignatures
*/
constructor(accountData, crossSigningKeys, keyBackupInfo, keySignatures) {
this._accountData = accountData;
this._crossSigningKeys = crossSigningKeys;
this._keyBackupInfo = keyBackupInfo;
this._keySignatures = keySignatures;
}
constructor(
private readonly accountData: Map<string, object>,
private readonly crossSigningKeys: ICrossSigningKeys,
private readonly keyBackupInfo: IKeyBackupInfo,
private readonly keySignatures: KeySignatures,
) {}
/**
* Runs the (remaining part of, in the future) operation by sending requests to the server.
* @param {Crypto} crypto
* @param {Crypto} crypto
*/
async apply(crypto) {
const baseApis = crypto._baseApis;
public async apply(crypto: Crypto): Promise<void> {
const baseApis = crypto.baseApis;
// upload cross-signing keys
if (this._crossSigningKeys) {
const keys = {};
for (const [name, key] of Object.entries(this._crossSigningKeys.keys)) {
if (this.crossSigningKeys) {
const keys: Partial<CrossSigningKeys> = {};
for (const [name, key] of Object.entries(this.crossSigningKeys.keys)) {
keys[name + "_key"] = key;
}
// We must only call `uploadDeviceSigningKeys` from inside this auth
// helper to ensure we properly handle auth errors.
await this._crossSigningKeys.authUpload(authDict => {
return baseApis.uploadDeviceSigningKeys(authDict, keys);
await this.crossSigningKeys.authUpload(authDict => {
return baseApis.uploadDeviceSigningKeys(authDict, keys as CrossSigningKeys);
});
// pass the new keys to the main instance of our own CrossSigningInfo.
crypto._crossSigningInfo.setKeys(this._crossSigningKeys.keys);
crypto.crossSigningInfo.setKeys(this.crossSigningKeys.keys);
}
// set account data
if (this._accountData) {
for (const [type, content] of this._accountData) {
if (this.accountData) {
for (const [type, content] of this.accountData) {
await baseApis.setAccountData(type, content);
}
}
// upload first cross-signing signatures with the new key
// (e.g. signing our own device)
if (this._keySignatures) {
await baseApis.uploadKeySignatures(this._keySignatures);
if (this.keySignatures) {
await baseApis.uploadKeySignatures(this.keySignatures);
}
// need to create/update key backup info
if (this._keyBackupInfo) {
if (this._keyBackupInfo.version) {
if (this.keyBackupInfo) {
if (this.keyBackupInfo.version) {
// session backup signature
// The backup is trusted because the user provided the private key.
// Sign the backup with the cross signing key so the key backup can
// be trusted via cross-signing.
await baseApis.http.authedRequest(
undefined, "PUT", "/room_keys/version/" + this._keyBackupInfo.version,
undefined, Method.Put, "/room_keys/version/" + this.keyBackupInfo.version,
undefined, {
algorithm: this._keyBackupInfo.algorithm,
auth_data: this._keyBackupInfo.auth_data,
algorithm: this.keyBackupInfo.algorithm,
auth_data: this.keyBackupInfo.auth_data,
},
{ prefix: PREFIX_UNSTABLE },
);
} else {
// add new key backup
await baseApis.http.authedRequest(
undefined, "POST", "/room_keys/version",
undefined, this._keyBackupInfo,
undefined, Method.Post, "/room_keys/version",
undefined, this.keyBackupInfo,
{ prefix: PREFIX_UNSTABLE },
);
}
@@ -228,21 +264,24 @@ export class EncryptionSetupOperation {
* Catches account data set by SecretStorage during bootstrapping by
* implementing the methods related to account data in MatrixClient
*/
class AccountDataClientAdapter extends EventEmitter {
class AccountDataClientAdapter
extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap>
implements IAccountDataClient {
//
public readonly values = new Map<string, MatrixEvent>();
/**
* @param {Object.<String, MatrixEvent>} accountData existing account data
* @param {Object.<String, MatrixEvent>} existingValues existing account data
*/
constructor(accountData) {
constructor(private readonly existingValues: Record<string, MatrixEvent>) {
super();
this._existingValues = accountData;
this._values = new Map();
}
/**
* @param {String} type
* @return {Promise<Object>} the content of the account data
*/
getAccountDataFromServer(type) {
public getAccountDataFromServer(type: string): Promise<any> {
return Promise.resolve(this.getAccountData(type));
}
@@ -250,12 +289,12 @@ class AccountDataClientAdapter extends EventEmitter {
* @param {String} type
* @return {Object} the content of the account data
*/
getAccountData(type) {
const modifiedValue = this._values.get(type);
public getAccountData(type: string): MatrixEvent {
const modifiedValue = this.values.get(type);
if (modifiedValue) {
return modifiedValue;
}
const existingValue = this._existingValues[type];
const existingValue = this.existingValues[type];
if (existingValue) {
return existingValue.getContent();
}
@@ -267,15 +306,16 @@ class AccountDataClientAdapter extends EventEmitter {
* @param {Object} content
* @return {Promise}
*/
setAccountData(type, content) {
const lastEvent = this._values.get(type);
this._values.set(type, content);
public setAccountData(type: string, content: any): Promise<{}> {
const lastEvent = this.values.get(type);
this.values.set(type, content);
// ensure accountData is emitted on the next tick,
// as SecretStorage listens for it while calling this method
// and it seems to rely on this.
return Promise.resolve().then(() => {
const event = new MatrixEvent({ type, content });
this.emit("accountData", event, lastEvent);
this.emit(ClientEvent.AccountData, event, lastEvent);
return {};
});
}
}
@@ -285,27 +325,25 @@ class AccountDataClientAdapter extends EventEmitter {
* by both cache callbacks (see createCryptoStoreCacheCallbacks) as non-cache callbacks.
* See CrossSigningInfo constructor
*/
class CrossSigningCallbacks {
constructor() {
this.privateKeys = new Map();
}
class CrossSigningCallbacks implements ICryptoCallbacks, ICacheCallbacks {
public readonly privateKeys = new Map<string, Uint8Array>();
// cache callbacks
getCrossSigningKeyCache(type, expectedPublicKey) {
public getCrossSigningKeyCache(type: string, expectedPublicKey: string): Promise<Uint8Array> {
return this.getCrossSigningKey(type, expectedPublicKey);
}
storeCrossSigningKeyCache(type, key) {
public storeCrossSigningKeyCache(type: string, key: Uint8Array): Promise<void> {
this.privateKeys.set(type, key);
return Promise.resolve();
}
// non-cache callbacks
getCrossSigningKey(type, _expectedPubkey) {
public getCrossSigningKey(type: string, expectedPubkey: string): Promise<Uint8Array> {
return Promise.resolve(this.privateKeys.get(type));
}
saveCrossSigningKeys(privateKeys) {
public saveCrossSigningKeys(privateKeys: Record<string, Uint8Array>) {
for (const [type, privateKey] of Object.entries(privateKeys)) {
this.privateKeys.set(type, privateKey);
}
@@ -317,39 +355,37 @@ class CrossSigningCallbacks {
* the SecretStorage crypto callbacks
*/
class SSSSCryptoCallbacks {
constructor(delegateCryptoCallbacks) {
this._privateKeys = new Map();
this._delegateCryptoCallbacks = delegateCryptoCallbacks;
}
private readonly privateKeys = new Map<string, Uint8Array>();
async getSecretStorageKey({ keys }, name) {
constructor(private readonly delegateCryptoCallbacks?: ICryptoCallbacks) {}
public async getSecretStorageKey(
{ keys }: { keys: Record<string, ISecretStorageKeyInfo> },
name: string,
): Promise<[string, Uint8Array]|null> {
for (const keyId of Object.keys(keys)) {
const privateKey = this._privateKeys.get(keyId);
const privateKey = this.privateKeys.get(keyId);
if (privateKey) {
return [keyId, privateKey];
}
}
// if we don't have the key cached yet, ask
// for it to the general crypto callbacks and cache it
if (this._delegateCryptoCallbacks) {
const result = await this._delegateCryptoCallbacks.
if (this?.delegateCryptoCallbacks?.getSecretStorageKey) {
const result = await this.delegateCryptoCallbacks.
getSecretStorageKey({ keys }, name);
if (result) {
const [keyId, privateKey] = result;
this._privateKeys.set(keyId, privateKey);
this.privateKeys.set(keyId, privateKey);
}
return result;
}
return null;
}
addPrivateKey(keyId, keyInfo, privKey) {
this._privateKeys.set(keyId, privKey);
public addPrivateKey(keyId: string, keyInfo: ISecretStorageKeyInfo, privKey: Uint8Array): void {
this.privateKeys.set(keyId, privKey);
// Also pass along to application to cache if it wishes
if (
this._delegateCryptoCallbacks &&
this._delegateCryptoCallbacks.cacheSecretStorageKey
) {
this._delegateCryptoCallbacks.cacheSecretStorageKey(keyId, keyInfo, privKey);
}
this.delegateCryptoCallbacks?.cacheSecretStorageKey?.(keyId, keyInfo, privKey);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,5 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2017 - 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.
@@ -15,6 +15,10 @@ limitations under the License.
*/
import { logger } from '../logger';
import { MatrixClient } from "../client";
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "./index";
import { CryptoStore, OutgoingRoomKeyRequest } from './store/base';
import { EventType } from "../@types/event";
/**
* Internal module. Management of outgoing room key requests.
@@ -57,61 +61,58 @@ const SEND_KEY_REQUESTS_DELAY_MS = 500;
*
* @enum {number}
*/
export const ROOM_KEY_REQUEST_STATES = {
export enum RoomKeyRequestState {
/** request not yet sent */
UNSENT: 0,
Unsent,
/** request sent, awaiting reply */
SENT: 1,
Sent,
/** reply received, cancellation not yet sent */
CANCELLATION_PENDING: 2,
CancellationPending,
/**
* Cancellation not yet sent and will transition to UNSENT instead of
* being deleted once the cancellation has been sent.
*/
CANCELLATION_PENDING_AND_WILL_RESEND: 3,
};
CancellationPendingAndWillResend,
}
export class OutgoingRoomKeyRequestManager {
constructor(baseApis, deviceId, cryptoStore) {
this._baseApis = baseApis;
this._deviceId = deviceId;
this._cryptoStore = cryptoStore;
// handle for the delayed call to sendOutgoingRoomKeyRequests. Non-null
// if the callback has been set, or if it is still running.
private sendOutgoingRoomKeyRequestsTimer: number = null;
// handle for the delayed call to _sendOutgoingRoomKeyRequests. Non-null
// if the callback has been set, or if it is still running.
this._sendOutgoingRoomKeyRequestsTimer = null;
// sanity check to ensure that we don't end up with two concurrent runs
// of sendOutgoingRoomKeyRequests
private sendOutgoingRoomKeyRequestsRunning = false;
// sanity check to ensure that we don't end up with two concurrent runs
// of _sendOutgoingRoomKeyRequests
this._sendOutgoingRoomKeyRequestsRunning = false;
private clientRunning = false;
this._clientRunning = false;
}
constructor(
private readonly baseApis: MatrixClient,
private readonly deviceId: string,
private readonly cryptoStore: CryptoStore,
) {}
/**
* Called when the client is started. Sets background processes running.
*/
start() {
this._clientRunning = true;
public start(): void {
this.clientRunning = true;
}
/**
* Called when the client is stopped. Stops any running background processes.
*/
stop() {
public stop(): void {
logger.log('stopping OutgoingRoomKeyRequestManager');
// stop the timer on the next run
this._clientRunning = false;
this.clientRunning = false;
}
/**
* Send any requests that have been queued
*/
sendQueuedRequests() {
this._startTimer();
public sendQueuedRequests(): void {
this.startTimer();
}
/**
@@ -131,95 +132,97 @@ export class OutgoingRoomKeyRequestManager {
* pending list (or we have established that a similar request already
* exists)
*/
async queueRoomKeyRequest(requestBody, recipients, resend=false) {
const req = await this._cryptoStore.getOutgoingRoomKeyRequest(
requestBody,
);
public async queueRoomKeyRequest(
requestBody: IRoomKeyRequestBody,
recipients: IRoomKeyRequestRecipient[],
resend = false,
): Promise<void> {
const req = await this.cryptoStore.getOutgoingRoomKeyRequest(requestBody);
if (!req) {
await this._cryptoStore.getOrAddOutgoingRoomKeyRequest({
await this.cryptoStore.getOrAddOutgoingRoomKeyRequest({
requestBody: requestBody,
recipients: recipients,
requestId: this._baseApis.makeTxnId(),
state: ROOM_KEY_REQUEST_STATES.UNSENT,
requestId: this.baseApis.makeTxnId(),
state: RoomKeyRequestState.Unsent,
});
} else {
switch (req.state) {
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND:
case ROOM_KEY_REQUEST_STATES.UNSENT:
// nothing to do here, since we're going to send a request anyways
return;
case RoomKeyRequestState.CancellationPendingAndWillResend:
case RoomKeyRequestState.Unsent:
// nothing to do here, since we're going to send a request anyways
return;
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING: {
// existing request is about to be cancelled. If we want to
// resend, then change the state so that it resends after
// cancelling. Otherwise, just cancel the cancellation.
const state = resend ?
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND :
ROOM_KEY_REQUEST_STATES.SENT;
await this._cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING, {
state,
cancellationTxnId: this._baseApis.makeTxnId(),
},
);
break;
}
case ROOM_KEY_REQUEST_STATES.SENT: {
// a request has already been sent. If we don't want to
// resend, then do nothing. If we do want to, then cancel the
// existing request and send a new one.
if (resend) {
const state =
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND;
const updatedReq =
await this._cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.SENT, {
state,
cancellationTxnId: this._baseApis.makeTxnId(),
// need to use a new transaction ID so that
// the request gets sent
requestTxnId: this._baseApis.makeTxnId(),
},
);
if (!updatedReq) {
// updateOutgoingRoomKeyRequest couldn't find the request
// in state ROOM_KEY_REQUEST_STATES.SENT, so we must have
// raced with another tab to mark the request cancelled.
// Try again, to make sure the request is resent.
return await this.queueRoomKeyRequest(
requestBody, recipients, resend,
);
}
// We don't want to wait for the timer, so we send it
// immediately. (We might actually end up racing with the timer,
// but that's ok: even if we make the request twice, we'll do it
// with the same transaction_id, so only one message will get
// sent).
//
// (We also don't want to wait for the response from the server
// here, as it will slow down processing of received keys if we
// do.)
try {
await this._sendOutgoingRoomKeyRequestCancellation(
updatedReq,
true,
);
} catch (e) {
logger.error(
"Error sending room key request cancellation;"
+ " will retry later.", e,
);
}
// The request has transitioned from
// CANCELLATION_PENDING_AND_WILL_RESEND to UNSENT. We
// still need to resend the request which is now UNSENT, so
// start the timer if it isn't already started.
case RoomKeyRequestState.CancellationPending: {
// existing request is about to be cancelled. If we want to
// resend, then change the state so that it resends after
// cancelling. Otherwise, just cancel the cancellation.
const state = resend ?
RoomKeyRequestState.CancellationPendingAndWillResend :
RoomKeyRequestState.Sent;
await this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, RoomKeyRequestState.CancellationPending, {
state,
cancellationTxnId: this.baseApis.makeTxnId(),
},
);
break;
}
break;
}
default:
throw new Error('unhandled state: ' + req.state);
case RoomKeyRequestState.Sent: {
// a request has already been sent. If we don't want to
// resend, then do nothing. If we do want to, then cancel the
// existing request and send a new one.
if (resend) {
const state =
RoomKeyRequestState.CancellationPendingAndWillResend;
const updatedReq =
await this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, RoomKeyRequestState.Sent, {
state,
cancellationTxnId: this.baseApis.makeTxnId(),
// need to use a new transaction ID so that
// the request gets sent
requestTxnId: this.baseApis.makeTxnId(),
},
);
if (!updatedReq) {
// updateOutgoingRoomKeyRequest couldn't find the request
// in state ROOM_KEY_REQUEST_STATES.SENT, so we must have
// raced with another tab to mark the request cancelled.
// Try again, to make sure the request is resent.
return await this.queueRoomKeyRequest(
requestBody, recipients, resend,
);
}
// We don't want to wait for the timer, so we send it
// immediately. (We might actually end up racing with the timer,
// but that's ok: even if we make the request twice, we'll do it
// with the same transaction_id, so only one message will get
// sent).
//
// (We also don't want to wait for the response from the server
// here, as it will slow down processing of received keys if we
// do.)
try {
await this.sendOutgoingRoomKeyRequestCancellation(
updatedReq,
true,
);
} catch (e) {
logger.error(
"Error sending room key request cancellation;"
+ " will retry later.", e,
);
}
// The request has transitioned from
// CANCELLATION_PENDING_AND_WILL_RESEND to UNSENT. We
// still need to resend the request which is now UNSENT, so
// start the timer if it isn't already started.
}
break;
}
default:
throw new Error('unhandled state: ' + req.state);
}
}
}
@@ -232,21 +235,21 @@ export class OutgoingRoomKeyRequestManager {
* @returns {Promise} resolves when the request has been updated in our
* pending list.
*/
cancelRoomKeyRequest(requestBody) {
return this._cryptoStore.getOutgoingRoomKeyRequest(
public cancelRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<unknown> {
return this.cryptoStore.getOutgoingRoomKeyRequest(
requestBody,
).then((req) => {
).then((req): unknown => {
if (!req) {
// no request was made for this key
return;
}
switch (req.state) {
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING:
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND:
case RoomKeyRequestState.CancellationPending:
case RoomKeyRequestState.CancellationPendingAndWillResend:
// nothing to do here
return;
case ROOM_KEY_REQUEST_STATES.UNSENT:
case RoomKeyRequestState.Unsent:
// just delete it
// FIXME: ghahah we may have attempted to send it, and
@@ -258,16 +261,14 @@ export class OutgoingRoomKeyRequestManager {
'deleting unnecessary room key request for ' +
stringifyRequestBody(requestBody),
);
return this._cryptoStore.deleteOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.UNSENT,
);
return this.cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId, RoomKeyRequestState.Unsent);
case ROOM_KEY_REQUEST_STATES.SENT: {
case RoomKeyRequestState.Sent: {
// send a cancellation.
return this._cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.SENT, {
state: ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING,
cancellationTxnId: this._baseApis.makeTxnId(),
return this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, RoomKeyRequestState.Sent, {
state: RoomKeyRequestState.CancellationPending,
cancellationTxnId: this.baseApis.makeTxnId(),
},
).then((updatedReq) => {
if (!updatedReq) {
@@ -294,14 +295,14 @@ export class OutgoingRoomKeyRequestManager {
// (We also don't want to wait for the response from the server
// here, as it will slow down processing of received keys if we
// do.)
this._sendOutgoingRoomKeyRequestCancellation(
this.sendOutgoingRoomKeyRequestCancellation(
updatedReq,
).catch((e) => {
logger.error(
"Error sending room key request cancellation;"
+ " will retry later.", e,
);
this._startTimer();
this.startTimer();
});
});
}
@@ -320,10 +321,8 @@ export class OutgoingRoomKeyRequestManager {
* @return {Promise} resolves to a list of all the
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
*/
getOutgoingSentRoomKeyRequest(userId, deviceId) {
return this._cryptoStore.getOutgoingRoomKeyRequestsByTarget(
userId, deviceId, [ROOM_KEY_REQUEST_STATES.SENT],
);
public getOutgoingSentRoomKeyRequest(userId: string, deviceId: string): Promise<OutgoingRoomKeyRequest[]> {
return this.cryptoStore.getOutgoingRoomKeyRequestsByTarget(userId, deviceId, [RoomKeyRequestState.Sent]);
}
/**
@@ -333,29 +332,27 @@ export class OutgoingRoomKeyRequestManager {
* For example, after initialization or self-verification.
* @return {Promise} An array of `queueRoomKeyRequest` outputs.
*/
async cancelAndResendAllOutgoingRequests() {
const outgoings = await this._cryptoStore.getAllOutgoingRoomKeyRequestsByState(
ROOM_KEY_REQUEST_STATES.SENT,
);
public async cancelAndResendAllOutgoingRequests(): Promise<void[]> {
const outgoings = await this.cryptoStore.getAllOutgoingRoomKeyRequestsByState(RoomKeyRequestState.Sent);
return Promise.all(outgoings.map(({ requestBody, recipients }) =>
this.queueRoomKeyRequest(requestBody, recipients, true)));
}
// start the background timer to send queued requests, if the timer isn't
// already running
_startTimer() {
if (this._sendOutgoingRoomKeyRequestsTimer) {
private startTimer(): void {
if (this.sendOutgoingRoomKeyRequestsTimer) {
return;
}
const startSendingOutgoingRoomKeyRequests = () => {
if (this._sendOutgoingRoomKeyRequestsRunning) {
if (this.sendOutgoingRoomKeyRequestsRunning) {
throw new Error("RoomKeyRequestSend already in progress!");
}
this._sendOutgoingRoomKeyRequestsRunning = true;
this.sendOutgoingRoomKeyRequestsRunning = true;
this._sendOutgoingRoomKeyRequests().finally(() => {
this._sendOutgoingRoomKeyRequestsRunning = false;
this.sendOutgoingRoomKeyRequests().finally(() => {
this.sendOutgoingRoomKeyRequestsRunning = false;
}).catch((e) => {
// this should only happen if there is an indexeddb error,
// in which case we're a bit stuffed anyway.
@@ -365,7 +362,7 @@ export class OutgoingRoomKeyRequestManager {
});
};
this._sendOutgoingRoomKeyRequestsTimer = global.setTimeout(
this.sendOutgoingRoomKeyRequestsTimer = setTimeout(
startSendingOutgoingRoomKeyRequests,
SEND_KEY_REQUESTS_DELAY_MS,
);
@@ -374,47 +371,47 @@ export class OutgoingRoomKeyRequestManager {
// look for and send any queued requests. Runs itself recursively until
// there are no more requests, or there is an error (in which case, the
// timer will be restarted before the promise resolves).
_sendOutgoingRoomKeyRequests() {
if (!this._clientRunning) {
this._sendOutgoingRoomKeyRequestsTimer = null;
private sendOutgoingRoomKeyRequests(): Promise<void> {
if (!this.clientRunning) {
this.sendOutgoingRoomKeyRequestsTimer = null;
return Promise.resolve();
}
return this._cryptoStore.getOutgoingRoomKeyRequestByState([
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING,
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND,
ROOM_KEY_REQUEST_STATES.UNSENT,
]).then((req) => {
return this.cryptoStore.getOutgoingRoomKeyRequestByState([
RoomKeyRequestState.CancellationPending,
RoomKeyRequestState.CancellationPendingAndWillResend,
RoomKeyRequestState.Unsent,
]).then((req: OutgoingRoomKeyRequest) => {
if (!req) {
this._sendOutgoingRoomKeyRequestsTimer = null;
this.sendOutgoingRoomKeyRequestsTimer = null;
return;
}
let prom;
switch (req.state) {
case ROOM_KEY_REQUEST_STATES.UNSENT:
prom = this._sendOutgoingRoomKeyRequest(req);
case RoomKeyRequestState.Unsent:
prom = this.sendOutgoingRoomKeyRequest(req);
break;
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING:
prom = this._sendOutgoingRoomKeyRequestCancellation(req);
case RoomKeyRequestState.CancellationPending:
prom = this.sendOutgoingRoomKeyRequestCancellation(req);
break;
case ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND:
prom = this._sendOutgoingRoomKeyRequestCancellation(req, true);
case RoomKeyRequestState.CancellationPendingAndWillResend:
prom = this.sendOutgoingRoomKeyRequestCancellation(req, true);
break;
}
return prom.then(() => {
// go around the loop again
return this._sendOutgoingRoomKeyRequests();
return this.sendOutgoingRoomKeyRequests();
}).catch((e) => {
logger.error("Error sending room key request; will retry later.", e);
this._sendOutgoingRoomKeyRequestsTimer = null;
this.sendOutgoingRoomKeyRequestsTimer = null;
});
});
}
// given a RoomKeyRequest, send it and update the request record
_sendOutgoingRoomKeyRequest(req) {
private sendOutgoingRoomKeyRequest(req: OutgoingRoomKeyRequest): Promise<unknown> {
logger.log(
`Requesting keys for ${stringifyRequestBody(req.requestBody)}` +
` from ${stringifyRecipientList(req.recipients)}` +
@@ -423,24 +420,24 @@ export class OutgoingRoomKeyRequestManager {
const requestMessage = {
action: "request",
requesting_device_id: this._deviceId,
requesting_device_id: this.deviceId,
request_id: req.requestId,
body: req.requestBody,
};
return this._sendMessageToDevices(
return this.sendMessageToDevices(
requestMessage, req.recipients, req.requestTxnId || req.requestId,
).then(() => {
return this._cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.UNSENT,
{ state: ROOM_KEY_REQUEST_STATES.SENT },
return this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, RoomKeyRequestState.Unsent,
{ state: RoomKeyRequestState.Sent },
);
});
}
// Given a RoomKeyRequest, cancel it and delete the request record unless
// andResend is set, in which case transition to UNSENT.
_sendOutgoingRoomKeyRequestCancellation(req, andResend) {
private sendOutgoingRoomKeyRequestCancellation(req: OutgoingRoomKeyRequest, andResend = false): Promise<unknown> {
logger.log(
`Sending cancellation for key request for ` +
`${stringifyRequestBody(req.requestBody)} to ` +
@@ -450,30 +447,30 @@ export class OutgoingRoomKeyRequestManager {
const requestMessage = {
action: "request_cancellation",
requesting_device_id: this._deviceId,
requesting_device_id: this.deviceId,
request_id: req.requestId,
};
return this._sendMessageToDevices(
return this.sendMessageToDevices(
requestMessage, req.recipients, req.cancellationTxnId,
).then(() => {
if (andResend) {
// We want to resend, so transition to UNSENT
return this._cryptoStore.updateOutgoingRoomKeyRequest(
return this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId,
ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND,
{ state: ROOM_KEY_REQUEST_STATES.UNSENT },
RoomKeyRequestState.CancellationPendingAndWillResend,
{ state: RoomKeyRequestState.Unsent },
);
}
return this._cryptoStore.deleteOutgoingRoomKeyRequest(
req.requestId, ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING,
return this.cryptoStore.deleteOutgoingRoomKeyRequest(
req.requestId, RoomKeyRequestState.CancellationPending,
);
});
}
// send a RoomKeyRequest to a list of recipients
_sendMessageToDevices(message, recipients, txnId) {
const contentMap = {};
private sendMessageToDevices(message, recipients, txnId: string): Promise<{}> {
const contentMap: Record<string, Record<string, Record<string, any>>> = {};
for (const recip of recipients) {
if (!contentMap[recip.userId]) {
contentMap[recip.userId] = {};
@@ -481,9 +478,7 @@ export class OutgoingRoomKeyRequestManager {
contentMap[recip.userId][recip.deviceId] = message;
}
return this._baseApis.sendToDevice(
'm.room_key_request', contentMap, txnId,
);
return this.baseApis.sendToDevice(EventType.RoomKeyRequest, contentMap, txnId);
}
}
@@ -1,5 +1,5 @@
/*
Copyright 2018, 2019 New Vector Ltd
Copyright 2018 - 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.
@@ -20,45 +20,52 @@ limitations under the License.
* Manages the list of encrypted rooms
*/
import { CryptoStore } from './store/base';
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
/* eslint-disable camelcase */
export interface IRoomEncryption {
algorithm: string;
rotation_period_ms?: number;
rotation_period_msgs?: number;
}
/* eslint-enable camelcase */
/**
* @alias module:crypto/RoomList
*/
export class RoomList {
constructor(cryptoStore) {
this._cryptoStore = cryptoStore;
// Object of roomId -> room e2e info object (body of the m.room.encryption event)
private roomEncryption: Record<string, IRoomEncryption> = {};
// Object of roomId -> room e2e info object (body of the m.room.encryption event)
this._roomEncryption = {};
}
constructor(private readonly cryptoStore: CryptoStore) {}
async init() {
await this._cryptoStore.doTxn(
public async init(): Promise<void> {
await this.cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
this._cryptoStore.getEndToEndRooms(txn, (result) => {
this._roomEncryption = result;
this.cryptoStore.getEndToEndRooms(txn, (result) => {
this.roomEncryption = result;
});
},
);
}
getRoomEncryption(roomId) {
return this._roomEncryption[roomId] || null;
public getRoomEncryption(roomId: string): IRoomEncryption {
return this.roomEncryption[roomId] || null;
}
isRoomEncrypted(roomId) {
public isRoomEncrypted(roomId: string): boolean {
return Boolean(this.getRoomEncryption(roomId));
}
async setRoomEncryption(roomId, roomInfo) {
public async setRoomEncryption(roomId: string, roomInfo: IRoomEncryption): Promise<void> {
// important that this happens before calling into the store
// as it prevents the Crypto::setRoomEncryption from calling
// this twice for consecutive m.room.encryption events
this._roomEncryption[roomId] = roomInfo;
await this._cryptoStore.doTxn(
this.roomEncryption[roomId] = roomInfo;
await this.cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
this._cryptoStore.storeEndToEndRoom(roomId, roomInfo, txn);
this.cryptoStore.storeEndToEndRoom(roomId, roomInfo, txn);
},
);
}
@@ -1,5 +1,5 @@
/*
Copyright 2019, 2020 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.
@@ -14,61 +14,103 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventEmitter } from 'events';
import { logger } from '../logger';
import * as olmlib from './olmlib';
import { encodeBase64 } from './olmlib';
import { randomString } from '../randomstring';
import { encryptAES, decryptAES } from './aes';
import { encodeBase64 } from "./olmlib";
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes';
import { ClientEvent, ICryptoCallbacks, MatrixEvent } from '../matrix';
import { ClientEventHandlerMap, MatrixClient } from "../client";
import { IAddSecretStorageKeyOpts, ISecretStorageKeyInfo } from './api';
import { TypedEventEmitter } from '../models/typed-event-emitter';
export const SECRET_STORAGE_ALGORITHM_V1_AES
= "m.secret_storage.v1.aes-hmac-sha2";
export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2";
const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
// Some of the key functions use a tuple and some use an object...
export type SecretStorageKeyTuple = [keyId: string, keyInfo: ISecretStorageKeyInfo];
export type SecretStorageKeyObject = {keyId: string, keyInfo: ISecretStorageKeyInfo};
export interface ISecretRequest {
requestId: string;
promise: Promise<string>;
cancel: (reason: string) => void;
}
export interface IAccountDataClient extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap> {
// Subset of MatrixClient (which also uses any for the event content)
getAccountDataFromServer: <T extends {[k: string]: any}>(eventType: string) => Promise<T>;
getAccountData: (eventType: string) => MatrixEvent;
setAccountData: (eventType: string, content: any) => Promise<{}>;
}
interface ISecretRequestInternal {
name: string;
devices: string[];
resolve: (reason: string) => void;
reject: (error: Error) => void;
}
interface IDecryptors {
encrypt: (plaintext: string) => Promise<IEncryptedPayload>;
decrypt: (ciphertext: IEncryptedPayload) => Promise<string>;
}
interface ISecretInfo {
encrypted: {
// eslint-disable-next-line camelcase
key_id: IEncryptedPayload;
};
}
/**
* Implements Secure Secret Storage and Sharing (MSC1946)
* @module crypto/SecretStorage
*/
export class SecretStorage extends EventEmitter {
constructor(baseApis, cryptoCallbacks) {
super();
this._baseApis = baseApis;
this._cryptoCallbacks = cryptoCallbacks;
this._requests = {};
this._incomingRequests = {};
}
export class SecretStorage {
private requests = new Map<string, ISecretRequestInternal>();
async getDefaultKeyId() {
const defaultKey = await this._baseApis.getAccountDataFromServer(
// In it's pure javascript days, this was relying on some proper Javascript-style
// type-abuse where sometimes we'd pass in a fake client object with just the account
// data methods implemented, which is all this class needs unless you use the secret
// sharing code, so it was fine. As a low-touch TypeScript migration, this now has
// an extra, optional param for a real matrix client, so you can not pass it as long
// as you don't request any secrets.
// A better solution would probably be to split this class up into secret storage and
// secret sharing which are really two separate things, even though they share an MSC.
constructor(
private readonly accountDataAdapter: IAccountDataClient,
private readonly cryptoCallbacks: ICryptoCallbacks,
private readonly baseApis?: MatrixClient,
) {}
public async getDefaultKeyId(): Promise<string | null> {
const defaultKey = await this.accountDataAdapter.getAccountDataFromServer<{ key: string }>(
'm.secret_storage.default_key',
);
if (!defaultKey) return null;
return defaultKey.key;
}
setDefaultKeyId(keyId) {
return new Promise(async (resolve, reject) => {
const listener = (ev) => {
public setDefaultKeyId(keyId: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
const listener = (ev: MatrixEvent): void => {
if (
ev.getType() === 'm.secret_storage.default_key' &&
ev.getContent().key === keyId
) {
this._baseApis.removeListener('accountData', listener);
this.accountDataAdapter.removeListener(ClientEvent.AccountData, listener);
resolve();
}
};
this._baseApis.on('accountData', listener);
this.accountDataAdapter.on(ClientEvent.AccountData, listener);
try {
await this._baseApis.setAccountData(
'm.secret_storage.default_key',
{ key: keyId },
);
} catch (e) {
this._baseApis.removeListener('accountData', listener);
this.accountDataAdapter.setAccountData(
'm.secret_storage.default_key',
{ key: keyId },
).catch(e => {
this.accountDataAdapter.removeListener(ClientEvent.AccountData, listener);
reject(e);
}
});
});
}
@@ -85,10 +127,14 @@ export class SecretStorage extends EventEmitter {
* keyId: {string} the ID of the key
* keyInfo: {object} details about the key (iv, mac, passphrase)
*/
async addKey(algorithm, opts, keyId) {
const keyInfo = { algorithm };
public async addKey(
algorithm: string,
opts: IAddSecretStorageKeyOpts,
keyId?: string,
): Promise<SecretStorageKeyObject> {
const keyInfo = { algorithm } as ISecretStorageKeyInfo;
if (!opts) opts = {};
if (!opts) opts = {} as IAddSecretStorageKeyOpts;
if (opts.name) {
keyInfo.name = opts.name;
@@ -99,25 +145,25 @@ export class SecretStorage extends EventEmitter {
keyInfo.passphrase = opts.passphrase;
}
if (opts.key) {
const { iv, mac } = await SecretStorage._calculateKeyCheck(opts.key);
const { iv, mac } = await calculateKeyCheck(opts.key);
keyInfo.iv = iv;
keyInfo.mac = mac;
}
} else {
throw new Error(`Unknown key algorithm ${opts.algorithm}`);
throw new Error(`Unknown key algorithm ${algorithm}`);
}
if (!keyId) {
do {
keyId = randomString(32);
} while (
await this._baseApis.getAccountDataFromServer(
await this.accountDataAdapter.getAccountDataFromServer<ISecretStorageKeyInfo>(
`m.secret_storage.key.${keyId}`,
)
);
}
await this._baseApis.setAccountData(
await this.accountDataAdapter.setAccountData(
`m.secret_storage.key.${keyId}`, keyInfo,
);
@@ -134,8 +180,9 @@ export class SecretStorage extends EventEmitter {
* for. Defaults to the default key ID if not provided.
* @returns {Array?} If the key was found, the return value is an array of
* the form [keyId, keyInfo]. Otherwise, null is returned.
* XXX: why is this an array when addKey returns an object?
*/
async getKey(keyId) {
public async getKey(keyId: string): Promise<SecretStorageKeyTuple | null> {
if (!keyId) {
keyId = await this.getDefaultKeyId();
}
@@ -143,7 +190,7 @@ export class SecretStorage extends EventEmitter {
return null;
}
const keyInfo = await this._baseApis.getAccountDataFromServer(
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer<ISecretStorageKeyInfo>(
"m.secret_storage.key." + keyId,
);
return keyInfo ? [keyId, keyInfo] : null;
@@ -156,8 +203,8 @@ export class SecretStorage extends EventEmitter {
* for. Defaults to the default key ID if not provided.
* @return {boolean} Whether we have the key.
*/
async hasKey(keyId) {
return !!(await this.getKey(keyId));
public async hasKey(keyId?: string): Promise<boolean> {
return Boolean(await this.getKey(keyId));
}
/**
@@ -168,10 +215,10 @@ export class SecretStorage extends EventEmitter {
*
* @return {boolean} whether or not the key matches
*/
async checkKey(key, info) {
public async checkKey(key: Uint8Array, info: ISecretStorageKeyInfo): Promise<boolean> {
if (info.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (info.mac) {
const { mac } = await SecretStorage._calculateKeyCheck(key, info.iv);
const { mac } = await calculateKeyCheck(key, info.iv);
return info.mac.replace(/=+$/g, '') === mac.replace(/=+$/g, '');
} else {
// if we have no information, we have to assume the key is right
@@ -182,10 +229,6 @@ export class SecretStorage extends EventEmitter {
}
}
static async _calculateKeyCheck(key, iv) {
return await encryptAES(ZERO_STR, key, "", iv);
}
/**
* Store an encrypted secret on the server
*
@@ -194,8 +237,8 @@ export class SecretStorage extends EventEmitter {
* @param {Array} keys The IDs of the keys to use to encrypt the secret
* or null/undefined to use the default key.
*/
async store(name, secret, keys) {
const encrypted = {};
public async store(name: string, secret: string, keys?: string[]): Promise<void> {
const encrypted: Record<string, IEncryptedPayload> = {};
if (!keys) {
const defaultKeyId = await this.getDefaultKeyId();
@@ -211,7 +254,7 @@ export class SecretStorage extends EventEmitter {
for (const keyId of keys) {
// get key information from key storage
const keyInfo = await this._baseApis.getAccountDataFromServer(
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer<ISecretStorageKeyInfo>(
"m.secret_storage.key." + keyId,
);
if (!keyInfo) {
@@ -221,7 +264,7 @@ export class SecretStorage extends EventEmitter {
// encrypt secret, based on the algorithm
if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
const keys = { [keyId]: keyInfo };
const [, encryption] = await this._getSecretStorageKey(keys, name);
const [, encryption] = await this.getSecretStorageKey(keys, name);
encrypted[keyId] = await encryption.encrypt(secret);
} else {
logger.warn("unknown algorithm for secret storage key " + keyId
@@ -231,34 +274,7 @@ export class SecretStorage extends EventEmitter {
}
// save encrypted secret
await this._baseApis.setAccountData(name, { encrypted });
}
/**
* Temporary method to fix up existing accounts where secrets
* are incorrectly stored without the 'encrypted' level
*
* @param {string} name The name of the secret
* @param {object} secretInfo The account data object
* @returns {object} The fixed object or null if no fix was performed
*/
async _fixupStoredSecret(name, secretInfo) {
// We assume the secret was only stored passthrough for 1
// key - this was all the broken code supported.
const keys = Object.keys(secretInfo);
if (
keys.length === 1 && keys[0] !== 'encrypted' &&
secretInfo[keys[0]].passthrough
) {
const hasKey = await this.hasKey(keys[0]);
if (hasKey) {
logger.log("Fixing up passthrough secret: " + name);
await this.storePassthrough(name, keys[0]);
const newData = await this._baseApis.getAccountDataFromServer(name);
return newData;
}
}
return null;
await this.accountDataAdapter.setAccountData(name, { encrypted });
}
/**
@@ -268,25 +284,23 @@ export class SecretStorage extends EventEmitter {
*
* @return {string} the contents of the secret
*/
async get(name) {
let secretInfo = await this._baseApis.getAccountDataFromServer(name);
public async get(name: string): Promise<string> {
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<ISecretInfo>(name);
if (!secretInfo) {
return;
}
if (!secretInfo.encrypted) {
// try to fix it up
secretInfo = await this._fixupStoredSecret(name, secretInfo);
if (!secretInfo || !secretInfo.encrypted) {
throw new Error("Content is not encrypted!");
}
throw new Error("Content is not encrypted!");
}
// get possible keys to decrypt
const keys = {};
const keys: Record<string, ISecretStorageKeyInfo> = {};
for (const keyId of Object.keys(secretInfo.encrypted)) {
// get key information from key storage
const keyInfo = await this._baseApis.getAccountDataFromServer(
"m.secret_storage.key." + keyId,
const keyInfo = (
await this.accountDataAdapter.getAccountDataFromServer<ISecretStorageKeyInfo>(
"m.secret_storage.key." + keyId,
)
);
const encInfo = secretInfo.encrypted[keyId];
// only use keys we understand the encryption algorithm of
@@ -302,11 +316,11 @@ export class SecretStorage extends EventEmitter {
`the keys it is encrypted with are for a supported algorithm`);
}
let keyId;
let keyId: string;
let decryption;
try {
// fetch private key from app
[keyId, decryption] = await this._getSecretStorageKey(keys, name);
[keyId, decryption] = await this.getSecretStorageKey(keys, name);
const encInfo = secretInfo.encrypted[keyId];
@@ -331,16 +345,12 @@ export class SecretStorage extends EventEmitter {
* with, or null if it is not present or not encrypted with a trusted
* key
*/
async isStored(name, checkKey) {
public async isStored(name: string, checkKey: boolean): Promise<Record<string, ISecretStorageKeyInfo> | null> {
// check if secret exists
let secretInfo = await this._baseApis.getAccountDataFromServer(name);
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<ISecretInfo>(name);
if (!secretInfo) return null;
if (!secretInfo.encrypted) {
// try to fix it up
secretInfo = await this._fixupStoredSecret(name, secretInfo);
if (!secretInfo || !secretInfo.encrypted) {
return null;
}
return null;
}
if (checkKey === undefined) checkKey = true;
@@ -350,7 +360,7 @@ export class SecretStorage extends EventEmitter {
// filter secret encryption keys with supported algorithm
for (const keyId of Object.keys(secretInfo.encrypted)) {
// get key information from key storage
const keyInfo = await this._baseApis.getAccountDataFromServer(
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer<ISecretStorageKeyInfo>(
"m.secret_storage.key." + keyId,
);
if (!keyInfo) continue;
@@ -371,45 +381,48 @@ export class SecretStorage extends EventEmitter {
*
* @param {string} name the name of the secret to request
* @param {string[]} devices the devices to request the secret from
*
* @return {string} the contents of the secret
*/
request(name, devices) {
const requestId = this._baseApis.makeTxnId();
public request(name: string, devices: string[]): ISecretRequest {
const requestId = this.baseApis.makeTxnId();
const requestControl = this._requests[requestId] = {
let resolve: (s: string) => void;
let reject: (e: Error) => void;
const promise = new Promise<string>((res, rej) => {
resolve = res;
reject = rej;
});
this.requests.set(requestId, {
name,
devices,
};
const promise = new Promise((resolve, reject) => {
requestControl.resolve = resolve;
requestControl.reject = reject;
resolve,
reject,
});
const cancel = (reason) => {
const cancel = (reason: string) => {
// send cancellation event
const cancelData = {
action: "request_cancellation",
requesting_device_id: this._baseApis.deviceId,
requesting_device_id: this.baseApis.deviceId,
request_id: requestId,
};
const toDevice = {};
for (const device of devices) {
toDevice[device] = cancelData;
}
this._baseApis.sendToDevice("m.secret.request", {
[this._baseApis.getUserId()]: toDevice,
this.baseApis.sendToDevice("m.secret.request", {
[this.baseApis.getUserId()]: toDevice,
});
// and reject the promise so that anyone waiting on it will be
// notified
requestControl.reject(new Error(reason || "Cancelled"));
reject(new Error(reason || "Cancelled"));
};
// send request to devices
const requestData = {
name,
action: "request",
requesting_device_id: this._baseApis.deviceId,
requesting_device_id: this.baseApis.deviceId,
request_id: requestId,
};
const toDevice = {};
@@ -417,21 +430,21 @@ export class SecretStorage extends EventEmitter {
toDevice[device] = requestData;
}
logger.info(`Request secret ${name} from ${devices}, id ${requestId}`);
this._baseApis.sendToDevice("m.secret.request", {
[this._baseApis.getUserId()]: toDevice,
this.baseApis.sendToDevice("m.secret.request", {
[this.baseApis.getUserId()]: toDevice,
});
return {
request_id: requestId,
requestId,
promise,
cancel,
};
}
async _onRequestReceived(event) {
public async onRequestReceived(event: MatrixEvent): Promise<void> {
const sender = event.getSender();
const content = event.getContent();
if (sender !== this._baseApis.getUserId()
if (sender !== this.baseApis.getUserId()
|| !(content.name && content.action
&& content.requesting_device_id && content.request_id)) {
// ignore requests from anyone else, for now
@@ -440,34 +453,45 @@ export class SecretStorage extends EventEmitter {
const deviceId = content.requesting_device_id;
// check if it's a cancel
if (content.action === "request_cancellation") {
/*
Looks like we intended to emit events when we got cancelations, but
we never put anything in the _incomingRequests object, and the request
itself doesn't use events anyway so if we were to wire up cancellations,
they probably ought to use the same callback interface. I'm leaving them
disabled for now while converting this file to typescript.
if (this._incomingRequests[deviceId]
&& this._incomingRequests[deviceId][content.request_id]) {
logger.info("received request cancellation for secret (" + sender
+ ", " + deviceId + ", " + content.request_id + ")");
this._baseApis.emit("crypto.secrets.requestCancelled", {
logger.info(
"received request cancellation for secret (" + sender +
", " + deviceId + ", " + content.request_id + ")",
);
this.baseApis.emit("crypto.secrets.requestCancelled", {
user_id: sender,
device_id: deviceId,
request_id: content.request_id,
});
}
*/
} else if (content.action === "request") {
if (deviceId === this._baseApis.deviceId) {
if (deviceId === this.baseApis.deviceId) {
// no point in trying to send ourself the secret
return;
}
// check if we have the secret
logger.info("received request for secret (" + sender
+ ", " + deviceId + ", " + content.request_id + ")");
if (!this._cryptoCallbacks.onSecretRequested) {
logger.info(
"received request for secret (" + sender +
", " + deviceId + ", " + content.request_id + ")",
);
if (!this.cryptoCallbacks.onSecretRequested) {
return;
}
const secret = await this._cryptoCallbacks.onSecretRequested(
const secret = await this.cryptoCallbacks.onSecretRequested(
sender,
deviceId,
content.request_id,
content.name,
this._baseApis.checkDeviceTrust(sender, deviceId),
this.baseApis.checkDeviceTrust(sender, deviceId),
);
if (secret) {
logger.info(`Preparing ${content.name} secret for ${deviceId}`);
@@ -480,25 +504,25 @@ export class SecretStorage extends EventEmitter {
};
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this._baseApis.crypto._olmDevice.deviceCurve25519Key,
sender_key: this.baseApis.crypto.olmDevice.deviceCurve25519Key,
ciphertext: {},
};
await olmlib.ensureOlmSessionsForDevices(
this._baseApis.crypto._olmDevice,
this._baseApis,
this.baseApis.crypto.olmDevice,
this.baseApis,
{
[sender]: [
this._baseApis.getStoredDevice(sender, deviceId),
this.baseApis.getStoredDevice(sender, deviceId),
],
},
);
await olmlib.encryptMessageForDevice(
encryptedContent.ciphertext,
this._baseApis.getUserId(),
this._baseApis.deviceId,
this._baseApis.crypto._olmDevice,
this.baseApis.getUserId(),
this.baseApis.deviceId,
this.baseApis.crypto.olmDevice,
sender,
this._baseApis.getStoredDevice(sender, deviceId),
this.baseApis.getStoredDevice(sender, deviceId),
payload,
);
const contentMap = {
@@ -508,26 +532,26 @@ export class SecretStorage extends EventEmitter {
};
logger.info(`Sending ${content.name} secret for ${deviceId}`);
this._baseApis.sendToDevice("m.room.encrypted", contentMap);
this.baseApis.sendToDevice("m.room.encrypted", contentMap);
} else {
logger.info(`Request denied for ${content.name} secret for ${deviceId}`);
}
}
}
_onSecretReceived(event) {
if (event.getSender() !== this._baseApis.getUserId()) {
public onSecretReceived(event: MatrixEvent): void {
if (event.getSender() !== this.baseApis.getUserId()) {
// we shouldn't be receiving secrets from anyone else, so ignore
// because someone could be trying to send us bogus data
return;
}
const content = event.getContent();
logger.log("got secret share for request", content.request_id);
const requestControl = this._requests[content.request_id];
const requestControl = this.requests.get(content.request_id);
if (requestControl) {
// make sure that the device that sent it is one of the devices that
// we requested from
const deviceInfo = this._baseApis.crypto._deviceList.getDeviceByIdentityKey(
const deviceInfo = this.baseApis.crypto.deviceList.getDeviceByIdentityKey(
olmlib.OLM_ALGORITHM,
event.getSenderKey(),
);
@@ -550,12 +574,15 @@ export class SecretStorage extends EventEmitter {
}
}
async _getSecretStorageKey(keys, name) {
if (!this._cryptoCallbacks.getSecretStorageKey) {
private async getSecretStorageKey(
keys: Record<string, ISecretStorageKeyInfo>,
name: string,
): Promise<[string, IDecryptors]> {
if (!this.cryptoCallbacks.getSecretStorageKey) {
throw new Error("No getSecretStorageKey callback supplied");
}
const returned = await this._cryptoCallbacks.getSecretStorageKey({ keys }, name);
const returned = await this.cryptoCallbacks.getSecretStorageKey({ keys }, name);
if (!returned) {
throw new Error("getSecretStorageKey callback returned falsey");
@@ -571,10 +598,10 @@ export class SecretStorage extends EventEmitter {
if (keys[keyId].algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
const decryption = {
encrypt: async function(secret) {
encrypt: async function(secret: string): Promise<IEncryptedPayload> {
return await encryptAES(secret, privateKey, name);
},
decrypt: async function(encInfo) {
decrypt: async function(encInfo: IEncryptedPayload): Promise<string> {
return await decryptAES(encInfo, privateKey, name);
},
};
+59 -34
View File
@@ -1,5 +1,5 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import type { BinaryLike } from "crypto";
import { getCrypto } from '../utils';
import { decodeBase64, encodeBase64 } from './olmlib';
@@ -21,7 +22,14 @@ const subtleCrypto = (typeof window !== "undefined" && window.crypto) ?
(window.crypto.subtle || window.crypto.webkitSubtle) : null;
// salt for HKDF, with 8 bytes of zeros
const zerosalt = new Uint8Array(8);
const zeroSalt = new Uint8Array(8);
export interface IEncryptedPayload {
[key: string]: any; // extensible
iv?: string;
ciphertext?: string;
mac?: string;
}
/**
* encrypt a string in Node.js
@@ -31,7 +39,7 @@ const zerosalt = new Uint8Array(8);
* @param {string} name the name of the secret
* @param {string} ivStr the initialization vector to use
*/
async function encryptNode(data, key, name, ivStr) {
async function encryptNode(data: string, key: Uint8Array, name: string, ivStr?: string): Promise<IEncryptedPayload> {
const crypto = getCrypto();
if (!crypto) {
throw new Error("No usable crypto implementation");
@@ -42,25 +50,27 @@ async function encryptNode(data, key, name, ivStr) {
iv = decodeBase64(ivStr);
} else {
iv = crypto.randomBytes(16);
}
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
// (which would mean we wouldn't be able to decrypt on Android). The loss
// of a single bit of iv is a price we have to pay.
iv[8] &= 0x7f;
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
// (which would mean we wouldn't be able to decrypt on Android). The loss
// of a single bit of iv is a price we have to pay.
iv[8] &= 0x7f;
}
const [aesKey, hmacKey] = deriveKeysNode(key, name);
const cipher = crypto.createCipheriv("aes-256-ctr", aesKey, iv);
const ciphertext = cipher.update(data, "utf-8", "base64")
+ cipher.final("base64");
const ciphertext = Buffer.concat([
cipher.update(data, "utf8"),
cipher.final(),
]);
const hmac = crypto.createHmac("sha256", hmacKey)
.update(ciphertext, "base64").digest("base64");
.update(ciphertext).digest("base64");
return {
iv: encodeBase64(iv),
ciphertext: ciphertext,
ciphertext: ciphertext.toString("base64"),
mac: hmac,
};
}
@@ -75,7 +85,7 @@ async function encryptNode(data, key, name, ivStr) {
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
*/
async function decryptNode(data, key, name) {
async function decryptNode(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
const crypto = getCrypto();
if (!crypto) {
throw new Error("No usable crypto implementation");
@@ -84,7 +94,8 @@ async function decryptNode(data, key, name) {
const [aesKey, hmacKey] = deriveKeysNode(key, name);
const hmac = crypto.createHmac("sha256", hmacKey)
.update(data.ciphertext, "base64").digest("base64").replace(/=+$/g, '');
.update(Buffer.from(data.ciphertext, "base64"))
.digest("base64").replace(/=+$/g, '');
if (hmac !== data.mac.replace(/=+$/g, '')) {
throw new Error(`Error decrypting secret ${name}: bad MAC`);
@@ -93,21 +104,20 @@ async function decryptNode(data, key, name) {
const decipher = crypto.createDecipheriv(
"aes-256-ctr", aesKey, decodeBase64(data.iv),
);
return decipher.update(data.ciphertext, "base64", "utf-8")
+ decipher.final("utf-8");
return decipher.update(data.ciphertext, "base64", "utf8")
+ decipher.final("utf8");
}
function deriveKeysNode(key, name) {
function deriveKeysNode(key: BinaryLike, name: string): [Buffer, Buffer] {
const crypto = getCrypto();
const prk = crypto.createHmac("sha256", zerosalt)
.update(key).digest();
const prk = crypto.createHmac("sha256", zeroSalt).update(key).digest();
const b = Buffer.alloc(1, 1);
const aesKey = crypto.createHmac("sha256", prk)
.update(name, "utf-8").update(b).digest();
.update(name, "utf8").update(b).digest();
b[0] = 2;
const hmacKey = crypto.createHmac("sha256", prk)
.update(aesKey).update(name, "utf-8").update(b).digest();
.update(aesKey).update(name, "utf8").update(b).digest();
return [aesKey, hmacKey];
}
@@ -120,19 +130,19 @@ function deriveKeysNode(key, name) {
* @param {string} name the name of the secret
* @param {string} ivStr the initialization vector to use
*/
async function encryptBrowser(data, key, name, ivStr) {
async function encryptBrowser(data: string, key: Uint8Array, name: string, ivStr?: string): Promise<IEncryptedPayload> {
let iv;
if (ivStr) {
iv = decodeBase64(ivStr);
} else {
iv = new Uint8Array(16);
window.crypto.getRandomValues(iv);
}
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
// (which would mean we wouldn't be able to decrypt on Android). The loss
// of a single bit of iv is a price we have to pay.
iv[8] &= 0x7f;
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
// (which would mean we wouldn't be able to decrypt on Android). The loss
// of a single bit of iv is a price we have to pay.
iv[8] &= 0x7f;
}
const [aesKey, hmacKey] = await deriveKeysBrowser(key, name);
const encodedData = new TextEncoder().encode(data);
@@ -170,7 +180,7 @@ async function encryptBrowser(data, key, name, ivStr) {
* @param {Uint8Array} key the encryption key to use
* @param {string} name the name of the secret
*/
async function decryptBrowser(data, key, name) {
async function decryptBrowser(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
const [aesKey, hmacKey] = await deriveKeysBrowser(key, name);
const ciphertext = decodeBase64(data.ciphertext);
@@ -197,7 +207,7 @@ async function decryptBrowser(data, key, name) {
return new TextDecoder().decode(new Uint8Array(plaintext));
}
async function deriveKeysBrowser(key, name) {
async function deriveKeysBrowser(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> {
const hkdfkey = await subtleCrypto.importKey(
'raw',
key,
@@ -208,7 +218,9 @@ async function deriveKeysBrowser(key, name) {
const keybits = await subtleCrypto.deriveBits(
{
name: "HKDF",
salt: zerosalt,
salt: zeroSalt,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/879
info: (new TextEncoder().encode(name)),
hash: "SHA-256",
},
@@ -241,11 +253,24 @@ async function deriveKeysBrowser(key, name) {
return await Promise.all([aesProm, hmacProm]);
}
export function encryptAES(...args) {
return subtleCrypto ? encryptBrowser(...args) : encryptNode(...args);
export function encryptAES(data: string, key: Uint8Array, name: string, ivStr?: string): Promise<IEncryptedPayload> {
return subtleCrypto ? encryptBrowser(data, key, name, ivStr) : encryptNode(data, key, name, ivStr);
}
export function decryptAES(...args) {
return subtleCrypto ? decryptBrowser(...args) : decryptNode(...args);
export function decryptAES(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
return subtleCrypto ? decryptBrowser(data, key, name) : decryptNode(data, key, name);
}
// string of zeroes, for calculating the key check
const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
/** Calculate the MAC for checking the key.
*
* @param {Uint8Array} key the key to use
* @param {string} [iv] The initialization vector as a base64-encoded string.
* If omitted, a random initialization vector will be created.
* @return {Promise<object>} An object that contains, `mac` and `iv` properties.
*/
export function calculateKeyCheck(key: Uint8Array, iv?: string): Promise<IEncryptedPayload> {
return encryptAES(ZERO_STR, key, "", iv);
}

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