@@ -30,6 +30,7 @@ import {
30
30
ITurnServer ,
31
31
IRoomEvent ,
32
32
IOpenIDCredentials ,
33
+ ISendEventFromWidgetResponseData ,
33
34
WidgetApiResponseError ,
34
35
} from "matrix-widget-api" ;
35
36
@@ -40,6 +41,7 @@ import { ICapabilities, RoomWidgetClient } from "../../src/embedded";
40
41
import { MatrixEvent } from "../../src/models/event" ;
41
42
import { ToDeviceBatch } from "../../src/models/ToDeviceMessage" ;
42
43
import { DeviceInfo } from "../../src/crypto/deviceinfo" ;
44
+ import { sleep } from "../../src/utils" ;
43
45
44
46
const testOIDCToken = {
45
47
access_token : "12345678" ,
@@ -127,9 +129,16 @@ describe("RoomWidgetClient", () => {
127
129
const makeClient = async (
128
130
capabilities : ICapabilities ,
129
131
sendContentLoaded : boolean | undefined = undefined ,
132
+ userId ?: string ,
130
133
) : Promise < void > => {
131
134
const baseUrl = "https://example.org" ;
132
- client = createRoomWidgetClient ( widgetApi , capabilities , "!1:example.org" , { baseUrl } , sendContentLoaded ) ;
135
+ client = createRoomWidgetClient (
136
+ widgetApi ,
137
+ capabilities ,
138
+ "!1:example.org" ,
139
+ { baseUrl, userId } ,
140
+ sendContentLoaded ,
141
+ ) ;
133
142
expect ( widgetApi . start ) . toHaveBeenCalled ( ) ; // needs to have been called early in order to not miss messages
134
143
widgetApi . emit ( "ready" ) ;
135
144
await client . startClient ( ) ;
@@ -192,6 +201,142 @@ describe("RoomWidgetClient", () => {
192
201
. map ( ( e ) => e . getEffectiveEvent ( ) ) ,
193
202
) . toEqual ( [ event ] ) ;
194
203
} ) ;
204
+ describe ( "local echos" , ( ) => {
205
+ const setupRemoteEcho = ( ) => {
206
+ makeClient (
207
+ {
208
+ receiveEvent : [ "org.matrix.rageshake_request" ] ,
209
+ sendEvent : [ "org.matrix.rageshake_request" ] ,
210
+ } ,
211
+ undefined ,
212
+ "@me:example.org" ,
213
+ ) ;
214
+ expect ( widgetApi . requestCapabilityForRoomTimeline ) . toHaveBeenCalledWith ( "!1:example.org" ) ;
215
+ expect ( widgetApi . requestCapabilityToReceiveEvent ) . toHaveBeenCalledWith ( "org.matrix.rageshake_request" ) ;
216
+ const injectSpy = jest . spyOn ( ( client as any ) . syncApi , "injectRoomEvents" ) ;
217
+ const widgetSendEmitter = new EventEmitter ( ) ;
218
+ const widgetSendPromise = new Promise < void > ( ( resolve ) =>
219
+ widgetSendEmitter . once ( "send" , ( ) => resolve ( ) ) ,
220
+ ) ;
221
+ const resolveWidgetSend = ( ) => widgetSendEmitter . emit ( "send" ) ;
222
+ widgetApi . sendRoomEvent . mockImplementation (
223
+ async ( eType , content , roomId ) : Promise < ISendEventFromWidgetResponseData > => {
224
+ await widgetSendPromise ;
225
+ return { room_id : "!1:example.org" , event_id : "event_id" } ;
226
+ } ,
227
+ ) ;
228
+ return { injectSpy, resolveWidgetSend } ;
229
+ } ;
230
+ const remoteEchoEvent = new CustomEvent ( `action:${ WidgetApiToWidgetAction . SendEvent } ` , {
231
+ detail : {
232
+ data : {
233
+ type : "org.matrix.rageshake_request" ,
234
+
235
+ room_id : "!1:example.org" ,
236
+ event_id : "event_id" ,
237
+ sender : "@me:example.org" ,
238
+ state_key : "bar" ,
239
+ content : { hello : "world" } ,
240
+ unsigned : { transaction_id : "1234" } ,
241
+ } ,
242
+ } ,
243
+ cancelable : true ,
244
+ } ) ;
245
+ it ( "get response then local echo" , async ( ) => {
246
+ await sleep ( 600 ) ;
247
+ const { injectSpy, resolveWidgetSend } = await setupRemoteEcho ( ) ;
248
+
249
+ // Begin by sending an event:
250
+ client . sendEvent ( "!1:example.org" , "org.matrix.rageshake_request" , { request_id : 12 } , "widgetTxId" ) ;
251
+ // we do not expect it to be send -- until we call `resolveWidgetSend`
252
+ expect ( injectSpy ) . not . toHaveBeenCalled ( ) ;
253
+
254
+ // We first get the response from the widget
255
+ resolveWidgetSend ( ) ;
256
+ // We then get the remote echo from the widget
257
+ widgetApi . emit ( `action:${ WidgetApiToWidgetAction . SendEvent } ` , remoteEchoEvent ) ;
258
+
259
+ // gets emitted after the event got injected
260
+ await new Promise < void > ( ( resolve ) => client . once ( ClientEvent . Event , ( ) => resolve ( ) ) ) ;
261
+ expect ( injectSpy ) . toHaveBeenCalled ( ) ;
262
+
263
+ const call = injectSpy . mock . calls [ 0 ] as any ;
264
+ const injectedEv = call [ 2 ] [ 0 ] ;
265
+ expect ( injectedEv . getType ( ) ) . toBe ( "org.matrix.rageshake_request" ) ;
266
+ expect ( injectedEv . getUnsigned ( ) . transaction_id ) . toBe ( "widgetTxId" ) ;
267
+ } ) ;
268
+
269
+ it ( "get local echo then response" , async ( ) => {
270
+ await sleep ( 600 ) ;
271
+ const { injectSpy, resolveWidgetSend } = await setupRemoteEcho ( ) ;
272
+
273
+ // Begin by sending an event:
274
+ client . sendEvent ( "!1:example.org" , "org.matrix.rageshake_request" , { request_id : 12 } , "widgetTxId" ) ;
275
+ // we do not expect it to be send -- until we call `resolveWidgetSend`
276
+ expect ( injectSpy ) . not . toHaveBeenCalled ( ) ;
277
+
278
+ // We first get the remote echo from the widget
279
+ widgetApi . emit ( `action:${ WidgetApiToWidgetAction . SendEvent } ` , remoteEchoEvent ) ;
280
+ expect ( injectSpy ) . not . toHaveBeenCalled ( ) ;
281
+
282
+ // We then get the response from the widget
283
+ resolveWidgetSend ( ) ;
284
+
285
+ // Gets emitted after the event got injected
286
+ await new Promise < void > ( ( resolve ) => client . once ( ClientEvent . Event , ( ) => resolve ( ) ) ) ;
287
+ expect ( injectSpy ) . toHaveBeenCalled ( ) ;
288
+
289
+ const call = injectSpy . mock . calls [ 0 ] as any ;
290
+ const injectedEv = call [ 2 ] [ 0 ] ;
291
+ expect ( injectedEv . getType ( ) ) . toBe ( "org.matrix.rageshake_request" ) ;
292
+ expect ( injectedEv . getUnsigned ( ) . transaction_id ) . toBe ( "widgetTxId" ) ;
293
+ } ) ;
294
+ it ( "__ local echo then response" , async ( ) => {
295
+ await sleep ( 600 ) ;
296
+ const { injectSpy, resolveWidgetSend } = await setupRemoteEcho ( ) ;
297
+
298
+ // Begin by sending an event:
299
+ client . sendEvent ( "!1:example.org" , "org.matrix.rageshake_request" , { request_id : 12 } , "widgetTxId" ) ;
300
+ // we do not expect it to be send -- until we call `resolveWidgetSend`
301
+ expect ( injectSpy ) . not . toHaveBeenCalled ( ) ;
302
+
303
+ // We first get the remote echo from the widget
304
+ widgetApi . emit ( `action:${ WidgetApiToWidgetAction . SendEvent } ` , remoteEchoEvent ) ;
305
+ const otherRemoteEcho = new CustomEvent ( `action:${ WidgetApiToWidgetAction . SendEvent } ` , {
306
+ detail : { data : { ...remoteEchoEvent . detail . data } } ,
307
+ } ) ;
308
+ otherRemoteEcho . detail . data . unsigned . transaction_id = "4567" ;
309
+ otherRemoteEcho . detail . data . event_id = "other_id" ;
310
+ widgetApi . emit ( `action:${ WidgetApiToWidgetAction . SendEvent } ` , otherRemoteEcho ) ;
311
+
312
+ // Simulate the wait time while the widget is waiting for a response
313
+ // after we already received the remote echo
314
+ await sleep ( 20 ) ;
315
+ // even after the wait we do not want any event to be injected.
316
+ // we do not know their event_id and cannot know if they are the remote echo
317
+ // where we need to update the txId because they are send by this client
318
+ expect ( injectSpy ) . not . toHaveBeenCalled ( ) ;
319
+ // We then get the response from the widget
320
+ resolveWidgetSend ( ) ;
321
+
322
+ // Gets emitted after the event got injected
323
+ await new Promise < void > ( ( resolve ) => client . once ( ClientEvent . Event , ( ) => resolve ( ) ) ) ;
324
+ // Now we want both events to be injected since we know the txId - event_id match
325
+ expect ( injectSpy ) . toHaveBeenCalled ( ) ;
326
+
327
+ // it has been called with the event sent by ourselves
328
+ const call = injectSpy . mock . calls [ 0 ] as any ;
329
+ const injectedEv = call [ 2 ] [ 0 ] ;
330
+ expect ( injectedEv . getType ( ) ) . toBe ( "org.matrix.rageshake_request" ) ;
331
+ expect ( injectedEv . getUnsigned ( ) . transaction_id ) . toBe ( "widgetTxId" ) ;
332
+
333
+ // It has been called by the event we blocked because of our send right afterwards
334
+ const call2 = injectSpy . mock . calls [ 1 ] as any ;
335
+ const injectedEv2 = call2 [ 2 ] [ 0 ] ;
336
+ expect ( injectedEv2 . getType ( ) ) . toBe ( "org.matrix.rageshake_request" ) ;
337
+ expect ( injectedEv2 . getUnsigned ( ) . transaction_id ) . toBe ( "4567" ) ;
338
+ } ) ;
339
+ } ) ;
195
340
196
341
it ( "handles widget errors with generic error data" , async ( ) => {
197
342
const error = new Error ( "failed to send" ) ;
0 commit comments