request-iframe 0.0.2 → 0.0.4

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 (84) hide show
  1. package/QUICKSTART.CN.md +35 -8
  2. package/QUICKSTART.md +35 -8
  3. package/README.CN.md +439 -36
  4. package/README.md +496 -30
  5. package/library/__tests__/channel.test.ts +420 -0
  6. package/library/__tests__/coverage-branches.test.ts +356 -0
  7. package/library/__tests__/debug.test.ts +588 -0
  8. package/library/__tests__/dispatcher.test.ts +481 -0
  9. package/library/__tests__/requestIframe.test.ts +3163 -185
  10. package/library/__tests__/server.test.ts +738 -0
  11. package/library/__tests__/stream.test.ts +46 -15
  12. package/library/api/client.d.ts.map +1 -1
  13. package/library/api/client.js +12 -6
  14. package/library/api/server.d.ts +4 -3
  15. package/library/api/server.d.ts.map +1 -1
  16. package/library/api/server.js +25 -7
  17. package/library/constants/index.d.ts +14 -4
  18. package/library/constants/index.d.ts.map +1 -1
  19. package/library/constants/index.js +15 -7
  20. package/library/constants/messages.d.ts +37 -0
  21. package/library/constants/messages.d.ts.map +1 -1
  22. package/library/constants/messages.js +38 -1
  23. package/library/core/client-server.d.ts +105 -0
  24. package/library/core/client-server.d.ts.map +1 -0
  25. package/library/core/client-server.js +289 -0
  26. package/library/core/client.d.ts +53 -10
  27. package/library/core/client.d.ts.map +1 -1
  28. package/library/core/client.js +529 -207
  29. package/library/core/request.d.ts +3 -1
  30. package/library/core/request.d.ts.map +1 -1
  31. package/library/core/request.js +2 -1
  32. package/library/core/response.d.ts +30 -4
  33. package/library/core/response.d.ts.map +1 -1
  34. package/library/core/response.js +176 -100
  35. package/library/core/server-client.d.ts +3 -1
  36. package/library/core/server-client.d.ts.map +1 -1
  37. package/library/core/server-client.js +19 -9
  38. package/library/core/server.d.ts +22 -1
  39. package/library/core/server.d.ts.map +1 -1
  40. package/library/core/server.js +304 -55
  41. package/library/index.d.ts +3 -2
  42. package/library/index.d.ts.map +1 -1
  43. package/library/index.js +34 -5
  44. package/library/interceptors/index.d.ts.map +1 -1
  45. package/library/message/channel.d.ts +3 -1
  46. package/library/message/channel.d.ts.map +1 -1
  47. package/library/message/dispatcher.d.ts +7 -2
  48. package/library/message/dispatcher.d.ts.map +1 -1
  49. package/library/message/dispatcher.js +48 -2
  50. package/library/message/index.d.ts.map +1 -1
  51. package/library/stream/file-stream.d.ts +5 -0
  52. package/library/stream/file-stream.d.ts.map +1 -1
  53. package/library/stream/file-stream.js +41 -12
  54. package/library/stream/index.d.ts +11 -1
  55. package/library/stream/index.d.ts.map +1 -1
  56. package/library/stream/index.js +21 -3
  57. package/library/stream/readable-stream.d.ts.map +1 -1
  58. package/library/stream/readable-stream.js +32 -30
  59. package/library/stream/types.d.ts +20 -2
  60. package/library/stream/types.d.ts.map +1 -1
  61. package/library/stream/writable-stream.d.ts +2 -1
  62. package/library/stream/writable-stream.d.ts.map +1 -1
  63. package/library/stream/writable-stream.js +13 -10
  64. package/library/types/index.d.ts +106 -32
  65. package/library/types/index.d.ts.map +1 -1
  66. package/library/utils/cache.d.ts +24 -0
  67. package/library/utils/cache.d.ts.map +1 -1
  68. package/library/utils/cache.js +76 -0
  69. package/library/utils/cookie.d.ts.map +1 -1
  70. package/library/utils/debug.d.ts.map +1 -1
  71. package/library/utils/debug.js +382 -20
  72. package/library/utils/index.d.ts +19 -0
  73. package/library/utils/index.d.ts.map +1 -1
  74. package/library/utils/index.js +113 -2
  75. package/library/utils/path-match.d.ts +16 -0
  76. package/library/utils/path-match.d.ts.map +1 -1
  77. package/library/utils/path-match.js +65 -0
  78. package/library/utils/protocol.d.ts.map +1 -1
  79. package/package.json +4 -1
  80. package/react/library/__tests__/index.test.tsx +274 -281
  81. package/react/library/index.d.ts +4 -3
  82. package/react/library/index.d.ts.map +1 -1
  83. package/react/library/index.js +225 -158
  84. package/react/package.json +7 -0
