request-iframe 0.0.3 → 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.
- package/QUICKSTART.CN.md +35 -8
- package/QUICKSTART.md +35 -8
- package/README.CN.md +170 -24
- package/README.md +230 -19
- package/library/__tests__/coverage-branches.test.ts +356 -0
- package/library/__tests__/requestIframe.test.ts +1008 -58
- package/library/__tests__/stream.test.ts +46 -15
- package/library/api/client.d.ts.map +1 -1
- package/library/api/client.js +1 -0
- package/library/constants/messages.d.ts +2 -0
- package/library/constants/messages.d.ts.map +1 -1
- package/library/constants/messages.js +2 -0
- package/library/core/client-server.d.ts +4 -0
- package/library/core/client-server.d.ts.map +1 -1
- package/library/core/client-server.js +45 -22
- package/library/core/client.d.ts +31 -4
- package/library/core/client.d.ts.map +1 -1
- package/library/core/client.js +471 -284
- package/library/core/request.d.ts +3 -1
- package/library/core/request.d.ts.map +1 -1
- package/library/core/request.js +2 -1
- package/library/core/response.d.ts +26 -4
- package/library/core/response.d.ts.map +1 -1
- package/library/core/response.js +142 -81
- package/library/core/server.d.ts +13 -0
- package/library/core/server.d.ts.map +1 -1
- package/library/core/server.js +211 -6
- package/library/index.d.ts +2 -1
- package/library/index.d.ts.map +1 -1
- package/library/index.js +32 -3
- package/library/message/dispatcher.d.ts.map +1 -1
- package/library/message/dispatcher.js +4 -3
- package/library/stream/index.d.ts +11 -1
- package/library/stream/index.d.ts.map +1 -1
- package/library/stream/index.js +21 -3
- package/library/stream/types.d.ts +2 -2
- package/library/stream/types.d.ts.map +1 -1
- package/library/stream/writable-stream.d.ts +1 -1
- package/library/stream/writable-stream.d.ts.map +1 -1
- package/library/stream/writable-stream.js +8 -10
- package/library/types/index.d.ts +26 -4
- package/library/types/index.d.ts.map +1 -1
- package/library/utils/index.d.ts +14 -0
- package/library/utils/index.d.ts.map +1 -1
- package/library/utils/index.js +99 -1
- package/library/utils/path-match.d.ts +16 -0
- package/library/utils/path-match.d.ts.map +1 -1
- package/library/utils/path-match.js +65 -0
- package/package.json +2 -1
- package/react/library/__tests__/index.test.tsx +44 -22
- package/react/library/index.d.ts.map +1 -1
- package/react/library/index.js +81 -23
- package/react/package.json +7 -0
|
@@ -2,6 +2,7 @@ import { requestIframeClient, clearRequestIframeClientCache } from '../api/clien
|
|
|
2
2
|
import { requestIframeServer, clearRequestIframeServerCache } from '../api/server';
|
|
3
3
|
import { RequestConfig, Response, ErrorResponse, PostMessageData } from '../types';
|
|
4
4
|
import { HttpHeader, MessageRole, Messages } from '../constants';
|
|
5
|
+
import { IframeWritableStream } from '../stream';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Create test iframe
|
|
@@ -22,6 +23,18 @@ function cleanupIframe(iframe: HTMLIFrameElement): void {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Convert Blob to text (for assertions)
|
|
28
|
+
*/
|
|
29
|
+
function blobToText(blob: Blob): Promise<string> {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const reader = new FileReader();
|
|
32
|
+
reader.onload = () => resolve(String(reader.result ?? ''));
|
|
33
|
+
reader.onerror = reject;
|
|
34
|
+
reader.readAsText(blob);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
describe('requestIframeClient and requestIframeServer', () => {
|
|
26
39
|
beforeEach(() => {
|
|
27
40
|
// Clear all caches
|
|
@@ -110,6 +123,251 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
110
123
|
cleanupIframe(iframe);
|
|
111
124
|
});
|
|
112
125
|
|
|
126
|
+
it('should return response.data when returnData is true in options', async () => {
|
|
127
|
+
const origin = 'https://example.com';
|
|
128
|
+
const iframe = createTestIframe(origin);
|
|
129
|
+
|
|
130
|
+
const mockContentWindow = {
|
|
131
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
132
|
+
if (msg.type === 'request') {
|
|
133
|
+
window.dispatchEvent(
|
|
134
|
+
new MessageEvent('message', {
|
|
135
|
+
data: {
|
|
136
|
+
__requestIframe__: 1,
|
|
137
|
+
type: 'ack',
|
|
138
|
+
requestId: msg.requestId,
|
|
139
|
+
path: msg.path,
|
|
140
|
+
role: MessageRole.SERVER
|
|
141
|
+
},
|
|
142
|
+
origin
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
window.dispatchEvent(
|
|
147
|
+
new MessageEvent('message', {
|
|
148
|
+
data: {
|
|
149
|
+
__requestIframe__: 1,
|
|
150
|
+
type: 'response',
|
|
151
|
+
requestId: msg.requestId,
|
|
152
|
+
data: { result: 'success' },
|
|
153
|
+
status: 200,
|
|
154
|
+
statusText: 'OK',
|
|
155
|
+
role: MessageRole.SERVER
|
|
156
|
+
},
|
|
157
|
+
origin
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
}, 10);
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
};
|
|
164
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
165
|
+
value: mockContentWindow,
|
|
166
|
+
writable: true
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const client = requestIframeClient(iframe);
|
|
170
|
+
const server = requestIframeServer();
|
|
171
|
+
|
|
172
|
+
server.on('test', (req, res) => {
|
|
173
|
+
res.send({ result: 'success' });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const data = await client.send('test', { param: 'value' }, { ackTimeout: 1000, returnData: true });
|
|
177
|
+
// Should return data directly, not Response object
|
|
178
|
+
expect(data).toEqual({ result: 'success' });
|
|
179
|
+
expect((data as any).status).toBeUndefined();
|
|
180
|
+
expect((data as any).requestId).toBeUndefined();
|
|
181
|
+
|
|
182
|
+
server.destroy();
|
|
183
|
+
cleanupIframe(iframe);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should return full Response when returnData is false', async () => {
|
|
187
|
+
const origin = 'https://example.com';
|
|
188
|
+
const iframe = createTestIframe(origin);
|
|
189
|
+
|
|
190
|
+
const mockContentWindow = {
|
|
191
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
192
|
+
if (msg.type === 'request') {
|
|
193
|
+
window.dispatchEvent(
|
|
194
|
+
new MessageEvent('message', {
|
|
195
|
+
data: {
|
|
196
|
+
__requestIframe__: 1,
|
|
197
|
+
type: 'ack',
|
|
198
|
+
requestId: msg.requestId,
|
|
199
|
+
path: msg.path,
|
|
200
|
+
role: MessageRole.SERVER
|
|
201
|
+
},
|
|
202
|
+
origin
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
setTimeout(() => {
|
|
206
|
+
window.dispatchEvent(
|
|
207
|
+
new MessageEvent('message', {
|
|
208
|
+
data: {
|
|
209
|
+
__requestIframe__: 1,
|
|
210
|
+
type: 'response',
|
|
211
|
+
requestId: msg.requestId,
|
|
212
|
+
data: { result: 'success' },
|
|
213
|
+
status: 200,
|
|
214
|
+
statusText: 'OK',
|
|
215
|
+
role: MessageRole.SERVER
|
|
216
|
+
},
|
|
217
|
+
origin
|
|
218
|
+
})
|
|
219
|
+
);
|
|
220
|
+
}, 10);
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
};
|
|
224
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
225
|
+
value: mockContentWindow,
|
|
226
|
+
writable: true
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const client = requestIframeClient(iframe);
|
|
230
|
+
const server = requestIframeServer();
|
|
231
|
+
|
|
232
|
+
server.on('test', (req, res) => {
|
|
233
|
+
res.send({ result: 'success' });
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const response = await client.send('test', { param: 'value' }, { ackTimeout: 1000, returnData: false });
|
|
237
|
+
// Should return full Response object
|
|
238
|
+
expect(response).toHaveProperty('data');
|
|
239
|
+
expect(response).toHaveProperty('status');
|
|
240
|
+
expect(response).toHaveProperty('statusText');
|
|
241
|
+
expect(response).toHaveProperty('requestId');
|
|
242
|
+
expect(response.data).toEqual({ result: 'success' });
|
|
243
|
+
expect(response.status).toBe(200);
|
|
244
|
+
|
|
245
|
+
server.destroy();
|
|
246
|
+
cleanupIframe(iframe);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should use default returnData from RequestIframeClientOptions', async () => {
|
|
250
|
+
const origin = 'https://example.com';
|
|
251
|
+
const iframe = createTestIframe(origin);
|
|
252
|
+
|
|
253
|
+
const mockContentWindow = {
|
|
254
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
255
|
+
if (msg.type === 'request') {
|
|
256
|
+
window.dispatchEvent(
|
|
257
|
+
new MessageEvent('message', {
|
|
258
|
+
data: {
|
|
259
|
+
__requestIframe__: 1,
|
|
260
|
+
type: 'ack',
|
|
261
|
+
requestId: msg.requestId,
|
|
262
|
+
path: msg.path,
|
|
263
|
+
role: MessageRole.SERVER
|
|
264
|
+
},
|
|
265
|
+
origin
|
|
266
|
+
})
|
|
267
|
+
);
|
|
268
|
+
setTimeout(() => {
|
|
269
|
+
window.dispatchEvent(
|
|
270
|
+
new MessageEvent('message', {
|
|
271
|
+
data: {
|
|
272
|
+
__requestIframe__: 1,
|
|
273
|
+
type: 'response',
|
|
274
|
+
requestId: msg.requestId,
|
|
275
|
+
data: { result: 'success' },
|
|
276
|
+
status: 200,
|
|
277
|
+
statusText: 'OK',
|
|
278
|
+
role: MessageRole.SERVER
|
|
279
|
+
},
|
|
280
|
+
origin
|
|
281
|
+
})
|
|
282
|
+
);
|
|
283
|
+
}, 10);
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
};
|
|
287
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
288
|
+
value: mockContentWindow,
|
|
289
|
+
writable: true
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Create client with returnData: true in options
|
|
293
|
+
const client = requestIframeClient(iframe, { returnData: true });
|
|
294
|
+
const server = requestIframeServer();
|
|
295
|
+
|
|
296
|
+
server.on('test', (req, res) => {
|
|
297
|
+
res.send({ result: 'success' });
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Should return data directly without specifying returnData in send options
|
|
301
|
+
const data = await client.send('test', { param: 'value' }, { ackTimeout: 1000 });
|
|
302
|
+
expect(data).toEqual({ result: 'success' });
|
|
303
|
+
expect((data as any).status).toBeUndefined();
|
|
304
|
+
|
|
305
|
+
server.destroy();
|
|
306
|
+
cleanupIframe(iframe);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should allow overriding default returnData in send options', async () => {
|
|
310
|
+
const origin = 'https://example.com';
|
|
311
|
+
const iframe = createTestIframe(origin);
|
|
312
|
+
|
|
313
|
+
const mockContentWindow = {
|
|
314
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
315
|
+
if (msg.type === 'request') {
|
|
316
|
+
window.dispatchEvent(
|
|
317
|
+
new MessageEvent('message', {
|
|
318
|
+
data: {
|
|
319
|
+
__requestIframe__: 1,
|
|
320
|
+
type: 'ack',
|
|
321
|
+
requestId: msg.requestId,
|
|
322
|
+
path: msg.path,
|
|
323
|
+
role: MessageRole.SERVER
|
|
324
|
+
},
|
|
325
|
+
origin
|
|
326
|
+
})
|
|
327
|
+
);
|
|
328
|
+
setTimeout(() => {
|
|
329
|
+
window.dispatchEvent(
|
|
330
|
+
new MessageEvent('message', {
|
|
331
|
+
data: {
|
|
332
|
+
__requestIframe__: 1,
|
|
333
|
+
type: 'response',
|
|
334
|
+
requestId: msg.requestId,
|
|
335
|
+
data: { result: 'success' },
|
|
336
|
+
status: 200,
|
|
337
|
+
statusText: 'OK',
|
|
338
|
+
role: MessageRole.SERVER
|
|
339
|
+
},
|
|
340
|
+
origin
|
|
341
|
+
})
|
|
342
|
+
);
|
|
343
|
+
}, 10);
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
};
|
|
347
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
348
|
+
value: mockContentWindow,
|
|
349
|
+
writable: true
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Create client with returnData: true in options
|
|
353
|
+
const client = requestIframeClient(iframe, { returnData: true });
|
|
354
|
+
const server = requestIframeServer();
|
|
355
|
+
|
|
356
|
+
server.on('test', (req, res) => {
|
|
357
|
+
res.send({ result: 'success' });
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Override with returnData: false in send options
|
|
361
|
+
const response = await client.send('test', { param: 'value' }, { ackTimeout: 1000, returnData: false });
|
|
362
|
+
// Should return full Response object despite default being true
|
|
363
|
+
expect(response).toHaveProperty('data');
|
|
364
|
+
expect(response).toHaveProperty('status');
|
|
365
|
+
expect(response.data).toEqual({ result: 'success' });
|
|
366
|
+
|
|
367
|
+
server.destroy();
|
|
368
|
+
cleanupIframe(iframe);
|
|
369
|
+
});
|
|
370
|
+
|
|
113
371
|
it('should throw error when iframe.contentWindow is unavailable', () => {
|
|
114
372
|
const iframe = document.createElement('iframe');
|
|
115
373
|
iframe.src = 'https://example.com/test.html';
|
|
@@ -459,70 +717,378 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
459
717
|
timeout: 200,
|
|
460
718
|
asyncTimeout: 5000
|
|
461
719
|
});
|
|
462
|
-
expect(response.data).toEqual({ result: 'async success' });
|
|
463
|
-
server.destroy();
|
|
464
|
-
cleanupIframe(iframe);
|
|
465
|
-
});
|
|
466
|
-
});
|
|
720
|
+
expect(response.data).toEqual({ result: 'async success' });
|
|
721
|
+
server.destroy();
|
|
722
|
+
cleanupIframe(iframe);
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
describe('MessageChannel sharing', () => {
|
|
727
|
+
it('should share the same message channel for the same secretKey', () => {
|
|
728
|
+
const iframe1 = createTestIframe('https://example.com');
|
|
729
|
+
const iframe2 = createTestIframe('https://example2.com');
|
|
730
|
+
|
|
731
|
+
const server1 = requestIframeServer({ secretKey: 'demo' });
|
|
732
|
+
const server2 = requestIframeServer({ secretKey: 'demo' });
|
|
733
|
+
|
|
734
|
+
// Server instances are different
|
|
735
|
+
expect(server1).not.toBe(server2);
|
|
736
|
+
|
|
737
|
+
// But they should share the same underlying message channel (verified by secretKey)
|
|
738
|
+
expect(server1.secretKey).toBe(server2.secretKey);
|
|
739
|
+
expect(server1.secretKey).toBe('demo');
|
|
740
|
+
|
|
741
|
+
server1.destroy();
|
|
742
|
+
server2.destroy();
|
|
743
|
+
cleanupIframe(iframe1);
|
|
744
|
+
cleanupIframe(iframe2);
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
it('should have independent message channels for different secretKeys', () => {
|
|
748
|
+
const iframe = createTestIframe('https://example.com');
|
|
749
|
+
|
|
750
|
+
const server1 = requestIframeServer({ secretKey: 'demo1' });
|
|
751
|
+
const server2 = requestIframeServer({ secretKey: 'demo2' });
|
|
752
|
+
|
|
753
|
+
// Verify different server instances
|
|
754
|
+
expect(server1).not.toBe(server2);
|
|
755
|
+
|
|
756
|
+
// secretKeys are different
|
|
757
|
+
expect(server1.secretKey).toBe('demo1');
|
|
758
|
+
expect(server2.secretKey).toBe('demo2');
|
|
759
|
+
|
|
760
|
+
server1.destroy();
|
|
761
|
+
server2.destroy();
|
|
762
|
+
cleanupIframe(iframe);
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it('should share the same message channel when no secretKey', () => {
|
|
766
|
+
const iframe1 = createTestIframe('https://example.com');
|
|
767
|
+
const iframe2 = createTestIframe('https://example2.com');
|
|
768
|
+
|
|
769
|
+
const server1 = requestIframeServer();
|
|
770
|
+
const server2 = requestIframeServer();
|
|
771
|
+
|
|
772
|
+
// Server instances are different
|
|
773
|
+
expect(server1).not.toBe(server2);
|
|
774
|
+
|
|
775
|
+
// But they should share the same underlying message channel (both have no secretKey)
|
|
776
|
+
expect(server1.secretKey).toBe(server2.secretKey);
|
|
777
|
+
expect(server1.secretKey).toBeUndefined();
|
|
778
|
+
|
|
779
|
+
server1.destroy();
|
|
780
|
+
server2.destroy();
|
|
781
|
+
cleanupIframe(iframe1);
|
|
782
|
+
cleanupIframe(iframe2);
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
describe('secretKey message isolation', () => {
|
|
787
|
+
it('should successfully communicate when client and server use the same secretKey', async () => {
|
|
788
|
+
const origin = 'https://example.com';
|
|
789
|
+
const iframe = createTestIframe(origin);
|
|
790
|
+
|
|
791
|
+
const mockContentWindow = {
|
|
792
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
793
|
+
if (msg.type === 'request') {
|
|
794
|
+
// Verify secretKey is in message
|
|
795
|
+
expect(msg.secretKey).toBe('test-key');
|
|
796
|
+
// Verify path is NOT prefixed with secretKey
|
|
797
|
+
expect(msg.path).toBe('test');
|
|
798
|
+
|
|
799
|
+
// Send ACK first
|
|
800
|
+
window.dispatchEvent(
|
|
801
|
+
new MessageEvent('message', {
|
|
802
|
+
data: {
|
|
803
|
+
__requestIframe__: 1,
|
|
804
|
+
type: 'ack',
|
|
805
|
+
requestId: msg.requestId,
|
|
806
|
+
path: msg.path,
|
|
807
|
+
secretKey: 'test-key',
|
|
808
|
+
role: MessageRole.SERVER
|
|
809
|
+
},
|
|
810
|
+
origin
|
|
811
|
+
})
|
|
812
|
+
);
|
|
813
|
+
// Then send response
|
|
814
|
+
setTimeout(() => {
|
|
815
|
+
window.dispatchEvent(
|
|
816
|
+
new MessageEvent('message', {
|
|
817
|
+
data: {
|
|
818
|
+
__requestIframe__: 1,
|
|
819
|
+
type: 'response',
|
|
820
|
+
requestId: msg.requestId,
|
|
821
|
+
data: { result: 'success' },
|
|
822
|
+
status: 200,
|
|
823
|
+
statusText: 'OK',
|
|
824
|
+
secretKey: 'test-key',
|
|
825
|
+
role: MessageRole.SERVER
|
|
826
|
+
},
|
|
827
|
+
origin
|
|
828
|
+
})
|
|
829
|
+
);
|
|
830
|
+
}, 10);
|
|
831
|
+
}
|
|
832
|
+
})
|
|
833
|
+
};
|
|
834
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
835
|
+
value: mockContentWindow,
|
|
836
|
+
writable: true
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
const client = requestIframeClient(iframe, { secretKey: 'test-key' });
|
|
840
|
+
const server = requestIframeServer({ secretKey: 'test-key' });
|
|
841
|
+
|
|
842
|
+
server.on('test', (req, res) => {
|
|
843
|
+
res.send({ result: 'success' });
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
const response = await client.send('test', { param: 'value' }, { ackTimeout: 1000 });
|
|
847
|
+
expect(response.data).toEqual({ result: 'success' });
|
|
848
|
+
expect(response.status).toBe(200);
|
|
849
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalled();
|
|
850
|
+
|
|
851
|
+
// Verify the sent message has secretKey
|
|
852
|
+
const sentMessage = (mockContentWindow.postMessage as jest.Mock).mock.calls[0][0];
|
|
853
|
+
expect(sentMessage.secretKey).toBe('test-key');
|
|
854
|
+
expect(sentMessage.path).toBe('test'); // Path should NOT be prefixed
|
|
855
|
+
|
|
856
|
+
server.destroy();
|
|
857
|
+
cleanupIframe(iframe);
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
it('should NOT communicate when client and server use different secretKeys', async () => {
|
|
861
|
+
const origin = 'https://example.com';
|
|
862
|
+
const iframe = createTestIframe(origin);
|
|
863
|
+
|
|
864
|
+
const mockContentWindow = {
|
|
865
|
+
postMessage: jest.fn()
|
|
866
|
+
};
|
|
867
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
868
|
+
value: mockContentWindow,
|
|
869
|
+
writable: true
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
const client = requestIframeClient(iframe, { secretKey: 'client-key' });
|
|
873
|
+
const server = requestIframeServer({ secretKey: 'server-key' });
|
|
874
|
+
|
|
875
|
+
server.on('test', (req, res) => {
|
|
876
|
+
res.send({ result: 'success' });
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// Request should timeout because server won't respond (different secretKey)
|
|
880
|
+
await expect(
|
|
881
|
+
client.send('test', { param: 'value' }, { ackTimeout: 100 })
|
|
882
|
+
).rejects.toMatchObject({
|
|
883
|
+
code: 'ACK_TIMEOUT'
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
server.destroy();
|
|
887
|
+
cleanupIframe(iframe);
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
it('should NOT communicate when client has secretKey but server does not', async () => {
|
|
891
|
+
const origin = 'https://example.com';
|
|
892
|
+
const iframe = createTestIframe(origin);
|
|
893
|
+
|
|
894
|
+
const mockContentWindow = {
|
|
895
|
+
postMessage: jest.fn()
|
|
896
|
+
};
|
|
897
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
898
|
+
value: mockContentWindow,
|
|
899
|
+
writable: true
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
const client = requestIframeClient(iframe, { secretKey: 'client-key' });
|
|
903
|
+
const server = requestIframeServer(); // No secretKey
|
|
904
|
+
|
|
905
|
+
server.on('test', (req, res) => {
|
|
906
|
+
res.send({ result: 'success' });
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
// Request should timeout because server won't respond (different secretKey)
|
|
910
|
+
await expect(
|
|
911
|
+
client.send('test', { param: 'value' }, { ackTimeout: 100 })
|
|
912
|
+
).rejects.toMatchObject({
|
|
913
|
+
code: 'ACK_TIMEOUT'
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
server.destroy();
|
|
917
|
+
cleanupIframe(iframe);
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
it('should NOT communicate when client has no secretKey but server has secretKey', async () => {
|
|
921
|
+
const origin = 'https://example.com';
|
|
922
|
+
const iframe = createTestIframe(origin);
|
|
923
|
+
|
|
924
|
+
const mockContentWindow = {
|
|
925
|
+
postMessage: jest.fn()
|
|
926
|
+
};
|
|
927
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
928
|
+
value: mockContentWindow,
|
|
929
|
+
writable: true
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
const client = requestIframeClient(iframe); // No secretKey
|
|
933
|
+
const server = requestIframeServer({ secretKey: 'server-key' });
|
|
934
|
+
|
|
935
|
+
server.on('test', (req, res) => {
|
|
936
|
+
res.send({ result: 'success' });
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
// Request should timeout because server won't respond (different secretKey)
|
|
940
|
+
await expect(
|
|
941
|
+
client.send('test', { param: 'value' }, { ackTimeout: 100 })
|
|
942
|
+
).rejects.toMatchObject({
|
|
943
|
+
code: 'ACK_TIMEOUT'
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
server.destroy();
|
|
947
|
+
cleanupIframe(iframe);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
it('should successfully communicate when both client and server have no secretKey', async () => {
|
|
951
|
+
const origin = 'https://example.com';
|
|
952
|
+
const iframe = createTestIframe(origin);
|
|
953
|
+
|
|
954
|
+
const mockContentWindow = {
|
|
955
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
956
|
+
if (msg.type === 'request') {
|
|
957
|
+
// Verify secretKey is NOT in message
|
|
958
|
+
expect(msg.secretKey).toBeUndefined();
|
|
959
|
+
// Verify path is NOT prefixed
|
|
960
|
+
expect(msg.path).toBe('test');
|
|
961
|
+
|
|
962
|
+
// Send ACK first
|
|
963
|
+
window.dispatchEvent(
|
|
964
|
+
new MessageEvent('message', {
|
|
965
|
+
data: {
|
|
966
|
+
__requestIframe__: 1,
|
|
967
|
+
type: 'ack',
|
|
968
|
+
requestId: msg.requestId,
|
|
969
|
+
path: msg.path,
|
|
970
|
+
role: MessageRole.SERVER
|
|
971
|
+
},
|
|
972
|
+
origin
|
|
973
|
+
})
|
|
974
|
+
);
|
|
975
|
+
// Then send response
|
|
976
|
+
setTimeout(() => {
|
|
977
|
+
window.dispatchEvent(
|
|
978
|
+
new MessageEvent('message', {
|
|
979
|
+
data: {
|
|
980
|
+
__requestIframe__: 1,
|
|
981
|
+
type: 'response',
|
|
982
|
+
requestId: msg.requestId,
|
|
983
|
+
data: { result: 'success' },
|
|
984
|
+
status: 200,
|
|
985
|
+
statusText: 'OK',
|
|
986
|
+
role: MessageRole.SERVER
|
|
987
|
+
},
|
|
988
|
+
origin
|
|
989
|
+
})
|
|
990
|
+
);
|
|
991
|
+
}, 10);
|
|
992
|
+
}
|
|
993
|
+
})
|
|
994
|
+
};
|
|
995
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
996
|
+
value: mockContentWindow,
|
|
997
|
+
writable: true
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
const client = requestIframeClient(iframe); // No secretKey
|
|
1001
|
+
const server = requestIframeServer(); // No secretKey
|
|
1002
|
+
|
|
1003
|
+
server.on('test', (req, res) => {
|
|
1004
|
+
res.send({ result: 'success' });
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
const response = await client.send('test', { param: 'value' }, { ackTimeout: 1000 });
|
|
1008
|
+
expect(response.data).toEqual({ result: 'success' });
|
|
1009
|
+
expect(response.status).toBe(200);
|
|
1010
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalled();
|
|
1011
|
+
|
|
1012
|
+
// Verify the sent message has no secretKey
|
|
1013
|
+
const sentMessage = (mockContentWindow.postMessage as jest.Mock).mock.calls[0][0];
|
|
1014
|
+
expect(sentMessage.secretKey).toBeUndefined();
|
|
1015
|
+
expect(sentMessage.path).toBe('test'); // Path should NOT be prefixed
|
|
1016
|
+
|
|
1017
|
+
server.destroy();
|
|
1018
|
+
cleanupIframe(iframe);
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
it('should handle path correctly with secretKey (path should not be prefixed)', async () => {
|
|
1022
|
+
const origin = 'https://example.com';
|
|
1023
|
+
const iframe = createTestIframe(origin);
|
|
1024
|
+
|
|
1025
|
+
const mockContentWindow = {
|
|
1026
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
1027
|
+
if (msg.type === 'request') {
|
|
1028
|
+
// Verify path is NOT prefixed with secretKey
|
|
1029
|
+
expect(msg.path).toBe('api/users');
|
|
1030
|
+
expect(msg.secretKey).toBe('my-app');
|
|
1031
|
+
|
|
1032
|
+
// Send ACK first
|
|
1033
|
+
window.dispatchEvent(
|
|
1034
|
+
new MessageEvent('message', {
|
|
1035
|
+
data: {
|
|
1036
|
+
__requestIframe__: 1,
|
|
1037
|
+
type: 'ack',
|
|
1038
|
+
requestId: msg.requestId,
|
|
1039
|
+
path: msg.path,
|
|
1040
|
+
secretKey: 'my-app',
|
|
1041
|
+
role: MessageRole.SERVER
|
|
1042
|
+
},
|
|
1043
|
+
origin
|
|
1044
|
+
})
|
|
1045
|
+
);
|
|
1046
|
+
// Then send response
|
|
1047
|
+
setTimeout(() => {
|
|
1048
|
+
window.dispatchEvent(
|
|
1049
|
+
new MessageEvent('message', {
|
|
1050
|
+
data: {
|
|
1051
|
+
__requestIframe__: 1,
|
|
1052
|
+
type: 'response',
|
|
1053
|
+
requestId: msg.requestId,
|
|
1054
|
+
data: { users: [] },
|
|
1055
|
+
status: 200,
|
|
1056
|
+
statusText: 'OK',
|
|
1057
|
+
secretKey: 'my-app',
|
|
1058
|
+
role: MessageRole.SERVER
|
|
1059
|
+
},
|
|
1060
|
+
origin
|
|
1061
|
+
})
|
|
1062
|
+
);
|
|
1063
|
+
}, 10);
|
|
1064
|
+
}
|
|
1065
|
+
})
|
|
1066
|
+
};
|
|
1067
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1068
|
+
value: mockContentWindow,
|
|
1069
|
+
writable: true
|
|
1070
|
+
});
|
|
467
1071
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const iframe1 = createTestIframe('https://example.com');
|
|
471
|
-
const iframe2 = createTestIframe('https://example2.com');
|
|
1072
|
+
const client = requestIframeClient(iframe, { secretKey: 'my-app' });
|
|
1073
|
+
const server = requestIframeServer({ secretKey: 'my-app' });
|
|
472
1074
|
|
|
473
|
-
|
|
474
|
-
|
|
1075
|
+
// Server registers handler with original path (not prefixed)
|
|
1076
|
+
server.on('api/users', (req, res) => {
|
|
1077
|
+
res.send({ users: [] });
|
|
1078
|
+
});
|
|
475
1079
|
|
|
476
|
-
//
|
|
477
|
-
|
|
1080
|
+
// Client sends request with original path (not prefixed)
|
|
1081
|
+
const response = await client.send('api/users', undefined, { ackTimeout: 1000 });
|
|
1082
|
+
expect(response.data).toEqual({ users: [] });
|
|
478
1083
|
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
expect(
|
|
482
|
-
|
|
483
|
-
server1.destroy();
|
|
484
|
-
server2.destroy();
|
|
485
|
-
cleanupIframe(iframe1);
|
|
486
|
-
cleanupIframe(iframe2);
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
it('should have independent message channels for different secretKeys', () => {
|
|
490
|
-
const iframe = createTestIframe('https://example.com');
|
|
491
|
-
|
|
492
|
-
const server1 = requestIframeServer({ secretKey: 'demo1' });
|
|
493
|
-
const server2 = requestIframeServer({ secretKey: 'demo2' });
|
|
494
|
-
|
|
495
|
-
// Verify different server instances
|
|
496
|
-
expect(server1).not.toBe(server2);
|
|
1084
|
+
// Verify path in sent message is NOT prefixed
|
|
1085
|
+
const sentMessage = (mockContentWindow.postMessage as jest.Mock).mock.calls[0][0];
|
|
1086
|
+
expect(sentMessage.path).toBe('api/users');
|
|
1087
|
+
expect(sentMessage.secretKey).toBe('my-app');
|
|
497
1088
|
|
|
498
|
-
|
|
499
|
-
expect(server1.secretKey).toBe('demo1');
|
|
500
|
-
expect(server2.secretKey).toBe('demo2');
|
|
501
|
-
|
|
502
|
-
server1.destroy();
|
|
503
|
-
server2.destroy();
|
|
1089
|
+
server.destroy();
|
|
504
1090
|
cleanupIframe(iframe);
|
|
505
1091
|
});
|
|
506
|
-
|
|
507
|
-
it('should share the same message channel when no secretKey', () => {
|
|
508
|
-
const iframe1 = createTestIframe('https://example.com');
|
|
509
|
-
const iframe2 = createTestIframe('https://example2.com');
|
|
510
|
-
|
|
511
|
-
const server1 = requestIframeServer();
|
|
512
|
-
const server2 = requestIframeServer();
|
|
513
|
-
|
|
514
|
-
// Server instances are different
|
|
515
|
-
expect(server1).not.toBe(server2);
|
|
516
|
-
|
|
517
|
-
// But they should share the same underlying message channel (both have no secretKey)
|
|
518
|
-
expect(server1.secretKey).toBe(server2.secretKey);
|
|
519
|
-
expect(server1.secretKey).toBeUndefined();
|
|
520
|
-
|
|
521
|
-
server1.destroy();
|
|
522
|
-
server2.destroy();
|
|
523
|
-
cleanupIframe(iframe1);
|
|
524
|
-
cleanupIframe(iframe2);
|
|
525
|
-
});
|
|
526
1092
|
});
|
|
527
1093
|
|
|
528
1094
|
describe('Middleware', () => {
|
|
@@ -701,7 +1267,7 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
701
1267
|
});
|
|
702
1268
|
|
|
703
1269
|
describe('sendFile', () => {
|
|
704
|
-
it('should support sending file (
|
|
1270
|
+
it('should support sending file (stream)', async () => {
|
|
705
1271
|
const origin = 'https://example.com';
|
|
706
1272
|
const iframe = createTestIframe(origin);
|
|
707
1273
|
|
|
@@ -1070,6 +1636,157 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
1070
1636
|
}, 20000);
|
|
1071
1637
|
});
|
|
1072
1638
|
|
|
1639
|
+
describe('Path parameters', () => {
|
|
1640
|
+
it('should extract path parameters from route pattern', async () => {
|
|
1641
|
+
const origin = 'https://example.com';
|
|
1642
|
+
const iframe = createTestIframe(origin);
|
|
1643
|
+
|
|
1644
|
+
const mockContentWindow: any = {
|
|
1645
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
1646
|
+
window.dispatchEvent(
|
|
1647
|
+
new MessageEvent('message', {
|
|
1648
|
+
data: msg,
|
|
1649
|
+
origin,
|
|
1650
|
+
source: mockContentWindow as any
|
|
1651
|
+
})
|
|
1652
|
+
);
|
|
1653
|
+
})
|
|
1654
|
+
};
|
|
1655
|
+
Object.defineProperty(iframe, 'contentWindow', { value: mockContentWindow, writable: true });
|
|
1656
|
+
|
|
1657
|
+
const client = requestIframeClient(iframe);
|
|
1658
|
+
const server = requestIframeServer();
|
|
1659
|
+
|
|
1660
|
+
server.on('/api/users/:id', (req, res) => {
|
|
1661
|
+
expect(req.params.id).toBe('123');
|
|
1662
|
+
expect(req.path).toBe('/api/users/123');
|
|
1663
|
+
res.send({ userId: req.params.id });
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
const resp = await client.send<any>('/api/users/123');
|
|
1667
|
+
expect((resp as any).data.userId).toBe('123');
|
|
1668
|
+
|
|
1669
|
+
client.destroy();
|
|
1670
|
+
server.destroy();
|
|
1671
|
+
cleanupIframe(iframe);
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1674
|
+
it('should extract multiple path parameters', async () => {
|
|
1675
|
+
const origin = 'https://example.com';
|
|
1676
|
+
const iframe = createTestIframe(origin);
|
|
1677
|
+
|
|
1678
|
+
const mockContentWindow: any = {
|
|
1679
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
1680
|
+
window.dispatchEvent(
|
|
1681
|
+
new MessageEvent('message', {
|
|
1682
|
+
data: msg,
|
|
1683
|
+
origin,
|
|
1684
|
+
source: mockContentWindow as any
|
|
1685
|
+
})
|
|
1686
|
+
);
|
|
1687
|
+
})
|
|
1688
|
+
};
|
|
1689
|
+
Object.defineProperty(iframe, 'contentWindow', { value: mockContentWindow, writable: true });
|
|
1690
|
+
|
|
1691
|
+
const client = requestIframeClient(iframe);
|
|
1692
|
+
const server = requestIframeServer();
|
|
1693
|
+
|
|
1694
|
+
server.on('/api/users/:userId/posts/:postId', (req, res) => {
|
|
1695
|
+
expect(req.params.userId).toBe('456');
|
|
1696
|
+
expect(req.params.postId).toBe('789');
|
|
1697
|
+
res.send({ userId: req.params.userId, postId: req.params.postId });
|
|
1698
|
+
});
|
|
1699
|
+
|
|
1700
|
+
const resp = await client.send<any>('/api/users/456/posts/789');
|
|
1701
|
+
expect((resp as any).data.userId).toBe('456');
|
|
1702
|
+
expect((resp as any).data.postId).toBe('789');
|
|
1703
|
+
|
|
1704
|
+
client.destroy();
|
|
1705
|
+
server.destroy();
|
|
1706
|
+
cleanupIframe(iframe);
|
|
1707
|
+
});
|
|
1708
|
+
|
|
1709
|
+
it('should return empty params for exact path match', async () => {
|
|
1710
|
+
const origin = 'https://example.com';
|
|
1711
|
+
const iframe = createTestIframe(origin);
|
|
1712
|
+
|
|
1713
|
+
const mockContentWindow: any = {
|
|
1714
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
1715
|
+
window.dispatchEvent(
|
|
1716
|
+
new MessageEvent('message', {
|
|
1717
|
+
data: msg,
|
|
1718
|
+
origin,
|
|
1719
|
+
source: mockContentWindow as any
|
|
1720
|
+
})
|
|
1721
|
+
);
|
|
1722
|
+
})
|
|
1723
|
+
};
|
|
1724
|
+
Object.defineProperty(iframe, 'contentWindow', { value: mockContentWindow, writable: true });
|
|
1725
|
+
|
|
1726
|
+
const client = requestIframeClient(iframe);
|
|
1727
|
+
const server = requestIframeServer();
|
|
1728
|
+
|
|
1729
|
+
server.on('/api/users', (req, res) => {
|
|
1730
|
+
expect(req.params).toEqual({});
|
|
1731
|
+
expect(req.path).toBe('/api/users');
|
|
1732
|
+
res.send({ success: true });
|
|
1733
|
+
});
|
|
1734
|
+
|
|
1735
|
+
const resp = await client.send<any>('/api/users');
|
|
1736
|
+
expect((resp as any).data.success).toBe(true);
|
|
1737
|
+
|
|
1738
|
+
client.destroy();
|
|
1739
|
+
server.destroy();
|
|
1740
|
+
cleanupIframe(iframe);
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1743
|
+
it('should work with stream requests', async () => {
|
|
1744
|
+
const origin = 'https://example.com';
|
|
1745
|
+
const iframe = createTestIframe(origin);
|
|
1746
|
+
|
|
1747
|
+
const mockContentWindow: any = {
|
|
1748
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
1749
|
+
window.dispatchEvent(
|
|
1750
|
+
new MessageEvent('message', {
|
|
1751
|
+
data: msg,
|
|
1752
|
+
origin,
|
|
1753
|
+
source: mockContentWindow as any
|
|
1754
|
+
})
|
|
1755
|
+
);
|
|
1756
|
+
})
|
|
1757
|
+
};
|
|
1758
|
+
Object.defineProperty(iframe, 'contentWindow', { value: mockContentWindow, writable: true });
|
|
1759
|
+
|
|
1760
|
+
const client = requestIframeClient(iframe);
|
|
1761
|
+
const server = requestIframeServer();
|
|
1762
|
+
|
|
1763
|
+
server.on('/api/upload/:fileId', async (req, res) => {
|
|
1764
|
+
expect(req.params.fileId).toBe('file-123');
|
|
1765
|
+
expect(req.stream).toBeDefined();
|
|
1766
|
+
const chunks: any[] = [];
|
|
1767
|
+
for await (const chunk of req.stream as any) {
|
|
1768
|
+
chunks.push(chunk);
|
|
1769
|
+
}
|
|
1770
|
+
res.send({ fileId: req.params.fileId, chunks });
|
|
1771
|
+
});
|
|
1772
|
+
|
|
1773
|
+
const stream = new IframeWritableStream({
|
|
1774
|
+
iterator: async function* () {
|
|
1775
|
+
yield 'chunk1';
|
|
1776
|
+
yield 'chunk2';
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1779
|
+
|
|
1780
|
+
const resp = await client.sendStream<any>('/api/upload/file-123', stream);
|
|
1781
|
+
expect((resp as any).data.fileId).toBe('file-123');
|
|
1782
|
+
expect((resp as any).data.chunks).toEqual(['chunk1', 'chunk2']);
|
|
1783
|
+
|
|
1784
|
+
client.destroy();
|
|
1785
|
+
server.destroy();
|
|
1786
|
+
cleanupIframe(iframe);
|
|
1787
|
+
});
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1073
1790
|
describe('server.map', () => {
|
|
1074
1791
|
it('should register multiple event handlers at once', async () => {
|
|
1075
1792
|
const origin = 'https://example.com';
|
|
@@ -2133,6 +2850,239 @@ describe('requestIframeClient and requestIframeServer', () => {
|
|
|
2133
2850
|
});
|
|
2134
2851
|
});
|
|
2135
2852
|
|
|
2853
|
+
describe('client send various body types', () => {
|
|
2854
|
+
it('should send plain object and server receives JSON + Content-Type', async () => {
|
|
2855
|
+
const origin = 'https://example.com';
|
|
2856
|
+
const iframe = createTestIframe(origin);
|
|
2857
|
+
|
|
2858
|
+
const mockContentWindow: any = {
|
|
2859
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
2860
|
+
window.dispatchEvent(
|
|
2861
|
+
new MessageEvent('message', {
|
|
2862
|
+
data: msg,
|
|
2863
|
+
origin,
|
|
2864
|
+
source: mockContentWindow as any
|
|
2865
|
+
})
|
|
2866
|
+
);
|
|
2867
|
+
})
|
|
2868
|
+
};
|
|
2869
|
+
Object.defineProperty(iframe, 'contentWindow', { value: mockContentWindow, writable: true });
|
|
2870
|
+
|
|
2871
|
+
const client = requestIframeClient(iframe);
|
|
2872
|
+
const server = requestIframeServer();
|
|
2873
|
+
|
|
2874
|
+
server.on('echoObject', (req, res) => {
|
|
2875
|
+
expect(req.headers[HttpHeader.CONTENT_TYPE]).toBe('application/json');
|
|
2876
|
+
res.send({ ok: true, received: req.body });
|
|
2877
|
+
});
|
|
2878
|
+
|
|
2879
|
+
const resp = await client.send<any>('echoObject', { a: 1 });
|
|
2880
|
+
expect((resp as any).data.ok).toBe(true);
|
|
2881
|
+
expect((resp as any).data.received).toEqual({ a: 1 });
|
|
2882
|
+
|
|
2883
|
+
client.destroy();
|
|
2884
|
+
server.destroy();
|
|
2885
|
+
cleanupIframe(iframe);
|
|
2886
|
+
});
|
|
2887
|
+
|
|
2888
|
+
it('should send string and server receives text/plain Content-Type', async () => {
|
|
2889
|
+
const origin = 'https://example.com';
|
|
2890
|
+
const iframe = createTestIframe(origin);
|
|
2891
|
+
|
|
2892
|
+
const mockContentWindow: any = {
|
|
2893
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
2894
|
+
window.dispatchEvent(
|
|
2895
|
+
new MessageEvent('message', {
|
|
2896
|
+
data: msg,
|
|
2897
|
+
origin,
|
|
2898
|
+
source: mockContentWindow as any
|
|
2899
|
+
})
|
|
2900
|
+
);
|
|
2901
|
+
})
|
|
2902
|
+
};
|
|
2903
|
+
Object.defineProperty(iframe, 'contentWindow', { value: mockContentWindow, writable: true });
|
|
2904
|
+
|
|
2905
|
+
const client = requestIframeClient(iframe);
|
|
2906
|
+
const server = requestIframeServer();
|
|
2907
|
+
|
|
2908
|
+
server.on('echoText', (req, res) => {
|
|
2909
|
+
expect(req.headers[HttpHeader.CONTENT_TYPE]).toContain('text/plain');
|
|
2910
|
+
res.send({ received: req.body, type: typeof req.body });
|
|
2911
|
+
});
|
|
2912
|
+
|
|
2913
|
+
const resp = await client.send<any>('echoText', 'hello');
|
|
2914
|
+
expect((resp as any).data.received).toBe('hello');
|
|
2915
|
+
expect((resp as any).data.type).toBe('string');
|
|
2916
|
+
|
|
2917
|
+
client.destroy();
|
|
2918
|
+
server.destroy();
|
|
2919
|
+
cleanupIframe(iframe);
|
|
2920
|
+
});
|
|
2921
|
+
|
|
2922
|
+
it('should send URLSearchParams and server receives correct Content-Type', async () => {
|
|
2923
|
+
const origin = 'https://example.com';
|
|
2924
|
+
const iframe = createTestIframe(origin);
|
|
2925
|
+
|
|
2926
|
+
const mockContentWindow: any = {
|
|
2927
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
2928
|
+
window.dispatchEvent(
|
|
2929
|
+
new MessageEvent('message', {
|
|
2930
|
+
data: msg,
|
|
2931
|
+
origin,
|
|
2932
|
+
source: mockContentWindow as any
|
|
2933
|
+
})
|
|
2934
|
+
);
|
|
2935
|
+
})
|
|
2936
|
+
};
|
|
2937
|
+
Object.defineProperty(iframe, 'contentWindow', { value: mockContentWindow, writable: true });
|
|
2938
|
+
|
|
2939
|
+
const client = requestIframeClient(iframe);
|
|
2940
|
+
const server = requestIframeServer();
|
|
2941
|
+
|
|
2942
|
+
server.on('echoParams', (req, res) => {
|
|
2943
|
+
expect(req.headers[HttpHeader.CONTENT_TYPE]).toBe('application/x-www-form-urlencoded');
|
|
2944
|
+
// URLSearchParams should be structured-cloneable in modern browsers
|
|
2945
|
+
const value = req.body?.toString?.() ?? String(req.body);
|
|
2946
|
+
res.send({ received: value });
|
|
2947
|
+
});
|
|
2948
|
+
|
|
2949
|
+
const params = new URLSearchParams({ a: '1', b: '2' });
|
|
2950
|
+
const resp = await client.send<any>('echoParams', params as any);
|
|
2951
|
+
expect((resp as any).data.received).toContain('a=1');
|
|
2952
|
+
expect((resp as any).data.received).toContain('b=2');
|
|
2953
|
+
|
|
2954
|
+
client.destroy();
|
|
2955
|
+
server.destroy();
|
|
2956
|
+
cleanupIframe(iframe);
|
|
2957
|
+
});
|
|
2958
|
+
|
|
2959
|
+
it('should auto-dispatch File/Blob body to client.sendFile and server receives file via stream (autoResolve)', async () => {
|
|
2960
|
+
const origin = 'https://example.com';
|
|
2961
|
+
const iframe = createTestIframe(origin);
|
|
2962
|
+
|
|
2963
|
+
const mockContentWindow: any = {
|
|
2964
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
2965
|
+
window.dispatchEvent(
|
|
2966
|
+
new MessageEvent('message', {
|
|
2967
|
+
data: msg,
|
|
2968
|
+
origin,
|
|
2969
|
+
source: mockContentWindow as any
|
|
2970
|
+
})
|
|
2971
|
+
);
|
|
2972
|
+
})
|
|
2973
|
+
};
|
|
2974
|
+
Object.defineProperty(iframe, 'contentWindow', { value: mockContentWindow, writable: true });
|
|
2975
|
+
|
|
2976
|
+
const client = requestIframeClient(iframe);
|
|
2977
|
+
const server = requestIframeServer();
|
|
2978
|
+
|
|
2979
|
+
server.on('uploadFile', async (req, res) => {
|
|
2980
|
+
expect(req.body).toBeDefined();
|
|
2981
|
+
const blob = req.body as Blob;
|
|
2982
|
+
const text = await blobToText(blob);
|
|
2983
|
+
res.send({ ok: true, text });
|
|
2984
|
+
});
|
|
2985
|
+
|
|
2986
|
+
const blob = new Blob(['Hello Upload'], { type: 'text/plain' });
|
|
2987
|
+
const resp = await client.send<any>('uploadFile', blob);
|
|
2988
|
+
expect((resp as any).data.ok).toBe(true);
|
|
2989
|
+
expect((resp as any).data.text).toBe('Hello Upload');
|
|
2990
|
+
|
|
2991
|
+
client.destroy();
|
|
2992
|
+
server.destroy();
|
|
2993
|
+
cleanupIframe(iframe);
|
|
2994
|
+
});
|
|
2995
|
+
|
|
2996
|
+
it('should send stream from client to server and server receives req.stream', async () => {
|
|
2997
|
+
const origin = 'https://example.com';
|
|
2998
|
+
const iframe = createTestIframe(origin);
|
|
2999
|
+
|
|
3000
|
+
const mockContentWindow: any = {
|
|
3001
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
3002
|
+
window.dispatchEvent(
|
|
3003
|
+
new MessageEvent('message', {
|
|
3004
|
+
data: msg,
|
|
3005
|
+
origin,
|
|
3006
|
+
source: mockContentWindow as any
|
|
3007
|
+
})
|
|
3008
|
+
);
|
|
3009
|
+
})
|
|
3010
|
+
};
|
|
3011
|
+
Object.defineProperty(iframe, 'contentWindow', { value: mockContentWindow, writable: true });
|
|
3012
|
+
|
|
3013
|
+
const client = requestIframeClient(iframe);
|
|
3014
|
+
const server = requestIframeServer();
|
|
3015
|
+
|
|
3016
|
+
server.on('uploadStream', async (req, res) => {
|
|
3017
|
+
expect(req.stream).toBeDefined();
|
|
3018
|
+
const chunks: any[] = [];
|
|
3019
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3020
|
+
for await (const chunk of req.stream as any) {
|
|
3021
|
+
chunks.push(chunk);
|
|
3022
|
+
}
|
|
3023
|
+
res.send({ chunks });
|
|
3024
|
+
});
|
|
3025
|
+
|
|
3026
|
+
const stream = new IframeWritableStream({
|
|
3027
|
+
iterator: async function* () {
|
|
3028
|
+
yield 'c1';
|
|
3029
|
+
yield 'c2';
|
|
3030
|
+
yield 'c3';
|
|
3031
|
+
}
|
|
3032
|
+
});
|
|
3033
|
+
|
|
3034
|
+
const resp = await client.sendStream<any>('uploadStream', stream);
|
|
3035
|
+
expect((resp as any).data.chunks).toEqual(['c1', 'c2', 'c3']);
|
|
3036
|
+
|
|
3037
|
+
client.destroy();
|
|
3038
|
+
server.destroy();
|
|
3039
|
+
cleanupIframe(iframe);
|
|
3040
|
+
});
|
|
3041
|
+
|
|
3042
|
+
it('should support client.sendFile with autoResolve (server receives File/Blob in req.body)', async () => {
|
|
3043
|
+
const origin = 'https://example.com';
|
|
3044
|
+
const iframe = createTestIframe(origin);
|
|
3045
|
+
|
|
3046
|
+
const mockContentWindow: any = {
|
|
3047
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
3048
|
+
window.dispatchEvent(
|
|
3049
|
+
new MessageEvent('message', {
|
|
3050
|
+
data: msg,
|
|
3051
|
+
origin,
|
|
3052
|
+
source: mockContentWindow as any
|
|
3053
|
+
})
|
|
3054
|
+
);
|
|
3055
|
+
})
|
|
3056
|
+
};
|
|
3057
|
+
Object.defineProperty(iframe, 'contentWindow', { value: mockContentWindow, writable: true });
|
|
3058
|
+
|
|
3059
|
+
const client = requestIframeClient(iframe);
|
|
3060
|
+
const server = requestIframeServer();
|
|
3061
|
+
|
|
3062
|
+
server.on('uploadFileStream', async (req, res) => {
|
|
3063
|
+
// autoResolve: server should get File/Blob directly
|
|
3064
|
+
expect(req.body).toBeDefined();
|
|
3065
|
+
expect(req.stream).toBeUndefined();
|
|
3066
|
+
const blob = req.body as Blob;
|
|
3067
|
+
const text = await blobToText(blob);
|
|
3068
|
+
res.send({ ok: true, text });
|
|
3069
|
+
});
|
|
3070
|
+
|
|
3071
|
+
const blob = new Blob(['Hello Upload Stream'], { type: 'text/plain' });
|
|
3072
|
+
const resp = await client.sendFile<any>('uploadFileStream', blob, {
|
|
3073
|
+
autoResolve: true,
|
|
3074
|
+
mimeType: 'text/plain',
|
|
3075
|
+
fileName: 'upload.txt'
|
|
3076
|
+
});
|
|
3077
|
+
expect((resp as any).data.ok).toBe(true);
|
|
3078
|
+
expect((resp as any).data.text).toBe('Hello Upload Stream');
|
|
3079
|
+
|
|
3080
|
+
client.destroy();
|
|
3081
|
+
server.destroy();
|
|
3082
|
+
cleanupIframe(iframe);
|
|
3083
|
+
});
|
|
3084
|
+
});
|
|
3085
|
+
|
|
2136
3086
|
describe('Stream response', () => {
|
|
2137
3087
|
it('should support sendStream', async () => {
|
|
2138
3088
|
const origin = 'https://example.com';
|