request-iframe 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.CN.md +269 -12
- package/README.md +266 -11
- package/library/__tests__/channel.test.ts +420 -0
- package/library/__tests__/debug.test.ts +588 -0
- package/library/__tests__/dispatcher.test.ts +481 -0
- package/library/__tests__/requestIframe.test.ts +2127 -99
- package/library/__tests__/server.test.ts +738 -0
- package/library/api/client.d.ts.map +1 -1
- package/library/api/client.js +11 -6
- package/library/api/server.d.ts +4 -3
- package/library/api/server.d.ts.map +1 -1
- package/library/api/server.js +25 -7
- package/library/constants/index.d.ts +14 -4
- package/library/constants/index.d.ts.map +1 -1
- package/library/constants/index.js +15 -7
- package/library/constants/messages.d.ts +35 -0
- package/library/constants/messages.d.ts.map +1 -1
- package/library/constants/messages.js +36 -1
- package/library/core/client-server.d.ts +101 -0
- package/library/core/client-server.d.ts.map +1 -0
- package/library/core/client-server.js +266 -0
- package/library/core/client.d.ts +22 -6
- package/library/core/client.d.ts.map +1 -1
- package/library/core/client.js +159 -24
- package/library/core/request.d.ts.map +1 -1
- package/library/core/response.d.ts +5 -1
- package/library/core/response.d.ts.map +1 -1
- package/library/core/response.js +85 -70
- package/library/core/server-client.d.ts +3 -1
- package/library/core/server-client.d.ts.map +1 -1
- package/library/core/server-client.js +19 -9
- package/library/core/server.d.ts +9 -1
- package/library/core/server.d.ts.map +1 -1
- package/library/core/server.js +96 -52
- package/library/index.d.ts +1 -1
- package/library/index.js +2 -2
- package/library/interceptors/index.d.ts.map +1 -1
- package/library/message/channel.d.ts +3 -1
- package/library/message/channel.d.ts.map +1 -1
- package/library/message/dispatcher.d.ts +7 -2
- package/library/message/dispatcher.d.ts.map +1 -1
- package/library/message/dispatcher.js +47 -2
- package/library/message/index.d.ts.map +1 -1
- package/library/stream/file-stream.d.ts +5 -0
- package/library/stream/file-stream.d.ts.map +1 -1
- package/library/stream/file-stream.js +41 -12
- package/library/stream/index.d.ts.map +1 -1
- package/library/stream/readable-stream.d.ts.map +1 -1
- package/library/stream/readable-stream.js +32 -30
- package/library/stream/types.d.ts +18 -0
- package/library/stream/types.d.ts.map +1 -1
- package/library/stream/writable-stream.d.ts +1 -0
- package/library/stream/writable-stream.d.ts.map +1 -1
- package/library/stream/writable-stream.js +7 -2
- package/library/types/index.d.ts +80 -28
- package/library/types/index.d.ts.map +1 -1
- package/library/utils/cache.d.ts +24 -0
- package/library/utils/cache.d.ts.map +1 -1
- package/library/utils/cache.js +76 -0
- package/library/utils/cookie.d.ts.map +1 -1
- package/library/utils/debug.d.ts.map +1 -1
- package/library/utils/debug.js +382 -20
- package/library/utils/index.d.ts +5 -0
- package/library/utils/index.d.ts.map +1 -1
- package/library/utils/index.js +14 -1
- package/library/utils/path-match.d.ts.map +1 -1
- package/library/utils/protocol.d.ts.map +1 -1
- package/package.json +3 -1
- package/react/library/__tests__/index.test.tsx +238 -267
- package/react/library/index.d.ts +4 -3
- package/react/library/index.d.ts.map +1 -1
- package/react/library/index.js +167 -158
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { requestIframeClient, clearRequestIframeClientCache } from '../api/client';
|
|
2
2
|
import { requestIframeServer, clearRequestIframeServerCache } from '../api/server';
|
|
3
3
|
import { RequestConfig, Response, ErrorResponse, PostMessageData } from '../types';
|
|
4
|
-
import { HttpHeader, Messages } from '../constants';
|
|
4
|
+
import { HttpHeader, MessageRole, Messages } from '../constants';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Create test iframe
|
|
@@ -64,7 +64,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
64
64
|
__requestIframe__: 1,
|
|
65
65
|
type: 'ack',
|
|
66
66
|
requestId: msg.requestId,
|
|
67
|
-
path: msg.path
|
|
67
|
+
path: msg.path,
|
|
68
|
+
role: MessageRole.SERVER
|
|
68
69
|
},
|
|
69
70
|
origin
|
|
70
71
|
})
|
|
@@ -79,7 +80,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
79
80
|
requestId: msg.requestId,
|
|
80
81
|
data: { result: 'success' },
|
|
81
82
|
status: 200,
|
|
82
|
-
statusText: 'OK'
|
|
83
|
+
statusText: 'OK',
|
|
84
|
+
role: MessageRole.SERVER
|
|
83
85
|
},
|
|
84
86
|
origin
|
|
85
87
|
})
|
|
@@ -157,7 +159,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
157
159
|
__requestIframe__: 1,
|
|
158
160
|
type: 'pong',
|
|
159
161
|
requestId: msg.requestId,
|
|
160
|
-
secretKey: msg.secretKey
|
|
162
|
+
secretKey: msg.secretKey,
|
|
163
|
+
role: MessageRole.SERVER
|
|
161
164
|
},
|
|
162
165
|
origin
|
|
163
166
|
})
|
|
@@ -199,7 +202,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
199
202
|
__requestIframe__: 1,
|
|
200
203
|
type: 'ack',
|
|
201
204
|
requestId: msg.requestId,
|
|
202
|
-
path: msg.path
|
|
205
|
+
path: msg.path,
|
|
206
|
+
role: MessageRole.SERVER
|
|
203
207
|
},
|
|
204
208
|
origin
|
|
205
209
|
})
|
|
@@ -215,7 +219,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
215
219
|
requestId: msg.requestId,
|
|
216
220
|
data: { success: true },
|
|
217
221
|
status: 200,
|
|
218
|
-
statusText: 'OK'
|
|
222
|
+
statusText: 'OK',
|
|
223
|
+
role: MessageRole.SERVER
|
|
219
224
|
},
|
|
220
225
|
origin
|
|
221
226
|
})
|
|
@@ -262,7 +267,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
262
267
|
__requestIframe__: 1,
|
|
263
268
|
type: 'ack',
|
|
264
269
|
requestId: msg.requestId,
|
|
265
|
-
path: msg.path
|
|
270
|
+
path: msg.path,
|
|
271
|
+
role: MessageRole.SERVER
|
|
266
272
|
},
|
|
267
273
|
origin
|
|
268
274
|
})
|
|
@@ -278,7 +284,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
278
284
|
requestId: msg.requestId,
|
|
279
285
|
data: { success: true },
|
|
280
286
|
status: 200,
|
|
281
|
-
statusText: 'OK'
|
|
287
|
+
statusText: 'OK',
|
|
288
|
+
role: MessageRole.SERVER
|
|
282
289
|
},
|
|
283
290
|
origin
|
|
284
291
|
})
|
|
@@ -328,7 +335,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
328
335
|
__requestIframe__: 1,
|
|
329
336
|
type: 'ack',
|
|
330
337
|
requestId: msg.requestId,
|
|
331
|
-
path: msg.path
|
|
338
|
+
path: msg.path,
|
|
339
|
+
role: MessageRole.SERVER
|
|
332
340
|
},
|
|
333
341
|
origin
|
|
334
342
|
})
|
|
@@ -347,7 +355,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
347
355
|
code: 'METHOD_NOT_FOUND'
|
|
348
356
|
},
|
|
349
357
|
status: 404,
|
|
350
|
-
statusText: 'Not Found'
|
|
358
|
+
statusText: 'Not Found',
|
|
359
|
+
role: MessageRole.SERVER
|
|
351
360
|
},
|
|
352
361
|
origin
|
|
353
362
|
})
|
|
@@ -389,7 +398,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
389
398
|
__requestIframe__: 1,
|
|
390
399
|
type: 'ack',
|
|
391
400
|
requestId: msg.requestId,
|
|
392
|
-
path: msg.path
|
|
401
|
+
path: msg.path,
|
|
402
|
+
role: MessageRole.SERVER
|
|
393
403
|
},
|
|
394
404
|
origin
|
|
395
405
|
})
|
|
@@ -403,7 +413,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
403
413
|
__requestIframe__: 1,
|
|
404
414
|
type: 'async',
|
|
405
415
|
requestId: msg.requestId,
|
|
406
|
-
path: msg.path
|
|
416
|
+
path: msg.path,
|
|
417
|
+
role: MessageRole.SERVER
|
|
407
418
|
},
|
|
408
419
|
origin
|
|
409
420
|
})
|
|
@@ -420,7 +431,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
420
431
|
requestId: msg.requestId,
|
|
421
432
|
data: { result: 'async success' },
|
|
422
433
|
status: 200,
|
|
423
|
-
statusText: 'OK'
|
|
434
|
+
statusText: 'OK',
|
|
435
|
+
role: MessageRole.SERVER
|
|
424
436
|
},
|
|
425
437
|
origin
|
|
426
438
|
})
|
|
@@ -705,11 +717,16 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
705
717
|
const server = requestIframeServer();
|
|
706
718
|
|
|
707
719
|
server.on('getFile', async (req, res) => {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
720
|
+
try {
|
|
721
|
+
const fileContent = 'Hello World';
|
|
722
|
+
await res.sendFile(fileContent, {
|
|
723
|
+
mimeType: 'text/plain',
|
|
724
|
+
fileName: 'test.txt'
|
|
725
|
+
});
|
|
726
|
+
} catch (error) {
|
|
727
|
+
console.error('Error in sendFile:', error);
|
|
728
|
+
throw error;
|
|
729
|
+
}
|
|
713
730
|
});
|
|
714
731
|
|
|
715
732
|
// Simulate request from iframe
|
|
@@ -727,22 +744,43 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
727
744
|
source: mockContentWindow as any
|
|
728
745
|
})
|
|
729
746
|
);
|
|
730
|
-
|
|
747
|
+
// Wait for async handler to complete
|
|
748
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
731
749
|
|
|
732
|
-
// Verify sendFile was called
|
|
750
|
+
// Verify sendFile was called - now it uses stream
|
|
733
751
|
expect(mockContentWindow.postMessage).toHaveBeenCalled();
|
|
734
|
-
const fileCall = mockContentWindow.postMessage.mock.calls.find(
|
|
735
|
-
(call: any[]) => call[0]?.type === 'response' && call[0]?.fileData
|
|
736
|
-
);
|
|
737
|
-
expect(fileCall).toBeDefined();
|
|
738
|
-
expect(fileCall[0].fileData.mimeType).toBe('text/plain');
|
|
739
|
-
expect(fileCall[0].fileData.fileName).toBe('test.txt');
|
|
740
752
|
|
|
741
|
-
//
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
753
|
+
// Debug: Check all message types sent
|
|
754
|
+
const allCalls = mockContentWindow.postMessage.mock.calls;
|
|
755
|
+
const messageTypes = allCalls.map(call => call[0]?.type).filter(Boolean);
|
|
756
|
+
if (messageTypes.length === 0) {
|
|
757
|
+
throw new Error('No messages were sent to mockContentWindow.postMessage');
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const streamStartCall = allCalls.find(
|
|
761
|
+
(call: any[]) => call[0]?.type === 'stream_start'
|
|
762
|
+
);
|
|
763
|
+
if (!streamStartCall) {
|
|
764
|
+
throw new Error(`stream_start not found. Message types sent: ${messageTypes.join(', ')}`);
|
|
745
765
|
}
|
|
766
|
+
expect(streamStartCall).toBeDefined();
|
|
767
|
+
const streamBody = streamStartCall![0].body;
|
|
768
|
+
expect(streamBody.type).toBe('file');
|
|
769
|
+
expect(streamBody.autoResolve).toBe(true);
|
|
770
|
+
expect(streamBody.metadata?.mimeType).toBe('text/plain');
|
|
771
|
+
expect(streamBody.metadata?.filename).toBe('test.txt');
|
|
772
|
+
|
|
773
|
+
// Verify stream_data was sent
|
|
774
|
+
const streamDataCall = mockContentWindow.postMessage.mock.calls.find(
|
|
775
|
+
(call: any[]) => call[0]?.type === 'stream_data'
|
|
776
|
+
);
|
|
777
|
+
expect(streamDataCall).toBeDefined();
|
|
778
|
+
|
|
779
|
+
// Verify stream_end was sent
|
|
780
|
+
const streamEndCall = mockContentWindow.postMessage.mock.calls.find(
|
|
781
|
+
(call: any[]) => call[0]?.type === 'stream_end'
|
|
782
|
+
);
|
|
783
|
+
expect(streamEndCall).toBeDefined();
|
|
746
784
|
|
|
747
785
|
server.destroy();
|
|
748
786
|
cleanupIframe(iframe);
|
|
@@ -784,13 +822,17 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
784
822
|
source: mockContentWindow as any
|
|
785
823
|
})
|
|
786
824
|
);
|
|
787
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
825
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
788
826
|
|
|
789
|
-
|
|
790
|
-
|
|
827
|
+
// Verify stream_start was sent
|
|
828
|
+
const streamStartCall = mockContentWindow.postMessage.mock.calls.find(
|
|
829
|
+
(call: any[]) => call[0]?.type === 'stream_start'
|
|
791
830
|
);
|
|
792
|
-
expect(
|
|
793
|
-
|
|
831
|
+
expect(streamStartCall).toBeDefined();
|
|
832
|
+
const streamBody = streamStartCall![0].body;
|
|
833
|
+
expect(streamBody.type).toBe('file');
|
|
834
|
+
expect(streamBody.autoResolve).toBe(true);
|
|
835
|
+
expect(streamBody.metadata?.mimeType).toBe('text/plain');
|
|
794
836
|
|
|
795
837
|
server.destroy();
|
|
796
838
|
cleanupIframe(iframe);
|
|
@@ -829,13 +871,17 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
829
871
|
source: mockContentWindow as any
|
|
830
872
|
})
|
|
831
873
|
);
|
|
832
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
874
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
833
875
|
|
|
834
|
-
|
|
835
|
-
|
|
876
|
+
// Verify stream_start was sent
|
|
877
|
+
const streamStartCall = mockContentWindow.postMessage.mock.calls.find(
|
|
878
|
+
(call: any[]) => call[0]?.type === 'stream_start'
|
|
836
879
|
);
|
|
837
|
-
expect(
|
|
838
|
-
|
|
880
|
+
expect(streamStartCall).toBeDefined();
|
|
881
|
+
const streamBody = streamStartCall![0].body;
|
|
882
|
+
expect(streamBody.type).toBe('file');
|
|
883
|
+
expect(streamBody.autoResolve).toBe(true);
|
|
884
|
+
expect(streamBody.metadata?.filename).toBe('test.txt');
|
|
839
885
|
|
|
840
886
|
server.destroy();
|
|
841
887
|
cleanupIframe(iframe);
|
|
@@ -845,52 +891,14 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
845
891
|
const origin = 'https://example.com';
|
|
846
892
|
const iframe = createTestIframe(origin);
|
|
847
893
|
|
|
848
|
-
let responseMessage: any = null;
|
|
849
894
|
const mockContentWindow = {
|
|
850
|
-
postMessage: jest.fn(
|
|
851
|
-
if (msg.type === 'request') {
|
|
852
|
-
window.dispatchEvent(
|
|
853
|
-
new MessageEvent('message', {
|
|
854
|
-
data: {
|
|
855
|
-
__requestIframe__: 1,
|
|
856
|
-
type: 'ack',
|
|
857
|
-
requestId: msg.requestId,
|
|
858
|
-
path: msg.path
|
|
859
|
-
},
|
|
860
|
-
origin
|
|
861
|
-
})
|
|
862
|
-
);
|
|
863
|
-
setTimeout(() => {
|
|
864
|
-
const response: PostMessageData = {
|
|
865
|
-
__requestIframe__: 1,
|
|
866
|
-
timestamp: Date.now(),
|
|
867
|
-
type: 'response',
|
|
868
|
-
requestId: msg.requestId,
|
|
869
|
-
fileData: {
|
|
870
|
-
content: btoa('test'),
|
|
871
|
-
mimeType: 'text/plain',
|
|
872
|
-
fileName: 'test.txt'
|
|
873
|
-
},
|
|
874
|
-
status: 200,
|
|
875
|
-
requireAck: true
|
|
876
|
-
};
|
|
877
|
-
responseMessage = response;
|
|
878
|
-
window.dispatchEvent(
|
|
879
|
-
new MessageEvent('message', {
|
|
880
|
-
data: response,
|
|
881
|
-
origin
|
|
882
|
-
})
|
|
883
|
-
);
|
|
884
|
-
}, 10);
|
|
885
|
-
}
|
|
886
|
-
})
|
|
895
|
+
postMessage: jest.fn()
|
|
887
896
|
};
|
|
888
897
|
Object.defineProperty(iframe, 'contentWindow', {
|
|
889
898
|
value: mockContentWindow,
|
|
890
899
|
writable: true
|
|
891
900
|
});
|
|
892
901
|
|
|
893
|
-
const client = requestIframeClient(iframe);
|
|
894
902
|
const server = requestIframeServer();
|
|
895
903
|
|
|
896
904
|
server.on('getFileAck', async (req, res) => {
|
|
@@ -916,19 +924,150 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
916
924
|
);
|
|
917
925
|
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
918
926
|
|
|
919
|
-
//
|
|
920
|
-
const
|
|
921
|
-
(call: any[]) => call[0]?.type === '
|
|
927
|
+
// Verify stream_start was sent with requireAck
|
|
928
|
+
const streamStartCall = mockContentWindow.postMessage.mock.calls.find(
|
|
929
|
+
(call: any[]) => call[0]?.type === 'stream_start'
|
|
922
930
|
);
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
}
|
|
931
|
+
expect(streamStartCall).toBeDefined();
|
|
932
|
+
const streamBody = streamStartCall![0].body;
|
|
933
|
+
expect(streamBody.type).toBe('file');
|
|
934
|
+
expect(streamBody.autoResolve).toBe(true);
|
|
928
935
|
|
|
929
936
|
server.destroy();
|
|
930
937
|
cleanupIframe(iframe);
|
|
931
938
|
});
|
|
939
|
+
|
|
940
|
+
it('should auto-resolve file stream to fileData on client side', async () => {
|
|
941
|
+
const origin = 'https://example.com';
|
|
942
|
+
const iframe = createTestIframe(origin);
|
|
943
|
+
|
|
944
|
+
const mockContentWindow = {
|
|
945
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
946
|
+
if (msg.type === 'request') {
|
|
947
|
+
// Send ACK first
|
|
948
|
+
window.dispatchEvent(
|
|
949
|
+
new MessageEvent('message', {
|
|
950
|
+
data: {
|
|
951
|
+
__requestIframe__: 1,
|
|
952
|
+
type: 'ack',
|
|
953
|
+
requestId: msg.requestId,
|
|
954
|
+
path: msg.path,
|
|
955
|
+
role: MessageRole.SERVER
|
|
956
|
+
},
|
|
957
|
+
origin
|
|
958
|
+
})
|
|
959
|
+
);
|
|
960
|
+
// Then send stream_start
|
|
961
|
+
setTimeout(() => {
|
|
962
|
+
const streamId = 'stream-test';
|
|
963
|
+
const fileContent = btoa('Hello World');
|
|
964
|
+
|
|
965
|
+
// Send stream_start
|
|
966
|
+
window.dispatchEvent(
|
|
967
|
+
new MessageEvent('message', {
|
|
968
|
+
data: {
|
|
969
|
+
__requestIframe__: 1,
|
|
970
|
+
timestamp: Date.now(),
|
|
971
|
+
type: 'stream_start',
|
|
972
|
+
requestId: msg.requestId,
|
|
973
|
+
status: 200,
|
|
974
|
+
statusText: 'OK',
|
|
975
|
+
headers: {
|
|
976
|
+
'Content-Type': 'text/plain',
|
|
977
|
+
'Content-Disposition': 'attachment; filename="test.txt"'
|
|
978
|
+
},
|
|
979
|
+
body: {
|
|
980
|
+
streamId,
|
|
981
|
+
type: 'file',
|
|
982
|
+
chunked: false,
|
|
983
|
+
autoResolve: true,
|
|
984
|
+
metadata: {
|
|
985
|
+
filename: 'test.txt',
|
|
986
|
+
mimeType: 'text/plain'
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
role: MessageRole.SERVER
|
|
990
|
+
},
|
|
991
|
+
origin
|
|
992
|
+
})
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
// Send stream_data
|
|
996
|
+
setTimeout(() => {
|
|
997
|
+
window.dispatchEvent(
|
|
998
|
+
new MessageEvent('message', {
|
|
999
|
+
data: {
|
|
1000
|
+
__requestIframe__: 1,
|
|
1001
|
+
timestamp: Date.now(),
|
|
1002
|
+
type: 'stream_data',
|
|
1003
|
+
requestId: msg.requestId,
|
|
1004
|
+
body: {
|
|
1005
|
+
streamId,
|
|
1006
|
+
data: fileContent,
|
|
1007
|
+
done: true
|
|
1008
|
+
},
|
|
1009
|
+
role: MessageRole.SERVER
|
|
1010
|
+
},
|
|
1011
|
+
origin
|
|
1012
|
+
})
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
// Send stream_end
|
|
1016
|
+
setTimeout(() => {
|
|
1017
|
+
window.dispatchEvent(
|
|
1018
|
+
new MessageEvent('message', {
|
|
1019
|
+
data: {
|
|
1020
|
+
__requestIframe__: 1,
|
|
1021
|
+
timestamp: Date.now(),
|
|
1022
|
+
type: 'stream_end',
|
|
1023
|
+
requestId: msg.requestId,
|
|
1024
|
+
body: {
|
|
1025
|
+
streamId
|
|
1026
|
+
},
|
|
1027
|
+
role: MessageRole.SERVER
|
|
1028
|
+
},
|
|
1029
|
+
origin
|
|
1030
|
+
})
|
|
1031
|
+
);
|
|
1032
|
+
}, 100);
|
|
1033
|
+
}, 100);
|
|
1034
|
+
}, 100);
|
|
1035
|
+
}
|
|
1036
|
+
})
|
|
1037
|
+
};
|
|
1038
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1039
|
+
value: mockContentWindow,
|
|
1040
|
+
writable: true
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
const client = requestIframeClient(iframe);
|
|
1044
|
+
|
|
1045
|
+
const response = await client.send('getFile', undefined, {
|
|
1046
|
+
ackTimeout: 1000,
|
|
1047
|
+
timeout: 10000
|
|
1048
|
+
}) as any;
|
|
1049
|
+
|
|
1050
|
+
// Verify that data is a File object (auto-resolved from stream)
|
|
1051
|
+
expect(response.data).toBeInstanceOf(File);
|
|
1052
|
+
const file = response.data as File;
|
|
1053
|
+
expect(file.name).toBe('test.txt');
|
|
1054
|
+
expect(file.type).toBe('text/plain');
|
|
1055
|
+
|
|
1056
|
+
// Verify file content using FileReader or arrayBuffer
|
|
1057
|
+
const fileContent = await new Promise<string>((resolve) => {
|
|
1058
|
+
const reader = new FileReader();
|
|
1059
|
+
reader.onload = () => {
|
|
1060
|
+
resolve(reader.result as string);
|
|
1061
|
+
};
|
|
1062
|
+
reader.readAsText(file);
|
|
1063
|
+
});
|
|
1064
|
+
expect(fileContent).toBe('Hello World');
|
|
1065
|
+
|
|
1066
|
+
// Verify that stream is not present (because it was auto-resolved)
|
|
1067
|
+
expect(response.stream).toBeUndefined();
|
|
1068
|
+
|
|
1069
|
+
cleanupIframe(iframe);
|
|
1070
|
+
}, 20000);
|
|
932
1071
|
});
|
|
933
1072
|
|
|
934
1073
|
describe('server.map', () => {
|
|
@@ -1219,11 +1358,19 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
1219
1358
|
);
|
|
1220
1359
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1221
1360
|
|
|
1361
|
+
// Wait for response to be sent
|
|
1362
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1363
|
+
|
|
1222
1364
|
// Simulate client receiving response
|
|
1223
1365
|
const responseCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1224
1366
|
(call: any[]) => call[0]?.type === 'response'
|
|
1225
1367
|
);
|
|
1226
|
-
|
|
1368
|
+
expect(responseCall).toBeDefined();
|
|
1369
|
+
if (responseCall && responseCall[0]) {
|
|
1370
|
+
// Verify response contains Set-Cookie header
|
|
1371
|
+
expect(responseCall[0].headers).toBeDefined();
|
|
1372
|
+
expect(responseCall[0].headers[HttpHeader.SET_COOKIE]).toBeDefined();
|
|
1373
|
+
|
|
1227
1374
|
window.dispatchEvent(
|
|
1228
1375
|
new MessageEvent('message', {
|
|
1229
1376
|
data: responseCall[0],
|
|
@@ -1232,6 +1379,9 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
1232
1379
|
})
|
|
1233
1380
|
);
|
|
1234
1381
|
}
|
|
1382
|
+
|
|
1383
|
+
// Wait for response to be processed
|
|
1384
|
+
await responsePromise;
|
|
1235
1385
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1236
1386
|
|
|
1237
1387
|
// Verify client automatically saved server-set cookies
|
|
@@ -1258,7 +1408,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
1258
1408
|
__requestIframe__: 1,
|
|
1259
1409
|
type: 'ack',
|
|
1260
1410
|
requestId: msg.requestId,
|
|
1261
|
-
path: msg.path
|
|
1411
|
+
path: msg.path,
|
|
1412
|
+
role: MessageRole.SERVER
|
|
1262
1413
|
},
|
|
1263
1414
|
origin
|
|
1264
1415
|
})
|
|
@@ -1271,7 +1422,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
1271
1422
|
requestId: msg.requestId,
|
|
1272
1423
|
data: { result: 'success' },
|
|
1273
1424
|
status: 200,
|
|
1274
|
-
requireAck: true
|
|
1425
|
+
requireAck: true,
|
|
1426
|
+
role: MessageRole.SERVER
|
|
1275
1427
|
};
|
|
1276
1428
|
responseMessage = response;
|
|
1277
1429
|
window.dispatchEvent(
|
|
@@ -1334,7 +1486,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
1334
1486
|
__requestIframe__: 1,
|
|
1335
1487
|
type: 'ack',
|
|
1336
1488
|
requestId: msg.requestId,
|
|
1337
|
-
path: msg.path
|
|
1489
|
+
path: msg.path,
|
|
1490
|
+
role: MessageRole.SERVER
|
|
1338
1491
|
},
|
|
1339
1492
|
origin
|
|
1340
1493
|
})
|
|
@@ -1348,7 +1501,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
1348
1501
|
requestId: msg.requestId,
|
|
1349
1502
|
data: { json: true },
|
|
1350
1503
|
status: 200,
|
|
1351
|
-
requireAck: true
|
|
1504
|
+
requireAck: true,
|
|
1505
|
+
role: MessageRole.SERVER
|
|
1352
1506
|
},
|
|
1353
1507
|
origin
|
|
1354
1508
|
})
|
|
@@ -1409,7 +1563,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
1409
1563
|
__requestIframe__: 1,
|
|
1410
1564
|
type: 'ack',
|
|
1411
1565
|
requestId: msg.requestId,
|
|
1412
|
-
path: msg.path
|
|
1566
|
+
path: msg.path,
|
|
1567
|
+
role: MessageRole.SERVER
|
|
1413
1568
|
},
|
|
1414
1569
|
origin
|
|
1415
1570
|
})
|
|
@@ -1423,7 +1578,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
1423
1578
|
requestId: msg.requestId,
|
|
1424
1579
|
data: { error: 'Not Found' },
|
|
1425
1580
|
status: 404,
|
|
1426
|
-
statusText: 'Not Found'
|
|
1581
|
+
statusText: 'Not Found',
|
|
1582
|
+
role: MessageRole.SERVER
|
|
1427
1583
|
},
|
|
1428
1584
|
origin
|
|
1429
1585
|
})
|
|
@@ -1991,7 +2147,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
1991
2147
|
__requestIframe__: 1,
|
|
1992
2148
|
type: 'ack',
|
|
1993
2149
|
requestId: msg.requestId,
|
|
1994
|
-
path: msg.path
|
|
2150
|
+
path: msg.path,
|
|
2151
|
+
role: MessageRole.SERVER
|
|
1995
2152
|
},
|
|
1996
2153
|
origin
|
|
1997
2154
|
})
|
|
@@ -2007,7 +2164,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
2007
2164
|
streamId: 'stream-123',
|
|
2008
2165
|
type: 'data',
|
|
2009
2166
|
chunked: true
|
|
2010
|
-
}
|
|
2167
|
+
},
|
|
2168
|
+
role: MessageRole.SERVER
|
|
2011
2169
|
},
|
|
2012
2170
|
origin
|
|
2013
2171
|
})
|
|
@@ -2073,7 +2231,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
2073
2231
|
__requestIframe__: 1,
|
|
2074
2232
|
type: 'ack',
|
|
2075
2233
|
requestId: msg.requestId,
|
|
2076
|
-
path: msg.path
|
|
2234
|
+
path: msg.path,
|
|
2235
|
+
role: MessageRole.SERVER
|
|
2077
2236
|
},
|
|
2078
2237
|
origin
|
|
2079
2238
|
})
|
|
@@ -2089,7 +2248,8 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
2089
2248
|
streamId: 'stream-123',
|
|
2090
2249
|
type: 'data',
|
|
2091
2250
|
chunked: true
|
|
2092
|
-
}
|
|
2251
|
+
},
|
|
2252
|
+
role: MessageRole.SERVER
|
|
2093
2253
|
},
|
|
2094
2254
|
origin
|
|
2095
2255
|
})
|
|
@@ -2131,7 +2291,7 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
2131
2291
|
source: mockContentWindow as any
|
|
2132
2292
|
})
|
|
2133
2293
|
);
|
|
2134
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
2294
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
2135
2295
|
|
|
2136
2296
|
const streamStartCall = mockContentWindow.postMessage.mock.calls.find(
|
|
2137
2297
|
(call: any[]) => call[0]?.type === 'stream_start'
|
|
@@ -2403,4 +2563,1872 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
2403
2563
|
cleanupIframe(iframe);
|
|
2404
2564
|
});
|
|
2405
2565
|
});
|
|
2566
|
+
|
|
2567
|
+
describe('Client additional features', () => {
|
|
2568
|
+
it('should support postMessage method for stream handler', () => {
|
|
2569
|
+
const origin = 'https://example.com';
|
|
2570
|
+
const iframe = createTestIframe(origin);
|
|
2571
|
+
const mockContentWindow = {
|
|
2572
|
+
postMessage: jest.fn()
|
|
2573
|
+
};
|
|
2574
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2575
|
+
value: mockContentWindow,
|
|
2576
|
+
writable: true
|
|
2577
|
+
});
|
|
2578
|
+
|
|
2579
|
+
const client = requestIframeClient(iframe);
|
|
2580
|
+
const message = { type: 'test', data: 'value' };
|
|
2581
|
+
|
|
2582
|
+
// Access postMessage through stream handler interface
|
|
2583
|
+
(client as any).postMessage(message);
|
|
2584
|
+
|
|
2585
|
+
// Verify message was sent via dispatcher
|
|
2586
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalled();
|
|
2587
|
+
|
|
2588
|
+
cleanupIframe(iframe);
|
|
2589
|
+
});
|
|
2590
|
+
|
|
2591
|
+
it('should handle function-type headers', async () => {
|
|
2592
|
+
const origin = 'https://example.com';
|
|
2593
|
+
const iframe = createTestIframe(origin);
|
|
2594
|
+
const mockContentWindow = {
|
|
2595
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
2596
|
+
if (msg.type === 'request') {
|
|
2597
|
+
window.dispatchEvent(
|
|
2598
|
+
new MessageEvent('message', {
|
|
2599
|
+
data: {
|
|
2600
|
+
__requestIframe__: 1,
|
|
2601
|
+
type: 'ack',
|
|
2602
|
+
requestId: msg.requestId,
|
|
2603
|
+
path: msg.path,
|
|
2604
|
+
role: MessageRole.SERVER
|
|
2605
|
+
},
|
|
2606
|
+
origin
|
|
2607
|
+
})
|
|
2608
|
+
);
|
|
2609
|
+
setTimeout(() => {
|
|
2610
|
+
window.dispatchEvent(
|
|
2611
|
+
new MessageEvent('message', {
|
|
2612
|
+
data: {
|
|
2613
|
+
__requestIframe__: 1,
|
|
2614
|
+
type: 'response',
|
|
2615
|
+
requestId: msg.requestId,
|
|
2616
|
+
data: { result: 'success' },
|
|
2617
|
+
status: 200,
|
|
2618
|
+
statusText: 'OK',
|
|
2619
|
+
role: MessageRole.SERVER
|
|
2620
|
+
},
|
|
2621
|
+
origin
|
|
2622
|
+
})
|
|
2623
|
+
);
|
|
2624
|
+
}, 10);
|
|
2625
|
+
}
|
|
2626
|
+
})
|
|
2627
|
+
};
|
|
2628
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2629
|
+
value: mockContentWindow,
|
|
2630
|
+
writable: true
|
|
2631
|
+
});
|
|
2632
|
+
|
|
2633
|
+
const client = requestIframeClient(iframe, {
|
|
2634
|
+
headers: {
|
|
2635
|
+
'X-Dynamic': (config: RequestConfig) => `value-${config.path}`
|
|
2636
|
+
}
|
|
2637
|
+
});
|
|
2638
|
+
|
|
2639
|
+
await client.send('test', {});
|
|
2640
|
+
|
|
2641
|
+
const requestCall = mockContentWindow.postMessage.mock.calls.find(
|
|
2642
|
+
(call: any[]) => call[0]?.type === 'request'
|
|
2643
|
+
);
|
|
2644
|
+
expect(requestCall).toBeDefined();
|
|
2645
|
+
if (requestCall && requestCall[0]) {
|
|
2646
|
+
expect(requestCall[0].headers?.['X-Dynamic']).toBe('value-test');
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
cleanupIframe(iframe);
|
|
2650
|
+
});
|
|
2651
|
+
|
|
2652
|
+
it('should handle isConnect timeout', async () => {
|
|
2653
|
+
const origin = 'https://example.com';
|
|
2654
|
+
const iframe = createTestIframe(origin);
|
|
2655
|
+
const mockContentWindow = {
|
|
2656
|
+
postMessage: jest.fn()
|
|
2657
|
+
};
|
|
2658
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2659
|
+
value: mockContentWindow,
|
|
2660
|
+
writable: true
|
|
2661
|
+
});
|
|
2662
|
+
|
|
2663
|
+
const client = requestIframeClient(iframe, { ackTimeout: 50 });
|
|
2664
|
+
|
|
2665
|
+
// Server doesn't respond, should timeout
|
|
2666
|
+
const connected = await client.isConnect();
|
|
2667
|
+
expect(connected).toBe(false);
|
|
2668
|
+
|
|
2669
|
+
cleanupIframe(iframe);
|
|
2670
|
+
});
|
|
2671
|
+
|
|
2672
|
+
it('should handle isConnect rejection', async () => {
|
|
2673
|
+
const origin = 'https://example.com';
|
|
2674
|
+
const iframe = createTestIframe(origin);
|
|
2675
|
+
const mockContentWindow = {
|
|
2676
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
2677
|
+
if (msg.type === 'ping') {
|
|
2678
|
+
// Simulate error by not sending pong
|
|
2679
|
+
setTimeout(() => {
|
|
2680
|
+
window.dispatchEvent(
|
|
2681
|
+
new MessageEvent('message', {
|
|
2682
|
+
data: {
|
|
2683
|
+
__requestIframe__: 1,
|
|
2684
|
+
type: 'error',
|
|
2685
|
+
requestId: msg.requestId,
|
|
2686
|
+
error: { message: 'Connection failed' },
|
|
2687
|
+
role: MessageRole.SERVER
|
|
2688
|
+
},
|
|
2689
|
+
origin
|
|
2690
|
+
})
|
|
2691
|
+
);
|
|
2692
|
+
}, 10);
|
|
2693
|
+
}
|
|
2694
|
+
})
|
|
2695
|
+
};
|
|
2696
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2697
|
+
value: mockContentWindow,
|
|
2698
|
+
writable: true
|
|
2699
|
+
});
|
|
2700
|
+
|
|
2701
|
+
const client = requestIframeClient(iframe);
|
|
2702
|
+
|
|
2703
|
+
const connected = await client.isConnect();
|
|
2704
|
+
expect(connected).toBe(false);
|
|
2705
|
+
|
|
2706
|
+
cleanupIframe(iframe);
|
|
2707
|
+
});
|
|
2708
|
+
|
|
2709
|
+
it('should remember targetServerId from ACK and use it in subsequent requests', async () => {
|
|
2710
|
+
const origin = 'https://example.com';
|
|
2711
|
+
const iframe = createTestIframe(origin);
|
|
2712
|
+
const serverId = 'server-123';
|
|
2713
|
+
|
|
2714
|
+
const mockContentWindow = {
|
|
2715
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
2716
|
+
if (msg.type === 'request') {
|
|
2717
|
+
window.dispatchEvent(
|
|
2718
|
+
new MessageEvent('message', {
|
|
2719
|
+
data: {
|
|
2720
|
+
__requestIframe__: 1,
|
|
2721
|
+
type: 'ack',
|
|
2722
|
+
requestId: msg.requestId,
|
|
2723
|
+
path: msg.path,
|
|
2724
|
+
role: MessageRole.SERVER,
|
|
2725
|
+
creatorId: serverId
|
|
2726
|
+
},
|
|
2727
|
+
origin
|
|
2728
|
+
})
|
|
2729
|
+
);
|
|
2730
|
+
setTimeout(() => {
|
|
2731
|
+
window.dispatchEvent(
|
|
2732
|
+
new MessageEvent('message', {
|
|
2733
|
+
data: {
|
|
2734
|
+
__requestIframe__: 1,
|
|
2735
|
+
type: 'response',
|
|
2736
|
+
requestId: msg.requestId,
|
|
2737
|
+
data: { result: 'success' },
|
|
2738
|
+
status: 200,
|
|
2739
|
+
statusText: 'OK',
|
|
2740
|
+
role: MessageRole.SERVER,
|
|
2741
|
+
creatorId: serverId
|
|
2742
|
+
},
|
|
2743
|
+
origin
|
|
2744
|
+
})
|
|
2745
|
+
);
|
|
2746
|
+
}, 10);
|
|
2747
|
+
}
|
|
2748
|
+
})
|
|
2749
|
+
};
|
|
2750
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2751
|
+
value: mockContentWindow,
|
|
2752
|
+
writable: true
|
|
2753
|
+
});
|
|
2754
|
+
|
|
2755
|
+
const client = requestIframeClient(iframe);
|
|
2756
|
+
|
|
2757
|
+
// First request - should remember serverId
|
|
2758
|
+
await client.send('test1', {});
|
|
2759
|
+
|
|
2760
|
+
// Second request - should use remembered serverId
|
|
2761
|
+
await client.send('test2', {});
|
|
2762
|
+
|
|
2763
|
+
const requestCalls = mockContentWindow.postMessage.mock.calls.filter(
|
|
2764
|
+
(call: any[]) => call[0]?.type === 'request'
|
|
2765
|
+
);
|
|
2766
|
+
|
|
2767
|
+
// First request may not have targetId (if serverId not remembered yet)
|
|
2768
|
+
// Second request should have targetId
|
|
2769
|
+
expect(requestCalls.length).toBeGreaterThanOrEqual(2);
|
|
2770
|
+
const secondRequest = requestCalls[requestCalls.length - 1];
|
|
2771
|
+
if (secondRequest) {
|
|
2772
|
+
expect(secondRequest[0].targetId).toBe(serverId);
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
cleanupIframe(iframe);
|
|
2776
|
+
});
|
|
2777
|
+
|
|
2778
|
+
it('should not override existing targetServerId', async () => {
|
|
2779
|
+
const origin = 'https://example.com';
|
|
2780
|
+
const iframe = createTestIframe(origin);
|
|
2781
|
+
const existingServerId = 'existing-server';
|
|
2782
|
+
const newServerId = 'new-server';
|
|
2783
|
+
|
|
2784
|
+
const mockContentWindow = {
|
|
2785
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
2786
|
+
if (msg.type === 'request') {
|
|
2787
|
+
window.dispatchEvent(
|
|
2788
|
+
new MessageEvent('message', {
|
|
2789
|
+
data: {
|
|
2790
|
+
__requestIframe__: 1,
|
|
2791
|
+
type: 'ack',
|
|
2792
|
+
requestId: msg.requestId,
|
|
2793
|
+
path: msg.path,
|
|
2794
|
+
role: MessageRole.SERVER,
|
|
2795
|
+
creatorId: newServerId
|
|
2796
|
+
},
|
|
2797
|
+
origin
|
|
2798
|
+
})
|
|
2799
|
+
);
|
|
2800
|
+
setTimeout(() => {
|
|
2801
|
+
window.dispatchEvent(
|
|
2802
|
+
new MessageEvent('message', {
|
|
2803
|
+
data: {
|
|
2804
|
+
__requestIframe__: 1,
|
|
2805
|
+
type: 'response',
|
|
2806
|
+
requestId: msg.requestId,
|
|
2807
|
+
data: { result: 'success' },
|
|
2808
|
+
status: 200,
|
|
2809
|
+
statusText: 'OK',
|
|
2810
|
+
role: MessageRole.SERVER,
|
|
2811
|
+
creatorId: newServerId
|
|
2812
|
+
},
|
|
2813
|
+
origin
|
|
2814
|
+
})
|
|
2815
|
+
);
|
|
2816
|
+
}, 10);
|
|
2817
|
+
}
|
|
2818
|
+
})
|
|
2819
|
+
};
|
|
2820
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2821
|
+
value: mockContentWindow,
|
|
2822
|
+
writable: true
|
|
2823
|
+
});
|
|
2824
|
+
|
|
2825
|
+
const client = requestIframeClient(iframe);
|
|
2826
|
+
|
|
2827
|
+
// Set existing targetServerId
|
|
2828
|
+
(client as any)._targetServerId = existingServerId;
|
|
2829
|
+
|
|
2830
|
+
// Send request with explicit targetId
|
|
2831
|
+
await client.send('test', {}, { targetId: existingServerId });
|
|
2832
|
+
|
|
2833
|
+
const requestCall = mockContentWindow.postMessage.mock.calls.find(
|
|
2834
|
+
(call: any[]) => call[0]?.type === 'request'
|
|
2835
|
+
);
|
|
2836
|
+
expect(requestCall).toBeDefined();
|
|
2837
|
+
if (requestCall) {
|
|
2838
|
+
expect(requestCall[0].targetId).toBe(existingServerId);
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// targetServerId should not be overridden
|
|
2842
|
+
expect((client as any)._targetServerId).toBe(existingServerId);
|
|
2843
|
+
|
|
2844
|
+
cleanupIframe(iframe);
|
|
2845
|
+
});
|
|
2846
|
+
|
|
2847
|
+
it('should handle setCookie with expires option', () => {
|
|
2848
|
+
const origin = 'https://example.com';
|
|
2849
|
+
const iframe = createTestIframe(origin);
|
|
2850
|
+
const mockContentWindow = {
|
|
2851
|
+
postMessage: jest.fn()
|
|
2852
|
+
};
|
|
2853
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2854
|
+
value: mockContentWindow,
|
|
2855
|
+
writable: true
|
|
2856
|
+
});
|
|
2857
|
+
|
|
2858
|
+
const client = requestIframeClient(iframe);
|
|
2859
|
+
const expires = new Date(Date.now() + 3600000); // 1 hour from now
|
|
2860
|
+
|
|
2861
|
+
client.setCookie('token', 'value', { expires });
|
|
2862
|
+
|
|
2863
|
+
expect(client.getCookie('token')).toBe('value');
|
|
2864
|
+
|
|
2865
|
+
cleanupIframe(iframe);
|
|
2866
|
+
});
|
|
2867
|
+
|
|
2868
|
+
it('should handle setCookie with maxAge option', () => {
|
|
2869
|
+
const origin = 'https://example.com';
|
|
2870
|
+
const iframe = createTestIframe(origin);
|
|
2871
|
+
const mockContentWindow = {
|
|
2872
|
+
postMessage: jest.fn()
|
|
2873
|
+
};
|
|
2874
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2875
|
+
value: mockContentWindow,
|
|
2876
|
+
writable: true
|
|
2877
|
+
});
|
|
2878
|
+
|
|
2879
|
+
const client = requestIframeClient(iframe);
|
|
2880
|
+
|
|
2881
|
+
client.setCookie('token', 'value', { maxAge: 3600 });
|
|
2882
|
+
|
|
2883
|
+
expect(client.getCookie('token')).toBe('value');
|
|
2884
|
+
|
|
2885
|
+
cleanupIframe(iframe);
|
|
2886
|
+
});
|
|
2887
|
+
|
|
2888
|
+
it('should handle getServer method', () => {
|
|
2889
|
+
const origin = 'https://example.com';
|
|
2890
|
+
const iframe = createTestIframe(origin);
|
|
2891
|
+
const mockContentWindow = {
|
|
2892
|
+
postMessage: jest.fn()
|
|
2893
|
+
};
|
|
2894
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2895
|
+
value: mockContentWindow,
|
|
2896
|
+
writable: true
|
|
2897
|
+
});
|
|
2898
|
+
|
|
2899
|
+
const client = requestIframeClient(iframe);
|
|
2900
|
+
const server = (client as any).getServer();
|
|
2901
|
+
|
|
2902
|
+
expect(server).toBeDefined();
|
|
2903
|
+
expect(server.isOpen).toBe(true);
|
|
2904
|
+
|
|
2905
|
+
cleanupIframe(iframe);
|
|
2906
|
+
});
|
|
2907
|
+
|
|
2908
|
+
it('should handle non-autoResolve file stream', async () => {
|
|
2909
|
+
const origin = 'https://example.com';
|
|
2910
|
+
const iframe = createTestIframe(origin);
|
|
2911
|
+
const mockContentWindow = {
|
|
2912
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
2913
|
+
if (msg.type === 'request') {
|
|
2914
|
+
window.dispatchEvent(
|
|
2915
|
+
new MessageEvent('message', {
|
|
2916
|
+
data: {
|
|
2917
|
+
__requestIframe__: 1,
|
|
2918
|
+
type: 'ack',
|
|
2919
|
+
requestId: msg.requestId,
|
|
2920
|
+
path: msg.path,
|
|
2921
|
+
role: MessageRole.SERVER
|
|
2922
|
+
},
|
|
2923
|
+
origin
|
|
2924
|
+
})
|
|
2925
|
+
);
|
|
2926
|
+
setTimeout(() => {
|
|
2927
|
+
const streamId = 'stream-test';
|
|
2928
|
+
window.dispatchEvent(
|
|
2929
|
+
new MessageEvent('message', {
|
|
2930
|
+
data: {
|
|
2931
|
+
__requestIframe__: 1,
|
|
2932
|
+
timestamp: Date.now(),
|
|
2933
|
+
type: 'stream_start',
|
|
2934
|
+
requestId: msg.requestId,
|
|
2935
|
+
status: 200,
|
|
2936
|
+
statusText: 'OK',
|
|
2937
|
+
body: {
|
|
2938
|
+
streamId,
|
|
2939
|
+
type: 'file',
|
|
2940
|
+
chunked: false,
|
|
2941
|
+
autoResolve: false, // Not auto-resolve
|
|
2942
|
+
metadata: {
|
|
2943
|
+
filename: 'test.txt',
|
|
2944
|
+
mimeType: 'text/plain'
|
|
2945
|
+
}
|
|
2946
|
+
},
|
|
2947
|
+
role: MessageRole.SERVER
|
|
2948
|
+
},
|
|
2949
|
+
origin
|
|
2950
|
+
})
|
|
2951
|
+
);
|
|
2952
|
+
setTimeout(() => {
|
|
2953
|
+
window.dispatchEvent(
|
|
2954
|
+
new MessageEvent('message', {
|
|
2955
|
+
data: {
|
|
2956
|
+
__requestIframe__: 1,
|
|
2957
|
+
timestamp: Date.now(),
|
|
2958
|
+
type: 'stream_data',
|
|
2959
|
+
requestId: msg.requestId,
|
|
2960
|
+
body: {
|
|
2961
|
+
streamId,
|
|
2962
|
+
data: btoa('Hello World'),
|
|
2963
|
+
done: true
|
|
2964
|
+
},
|
|
2965
|
+
role: MessageRole.SERVER
|
|
2966
|
+
},
|
|
2967
|
+
origin
|
|
2968
|
+
})
|
|
2969
|
+
);
|
|
2970
|
+
setTimeout(() => {
|
|
2971
|
+
window.dispatchEvent(
|
|
2972
|
+
new MessageEvent('message', {
|
|
2973
|
+
data: {
|
|
2974
|
+
__requestIframe__: 1,
|
|
2975
|
+
timestamp: Date.now(),
|
|
2976
|
+
type: 'stream_end',
|
|
2977
|
+
requestId: msg.requestId,
|
|
2978
|
+
body: { streamId },
|
|
2979
|
+
role: MessageRole.SERVER
|
|
2980
|
+
},
|
|
2981
|
+
origin
|
|
2982
|
+
})
|
|
2983
|
+
);
|
|
2984
|
+
}, 10);
|
|
2985
|
+
}, 10);
|
|
2986
|
+
}, 10);
|
|
2987
|
+
}
|
|
2988
|
+
})
|
|
2989
|
+
};
|
|
2990
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2991
|
+
value: mockContentWindow,
|
|
2992
|
+
writable: true
|
|
2993
|
+
});
|
|
2994
|
+
|
|
2995
|
+
const client = requestIframeClient(iframe);
|
|
2996
|
+
const response = await client.send('getFile', {}, {
|
|
2997
|
+
ackTimeout: 1000,
|
|
2998
|
+
timeout: 10000
|
|
2999
|
+
}) as any;
|
|
3000
|
+
|
|
3001
|
+
expect(response.stream).toBeDefined();
|
|
3002
|
+
expect(response.data).not.toBeInstanceOf(File); // Not auto-resolved, data is not a File
|
|
3003
|
+
|
|
3004
|
+
cleanupIframe(iframe);
|
|
3005
|
+
}, 20000);
|
|
3006
|
+
|
|
3007
|
+
it('should handle regular data stream (non-file)', async () => {
|
|
3008
|
+
const origin = 'https://example.com';
|
|
3009
|
+
const iframe = createTestIframe(origin);
|
|
3010
|
+
const mockContentWindow = {
|
|
3011
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
3012
|
+
if (msg.type === 'request') {
|
|
3013
|
+
window.dispatchEvent(
|
|
3014
|
+
new MessageEvent('message', {
|
|
3015
|
+
data: {
|
|
3016
|
+
__requestIframe__: 1,
|
|
3017
|
+
type: 'ack',
|
|
3018
|
+
requestId: msg.requestId,
|
|
3019
|
+
path: msg.path,
|
|
3020
|
+
role: MessageRole.SERVER
|
|
3021
|
+
},
|
|
3022
|
+
origin
|
|
3023
|
+
})
|
|
3024
|
+
);
|
|
3025
|
+
setTimeout(() => {
|
|
3026
|
+
const streamId = 'stream-test';
|
|
3027
|
+
window.dispatchEvent(
|
|
3028
|
+
new MessageEvent('message', {
|
|
3029
|
+
data: {
|
|
3030
|
+
__requestIframe__: 1,
|
|
3031
|
+
timestamp: Date.now(),
|
|
3032
|
+
type: 'stream_start',
|
|
3033
|
+
requestId: msg.requestId,
|
|
3034
|
+
status: 200,
|
|
3035
|
+
statusText: 'OK',
|
|
3036
|
+
body: {
|
|
3037
|
+
streamId,
|
|
3038
|
+
type: 'data',
|
|
3039
|
+
chunked: true
|
|
3040
|
+
},
|
|
3041
|
+
role: MessageRole.SERVER
|
|
3042
|
+
},
|
|
3043
|
+
origin
|
|
3044
|
+
})
|
|
3045
|
+
);
|
|
3046
|
+
setTimeout(() => {
|
|
3047
|
+
window.dispatchEvent(
|
|
3048
|
+
new MessageEvent('message', {
|
|
3049
|
+
data: {
|
|
3050
|
+
__requestIframe__: 1,
|
|
3051
|
+
timestamp: Date.now(),
|
|
3052
|
+
type: 'stream_data',
|
|
3053
|
+
requestId: msg.requestId,
|
|
3054
|
+
body: {
|
|
3055
|
+
streamId,
|
|
3056
|
+
data: btoa('chunk1'),
|
|
3057
|
+
done: false
|
|
3058
|
+
},
|
|
3059
|
+
role: MessageRole.SERVER
|
|
3060
|
+
},
|
|
3061
|
+
origin
|
|
3062
|
+
})
|
|
3063
|
+
);
|
|
3064
|
+
setTimeout(() => {
|
|
3065
|
+
window.dispatchEvent(
|
|
3066
|
+
new MessageEvent('message', {
|
|
3067
|
+
data: {
|
|
3068
|
+
__requestIframe__: 1,
|
|
3069
|
+
timestamp: Date.now(),
|
|
3070
|
+
type: 'stream_data',
|
|
3071
|
+
requestId: msg.requestId,
|
|
3072
|
+
body: {
|
|
3073
|
+
streamId,
|
|
3074
|
+
data: btoa('chunk2'),
|
|
3075
|
+
done: true
|
|
3076
|
+
},
|
|
3077
|
+
role: MessageRole.SERVER
|
|
3078
|
+
},
|
|
3079
|
+
origin
|
|
3080
|
+
})
|
|
3081
|
+
);
|
|
3082
|
+
setTimeout(() => {
|
|
3083
|
+
window.dispatchEvent(
|
|
3084
|
+
new MessageEvent('message', {
|
|
3085
|
+
data: {
|
|
3086
|
+
__requestIframe__: 1,
|
|
3087
|
+
timestamp: Date.now(),
|
|
3088
|
+
type: 'stream_end',
|
|
3089
|
+
requestId: msg.requestId,
|
|
3090
|
+
body: { streamId },
|
|
3091
|
+
role: MessageRole.SERVER
|
|
3092
|
+
},
|
|
3093
|
+
origin
|
|
3094
|
+
})
|
|
3095
|
+
);
|
|
3096
|
+
}, 10);
|
|
3097
|
+
}, 10);
|
|
3098
|
+
}, 10);
|
|
3099
|
+
}, 10);
|
|
3100
|
+
}
|
|
3101
|
+
})
|
|
3102
|
+
};
|
|
3103
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3104
|
+
value: mockContentWindow,
|
|
3105
|
+
writable: true
|
|
3106
|
+
});
|
|
3107
|
+
|
|
3108
|
+
const client = requestIframeClient(iframe);
|
|
3109
|
+
const response = await client.send('getStream', {}, {
|
|
3110
|
+
ackTimeout: 1000,
|
|
3111
|
+
timeout: 10000
|
|
3112
|
+
}) as any;
|
|
3113
|
+
|
|
3114
|
+
expect(response.stream).toBeDefined();
|
|
3115
|
+
expect(response.stream.type).toBe('data');
|
|
3116
|
+
|
|
3117
|
+
cleanupIframe(iframe);
|
|
3118
|
+
}, 20000);
|
|
3119
|
+
|
|
3120
|
+
it('should handle dispatchStreamMessage for stream messages', async () => {
|
|
3121
|
+
const origin = 'https://example.com';
|
|
3122
|
+
const iframe = createTestIframe(origin);
|
|
3123
|
+
const mockContentWindow = {
|
|
3124
|
+
postMessage: jest.fn()
|
|
3125
|
+
};
|
|
3126
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3127
|
+
value: mockContentWindow,
|
|
3128
|
+
writable: true
|
|
3129
|
+
});
|
|
3130
|
+
|
|
3131
|
+
const client = requestIframeClient(iframe);
|
|
3132
|
+
const streamId = 'test-stream';
|
|
3133
|
+
const handler = jest.fn();
|
|
3134
|
+
|
|
3135
|
+
// Register stream handler
|
|
3136
|
+
(client as any).registerStreamHandler(streamId, handler);
|
|
3137
|
+
|
|
3138
|
+
// Dispatch stream message
|
|
3139
|
+
window.dispatchEvent(
|
|
3140
|
+
new MessageEvent('message', {
|
|
3141
|
+
data: {
|
|
3142
|
+
__requestIframe__: 1,
|
|
3143
|
+
type: 'stream_data',
|
|
3144
|
+
requestId: 'req123',
|
|
3145
|
+
body: {
|
|
3146
|
+
streamId,
|
|
3147
|
+
data: 'test',
|
|
3148
|
+
type: 'data'
|
|
3149
|
+
},
|
|
3150
|
+
role: MessageRole.SERVER
|
|
3151
|
+
},
|
|
3152
|
+
origin
|
|
3153
|
+
})
|
|
3154
|
+
);
|
|
3155
|
+
|
|
3156
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
3157
|
+
|
|
3158
|
+
expect(handler).toHaveBeenCalled();
|
|
3159
|
+
|
|
3160
|
+
cleanupIframe(iframe);
|
|
3161
|
+
});
|
|
3162
|
+
|
|
3163
|
+
it('should handle error in response interceptor rejected callback', async () => {
|
|
3164
|
+
const origin = 'https://example.com';
|
|
3165
|
+
const iframe = createTestIframe(origin);
|
|
3166
|
+
const mockContentWindow = {
|
|
3167
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
3168
|
+
if (msg.type === 'request') {
|
|
3169
|
+
window.dispatchEvent(
|
|
3170
|
+
new MessageEvent('message', {
|
|
3171
|
+
data: {
|
|
3172
|
+
__requestIframe__: 1,
|
|
3173
|
+
type: 'ack',
|
|
3174
|
+
requestId: msg.requestId,
|
|
3175
|
+
path: msg.path,
|
|
3176
|
+
role: MessageRole.SERVER
|
|
3177
|
+
},
|
|
3178
|
+
origin
|
|
3179
|
+
})
|
|
3180
|
+
);
|
|
3181
|
+
setTimeout(() => {
|
|
3182
|
+
window.dispatchEvent(
|
|
3183
|
+
new MessageEvent('message', {
|
|
3184
|
+
data: {
|
|
3185
|
+
__requestIframe__: 1,
|
|
3186
|
+
type: 'error',
|
|
3187
|
+
requestId: msg.requestId,
|
|
3188
|
+
error: { message: 'Test error', code: 'TEST_ERROR' },
|
|
3189
|
+
status: 500,
|
|
3190
|
+
statusText: 'Internal Server Error',
|
|
3191
|
+
role: MessageRole.SERVER
|
|
3192
|
+
},
|
|
3193
|
+
origin
|
|
3194
|
+
})
|
|
3195
|
+
);
|
|
3196
|
+
}, 10);
|
|
3197
|
+
}
|
|
3198
|
+
})
|
|
3199
|
+
};
|
|
3200
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3201
|
+
value: mockContentWindow,
|
|
3202
|
+
writable: true
|
|
3203
|
+
});
|
|
3204
|
+
|
|
3205
|
+
const client = requestIframeClient(iframe);
|
|
3206
|
+
|
|
3207
|
+
// Add error interceptor that rejects
|
|
3208
|
+
client.interceptors.response.use(
|
|
3209
|
+
(response) => response,
|
|
3210
|
+
(error) => {
|
|
3211
|
+
// Reject to test the catch path
|
|
3212
|
+
return Promise.reject(error);
|
|
3213
|
+
}
|
|
3214
|
+
);
|
|
3215
|
+
|
|
3216
|
+
try {
|
|
3217
|
+
await client.send('test', {});
|
|
3218
|
+
fail('Should have thrown error');
|
|
3219
|
+
} catch (error: any) {
|
|
3220
|
+
expect(error.message).toBe('Test error');
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
cleanupIframe(iframe);
|
|
3224
|
+
});
|
|
3225
|
+
});
|
|
3226
|
+
|
|
3227
|
+
describe('Server additional features', () => {
|
|
3228
|
+
it('should handle protocol version error', async () => {
|
|
3229
|
+
const origin = 'https://example.com';
|
|
3230
|
+
const iframe = createTestIframe(origin);
|
|
3231
|
+
const mockContentWindow = {
|
|
3232
|
+
postMessage: jest.fn()
|
|
3233
|
+
};
|
|
3234
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3235
|
+
value: mockContentWindow,
|
|
3236
|
+
writable: true
|
|
3237
|
+
});
|
|
3238
|
+
|
|
3239
|
+
const server = requestIframeServer();
|
|
3240
|
+
|
|
3241
|
+
// Send message with incompatible version
|
|
3242
|
+
window.dispatchEvent(
|
|
3243
|
+
new MessageEvent('message', {
|
|
3244
|
+
data: {
|
|
3245
|
+
__requestIframe__: 0, // Incompatible version
|
|
3246
|
+
timestamp: Date.now(),
|
|
3247
|
+
type: 'request',
|
|
3248
|
+
requestId: 'req123',
|
|
3249
|
+
path: 'test',
|
|
3250
|
+
role: MessageRole.CLIENT
|
|
3251
|
+
},
|
|
3252
|
+
origin,
|
|
3253
|
+
source: mockContentWindow as any
|
|
3254
|
+
})
|
|
3255
|
+
);
|
|
3256
|
+
|
|
3257
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
3258
|
+
|
|
3259
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
|
|
3260
|
+
expect.objectContaining({
|
|
3261
|
+
type: 'error',
|
|
3262
|
+
requestId: 'req123'
|
|
3263
|
+
}),
|
|
3264
|
+
origin
|
|
3265
|
+
);
|
|
3266
|
+
|
|
3267
|
+
server.destroy();
|
|
3268
|
+
cleanupIframe(iframe);
|
|
3269
|
+
});
|
|
3270
|
+
|
|
3271
|
+
it('should handle handler returning undefined result', async () => {
|
|
3272
|
+
const origin = 'https://example.com';
|
|
3273
|
+
const iframe = createTestIframe(origin);
|
|
3274
|
+
const mockContentWindow = {
|
|
3275
|
+
postMessage: jest.fn()
|
|
3276
|
+
};
|
|
3277
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3278
|
+
value: mockContentWindow,
|
|
3279
|
+
writable: true
|
|
3280
|
+
});
|
|
3281
|
+
|
|
3282
|
+
const server = requestIframeServer();
|
|
3283
|
+
|
|
3284
|
+
server.on('test', (req, res) => {
|
|
3285
|
+
// Handler doesn't return anything (undefined)
|
|
3286
|
+
// This should trigger NO_RESPONSE_SENT error
|
|
3287
|
+
});
|
|
3288
|
+
|
|
3289
|
+
window.dispatchEvent(
|
|
3290
|
+
new MessageEvent('message', {
|
|
3291
|
+
data: {
|
|
3292
|
+
__requestIframe__: 1,
|
|
3293
|
+
timestamp: Date.now(),
|
|
3294
|
+
type: 'request',
|
|
3295
|
+
requestId: 'req123',
|
|
3296
|
+
path: 'test',
|
|
3297
|
+
role: MessageRole.CLIENT,
|
|
3298
|
+
targetId: server.id
|
|
3299
|
+
},
|
|
3300
|
+
origin,
|
|
3301
|
+
source: mockContentWindow as any
|
|
3302
|
+
})
|
|
3303
|
+
);
|
|
3304
|
+
|
|
3305
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
3306
|
+
|
|
3307
|
+
const errorCall = mockContentWindow.postMessage.mock.calls.find(
|
|
3308
|
+
(call: any[]) => call[0]?.type === 'error' && call[0]?.requestId === 'req123'
|
|
3309
|
+
);
|
|
3310
|
+
expect(errorCall).toBeDefined();
|
|
3311
|
+
if (errorCall && errorCall[0]) {
|
|
3312
|
+
expect(errorCall[0]).toMatchObject({
|
|
3313
|
+
type: 'error',
|
|
3314
|
+
requestId: 'req123',
|
|
3315
|
+
error: expect.objectContaining({
|
|
3316
|
+
code: 'NO_RESPONSE'
|
|
3317
|
+
})
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
server.destroy();
|
|
3322
|
+
cleanupIframe(iframe);
|
|
3323
|
+
});
|
|
3324
|
+
|
|
3325
|
+
it('should skip processing when message already handled by another server', async () => {
|
|
3326
|
+
const origin = 'https://example.com';
|
|
3327
|
+
const iframe = createTestIframe(origin);
|
|
3328
|
+
const mockContentWindow = {
|
|
3329
|
+
postMessage: jest.fn()
|
|
3330
|
+
};
|
|
3331
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3332
|
+
value: mockContentWindow,
|
|
3333
|
+
writable: true
|
|
3334
|
+
});
|
|
3335
|
+
|
|
3336
|
+
const server1 = requestIframeServer();
|
|
3337
|
+
const server2 = requestIframeServer();
|
|
3338
|
+
|
|
3339
|
+
const handler1 = jest.fn((req, res) => res.send({ server: 1 }));
|
|
3340
|
+
const handler2 = jest.fn((req, res) => res.send({ server: 2 }));
|
|
3341
|
+
|
|
3342
|
+
server1.on('test', handler1);
|
|
3343
|
+
server2.on('test', handler2);
|
|
3344
|
+
|
|
3345
|
+
// Create a context that indicates message was already handled
|
|
3346
|
+
const messageData = {
|
|
3347
|
+
__requestIframe__: 1,
|
|
3348
|
+
timestamp: Date.now(),
|
|
3349
|
+
type: 'request' as const,
|
|
3350
|
+
requestId: 'req123',
|
|
3351
|
+
path: 'test',
|
|
3352
|
+
role: MessageRole.CLIENT,
|
|
3353
|
+
targetId: server1.id
|
|
3354
|
+
};
|
|
3355
|
+
|
|
3356
|
+
window.dispatchEvent(
|
|
3357
|
+
new MessageEvent('message', {
|
|
3358
|
+
data: messageData,
|
|
3359
|
+
origin,
|
|
3360
|
+
source: mockContentWindow as any
|
|
3361
|
+
})
|
|
3362
|
+
);
|
|
3363
|
+
|
|
3364
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
3365
|
+
|
|
3366
|
+
// Only server1 should handle it (because of targetId)
|
|
3367
|
+
expect(handler1).toHaveBeenCalled();
|
|
3368
|
+
expect(handler2).not.toHaveBeenCalled();
|
|
3369
|
+
|
|
3370
|
+
server1.destroy();
|
|
3371
|
+
server2.destroy();
|
|
3372
|
+
cleanupIframe(iframe);
|
|
3373
|
+
});
|
|
3374
|
+
|
|
3375
|
+
it('should handle ack timeout in registerPendingAck', async () => {
|
|
3376
|
+
const origin = 'https://example.com';
|
|
3377
|
+
const iframe = createTestIframe(origin);
|
|
3378
|
+
const mockContentWindow = {
|
|
3379
|
+
postMessage: jest.fn()
|
|
3380
|
+
};
|
|
3381
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3382
|
+
value: mockContentWindow,
|
|
3383
|
+
writable: true
|
|
3384
|
+
});
|
|
3385
|
+
|
|
3386
|
+
const server = requestIframeServer({ ackTimeout: 50 });
|
|
3387
|
+
|
|
3388
|
+
server.on('test', (req, res) => {
|
|
3389
|
+
// Send response with requireAck, but client never sends 'received'
|
|
3390
|
+
res.send({ result: 'success' }, { requireAck: true });
|
|
3391
|
+
});
|
|
3392
|
+
|
|
3393
|
+
window.dispatchEvent(
|
|
3394
|
+
new MessageEvent('message', {
|
|
3395
|
+
data: {
|
|
3396
|
+
__requestIframe__: 1,
|
|
3397
|
+
timestamp: Date.now(),
|
|
3398
|
+
type: 'request',
|
|
3399
|
+
requestId: 'req123',
|
|
3400
|
+
path: 'test',
|
|
3401
|
+
role: MessageRole.CLIENT,
|
|
3402
|
+
targetId: server.id
|
|
3403
|
+
},
|
|
3404
|
+
origin,
|
|
3405
|
+
source: mockContentWindow as any
|
|
3406
|
+
})
|
|
3407
|
+
);
|
|
3408
|
+
|
|
3409
|
+
// Wait for ack timeout
|
|
3410
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
3411
|
+
|
|
3412
|
+
// Server should have sent response
|
|
3413
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
|
|
3414
|
+
expect.objectContaining({
|
|
3415
|
+
type: 'response',
|
|
3416
|
+
requestId: 'req123'
|
|
3417
|
+
}),
|
|
3418
|
+
origin
|
|
3419
|
+
);
|
|
3420
|
+
|
|
3421
|
+
server.destroy();
|
|
3422
|
+
cleanupIframe(iframe);
|
|
3423
|
+
});
|
|
3424
|
+
|
|
3425
|
+
it('should handle middleware that sends response early', async () => {
|
|
3426
|
+
const origin = 'https://example.com';
|
|
3427
|
+
const iframe = createTestIframe(origin);
|
|
3428
|
+
const mockContentWindow = {
|
|
3429
|
+
postMessage: jest.fn()
|
|
3430
|
+
};
|
|
3431
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3432
|
+
value: mockContentWindow,
|
|
3433
|
+
writable: true
|
|
3434
|
+
});
|
|
3435
|
+
|
|
3436
|
+
const server = requestIframeServer();
|
|
3437
|
+
|
|
3438
|
+
const middleware = jest.fn((req, res, next) => {
|
|
3439
|
+
res.send({ middleware: true });
|
|
3440
|
+
// Don't call next() - response already sent
|
|
3441
|
+
});
|
|
3442
|
+
|
|
3443
|
+
const handler = jest.fn((req, res) => {
|
|
3444
|
+
res.send({ handler: true });
|
|
3445
|
+
});
|
|
3446
|
+
|
|
3447
|
+
server.use(middleware);
|
|
3448
|
+
server.on('test', handler);
|
|
3449
|
+
|
|
3450
|
+
window.dispatchEvent(
|
|
3451
|
+
new MessageEvent('message', {
|
|
3452
|
+
data: {
|
|
3453
|
+
__requestIframe__: 1,
|
|
3454
|
+
timestamp: Date.now(),
|
|
3455
|
+
type: 'request',
|
|
3456
|
+
requestId: 'req123',
|
|
3457
|
+
path: 'test',
|
|
3458
|
+
role: MessageRole.CLIENT,
|
|
3459
|
+
targetId: server.id
|
|
3460
|
+
},
|
|
3461
|
+
origin,
|
|
3462
|
+
source: mockContentWindow as any
|
|
3463
|
+
})
|
|
3464
|
+
);
|
|
3465
|
+
|
|
3466
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
3467
|
+
|
|
3468
|
+
// Middleware should be called
|
|
3469
|
+
expect(middleware).toHaveBeenCalled();
|
|
3470
|
+
// Handler should NOT be called because response was already sent
|
|
3471
|
+
expect(handler).not.toHaveBeenCalled();
|
|
3472
|
+
|
|
3473
|
+
// Response should be from middleware
|
|
3474
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
|
|
3475
|
+
expect.objectContaining({
|
|
3476
|
+
type: 'response',
|
|
3477
|
+
requestId: 'req123',
|
|
3478
|
+
data: { middleware: true }
|
|
3479
|
+
}),
|
|
3480
|
+
origin
|
|
3481
|
+
);
|
|
3482
|
+
|
|
3483
|
+
server.destroy();
|
|
3484
|
+
cleanupIframe(iframe);
|
|
3485
|
+
});
|
|
3486
|
+
|
|
3487
|
+
it('should handle map return cleanup function', () => {
|
|
3488
|
+
const server = requestIframeServer();
|
|
3489
|
+
|
|
3490
|
+
const handler1 = jest.fn((req, res) => res.send({}));
|
|
3491
|
+
const handler2 = jest.fn((req, res) => res.send({}));
|
|
3492
|
+
|
|
3493
|
+
const cleanup = server.map({
|
|
3494
|
+
'path1': handler1,
|
|
3495
|
+
'path2': handler2
|
|
3496
|
+
});
|
|
3497
|
+
|
|
3498
|
+
// Cleanup should unregister all handlers
|
|
3499
|
+
cleanup();
|
|
3500
|
+
|
|
3501
|
+
// Verify handlers are unregistered
|
|
3502
|
+
expect(server).toBeDefined();
|
|
3503
|
+
|
|
3504
|
+
server.destroy();
|
|
3505
|
+
});
|
|
3506
|
+
});
|
|
3507
|
+
|
|
3508
|
+
describe('Cache utilities', () => {
|
|
3509
|
+
it('should test server cache functions', () => {
|
|
3510
|
+
const { getCachedServer, cacheServer, removeCachedServer, clearServerCache } = require('../utils/cache');
|
|
3511
|
+
const { requestIframeServer } = require('../api/server');
|
|
3512
|
+
|
|
3513
|
+
// Test getCachedServer with no id
|
|
3514
|
+
expect(getCachedServer('key1')).toBeNull();
|
|
3515
|
+
expect(getCachedServer(undefined, undefined)).toBeNull();
|
|
3516
|
+
|
|
3517
|
+
// Test cacheServer with no id
|
|
3518
|
+
const server1 = requestIframeServer({ id: 'server1', secretKey: 'key1' });
|
|
3519
|
+
cacheServer(server1, 'key1', 'server1');
|
|
3520
|
+
|
|
3521
|
+
// Test getCachedServer with id
|
|
3522
|
+
const cached = getCachedServer('key1', 'server1');
|
|
3523
|
+
expect(cached).toBe(server1);
|
|
3524
|
+
|
|
3525
|
+
// Test removeCachedServer with no id
|
|
3526
|
+
removeCachedServer('key1'); // Should not throw
|
|
3527
|
+
removeCachedServer(undefined, undefined); // Should not throw
|
|
3528
|
+
|
|
3529
|
+
// Test removeCachedServer with id
|
|
3530
|
+
removeCachedServer('key1', 'server1');
|
|
3531
|
+
expect(getCachedServer('key1', 'server1')).toBeNull();
|
|
3532
|
+
|
|
3533
|
+
// Test clearServerCache
|
|
3534
|
+
const server2 = requestIframeServer({ id: 'server2', secretKey: 'key2' });
|
|
3535
|
+
cacheServer(server2, 'key2', 'server2');
|
|
3536
|
+
clearServerCache();
|
|
3537
|
+
expect(getCachedServer('key2', 'server2')).toBeNull();
|
|
3538
|
+
|
|
3539
|
+
server1.destroy();
|
|
3540
|
+
server2.destroy();
|
|
3541
|
+
});
|
|
3542
|
+
|
|
3543
|
+
it('should test clearMessageChannelCache', () => {
|
|
3544
|
+
const { clearMessageChannelCache, getOrCreateMessageChannel } = require('../utils/cache');
|
|
3545
|
+
|
|
3546
|
+
// Create a channel
|
|
3547
|
+
const channel1 = getOrCreateMessageChannel('test-key');
|
|
3548
|
+
expect(channel1).toBeDefined();
|
|
3549
|
+
|
|
3550
|
+
// Clear cache
|
|
3551
|
+
clearMessageChannelCache();
|
|
3552
|
+
|
|
3553
|
+
// Create another channel - should be new instance
|
|
3554
|
+
const channel2 = getOrCreateMessageChannel('test-key');
|
|
3555
|
+
expect(channel2).toBeDefined();
|
|
3556
|
+
|
|
3557
|
+
channel1.release();
|
|
3558
|
+
channel2.release();
|
|
3559
|
+
});
|
|
3560
|
+
});
|
|
3561
|
+
|
|
3562
|
+
describe('Additional edge cases', () => {
|
|
3563
|
+
it('should handle headers in request options', async () => {
|
|
3564
|
+
const origin = 'https://example.com';
|
|
3565
|
+
const iframe = createTestIframe(origin);
|
|
3566
|
+
const mockContentWindow = {
|
|
3567
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
3568
|
+
if (msg.type === 'request') {
|
|
3569
|
+
window.dispatchEvent(
|
|
3570
|
+
new MessageEvent('message', {
|
|
3571
|
+
data: {
|
|
3572
|
+
__requestIframe__: 1,
|
|
3573
|
+
type: 'ack',
|
|
3574
|
+
requestId: msg.requestId,
|
|
3575
|
+
path: msg.path,
|
|
3576
|
+
role: MessageRole.SERVER
|
|
3577
|
+
},
|
|
3578
|
+
origin
|
|
3579
|
+
})
|
|
3580
|
+
);
|
|
3581
|
+
setTimeout(() => {
|
|
3582
|
+
window.dispatchEvent(
|
|
3583
|
+
new MessageEvent('message', {
|
|
3584
|
+
data: {
|
|
3585
|
+
__requestIframe__: 1,
|
|
3586
|
+
type: 'response',
|
|
3587
|
+
requestId: msg.requestId,
|
|
3588
|
+
data: { result: 'success' },
|
|
3589
|
+
status: 200,
|
|
3590
|
+
statusText: 'OK',
|
|
3591
|
+
role: MessageRole.SERVER
|
|
3592
|
+
},
|
|
3593
|
+
origin
|
|
3594
|
+
})
|
|
3595
|
+
);
|
|
3596
|
+
}, 10);
|
|
3597
|
+
}
|
|
3598
|
+
})
|
|
3599
|
+
};
|
|
3600
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3601
|
+
value: mockContentWindow,
|
|
3602
|
+
writable: true
|
|
3603
|
+
});
|
|
3604
|
+
|
|
3605
|
+
const client = requestIframeClient(iframe, {
|
|
3606
|
+
headers: {
|
|
3607
|
+
'X-Initial': 'initial-value'
|
|
3608
|
+
}
|
|
3609
|
+
});
|
|
3610
|
+
|
|
3611
|
+
// Send request with additional headers
|
|
3612
|
+
await client.send('test', {}, {
|
|
3613
|
+
headers: {
|
|
3614
|
+
'X-Request': 'request-value',
|
|
3615
|
+
'X-Dynamic': (config: RequestConfig) => `dynamic-${config.path}`
|
|
3616
|
+
}
|
|
3617
|
+
});
|
|
3618
|
+
|
|
3619
|
+
const requestCall = mockContentWindow.postMessage.mock.calls.find(
|
|
3620
|
+
(call: any[]) => call[0]?.type === 'request'
|
|
3621
|
+
);
|
|
3622
|
+
expect(requestCall).toBeDefined();
|
|
3623
|
+
if (requestCall && requestCall[0]) {
|
|
3624
|
+
expect(requestCall[0].headers?.['X-Initial']).toBe('initial-value');
|
|
3625
|
+
expect(requestCall[0].headers?.['X-Request']).toBe('request-value');
|
|
3626
|
+
expect(requestCall[0].headers?.['X-Dynamic']).toBe('dynamic-test');
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3629
|
+
cleanupIframe(iframe);
|
|
3630
|
+
});
|
|
3631
|
+
|
|
3632
|
+
it('should handle isConnect with error response', async () => {
|
|
3633
|
+
const origin = 'https://example.com';
|
|
3634
|
+
const iframe = createTestIframe(origin);
|
|
3635
|
+
const mockContentWindow = {
|
|
3636
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
3637
|
+
if (msg.type === 'ping') {
|
|
3638
|
+
// Send error instead of pong
|
|
3639
|
+
setTimeout(() => {
|
|
3640
|
+
window.dispatchEvent(
|
|
3641
|
+
new MessageEvent('message', {
|
|
3642
|
+
data: {
|
|
3643
|
+
__requestIframe__: 1,
|
|
3644
|
+
type: 'error',
|
|
3645
|
+
requestId: msg.requestId,
|
|
3646
|
+
error: { message: 'Connection error' },
|
|
3647
|
+
role: MessageRole.SERVER
|
|
3648
|
+
},
|
|
3649
|
+
origin
|
|
3650
|
+
})
|
|
3651
|
+
);
|
|
3652
|
+
}, 10);
|
|
3653
|
+
}
|
|
3654
|
+
})
|
|
3655
|
+
};
|
|
3656
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3657
|
+
value: mockContentWindow,
|
|
3658
|
+
writable: true
|
|
3659
|
+
});
|
|
3660
|
+
|
|
3661
|
+
const client = requestIframeClient(iframe, { ackTimeout: 1000 });
|
|
3662
|
+
|
|
3663
|
+
const connected = await client.isConnect();
|
|
3664
|
+
expect(connected).toBe(false);
|
|
3665
|
+
|
|
3666
|
+
cleanupIframe(iframe);
|
|
3667
|
+
});
|
|
3668
|
+
|
|
3669
|
+
it('should handle response interceptor without rejected callback', async () => {
|
|
3670
|
+
const origin = 'https://example.com';
|
|
3671
|
+
const iframe = createTestIframe(origin);
|
|
3672
|
+
const mockContentWindow = {
|
|
3673
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
3674
|
+
if (msg.type === 'request') {
|
|
3675
|
+
window.dispatchEvent(
|
|
3676
|
+
new MessageEvent('message', {
|
|
3677
|
+
data: {
|
|
3678
|
+
__requestIframe__: 1,
|
|
3679
|
+
type: 'ack',
|
|
3680
|
+
requestId: msg.requestId,
|
|
3681
|
+
path: msg.path,
|
|
3682
|
+
role: MessageRole.SERVER
|
|
3683
|
+
},
|
|
3684
|
+
origin
|
|
3685
|
+
})
|
|
3686
|
+
);
|
|
3687
|
+
setTimeout(() => {
|
|
3688
|
+
window.dispatchEvent(
|
|
3689
|
+
new MessageEvent('message', {
|
|
3690
|
+
data: {
|
|
3691
|
+
__requestIframe__: 1,
|
|
3692
|
+
type: 'error',
|
|
3693
|
+
requestId: msg.requestId,
|
|
3694
|
+
error: { message: 'Test error', code: 'TEST_ERROR' },
|
|
3695
|
+
status: 500,
|
|
3696
|
+
statusText: 'Internal Server Error',
|
|
3697
|
+
role: MessageRole.SERVER
|
|
3698
|
+
},
|
|
3699
|
+
origin
|
|
3700
|
+
})
|
|
3701
|
+
);
|
|
3702
|
+
}, 10);
|
|
3703
|
+
}
|
|
3704
|
+
})
|
|
3705
|
+
};
|
|
3706
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3707
|
+
value: mockContentWindow,
|
|
3708
|
+
writable: true
|
|
3709
|
+
});
|
|
3710
|
+
|
|
3711
|
+
const client = requestIframeClient(iframe);
|
|
3712
|
+
|
|
3713
|
+
// Add response interceptor without rejected callback
|
|
3714
|
+
client.interceptors.response.use(
|
|
3715
|
+
(response) => response
|
|
3716
|
+
// No rejected callback - should test the Promise.reject path
|
|
3717
|
+
);
|
|
3718
|
+
|
|
3719
|
+
try {
|
|
3720
|
+
await client.send('test', {});
|
|
3721
|
+
fail('Should have thrown error');
|
|
3722
|
+
} catch (error: any) {
|
|
3723
|
+
expect(error.message).toBe('Test error');
|
|
3724
|
+
}
|
|
3725
|
+
|
|
3726
|
+
cleanupIframe(iframe);
|
|
3727
|
+
});
|
|
3728
|
+
|
|
3729
|
+
it('should handle request timeout', async () => {
|
|
3730
|
+
const origin = 'https://example.com';
|
|
3731
|
+
const iframe = createTestIframe(origin);
|
|
3732
|
+
const mockContentWindow = {
|
|
3733
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
3734
|
+
if (msg.type === 'request') {
|
|
3735
|
+
// Send ACK but never send response
|
|
3736
|
+
window.dispatchEvent(
|
|
3737
|
+
new MessageEvent('message', {
|
|
3738
|
+
data: {
|
|
3739
|
+
__requestIframe__: 1,
|
|
3740
|
+
type: 'ack',
|
|
3741
|
+
requestId: msg.requestId,
|
|
3742
|
+
path: msg.path,
|
|
3743
|
+
role: MessageRole.SERVER
|
|
3744
|
+
},
|
|
3745
|
+
origin
|
|
3746
|
+
})
|
|
3747
|
+
);
|
|
3748
|
+
// Don't send response - should timeout
|
|
3749
|
+
}
|
|
3750
|
+
})
|
|
3751
|
+
};
|
|
3752
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3753
|
+
value: mockContentWindow,
|
|
3754
|
+
writable: true
|
|
3755
|
+
});
|
|
3756
|
+
|
|
3757
|
+
const client = requestIframeClient(iframe, { timeout: 50 });
|
|
3758
|
+
|
|
3759
|
+
try {
|
|
3760
|
+
await client.send('test', {});
|
|
3761
|
+
fail('Should have timed out');
|
|
3762
|
+
} catch (error: any) {
|
|
3763
|
+
expect(error.message).toContain('timeout');
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
cleanupIframe(iframe);
|
|
3767
|
+
});
|
|
3768
|
+
|
|
3769
|
+
it('should handle async timeout', async () => {
|
|
3770
|
+
const origin = 'https://example.com';
|
|
3771
|
+
const iframe = createTestIframe(origin);
|
|
3772
|
+
const mockContentWindow = {
|
|
3773
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
3774
|
+
if (msg.type === 'request') {
|
|
3775
|
+
window.dispatchEvent(
|
|
3776
|
+
new MessageEvent('message', {
|
|
3777
|
+
data: {
|
|
3778
|
+
__requestIframe__: 1,
|
|
3779
|
+
type: 'ack',
|
|
3780
|
+
requestId: msg.requestId,
|
|
3781
|
+
path: msg.path,
|
|
3782
|
+
role: MessageRole.SERVER
|
|
3783
|
+
},
|
|
3784
|
+
origin
|
|
3785
|
+
})
|
|
3786
|
+
);
|
|
3787
|
+
setTimeout(() => {
|
|
3788
|
+
// Send ASYNC but never send response
|
|
3789
|
+
window.dispatchEvent(
|
|
3790
|
+
new MessageEvent('message', {
|
|
3791
|
+
data: {
|
|
3792
|
+
__requestIframe__: 1,
|
|
3793
|
+
type: 'async',
|
|
3794
|
+
requestId: msg.requestId,
|
|
3795
|
+
path: msg.path,
|
|
3796
|
+
role: MessageRole.SERVER
|
|
3797
|
+
},
|
|
3798
|
+
origin
|
|
3799
|
+
})
|
|
3800
|
+
);
|
|
3801
|
+
}, 10);
|
|
3802
|
+
// Don't send response - should timeout
|
|
3803
|
+
}
|
|
3804
|
+
})
|
|
3805
|
+
};
|
|
3806
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3807
|
+
value: mockContentWindow,
|
|
3808
|
+
writable: true
|
|
3809
|
+
});
|
|
3810
|
+
|
|
3811
|
+
const client = requestIframeClient(iframe, { asyncTimeout: 50 });
|
|
3812
|
+
|
|
3813
|
+
try {
|
|
3814
|
+
await client.send('test', {});
|
|
3815
|
+
fail('Should have timed out');
|
|
3816
|
+
} catch (error: any) {
|
|
3817
|
+
expect(error.message).toContain('timeout');
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
cleanupIframe(iframe);
|
|
3821
|
+
});
|
|
3822
|
+
|
|
3823
|
+
it('should not override existing targetServerId when receiving ACK', async () => {
|
|
3824
|
+
const origin = 'https://example.com';
|
|
3825
|
+
const iframe = createTestIframe(origin);
|
|
3826
|
+
const existingServerId = 'existing-server';
|
|
3827
|
+
const newServerId = 'new-server';
|
|
3828
|
+
|
|
3829
|
+
const mockContentWindow = {
|
|
3830
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
3831
|
+
if (msg.type === 'request') {
|
|
3832
|
+
window.dispatchEvent(
|
|
3833
|
+
new MessageEvent('message', {
|
|
3834
|
+
data: {
|
|
3835
|
+
__requestIframe__: 1,
|
|
3836
|
+
type: 'ack',
|
|
3837
|
+
requestId: msg.requestId,
|
|
3838
|
+
path: msg.path,
|
|
3839
|
+
role: MessageRole.SERVER,
|
|
3840
|
+
creatorId: newServerId
|
|
3841
|
+
},
|
|
3842
|
+
origin
|
|
3843
|
+
})
|
|
3844
|
+
);
|
|
3845
|
+
setTimeout(() => {
|
|
3846
|
+
window.dispatchEvent(
|
|
3847
|
+
new MessageEvent('message', {
|
|
3848
|
+
data: {
|
|
3849
|
+
__requestIframe__: 1,
|
|
3850
|
+
type: 'response',
|
|
3851
|
+
requestId: msg.requestId,
|
|
3852
|
+
data: { result: 'success' },
|
|
3853
|
+
status: 200,
|
|
3854
|
+
statusText: 'OK',
|
|
3855
|
+
role: MessageRole.SERVER,
|
|
3856
|
+
creatorId: newServerId
|
|
3857
|
+
},
|
|
3858
|
+
origin
|
|
3859
|
+
})
|
|
3860
|
+
);
|
|
3861
|
+
}, 10);
|
|
3862
|
+
}
|
|
3863
|
+
})
|
|
3864
|
+
};
|
|
3865
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3866
|
+
value: mockContentWindow,
|
|
3867
|
+
writable: true
|
|
3868
|
+
});
|
|
3869
|
+
|
|
3870
|
+
const client = requestIframeClient(iframe);
|
|
3871
|
+
|
|
3872
|
+
// Set existing targetServerId
|
|
3873
|
+
(client as any)._targetServerId = existingServerId;
|
|
3874
|
+
|
|
3875
|
+
await client.send('test', {});
|
|
3876
|
+
|
|
3877
|
+
// targetServerId should not be overridden
|
|
3878
|
+
expect((client as any)._targetServerId).toBe(existingServerId);
|
|
3879
|
+
|
|
3880
|
+
cleanupIframe(iframe);
|
|
3881
|
+
});
|
|
3882
|
+
|
|
3883
|
+
it('should handle response with requireAck', async () => {
|
|
3884
|
+
const origin = 'https://example.com';
|
|
3885
|
+
const iframe = createTestIframe(origin);
|
|
3886
|
+
const mockContentWindow = {
|
|
3887
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
3888
|
+
if (msg.type === 'request') {
|
|
3889
|
+
window.dispatchEvent(
|
|
3890
|
+
new MessageEvent('message', {
|
|
3891
|
+
data: {
|
|
3892
|
+
__requestIframe__: 1,
|
|
3893
|
+
type: 'ack',
|
|
3894
|
+
requestId: msg.requestId,
|
|
3895
|
+
path: msg.path,
|
|
3896
|
+
role: MessageRole.SERVER
|
|
3897
|
+
},
|
|
3898
|
+
origin
|
|
3899
|
+
})
|
|
3900
|
+
);
|
|
3901
|
+
setTimeout(() => {
|
|
3902
|
+
window.dispatchEvent(
|
|
3903
|
+
new MessageEvent('message', {
|
|
3904
|
+
data: {
|
|
3905
|
+
__requestIframe__: 1,
|
|
3906
|
+
type: 'response',
|
|
3907
|
+
requestId: msg.requestId,
|
|
3908
|
+
data: { result: 'success' },
|
|
3909
|
+
status: 200,
|
|
3910
|
+
statusText: 'OK',
|
|
3911
|
+
role: MessageRole.SERVER,
|
|
3912
|
+
requireAck: true
|
|
3913
|
+
},
|
|
3914
|
+
origin
|
|
3915
|
+
})
|
|
3916
|
+
);
|
|
3917
|
+
}, 10);
|
|
3918
|
+
} else if (msg.type === 'received') {
|
|
3919
|
+
// Acknowledge receipt
|
|
3920
|
+
}
|
|
3921
|
+
})
|
|
3922
|
+
};
|
|
3923
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3924
|
+
value: mockContentWindow,
|
|
3925
|
+
writable: true
|
|
3926
|
+
});
|
|
3927
|
+
|
|
3928
|
+
const client = requestIframeClient(iframe);
|
|
3929
|
+
const response = await client.send('test', {});
|
|
3930
|
+
|
|
3931
|
+
expect(response.data).toEqual({ result: 'success' });
|
|
3932
|
+
|
|
3933
|
+
// Verify RECEIVED message was sent
|
|
3934
|
+
const receivedCall = mockContentWindow.postMessage.mock.calls.find(
|
|
3935
|
+
(call: any[]) => call[0]?.type === 'received'
|
|
3936
|
+
);
|
|
3937
|
+
expect(receivedCall).toBeDefined();
|
|
3938
|
+
|
|
3939
|
+
cleanupIframe(iframe);
|
|
3940
|
+
});
|
|
3941
|
+
|
|
3942
|
+
it('should handle handler returning a value', async () => {
|
|
3943
|
+
const origin = 'https://example.com';
|
|
3944
|
+
const iframe = createTestIframe(origin);
|
|
3945
|
+
const mockContentWindow = {
|
|
3946
|
+
postMessage: jest.fn()
|
|
3947
|
+
};
|
|
3948
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3949
|
+
value: mockContentWindow,
|
|
3950
|
+
writable: true
|
|
3951
|
+
});
|
|
3952
|
+
|
|
3953
|
+
const server = requestIframeServer();
|
|
3954
|
+
|
|
3955
|
+
// Handler returns a value (not undefined)
|
|
3956
|
+
server.on('test', (req, res) => {
|
|
3957
|
+
return { result: 'from-return' };
|
|
3958
|
+
});
|
|
3959
|
+
|
|
3960
|
+
window.dispatchEvent(
|
|
3961
|
+
new MessageEvent('message', {
|
|
3962
|
+
data: {
|
|
3963
|
+
__requestIframe__: 1,
|
|
3964
|
+
timestamp: Date.now(),
|
|
3965
|
+
type: 'request',
|
|
3966
|
+
requestId: 'req123',
|
|
3967
|
+
path: 'test',
|
|
3968
|
+
role: MessageRole.CLIENT,
|
|
3969
|
+
targetId: server.id
|
|
3970
|
+
},
|
|
3971
|
+
origin,
|
|
3972
|
+
source: mockContentWindow as any
|
|
3973
|
+
})
|
|
3974
|
+
);
|
|
3975
|
+
|
|
3976
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
3977
|
+
|
|
3978
|
+
// Should send response with returned value
|
|
3979
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
|
|
3980
|
+
expect.objectContaining({
|
|
3981
|
+
type: 'response',
|
|
3982
|
+
requestId: 'req123',
|
|
3983
|
+
data: { result: 'from-return' }
|
|
3984
|
+
}),
|
|
3985
|
+
origin
|
|
3986
|
+
);
|
|
3987
|
+
|
|
3988
|
+
server.destroy();
|
|
3989
|
+
cleanupIframe(iframe);
|
|
3990
|
+
});
|
|
3991
|
+
|
|
3992
|
+
it('should handle ack timeout in registerPendingAck reject callback', async () => {
|
|
3993
|
+
const origin = 'https://example.com';
|
|
3994
|
+
const iframe = createTestIframe(origin);
|
|
3995
|
+
const mockContentWindow = {
|
|
3996
|
+
postMessage: jest.fn()
|
|
3997
|
+
};
|
|
3998
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
3999
|
+
value: mockContentWindow,
|
|
4000
|
+
writable: true
|
|
4001
|
+
});
|
|
4002
|
+
|
|
4003
|
+
const server = requestIframeServer({ ackTimeout: 50 });
|
|
4004
|
+
|
|
4005
|
+
server.on('test', (req, res) => {
|
|
4006
|
+
// Send response with requireAck, but client never sends 'received'
|
|
4007
|
+
res.send({ result: 'success' }, { requireAck: true });
|
|
4008
|
+
});
|
|
4009
|
+
|
|
4010
|
+
window.dispatchEvent(
|
|
4011
|
+
new MessageEvent('message', {
|
|
4012
|
+
data: {
|
|
4013
|
+
__requestIframe__: 1,
|
|
4014
|
+
timestamp: Date.now(),
|
|
4015
|
+
type: 'request',
|
|
4016
|
+
requestId: 'req123',
|
|
4017
|
+
path: 'test',
|
|
4018
|
+
role: MessageRole.CLIENT,
|
|
4019
|
+
targetId: server.id
|
|
4020
|
+
},
|
|
4021
|
+
origin,
|
|
4022
|
+
source: mockContentWindow as any
|
|
4023
|
+
})
|
|
4024
|
+
);
|
|
4025
|
+
|
|
4026
|
+
// Wait for ack timeout (reject callback should be called)
|
|
4027
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
4028
|
+
|
|
4029
|
+
// Server should have sent response
|
|
4030
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
|
|
4031
|
+
expect.objectContaining({
|
|
4032
|
+
type: 'response',
|
|
4033
|
+
requestId: 'req123'
|
|
4034
|
+
}),
|
|
4035
|
+
origin
|
|
4036
|
+
);
|
|
4037
|
+
|
|
4038
|
+
server.destroy();
|
|
4039
|
+
cleanupIframe(iframe);
|
|
4040
|
+
});
|
|
4041
|
+
|
|
4042
|
+
it('should skip middleware when response already sent', async () => {
|
|
4043
|
+
const origin = 'https://example.com';
|
|
4044
|
+
const iframe = createTestIframe(origin);
|
|
4045
|
+
const mockContentWindow = {
|
|
4046
|
+
postMessage: jest.fn()
|
|
4047
|
+
};
|
|
4048
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
4049
|
+
value: mockContentWindow,
|
|
4050
|
+
writable: true
|
|
4051
|
+
});
|
|
4052
|
+
|
|
4053
|
+
const server = requestIframeServer();
|
|
4054
|
+
|
|
4055
|
+
const middleware1 = jest.fn((req, res, next) => {
|
|
4056
|
+
res.send({ middleware1: true });
|
|
4057
|
+
// Response sent, don't call next
|
|
4058
|
+
});
|
|
4059
|
+
|
|
4060
|
+
const middleware2 = jest.fn((req, res, next) => {
|
|
4061
|
+
next();
|
|
4062
|
+
});
|
|
4063
|
+
|
|
4064
|
+
const handler = jest.fn((req, res) => {
|
|
4065
|
+
res.send({ handler: true });
|
|
4066
|
+
});
|
|
4067
|
+
|
|
4068
|
+
server.use(middleware1);
|
|
4069
|
+
server.use(middleware2);
|
|
4070
|
+
server.on('test', handler);
|
|
4071
|
+
|
|
4072
|
+
window.dispatchEvent(
|
|
4073
|
+
new MessageEvent('message', {
|
|
4074
|
+
data: {
|
|
4075
|
+
__requestIframe__: 1,
|
|
4076
|
+
timestamp: Date.now(),
|
|
4077
|
+
type: 'request',
|
|
4078
|
+
requestId: 'req123',
|
|
4079
|
+
path: 'test',
|
|
4080
|
+
role: MessageRole.CLIENT,
|
|
4081
|
+
targetId: server.id
|
|
4082
|
+
},
|
|
4083
|
+
origin,
|
|
4084
|
+
source: mockContentWindow as any
|
|
4085
|
+
})
|
|
4086
|
+
);
|
|
4087
|
+
|
|
4088
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
4089
|
+
|
|
4090
|
+
// Middleware1 should be called
|
|
4091
|
+
expect(middleware1).toHaveBeenCalled();
|
|
4092
|
+
// Middleware2 should NOT be called because response was already sent in middleware1
|
|
4093
|
+
expect(middleware2).not.toHaveBeenCalled();
|
|
4094
|
+
// Handler should NOT be called because response was already sent
|
|
4095
|
+
expect(handler).not.toHaveBeenCalled();
|
|
4096
|
+
|
|
4097
|
+
server.destroy();
|
|
4098
|
+
cleanupIframe(iframe);
|
|
4099
|
+
});
|
|
4100
|
+
|
|
4101
|
+
it('should handle isConnect reject callback', async () => {
|
|
4102
|
+
const origin = 'https://example.com';
|
|
4103
|
+
const iframe = createTestIframe(origin);
|
|
4104
|
+
const mockContentWindow = {
|
|
4105
|
+
postMessage: jest.fn()
|
|
4106
|
+
};
|
|
4107
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
4108
|
+
value: mockContentWindow,
|
|
4109
|
+
writable: true
|
|
4110
|
+
});
|
|
4111
|
+
|
|
4112
|
+
const client = requestIframeClient(iframe, { ackTimeout: 50 });
|
|
4113
|
+
|
|
4114
|
+
// Simulate error in pending request registration
|
|
4115
|
+
// This will trigger the reject callback
|
|
4116
|
+
const connected = await client.isConnect();
|
|
4117
|
+
expect(connected).toBe(false);
|
|
4118
|
+
|
|
4119
|
+
cleanupIframe(iframe);
|
|
4120
|
+
});
|
|
4121
|
+
|
|
4122
|
+
it('should handle stream messages via dispatchStreamMessage', async () => {
|
|
4123
|
+
const origin = 'https://example.com';
|
|
4124
|
+
const iframe = createTestIframe(origin);
|
|
4125
|
+
const mockContentWindow = {
|
|
4126
|
+
postMessage: jest.fn()
|
|
4127
|
+
};
|
|
4128
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
4129
|
+
value: mockContentWindow,
|
|
4130
|
+
writable: true
|
|
4131
|
+
});
|
|
4132
|
+
|
|
4133
|
+
const client = requestIframeClient(iframe);
|
|
4134
|
+
const streamId = 'test-stream';
|
|
4135
|
+
const handler = jest.fn();
|
|
4136
|
+
|
|
4137
|
+
// Register stream handler
|
|
4138
|
+
(client as any).registerStreamHandler(streamId, handler);
|
|
4139
|
+
|
|
4140
|
+
// Dispatch stream_data message
|
|
4141
|
+
window.dispatchEvent(
|
|
4142
|
+
new MessageEvent('message', {
|
|
4143
|
+
data: {
|
|
4144
|
+
__requestIframe__: 1,
|
|
4145
|
+
type: 'stream_data',
|
|
4146
|
+
requestId: 'req123',
|
|
4147
|
+
body: {
|
|
4148
|
+
streamId,
|
|
4149
|
+
data: 'test',
|
|
4150
|
+
type: 'data'
|
|
4151
|
+
},
|
|
4152
|
+
role: MessageRole.SERVER
|
|
4153
|
+
},
|
|
4154
|
+
origin
|
|
4155
|
+
})
|
|
4156
|
+
);
|
|
4157
|
+
|
|
4158
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
4159
|
+
|
|
4160
|
+
expect(handler).toHaveBeenCalled();
|
|
4161
|
+
|
|
4162
|
+
// Dispatch stream_end message
|
|
4163
|
+
window.dispatchEvent(
|
|
4164
|
+
new MessageEvent('message', {
|
|
4165
|
+
data: {
|
|
4166
|
+
__requestIframe__: 1,
|
|
4167
|
+
type: 'stream_end',
|
|
4168
|
+
requestId: 'req123',
|
|
4169
|
+
body: {
|
|
4170
|
+
streamId
|
|
4171
|
+
},
|
|
4172
|
+
role: MessageRole.SERVER
|
|
4173
|
+
},
|
|
4174
|
+
origin
|
|
4175
|
+
})
|
|
4176
|
+
);
|
|
4177
|
+
|
|
4178
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
4179
|
+
|
|
4180
|
+
cleanupIframe(iframe);
|
|
4181
|
+
});
|
|
4182
|
+
|
|
4183
|
+
it('should handle error with requireAck', async () => {
|
|
4184
|
+
const origin = 'https://example.com';
|
|
4185
|
+
const iframe = createTestIframe(origin);
|
|
4186
|
+
const mockContentWindow = {
|
|
4187
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
4188
|
+
if (msg.type === 'request') {
|
|
4189
|
+
window.dispatchEvent(
|
|
4190
|
+
new MessageEvent('message', {
|
|
4191
|
+
data: {
|
|
4192
|
+
__requestIframe__: 1,
|
|
4193
|
+
type: 'ack',
|
|
4194
|
+
requestId: msg.requestId,
|
|
4195
|
+
path: msg.path,
|
|
4196
|
+
role: MessageRole.SERVER
|
|
4197
|
+
},
|
|
4198
|
+
origin
|
|
4199
|
+
})
|
|
4200
|
+
);
|
|
4201
|
+
setTimeout(() => {
|
|
4202
|
+
window.dispatchEvent(
|
|
4203
|
+
new MessageEvent('message', {
|
|
4204
|
+
data: {
|
|
4205
|
+
__requestIframe__: 1,
|
|
4206
|
+
type: 'error',
|
|
4207
|
+
requestId: msg.requestId,
|
|
4208
|
+
error: { message: 'Test error', code: 'TEST_ERROR' },
|
|
4209
|
+
status: 500,
|
|
4210
|
+
statusText: 'Internal Server Error',
|
|
4211
|
+
role: MessageRole.SERVER,
|
|
4212
|
+
requireAck: true
|
|
4213
|
+
},
|
|
4214
|
+
origin
|
|
4215
|
+
})
|
|
4216
|
+
);
|
|
4217
|
+
}, 10);
|
|
4218
|
+
}
|
|
4219
|
+
})
|
|
4220
|
+
};
|
|
4221
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
4222
|
+
value: mockContentWindow,
|
|
4223
|
+
writable: true
|
|
4224
|
+
});
|
|
4225
|
+
|
|
4226
|
+
const client = requestIframeClient(iframe);
|
|
4227
|
+
|
|
4228
|
+
try {
|
|
4229
|
+
await client.send('test', {});
|
|
4230
|
+
fail('Should have thrown error');
|
|
4231
|
+
} catch (error: any) {
|
|
4232
|
+
expect(error.message).toBe('Test error');
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4235
|
+
// Verify RECEIVED message was sent
|
|
4236
|
+
const receivedCall = mockContentWindow.postMessage.mock.calls.find(
|
|
4237
|
+
(call: any[]) => call[0]?.type === 'received'
|
|
4238
|
+
);
|
|
4239
|
+
expect(receivedCall).toBeDefined();
|
|
4240
|
+
|
|
4241
|
+
cleanupIframe(iframe);
|
|
4242
|
+
});
|
|
4243
|
+
|
|
4244
|
+
it('should handle error in pending request registration', async () => {
|
|
4245
|
+
const origin = 'https://example.com';
|
|
4246
|
+
const iframe = createTestIframe(origin);
|
|
4247
|
+
const mockContentWindow = {
|
|
4248
|
+
postMessage: jest.fn()
|
|
4249
|
+
};
|
|
4250
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
4251
|
+
value: mockContentWindow,
|
|
4252
|
+
writable: true
|
|
4253
|
+
});
|
|
4254
|
+
|
|
4255
|
+
const client = requestIframeClient(iframe);
|
|
4256
|
+
|
|
4257
|
+
// Simulate error during request registration
|
|
4258
|
+
// This will trigger the error callback in _registerPendingRequest
|
|
4259
|
+
try {
|
|
4260
|
+
// Force an error by making the server unavailable
|
|
4261
|
+
await client.send('test', {}, { timeout: 50 });
|
|
4262
|
+
fail('Should have thrown error');
|
|
4263
|
+
} catch (error: any) {
|
|
4264
|
+
expect(error).toBeDefined();
|
|
4265
|
+
}
|
|
4266
|
+
|
|
4267
|
+
cleanupIframe(iframe);
|
|
4268
|
+
});
|
|
4269
|
+
|
|
4270
|
+
it('should handle message already handled by another server', async () => {
|
|
4271
|
+
const origin = 'https://example.com';
|
|
4272
|
+
const iframe = createTestIframe(origin);
|
|
4273
|
+
const mockContentWindow = {
|
|
4274
|
+
postMessage: jest.fn()
|
|
4275
|
+
};
|
|
4276
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
4277
|
+
value: mockContentWindow,
|
|
4278
|
+
writable: true
|
|
4279
|
+
});
|
|
4280
|
+
|
|
4281
|
+
const server1 = requestIframeServer();
|
|
4282
|
+
const server2 = requestIframeServer();
|
|
4283
|
+
|
|
4284
|
+
const handler1 = jest.fn((req, res) => res.send({ server: 1 }));
|
|
4285
|
+
const handler2 = jest.fn((req, res) => res.send({ server: 2 }));
|
|
4286
|
+
|
|
4287
|
+
server1.on('test', handler1);
|
|
4288
|
+
server2.on('test', handler2);
|
|
4289
|
+
|
|
4290
|
+
// Create message context that indicates it was already handled
|
|
4291
|
+
// This simulates the case where context.handledBy is set
|
|
4292
|
+
const messageData = {
|
|
4293
|
+
__requestIframe__: 1,
|
|
4294
|
+
timestamp: Date.now(),
|
|
4295
|
+
type: 'request' as const,
|
|
4296
|
+
requestId: 'req123',
|
|
4297
|
+
path: 'test',
|
|
4298
|
+
role: MessageRole.CLIENT,
|
|
4299
|
+
targetId: server1.id
|
|
4300
|
+
};
|
|
4301
|
+
|
|
4302
|
+
// First server processes it
|
|
4303
|
+
window.dispatchEvent(
|
|
4304
|
+
new MessageEvent('message', {
|
|
4305
|
+
data: messageData,
|
|
4306
|
+
origin,
|
|
4307
|
+
source: mockContentWindow as any
|
|
4308
|
+
})
|
|
4309
|
+
);
|
|
4310
|
+
|
|
4311
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
4312
|
+
|
|
4313
|
+
// Only server1 should handle it (because of targetId)
|
|
4314
|
+
expect(handler1).toHaveBeenCalled();
|
|
4315
|
+
expect(handler2).not.toHaveBeenCalled();
|
|
4316
|
+
|
|
4317
|
+
server1.destroy();
|
|
4318
|
+
server2.destroy();
|
|
4319
|
+
cleanupIframe(iframe);
|
|
4320
|
+
});
|
|
4321
|
+
|
|
4322
|
+
it('should handle ack timeout reject callback', async () => {
|
|
4323
|
+
const origin = 'https://example.com';
|
|
4324
|
+
const iframe = createTestIframe(origin);
|
|
4325
|
+
const mockContentWindow = {
|
|
4326
|
+
postMessage: jest.fn()
|
|
4327
|
+
};
|
|
4328
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
4329
|
+
value: mockContentWindow,
|
|
4330
|
+
writable: true
|
|
4331
|
+
});
|
|
4332
|
+
|
|
4333
|
+
const server = requestIframeServer({ ackTimeout: 50 });
|
|
4334
|
+
|
|
4335
|
+
server.on('test', (req, res) => {
|
|
4336
|
+
// Send response with requireAck, but client never sends 'received'
|
|
4337
|
+
// This will trigger ack timeout and the reject callback
|
|
4338
|
+
res.send({ result: 'success' }, { requireAck: true });
|
|
4339
|
+
});
|
|
4340
|
+
|
|
4341
|
+
window.dispatchEvent(
|
|
4342
|
+
new MessageEvent('message', {
|
|
4343
|
+
data: {
|
|
4344
|
+
__requestIframe__: 1,
|
|
4345
|
+
timestamp: Date.now(),
|
|
4346
|
+
type: 'request',
|
|
4347
|
+
requestId: 'req123',
|
|
4348
|
+
path: 'test',
|
|
4349
|
+
role: MessageRole.CLIENT,
|
|
4350
|
+
targetId: server.id
|
|
4351
|
+
},
|
|
4352
|
+
origin,
|
|
4353
|
+
source: mockContentWindow as any
|
|
4354
|
+
})
|
|
4355
|
+
);
|
|
4356
|
+
|
|
4357
|
+
// Wait for ack timeout (reject callback should be called)
|
|
4358
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
4359
|
+
|
|
4360
|
+
// Server should have sent response
|
|
4361
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
|
|
4362
|
+
expect.objectContaining({
|
|
4363
|
+
type: 'response',
|
|
4364
|
+
requestId: 'req123'
|
|
4365
|
+
}),
|
|
4366
|
+
origin
|
|
4367
|
+
);
|
|
4368
|
+
|
|
4369
|
+
server.destroy();
|
|
4370
|
+
cleanupIframe(iframe);
|
|
4371
|
+
});
|
|
4372
|
+
|
|
4373
|
+
it('should skip next middleware when response already sent', async () => {
|
|
4374
|
+
const origin = 'https://example.com';
|
|
4375
|
+
const iframe = createTestIframe(origin);
|
|
4376
|
+
const mockContentWindow = {
|
|
4377
|
+
postMessage: jest.fn()
|
|
4378
|
+
};
|
|
4379
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
4380
|
+
value: mockContentWindow,
|
|
4381
|
+
writable: true
|
|
4382
|
+
});
|
|
4383
|
+
|
|
4384
|
+
const server = requestIframeServer();
|
|
4385
|
+
|
|
4386
|
+
const middleware1 = jest.fn((req, res, next) => {
|
|
4387
|
+
res.send({ middleware1: true });
|
|
4388
|
+
// Response sent, but still call next to test the res._sent check
|
|
4389
|
+
next();
|
|
4390
|
+
});
|
|
4391
|
+
|
|
4392
|
+
const middleware2 = jest.fn((req, res, next) => {
|
|
4393
|
+
// This should not execute because res._sent is true
|
|
4394
|
+
next();
|
|
4395
|
+
});
|
|
4396
|
+
|
|
4397
|
+
const handler = jest.fn((req, res) => {
|
|
4398
|
+
res.send({ handler: true });
|
|
4399
|
+
});
|
|
4400
|
+
|
|
4401
|
+
server.use(middleware1);
|
|
4402
|
+
server.use(middleware2);
|
|
4403
|
+
server.on('test', handler);
|
|
4404
|
+
|
|
4405
|
+
window.dispatchEvent(
|
|
4406
|
+
new MessageEvent('message', {
|
|
4407
|
+
data: {
|
|
4408
|
+
__requestIframe__: 1,
|
|
4409
|
+
timestamp: Date.now(),
|
|
4410
|
+
type: 'request',
|
|
4411
|
+
requestId: 'req123',
|
|
4412
|
+
path: 'test',
|
|
4413
|
+
role: MessageRole.CLIENT,
|
|
4414
|
+
targetId: server.id
|
|
4415
|
+
},
|
|
4416
|
+
origin,
|
|
4417
|
+
source: mockContentWindow as any
|
|
4418
|
+
})
|
|
4419
|
+
);
|
|
4420
|
+
|
|
4421
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
4422
|
+
|
|
4423
|
+
// Middleware1 should be called
|
|
4424
|
+
expect(middleware1).toHaveBeenCalled();
|
|
4425
|
+
// Middleware2's next() should check res._sent and return early, so handler should not be called
|
|
4426
|
+
// Note: middleware2 itself may or may not be called depending on implementation
|
|
4427
|
+
// Handler should NOT be called because response was already sent
|
|
4428
|
+
expect(handler).not.toHaveBeenCalled();
|
|
4429
|
+
|
|
4430
|
+
server.destroy();
|
|
4431
|
+
cleanupIframe(iframe);
|
|
4432
|
+
});
|
|
4433
|
+
});
|
|
2406
4434
|
});
|