request-iframe 0.0.1

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 (96) hide show
  1. package/QUICKSTART.CN.md +269 -0
  2. package/QUICKSTART.md +269 -0
  3. package/README.CN.md +1369 -0
  4. package/README.md +1016 -0
  5. package/library/__tests__/interceptors.test.ts +124 -0
  6. package/library/__tests__/requestIframe.test.ts +2216 -0
  7. package/library/__tests__/stream.test.ts +650 -0
  8. package/library/__tests__/utils.test.ts +433 -0
  9. package/library/api/client.d.ts +16 -0
  10. package/library/api/client.d.ts.map +1 -0
  11. package/library/api/client.js +72 -0
  12. package/library/api/server.d.ts +16 -0
  13. package/library/api/server.d.ts.map +1 -0
  14. package/library/api/server.js +44 -0
  15. package/library/constants/index.d.ts +209 -0
  16. package/library/constants/index.d.ts.map +1 -0
  17. package/library/constants/index.js +260 -0
  18. package/library/constants/messages.d.ts +80 -0
  19. package/library/constants/messages.d.ts.map +1 -0
  20. package/library/constants/messages.js +123 -0
  21. package/library/core/client.d.ts +99 -0
  22. package/library/core/client.d.ts.map +1 -0
  23. package/library/core/client.js +440 -0
  24. package/library/core/message-handler.d.ts +110 -0
  25. package/library/core/message-handler.d.ts.map +1 -0
  26. package/library/core/message-handler.js +320 -0
  27. package/library/core/request-response.d.ts +59 -0
  28. package/library/core/request-response.d.ts.map +1 -0
  29. package/library/core/request-response.js +337 -0
  30. package/library/core/request.d.ts +17 -0
  31. package/library/core/request.d.ts.map +1 -0
  32. package/library/core/request.js +34 -0
  33. package/library/core/response.d.ts +51 -0
  34. package/library/core/response.d.ts.map +1 -0
  35. package/library/core/response.js +323 -0
  36. package/library/core/server-base.d.ts +86 -0
  37. package/library/core/server-base.d.ts.map +1 -0
  38. package/library/core/server-base.js +257 -0
  39. package/library/core/server-client.d.ts +99 -0
  40. package/library/core/server-client.d.ts.map +1 -0
  41. package/library/core/server-client.js +256 -0
  42. package/library/core/server.d.ts +82 -0
  43. package/library/core/server.d.ts.map +1 -0
  44. package/library/core/server.js +338 -0
  45. package/library/index.d.ts +16 -0
  46. package/library/index.d.ts.map +1 -0
  47. package/library/index.js +211 -0
  48. package/library/interceptors/index.d.ts +41 -0
  49. package/library/interceptors/index.d.ts.map +1 -0
  50. package/library/interceptors/index.js +126 -0
  51. package/library/message/channel.d.ts +107 -0
  52. package/library/message/channel.d.ts.map +1 -0
  53. package/library/message/channel.js +184 -0
  54. package/library/message/dispatcher.d.ts +119 -0
  55. package/library/message/dispatcher.d.ts.map +1 -0
  56. package/library/message/dispatcher.js +249 -0
  57. package/library/message/index.d.ts +5 -0
  58. package/library/message/index.d.ts.map +1 -0
  59. package/library/message/index.js +25 -0
  60. package/library/stream/file-stream.d.ts +48 -0
  61. package/library/stream/file-stream.d.ts.map +1 -0
  62. package/library/stream/file-stream.js +240 -0
  63. package/library/stream/index.d.ts +15 -0
  64. package/library/stream/index.d.ts.map +1 -0
  65. package/library/stream/index.js +83 -0
  66. package/library/stream/readable-stream.d.ts +83 -0
  67. package/library/stream/readable-stream.d.ts.map +1 -0
  68. package/library/stream/readable-stream.js +249 -0
  69. package/library/stream/types.d.ts +165 -0
  70. package/library/stream/types.d.ts.map +1 -0
  71. package/library/stream/types.js +5 -0
  72. package/library/stream/writable-stream.d.ts +60 -0
  73. package/library/stream/writable-stream.d.ts.map +1 -0
  74. package/library/stream/writable-stream.js +348 -0
  75. package/library/types/index.d.ts +408 -0
  76. package/library/types/index.d.ts.map +1 -0
  77. package/library/types/index.js +5 -0
  78. package/library/utils/cache.d.ts +19 -0
  79. package/library/utils/cache.d.ts.map +1 -0
  80. package/library/utils/cache.js +83 -0
  81. package/library/utils/cookie.d.ts +117 -0
  82. package/library/utils/cookie.d.ts.map +1 -0
  83. package/library/utils/cookie.js +365 -0
  84. package/library/utils/debug.d.ts +11 -0
  85. package/library/utils/debug.d.ts.map +1 -0
  86. package/library/utils/debug.js +162 -0
  87. package/library/utils/index.d.ts +13 -0
  88. package/library/utils/index.d.ts.map +1 -0
  89. package/library/utils/index.js +132 -0
  90. package/library/utils/path-match.d.ts +17 -0
  91. package/library/utils/path-match.d.ts.map +1 -0
  92. package/library/utils/path-match.js +90 -0
  93. package/library/utils/protocol.d.ts +61 -0
  94. package/library/utils/protocol.d.ts.map +1 -0
  95. package/library/utils/protocol.js +169 -0
  96. package/package.json +58 -0
