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.
- package/QUICKSTART.CN.md +269 -0
- package/QUICKSTART.md +269 -0
- package/README.CN.md +1369 -0
- package/README.md +1016 -0
- package/library/__tests__/interceptors.test.ts +124 -0
- package/library/__tests__/requestIframe.test.ts +2216 -0
- package/library/__tests__/stream.test.ts +650 -0
- package/library/__tests__/utils.test.ts +433 -0
- package/library/api/client.d.ts +16 -0
- package/library/api/client.d.ts.map +1 -0
- package/library/api/client.js +72 -0
- package/library/api/server.d.ts +16 -0
- package/library/api/server.d.ts.map +1 -0
- package/library/api/server.js +44 -0
- package/library/constants/index.d.ts +209 -0
- package/library/constants/index.d.ts.map +1 -0
- package/library/constants/index.js +260 -0
- package/library/constants/messages.d.ts +80 -0
- package/library/constants/messages.d.ts.map +1 -0
- package/library/constants/messages.js +123 -0
- package/library/core/client.d.ts +99 -0
- package/library/core/client.d.ts.map +1 -0
- package/library/core/client.js +440 -0
- package/library/core/message-handler.d.ts +110 -0
- package/library/core/message-handler.d.ts.map +1 -0
- package/library/core/message-handler.js +320 -0
- package/library/core/request-response.d.ts +59 -0
- package/library/core/request-response.d.ts.map +1 -0
- package/library/core/request-response.js +337 -0
- package/library/core/request.d.ts +17 -0
- package/library/core/request.d.ts.map +1 -0
- package/library/core/request.js +34 -0
- package/library/core/response.d.ts +51 -0
- package/library/core/response.d.ts.map +1 -0
- package/library/core/response.js +323 -0
- package/library/core/server-base.d.ts +86 -0
- package/library/core/server-base.d.ts.map +1 -0
- package/library/core/server-base.js +257 -0
- package/library/core/server-client.d.ts +99 -0
- package/library/core/server-client.d.ts.map +1 -0
- package/library/core/server-client.js +256 -0
- package/library/core/server.d.ts +82 -0
- package/library/core/server.d.ts.map +1 -0
- package/library/core/server.js +338 -0
- package/library/index.d.ts +16 -0
- package/library/index.d.ts.map +1 -0
- package/library/index.js +211 -0
- package/library/interceptors/index.d.ts +41 -0
- package/library/interceptors/index.d.ts.map +1 -0
- package/library/interceptors/index.js +126 -0
- package/library/message/channel.d.ts +107 -0
- package/library/message/channel.d.ts.map +1 -0
- package/library/message/channel.js +184 -0
- package/library/message/dispatcher.d.ts +119 -0
- package/library/message/dispatcher.d.ts.map +1 -0
- package/library/message/dispatcher.js +249 -0
- package/library/message/index.d.ts +5 -0
- package/library/message/index.d.ts.map +1 -0
- package/library/message/index.js +25 -0
- package/library/stream/file-stream.d.ts +48 -0
- package/library/stream/file-stream.d.ts.map +1 -0
- package/library/stream/file-stream.js +240 -0
- package/library/stream/index.d.ts +15 -0
- package/library/stream/index.d.ts.map +1 -0
- package/library/stream/index.js +83 -0
- package/library/stream/readable-stream.d.ts +83 -0
- package/library/stream/readable-stream.d.ts.map +1 -0
- package/library/stream/readable-stream.js +249 -0
- package/library/stream/types.d.ts +165 -0
- package/library/stream/types.d.ts.map +1 -0
- package/library/stream/types.js +5 -0
- package/library/stream/writable-stream.d.ts +60 -0
- package/library/stream/writable-stream.d.ts.map +1 -0
- package/library/stream/writable-stream.js +348 -0
- package/library/types/index.d.ts +408 -0
- package/library/types/index.d.ts.map +1 -0
- package/library/types/index.js +5 -0
- package/library/utils/cache.d.ts +19 -0
- package/library/utils/cache.d.ts.map +1 -0
- package/library/utils/cache.js +83 -0
- package/library/utils/cookie.d.ts +117 -0
- package/library/utils/cookie.d.ts.map +1 -0
- package/library/utils/cookie.js +365 -0
- package/library/utils/debug.d.ts +11 -0
- package/library/utils/debug.d.ts.map +1 -0
- package/library/utils/debug.js +162 -0
- package/library/utils/index.d.ts +13 -0
- package/library/utils/index.d.ts.map +1 -0
- package/library/utils/index.js +132 -0
- package/library/utils/path-match.d.ts +17 -0
- package/library/utils/path-match.d.ts.map +1 -0
- package/library/utils/path-match.js +90 -0
- package/library/utils/protocol.d.ts +61 -0
- package/library/utils/protocol.d.ts.map +1 -0
- package/library/utils/protocol.js +169 -0
- package/package.json +58 -0
|
@@ -0,0 +1,2216 @@
|
|
|
1
|
+
import { requestIframeClient, clearRequestIframeClientCache } from '../api/client';
|
|
2
|
+
import { requestIframeServer, clearRequestIframeServerCache } from '../api/server';
|
|
3
|
+
import { RequestConfig, Response, ErrorResponse, PostMessageData } from '../types';
|
|
4
|
+
import { HttpHeader, Messages } 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('requestIframeClient and requestIframeServer', () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
// Clear all caches
|
|
28
|
+
clearRequestIframeClientCache();
|
|
29
|
+
clearRequestIframeServerCache();
|
|
30
|
+
// Clear all iframes
|
|
31
|
+
document.querySelectorAll('iframe').forEach((iframe) => {
|
|
32
|
+
if (iframe.parentNode) {
|
|
33
|
+
iframe.parentNode.removeChild(iframe);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
// Clear all caches
|
|
40
|
+
clearRequestIframeClientCache();
|
|
41
|
+
clearRequestIframeServerCache();
|
|
42
|
+
// Clear all iframes
|
|
43
|
+
document.querySelectorAll('iframe').forEach((iframe) => {
|
|
44
|
+
if (iframe.parentNode) {
|
|
45
|
+
iframe.parentNode.removeChild(iframe);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('Basic functionality', () => {
|
|
51
|
+
it('should send request and receive response', async () => {
|
|
52
|
+
const origin = 'https://example.com';
|
|
53
|
+
const iframe = createTestIframe(origin);
|
|
54
|
+
|
|
55
|
+
// Mock iframe response
|
|
56
|
+
const mockContentWindow = {
|
|
57
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
58
|
+
// Simulate server handling request
|
|
59
|
+
if (msg.type === 'request') {
|
|
60
|
+
// Send ACK first
|
|
61
|
+
window.dispatchEvent(
|
|
62
|
+
new MessageEvent('message', {
|
|
63
|
+
data: {
|
|
64
|
+
__requestIframe__: 1,
|
|
65
|
+
type: 'ack',
|
|
66
|
+
requestId: msg.requestId,
|
|
67
|
+
path: msg.path
|
|
68
|
+
},
|
|
69
|
+
origin
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
// Then send response
|
|
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
|
+
},
|
|
84
|
+
origin
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
}, 10);
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
};
|
|
91
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
92
|
+
value: mockContentWindow,
|
|
93
|
+
writable: true
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const client = requestIframeClient(iframe);
|
|
97
|
+
const server = requestIframeServer();
|
|
98
|
+
|
|
99
|
+
server.on('test', (req, res) => {
|
|
100
|
+
res.send({ result: 'success' });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const response = await client.send('test', { param: 'value' }, { ackTimeout: 1000 });
|
|
104
|
+
expect(response.data).toEqual({ result: 'success' });
|
|
105
|
+
expect(response.status).toBe(200);
|
|
106
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalled();
|
|
107
|
+
server.destroy();
|
|
108
|
+
cleanupIframe(iframe);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should throw error when iframe.contentWindow is unavailable', () => {
|
|
112
|
+
const iframe = document.createElement('iframe');
|
|
113
|
+
iframe.src = 'https://example.com/test.html';
|
|
114
|
+
document.body.appendChild(iframe);
|
|
115
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
116
|
+
value: null,
|
|
117
|
+
writable: true
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(() => requestIframeClient(iframe)).toThrow();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should throw error on connection timeout', async () => {
|
|
124
|
+
const origin = 'https://example.com';
|
|
125
|
+
const iframe = createTestIframe(origin);
|
|
126
|
+
|
|
127
|
+
const mockContentWindow = {
|
|
128
|
+
postMessage: jest.fn()
|
|
129
|
+
};
|
|
130
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
131
|
+
value: mockContentWindow,
|
|
132
|
+
writable: true
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const client = requestIframeClient(iframe);
|
|
136
|
+
const server = requestIframeServer();
|
|
137
|
+
|
|
138
|
+
await expect(
|
|
139
|
+
client.send('test', undefined, { ackTimeout: 100 })
|
|
140
|
+
).rejects.toMatchObject({
|
|
141
|
+
code: 'ACK_TIMEOUT'
|
|
142
|
+
});
|
|
143
|
+
cleanupIframe(iframe);
|
|
144
|
+
server.destroy();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should support isConnect method to check server availability', async () => {
|
|
148
|
+
const origin = 'https://example.com';
|
|
149
|
+
const iframe = createTestIframe(origin);
|
|
150
|
+
|
|
151
|
+
const mockContentWindow = {
|
|
152
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
153
|
+
if (msg.type === 'ping') {
|
|
154
|
+
window.dispatchEvent(
|
|
155
|
+
new MessageEvent('message', {
|
|
156
|
+
data: {
|
|
157
|
+
__requestIframe__: 1,
|
|
158
|
+
type: 'pong',
|
|
159
|
+
requestId: msg.requestId,
|
|
160
|
+
secretKey: msg.secretKey
|
|
161
|
+
},
|
|
162
|
+
origin
|
|
163
|
+
})
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
};
|
|
168
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
169
|
+
value: mockContentWindow,
|
|
170
|
+
writable: true
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const client = requestIframeClient(iframe);
|
|
174
|
+
const server = requestIframeServer();
|
|
175
|
+
|
|
176
|
+
const connected = await client.isConnect();
|
|
177
|
+
expect(connected).toBe(true);
|
|
178
|
+
|
|
179
|
+
server.destroy();
|
|
180
|
+
cleanupIframe(iframe);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('Interceptors', () => {
|
|
185
|
+
it('should support request interceptors', async () => {
|
|
186
|
+
const origin = 'https://example.com';
|
|
187
|
+
const iframe = createTestIframe(origin);
|
|
188
|
+
|
|
189
|
+
const mockContentWindow = {
|
|
190
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
191
|
+
if (msg.type === 'request') {
|
|
192
|
+
// Verify interceptor is effective
|
|
193
|
+
expect(msg.body).toHaveProperty('intercepted', true);
|
|
194
|
+
|
|
195
|
+
// Send ACK first
|
|
196
|
+
window.dispatchEvent(
|
|
197
|
+
new MessageEvent('message', {
|
|
198
|
+
data: {
|
|
199
|
+
__requestIframe__: 1,
|
|
200
|
+
type: 'ack',
|
|
201
|
+
requestId: msg.requestId,
|
|
202
|
+
path: msg.path
|
|
203
|
+
},
|
|
204
|
+
origin
|
|
205
|
+
})
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Then send response
|
|
209
|
+
setTimeout(() => {
|
|
210
|
+
window.dispatchEvent(
|
|
211
|
+
new MessageEvent('message', {
|
|
212
|
+
data: {
|
|
213
|
+
__requestIframe__: 1,
|
|
214
|
+
type: 'response',
|
|
215
|
+
requestId: msg.requestId,
|
|
216
|
+
data: { success: true },
|
|
217
|
+
status: 200,
|
|
218
|
+
statusText: 'OK'
|
|
219
|
+
},
|
|
220
|
+
origin
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
}, 10);
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
};
|
|
227
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
228
|
+
value: mockContentWindow,
|
|
229
|
+
writable: true
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const client = requestIframeClient(iframe);
|
|
233
|
+
const server = requestIframeServer();
|
|
234
|
+
|
|
235
|
+
server.on('test', (req, res) => {
|
|
236
|
+
res.send({ success: true });
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const requestInterceptor = jest.fn((config: RequestConfig) => {
|
|
240
|
+
config.body = { ...config.body, intercepted: true };
|
|
241
|
+
return config;
|
|
242
|
+
});
|
|
243
|
+
client.interceptors.request.use(requestInterceptor);
|
|
244
|
+
|
|
245
|
+
await client.send('test', { param: 'value' }, { ackTimeout: 1000 });
|
|
246
|
+
expect(requestInterceptor).toHaveBeenCalled();
|
|
247
|
+
server.destroy();
|
|
248
|
+
cleanupIframe(iframe);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should support response interceptors', async () => {
|
|
252
|
+
const origin = 'https://example.com';
|
|
253
|
+
const iframe = createTestIframe(origin);
|
|
254
|
+
|
|
255
|
+
const mockContentWindow = {
|
|
256
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
257
|
+
if (msg.type === 'request') {
|
|
258
|
+
// Send ACK first
|
|
259
|
+
window.dispatchEvent(
|
|
260
|
+
new MessageEvent('message', {
|
|
261
|
+
data: {
|
|
262
|
+
__requestIframe__: 1,
|
|
263
|
+
type: 'ack',
|
|
264
|
+
requestId: msg.requestId,
|
|
265
|
+
path: msg.path
|
|
266
|
+
},
|
|
267
|
+
origin
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// Then send response
|
|
272
|
+
setTimeout(() => {
|
|
273
|
+
window.dispatchEvent(
|
|
274
|
+
new MessageEvent('message', {
|
|
275
|
+
data: {
|
|
276
|
+
__requestIframe__: 1,
|
|
277
|
+
type: 'response',
|
|
278
|
+
requestId: msg.requestId,
|
|
279
|
+
data: { success: true },
|
|
280
|
+
status: 200,
|
|
281
|
+
statusText: 'OK'
|
|
282
|
+
},
|
|
283
|
+
origin
|
|
284
|
+
})
|
|
285
|
+
);
|
|
286
|
+
}, 10);
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
};
|
|
290
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
291
|
+
value: mockContentWindow,
|
|
292
|
+
writable: true
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const client = requestIframeClient(iframe);
|
|
296
|
+
const server = requestIframeServer();
|
|
297
|
+
|
|
298
|
+
server.on('test', (req, res) => {
|
|
299
|
+
res.send({ success: true });
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const responseInterceptor = jest.fn((response: Response) => {
|
|
303
|
+
response.data = { ...response.data, intercepted: true };
|
|
304
|
+
return response;
|
|
305
|
+
});
|
|
306
|
+
client.interceptors.response.use(responseInterceptor as any);
|
|
307
|
+
|
|
308
|
+
const response = await client.send('test', undefined, { ackTimeout: 1000 });
|
|
309
|
+
expect(response.data).toHaveProperty('intercepted', true);
|
|
310
|
+
expect(responseInterceptor).toHaveBeenCalled();
|
|
311
|
+
server.destroy();
|
|
312
|
+
cleanupIframe(iframe);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe('Error handling', () => {
|
|
317
|
+
it('should handle error response correctly', async () => {
|
|
318
|
+
const origin = 'https://example.com';
|
|
319
|
+
const iframe = createTestIframe(origin);
|
|
320
|
+
|
|
321
|
+
const mockContentWindow = {
|
|
322
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
323
|
+
if (msg.type === 'request') {
|
|
324
|
+
// Send ACK first
|
|
325
|
+
window.dispatchEvent(
|
|
326
|
+
new MessageEvent('message', {
|
|
327
|
+
data: {
|
|
328
|
+
__requestIframe__: 1,
|
|
329
|
+
type: 'ack',
|
|
330
|
+
requestId: msg.requestId,
|
|
331
|
+
path: msg.path
|
|
332
|
+
},
|
|
333
|
+
origin
|
|
334
|
+
})
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// Then send error response
|
|
338
|
+
setTimeout(() => {
|
|
339
|
+
window.dispatchEvent(
|
|
340
|
+
new MessageEvent('message', {
|
|
341
|
+
data: {
|
|
342
|
+
__requestIframe__: 1,
|
|
343
|
+
type: 'error',
|
|
344
|
+
requestId: msg.requestId,
|
|
345
|
+
error: {
|
|
346
|
+
message: 'Method not found',
|
|
347
|
+
code: 'METHOD_NOT_FOUND'
|
|
348
|
+
},
|
|
349
|
+
status: 404,
|
|
350
|
+
statusText: 'Not Found'
|
|
351
|
+
},
|
|
352
|
+
origin
|
|
353
|
+
})
|
|
354
|
+
);
|
|
355
|
+
}, 10);
|
|
356
|
+
}
|
|
357
|
+
})
|
|
358
|
+
};
|
|
359
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
360
|
+
value: mockContentWindow,
|
|
361
|
+
writable: true
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const client = requestIframeClient(iframe);
|
|
365
|
+
const server = requestIframeServer();
|
|
366
|
+
|
|
367
|
+
// No handler registered, should return METHOD_NOT_FOUND
|
|
368
|
+
await expect(client.send('test', undefined, { ackTimeout: 1000 })).rejects.toMatchObject({
|
|
369
|
+
code: 'METHOD_NOT_FOUND',
|
|
370
|
+
response: { status: 404 }
|
|
371
|
+
});
|
|
372
|
+
cleanupIframe(iframe);
|
|
373
|
+
server.destroy();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
describe('Async tasks', () => {
|
|
378
|
+
it('should support async task handling', async () => {
|
|
379
|
+
const origin = 'https://example.com';
|
|
380
|
+
const iframe = createTestIframe(origin);
|
|
381
|
+
|
|
382
|
+
const mockContentWindow = {
|
|
383
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
384
|
+
if (msg.type === 'request') {
|
|
385
|
+
// Send ACK first
|
|
386
|
+
window.dispatchEvent(
|
|
387
|
+
new MessageEvent('message', {
|
|
388
|
+
data: {
|
|
389
|
+
__requestIframe__: 1,
|
|
390
|
+
type: 'ack',
|
|
391
|
+
requestId: msg.requestId,
|
|
392
|
+
path: msg.path
|
|
393
|
+
},
|
|
394
|
+
origin
|
|
395
|
+
})
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
// Send async notification
|
|
399
|
+
setTimeout(() => {
|
|
400
|
+
window.dispatchEvent(
|
|
401
|
+
new MessageEvent('message', {
|
|
402
|
+
data: {
|
|
403
|
+
__requestIframe__: 1,
|
|
404
|
+
type: 'async',
|
|
405
|
+
requestId: msg.requestId,
|
|
406
|
+
path: msg.path
|
|
407
|
+
},
|
|
408
|
+
origin
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
}, 10);
|
|
412
|
+
|
|
413
|
+
// Delay response (simulate async processing)
|
|
414
|
+
setTimeout(() => {
|
|
415
|
+
window.dispatchEvent(
|
|
416
|
+
new MessageEvent('message', {
|
|
417
|
+
data: {
|
|
418
|
+
__requestIframe__: 1,
|
|
419
|
+
type: 'response',
|
|
420
|
+
requestId: msg.requestId,
|
|
421
|
+
data: { result: 'async success' },
|
|
422
|
+
status: 200,
|
|
423
|
+
statusText: 'OK'
|
|
424
|
+
},
|
|
425
|
+
origin
|
|
426
|
+
})
|
|
427
|
+
);
|
|
428
|
+
}, 100);
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
};
|
|
432
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
433
|
+
value: mockContentWindow,
|
|
434
|
+
writable: true
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const client = requestIframeClient(iframe);
|
|
438
|
+
const server = requestIframeServer();
|
|
439
|
+
|
|
440
|
+
server.on('asyncTest', async (req, res) => {
|
|
441
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
442
|
+
res.send({ result: 'async success' });
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const response = await client.send('asyncTest', undefined, {
|
|
446
|
+
ackTimeout: 1000,
|
|
447
|
+
timeout: 200,
|
|
448
|
+
asyncTimeout: 5000
|
|
449
|
+
});
|
|
450
|
+
expect(response.data).toEqual({ result: 'async success' });
|
|
451
|
+
server.destroy();
|
|
452
|
+
cleanupIframe(iframe);
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
describe('MessageChannel sharing', () => {
|
|
457
|
+
it('should share the same message channel for the same secretKey', () => {
|
|
458
|
+
const iframe1 = createTestIframe('https://example.com');
|
|
459
|
+
const iframe2 = createTestIframe('https://example2.com');
|
|
460
|
+
|
|
461
|
+
const server1 = requestIframeServer({ secretKey: 'demo' });
|
|
462
|
+
const server2 = requestIframeServer({ secretKey: 'demo' });
|
|
463
|
+
|
|
464
|
+
// Server instances are different
|
|
465
|
+
expect(server1).not.toBe(server2);
|
|
466
|
+
|
|
467
|
+
// But they should share the same underlying message channel (verified by secretKey)
|
|
468
|
+
expect(server1.secretKey).toBe(server2.secretKey);
|
|
469
|
+
expect(server1.secretKey).toBe('demo');
|
|
470
|
+
|
|
471
|
+
server1.destroy();
|
|
472
|
+
server2.destroy();
|
|
473
|
+
cleanupIframe(iframe1);
|
|
474
|
+
cleanupIframe(iframe2);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('should have independent message channels for different secretKeys', () => {
|
|
478
|
+
const iframe = createTestIframe('https://example.com');
|
|
479
|
+
|
|
480
|
+
const server1 = requestIframeServer({ secretKey: 'demo1' });
|
|
481
|
+
const server2 = requestIframeServer({ secretKey: 'demo2' });
|
|
482
|
+
|
|
483
|
+
// Verify different server instances
|
|
484
|
+
expect(server1).not.toBe(server2);
|
|
485
|
+
|
|
486
|
+
// secretKeys are different
|
|
487
|
+
expect(server1.secretKey).toBe('demo1');
|
|
488
|
+
expect(server2.secretKey).toBe('demo2');
|
|
489
|
+
|
|
490
|
+
server1.destroy();
|
|
491
|
+
server2.destroy();
|
|
492
|
+
cleanupIframe(iframe);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('should share the same message channel when no secretKey', () => {
|
|
496
|
+
const iframe1 = createTestIframe('https://example.com');
|
|
497
|
+
const iframe2 = createTestIframe('https://example2.com');
|
|
498
|
+
|
|
499
|
+
const server1 = requestIframeServer();
|
|
500
|
+
const server2 = requestIframeServer();
|
|
501
|
+
|
|
502
|
+
// Server instances are different
|
|
503
|
+
expect(server1).not.toBe(server2);
|
|
504
|
+
|
|
505
|
+
// But they should share the same underlying message channel (both have no secretKey)
|
|
506
|
+
expect(server1.secretKey).toBe(server2.secretKey);
|
|
507
|
+
expect(server1.secretKey).toBeUndefined();
|
|
508
|
+
|
|
509
|
+
server1.destroy();
|
|
510
|
+
server2.destroy();
|
|
511
|
+
cleanupIframe(iframe1);
|
|
512
|
+
cleanupIframe(iframe2);
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
describe('Middleware', () => {
|
|
517
|
+
it('should support global middleware', async () => {
|
|
518
|
+
const origin = 'https://example.com';
|
|
519
|
+
const iframe = createTestIframe(origin);
|
|
520
|
+
|
|
521
|
+
const mockContentWindow = {
|
|
522
|
+
postMessage: jest.fn()
|
|
523
|
+
};
|
|
524
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
525
|
+
value: mockContentWindow,
|
|
526
|
+
writable: true
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const client = requestIframeClient(iframe);
|
|
530
|
+
const server = requestIframeServer();
|
|
531
|
+
|
|
532
|
+
// Add middleware (auth validation)
|
|
533
|
+
const middleware = jest.fn((req, res, next) => {
|
|
534
|
+
if (req.headers['authorization'] === 'Bearer token123') {
|
|
535
|
+
next();
|
|
536
|
+
} else {
|
|
537
|
+
res.status(401).send({ error: 'Unauthorized' });
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
server.use(middleware);
|
|
542
|
+
|
|
543
|
+
server.on('test', (req, res) => {
|
|
544
|
+
res.send({ result: 'success' });
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// Simulate request from iframe to current window (unauthorized)
|
|
548
|
+
const requestId1 = 'req-unauthorized';
|
|
549
|
+
window.dispatchEvent(
|
|
550
|
+
new MessageEvent('message', {
|
|
551
|
+
data: {
|
|
552
|
+
__requestIframe__: 1,
|
|
553
|
+
type: 'request',
|
|
554
|
+
requestId: requestId1,
|
|
555
|
+
path: 'test',
|
|
556
|
+
body: {},
|
|
557
|
+
headers: {}
|
|
558
|
+
},
|
|
559
|
+
origin,
|
|
560
|
+
source: mockContentWindow as any
|
|
561
|
+
})
|
|
562
|
+
);
|
|
563
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
564
|
+
|
|
565
|
+
// Verify middleware was called
|
|
566
|
+
expect(middleware).toHaveBeenCalled();
|
|
567
|
+
|
|
568
|
+
// Find response (should be 401 or contain Unauthorized)
|
|
569
|
+
const ackCall = mockContentWindow.postMessage.mock.calls.find(
|
|
570
|
+
(call: any[]) => call[0]?.type === 'ack'
|
|
571
|
+
);
|
|
572
|
+
expect(ackCall).toBeDefined();
|
|
573
|
+
|
|
574
|
+
const errorCall = mockContentWindow.postMessage.mock.calls.find(
|
|
575
|
+
(call: any[]) => {
|
|
576
|
+
const msg = call[0];
|
|
577
|
+
return (msg?.type === 'error' || msg?.type === 'response') &&
|
|
578
|
+
(msg?.status === 401 || msg?.data?.error === 'Unauthorized');
|
|
579
|
+
}
|
|
580
|
+
);
|
|
581
|
+
expect(errorCall).toBeDefined();
|
|
582
|
+
|
|
583
|
+
// Test authorized request
|
|
584
|
+
const requestId2 = 'req-authorized';
|
|
585
|
+
window.dispatchEvent(
|
|
586
|
+
new MessageEvent('message', {
|
|
587
|
+
data: {
|
|
588
|
+
__requestIframe__: 1,
|
|
589
|
+
type: 'request',
|
|
590
|
+
requestId: requestId2,
|
|
591
|
+
path: 'test',
|
|
592
|
+
body: {},
|
|
593
|
+
headers: { authorization: 'Bearer token123' }
|
|
594
|
+
},
|
|
595
|
+
origin,
|
|
596
|
+
source: mockContentWindow as any
|
|
597
|
+
})
|
|
598
|
+
);
|
|
599
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
600
|
+
|
|
601
|
+
// Verify authorized request succeeded
|
|
602
|
+
const successCall = mockContentWindow.postMessage.mock.calls.find(
|
|
603
|
+
(call: any[]) => call[0]?.type === 'response' && call[0]?.status === 200
|
|
604
|
+
);
|
|
605
|
+
expect(successCall).toBeDefined();
|
|
606
|
+
expect(successCall[0].data).toEqual({ result: 'success' });
|
|
607
|
+
|
|
608
|
+
server.destroy();
|
|
609
|
+
cleanupIframe(iframe);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
it('should support path-matching middleware', async () => {
|
|
613
|
+
const origin = 'https://example.com';
|
|
614
|
+
const iframe = createTestIframe(origin);
|
|
615
|
+
|
|
616
|
+
const mockContentWindow = {
|
|
617
|
+
postMessage: jest.fn()
|
|
618
|
+
};
|
|
619
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
620
|
+
value: mockContentWindow,
|
|
621
|
+
writable: true
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
const client = requestIframeClient(iframe);
|
|
625
|
+
const server = requestIframeServer();
|
|
626
|
+
|
|
627
|
+
const apiMiddleware = jest.fn((req, res, next) => {
|
|
628
|
+
next();
|
|
629
|
+
});
|
|
630
|
+
const otherMiddleware = jest.fn((req, res, next) => {
|
|
631
|
+
next();
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Apply middleware only to /api path
|
|
635
|
+
server.use('/api', apiMiddleware);
|
|
636
|
+
server.use('/other', otherMiddleware);
|
|
637
|
+
|
|
638
|
+
server.on('api/test', (req, res) => {
|
|
639
|
+
res.send({ result: 'api success' });
|
|
640
|
+
});
|
|
641
|
+
server.on('other/test', (req, res) => {
|
|
642
|
+
res.send({ result: 'other success' });
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// Test /api/test path
|
|
646
|
+
const requestId1 = 'req-api';
|
|
647
|
+
window.dispatchEvent(
|
|
648
|
+
new MessageEvent('message', {
|
|
649
|
+
data: {
|
|
650
|
+
__requestIframe__: 1,
|
|
651
|
+
type: 'request',
|
|
652
|
+
requestId: requestId1,
|
|
653
|
+
path: 'api/test',
|
|
654
|
+
body: {}
|
|
655
|
+
},
|
|
656
|
+
origin,
|
|
657
|
+
source: mockContentWindow as any
|
|
658
|
+
})
|
|
659
|
+
);
|
|
660
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
661
|
+
|
|
662
|
+
// Verify apiMiddleware was called, otherMiddleware was not
|
|
663
|
+
expect(apiMiddleware).toHaveBeenCalled();
|
|
664
|
+
expect(otherMiddleware).not.toHaveBeenCalled();
|
|
665
|
+
|
|
666
|
+
// Test /other/test path
|
|
667
|
+
const requestId2 = 'req-other';
|
|
668
|
+
window.dispatchEvent(
|
|
669
|
+
new MessageEvent('message', {
|
|
670
|
+
data: {
|
|
671
|
+
__requestIframe__: 1,
|
|
672
|
+
type: 'request',
|
|
673
|
+
requestId: requestId2,
|
|
674
|
+
path: 'other/test',
|
|
675
|
+
body: {}
|
|
676
|
+
},
|
|
677
|
+
origin,
|
|
678
|
+
source: mockContentWindow as any
|
|
679
|
+
})
|
|
680
|
+
);
|
|
681
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
682
|
+
|
|
683
|
+
// Verify otherMiddleware was called
|
|
684
|
+
expect(otherMiddleware).toHaveBeenCalled();
|
|
685
|
+
|
|
686
|
+
server.destroy();
|
|
687
|
+
cleanupIframe(iframe);
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
describe('sendFile', () => {
|
|
692
|
+
it('should support sending file (base64 encoded)', async () => {
|
|
693
|
+
const origin = 'https://example.com';
|
|
694
|
+
const iframe = createTestIframe(origin);
|
|
695
|
+
|
|
696
|
+
const mockContentWindow = {
|
|
697
|
+
postMessage: jest.fn()
|
|
698
|
+
};
|
|
699
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
700
|
+
value: mockContentWindow,
|
|
701
|
+
writable: true
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
const client = requestIframeClient(iframe);
|
|
705
|
+
const server = requestIframeServer();
|
|
706
|
+
|
|
707
|
+
server.on('getFile', async (req, res) => {
|
|
708
|
+
const fileContent = 'Hello World';
|
|
709
|
+
await res.sendFile(fileContent, {
|
|
710
|
+
mimeType: 'text/plain',
|
|
711
|
+
fileName: 'test.txt'
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
// Simulate request from iframe
|
|
716
|
+
const requestId = 'req-file';
|
|
717
|
+
window.dispatchEvent(
|
|
718
|
+
new MessageEvent('message', {
|
|
719
|
+
data: {
|
|
720
|
+
__requestIframe__: 1,
|
|
721
|
+
type: 'request',
|
|
722
|
+
requestId: requestId,
|
|
723
|
+
path: 'getFile',
|
|
724
|
+
body: {}
|
|
725
|
+
},
|
|
726
|
+
origin,
|
|
727
|
+
source: mockContentWindow as any
|
|
728
|
+
})
|
|
729
|
+
);
|
|
730
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
731
|
+
|
|
732
|
+
// Verify sendFile was called
|
|
733
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalled();
|
|
734
|
+
const fileCall = mockContentWindow.postMessage.mock.calls.find(
|
|
735
|
+
(call: any[]) => call[0]?.type === 'response' && call[0]?.fileData
|
|
736
|
+
);
|
|
737
|
+
expect(fileCall).toBeDefined();
|
|
738
|
+
expect(fileCall[0].fileData.mimeType).toBe('text/plain');
|
|
739
|
+
expect(fileCall[0].fileData.fileName).toBe('test.txt');
|
|
740
|
+
|
|
741
|
+
// Decode base64 to verify content
|
|
742
|
+
if (fileCall[0].fileData.content) {
|
|
743
|
+
const decoded = atob(fileCall[0].fileData.content);
|
|
744
|
+
expect(decoded).toBe('Hello World');
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
server.destroy();
|
|
748
|
+
cleanupIframe(iframe);
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
it('should support sending Blob file', async () => {
|
|
752
|
+
const origin = 'https://example.com';
|
|
753
|
+
const iframe = createTestIframe(origin);
|
|
754
|
+
|
|
755
|
+
const mockContentWindow = {
|
|
756
|
+
postMessage: jest.fn()
|
|
757
|
+
};
|
|
758
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
759
|
+
value: mockContentWindow,
|
|
760
|
+
writable: true
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
const server = requestIframeServer();
|
|
764
|
+
|
|
765
|
+
server.on('getBlob', async (req, res) => {
|
|
766
|
+
const blob = new Blob(['test content'], { type: 'text/plain' });
|
|
767
|
+
await res.sendFile(blob, {
|
|
768
|
+
fileName: 'blob.txt',
|
|
769
|
+
mimeType: 'text/plain'
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
const requestId = 'req-blob';
|
|
774
|
+
window.dispatchEvent(
|
|
775
|
+
new MessageEvent('message', {
|
|
776
|
+
data: {
|
|
777
|
+
__requestIframe__: 1,
|
|
778
|
+
type: 'request',
|
|
779
|
+
requestId: requestId,
|
|
780
|
+
path: 'getBlob',
|
|
781
|
+
body: {}
|
|
782
|
+
},
|
|
783
|
+
origin,
|
|
784
|
+
source: mockContentWindow as any
|
|
785
|
+
})
|
|
786
|
+
);
|
|
787
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
788
|
+
|
|
789
|
+
const fileCall = mockContentWindow.postMessage.mock.calls.find(
|
|
790
|
+
(call: any[]) => call[0]?.type === 'response' && call[0]?.fileData
|
|
791
|
+
);
|
|
792
|
+
expect(fileCall).toBeDefined();
|
|
793
|
+
expect(fileCall![0].fileData.mimeType).toBe('text/plain');
|
|
794
|
+
|
|
795
|
+
server.destroy();
|
|
796
|
+
cleanupIframe(iframe);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
it('should support sending File object', async () => {
|
|
800
|
+
const origin = 'https://example.com';
|
|
801
|
+
const iframe = createTestIframe(origin);
|
|
802
|
+
|
|
803
|
+
const mockContentWindow = {
|
|
804
|
+
postMessage: jest.fn()
|
|
805
|
+
};
|
|
806
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
807
|
+
value: mockContentWindow,
|
|
808
|
+
writable: true
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
const server = requestIframeServer();
|
|
812
|
+
|
|
813
|
+
server.on('getFileObj', async (req, res) => {
|
|
814
|
+
const file = new File(['file content'], 'test.txt', { type: 'text/plain' });
|
|
815
|
+
await res.sendFile(file);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
const requestId = 'req-fileobj';
|
|
819
|
+
window.dispatchEvent(
|
|
820
|
+
new MessageEvent('message', {
|
|
821
|
+
data: {
|
|
822
|
+
__requestIframe__: 1,
|
|
823
|
+
type: 'request',
|
|
824
|
+
requestId: requestId,
|
|
825
|
+
path: 'getFileObj',
|
|
826
|
+
body: {}
|
|
827
|
+
},
|
|
828
|
+
origin,
|
|
829
|
+
source: mockContentWindow as any
|
|
830
|
+
})
|
|
831
|
+
);
|
|
832
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
833
|
+
|
|
834
|
+
const fileCall = mockContentWindow.postMessage.mock.calls.find(
|
|
835
|
+
(call: any[]) => call[0]?.type === 'response' && call[0]?.fileData
|
|
836
|
+
);
|
|
837
|
+
expect(fileCall).toBeDefined();
|
|
838
|
+
expect(fileCall[0].fileData.fileName).toBe('test.txt');
|
|
839
|
+
|
|
840
|
+
server.destroy();
|
|
841
|
+
cleanupIframe(iframe);
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
it('should support sendFile with requireAck', async () => {
|
|
845
|
+
const origin = 'https://example.com';
|
|
846
|
+
const iframe = createTestIframe(origin);
|
|
847
|
+
|
|
848
|
+
let responseMessage: any = null;
|
|
849
|
+
const mockContentWindow = {
|
|
850
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
851
|
+
if (msg.type === 'request') {
|
|
852
|
+
window.dispatchEvent(
|
|
853
|
+
new MessageEvent('message', {
|
|
854
|
+
data: {
|
|
855
|
+
__requestIframe__: 1,
|
|
856
|
+
type: 'ack',
|
|
857
|
+
requestId: msg.requestId,
|
|
858
|
+
path: msg.path
|
|
859
|
+
},
|
|
860
|
+
origin
|
|
861
|
+
})
|
|
862
|
+
);
|
|
863
|
+
setTimeout(() => {
|
|
864
|
+
const response: PostMessageData = {
|
|
865
|
+
__requestIframe__: 1,
|
|
866
|
+
timestamp: Date.now(),
|
|
867
|
+
type: 'response',
|
|
868
|
+
requestId: msg.requestId,
|
|
869
|
+
fileData: {
|
|
870
|
+
content: btoa('test'),
|
|
871
|
+
mimeType: 'text/plain',
|
|
872
|
+
fileName: 'test.txt'
|
|
873
|
+
},
|
|
874
|
+
status: 200,
|
|
875
|
+
requireAck: true
|
|
876
|
+
};
|
|
877
|
+
responseMessage = response;
|
|
878
|
+
window.dispatchEvent(
|
|
879
|
+
new MessageEvent('message', {
|
|
880
|
+
data: response,
|
|
881
|
+
origin
|
|
882
|
+
})
|
|
883
|
+
);
|
|
884
|
+
}, 10);
|
|
885
|
+
}
|
|
886
|
+
})
|
|
887
|
+
};
|
|
888
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
889
|
+
value: mockContentWindow,
|
|
890
|
+
writable: true
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
const client = requestIframeClient(iframe);
|
|
894
|
+
const server = requestIframeServer();
|
|
895
|
+
|
|
896
|
+
server.on('getFileAck', async (req, res) => {
|
|
897
|
+
await res.sendFile('test', {
|
|
898
|
+
fileName: 'test.txt',
|
|
899
|
+
requireAck: true
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
const requestId = 'req-fileack';
|
|
904
|
+
window.dispatchEvent(
|
|
905
|
+
new MessageEvent('message', {
|
|
906
|
+
data: {
|
|
907
|
+
__requestIframe__: 1,
|
|
908
|
+
type: 'request',
|
|
909
|
+
requestId: requestId,
|
|
910
|
+
path: 'getFileAck',
|
|
911
|
+
body: {}
|
|
912
|
+
},
|
|
913
|
+
origin,
|
|
914
|
+
source: mockContentWindow as any
|
|
915
|
+
})
|
|
916
|
+
);
|
|
917
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
918
|
+
|
|
919
|
+
// Client should send received message when requireAck is true
|
|
920
|
+
const receivedCall = mockContentWindow.postMessage.mock.calls.find(
|
|
921
|
+
(call: any[]) => call[0]?.type === 'received'
|
|
922
|
+
);
|
|
923
|
+
// Note: received message is sent by client, not server
|
|
924
|
+
// So we check that the response was sent with requireAck
|
|
925
|
+
if (responseMessage && 'requireAck' in responseMessage) {
|
|
926
|
+
expect(responseMessage.requireAck).toBe(true);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
server.destroy();
|
|
930
|
+
cleanupIframe(iframe);
|
|
931
|
+
});
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
describe('server.map', () => {
|
|
935
|
+
it('should register multiple event handlers at once', async () => {
|
|
936
|
+
const origin = 'https://example.com';
|
|
937
|
+
const iframe = createTestIframe(origin);
|
|
938
|
+
|
|
939
|
+
const handlers: Record<string, jest.Mock> = {
|
|
940
|
+
'api/getUser': jest.fn(async (req, res) => {
|
|
941
|
+
res.send({ id: req.body.id, name: 'Tom' });
|
|
942
|
+
}),
|
|
943
|
+
'api/saveUser': jest.fn(async (req, res) => {
|
|
944
|
+
res.send({ success: true, saved: req.body });
|
|
945
|
+
})
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
const mockContentWindow = {
|
|
949
|
+
postMessage: jest.fn()
|
|
950
|
+
};
|
|
951
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
952
|
+
value: mockContentWindow,
|
|
953
|
+
writable: true
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
const server = requestIframeServer();
|
|
957
|
+
|
|
958
|
+
// Use map method to register multiple handlers at once
|
|
959
|
+
server.map(handlers);
|
|
960
|
+
|
|
961
|
+
// Send request
|
|
962
|
+
const requestId1 = 'req-1';
|
|
963
|
+
window.dispatchEvent(
|
|
964
|
+
new MessageEvent('message', {
|
|
965
|
+
data: {
|
|
966
|
+
__requestIframe__: 1,
|
|
967
|
+
type: 'request',
|
|
968
|
+
requestId: requestId1,
|
|
969
|
+
path: 'api/getUser',
|
|
970
|
+
body: { id: 1 }
|
|
971
|
+
},
|
|
972
|
+
origin,
|
|
973
|
+
source: mockContentWindow as any
|
|
974
|
+
})
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
978
|
+
|
|
979
|
+
// Verify first handler was called
|
|
980
|
+
expect(handlers['api/getUser']).toHaveBeenCalled();
|
|
981
|
+
const callArgs = handlers['api/getUser'].mock.calls[0];
|
|
982
|
+
expect(callArgs[0].body).toEqual({ id: 1 });
|
|
983
|
+
expect(callArgs[0].path).toBe('api/getUser');
|
|
984
|
+
expect(callArgs[0].requestId).toBe(requestId1);
|
|
985
|
+
|
|
986
|
+
// Test second handler
|
|
987
|
+
const requestId2 = 'req-2';
|
|
988
|
+
window.dispatchEvent(
|
|
989
|
+
new MessageEvent('message', {
|
|
990
|
+
data: {
|
|
991
|
+
__requestIframe__: 1,
|
|
992
|
+
type: 'request',
|
|
993
|
+
requestId: requestId2,
|
|
994
|
+
path: 'api/saveUser',
|
|
995
|
+
body: { name: 'Alice' }
|
|
996
|
+
},
|
|
997
|
+
origin,
|
|
998
|
+
source: mockContentWindow as any
|
|
999
|
+
})
|
|
1000
|
+
);
|
|
1001
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1002
|
+
|
|
1003
|
+
expect(handlers['api/saveUser']).toHaveBeenCalled();
|
|
1004
|
+
const callArgs2 = handlers['api/saveUser'].mock.calls[0];
|
|
1005
|
+
expect(callArgs2[0].body).toEqual({ name: 'Alice' });
|
|
1006
|
+
expect(callArgs2[0].path).toBe('api/saveUser');
|
|
1007
|
+
expect(callArgs2[0].requestId).toBe(requestId2);
|
|
1008
|
+
|
|
1009
|
+
server.destroy();
|
|
1010
|
+
cleanupIframe(iframe);
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
describe('Automatic cookie management', () => {
|
|
1015
|
+
it('should manually set and get cookies', async () => {
|
|
1016
|
+
const origin = 'https://example.com';
|
|
1017
|
+
const iframe = createTestIframe(origin);
|
|
1018
|
+
|
|
1019
|
+
const mockContentWindow = {
|
|
1020
|
+
postMessage: jest.fn()
|
|
1021
|
+
};
|
|
1022
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1023
|
+
value: mockContentWindow,
|
|
1024
|
+
writable: true
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
const client = requestIframeClient(iframe);
|
|
1028
|
+
|
|
1029
|
+
// Initial state should be empty
|
|
1030
|
+
expect(client.getCookies()).toEqual({});
|
|
1031
|
+
expect(client.getCookie('token')).toBeUndefined();
|
|
1032
|
+
|
|
1033
|
+
// Set cookie (default path '/')
|
|
1034
|
+
client.setCookie('token', 'abc123');
|
|
1035
|
+
client.setCookie('userId', '42');
|
|
1036
|
+
|
|
1037
|
+
// Get cookie
|
|
1038
|
+
expect(client.getCookie('token')).toBe('abc123');
|
|
1039
|
+
expect(client.getCookie('userId')).toBe('42');
|
|
1040
|
+
expect(client.getCookies()).toEqual({ token: 'abc123', userId: '42' });
|
|
1041
|
+
|
|
1042
|
+
// Remove single cookie
|
|
1043
|
+
client.removeCookie('token');
|
|
1044
|
+
expect(client.getCookie('token')).toBeUndefined();
|
|
1045
|
+
expect(client.getCookie('userId')).toBe('42');
|
|
1046
|
+
|
|
1047
|
+
// Clear all cookies
|
|
1048
|
+
client.clearCookies();
|
|
1049
|
+
expect(client.getCookies()).toEqual({});
|
|
1050
|
+
|
|
1051
|
+
cleanupIframe(iframe);
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
it('should support path-based cookie isolation', () => {
|
|
1055
|
+
const origin = 'https://example.com';
|
|
1056
|
+
const iframe = createTestIframe(origin);
|
|
1057
|
+
|
|
1058
|
+
const mockContentWindow = {
|
|
1059
|
+
postMessage: jest.fn()
|
|
1060
|
+
};
|
|
1061
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1062
|
+
value: mockContentWindow,
|
|
1063
|
+
writable: true
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
const client = requestIframeClient(iframe);
|
|
1067
|
+
|
|
1068
|
+
// Set cookies with different paths
|
|
1069
|
+
client.setCookie('globalToken', 'global_123', { path: '/' });
|
|
1070
|
+
client.setCookie('apiToken', 'api_456', { path: '/api' });
|
|
1071
|
+
client.setCookie('adminToken', 'admin_789', { path: '/admin' });
|
|
1072
|
+
|
|
1073
|
+
// Get root path cookies - should only include path='/' ones
|
|
1074
|
+
expect(client.getCookies('/')).toEqual({ globalToken: 'global_123' });
|
|
1075
|
+
|
|
1076
|
+
// Get /api path cookies - should include '/' and '/api' ones
|
|
1077
|
+
expect(client.getCookies('/api')).toEqual({
|
|
1078
|
+
globalToken: 'global_123',
|
|
1079
|
+
apiToken: 'api_456'
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
// Get /api/users path cookies - should include '/' and '/api' ones
|
|
1083
|
+
expect(client.getCookies('/api/users')).toEqual({
|
|
1084
|
+
globalToken: 'global_123',
|
|
1085
|
+
apiToken: 'api_456'
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
// Get /admin path cookies - should include '/' and '/admin' ones
|
|
1089
|
+
expect(client.getCookies('/admin')).toEqual({
|
|
1090
|
+
globalToken: 'global_123',
|
|
1091
|
+
adminToken: 'admin_789'
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
// Get all cookies
|
|
1095
|
+
expect(client.getCookies()).toEqual({
|
|
1096
|
+
globalToken: 'global_123',
|
|
1097
|
+
apiToken: 'api_456',
|
|
1098
|
+
adminToken: 'admin_789'
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
cleanupIframe(iframe);
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
it('should automatically include set cookies in requests', async () => {
|
|
1105
|
+
const origin = 'https://example.com';
|
|
1106
|
+
const iframe = createTestIframe(origin);
|
|
1107
|
+
|
|
1108
|
+
const mockContentWindow = {
|
|
1109
|
+
postMessage: jest.fn()
|
|
1110
|
+
};
|
|
1111
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1112
|
+
value: mockContentWindow,
|
|
1113
|
+
writable: true
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
const client = requestIframeClient(iframe);
|
|
1117
|
+
|
|
1118
|
+
// Pre-set cookies
|
|
1119
|
+
client.setCookie('sessionId', 'sess_123');
|
|
1120
|
+
client.setCookie('theme', 'dark');
|
|
1121
|
+
|
|
1122
|
+
// Send request (don't wait for response, just check request data)
|
|
1123
|
+
client.send('/api/test', { data: 'test' }).catch(() => {});
|
|
1124
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1125
|
+
|
|
1126
|
+
// Verify request includes pre-set cookies
|
|
1127
|
+
expect(mockContentWindow.postMessage).toHaveBeenCalled();
|
|
1128
|
+
const requestCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1129
|
+
(call: any[]) => call[0]?.type === 'request'
|
|
1130
|
+
);
|
|
1131
|
+
expect(requestCall).toBeDefined();
|
|
1132
|
+
expect(requestCall[0].cookies).toEqual({
|
|
1133
|
+
sessionId: 'sess_123',
|
|
1134
|
+
theme: 'dark'
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
cleanupIframe(iframe);
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
it('should override internal cookies with user-provided cookies', async () => {
|
|
1141
|
+
const origin = 'https://example.com';
|
|
1142
|
+
const iframe = createTestIframe(origin);
|
|
1143
|
+
|
|
1144
|
+
const mockContentWindow = {
|
|
1145
|
+
postMessage: jest.fn()
|
|
1146
|
+
};
|
|
1147
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1148
|
+
value: mockContentWindow,
|
|
1149
|
+
writable: true
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
const client = requestIframeClient(iframe);
|
|
1153
|
+
|
|
1154
|
+
// Pre-set cookies
|
|
1155
|
+
client.setCookie('token', 'old_token');
|
|
1156
|
+
client.setCookie('lang', 'en');
|
|
1157
|
+
|
|
1158
|
+
// Pass new token in request
|
|
1159
|
+
client.send('/api/test', {}, {
|
|
1160
|
+
cookies: { token: 'new_token', extra: 'value' }
|
|
1161
|
+
}).catch(() => {});
|
|
1162
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1163
|
+
|
|
1164
|
+
// Verify user-provided cookies override internal ones
|
|
1165
|
+
const requestCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1166
|
+
(call: any[]) => call[0]?.type === 'request'
|
|
1167
|
+
);
|
|
1168
|
+
expect(requestCall[0].cookies).toEqual({
|
|
1169
|
+
token: 'new_token', // User-provided overrides internal
|
|
1170
|
+
lang: 'en', // Internal preserved
|
|
1171
|
+
extra: 'value' // User-provided extra
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
cleanupIframe(iframe);
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
it('should automatically save server-set cookies after response', async () => {
|
|
1178
|
+
const origin = 'https://example.com';
|
|
1179
|
+
const iframe = createTestIframe(origin);
|
|
1180
|
+
|
|
1181
|
+
// Mock iframe contentWindow
|
|
1182
|
+
const mockContentWindow = {
|
|
1183
|
+
postMessage: jest.fn()
|
|
1184
|
+
};
|
|
1185
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1186
|
+
value: mockContentWindow,
|
|
1187
|
+
writable: true
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
const client = requestIframeClient(iframe);
|
|
1191
|
+
const server = requestIframeServer();
|
|
1192
|
+
|
|
1193
|
+
// Server sets cookie
|
|
1194
|
+
server.on('/api/login', (req, res) => {
|
|
1195
|
+
res.cookie('authToken', 'jwt_xxx');
|
|
1196
|
+
res.cookie('refreshToken', 'refresh_yyy');
|
|
1197
|
+
res.send({ success: true });
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
const requestId = 'test-cookie-req-1';
|
|
1201
|
+
|
|
1202
|
+
// Make request
|
|
1203
|
+
const responsePromise = client.send('/api/login', { username: 'test' }, { requestId });
|
|
1204
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1205
|
+
|
|
1206
|
+
// Simulate server receiving request and responding
|
|
1207
|
+
window.dispatchEvent(
|
|
1208
|
+
new MessageEvent('message', {
|
|
1209
|
+
data: {
|
|
1210
|
+
__requestIframe__: 1,
|
|
1211
|
+
type: 'request',
|
|
1212
|
+
requestId,
|
|
1213
|
+
path: '/api/login',
|
|
1214
|
+
body: { username: 'test' }
|
|
1215
|
+
},
|
|
1216
|
+
origin,
|
|
1217
|
+
source: mockContentWindow as any
|
|
1218
|
+
})
|
|
1219
|
+
);
|
|
1220
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1221
|
+
|
|
1222
|
+
// Simulate client receiving response
|
|
1223
|
+
const responseCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1224
|
+
(call: any[]) => call[0]?.type === 'response'
|
|
1225
|
+
);
|
|
1226
|
+
if (responseCall) {
|
|
1227
|
+
window.dispatchEvent(
|
|
1228
|
+
new MessageEvent('message', {
|
|
1229
|
+
data: responseCall[0],
|
|
1230
|
+
origin,
|
|
1231
|
+
source: mockContentWindow as any
|
|
1232
|
+
})
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1236
|
+
|
|
1237
|
+
// Verify client automatically saved server-set cookies
|
|
1238
|
+
expect(client.getCookie('authToken')).toBe('jwt_xxx');
|
|
1239
|
+
expect(client.getCookie('refreshToken')).toBe('refresh_yyy');
|
|
1240
|
+
|
|
1241
|
+
server.destroy();
|
|
1242
|
+
cleanupIframe(iframe);
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
describe('Response methods', () => {
|
|
1247
|
+
it('should support res.send with requireAck', async () => {
|
|
1248
|
+
const origin = 'https://example.com';
|
|
1249
|
+
const iframe = createTestIframe(origin);
|
|
1250
|
+
|
|
1251
|
+
let responseMessage: any = null;
|
|
1252
|
+
const mockContentWindow = {
|
|
1253
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
1254
|
+
if (msg.type === 'request') {
|
|
1255
|
+
window.dispatchEvent(
|
|
1256
|
+
new MessageEvent('message', {
|
|
1257
|
+
data: {
|
|
1258
|
+
__requestIframe__: 1,
|
|
1259
|
+
type: 'ack',
|
|
1260
|
+
requestId: msg.requestId,
|
|
1261
|
+
path: msg.path
|
|
1262
|
+
},
|
|
1263
|
+
origin
|
|
1264
|
+
})
|
|
1265
|
+
);
|
|
1266
|
+
setTimeout(() => {
|
|
1267
|
+
const response: PostMessageData = {
|
|
1268
|
+
__requestIframe__: 1,
|
|
1269
|
+
timestamp: Date.now(),
|
|
1270
|
+
type: 'response',
|
|
1271
|
+
requestId: msg.requestId,
|
|
1272
|
+
data: { result: 'success' },
|
|
1273
|
+
status: 200,
|
|
1274
|
+
requireAck: true
|
|
1275
|
+
};
|
|
1276
|
+
responseMessage = response;
|
|
1277
|
+
window.dispatchEvent(
|
|
1278
|
+
new MessageEvent('message', {
|
|
1279
|
+
data: response,
|
|
1280
|
+
origin
|
|
1281
|
+
})
|
|
1282
|
+
);
|
|
1283
|
+
}, 10);
|
|
1284
|
+
}
|
|
1285
|
+
})
|
|
1286
|
+
};
|
|
1287
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1288
|
+
value: mockContentWindow,
|
|
1289
|
+
writable: true
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
const client = requestIframeClient(iframe);
|
|
1293
|
+
const server = requestIframeServer();
|
|
1294
|
+
|
|
1295
|
+
server.on('testAck', async (req, res) => {
|
|
1296
|
+
await res.send({ result: 'success' }, { requireAck: true });
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
const requestId = 'req-ack';
|
|
1300
|
+
window.dispatchEvent(
|
|
1301
|
+
new MessageEvent('message', {
|
|
1302
|
+
data: {
|
|
1303
|
+
__requestIframe__: 1,
|
|
1304
|
+
type: 'request',
|
|
1305
|
+
requestId: requestId,
|
|
1306
|
+
path: 'testAck',
|
|
1307
|
+
body: {}
|
|
1308
|
+
},
|
|
1309
|
+
origin,
|
|
1310
|
+
source: mockContentWindow as any
|
|
1311
|
+
})
|
|
1312
|
+
);
|
|
1313
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1314
|
+
|
|
1315
|
+
// Verify response was sent with requireAck
|
|
1316
|
+
if (responseMessage && 'requireAck' in responseMessage) {
|
|
1317
|
+
expect(responseMessage.requireAck).toBe(true);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
server.destroy();
|
|
1321
|
+
cleanupIframe(iframe);
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
it('should support res.json with requireAck', async () => {
|
|
1325
|
+
const origin = 'https://example.com';
|
|
1326
|
+
const iframe = createTestIframe(origin);
|
|
1327
|
+
|
|
1328
|
+
const mockContentWindow = {
|
|
1329
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
1330
|
+
if (msg.type === 'request') {
|
|
1331
|
+
window.dispatchEvent(
|
|
1332
|
+
new MessageEvent('message', {
|
|
1333
|
+
data: {
|
|
1334
|
+
__requestIframe__: 1,
|
|
1335
|
+
type: 'ack',
|
|
1336
|
+
requestId: msg.requestId,
|
|
1337
|
+
path: msg.path
|
|
1338
|
+
},
|
|
1339
|
+
origin
|
|
1340
|
+
})
|
|
1341
|
+
);
|
|
1342
|
+
setTimeout(() => {
|
|
1343
|
+
window.dispatchEvent(
|
|
1344
|
+
new MessageEvent('message', {
|
|
1345
|
+
data: {
|
|
1346
|
+
__requestIframe__: 1,
|
|
1347
|
+
type: 'response',
|
|
1348
|
+
requestId: msg.requestId,
|
|
1349
|
+
data: { json: true },
|
|
1350
|
+
status: 200,
|
|
1351
|
+
requireAck: true
|
|
1352
|
+
},
|
|
1353
|
+
origin
|
|
1354
|
+
})
|
|
1355
|
+
);
|
|
1356
|
+
}, 10);
|
|
1357
|
+
}
|
|
1358
|
+
})
|
|
1359
|
+
};
|
|
1360
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1361
|
+
value: mockContentWindow,
|
|
1362
|
+
writable: true
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
const server = requestIframeServer();
|
|
1366
|
+
|
|
1367
|
+
server.on('testJson', async (req, res) => {
|
|
1368
|
+
await res.json({ json: true }, { requireAck: true });
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
const requestId = 'req-json';
|
|
1372
|
+
window.dispatchEvent(
|
|
1373
|
+
new MessageEvent('message', {
|
|
1374
|
+
data: {
|
|
1375
|
+
__requestIframe__: 1,
|
|
1376
|
+
type: 'request',
|
|
1377
|
+
requestId: requestId,
|
|
1378
|
+
path: 'testJson',
|
|
1379
|
+
body: {}
|
|
1380
|
+
},
|
|
1381
|
+
origin,
|
|
1382
|
+
source: mockContentWindow as any
|
|
1383
|
+
})
|
|
1384
|
+
);
|
|
1385
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1386
|
+
|
|
1387
|
+
const responseCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1388
|
+
(call: any[]) => call[0]?.type === 'response'
|
|
1389
|
+
);
|
|
1390
|
+
expect(responseCall).toBeDefined();
|
|
1391
|
+
if (responseCall && responseCall[0] && responseCall[0].headers) {
|
|
1392
|
+
expect(responseCall[0].headers[HttpHeader.CONTENT_TYPE]).toBe('application/json');
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
server.destroy();
|
|
1396
|
+
cleanupIframe(iframe);
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
it('should support res.status()', async () => {
|
|
1400
|
+
const origin = 'https://example.com';
|
|
1401
|
+
const iframe = createTestIframe(origin);
|
|
1402
|
+
|
|
1403
|
+
const mockContentWindow = {
|
|
1404
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
1405
|
+
if (msg.type === 'request') {
|
|
1406
|
+
window.dispatchEvent(
|
|
1407
|
+
new MessageEvent('message', {
|
|
1408
|
+
data: {
|
|
1409
|
+
__requestIframe__: 1,
|
|
1410
|
+
type: 'ack',
|
|
1411
|
+
requestId: msg.requestId,
|
|
1412
|
+
path: msg.path
|
|
1413
|
+
},
|
|
1414
|
+
origin
|
|
1415
|
+
})
|
|
1416
|
+
);
|
|
1417
|
+
setTimeout(() => {
|
|
1418
|
+
window.dispatchEvent(
|
|
1419
|
+
new MessageEvent('message', {
|
|
1420
|
+
data: {
|
|
1421
|
+
__requestIframe__: 1,
|
|
1422
|
+
type: 'response',
|
|
1423
|
+
requestId: msg.requestId,
|
|
1424
|
+
data: { error: 'Not Found' },
|
|
1425
|
+
status: 404,
|
|
1426
|
+
statusText: 'Not Found'
|
|
1427
|
+
},
|
|
1428
|
+
origin
|
|
1429
|
+
})
|
|
1430
|
+
);
|
|
1431
|
+
}, 10);
|
|
1432
|
+
}
|
|
1433
|
+
})
|
|
1434
|
+
};
|
|
1435
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1436
|
+
value: mockContentWindow,
|
|
1437
|
+
writable: true
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
const server = requestIframeServer();
|
|
1441
|
+
|
|
1442
|
+
server.on('testStatus', (req, res) => {
|
|
1443
|
+
res.status(404).send({ error: 'Not Found' });
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
const requestId = 'req-status';
|
|
1447
|
+
window.dispatchEvent(
|
|
1448
|
+
new MessageEvent('message', {
|
|
1449
|
+
data: {
|
|
1450
|
+
__requestIframe__: 1,
|
|
1451
|
+
type: 'request',
|
|
1452
|
+
requestId: requestId,
|
|
1453
|
+
path: 'testStatus',
|
|
1454
|
+
body: {}
|
|
1455
|
+
},
|
|
1456
|
+
origin,
|
|
1457
|
+
source: mockContentWindow as any
|
|
1458
|
+
})
|
|
1459
|
+
);
|
|
1460
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1461
|
+
|
|
1462
|
+
const responseCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1463
|
+
(call: any[]) => call[0]?.type === 'response' && call[0]?.status === 404
|
|
1464
|
+
);
|
|
1465
|
+
expect(responseCall).toBeDefined();
|
|
1466
|
+
|
|
1467
|
+
server.destroy();
|
|
1468
|
+
cleanupIframe(iframe);
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
it('should support res.setHeader() with array values', async () => {
|
|
1472
|
+
const origin = 'https://example.com';
|
|
1473
|
+
const iframe = createTestIframe(origin);
|
|
1474
|
+
|
|
1475
|
+
const mockContentWindow = {
|
|
1476
|
+
postMessage: jest.fn()
|
|
1477
|
+
};
|
|
1478
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1479
|
+
value: mockContentWindow,
|
|
1480
|
+
writable: true
|
|
1481
|
+
});
|
|
1482
|
+
|
|
1483
|
+
const server = requestIframeServer();
|
|
1484
|
+
|
|
1485
|
+
server.on('testHeader', (req, res) => {
|
|
1486
|
+
res.setHeader('X-Custom', ['value1', 'value2']);
|
|
1487
|
+
res.send({});
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
const requestId = 'req-header';
|
|
1491
|
+
window.dispatchEvent(
|
|
1492
|
+
new MessageEvent('message', {
|
|
1493
|
+
data: {
|
|
1494
|
+
__requestIframe__: 1,
|
|
1495
|
+
type: 'request',
|
|
1496
|
+
requestId: requestId,
|
|
1497
|
+
path: 'testHeader',
|
|
1498
|
+
body: {}
|
|
1499
|
+
},
|
|
1500
|
+
origin,
|
|
1501
|
+
source: mockContentWindow as any
|
|
1502
|
+
})
|
|
1503
|
+
);
|
|
1504
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1505
|
+
|
|
1506
|
+
const responseCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1507
|
+
(call: any[]) => call[0]?.type === 'response'
|
|
1508
|
+
);
|
|
1509
|
+
expect(responseCall).toBeDefined();
|
|
1510
|
+
expect(responseCall[0].headers['X-Custom']).toBe('value1, value2');
|
|
1511
|
+
|
|
1512
|
+
server.destroy();
|
|
1513
|
+
cleanupIframe(iframe);
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
it('should support res.set() method', async () => {
|
|
1517
|
+
const origin = 'https://example.com';
|
|
1518
|
+
const iframe = createTestIframe(origin);
|
|
1519
|
+
|
|
1520
|
+
const mockContentWindow = {
|
|
1521
|
+
postMessage: jest.fn()
|
|
1522
|
+
};
|
|
1523
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1524
|
+
value: mockContentWindow,
|
|
1525
|
+
writable: true
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
const server = requestIframeServer();
|
|
1529
|
+
|
|
1530
|
+
server.on('testSet', (req, res) => {
|
|
1531
|
+
res.set('X-Custom', 'value').send({});
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
const requestId = 'req-set';
|
|
1535
|
+
window.dispatchEvent(
|
|
1536
|
+
new MessageEvent('message', {
|
|
1537
|
+
data: {
|
|
1538
|
+
__requestIframe__: 1,
|
|
1539
|
+
type: 'request',
|
|
1540
|
+
requestId: requestId,
|
|
1541
|
+
path: 'testSet',
|
|
1542
|
+
body: {}
|
|
1543
|
+
},
|
|
1544
|
+
origin,
|
|
1545
|
+
source: mockContentWindow as any
|
|
1546
|
+
})
|
|
1547
|
+
);
|
|
1548
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1549
|
+
|
|
1550
|
+
const responseCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1551
|
+
(call: any[]) => call[0]?.type === 'response'
|
|
1552
|
+
);
|
|
1553
|
+
expect(responseCall).toBeDefined();
|
|
1554
|
+
expect(responseCall[0].headers['X-Custom']).toBe('value');
|
|
1555
|
+
|
|
1556
|
+
server.destroy();
|
|
1557
|
+
cleanupIframe(iframe);
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
it('should support res.cookie()', async () => {
|
|
1561
|
+
const origin = 'https://example.com';
|
|
1562
|
+
const iframe = createTestIframe(origin);
|
|
1563
|
+
|
|
1564
|
+
const mockContentWindow = {
|
|
1565
|
+
postMessage: jest.fn()
|
|
1566
|
+
};
|
|
1567
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1568
|
+
value: mockContentWindow,
|
|
1569
|
+
writable: true
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
const server = requestIframeServer();
|
|
1573
|
+
|
|
1574
|
+
server.on('testCookie', (req, res) => {
|
|
1575
|
+
res.cookie('token', 'abc123', {
|
|
1576
|
+
path: '/api',
|
|
1577
|
+
httpOnly: true,
|
|
1578
|
+
secure: true,
|
|
1579
|
+
sameSite: 'strict'
|
|
1580
|
+
});
|
|
1581
|
+
res.send({});
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
const requestId = 'req-cookie';
|
|
1585
|
+
window.dispatchEvent(
|
|
1586
|
+
new MessageEvent('message', {
|
|
1587
|
+
data: {
|
|
1588
|
+
__requestIframe__: 1,
|
|
1589
|
+
type: 'request',
|
|
1590
|
+
requestId: requestId,
|
|
1591
|
+
path: 'testCookie',
|
|
1592
|
+
body: {}
|
|
1593
|
+
},
|
|
1594
|
+
origin,
|
|
1595
|
+
source: mockContentWindow as any
|
|
1596
|
+
})
|
|
1597
|
+
);
|
|
1598
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1599
|
+
|
|
1600
|
+
const responseCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1601
|
+
(call: any[]) => call[0]?.type === 'response'
|
|
1602
|
+
);
|
|
1603
|
+
expect(responseCall).toBeDefined();
|
|
1604
|
+
const setCookies = responseCall[0].headers[HttpHeader.SET_COOKIE];
|
|
1605
|
+
expect(Array.isArray(setCookies)).toBe(true);
|
|
1606
|
+
expect(setCookies[0]).toContain('token=abc123');
|
|
1607
|
+
expect(setCookies[0]).toContain('Path=/api');
|
|
1608
|
+
|
|
1609
|
+
server.destroy();
|
|
1610
|
+
cleanupIframe(iframe);
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
it('should support res.clearCookie()', async () => {
|
|
1614
|
+
const origin = 'https://example.com';
|
|
1615
|
+
const iframe = createTestIframe(origin);
|
|
1616
|
+
|
|
1617
|
+
const mockContentWindow = {
|
|
1618
|
+
postMessage: jest.fn()
|
|
1619
|
+
};
|
|
1620
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1621
|
+
value: mockContentWindow,
|
|
1622
|
+
writable: true
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
const server = requestIframeServer();
|
|
1626
|
+
|
|
1627
|
+
server.on('testClearCookie', (req, res) => {
|
|
1628
|
+
res.clearCookie('token', { path: '/api' });
|
|
1629
|
+
res.send({});
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
const requestId = 'req-clearcookie';
|
|
1633
|
+
window.dispatchEvent(
|
|
1634
|
+
new MessageEvent('message', {
|
|
1635
|
+
data: {
|
|
1636
|
+
__requestIframe__: 1,
|
|
1637
|
+
type: 'request',
|
|
1638
|
+
requestId: requestId,
|
|
1639
|
+
path: 'testClearCookie',
|
|
1640
|
+
body: {}
|
|
1641
|
+
},
|
|
1642
|
+
origin,
|
|
1643
|
+
source: mockContentWindow as any
|
|
1644
|
+
})
|
|
1645
|
+
);
|
|
1646
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1647
|
+
|
|
1648
|
+
const responseCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1649
|
+
(call: any[]) => call[0]?.type === 'response'
|
|
1650
|
+
);
|
|
1651
|
+
expect(responseCall).toBeDefined();
|
|
1652
|
+
const setCookies = responseCall[0].headers[HttpHeader.SET_COOKIE];
|
|
1653
|
+
expect(setCookies[0]).toContain('Max-Age=0');
|
|
1654
|
+
|
|
1655
|
+
server.destroy();
|
|
1656
|
+
cleanupIframe(iframe);
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
it('should handle async handler without sending response', async () => {
|
|
1660
|
+
const origin = 'https://example.com';
|
|
1661
|
+
const iframe = createTestIframe(origin);
|
|
1662
|
+
|
|
1663
|
+
const mockContentWindow = {
|
|
1664
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
1665
|
+
if (msg.type === 'request') {
|
|
1666
|
+
window.dispatchEvent(
|
|
1667
|
+
new MessageEvent('message', {
|
|
1668
|
+
data: {
|
|
1669
|
+
__requestIframe__: 1,
|
|
1670
|
+
type: 'ack',
|
|
1671
|
+
requestId: msg.requestId,
|
|
1672
|
+
path: msg.path
|
|
1673
|
+
},
|
|
1674
|
+
origin
|
|
1675
|
+
})
|
|
1676
|
+
);
|
|
1677
|
+
setTimeout(() => {
|
|
1678
|
+
window.dispatchEvent(
|
|
1679
|
+
new MessageEvent('message', {
|
|
1680
|
+
data: {
|
|
1681
|
+
__requestIframe__: 1,
|
|
1682
|
+
type: 'async',
|
|
1683
|
+
requestId: msg.requestId,
|
|
1684
|
+
path: msg.path
|
|
1685
|
+
},
|
|
1686
|
+
origin
|
|
1687
|
+
})
|
|
1688
|
+
);
|
|
1689
|
+
}, 10);
|
|
1690
|
+
setTimeout(() => {
|
|
1691
|
+
window.dispatchEvent(
|
|
1692
|
+
new MessageEvent('message', {
|
|
1693
|
+
data: {
|
|
1694
|
+
__requestIframe__: 1,
|
|
1695
|
+
type: 'error',
|
|
1696
|
+
requestId: msg.requestId,
|
|
1697
|
+
error: {
|
|
1698
|
+
message: Messages.NO_RESPONSE_SENT,
|
|
1699
|
+
code: 'NO_RESPONSE'
|
|
1700
|
+
},
|
|
1701
|
+
status: 500
|
|
1702
|
+
},
|
|
1703
|
+
origin
|
|
1704
|
+
})
|
|
1705
|
+
);
|
|
1706
|
+
}, 50);
|
|
1707
|
+
}
|
|
1708
|
+
})
|
|
1709
|
+
};
|
|
1710
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1711
|
+
value: mockContentWindow,
|
|
1712
|
+
writable: true
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
const client = requestIframeClient(iframe);
|
|
1716
|
+
const server = requestIframeServer();
|
|
1717
|
+
|
|
1718
|
+
server.on('asyncNoResponse', async (req, res) => {
|
|
1719
|
+
await new Promise(resolve => setTimeout(resolve, 30));
|
|
1720
|
+
// Intentionally not sending response
|
|
1721
|
+
});
|
|
1722
|
+
|
|
1723
|
+
await expect(
|
|
1724
|
+
client.send('asyncNoResponse', undefined, { ackTimeout: 1000, asyncTimeout: 5000 })
|
|
1725
|
+
).rejects.toMatchObject({
|
|
1726
|
+
code: 'NO_RESPONSE'
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
server.destroy();
|
|
1730
|
+
cleanupIframe(iframe);
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
it('should handle async handler with promise rejection', async () => {
|
|
1734
|
+
const origin = 'https://example.com';
|
|
1735
|
+
const iframe = createTestIframe(origin);
|
|
1736
|
+
|
|
1737
|
+
const mockContentWindow = {
|
|
1738
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
1739
|
+
if (msg.type === 'request') {
|
|
1740
|
+
window.dispatchEvent(
|
|
1741
|
+
new MessageEvent('message', {
|
|
1742
|
+
data: {
|
|
1743
|
+
__requestIframe__: 1,
|
|
1744
|
+
type: 'ack',
|
|
1745
|
+
requestId: msg.requestId,
|
|
1746
|
+
path: msg.path
|
|
1747
|
+
},
|
|
1748
|
+
origin
|
|
1749
|
+
})
|
|
1750
|
+
);
|
|
1751
|
+
setTimeout(() => {
|
|
1752
|
+
window.dispatchEvent(
|
|
1753
|
+
new MessageEvent('message', {
|
|
1754
|
+
data: {
|
|
1755
|
+
__requestIframe__: 1,
|
|
1756
|
+
type: 'async',
|
|
1757
|
+
requestId: msg.requestId,
|
|
1758
|
+
path: msg.path
|
|
1759
|
+
},
|
|
1760
|
+
origin
|
|
1761
|
+
})
|
|
1762
|
+
);
|
|
1763
|
+
}, 10);
|
|
1764
|
+
setTimeout(() => {
|
|
1765
|
+
window.dispatchEvent(
|
|
1766
|
+
new MessageEvent('message', {
|
|
1767
|
+
data: {
|
|
1768
|
+
__requestIframe__: 1,
|
|
1769
|
+
type: 'error',
|
|
1770
|
+
requestId: msg.requestId,
|
|
1771
|
+
error: {
|
|
1772
|
+
message: 'Test error',
|
|
1773
|
+
code: 'REQUEST_ERROR'
|
|
1774
|
+
},
|
|
1775
|
+
status: 500
|
|
1776
|
+
},
|
|
1777
|
+
origin
|
|
1778
|
+
})
|
|
1779
|
+
);
|
|
1780
|
+
}, 50);
|
|
1781
|
+
}
|
|
1782
|
+
})
|
|
1783
|
+
};
|
|
1784
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1785
|
+
value: mockContentWindow,
|
|
1786
|
+
writable: true
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
const client = requestIframeClient(iframe);
|
|
1790
|
+
const server = requestIframeServer();
|
|
1791
|
+
|
|
1792
|
+
server.on('asyncError', async (req, res) => {
|
|
1793
|
+
await new Promise(resolve => setTimeout(resolve, 30));
|
|
1794
|
+
throw new Error('Test error');
|
|
1795
|
+
});
|
|
1796
|
+
|
|
1797
|
+
await expect(
|
|
1798
|
+
client.send('asyncError', undefined, { ackTimeout: 1000, asyncTimeout: 5000 })
|
|
1799
|
+
).rejects.toMatchObject({
|
|
1800
|
+
code: 'REQUEST_ERROR'
|
|
1801
|
+
});
|
|
1802
|
+
|
|
1803
|
+
server.destroy();
|
|
1804
|
+
cleanupIframe(iframe);
|
|
1805
|
+
});
|
|
1806
|
+
|
|
1807
|
+
it('should handle middleware error', async () => {
|
|
1808
|
+
const origin = 'https://example.com';
|
|
1809
|
+
const iframe = createTestIframe(origin);
|
|
1810
|
+
|
|
1811
|
+
const mockContentWindow = {
|
|
1812
|
+
postMessage: jest.fn()
|
|
1813
|
+
};
|
|
1814
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1815
|
+
value: mockContentWindow,
|
|
1816
|
+
writable: true
|
|
1817
|
+
});
|
|
1818
|
+
|
|
1819
|
+
const server = requestIframeServer();
|
|
1820
|
+
|
|
1821
|
+
server.use((req, res, next) => {
|
|
1822
|
+
throw new Error('Middleware error');
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
server.on('test', (req, res) => {
|
|
1826
|
+
res.send({});
|
|
1827
|
+
});
|
|
1828
|
+
|
|
1829
|
+
const requestId = 'req-middleware-error';
|
|
1830
|
+
window.dispatchEvent(
|
|
1831
|
+
new MessageEvent('message', {
|
|
1832
|
+
data: {
|
|
1833
|
+
__requestIframe__: 1,
|
|
1834
|
+
type: 'request',
|
|
1835
|
+
requestId: requestId,
|
|
1836
|
+
path: 'test',
|
|
1837
|
+
body: {}
|
|
1838
|
+
},
|
|
1839
|
+
origin,
|
|
1840
|
+
source: mockContentWindow as any
|
|
1841
|
+
})
|
|
1842
|
+
);
|
|
1843
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1844
|
+
|
|
1845
|
+
const errorCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1846
|
+
(call: any[]) => call[0]?.type === 'error' ||
|
|
1847
|
+
(call[0]?.type === 'response' && call[0]?.status === 500)
|
|
1848
|
+
);
|
|
1849
|
+
expect(errorCall).toBeDefined();
|
|
1850
|
+
|
|
1851
|
+
server.destroy();
|
|
1852
|
+
cleanupIframe(iframe);
|
|
1853
|
+
});
|
|
1854
|
+
|
|
1855
|
+
it('should handle middleware promise rejection', async () => {
|
|
1856
|
+
const origin = 'https://example.com';
|
|
1857
|
+
const iframe = createTestIframe(origin);
|
|
1858
|
+
|
|
1859
|
+
const mockContentWindow = {
|
|
1860
|
+
postMessage: jest.fn()
|
|
1861
|
+
};
|
|
1862
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1863
|
+
value: mockContentWindow,
|
|
1864
|
+
writable: true
|
|
1865
|
+
});
|
|
1866
|
+
|
|
1867
|
+
const server = requestIframeServer();
|
|
1868
|
+
|
|
1869
|
+
server.use(async (req, res, next) => {
|
|
1870
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
1871
|
+
throw new Error('Async middleware error');
|
|
1872
|
+
});
|
|
1873
|
+
|
|
1874
|
+
server.on('test', (req, res) => {
|
|
1875
|
+
res.send({});
|
|
1876
|
+
});
|
|
1877
|
+
|
|
1878
|
+
const requestId = 'req-middleware-async-error';
|
|
1879
|
+
window.dispatchEvent(
|
|
1880
|
+
new MessageEvent('message', {
|
|
1881
|
+
data: {
|
|
1882
|
+
__requestIframe__: 1,
|
|
1883
|
+
type: 'request',
|
|
1884
|
+
requestId: requestId,
|
|
1885
|
+
path: 'test',
|
|
1886
|
+
body: {}
|
|
1887
|
+
},
|
|
1888
|
+
origin,
|
|
1889
|
+
source: mockContentWindow as any
|
|
1890
|
+
})
|
|
1891
|
+
);
|
|
1892
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1893
|
+
|
|
1894
|
+
const errorCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1895
|
+
(call: any[]) => call[0]?.type === 'error' ||
|
|
1896
|
+
(call[0]?.type === 'response' && call[0]?.status === 500)
|
|
1897
|
+
);
|
|
1898
|
+
expect(errorCall).toBeDefined();
|
|
1899
|
+
|
|
1900
|
+
server.destroy();
|
|
1901
|
+
cleanupIframe(iframe);
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
it('should handle request without path', async () => {
|
|
1905
|
+
const origin = 'https://example.com';
|
|
1906
|
+
const iframe = createTestIframe(origin);
|
|
1907
|
+
|
|
1908
|
+
const mockContentWindow = {
|
|
1909
|
+
postMessage: jest.fn()
|
|
1910
|
+
};
|
|
1911
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1912
|
+
value: mockContentWindow,
|
|
1913
|
+
writable: true
|
|
1914
|
+
});
|
|
1915
|
+
|
|
1916
|
+
const server = requestIframeServer();
|
|
1917
|
+
|
|
1918
|
+
const requestId = 'req-no-path';
|
|
1919
|
+
window.dispatchEvent(
|
|
1920
|
+
new MessageEvent('message', {
|
|
1921
|
+
data: {
|
|
1922
|
+
__requestIframe__: 1,
|
|
1923
|
+
type: 'request',
|
|
1924
|
+
requestId: requestId,
|
|
1925
|
+
body: {}
|
|
1926
|
+
},
|
|
1927
|
+
origin,
|
|
1928
|
+
source: mockContentWindow as any
|
|
1929
|
+
})
|
|
1930
|
+
);
|
|
1931
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1932
|
+
|
|
1933
|
+
// Should not crash, but also shouldn't process the request (no path means early return)
|
|
1934
|
+
// Server should still send ACK, but won't process further
|
|
1935
|
+
const ackCall = mockContentWindow.postMessage.mock.calls.find(
|
|
1936
|
+
(call: any[]) => call[0]?.type === 'ack'
|
|
1937
|
+
);
|
|
1938
|
+
// Server may or may not send ACK if path is missing, but should not crash
|
|
1939
|
+
expect(() => server.destroy()).not.toThrow();
|
|
1940
|
+
|
|
1941
|
+
cleanupIframe(iframe);
|
|
1942
|
+
});
|
|
1943
|
+
|
|
1944
|
+
it('should handle request without source', async () => {
|
|
1945
|
+
const origin = 'https://example.com';
|
|
1946
|
+
const iframe = createTestIframe(origin);
|
|
1947
|
+
|
|
1948
|
+
const mockContentWindow = {
|
|
1949
|
+
postMessage: jest.fn()
|
|
1950
|
+
};
|
|
1951
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
1952
|
+
value: mockContentWindow,
|
|
1953
|
+
writable: true
|
|
1954
|
+
});
|
|
1955
|
+
|
|
1956
|
+
const server = requestIframeServer();
|
|
1957
|
+
|
|
1958
|
+
const requestId = 'req-no-source';
|
|
1959
|
+
window.dispatchEvent(
|
|
1960
|
+
new MessageEvent('message', {
|
|
1961
|
+
data: {
|
|
1962
|
+
__requestIframe__: 1,
|
|
1963
|
+
type: 'request',
|
|
1964
|
+
requestId: requestId,
|
|
1965
|
+
path: 'test',
|
|
1966
|
+
body: {}
|
|
1967
|
+
},
|
|
1968
|
+
origin
|
|
1969
|
+
// Intentionally no source
|
|
1970
|
+
})
|
|
1971
|
+
);
|
|
1972
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1973
|
+
|
|
1974
|
+
// Should not crash
|
|
1975
|
+
server.destroy();
|
|
1976
|
+
cleanupIframe(iframe);
|
|
1977
|
+
});
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1980
|
+
describe('Stream response', () => {
|
|
1981
|
+
it('should support sendStream', async () => {
|
|
1982
|
+
const origin = 'https://example.com';
|
|
1983
|
+
const iframe = createTestIframe(origin);
|
|
1984
|
+
|
|
1985
|
+
const mockContentWindow = {
|
|
1986
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
1987
|
+
if (msg.type === 'request') {
|
|
1988
|
+
window.dispatchEvent(
|
|
1989
|
+
new MessageEvent('message', {
|
|
1990
|
+
data: {
|
|
1991
|
+
__requestIframe__: 1,
|
|
1992
|
+
type: 'ack',
|
|
1993
|
+
requestId: msg.requestId,
|
|
1994
|
+
path: msg.path
|
|
1995
|
+
},
|
|
1996
|
+
origin
|
|
1997
|
+
})
|
|
1998
|
+
);
|
|
1999
|
+
setTimeout(() => {
|
|
2000
|
+
window.dispatchEvent(
|
|
2001
|
+
new MessageEvent('message', {
|
|
2002
|
+
data: {
|
|
2003
|
+
__requestIframe__: 1,
|
|
2004
|
+
type: 'stream_start',
|
|
2005
|
+
requestId: msg.requestId,
|
|
2006
|
+
body: {
|
|
2007
|
+
streamId: 'stream-123',
|
|
2008
|
+
type: 'data',
|
|
2009
|
+
chunked: true
|
|
2010
|
+
}
|
|
2011
|
+
},
|
|
2012
|
+
origin
|
|
2013
|
+
})
|
|
2014
|
+
);
|
|
2015
|
+
}, 10);
|
|
2016
|
+
}
|
|
2017
|
+
})
|
|
2018
|
+
};
|
|
2019
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2020
|
+
value: mockContentWindow,
|
|
2021
|
+
writable: true
|
|
2022
|
+
});
|
|
2023
|
+
|
|
2024
|
+
const client = requestIframeClient(iframe);
|
|
2025
|
+
const server = requestIframeServer();
|
|
2026
|
+
|
|
2027
|
+
server.on('getStream', async (req, res) => {
|
|
2028
|
+
const { IframeWritableStream } = await import('../stream');
|
|
2029
|
+
const stream = new IframeWritableStream({
|
|
2030
|
+
iterator: async function* () {
|
|
2031
|
+
yield 'chunk1';
|
|
2032
|
+
yield 'chunk2';
|
|
2033
|
+
}
|
|
2034
|
+
});
|
|
2035
|
+
await res.sendStream(stream);
|
|
2036
|
+
});
|
|
2037
|
+
|
|
2038
|
+
const requestId = 'req-stream';
|
|
2039
|
+
window.dispatchEvent(
|
|
2040
|
+
new MessageEvent('message', {
|
|
2041
|
+
data: {
|
|
2042
|
+
__requestIframe__: 1,
|
|
2043
|
+
type: 'request',
|
|
2044
|
+
requestId: requestId,
|
|
2045
|
+
path: 'getStream',
|
|
2046
|
+
body: {}
|
|
2047
|
+
},
|
|
2048
|
+
origin,
|
|
2049
|
+
source: mockContentWindow as any
|
|
2050
|
+
})
|
|
2051
|
+
);
|
|
2052
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
2053
|
+
|
|
2054
|
+
const streamStartCall = mockContentWindow.postMessage.mock.calls.find(
|
|
2055
|
+
(call: any[]) => call[0]?.type === 'stream_start'
|
|
2056
|
+
);
|
|
2057
|
+
expect(streamStartCall).toBeDefined();
|
|
2058
|
+
|
|
2059
|
+
server.destroy();
|
|
2060
|
+
cleanupIframe(iframe);
|
|
2061
|
+
});
|
|
2062
|
+
|
|
2063
|
+
it('should handle stream response from server', async () => {
|
|
2064
|
+
const origin = 'https://example.com';
|
|
2065
|
+
const iframe = createTestIframe(origin);
|
|
2066
|
+
|
|
2067
|
+
const mockContentWindow = {
|
|
2068
|
+
postMessage: jest.fn((msg: PostMessageData) => {
|
|
2069
|
+
if (msg.type === 'request') {
|
|
2070
|
+
window.dispatchEvent(
|
|
2071
|
+
new MessageEvent('message', {
|
|
2072
|
+
data: {
|
|
2073
|
+
__requestIframe__: 1,
|
|
2074
|
+
type: 'ack',
|
|
2075
|
+
requestId: msg.requestId,
|
|
2076
|
+
path: msg.path
|
|
2077
|
+
},
|
|
2078
|
+
origin
|
|
2079
|
+
})
|
|
2080
|
+
);
|
|
2081
|
+
setTimeout(() => {
|
|
2082
|
+
window.dispatchEvent(
|
|
2083
|
+
new MessageEvent('message', {
|
|
2084
|
+
data: {
|
|
2085
|
+
__requestIframe__: 1,
|
|
2086
|
+
type: 'stream_start',
|
|
2087
|
+
requestId: msg.requestId,
|
|
2088
|
+
body: {
|
|
2089
|
+
streamId: 'stream-123',
|
|
2090
|
+
type: 'data',
|
|
2091
|
+
chunked: true
|
|
2092
|
+
}
|
|
2093
|
+
},
|
|
2094
|
+
origin
|
|
2095
|
+
})
|
|
2096
|
+
);
|
|
2097
|
+
}, 10);
|
|
2098
|
+
}
|
|
2099
|
+
})
|
|
2100
|
+
};
|
|
2101
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2102
|
+
value: mockContentWindow,
|
|
2103
|
+
writable: true
|
|
2104
|
+
});
|
|
2105
|
+
|
|
2106
|
+
const client = requestIframeClient(iframe);
|
|
2107
|
+
const server = requestIframeServer();
|
|
2108
|
+
|
|
2109
|
+
server.on('getStream', async (req, res) => {
|
|
2110
|
+
const { IframeWritableStream } = await import('../stream');
|
|
2111
|
+
const stream = new IframeWritableStream({
|
|
2112
|
+
iterator: async function* () {
|
|
2113
|
+
yield 'chunk1';
|
|
2114
|
+
yield 'chunk2';
|
|
2115
|
+
}
|
|
2116
|
+
});
|
|
2117
|
+
await res.sendStream(stream);
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2120
|
+
const requestId = 'req-stream';
|
|
2121
|
+
window.dispatchEvent(
|
|
2122
|
+
new MessageEvent('message', {
|
|
2123
|
+
data: {
|
|
2124
|
+
__requestIframe__: 1,
|
|
2125
|
+
type: 'request',
|
|
2126
|
+
requestId: requestId,
|
|
2127
|
+
path: 'getStream',
|
|
2128
|
+
body: {}
|
|
2129
|
+
},
|
|
2130
|
+
origin,
|
|
2131
|
+
source: mockContentWindow as any
|
|
2132
|
+
})
|
|
2133
|
+
);
|
|
2134
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
2135
|
+
|
|
2136
|
+
const streamStartCall = mockContentWindow.postMessage.mock.calls.find(
|
|
2137
|
+
(call: any[]) => call[0]?.type === 'stream_start'
|
|
2138
|
+
);
|
|
2139
|
+
expect(streamStartCall).toBeDefined();
|
|
2140
|
+
|
|
2141
|
+
server.destroy();
|
|
2142
|
+
cleanupIframe(iframe);
|
|
2143
|
+
});
|
|
2144
|
+
|
|
2145
|
+
it('should handle server open/close methods', () => {
|
|
2146
|
+
const origin = 'https://example.com';
|
|
2147
|
+
const iframe = createTestIframe(origin);
|
|
2148
|
+
|
|
2149
|
+
const mockContentWindow = {
|
|
2150
|
+
postMessage: jest.fn()
|
|
2151
|
+
};
|
|
2152
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2153
|
+
value: mockContentWindow,
|
|
2154
|
+
writable: true
|
|
2155
|
+
});
|
|
2156
|
+
|
|
2157
|
+
const server = requestIframeServer();
|
|
2158
|
+
|
|
2159
|
+
expect(server.isOpen).toBe(true);
|
|
2160
|
+
|
|
2161
|
+
server.close();
|
|
2162
|
+
expect(server.isOpen).toBe(false);
|
|
2163
|
+
|
|
2164
|
+
server.open();
|
|
2165
|
+
expect(server.isOpen).toBe(true);
|
|
2166
|
+
|
|
2167
|
+
server.destroy();
|
|
2168
|
+
cleanupIframe(iframe);
|
|
2169
|
+
});
|
|
2170
|
+
|
|
2171
|
+
it('should handle server off method', async () => {
|
|
2172
|
+
const origin = 'https://example.com';
|
|
2173
|
+
const iframe = createTestIframe(origin);
|
|
2174
|
+
|
|
2175
|
+
const mockContentWindow = {
|
|
2176
|
+
postMessage: jest.fn()
|
|
2177
|
+
};
|
|
2178
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
2179
|
+
value: mockContentWindow,
|
|
2180
|
+
writable: true
|
|
2181
|
+
});
|
|
2182
|
+
|
|
2183
|
+
const server = requestIframeServer();
|
|
2184
|
+
|
|
2185
|
+
server.on('test', (req, res) => {
|
|
2186
|
+
res.send({});
|
|
2187
|
+
});
|
|
2188
|
+
|
|
2189
|
+
server.off('test');
|
|
2190
|
+
|
|
2191
|
+
const requestId = 'req-off';
|
|
2192
|
+
window.dispatchEvent(
|
|
2193
|
+
new MessageEvent('message', {
|
|
2194
|
+
data: {
|
|
2195
|
+
__requestIframe__: 1,
|
|
2196
|
+
type: 'request',
|
|
2197
|
+
requestId: requestId,
|
|
2198
|
+
path: 'test',
|
|
2199
|
+
body: {}
|
|
2200
|
+
},
|
|
2201
|
+
origin,
|
|
2202
|
+
source: mockContentWindow as any
|
|
2203
|
+
})
|
|
2204
|
+
);
|
|
2205
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2206
|
+
|
|
2207
|
+
const errorCall = mockContentWindow.postMessage.mock.calls.find(
|
|
2208
|
+
(call: any[]) => call[0]?.type === 'error' && call[0]?.error?.code === 'METHOD_NOT_FOUND'
|
|
2209
|
+
);
|
|
2210
|
+
expect(errorCall).toBeDefined();
|
|
2211
|
+
|
|
2212
|
+
server.destroy();
|
|
2213
|
+
cleanupIframe(iframe);
|
|
2214
|
+
});
|
|
2215
|
+
});
|
|
2216
|
+
});
|