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.
Files changed (72) hide show
  1. package/README.CN.md +269 -12
  2. package/README.md +266 -11
  3. package/library/__tests__/channel.test.ts +420 -0
  4. package/library/__tests__/debug.test.ts +588 -0
  5. package/library/__tests__/dispatcher.test.ts +481 -0
  6. package/library/__tests__/requestIframe.test.ts +2127 -99
  7. package/library/__tests__/server.test.ts +738 -0
  8. package/library/api/client.d.ts.map +1 -1
  9. package/library/api/client.js +11 -6
  10. package/library/api/server.d.ts +4 -3
  11. package/library/api/server.d.ts.map +1 -1
  12. package/library/api/server.js +25 -7
  13. package/library/constants/index.d.ts +14 -4
  14. package/library/constants/index.d.ts.map +1 -1
  15. package/library/constants/index.js +15 -7
  16. package/library/constants/messages.d.ts +35 -0
  17. package/library/constants/messages.d.ts.map +1 -1
  18. package/library/constants/messages.js +36 -1
  19. package/library/core/client-server.d.ts +101 -0
  20. package/library/core/client-server.d.ts.map +1 -0
  21. package/library/core/client-server.js +266 -0
  22. package/library/core/client.d.ts +22 -6
  23. package/library/core/client.d.ts.map +1 -1
  24. package/library/core/client.js +159 -24
  25. package/library/core/request.d.ts.map +1 -1
  26. package/library/core/response.d.ts +5 -1
  27. package/library/core/response.d.ts.map +1 -1
  28. package/library/core/response.js +85 -70
  29. package/library/core/server-client.d.ts +3 -1
  30. package/library/core/server-client.d.ts.map +1 -1
  31. package/library/core/server-client.js +19 -9
  32. package/library/core/server.d.ts +9 -1
  33. package/library/core/server.d.ts.map +1 -1
  34. package/library/core/server.js +96 -52
  35. package/library/index.d.ts +1 -1
  36. package/library/index.js +2 -2
  37. package/library/interceptors/index.d.ts.map +1 -1
  38. package/library/message/channel.d.ts +3 -1
  39. package/library/message/channel.d.ts.map +1 -1
  40. package/library/message/dispatcher.d.ts +7 -2
  41. package/library/message/dispatcher.d.ts.map +1 -1
  42. package/library/message/dispatcher.js +47 -2
  43. package/library/message/index.d.ts.map +1 -1
  44. package/library/stream/file-stream.d.ts +5 -0
  45. package/library/stream/file-stream.d.ts.map +1 -1
  46. package/library/stream/file-stream.js +41 -12
  47. package/library/stream/index.d.ts.map +1 -1
  48. package/library/stream/readable-stream.d.ts.map +1 -1
  49. package/library/stream/readable-stream.js +32 -30
  50. package/library/stream/types.d.ts +18 -0
  51. package/library/stream/types.d.ts.map +1 -1
  52. package/library/stream/writable-stream.d.ts +1 -0
  53. package/library/stream/writable-stream.d.ts.map +1 -1
  54. package/library/stream/writable-stream.js +7 -2
  55. package/library/types/index.d.ts +80 -28
  56. package/library/types/index.d.ts.map +1 -1
  57. package/library/utils/cache.d.ts +24 -0
  58. package/library/utils/cache.d.ts.map +1 -1
  59. package/library/utils/cache.js +76 -0
  60. package/library/utils/cookie.d.ts.map +1 -1
  61. package/library/utils/debug.d.ts.map +1 -1
  62. package/library/utils/debug.js +382 -20
  63. package/library/utils/index.d.ts +5 -0
  64. package/library/utils/index.d.ts.map +1 -1
  65. package/library/utils/index.js +14 -1
  66. package/library/utils/path-match.d.ts.map +1 -1
  67. package/library/utils/protocol.d.ts.map +1 -1
  68. package/package.json +3 -1
  69. package/react/library/__tests__/index.test.tsx +238 -267
  70. package/react/library/index.d.ts +4 -3
  71. package/react/library/index.d.ts.map +1 -1
  72. package/react/library/index.js +167 -158