@@ -0,0 +1,588 @@
1
+ import { setupClientDebugInterceptors, setupServerDebugListeners } from '../utils/debug';
2
+ import { requestIframeClient, clearRequestIframeClientCache } from '../api/client';
3
+ import { requestIframeServer, clearRequestIframeServerCache } from '../api/server';
4
+ import { MessageType, MessageRole } from '../constants';
5
+
6
+ /**
7
+ * Create test iframe
8
+ */
9
+ function createTestIframe(origin: string): HTMLIFrameElement {
10
+ const iframe = document.createElement('iframe');
11
+ iframe.src = `${origin}/test.html`;
12
+ document.body.appendChild(iframe);
13
+ return iframe;
14
+ }
15
+
16
+ /**
17
+ * Cleanup test iframe
18
+ */
19
+ function cleanupIframe(iframe: HTMLIFrameElement): void {
20
+ if (iframe.parentNode) {
21
+ iframe.parentNode.removeChild(iframe);
22
+ }
23
+ }
24
+
25
+ describe('debug', () => {
26
+ beforeEach(() => {
27
+ clearRequestIframeClientCache();
28
+ clearRequestIframeServerCache();
29
+ jest.clearAllMocks();
30
+ console.info = jest.fn();
31
+ console.warn = jest.fn();
32
+ console.error = jest.fn();
33
+ // Clear all iframes
34
+ document.querySelectorAll('iframe').forEach((iframe) => {
35
+ if (iframe.parentNode) {
36
+ iframe.parentNode.removeChild(iframe);
37
+ }
38
+ });
39
+ });
40
+
41
+ afterEach(() => {
42
+ // Clear all caches
43
+ clearRequestIframeClientCache();
44
+ clearRequestIframeServerCache();
45
+ // Clear all iframes
46
+ document.querySelectorAll('iframe').forEach((iframe) => {
47
+ if (iframe.parentNode) {
48
+ iframe.parentNode.removeChild(iframe);
49
+ }
50
+ });
51
+ });
52
+
53
+ describe('setupClientDebugInterceptors', () => {
54
+ it('should log request start', async () => {
55
+ const origin = 'https://example.com';
56
+ const iframe = createTestIframe(origin);
57
+
58
+ const mockContentWindow = {
59
+ postMessage: jest.fn((msg: any) => {
60
+ if (msg.type === 'request') {
61
+ window.dispatchEvent(
62
+ new MessageEvent('message', {
63
+ data: {
64
+ __requestIframe__: 1,
65
+ type: 'ack',
66
+ requestId: msg.requestId,
67
+ path: msg.path,
68
+ role: MessageRole.SERVER
69
+ },
70
+ origin
71
+ })
72
+ );
73
+ setTimeout(() => {
74
+ window.dispatchEvent(
75
+ new MessageEvent('message', {
76
+ data: {
77
+ __requestIframe__: 1,
78
+ type: 'response',
79
+ requestId: msg.requestId,
80
+ data: { result: 'success' },
81
+ status: 200,
82
+ statusText: 'OK',
83
+ role: MessageRole.SERVER
84
+ },
85
+ origin
86
+ })
87
+ );
88
+ }, 10);
89
+ }
90
+ })
91
+ };
92
+ Object.defineProperty(iframe, 'contentWindow', {
93
+ value: mockContentWindow,
94
+ writable: true
95
+ });
96
+
97
+ const client = requestIframeClient(iframe);
98
+ setupClientDebugInterceptors(client);
99
+
100
+ await client.send('test', { param: 'value' }, { ackTimeout: 1000 });
101
+
102
+ expect(console.info).toHaveBeenCalledWith(
103
+ expect.stringContaining('[Client] Request Start'),
104
+ expect.objectContaining({
105
+ path: 'test',
106
+ body: { param: 'value' }
107
+ })
108
+ );
109
+
110
+ expect(console.info).toHaveBeenCalledWith(
111
+ expect.stringContaining('[Client] Request Success'),
112
+ expect.objectContaining({
113
+ requestId: expect.any(String),
114
+ status: 200
115
+ })
116
+ );
117
+
118
+ cleanupIframe(iframe);
119
+ });
120
+
121
+ it('should log request failure', async () => {
122
+ const origin = 'https://example.com';
123
+ const iframe = createTestIframe(origin);
124
+
125
+ const mockContentWindow = {
126
+ postMessage: jest.fn()
127
+ };
128
+ Object.defineProperty(iframe, 'contentWindow', {
129
+ value: mockContentWindow,
130
+ writable: true
131
+ });
132
+
133
+ const client = requestIframeClient(iframe);
134
+ setupClientDebugInterceptors(client);
135
+
136
+ try {
137
+ await client.send('test', undefined, { ackTimeout: 50, timeout: 100 });
138
+ } catch (error) {
139
+ // Expected to fail
140
+ }
141
+
142
+ // Wait a bit for error logging
143
+ await new Promise(resolve => setTimeout(resolve, 150));
144
+
145
+ expect(console.error).toHaveBeenCalledWith(
146
+ expect.stringContaining('[Client] Request Failed'),
147
+ expect.objectContaining({
148
+ code: expect.any(String)
149
+ })
150
+ );
151
+
152
+ cleanupIframe(iframe);
153
+ });
154
+
155
+ it('should log file response', async () => {
156
+ const origin = 'https://example.com';
157
+ const iframe = createTestIframe(origin);
158
+
159
+ const mockContentWindow = {
160
+ postMessage: jest.fn((msg: any) => {
161
+ if (msg.type === 'request') {
162
+ window.dispatchEvent(
163
+ new MessageEvent('message', {
164
+ data: {
165
+ __requestIframe__: 1,
166
+ type: 'ack',
167
+ requestId: msg.requestId,
168
+ path: msg.path,
169
+ role: MessageRole.SERVER
170
+ },
171
+ origin
172
+ })
173
+ );
174
+ setTimeout(() => {
175
+ const streamId = 'stream-test';
176
+ window.dispatchEvent(
177
+ new MessageEvent('message', {
178
+ data: {
179
+ __requestIframe__: 1,
180
+ timestamp: Date.now(),
181
+ type: 'stream_start',
182
+ requestId: msg.requestId,
183
+ status: 200,
184
+ statusText: 'OK',
185
+ headers: {
186
+ 'Content-Type': 'text/plain',
187
+ 'Content-Disposition': 'attachment; filename="test.txt"'
188
+ },
189
+ body: {
190
+ streamId,
191
+ type: 'file',
192
+ chunked: false,
193
+ autoResolve: true,
194
+ metadata: {
195
+ filename: 'test.txt',
196
+ mimeType: 'text/plain'
197
+ }
198
+ },
199
+ role: MessageRole.SERVER
200
+ },
201
+ origin
202
+ })
203
+ );
204
+ setTimeout(() => {
205
+ window.dispatchEvent(
206
+ new MessageEvent('message', {
207
+ data: {
208
+ __requestIframe__: 1,
209
+ timestamp: Date.now(),
210
+ type: 'stream_data',
211
+ requestId: msg.requestId,
212
+ body: {
213
+ streamId,
214
+ data: btoa('Hello World'),
215
+ done: true
216
+ },
217
+ role: MessageRole.SERVER
218
+ },
219
+ origin
220
+ })
221
+ );
222
+ setTimeout(() => {
223
+ window.dispatchEvent(
224
+ new MessageEvent('message', {
225
+ data: {
226
+ __requestIframe__: 1,
227
+ timestamp: Date.now(),
228
+ type: 'stream_end',
229
+ requestId: msg.requestId,
230
+ body: { streamId },
231
+ role: MessageRole.SERVER
232
+ },
233
+ origin
234
+ })
235
+ );
236
+ }, 10);
237
+ }, 10);
238
+ }, 10);
239
+ }
240
+ })
241
+ };
242
+ Object.defineProperty(iframe, 'contentWindow', {
243
+ value: mockContentWindow,
244
+ writable: true
245
+ });
246
+
247
+ const client = requestIframeClient(iframe);
248
+ setupClientDebugInterceptors(client);
249
+
250
+ const response = await client.send('getFile', undefined, {
251
+ ackTimeout: 1000,
252
+ timeout: 10000
253
+ }) as any;
254
+
255
+ expect(response.data).toBeInstanceOf(File);
256
+ const file = response.data as File;
257
+ expect(file.name).toBe('test.txt');
258
+
259
+ expect(console.info).toHaveBeenCalledWith(
260
+ expect.stringContaining('[Client] Request Success (File)'),
261
+ expect.objectContaining({
262
+ fileData: expect.objectContaining({
263
+ fileName: 'test.txt'
264
+ })
265
+ })
266
+ );
267
+
268
+ cleanupIframe(iframe);
269
+ }, 20000);
270
+
271
+ it('should log incoming messages', async () => {
272
+ const origin = 'https://example.com';
273
+ const iframe = createTestIframe(origin);
274
+
275
+ const mockContentWindow = {
276
+ postMessage: jest.fn((msg: any) => {
277
+ if (msg.type === 'request') {
278
+ setTimeout(() => {
279
+ window.dispatchEvent(
280
+ new MessageEvent('message', {
281
+ data: {
282
+ __requestIframe__: 1,
283
+ type: 'ack',
284
+ requestId: msg.requestId,
285
+ path: msg.path,
286
+ role: MessageRole.SERVER
287
+ },
288
+ origin
289
+ })
290
+ );
291
+ setTimeout(() => {
292
+ window.dispatchEvent(
293
+ new MessageEvent('message', {
294
+ data: {
295
+ __requestIframe__: 1,
296
+ type: 'response',
297
+ requestId: msg.requestId,
298
+ data: { result: 'success' },
299
+ status: 200,
300
+ statusText: 'OK',
301
+ role: MessageRole.SERVER
302
+ },
303
+ origin
304
+ })
305
+ );
306
+ }, 10);
307
+ }, 10);
308
+ }
309
+ })
310
+ };
311
+ Object.defineProperty(iframe, 'contentWindow', {
312
+ value: mockContentWindow,
313
+ writable: true
314
+ });
315
+
316
+ const client = requestIframeClient(iframe);
317
+ setupClientDebugInterceptors(client);
318
+
319
+ await client.send('test', undefined, { ackTimeout: 1000, timeout: 5000 });
320
+
321
+ expect(console.info).toHaveBeenCalledWith(
322
+ expect.stringContaining('[Client] Received ACK'),
323
+ expect.objectContaining({
324
+ requestId: expect.any(String)
325
+ })
326
+ );
327
+
328
+ cleanupIframe(iframe);
329
+ }, 10000);
330
+ });
331
+
332
+ describe('setupServerDebugListeners', () => {
333
+ it('should log received request', async () => {
334
+ const origin = 'https://example.com';
335
+ const iframe = createTestIframe(origin);
336
+
337
+ const mockContentWindow = {
338
+ postMessage: jest.fn()
339
+ };
340
+ Object.defineProperty(iframe, 'contentWindow', {
341
+ value: mockContentWindow,
342
+ writable: true
343
+ });
344
+
345
+ const server = requestIframeServer();
346
+ setupServerDebugListeners(server);
347
+
348
+ server.on('test', (req, res) => {
349
+ res.send({ result: 'success' });
350
+ });
351
+
352
+ window.dispatchEvent(
353
+ new MessageEvent('message', {
354
+ data: {
355
+ __requestIframe__: 1,
356
+ timestamp: Date.now(),
357
+ type: 'request',
358
+ requestId: 'req123',
359
+ path: 'test',
360
+ body: { param: 'value' },
361
+ role: MessageRole.CLIENT,
362
+ targetId: server.id
363
+ },
364
+ origin,
365
+ source: mockContentWindow as any
366
+ })
367
+ );
368
+
369
+ await new Promise(resolve => setTimeout(resolve, 50));
370
+
371
+ expect(console.info).toHaveBeenCalledWith(
372
+ expect.stringContaining('[Server] Received Request'),
373
+ expect.objectContaining({
374
+ path: 'test',
375
+ body: { param: 'value' }
376
+ })
377
+ );
378
+
379
+ expect(console.info).toHaveBeenCalledWith(
380
+ expect.stringContaining('[Server] Sending Response'),
381
+ expect.objectContaining({
382
+ status: 200
383
+ })
384
+ );
385
+
386
+ server.destroy();
387
+ cleanupIframe(iframe);
388
+ });
389
+
390
+ it('should log status code changes', async () => {
391
+ const origin = 'https://example.com';
392
+ const iframe = createTestIframe(origin);
393
+
394
+ const mockContentWindow = {
395
+ postMessage: jest.fn()
396
+ };
397
+ Object.defineProperty(iframe, 'contentWindow', {
398
+ value: mockContentWindow,
399
+ writable: true
400
+ });
401
+
402
+ const server = requestIframeServer();
403
+ setupServerDebugListeners(server);
404
+
405
+ server.on('test', (req, res) => {
406
+ res.status(404).send({ error: 'Not Found' });
407
+ });
408
+
409
+ window.dispatchEvent(
410
+ new MessageEvent('message', {
411
+ data: {
412
+ __requestIframe__: 1,
413
+ timestamp: Date.now(),
414
+ type: 'request',
415
+ requestId: 'req123',
416
+ path: 'test',
417
+ role: MessageRole.CLIENT,
418
+ targetId: server.id
419
+ },
420
+ origin,
421
+ source: mockContentWindow as any
422
+ })
423
+ );
424
+
425
+ await new Promise(resolve => setTimeout(resolve, 50));
426
+
427
+ expect(console.info).toHaveBeenCalledWith(
428
+ expect.stringContaining('[Server] Setting Status Code'),
429
+ expect.objectContaining({
430
+ statusCode: 404
431
+ })
432
+ );
433
+
434
+ server.destroy();
435
+ cleanupIframe(iframe);
436
+ });
437
+
438
+ it('should log header changes', async () => {
439
+ const origin = 'https://example.com';
440
+ const iframe = createTestIframe(origin);
441
+
442
+ const mockContentWindow = {
443
+ postMessage: jest.fn()
444
+ };
445
+ Object.defineProperty(iframe, 'contentWindow', {
446
+ value: mockContentWindow,
447
+ writable: true
448
+ });
449
+
450
+ const server = requestIframeServer();
451
+ setupServerDebugListeners(server);
452
+
453
+ server.on('test', (req, res) => {
454
+ res.setHeader('X-Custom', 'value');
455
+ res.send({ result: 'success' });
456
+ });
457
+
458
+ window.dispatchEvent(
459
+ new MessageEvent('message', {
460
+ data: {
461
+ __requestIframe__: 1,
462
+ timestamp: Date.now(),
463
+ type: 'request',
464
+ requestId: 'req123',
465
+ path: 'test',
466
+ role: MessageRole.CLIENT,
467
+ targetId: server.id
468
+ },
469
+ origin,
470
+ source: mockContentWindow as any
471
+ })
472
+ );
473
+
474
+ await new Promise(resolve => setTimeout(resolve, 50));
475
+
476
+ expect(console.info).toHaveBeenCalledWith(
477
+ expect.stringContaining('[Server] Setting Header'),
478
+ expect.objectContaining({
479
+ header: 'X-Custom',
480
+ value: 'value'
481
+ })
482
+ );
483
+
484
+ server.destroy();
485
+ cleanupIframe(iframe);
486
+ });
487
+
488
+ it('should log sendFile', async () => {
489
+ const origin = 'https://example.com';
490
+ const iframe = createTestIframe(origin);
491
+
492
+ const mockContentWindow = {
493
+ postMessage: jest.fn()
494
+ };
495
+ Object.defineProperty(iframe, 'contentWindow', {
496
+ value: mockContentWindow,
497
+ writable: true
498
+ });
499
+
500
+ const server = requestIframeServer();
501
+ setupServerDebugListeners(server);
502
+
503
+ server.on('test', async (req, res) => {
504
+ await res.sendFile('Hello World', {
505
+ fileName: 'test.txt',
506
+ mimeType: 'text/plain'
507
+ });
508
+ });
509
+
510
+ window.dispatchEvent(
511
+ new MessageEvent('message', {
512
+ data: {
513
+ __requestIframe__: 1,
514
+ timestamp: Date.now(),
515
+ type: 'request',
516
+ requestId: 'req123',
517
+ path: 'test',
518
+ role: MessageRole.CLIENT,
519
+ targetId: server.id
520
+ },
521
+ origin,
522
+ source: mockContentWindow as any
523
+ })
524
+ );
525
+
526
+ await new Promise(resolve => setTimeout(resolve, 200));
527
+
528
+ expect(console.info).toHaveBeenCalledWith(
529
+ expect.stringContaining('[Server] Sending File'),
530
+ expect.objectContaining({
531
+ fileName: 'test.txt',
532
+ mimeType: 'text/plain'
533
+ })
534
+ );
535
+
536
+ server.destroy();
537
+ cleanupIframe(iframe);
538
+ }, 10000);
539
+
540
+ it('should log json response', async () => {
541
+ const origin = 'https://example.com';
542
+ const iframe = createTestIframe(origin);
543
+
544
+ const mockContentWindow = {
545
+ postMessage: jest.fn()
546
+ };
547
+ Object.defineProperty(iframe, 'contentWindow', {
548
+ value: mockContentWindow,
549
+ writable: true
550
+ });
551
+
552
+ const server = requestIframeServer();
553
+ setupServerDebugListeners(server);
554
+
555
+ server.on('test', (req, res) => {
556
+ res.json({ result: 'success' });
557
+ });
558
+
559
+ window.dispatchEvent(
560
+ new MessageEvent('message', {
561
+ data: {
562
+ __requestIframe__: 1,
563
+ timestamp: Date.now(),
564
+ type: 'request',
565
+ requestId: 'req123',
566
+ path: 'test',
567
+ role: MessageRole.CLIENT,
568
+ targetId: server.id
569
+ },
570
+ origin,
571
+ source: mockContentWindow as any
572
+ })
573
+ );
574
+
575
+ await new Promise(resolve => setTimeout(resolve, 50));
576
+
577
+ expect(console.info).toHaveBeenCalledWith(
578
+ expect.stringContaining('[Server] Sending JSON Response'),
579
+ expect.objectContaining({
580
+ status: 200
581
+ })
582
+ );
583
+
584
+ server.destroy();
585
+ cleanupIframe(iframe);
586
+ });
587
+ });
588
+ });