@@ -0,0 +1,650 @@
1
+ /**
2
+ * Stream functionality tests
3
+ */
4
+ import {
5
+ IframeWritableStream,
6
+ IframeReadableStream,
7
+ IframeFileWritableStream,
8
+ IframeFileReadableStream,
9
+ isIframeReadableStream,
10
+ isIframeFileStream,
11
+ StreamMessageHandler
12
+ } from '../stream';
13
+
14
+ describe('Stream', () => {
15
+ describe('IframeWritableStream', () => {
16
+ let mockTargetWindow: Window;
17
+ let mockPostMessage: jest.Mock;
18
+
19
+ beforeEach(() => {
20
+ mockPostMessage = jest.fn();
21
+ mockTargetWindow = {
22
+ postMessage: mockPostMessage
23
+ } as any;
24
+ });
25
+
26
+ it('should create stream with default options', () => {
27
+ const stream = new IframeWritableStream();
28
+
29
+ expect(stream.streamId).toBeDefined();
30
+ expect(stream.type).toBe('data');
31
+ expect(stream.chunked).toBe(true);
32
+ expect(stream.state).toBe('pending');
33
+ });
34
+
35
+ it('should create stream with custom options', () => {
36
+ const stream = new IframeWritableStream({
37
+ type: 'file',
38
+ chunked: false,
39
+ metadata: { foo: 'bar' }
40
+ });
41
+
42
+ expect(stream.type).toBe('file');
43
+ expect(stream.chunked).toBe(false);
44
+ });
45
+
46
+ it('should throw error when starting without binding', async () => {
47
+ const stream = new IframeWritableStream();
48
+
49
+ await expect(stream.start()).rejects.toThrow();
50
+ });
51
+
52
+ it('should generate unique stream IDs', () => {
53
+ const stream1 = new IframeWritableStream();
54
+ const stream2 = new IframeWritableStream();
55
+
56
+ expect(stream1.streamId).not.toBe(stream2.streamId);
57
+ });
58
+
59
+ it('should start stream with iterator', async () => {
60
+ const stream = new IframeWritableStream({
61
+ iterator: async function* () {
62
+ yield 'chunk1';
63
+ yield 'chunk2';
64
+ yield 'chunk3';
65
+ }
66
+ });
67
+
68
+ stream._bind({
69
+ requestId: 'req-123',
70
+ targetWindow: mockTargetWindow,
71
+ targetOrigin: 'https://example.com',
72
+ secretKey: 'test'
73
+ });
74
+
75
+ await stream.start();
76
+
77
+ expect(stream.state).toBe('ended');
78
+ // start + 3 data chunks + end = 5 calls
79
+ expect(mockPostMessage).toHaveBeenCalledTimes(5);
80
+ });
81
+
82
+ it('should start stream with next function', async () => {
83
+ let callCount = 0;
84
+ const stream = new IframeWritableStream({
85
+ next: () => {
86
+ callCount++;
87
+ if (callCount === 1) {
88
+ return { data: 'chunk1', done: false };
89
+ } else if (callCount === 2) {
90
+ return { data: 'chunk2', done: false };
91
+ } else {
92
+ return { data: 'chunk3', done: true };
93
+ }
94
+ }
95
+ });
96
+
97
+ stream._bind({
98
+ requestId: 'req-123',
99
+ targetWindow: mockTargetWindow,
100
+ targetOrigin: 'https://example.com',
101
+ secretKey: 'test'
102
+ });
103
+
104
+ await stream.start();
105
+
106
+ expect(stream.state).toBe('ended');
107
+ // start + 3 data chunks + end = 5 calls
108
+ expect(mockPostMessage).toHaveBeenCalledTimes(5);
109
+ });
110
+
111
+ it('should start stream without data source', async () => {
112
+ const stream = new IframeWritableStream();
113
+
114
+ stream._bind({
115
+ requestId: 'req-123',
116
+ targetWindow: mockTargetWindow,
117
+ targetOrigin: 'https://example.com',
118
+ secretKey: 'test'
119
+ });
120
+
121
+ await stream.start();
122
+
123
+ expect(stream.state).toBe('ended');
124
+ expect(mockPostMessage).toHaveBeenCalledTimes(2); // start + end
125
+ });
126
+
127
+ it('should handle iterator error', async () => {
128
+ const stream = new IframeWritableStream({
129
+ iterator: async function* () {
130
+ yield 'chunk1';
131
+ throw new Error('Iterator error');
132
+ }
133
+ });
134
+
135
+ stream._bind({
136
+ requestId: 'req-123',
137
+ targetWindow: mockTargetWindow,
138
+ targetOrigin: 'https://example.com',
139
+ secretKey: 'test'
140
+ });
141
+
142
+ await stream.start();
143
+
144
+ expect(stream.state).toBe('error');
145
+ const errorCall = mockPostMessage.mock.calls.find((call: any[]) =>
146
+ call[0]?.type === 'stream_error'
147
+ );
148
+ expect(errorCall).toBeDefined();
149
+ });
150
+
151
+ it('should handle next function error', async () => {
152
+ const stream = new IframeWritableStream({
153
+ next: () => {
154
+ throw new Error('Next error');
155
+ }
156
+ });
157
+
158
+ stream._bind({
159
+ requestId: 'req-123',
160
+ targetWindow: mockTargetWindow,
161
+ targetOrigin: 'https://example.com',
162
+ secretKey: 'test'
163
+ });
164
+
165
+ await stream.start();
166
+
167
+ expect(stream.state).toBe('error');
168
+ });
169
+
170
+ it('should cancel stream', () => {
171
+ const stream = new IframeWritableStream();
172
+
173
+ stream._bind({
174
+ requestId: 'req-123',
175
+ targetWindow: mockTargetWindow,
176
+ targetOrigin: 'https://example.com',
177
+ secretKey: 'test'
178
+ });
179
+
180
+ stream.cancel('User cancelled');
181
+
182
+ expect(stream.state).toBe('cancelled');
183
+ const cancelCall = mockPostMessage.mock.calls.find((call: any[]) =>
184
+ call[0]?.type === 'stream_cancel'
185
+ );
186
+ expect(cancelCall).toBeDefined();
187
+ });
188
+
189
+ it('should not cancel if already ended', () => {
190
+ const stream = new IframeWritableStream();
191
+ stream._bind({
192
+ requestId: 'req-123',
193
+ targetWindow: mockTargetWindow,
194
+ targetOrigin: 'https://example.com',
195
+ secretKey: 'test'
196
+ });
197
+
198
+ // Manually set state to ended (simulating already ended)
199
+ (stream as any)._state = 'ended';
200
+ mockPostMessage.mockClear();
201
+
202
+ stream.cancel('User cancelled');
203
+
204
+ expect(stream.state).toBe('ended');
205
+ expect(mockPostMessage).not.toHaveBeenCalled();
206
+ });
207
+
208
+ it('should use channel if provided', async () => {
209
+ const mockChannel = {
210
+ send: jest.fn()
211
+ } as any;
212
+
213
+ const stream = new IframeWritableStream();
214
+
215
+ stream._bind({
216
+ requestId: 'req-123',
217
+ targetWindow: mockTargetWindow,
218
+ targetOrigin: 'https://example.com',
219
+ secretKey: 'test',
220
+ channel: mockChannel
221
+ });
222
+
223
+ await stream.start();
224
+
225
+ expect(mockChannel.send).toHaveBeenCalled();
226
+ expect(mockPostMessage).not.toHaveBeenCalled();
227
+ });
228
+
229
+ it('should handle stream cancellation before start', () => {
230
+ const stream = new IframeWritableStream({
231
+ iterator: async function* () {
232
+ yield 'chunk1';
233
+ }
234
+ });
235
+
236
+ stream._bind({
237
+ requestId: 'req-123',
238
+ targetWindow: mockTargetWindow,
239
+ targetOrigin: 'https://example.com',
240
+ secretKey: 'test'
241
+ });
242
+
243
+ stream.cancel('User cancelled');
244
+
245
+ expect(stream.state).toBe('cancelled');
246
+ const cancelCall = mockPostMessage.mock.calls.find(
247
+ (call: any[]) => call[0]?.type === 'stream_cancel'
248
+ );
249
+ expect(cancelCall).toBeDefined();
250
+ });
251
+
252
+ it('should handle stream error during iteration', async () => {
253
+ const stream = new IframeWritableStream({
254
+ iterator: async function* () {
255
+ yield 'chunk1';
256
+ throw new Error('Stream error');
257
+ }
258
+ });
259
+
260
+ stream._bind({
261
+ requestId: 'req-123',
262
+ targetWindow: mockTargetWindow,
263
+ targetOrigin: 'https://example.com',
264
+ secretKey: 'test'
265
+ });
266
+
267
+ await stream.start();
268
+
269
+ expect(stream.state).toBe('error');
270
+ const errorCall = mockPostMessage.mock.calls.find(
271
+ (call: any[]) => call[0]?.type === 'stream_error'
272
+ );
273
+ expect(errorCall).toBeDefined();
274
+ });
275
+
276
+ it('should not end if already cancelled', async () => {
277
+ const stream = new IframeWritableStream({
278
+ iterator: async function* () {
279
+ yield 'chunk1';
280
+ }
281
+ });
282
+
283
+ stream._bind({
284
+ requestId: 'req-123',
285
+ targetWindow: mockTargetWindow,
286
+ targetOrigin: 'https://example.com',
287
+ secretKey: 'test'
288
+ });
289
+
290
+ const startPromise = stream.start();
291
+ stream.cancel('Cancelled');
292
+ await startPromise;
293
+
294
+ expect(stream.state).toBe('cancelled');
295
+ });
296
+ });
297
+
298
+ describe('IframeReadableStream', () => {
299
+ let mockHandler: StreamMessageHandler;
300
+ let registeredHandlers: Map<string, (data: any) => void>;
301
+
302
+ beforeEach(() => {
303
+ registeredHandlers = new Map();
304
+ mockHandler = {
305
+ registerStreamHandler: jest.fn((streamId, handler) => {
306
+ registeredHandlers.set(streamId, handler);
307
+ }),
308
+ unregisterStreamHandler: jest.fn((streamId) => {
309
+ registeredHandlers.delete(streamId);
310
+ }),
311
+ postMessage: jest.fn()
312
+ };
313
+ });
314
+
315
+ it('should create readable stream', () => {
316
+ const stream = new IframeReadableStream(
317
+ 'test-stream-id',
318
+ 'test-request-id',
319
+ mockHandler,
320
+ { type: 'data', chunked: true }
321
+ );
322
+
323
+ expect(stream.streamId).toBe('test-stream-id');
324
+ expect(stream.type).toBe('data');
325
+ expect(stream.chunked).toBe(true);
326
+ expect(stream.state).toBe('pending');
327
+ expect(mockHandler.registerStreamHandler).toHaveBeenCalledWith(
328
+ 'test-stream-id',
329
+ expect.any(Function)
330
+ );
331
+ });
332
+
333
+ it('should handle stream data', async () => {
334
+ const stream = new IframeReadableStream<string>(
335
+ 'test-stream-id',
336
+ 'test-request-id',
337
+ mockHandler
338
+ );
339
+
340
+ const handler = registeredHandlers.get('test-stream-id');
341
+ expect(handler).toBeDefined();
342
+
343
+ // Send data
344
+ handler!({ type: 'data', streamId: 'test-stream-id', data: 'chunk1' });
345
+ handler!({ type: 'data', streamId: 'test-stream-id', data: 'chunk2', done: true });
346
+
347
+ // Read data
348
+ const result = await stream.read();
349
+ expect(result).toEqual(['chunk1', 'chunk2']);
350
+ expect(stream.state).toBe('ended');
351
+ });
352
+
353
+ it('should handle stream end', async () => {
354
+ const stream = new IframeReadableStream<string>(
355
+ 'test-stream-id',
356
+ 'test-request-id',
357
+ mockHandler
358
+ );
359
+
360
+ const handler = registeredHandlers.get('test-stream-id');
361
+
362
+ const onEndCallback = jest.fn();
363
+ stream.onEnd(onEndCallback);
364
+
365
+ handler!({ type: 'data', streamId: 'test-stream-id', data: 'test' });
366
+ handler!({ type: 'end', streamId: 'test-stream-id' });
367
+
368
+ await stream.read();
369
+ expect(onEndCallback).toHaveBeenCalled();
370
+ });
371
+
372
+ it('should handle stream error', async () => {
373
+ const stream = new IframeReadableStream<string>(
374
+ 'test-stream-id',
375
+ 'test-request-id',
376
+ mockHandler
377
+ );
378
+
379
+ const handler = registeredHandlers.get('test-stream-id');
380
+
381
+ const onErrorCallback = jest.fn();
382
+ stream.onError(onErrorCallback);
383
+
384
+ handler!({ type: 'error', streamId: 'test-stream-id', error: 'Test error' });
385
+
386
+ await expect(stream.read()).rejects.toThrow();
387
+ expect(onErrorCallback).toHaveBeenCalled();
388
+ expect(stream.state).toBe('error');
389
+ });
390
+
391
+ it('should support async iterator', async () => {
392
+ const stream = new IframeReadableStream<string>(
393
+ 'test-stream-id',
394
+ 'test-request-id',
395
+ mockHandler
396
+ );
397
+
398
+ const handler = registeredHandlers.get('test-stream-id');
399
+
400
+ // Simulate async data sending
401
+ setTimeout(() => {
402
+ handler!({ type: 'data', streamId: 'test-stream-id', data: 'a' });
403
+ handler!({ type: 'data', streamId: 'test-stream-id', data: 'b' });
404
+ handler!({ type: 'end', streamId: 'test-stream-id' });
405
+ }, 10);
406
+
407
+ const chunks: string[] = [];
408
+ for await (const chunk of stream) {
409
+ chunks.push(chunk);
410
+ if (chunks.length >= 2) break; // Prevent infinite wait
411
+ }
412
+
413
+ expect(chunks).toEqual(['a', 'b']);
414
+ });
415
+
416
+ it('should cancel stream', () => {
417
+ const stream = new IframeReadableStream<string>(
418
+ 'test-stream-id',
419
+ 'test-request-id',
420
+ mockHandler
421
+ );
422
+
423
+ stream.cancel('User cancelled');
424
+
425
+ expect(stream.state).toBe('cancelled');
426
+ expect(mockHandler.postMessage).toHaveBeenCalled();
427
+ expect(mockHandler.unregisterStreamHandler).toHaveBeenCalledWith('test-stream-id');
428
+ });
429
+
430
+ it('should not cancel if already ended', () => {
431
+ const stream = new IframeReadableStream<string>(
432
+ 'test-stream-id',
433
+ 'test-request-id',
434
+ mockHandler
435
+ );
436
+
437
+ const handler = registeredHandlers.get('test-stream-id');
438
+ handler!({ type: 'end', streamId: 'test-stream-id' });
439
+
440
+ jest.clearAllMocks();
441
+ stream.cancel('User cancelled');
442
+
443
+ expect(stream.state).toBe('ended');
444
+ expect(mockHandler.postMessage).not.toHaveBeenCalled();
445
+ });
446
+
447
+ it('should not cancel if already in error state', () => {
448
+ const stream = new IframeReadableStream<string>(
449
+ 'test-stream-id',
450
+ 'test-request-id',
451
+ mockHandler
452
+ );
453
+
454
+ const handler = registeredHandlers.get('test-stream-id');
455
+ handler!({ type: 'error', streamId: 'test-stream-id', error: 'Error' });
456
+
457
+ jest.clearAllMocks();
458
+ stream.cancel('User cancelled');
459
+
460
+ expect(stream.state).toBe('error');
461
+ expect(mockHandler.postMessage).not.toHaveBeenCalled();
462
+ });
463
+
464
+ it('should handle stream data with done flag', async () => {
465
+ const stream = new IframeReadableStream<string>(
466
+ 'test-stream-id',
467
+ 'test-request-id',
468
+ mockHandler
469
+ );
470
+
471
+ const handler = registeredHandlers.get('test-stream-id');
472
+ handler!({ type: 'data', streamId: 'test-stream-id', data: 'chunk1', done: true });
473
+
474
+ const result = await stream.read();
475
+ expect(result).toBe('chunk1');
476
+ expect(stream.state).toBe('ended');
477
+ });
478
+
479
+ it('should handle onEnd callback when stream already ended', () => {
480
+ const stream = new IframeReadableStream<string>(
481
+ 'test-stream-id',
482
+ 'test-request-id',
483
+ mockHandler
484
+ );
485
+
486
+ const handler = registeredHandlers.get('test-stream-id');
487
+ handler!({ type: 'end', streamId: 'test-stream-id' });
488
+
489
+ const callback = jest.fn();
490
+ stream.onEnd(callback);
491
+ expect(callback).toHaveBeenCalled();
492
+ });
493
+ });
494
+
495
+ describe('IframeFileWritableStream', () => {
496
+ it('should create file stream with filename', () => {
497
+ const stream = new IframeFileWritableStream({
498
+ filename: 'test.txt',
499
+ mimeType: 'text/plain',
500
+ size: 1024
501
+ });
502
+
503
+ expect(stream.type).toBe('file');
504
+ expect(stream.filename).toBe('test.txt');
505
+ expect(stream.mimeType).toBe('text/plain');
506
+ expect(stream.size).toBe(1024);
507
+ });
508
+ });
509
+
510
+ describe('IframeFileReadableStream', () => {
511
+ let mockHandler: StreamMessageHandler;
512
+ let registeredHandlers: Map<string, (data: any) => void>;
513
+
514
+ beforeEach(() => {
515
+ registeredHandlers = new Map();
516
+ mockHandler = {
517
+ registerStreamHandler: jest.fn((streamId, handler) => {
518
+ registeredHandlers.set(streamId, handler);
519
+ }),
520
+ unregisterStreamHandler: jest.fn((streamId) => {
521
+ registeredHandlers.delete(streamId);
522
+ }),
523
+ postMessage: jest.fn()
524
+ };
525
+ });
526
+
527
+ it('should create file readable stream', () => {
528
+ const stream = new IframeFileReadableStream(
529
+ 'test-stream-id',
530
+ 'test-request-id',
531
+ mockHandler,
532
+ {
533
+ filename: 'test.txt',
534
+ mimeType: 'text/plain',
535
+ size: 100
536
+ }
537
+ );
538
+
539
+ expect(stream.type).toBe('file');
540
+ expect(stream.filename).toBe('test.txt');
541
+ expect(stream.mimeType).toBe('text/plain');
542
+ });
543
+
544
+ it('should decode base64 data', async () => {
545
+ const stream = new IframeFileReadableStream(
546
+ 'test-stream-id',
547
+ 'test-request-id',
548
+ mockHandler
549
+ );
550
+
551
+ const handler = registeredHandlers.get('test-stream-id');
552
+
553
+ // Send base64 encoded data
554
+ const testData = 'Hello, World!';
555
+ const base64Data = btoa(testData);
556
+ handler!({ type: 'data', streamId: 'test-stream-id', data: base64Data, done: true });
557
+
558
+ const result = await stream.read();
559
+ expect(result).toBeInstanceOf(Uint8Array);
560
+
561
+ // Decode to verify content (manually convert Uint8Array to string)
562
+ let decoded = '';
563
+ for (let i = 0; i < result.length; i++) {
564
+ decoded += String.fromCharCode(result[i]);
565
+ }
566
+ expect(decoded).toBe(testData);
567
+ });
568
+
569
+ it('should read as Blob', async () => {
570
+ const stream = new IframeFileReadableStream(
571
+ 'test-stream-id',
572
+ 'test-request-id',
573
+ mockHandler,
574
+ { mimeType: 'text/plain' }
575
+ );
576
+
577
+ const handler = registeredHandlers.get('test-stream-id');
578
+ const base64Data = btoa('test');
579
+ handler!({ type: 'data', streamId: 'test-stream-id', data: base64Data, done: true });
580
+
581
+ const blob = await stream.readAsBlob();
582
+ expect(blob).toBeInstanceOf(Blob);
583
+ expect(blob.type).toBe('text/plain');
584
+ });
585
+
586
+ it('should read as ArrayBuffer', async () => {
587
+ const stream = new IframeFileReadableStream(
588
+ 'test-stream-id',
589
+ 'test-request-id',
590
+ mockHandler
591
+ );
592
+
593
+ const handler = registeredHandlers.get('test-stream-id');
594
+ const base64Data = btoa('test');
595
+ handler!({ type: 'data', streamId: 'test-stream-id', data: base64Data, done: true });
596
+
597
+ const buffer = await stream.readAsArrayBuffer();
598
+ expect(buffer).toBeInstanceOf(ArrayBuffer);
599
+ });
600
+
601
+ it('should read as Data URL', async () => {
602
+ const stream = new IframeFileReadableStream(
603
+ 'test-stream-id',
604
+ 'test-request-id',
605
+ mockHandler,
606
+ { mimeType: 'text/plain' }
607
+ );
608
+
609
+ const handler = registeredHandlers.get('test-stream-id');
610
+ const base64Data = btoa('test');
611
+ handler!({ type: 'data', streamId: 'test-stream-id', data: base64Data, done: true });
612
+
613
+ const dataUrl = await stream.readAsDataURL();
614
+ expect(dataUrl).toMatch(/^data:text\/plain;base64,/);
615
+ });
616
+ });
617
+
618
+ describe('Type guards', () => {
619
+ let mockHandler: StreamMessageHandler;
620
+
621
+ beforeEach(() => {
622
+ mockHandler = {
623
+ registerStreamHandler: jest.fn(),
624
+ unregisterStreamHandler: jest.fn(),
625
+ postMessage: jest.fn()
626
+ };
627
+ });
628
+
629
+ it('isIframeReadableStream should return true for IframeReadableStream', () => {
630
+ const stream = new IframeReadableStream('id', 'reqId', mockHandler);
631
+ expect(isIframeReadableStream(stream)).toBe(true);
632
+ });
633
+
634
+ it('isIframeReadableStream should return false for non-stream objects', () => {
635
+ expect(isIframeReadableStream({})).toBe(false);
636
+ expect(isIframeReadableStream(null)).toBe(false);
637
+ expect(isIframeReadableStream('string')).toBe(false);
638
+ });
639
+
640
+ it('isIframeFileStream should return true for IframeFileReadableStream', () => {
641
+ const stream = new IframeFileReadableStream('id', 'reqId', mockHandler);
642
+ expect(isIframeFileStream(stream)).toBe(true);
643
+ });
644
+
645
+ it('isIframeFileStream should return false for regular IframeReadableStream', () => {
646
+ const stream = new IframeReadableStream('id', 'reqId', mockHandler);
647
+ expect(isIframeFileStream(stream)).toBe(false);
648
+ });
649
+ });
650
+ });