@@ -16,11 +16,10 @@ limitations under the License.
16
16
17
17
import { encodeBase64 , EventType , MatrixClient , type MatrixError , type MatrixEvent , type Room } from "../../../src" ;
18
18
import { KnownMembership } from "../../../src/@types/membership" ;
19
- import { type SessionMembershipData } from "../../../src/matrixrtc/CallMembership" ;
20
19
import { MatrixRTCSession , MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession" ;
21
20
import { type EncryptionKeysEventContent } from "../../../src/matrixrtc/types" ;
22
21
import { secureRandomString } from "../../../src/randomstring" ;
23
- import { makeMockEvent , makeMockRoom , makeMockRoomState , membershipTemplate , makeKey } from "./mocks" ;
22
+ import { makeMockEvent , makeMockRoom , membershipTemplate , makeKey , type MembershipData , mockRoomState } from "./mocks" ;
24
23
import { RTCEncryptionManager } from "../../../src/matrixrtc/RTCEncryptionManager.ts" ;
25
24
26
25
const mockFocus = { type : "mock" } ;
@@ -48,7 +47,7 @@ describe("MatrixRTCSession", () => {
48
47
49
48
describe ( "roomSessionForRoom" , ( ) => {
50
49
it ( "creates a room-scoped session from room state" , ( ) => {
51
- const mockRoom = makeMockRoom ( membershipTemplate ) ;
50
+ const mockRoom = makeMockRoom ( [ membershipTemplate ] ) ;
52
51
53
52
sess = MatrixRTCSession . roomSessionForRoom ( client , mockRoom ) ;
54
53
expect ( sess ?. memberships . length ) . toEqual ( 1 ) ;
@@ -75,7 +74,7 @@ describe("MatrixRTCSession", () => {
75
74
} ) ;
76
75
77
76
it ( "ignores memberships events of members not in the room" , ( ) => {
78
- const mockRoom = makeMockRoom ( membershipTemplate ) ;
77
+ const mockRoom = makeMockRoom ( [ membershipTemplate ] ) ;
79
78
mockRoom . hasMembershipState = ( state ) => state === KnownMembership . Join ;
80
79
sess = MatrixRTCSession . roomSessionForRoom ( client , mockRoom ) ;
81
80
expect ( sess ?. memberships . length ) . toEqual ( 0 ) ;
@@ -270,9 +269,15 @@ describe("MatrixRTCSession", () => {
270
269
describe ( "joining" , ( ) => {
271
270
let mockRoom : Room ;
272
271
let sendEventMock : jest . Mock ;
272
+ let sendStateEventMock : jest . Mock ;
273
273
274
+ let sentStateEvent : Promise < void > ;
274
275
beforeEach ( ( ) => {
275
- sendEventMock = jest . fn ( ) ;
276
+ sentStateEvent = new Promise ( ( resolve ) => {
277
+ sendStateEventMock = jest . fn ( resolve ) ;
278
+ } ) ;
279
+ sendEventMock = jest . fn ( ) . mockResolvedValue ( undefined ) ;
280
+ client . sendStateEvent = sendStateEventMock ;
276
281
client . sendEvent = sendEventMock ;
277
282
278
283
client . _unstable_updateDelayedEvent = jest . fn ( ) ;
@@ -298,11 +303,69 @@ describe("MatrixRTCSession", () => {
298
303
sess ! . joinRoomSession ( [ mockFocus ] , mockFocus ) ;
299
304
expect ( sess ! . isJoined ( ) ) . toEqual ( true ) ;
300
305
} ) ;
306
+
307
+ it ( "sends a notification when starting a call" , async ( ) => {
308
+ // Simulate a join, including the update to the room state
309
+ sess ! . joinRoomSession ( [ mockFocus ] , mockFocus , { notificationType : "ring" } ) ;
310
+ await Promise . race ( [ sentStateEvent , new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ] ) ;
311
+ mockRoomState ( mockRoom , [ { ...membershipTemplate , user_id : client . getUserId ( ) ! } ] ) ;
312
+ sess ! . onRTCSessionMemberUpdate ( ) ;
313
+ const ownMembershipId = sess ?. memberships [ 0 ] . eventId ;
314
+
315
+ expect ( client . sendEvent ) . toHaveBeenCalledWith ( mockRoom ! . roomId , EventType . RTCNotification , {
316
+ "m.mentions" : { user_ids : [ ] , room : true } ,
317
+ "notification_type" : "ring" ,
318
+ "m.relates_to" : {
319
+ event_id : ownMembershipId ,
320
+ rel_type : "org.matrix.msc4075.rtc.notification.parent" ,
321
+ } ,
322
+ "lifetime" : 30000 ,
323
+ "sender_ts" : expect . any ( Number ) ,
324
+ } ) ;
325
+
326
+ // Check if deprecated notify event is also sent.
327
+ expect ( client . sendEvent ) . toHaveBeenCalledWith ( mockRoom ! . roomId , EventType . CallNotify , {
328
+ "application" : "m.call" ,
329
+ "m.mentions" : { user_ids : [ ] , room : true } ,
330
+ "notify_type" : "ring" ,
331
+ "call_id" : "" ,
332
+ } ) ;
333
+ } ) ;
334
+
335
+ it ( "doesn't send a notification when joining an existing call" , async ( ) => {
336
+ // Add another member to the call so that it is considered an existing call
337
+ mockRoomState ( mockRoom , [ membershipTemplate ] ) ;
338
+ sess ! . onRTCSessionMemberUpdate ( ) ;
339
+
340
+ // Simulate a join, including the update to the room state
341
+ sess ! . joinRoomSession ( [ mockFocus ] , mockFocus , { notificationType : "ring" } ) ;
342
+ await Promise . race ( [ sentStateEvent , new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ] ) ;
343
+ mockRoomState ( mockRoom , [ membershipTemplate , { ...membershipTemplate , user_id : client . getUserId ( ) ! } ] ) ;
344
+ sess ! . onRTCSessionMemberUpdate ( ) ;
345
+
346
+ expect ( client . sendEvent ) . not . toHaveBeenCalled ( ) ;
347
+ } ) ;
348
+
349
+ it ( "doesn't send a notification when someone else starts the call faster than us" , async ( ) => {
350
+ // Simulate a join, including the update to the room state
351
+ sess ! . joinRoomSession ( [ mockFocus ] , mockFocus , { notificationType : "ring" } ) ;
352
+ await Promise . race ( [ sentStateEvent , new Promise ( ( resolve ) => setTimeout ( resolve , 5000 ) ) ] ) ;
353
+ // But this time we want to simulate a race condition in which we receive a state event
354
+ // from someone else, starting the call before our own state event has been sent
355
+ mockRoomState ( mockRoom , [ membershipTemplate ] ) ;
356
+ sess ! . onRTCSessionMemberUpdate ( ) ;
357
+ mockRoomState ( mockRoom , [ membershipTemplate , { ...membershipTemplate , user_id : client . getUserId ( ) ! } ] ) ;
358
+ sess ! . onRTCSessionMemberUpdate ( ) ;
359
+
360
+ // We assume that the responsibility to send a notification, if any, lies with the other
361
+ // participant that won the race
362
+ expect ( client . sendEvent ) . not . toHaveBeenCalled ( ) ;
363
+ } ) ;
301
364
} ) ;
302
365
303
366
describe ( "onMembershipsChanged" , ( ) => {
304
367
it ( "does not emit if no membership changes" , ( ) => {
305
- const mockRoom = makeMockRoom ( membershipTemplate ) ;
368
+ const mockRoom = makeMockRoom ( [ membershipTemplate ] ) ;
306
369
sess = MatrixRTCSession . roomSessionForRoom ( client , mockRoom ) ;
307
370
308
371
const onMembershipsChanged = jest . fn ( ) ;
@@ -313,13 +376,13 @@ describe("MatrixRTCSession", () => {
313
376
} ) ;
314
377
315
378
it ( "emits on membership changes" , ( ) => {
316
- const mockRoom = makeMockRoom ( membershipTemplate ) ;
379
+ const mockRoom = makeMockRoom ( [ membershipTemplate ] ) ;
317
380
sess = MatrixRTCSession . roomSessionForRoom ( client , mockRoom ) ;
318
381
319
382
const onMembershipsChanged = jest . fn ( ) ;
320
383
sess . on ( MatrixRTCSessionEvent . MembershipsChanged , onMembershipsChanged ) ;
321
384
322
- mockRoom . getLiveTimeline ( ) . getState = jest . fn ( ) . mockReturnValue ( makeMockRoomState ( [ ] , mockRoom . roomId ) ) ;
385
+ mockRoomState ( mockRoom , [ ] ) ;
323
386
sess . onRTCSessionMemberUpdate ( ) ;
324
387
325
388
expect ( onMembershipsChanged ) . toHaveBeenCalled ( ) ;
@@ -503,18 +566,14 @@ describe("MatrixRTCSession", () => {
503
566
expect ( sess ! . statistics . counters . roomEventEncryptionKeysSent ) . toEqual ( 1 ) ;
504
567
505
568
// member2 leaves triggering key rotation
506
- mockRoom . getLiveTimeline ( ) . getState = jest
507
- . fn ( )
508
- . mockReturnValue ( makeMockRoomState ( [ membershipTemplate ] , mockRoom . roomId ) ) ;
569
+ mockRoomState ( mockRoom , [ membershipTemplate ] ) ;
509
570
sess . onRTCSessionMemberUpdate ( ) ;
510
571
511
572
// member2 re-joins which should trigger an immediate re-send
512
573
const keysSentPromise2 = new Promise < EncryptionKeysEventContent > ( ( resolve ) => {
513
574
sendEventMock . mockImplementation ( ( _roomId , _evType , payload ) => resolve ( payload ) ) ;
514
575
} ) ;
515
- mockRoom . getLiveTimeline ( ) . getState = jest
516
- . fn ( )
517
- . mockReturnValue ( makeMockRoomState ( [ membershipTemplate , member2 ] , mockRoom . roomId ) ) ;
576
+ mockRoomState ( mockRoom , [ membershipTemplate , member2 ] ) ;
518
577
sess . onRTCSessionMemberUpdate ( ) ;
519
578
// but, that immediate resend is throttled so we need to wait a bit
520
579
jest . advanceTimersByTime ( 1000 ) ;
@@ -565,9 +624,7 @@ describe("MatrixRTCSession", () => {
565
624
device_id : "BBBBBBB" ,
566
625
} ) ;
567
626
568
- mockRoom . getLiveTimeline ( ) . getState = jest
569
- . fn ( )
570
- . mockReturnValue ( makeMockRoomState ( [ membershipTemplate , member2 ] , mockRoom . roomId ) ) ;
627
+ mockRoomState ( mockRoom , [ membershipTemplate , member2 ] ) ;
571
628
sess . onRTCSessionMemberUpdate ( ) ;
572
629
573
630
await keysSentPromise2 ;
@@ -592,9 +649,7 @@ describe("MatrixRTCSession", () => {
592
649
} ) ;
593
650
594
651
const mockRoom = makeMockRoom ( [ member1 , member2 ] ) ;
595
- mockRoom . getLiveTimeline ( ) . getState = jest
596
- . fn ( )
597
- . mockReturnValue ( makeMockRoomState ( [ member1 , member2 ] , mockRoom . roomId ) ) ;
652
+ mockRoomState ( mockRoom , [ member1 , member2 ] ) ;
598
653
599
654
sess = MatrixRTCSession . roomSessionForRoom ( client , mockRoom ) ;
600
655
sess . joinRoomSession ( [ mockFocus ] , mockFocus , { manageMediaKeys : true } ) ;
@@ -641,10 +696,6 @@ describe("MatrixRTCSession", () => {
641
696
} ;
642
697
643
698
const mockRoom = makeMockRoom ( [ member1 , member2 ] ) ;
644
- mockRoom . getLiveTimeline ( ) . getState = jest
645
- . fn ( )
646
- . mockReturnValue ( makeMockRoomState ( [ member1 , member2 ] , mockRoom . roomId ) ) ;
647
-
648
699
sess = MatrixRTCSession . roomSessionForRoom ( client , mockRoom ) ;
649
700
sess . joinRoomSession ( [ mockFocus ] , mockFocus , { manageMediaKeys : true } ) ;
650
701
@@ -674,6 +725,7 @@ describe("MatrixRTCSession", () => {
674
725
675
726
// update created_ts
676
727
member2 . created_ts = 5000 ;
728
+ mockRoomState ( mockRoom , [ member1 , member2 ] ) ;
677
729
678
730
const keysSentPromise2 = new Promise ( ( resolve ) => {
679
731
sendEventMock . mockImplementation ( resolve ) ;
@@ -737,9 +789,7 @@ describe("MatrixRTCSession", () => {
737
789
sendEventMock . mockImplementation ( ( _roomId , _evType , payload ) => resolve ( payload ) ) ;
738
790
} ) ;
739
791
740
- mockRoom . getLiveTimeline ( ) . getState = jest
741
- . fn ( )
742
- . mockReturnValue ( makeMockRoomState ( [ membershipTemplate ] , mockRoom . roomId ) ) ;
792
+ mockRoomState ( mockRoom , [ membershipTemplate ] ) ;
743
793
sess . onRTCSessionMemberUpdate ( ) ;
744
794
745
795
jest . advanceTimersByTime ( KEY_DELAY ) ;
@@ -784,7 +834,7 @@ describe("MatrixRTCSession", () => {
784
834
it ( "wraps key index around to 0 when it reaches the maximum" , async ( ) => {
785
835
// this should give us keys with index [0...255, 0, 1]
786
836
const membersToTest = 258 ;
787
- const members : SessionMembershipData [ ] = [ ] ;
837
+ const members : MembershipData [ ] = [ ] ;
788
838
for ( let i = 0 ; i < membersToTest ; i ++ ) {
789
839
members . push ( Object . assign ( { } , membershipTemplate , { device_id : `DEVICE${ i } ` } ) ) ;
790
840
}
@@ -804,11 +854,7 @@ describe("MatrixRTCSession", () => {
804
854
sess . joinRoomSession ( [ mockFocus ] , mockFocus , { manageMediaKeys : true } ) ;
805
855
} else {
806
856
// otherwise update the state reducing the membership each time in order to trigger key rotation
807
- mockRoom . getLiveTimeline ( ) . getState = jest
808
- . fn ( )
809
- . mockReturnValue (
810
- makeMockRoomState ( members . slice ( 0 , membersToTest - i ) , mockRoom . roomId ) ,
811
- ) ;
857
+ mockRoomState ( mockRoom , members . slice ( 0 , membersToTest - i ) ) ;
812
858
}
813
859
814
860
sess ! . onRTCSessionMemberUpdate ( ) ;
@@ -849,9 +895,7 @@ describe("MatrixRTCSession", () => {
849
895
device_id : "BBBBBBB" ,
850
896
} ) ;
851
897
852
- mockRoom . getLiveTimeline ( ) . getState = jest
853
- . fn ( )
854
- . mockReturnValue ( makeMockRoomState ( [ membershipTemplate , member2 ] , mockRoom . roomId ) ) ;
898
+ mockRoomState ( mockRoom , [ membershipTemplate , member2 ] ) ;
855
899
sess . onRTCSessionMemberUpdate ( ) ;
856
900
857
901
await new Promise ( ( resolve ) => {
0 commit comments