@@ -0,0 +1,738 @@
1
+ import { requestIframeServer, clearRequestIframeServerCache } from '../api/server';
2
+ import { MessageType, MessageRole, HttpStatus, ErrorCode, Messages } from '../constants';
3
+ import { PostMessageData } from '../types';
4
+
5
+ /**
6
+ * Create test iframe
7
+ */
8
+ function createTestIframe(origin: string): HTMLIFrameElement {
9
+ const iframe = document.createElement('iframe');
10
+ iframe.src = `${origin}/test.html`;
11
+ document.body.appendChild(iframe);
12
+ return iframe;
13
+ }
14
+
15
+ /**
16
+ * Cleanup test iframe
17
+ */
18
+ function cleanupIframe(iframe: HTMLIFrameElement): void {
19
+ if (iframe.parentNode) {
20
+ iframe.parentNode.removeChild(iframe);
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Create a mock window object that can be used as MessageEvent source
26
+ * This ensures the source is recognized as a Window object by the message channel
27
+ */
28
+ function createMockWindow(): { postMessage: jest.Mock } & Window {
29
+ const mockPostMessage = jest.fn();
30
+ // Create a mock window that extends the real window prototype
31
+ const mockWindow = Object.create(window) as any;
32
+ mockWindow.postMessage = mockPostMessage;
33
+ return mockWindow;
34
+ }
35
+
36
+ describe('RequestIframeServer', () => {
37
+ beforeEach(() => {
38
+ clearRequestIframeServerCache();
39
+ // Clear all iframes
40
+ document.querySelectorAll('iframe').forEach((iframe) => {
41
+ if (iframe.parentNode) {
42
+ iframe.parentNode.removeChild(iframe);
43
+ }
44
+ });
45
+ });
46
+
47
+ afterEach(() => {
48
+ // Clear all caches
49
+ clearRequestIframeServerCache();
50
+ // Clear all iframes
51
+ document.querySelectorAll('iframe').forEach((iframe) => {
52
+ if (iframe.parentNode) {
53
+ iframe.parentNode.removeChild(iframe);
54
+ }
55
+ });
56
+ });
57
+
58
+ describe('constructor', () => {
59
+ it('should create server with default options', () => {
60
+ const server = requestIframeServer();
61
+ expect(server).toBeDefined();
62
+ expect(server.isOpen).toBe(true);
63
+ server.destroy();
64
+ });
65
+
66
+ it('should create server with secretKey', () => {
67
+ const server = requestIframeServer({ secretKey: 'test-key' });
68
+ expect(server.secretKey).toBe('test-key');
69
+ server.destroy();
70
+ });
71
+
72
+ it('should create server with custom ackTimeout', () => {
73
+ const server = requestIframeServer({ ackTimeout: 1000 });
74
+ expect(server).toBeDefined();
75
+ server.destroy();
76
+ });
77
+
78
+ it('should create server with autoOpen false', () => {
79
+ const server = requestIframeServer({ autoOpen: false });
80
+ expect(server.isOpen).toBe(false);
81
+ server.open();
82
+ expect(server.isOpen).toBe(true);
83
+ server.destroy();
84
+ });
85
+
86
+ it('should create server with custom versionValidator', () => {
87
+ const versionValidator = jest.fn(() => true);
88
+ const server = requestIframeServer({ versionValidator } as any);
89
+ expect(server).toBeDefined();
90
+ server.destroy();
91
+ });
92
+ });
93
+
94
+ describe('open and close', () => {
95
+ it('should open server', () => {
96
+ const server = requestIframeServer({ autoOpen: false });
97
+ expect(server.isOpen).toBe(false);
98
+ server.open();
99
+ expect(server.isOpen).toBe(true);
100
+ server.destroy();
101
+ });
102
+
103
+ it('should not open if already open', () => {
104
+ const server = requestIframeServer();
105
+ const originalOpen = server.open;
106
+ server.open = jest.fn();
107
+ server.open();
108
+ expect(server.open).toHaveBeenCalledTimes(1);
109
+ server.destroy();
110
+ });
111
+
112
+ it('should close server', () => {
113
+ const server = requestIframeServer();
114
+ expect(server.isOpen).toBe(true);
115
+ server.close();
116
+ expect(server.isOpen).toBe(false);
117
+ server.destroy();
118
+ });
119
+
120
+ it('should not close if already closed', () => {
121
+ const server = requestIframeServer();
122
+ server.close();
123
+ const originalClose = server.close;
124
+ server.close = jest.fn();
125
+ server.close();
126
+ expect(server.close).toHaveBeenCalledTimes(1);
127
+ server.destroy();
128
+ });
129
+ });
130
+
131
+ describe('on and off', () => {
132
+ it('should register handler', async () => {
133
+ const server = requestIframeServer();
134
+ const handler = jest.fn();
135
+
136
+ server.on('test', handler);
137
+
138
+ const origin = 'https://example.com';
139
+ const iframe = createTestIframe(origin);
140
+ const mockWindow = createMockWindow();
141
+
142
+ window.dispatchEvent(
143
+ new MessageEvent('message', {
144
+ data: {
145
+ __requestIframe__: 1,
146
+ timestamp: Date.now(),
147
+ type: 'request',
148
+ requestId: 'req123',
149
+ path: 'test',
150
+ body: { param: 'value' },
151
+ role: MessageRole.CLIENT
152
+ },
153
+ origin,
154
+ source: mockWindow
155
+ })
156
+ );
157
+
158
+ await new Promise(resolve => setTimeout(resolve, 100));
159
+ expect(handler).toHaveBeenCalled();
160
+ server.destroy();
161
+ cleanupIframe(iframe);
162
+ });
163
+
164
+ it('should unregister handler', async () => {
165
+ const server = requestIframeServer();
166
+ const handler = jest.fn();
167
+
168
+ server.on('test', handler);
169
+ server.off('test');
170
+
171
+ const origin = 'https://example.com';
172
+ const iframe = createTestIframe(origin);
173
+ const mockWindow = createMockWindow();
174
+
175
+ window.dispatchEvent(
176
+ new MessageEvent('message', {
177
+ data: {
178
+ __requestIframe__: 1,
179
+ timestamp: Date.now(),
180
+ type: 'request',
181
+ requestId: 'req123',
182
+ path: 'test',
183
+ role: MessageRole.CLIENT
184
+ },
185
+ origin,
186
+ source: mockWindow
187
+ })
188
+ );
189
+
190
+ await new Promise(resolve => setTimeout(resolve, 100));
191
+
192
+ expect(handler).not.toHaveBeenCalled();
193
+ server.destroy();
194
+ cleanupIframe(iframe);
195
+ });
196
+
197
+ it('should handle multiple handlers for same path', async () => {
198
+ const server = requestIframeServer();
199
+ const handler1 = jest.fn((req, res) => {
200
+ res.send({ result: 'handler1' });
201
+ });
202
+ const handler2 = jest.fn((req, res) => {
203
+ res.send({ result: 'handler2' });
204
+ });
205
+
206
+ server.on('test', handler1);
207
+ server.on('test', handler2);
208
+
209
+ // Only the last registered handler should be called
210
+ const origin = 'https://example.com';
211
+ const iframe = createTestIframe(origin);
212
+ const mockContentWindow = {
213
+ postMessage: jest.fn()
214
+ };
215
+ Object.defineProperty(iframe, 'contentWindow', {
216
+ value: mockContentWindow,
217
+ writable: true
218
+ });
219
+
220
+ const mockWindow = createMockWindow();
221
+
222
+ window.dispatchEvent(
223
+ new MessageEvent('message', {
224
+ data: {
225
+ __requestIframe__: 1,
226
+ timestamp: Date.now(),
227
+ type: 'request',
228
+ requestId: 'req123',
229
+ path: 'test',
230
+ role: MessageRole.CLIENT
231
+ },
232
+ origin,
233
+ source: mockWindow
234
+ })
235
+ );
236
+
237
+ await new Promise(resolve => setTimeout(resolve, 100));
238
+ expect(handler1).not.toHaveBeenCalled();
239
+ expect(handler2).toHaveBeenCalled();
240
+ server.destroy();
241
+ cleanupIframe(iframe);
242
+ });
243
+ });
244
+
245
+ describe('map', () => {
246
+ it('should register multiple handlers at once', () => {
247
+ const server = requestIframeServer();
248
+ const handler1 = jest.fn((req, res) => res.send({ path: '1' }));
249
+ const handler2 = jest.fn((req, res) => res.send({ path: '2' }));
250
+ const handler3 = jest.fn((req, res) => res.send({ path: '3' }));
251
+
252
+ server.map({
253
+ path1: handler1,
254
+ path2: handler2,
255
+ path3: handler3
256
+ });
257
+
258
+ expect(server).toBeDefined();
259
+ server.destroy();
260
+ });
261
+ });
262
+
263
+ describe('use (middleware)', () => {
264
+ it('should register global middleware', async () => {
265
+ const server = requestIframeServer();
266
+ const middleware = jest.fn((req, res, next) => next());
267
+
268
+ server.use(middleware);
269
+
270
+ server.on('test', (req, res) => {
271
+ res.send({ result: 'success' });
272
+ });
273
+
274
+ const origin = 'https://example.com';
275
+ const iframe = createTestIframe(origin);
276
+ const mockWindow = createMockWindow();
277
+
278
+ window.dispatchEvent(
279
+ new MessageEvent('message', {
280
+ data: {
281
+ __requestIframe__: 1,
282
+ timestamp: Date.now(),
283
+ type: 'request',
284
+ requestId: 'req123',
285
+ path: 'test',
286
+ role: MessageRole.CLIENT
287
+ },
288
+ origin,
289
+ source: mockWindow
290
+ })
291
+ );
292
+
293
+ await new Promise(resolve => setTimeout(resolve, 100));
294
+ expect(middleware).toHaveBeenCalled();
295
+ server.destroy();
296
+ cleanupIframe(iframe);
297
+ });
298
+
299
+ it('should register path-specific middleware', async () => {
300
+ const server = requestIframeServer();
301
+ const middleware = jest.fn((req, res, next) => next());
302
+
303
+ server.use('/api/*', middleware);
304
+
305
+ server.on('/api/test', (req, res) => {
306
+ res.send({ result: 'success' });
307
+ });
308
+
309
+ const origin = 'https://example.com';
310
+ const iframe = createTestIframe(origin);
311
+ const mockWindow = createMockWindow();
312
+
313
+ window.dispatchEvent(
314
+ new MessageEvent('message', {
315
+ data: {
316
+ __requestIframe__: 1,
317
+ timestamp: Date.now(),
318
+ type: 'request',
319
+ requestId: 'req123',
320
+ path: '/api/test',
321
+ role: MessageRole.CLIENT
322
+ },
323
+ origin,
324
+ source: mockWindow
325
+ })
326
+ );
327
+
328
+ await new Promise(resolve => setTimeout(resolve, 100));
329
+ expect(middleware).toHaveBeenCalled();
330
+ server.destroy();
331
+ cleanupIframe(iframe);
332
+ });
333
+
334
+ it('should execute middleware in order', async () => {
335
+ const server = requestIframeServer();
336
+ const order: number[] = [];
337
+
338
+ server.use((req, res, next) => {
339
+ order.push(1);
340
+ next();
341
+ });
342
+
343
+ server.use((req, res, next) => {
344
+ order.push(2);
345
+ next();
346
+ });
347
+
348
+ server.on('test', (req, res) => {
349
+ order.push(3);
350
+ res.send({ result: 'success' });
351
+ });
352
+
353
+ const origin = 'https://example.com';
354
+ const iframe = createTestIframe(origin);
355
+ const mockWindow = createMockWindow();
356
+
357
+ window.dispatchEvent(
358
+ new MessageEvent('message', {
359
+ data: {
360
+ __requestIframe__: 1,
361
+ timestamp: Date.now(),
362
+ type: 'request',
363
+ requestId: 'req123',
364
+ path: 'test',
365
+ role: MessageRole.CLIENT
366
+ },
367
+ origin,
368
+ source: mockWindow
369
+ })
370
+ );
371
+
372
+ await new Promise(resolve => setTimeout(resolve, 100));
373
+ expect(order).toEqual([1, 2, 3]);
374
+ server.destroy();
375
+ cleanupIframe(iframe);
376
+ });
377
+ });
378
+
379
+ describe('error handling', () => {
380
+ it('should send METHOD_NOT_FOUND error when handler not found', async () => {
381
+ const server = requestIframeServer();
382
+ const origin = 'https://example.com';
383
+ const iframe = createTestIframe(origin);
384
+ const mockContentWindow = {
385
+ postMessage: jest.fn()
386
+ };
387
+ Object.defineProperty(iframe, 'contentWindow', {
388
+ value: mockContentWindow,
389
+ writable: true
390
+ });
391
+
392
+ window.dispatchEvent(
393
+ new MessageEvent('message', {
394
+ data: {
395
+ __requestIframe__: 1,
396
+ timestamp: Date.now(),
397
+ type: 'request',
398
+ requestId: 'req123',
399
+ path: 'nonexistent',
400
+ role: MessageRole.CLIENT
401
+ },
402
+ origin,
403
+ source: mockContentWindow as any
404
+ })
405
+ );
406
+
407
+ await new Promise(resolve => setTimeout(resolve, 50));
408
+
409
+ expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
410
+ expect.objectContaining({
411
+ type: 'error',
412
+ requestId: 'req123',
413
+ error: expect.objectContaining({
414
+ message: Messages.METHOD_NOT_FOUND,
415
+ code: ErrorCode.METHOD_NOT_FOUND
416
+ }),
417
+ status: HttpStatus.NOT_FOUND
418
+ }),
419
+ origin
420
+ );
421
+
422
+ server.destroy();
423
+ cleanupIframe(iframe);
424
+ });
425
+
426
+ it('should handle handler errors', async () => {
427
+ const server = requestIframeServer();
428
+ const origin = 'https://example.com';
429
+ const iframe = createTestIframe(origin);
430
+ const mockContentWindow = {
431
+ postMessage: jest.fn()
432
+ };
433
+ Object.defineProperty(iframe, 'contentWindow', {
434
+ value: mockContentWindow,
435
+ writable: true
436
+ });
437
+
438
+ server.on('test', () => {
439
+ throw new Error('Handler error');
440
+ });
441
+
442
+ window.dispatchEvent(
443
+ new MessageEvent('message', {
444
+ data: {
445
+ __requestIframe__: 1,
446
+ timestamp: Date.now(),
447
+ type: 'request',
448
+ requestId: 'req123',
449
+ path: 'test',
450
+ role: MessageRole.CLIENT,
451
+ targetId: server.id
452
+ },
453
+ origin,
454
+ source: mockContentWindow as any
455
+ })
456
+ );
457
+
458
+ await new Promise(resolve => setTimeout(resolve, 150));
459
+
460
+ expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
461
+ expect.objectContaining({
462
+ type: 'error',
463
+ requestId: 'req123',
464
+ status: HttpStatus.INTERNAL_SERVER_ERROR
465
+ }),
466
+ origin
467
+ );
468
+
469
+ server.destroy();
470
+ cleanupIframe(iframe);
471
+ });
472
+
473
+ it('should handle async handler errors', async () => {
474
+ const server = requestIframeServer();
475
+ const origin = 'https://example.com';
476
+ const iframe = createTestIframe(origin);
477
+ const mockContentWindow = {
478
+ postMessage: jest.fn()
479
+ };
480
+ Object.defineProperty(iframe, 'contentWindow', {
481
+ value: mockContentWindow,
482
+ writable: true
483
+ });
484
+
485
+ server.on('test', async () => {
486
+ throw new Error('Async handler error');
487
+ });
488
+
489
+ window.dispatchEvent(
490
+ new MessageEvent('message', {
491
+ data: {
492
+ __requestIframe__: 1,
493
+ timestamp: Date.now(),
494
+ type: 'request',
495
+ requestId: 'req123',
496
+ path: 'test',
497
+ role: MessageRole.CLIENT,
498
+ targetId: server.id
499
+ },
500
+ origin,
501
+ source: mockContentWindow as any
502
+ })
503
+ );
504
+
505
+ await new Promise(resolve => setTimeout(resolve, 200));
506
+
507
+ expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
508
+ expect.objectContaining({
509
+ type: 'error',
510
+ requestId: 'req123',
511
+ status: HttpStatus.INTERNAL_SERVER_ERROR
512
+ }),
513
+ origin
514
+ );
515
+
516
+ server.destroy();
517
+ cleanupIframe(iframe);
518
+ });
519
+ });
520
+
521
+ describe('async handlers', () => {
522
+ it('should send ASYNC notification for async handlers', async () => {
523
+ const server = requestIframeServer();
524
+ const origin = 'https://example.com';
525
+ const iframe = createTestIframe(origin);
526
+ const mockContentWindow = {
527
+ postMessage: jest.fn()
528
+ };
529
+ Object.defineProperty(iframe, 'contentWindow', {
530
+ value: mockContentWindow,
531
+ writable: true
532
+ });
533
+
534
+ server.on('test', async (req, res) => {
535
+ await new Promise(resolve => setTimeout(resolve, 10));
536
+ res.send({ result: 'success' });
537
+ });
538
+
539
+ window.dispatchEvent(
540
+ new MessageEvent('message', {
541
+ data: {
542
+ __requestIframe__: 1,
543
+ timestamp: Date.now(),
544
+ type: 'request',
545
+ requestId: 'req123',
546
+ path: 'test',
547
+ role: MessageRole.CLIENT,
548
+ targetId: server.id
549
+ },
550
+ origin,
551
+ source: mockContentWindow as any
552
+ })
553
+ );
554
+
555
+ await new Promise(resolve => setTimeout(resolve, 150));
556
+
557
+ expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
558
+ expect.objectContaining({
559
+ type: 'async',
560
+ requestId: 'req123'
561
+ }),
562
+ origin
563
+ );
564
+
565
+ server.destroy();
566
+ cleanupIframe(iframe);
567
+ });
568
+
569
+ it('should handle async handler without response', async () => {
570
+ const server = requestIframeServer();
571
+ const origin = 'https://example.com';
572
+ const iframe = createTestIframe(origin);
573
+ const mockContentWindow = {
574
+ postMessage: jest.fn()
575
+ };
576
+ Object.defineProperty(iframe, 'contentWindow', {
577
+ value: mockContentWindow,
578
+ writable: true
579
+ });
580
+
581
+ server.on('test', async () => {
582
+ // No response sent
583
+ });
584
+
585
+ window.dispatchEvent(
586
+ new MessageEvent('message', {
587
+ data: {
588
+ __requestIframe__: 1,
589
+ timestamp: Date.now(),
590
+ type: 'request',
591
+ requestId: 'req123',
592
+ path: 'test',
593
+ role: MessageRole.CLIENT,
594
+ targetId: server.id
595
+ },
596
+ origin,
597
+ source: mockContentWindow as any
598
+ })
599
+ );
600
+
601
+ await new Promise(resolve => setTimeout(resolve, 200));
602
+
603
+ expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
604
+ expect.objectContaining({
605
+ type: 'error',
606
+ requestId: 'req123'
607
+ }),
608
+ origin
609
+ );
610
+
611
+ server.destroy();
612
+ cleanupIframe(iframe);
613
+ });
614
+ });
615
+
616
+ describe('ping/pong', () => {
617
+ it('should handle ping messages', async () => {
618
+ const server = requestIframeServer();
619
+ const origin = 'https://example.com';
620
+ const iframe = createTestIframe(origin);
621
+ const mockContentWindow = {
622
+ postMessage: jest.fn()
623
+ };
624
+ Object.defineProperty(iframe, 'contentWindow', {
625
+ value: mockContentWindow,
626
+ writable: true
627
+ });
628
+
629
+ window.dispatchEvent(
630
+ new MessageEvent('message', {
631
+ data: {
632
+ __requestIframe__: 1,
633
+ timestamp: Date.now(),
634
+ type: 'ping',
635
+ requestId: 'req123',
636
+ role: MessageRole.CLIENT
637
+ },
638
+ origin,
639
+ source: mockContentWindow as any
640
+ })
641
+ );
642
+
643
+ await new Promise(resolve => setTimeout(resolve, 50));
644
+
645
+ expect(mockContentWindow.postMessage).toHaveBeenCalledWith(
646
+ expect.objectContaining({
647
+ type: 'pong',
648
+ requestId: 'req123'
649
+ }),
650
+ origin
651
+ );
652
+
653
+ server.destroy();
654
+ cleanupIframe(iframe);
655
+ });
656
+ });
657
+
658
+ describe('received acknowledgment', () => {
659
+ it('should handle received acknowledgment', async () => {
660
+ const server = requestIframeServer();
661
+ const origin = 'https://example.com';
662
+ const iframe = createTestIframe(origin);
663
+ const mockWindow = createMockWindow();
664
+
665
+ let ackResolve: (value: boolean) => void;
666
+ const ackPromise = new Promise<boolean>(resolve => {
667
+ ackResolve = resolve;
668
+ });
669
+
670
+ server.on('test', (req, res) => {
671
+ res.send({ result: 'success' }, { requireAck: true }).then(ackResolve);
672
+ });
673
+
674
+ window.dispatchEvent(
675
+ new MessageEvent('message', {
676
+ data: {
677
+ __requestIframe__: 1,
678
+ timestamp: Date.now(),
679
+ type: 'request',
680
+ requestId: 'req123',
681
+ path: 'test',
682
+ role: MessageRole.CLIENT,
683
+ targetId: server.id
684
+ },
685
+ origin,
686
+ source: mockWindow
687
+ })
688
+ );
689
+
690
+ await new Promise(resolve => setTimeout(resolve, 150));
691
+
692
+ // Send received acknowledgment
693
+ window.dispatchEvent(
694
+ new MessageEvent('message', {
695
+ data: {
696
+ __requestIframe__: 1,
697
+ timestamp: Date.now(),
698
+ type: 'received',
699
+ requestId: 'req123',
700
+ role: MessageRole.CLIENT
701
+ },
702
+ origin,
703
+ source: mockWindow
704
+ })
705
+ );
706
+
707
+ await new Promise(resolve => setTimeout(resolve, 50));
708
+
709
+ const received = await Promise.race([
710
+ ackPromise,
711
+ new Promise<boolean>(resolve => setTimeout(() => resolve(false), 2000))
712
+ ]);
713
+ expect(received).toBe(true);
714
+
715
+ server.destroy();
716
+ cleanupIframe(iframe);
717
+ }, 10000);
718
+ });
719
+
720
+ describe('destroy', () => {
721
+ it('should destroy server and clean up', () => {
722
+ const server = requestIframeServer();
723
+ server.on('test', (req, res) => res.send({}));
724
+
725
+ expect(server.isOpen).toBe(true);
726
+ server.destroy();
727
+ expect(server.isOpen).toBe(false);
728
+ });
729
+ });
730
+
731
+ describe('messageDispatcher access', () => {
732
+ it('should provide access to messageDispatcher', () => {
733
+ const server = requestIframeServer();
734
+ expect((server as any).messageDispatcher).toBeDefined();
735
+ server.destroy();
736
+ });
737
+ });
738
+ });