request-iframe 0.0.1 → 0.0.2
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/README.CN.md +2 -0
- package/README.md +2 -0
- package/library/__tests__/interceptors.test.ts +22 -0
- package/library/__tests__/requestIframe.test.ts +190 -0
- package/library/api/client.d.js +5 -0
- package/library/api/client.d.ts.map +1 -1
- package/library/api/server.d.js +5 -0
- package/library/api/server.d.ts.map +1 -1
- package/library/constants/index.d.js +36 -0
- package/library/constants/index.d.ts.map +1 -1
- package/library/constants/messages.d.js +5 -0
- package/library/constants/messages.d.ts.map +1 -1
- package/library/core/client.d.js +5 -0
- package/library/core/client.d.ts +16 -0
- package/library/core/client.d.ts.map +1 -1
- package/library/core/client.js +39 -0
- package/library/core/request.d.js +5 -0
- package/library/core/request.d.ts.map +1 -1
- package/library/core/response.d.js +5 -0
- package/library/core/response.d.ts.map +1 -1
- package/library/core/server-client.d.js +5 -0
- package/library/core/server-client.d.ts.map +1 -1
- package/library/core/server.d.js +5 -0
- package/library/core/server.d.ts +2 -2
- package/library/core/server.d.ts.map +1 -1
- package/library/core/server.js +16 -2
- package/library/interceptors/index.d.js +5 -0
- package/library/interceptors/index.d.ts +4 -0
- package/library/interceptors/index.d.ts.map +1 -1
- package/library/interceptors/index.js +7 -0
- package/library/message/channel.d.js +5 -0
- package/library/message/channel.d.ts.map +1 -1
- package/library/message/dispatcher.d.js +5 -0
- package/library/message/dispatcher.d.ts.map +1 -1
- package/library/message/index.d.js +25 -0
- package/library/message/index.d.ts.map +1 -1
- package/library/stream/file-stream.d.js +4 -0
- package/library/stream/file-stream.d.ts.map +1 -1
- package/library/stream/index.d.js +58 -0
- package/library/stream/index.d.ts.map +1 -1
- package/library/stream/readable-stream.d.js +5 -0
- package/library/stream/readable-stream.d.ts.map +1 -1
- package/library/stream/types.d.js +5 -0
- package/library/stream/types.d.ts.map +1 -1
- package/library/stream/writable-stream.d.js +5 -0
- package/library/stream/writable-stream.d.ts.map +1 -1
- package/library/types/index.d.js +5 -0
- package/library/types/index.d.ts +12 -4
- package/library/types/index.d.ts.map +1 -1
- package/library/utils/cache.d.js +5 -0
- package/library/utils/cache.d.ts.map +1 -1
- package/library/utils/cookie.d.js +5 -0
- package/library/utils/cookie.d.ts.map +1 -1
- package/library/utils/debug.d.js +5 -0
- package/library/utils/debug.d.ts.map +1 -1
- package/library/utils/index.d.js +94 -0
- package/library/utils/index.d.ts.map +1 -1
- package/library/utils/path-match.d.js +5 -0
- package/library/utils/path-match.d.ts.map +1 -1
- package/library/utils/protocol.d.js +5 -0
- package/library/utils/protocol.d.ts.map +1 -1
- package/package.json +14 -2
- package/react/library/__tests__/index.test.d.ts +2 -0
- package/react/library/__tests__/index.test.d.ts.map +1 -0
- package/react/library/__tests__/index.test.tsx +799 -0
- package/react/library/index.d.ts +117 -0
- package/react/library/index.d.ts.map +1 -0
- package/react/library/index.js +223 -0
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { useClient, useServer, useServerHandler, useServerHandlerMap } from '../index';
|
|
4
|
+
import { requestIframeClient, clearRequestIframeClientCache } from '../../../library/api/client';
|
|
5
|
+
import { requestIframeServer, clearRequestIframeServerCache } from '../../../library/api/server';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create test iframe
|
|
9
|
+
*/
|
|
10
|
+
function createTestIframe(origin: string): HTMLIFrameElement {
|
|
11
|
+
const iframe = document.createElement('iframe');
|
|
12
|
+
iframe.src = `${origin}/test.html`;
|
|
13
|
+
document.body.appendChild(iframe);
|
|
14
|
+
return iframe;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Cleanup test iframe
|
|
19
|
+
*/
|
|
20
|
+
function cleanupIframe(iframe: HTMLIFrameElement): void {
|
|
21
|
+
if (iframe.parentNode) {
|
|
22
|
+
iframe.parentNode.removeChild(iframe);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('React Hooks', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
clearRequestIframeClientCache();
|
|
29
|
+
clearRequestIframeServerCache();
|
|
30
|
+
document.querySelectorAll('iframe').forEach((iframe) => {
|
|
31
|
+
if (iframe.parentNode) {
|
|
32
|
+
iframe.parentNode.removeChild(iframe);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
clearRequestIframeClientCache();
|
|
39
|
+
clearRequestIframeServerCache();
|
|
40
|
+
document.querySelectorAll('iframe').forEach((iframe) => {
|
|
41
|
+
if (iframe.parentNode) {
|
|
42
|
+
iframe.parentNode.removeChild(iframe);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('useClient', () => {
|
|
48
|
+
it('should return null when getTarget returns null', () => {
|
|
49
|
+
const { result } = renderHook(() => useClient(() => null));
|
|
50
|
+
expect(result.current).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should create client when getTarget returns valid target', async () => {
|
|
54
|
+
const iframe = createTestIframe('https://example.com');
|
|
55
|
+
const mockContentWindow = {
|
|
56
|
+
postMessage: jest.fn()
|
|
57
|
+
};
|
|
58
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
59
|
+
value: mockContentWindow,
|
|
60
|
+
writable: true,
|
|
61
|
+
configurable: true
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const { result } = renderHook(() => useClient(() => iframe));
|
|
65
|
+
|
|
66
|
+
await waitFor(() => {
|
|
67
|
+
expect(result.current).toBeDefined();
|
|
68
|
+
expect(result.current).not.toBeNull();
|
|
69
|
+
}, { timeout: 2000 });
|
|
70
|
+
|
|
71
|
+
cleanupIframe(iframe);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should create client with options', async () => {
|
|
75
|
+
const iframe = createTestIframe('https://example.com');
|
|
76
|
+
const mockContentWindow = {
|
|
77
|
+
postMessage: jest.fn()
|
|
78
|
+
};
|
|
79
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
80
|
+
value: mockContentWindow,
|
|
81
|
+
writable: true,
|
|
82
|
+
configurable: true
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const options = { secretKey: 'test-key', timeout: 1000 };
|
|
86
|
+
const { result } = renderHook(() => useClient(() => iframe, options));
|
|
87
|
+
|
|
88
|
+
await waitFor(() => {
|
|
89
|
+
expect(result.current).toBeDefined();
|
|
90
|
+
if (result.current) {
|
|
91
|
+
expect(result.current.isOpen).toBe(true);
|
|
92
|
+
}
|
|
93
|
+
}, { timeout: 2000 });
|
|
94
|
+
|
|
95
|
+
cleanupIframe(iframe);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should destroy client on unmount', async () => {
|
|
99
|
+
const iframe = createTestIframe('https://example.com');
|
|
100
|
+
const mockContentWindow = {
|
|
101
|
+
postMessage: jest.fn()
|
|
102
|
+
};
|
|
103
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
104
|
+
value: mockContentWindow,
|
|
105
|
+
writable: true,
|
|
106
|
+
configurable: true
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const { result, unmount } = renderHook(() => useClient(() => iframe));
|
|
110
|
+
|
|
111
|
+
await waitFor(() => {
|
|
112
|
+
expect(result.current).toBeDefined();
|
|
113
|
+
}, { timeout: 2000 });
|
|
114
|
+
|
|
115
|
+
const client = result.current;
|
|
116
|
+
expect(client).toBeDefined();
|
|
117
|
+
|
|
118
|
+
unmount();
|
|
119
|
+
|
|
120
|
+
// Client should be destroyed
|
|
121
|
+
if (client) {
|
|
122
|
+
expect(client.isOpen).toBe(false);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
cleanupIframe(iframe);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should recreate client when getTarget function changes', async () => {
|
|
129
|
+
const iframe1 = createTestIframe('https://example.com');
|
|
130
|
+
const mockContentWindow1 = {
|
|
131
|
+
postMessage: jest.fn()
|
|
132
|
+
};
|
|
133
|
+
Object.defineProperty(iframe1, 'contentWindow', {
|
|
134
|
+
value: mockContentWindow1,
|
|
135
|
+
writable: true,
|
|
136
|
+
configurable: true
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const { result, rerender } = renderHook(
|
|
140
|
+
(props: { getTarget: () => HTMLIFrameElement | Window | null }) => useClient(props.getTarget),
|
|
141
|
+
{ initialProps: { getTarget: () => iframe1 } }
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
await waitFor(() => {
|
|
145
|
+
expect(result.current).toBeDefined();
|
|
146
|
+
}, { timeout: 2000 });
|
|
147
|
+
|
|
148
|
+
const client1 = result.current;
|
|
149
|
+
expect(client1).toBeDefined();
|
|
150
|
+
|
|
151
|
+
const iframe2 = createTestIframe('https://example2.com');
|
|
152
|
+
const mockContentWindow2 = {
|
|
153
|
+
postMessage: jest.fn()
|
|
154
|
+
};
|
|
155
|
+
Object.defineProperty(iframe2, 'contentWindow', {
|
|
156
|
+
value: mockContentWindow2,
|
|
157
|
+
writable: true,
|
|
158
|
+
configurable: true
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
rerender({ getTarget: () => iframe2 });
|
|
162
|
+
|
|
163
|
+
await waitFor(() => {
|
|
164
|
+
// Previous client should be destroyed
|
|
165
|
+
if (client1) {
|
|
166
|
+
expect(client1.isOpen).toBe(false);
|
|
167
|
+
}
|
|
168
|
+
// New client should be created
|
|
169
|
+
expect(result.current).toBeDefined();
|
|
170
|
+
expect(result.current).not.toBe(client1);
|
|
171
|
+
}, { timeout: 2000 });
|
|
172
|
+
|
|
173
|
+
cleanupIframe(iframe1);
|
|
174
|
+
cleanupIframe(iframe2);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should handle getTarget returning null after initial mount', async () => {
|
|
178
|
+
const iframe = createTestIframe('https://example.com');
|
|
179
|
+
const mockContentWindow = {
|
|
180
|
+
postMessage: jest.fn()
|
|
181
|
+
};
|
|
182
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
183
|
+
value: mockContentWindow,
|
|
184
|
+
writable: true,
|
|
185
|
+
configurable: true
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
type Props = { getTarget: () => HTMLIFrameElement | Window | null };
|
|
189
|
+
const { result, rerender } = renderHook(
|
|
190
|
+
(props: Props) => useClient(props.getTarget),
|
|
191
|
+
{ initialProps: { getTarget: () => iframe } as Props }
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
await waitFor(() => {
|
|
195
|
+
expect(result.current).toBeDefined();
|
|
196
|
+
}, { timeout: 2000 });
|
|
197
|
+
|
|
198
|
+
// Change getTarget to return null
|
|
199
|
+
rerender({ getTarget: () => null } as Props);
|
|
200
|
+
|
|
201
|
+
// Wait for cleanup
|
|
202
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
203
|
+
|
|
204
|
+
// Note: clientRef.current may still hold the old client until next render
|
|
205
|
+
// This is expected behavior with useRef - the component needs to re-render
|
|
206
|
+
// to reflect the change. In real usage, this would trigger a re-render.
|
|
207
|
+
|
|
208
|
+
cleanupIframe(iframe);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should work with function pattern', async () => {
|
|
212
|
+
const iframeRef = { current: null as HTMLIFrameElement | null };
|
|
213
|
+
const iframe = createTestIframe('https://example.com');
|
|
214
|
+
const mockContentWindow = {
|
|
215
|
+
postMessage: jest.fn()
|
|
216
|
+
};
|
|
217
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
218
|
+
value: mockContentWindow,
|
|
219
|
+
writable: true,
|
|
220
|
+
configurable: true
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const { result, rerender } = renderHook(() => useClient(() => iframeRef.current));
|
|
224
|
+
|
|
225
|
+
// Initially null (ref not set)
|
|
226
|
+
expect(result.current).toBeNull();
|
|
227
|
+
|
|
228
|
+
// Set ref
|
|
229
|
+
iframeRef.current = iframe;
|
|
230
|
+
rerender();
|
|
231
|
+
|
|
232
|
+
await waitFor(() => {
|
|
233
|
+
expect(result.current).toBeDefined();
|
|
234
|
+
}, { timeout: 2000 });
|
|
235
|
+
|
|
236
|
+
cleanupIframe(iframe);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should work with ref object directly', async () => {
|
|
240
|
+
const iframe = createTestIframe('https://example.com');
|
|
241
|
+
const mockContentWindow = {
|
|
242
|
+
postMessage: jest.fn()
|
|
243
|
+
};
|
|
244
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
245
|
+
value: mockContentWindow,
|
|
246
|
+
writable: true,
|
|
247
|
+
configurable: true
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const { result } = renderHook(() => {
|
|
251
|
+
const iframeRef = useRef<HTMLIFrameElement | null>(iframe);
|
|
252
|
+
return useClient(iframeRef);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
await waitFor(() => {
|
|
256
|
+
expect(result.current).toBeDefined();
|
|
257
|
+
expect(result.current).not.toBeNull();
|
|
258
|
+
}, { timeout: 2000 });
|
|
259
|
+
|
|
260
|
+
cleanupIframe(iframe);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should recreate client when deps change', async () => {
|
|
264
|
+
const iframe = createTestIframe('https://example.com');
|
|
265
|
+
const mockContentWindow = {
|
|
266
|
+
postMessage: jest.fn()
|
|
267
|
+
};
|
|
268
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
269
|
+
value: mockContentWindow,
|
|
270
|
+
writable: true,
|
|
271
|
+
configurable: true
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
let userId = 1;
|
|
275
|
+
const { result, rerender } = renderHook(() => {
|
|
276
|
+
return useClient(() => iframe, { secretKey: `key-${userId}` }, [userId]);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
await waitFor(() => {
|
|
280
|
+
expect(result.current).toBeDefined();
|
|
281
|
+
}, { timeout: 2000 });
|
|
282
|
+
|
|
283
|
+
const client1 = result.current;
|
|
284
|
+
|
|
285
|
+
// Change dependency
|
|
286
|
+
userId = 2;
|
|
287
|
+
rerender();
|
|
288
|
+
|
|
289
|
+
await waitFor(() => {
|
|
290
|
+
// Previous client should be destroyed
|
|
291
|
+
if (client1) {
|
|
292
|
+
expect(client1.isOpen).toBe(false);
|
|
293
|
+
}
|
|
294
|
+
// New client should be created
|
|
295
|
+
expect(result.current).toBeDefined();
|
|
296
|
+
expect(result.current).not.toBe(client1);
|
|
297
|
+
}, { timeout: 2000 });
|
|
298
|
+
|
|
299
|
+
cleanupIframe(iframe);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe('useServer', () => {
|
|
304
|
+
it('should create server instance', () => {
|
|
305
|
+
const { result } = renderHook(() => useServer());
|
|
306
|
+
|
|
307
|
+
expect(result.current).toBeDefined();
|
|
308
|
+
if (result.current) {
|
|
309
|
+
expect(result.current.isOpen).toBe(true);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should create server with options', () => {
|
|
314
|
+
const options = { secretKey: 'test-key', ackTimeout: 1000 };
|
|
315
|
+
const { result } = renderHook(() => useServer(options));
|
|
316
|
+
|
|
317
|
+
expect(result.current).toBeDefined();
|
|
318
|
+
if (result.current) {
|
|
319
|
+
expect(result.current.secretKey).toBe('test-key');
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should destroy server on unmount', async () => {
|
|
324
|
+
const { result, unmount } = renderHook(() => useServer());
|
|
325
|
+
const server = result.current;
|
|
326
|
+
|
|
327
|
+
expect(server).toBeDefined();
|
|
328
|
+
|
|
329
|
+
unmount();
|
|
330
|
+
|
|
331
|
+
// Server should be destroyed
|
|
332
|
+
if (server) {
|
|
333
|
+
expect(server.isOpen).toBe(false);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should create server only once on mount', () => {
|
|
338
|
+
const { result, rerender } = renderHook(() => useServer());
|
|
339
|
+
const server1 = result.current;
|
|
340
|
+
|
|
341
|
+
expect(server1).toBeDefined();
|
|
342
|
+
|
|
343
|
+
rerender();
|
|
344
|
+
|
|
345
|
+
// Should return the same instance (not null)
|
|
346
|
+
expect(result.current).toBeDefined();
|
|
347
|
+
expect(result.current).toBe(server1);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should recreate server when deps change', async () => {
|
|
351
|
+
let userId = 1;
|
|
352
|
+
const { result, rerender } = renderHook(() => {
|
|
353
|
+
return useServer({ secretKey: `key-${userId}` }, [userId]);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const server1 = result.current;
|
|
357
|
+
expect(server1).toBeDefined();
|
|
358
|
+
|
|
359
|
+
// Change dependency
|
|
360
|
+
userId = 2;
|
|
361
|
+
rerender();
|
|
362
|
+
|
|
363
|
+
await waitFor(() => {
|
|
364
|
+
// Previous server should be destroyed
|
|
365
|
+
if (server1) {
|
|
366
|
+
expect(server1.isOpen).toBe(false);
|
|
367
|
+
}
|
|
368
|
+
// New server should be created
|
|
369
|
+
expect(result.current).toBeDefined();
|
|
370
|
+
expect(result.current).not.toBe(server1);
|
|
371
|
+
}, { timeout: 2000 });
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
describe('useServerHandler', () => {
|
|
376
|
+
it('should register handler when server is available', async () => {
|
|
377
|
+
const origin = 'https://example.com';
|
|
378
|
+
const iframe = createTestIframe(origin);
|
|
379
|
+
const mockContentWindow = {
|
|
380
|
+
postMessage: jest.fn((msg: any) => {
|
|
381
|
+
if (msg.type === 'request') {
|
|
382
|
+
// Send ACK
|
|
383
|
+
window.dispatchEvent(
|
|
384
|
+
new MessageEvent('message', {
|
|
385
|
+
data: {
|
|
386
|
+
__requestIframe__: 1,
|
|
387
|
+
type: 'ack',
|
|
388
|
+
requestId: msg.requestId,
|
|
389
|
+
path: msg.path,
|
|
390
|
+
timestamp: Date.now()
|
|
391
|
+
},
|
|
392
|
+
origin,
|
|
393
|
+
source: mockContentWindow as any
|
|
394
|
+
})
|
|
395
|
+
);
|
|
396
|
+
// Send response
|
|
397
|
+
setTimeout(() => {
|
|
398
|
+
window.dispatchEvent(
|
|
399
|
+
new MessageEvent('message', {
|
|
400
|
+
data: {
|
|
401
|
+
__requestIframe__: 1,
|
|
402
|
+
type: 'response',
|
|
403
|
+
requestId: msg.requestId,
|
|
404
|
+
data: { success: true },
|
|
405
|
+
status: 200,
|
|
406
|
+
statusText: 'OK',
|
|
407
|
+
timestamp: Date.now()
|
|
408
|
+
},
|
|
409
|
+
origin,
|
|
410
|
+
source: mockContentWindow as any
|
|
411
|
+
})
|
|
412
|
+
);
|
|
413
|
+
}, 10);
|
|
414
|
+
}
|
|
415
|
+
})
|
|
416
|
+
};
|
|
417
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
418
|
+
value: mockContentWindow,
|
|
419
|
+
writable: true,
|
|
420
|
+
configurable: true
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const handler = jest.fn((req, res) => {
|
|
424
|
+
res.send({ success: true });
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
renderHook(() => {
|
|
428
|
+
const server = useServer();
|
|
429
|
+
useServerHandler(server, 'api/test', handler);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// Wait for handler to be registered
|
|
433
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
434
|
+
|
|
435
|
+
// Send a test request
|
|
436
|
+
const client = requestIframeClient(iframe);
|
|
437
|
+
await client.send('api/test', {});
|
|
438
|
+
|
|
439
|
+
await waitFor(() => {
|
|
440
|
+
expect(handler).toHaveBeenCalled();
|
|
441
|
+
}, { timeout: 3000 });
|
|
442
|
+
|
|
443
|
+
client.destroy();
|
|
444
|
+
cleanupIframe(iframe);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should not register handler when server is null', () => {
|
|
448
|
+
const handler = jest.fn();
|
|
449
|
+
|
|
450
|
+
renderHook(() => {
|
|
451
|
+
useServerHandler(null, 'api/test', handler);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// Should not throw
|
|
455
|
+
expect(handler).not.toHaveBeenCalled();
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('should unregister handler on unmount', async () => {
|
|
459
|
+
const origin = 'https://example.com';
|
|
460
|
+
const iframe = createTestIframe(origin);
|
|
461
|
+
const mockContentWindow = {
|
|
462
|
+
postMessage: jest.fn()
|
|
463
|
+
};
|
|
464
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
465
|
+
value: mockContentWindow,
|
|
466
|
+
writable: true,
|
|
467
|
+
configurable: true
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const handler = jest.fn();
|
|
471
|
+
|
|
472
|
+
const { unmount } = renderHook(() => {
|
|
473
|
+
const server = useServer();
|
|
474
|
+
useServerHandler(server, 'api/test', handler);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
unmount();
|
|
478
|
+
|
|
479
|
+
// Handler should be unregistered
|
|
480
|
+
const client = requestIframeClient(iframe);
|
|
481
|
+
const server = requestIframeServer();
|
|
482
|
+
|
|
483
|
+
// Try to send request - should get METHOD_NOT_FOUND
|
|
484
|
+
client.send('api/test', {}).catch(() => {});
|
|
485
|
+
|
|
486
|
+
await waitFor(() => {
|
|
487
|
+
expect(handler).not.toHaveBeenCalled();
|
|
488
|
+
}, { timeout: 1000 });
|
|
489
|
+
|
|
490
|
+
client.destroy();
|
|
491
|
+
server.destroy();
|
|
492
|
+
cleanupIframe(iframe);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('should re-register handler when dependencies change', async () => {
|
|
496
|
+
const origin = 'https://example.com';
|
|
497
|
+
const iframe = createTestIframe(origin);
|
|
498
|
+
const mockContentWindow = {
|
|
499
|
+
postMessage: jest.fn((msg: any) => {
|
|
500
|
+
if (msg.type === 'request') {
|
|
501
|
+
window.dispatchEvent(
|
|
502
|
+
new MessageEvent('message', {
|
|
503
|
+
data: {
|
|
504
|
+
__requestIframe__: 1,
|
|
505
|
+
type: 'ack',
|
|
506
|
+
requestId: msg.requestId,
|
|
507
|
+
path: msg.path,
|
|
508
|
+
timestamp: Date.now()
|
|
509
|
+
},
|
|
510
|
+
origin,
|
|
511
|
+
source: mockContentWindow as any
|
|
512
|
+
})
|
|
513
|
+
);
|
|
514
|
+
setTimeout(() => {
|
|
515
|
+
window.dispatchEvent(
|
|
516
|
+
new MessageEvent('message', {
|
|
517
|
+
data: {
|
|
518
|
+
__requestIframe__: 1,
|
|
519
|
+
type: 'response',
|
|
520
|
+
requestId: msg.requestId,
|
|
521
|
+
data: { success: true },
|
|
522
|
+
status: 200,
|
|
523
|
+
statusText: 'OK',
|
|
524
|
+
timestamp: Date.now()
|
|
525
|
+
},
|
|
526
|
+
origin,
|
|
527
|
+
source: mockContentWindow as any
|
|
528
|
+
})
|
|
529
|
+
);
|
|
530
|
+
}, 10);
|
|
531
|
+
}
|
|
532
|
+
})
|
|
533
|
+
};
|
|
534
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
535
|
+
value: mockContentWindow,
|
|
536
|
+
writable: true,
|
|
537
|
+
configurable: true
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
let userId = 1;
|
|
541
|
+
const handler = jest.fn((req, res) => {
|
|
542
|
+
res.send({ userId });
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
const { rerender } = renderHook(() => {
|
|
546
|
+
const server = useServer();
|
|
547
|
+
useServerHandler(server, 'api/test', handler, [userId]);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Wait for initial registration
|
|
551
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
552
|
+
|
|
553
|
+
const client = requestIframeClient(iframe);
|
|
554
|
+
await client.send('api/test', {});
|
|
555
|
+
|
|
556
|
+
await waitFor(() => {
|
|
557
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
558
|
+
}, { timeout: 3000 });
|
|
559
|
+
|
|
560
|
+
// Change dependency
|
|
561
|
+
userId = 2;
|
|
562
|
+
rerender();
|
|
563
|
+
|
|
564
|
+
// Wait for re-registration
|
|
565
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
566
|
+
|
|
567
|
+
await client.send('api/test', {});
|
|
568
|
+
|
|
569
|
+
await waitFor(() => {
|
|
570
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
571
|
+
}, { timeout: 3000 });
|
|
572
|
+
|
|
573
|
+
client.destroy();
|
|
574
|
+
cleanupIframe(iframe);
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
describe('useServerHandlerMap', () => {
|
|
579
|
+
it('should register handlers using map when server is available', async () => {
|
|
580
|
+
const origin = 'https://example.com';
|
|
581
|
+
const iframe = createTestIframe(origin);
|
|
582
|
+
const mockContentWindow = {
|
|
583
|
+
postMessage: jest.fn((msg: any) => {
|
|
584
|
+
if (msg.type === 'request') {
|
|
585
|
+
window.dispatchEvent(
|
|
586
|
+
new MessageEvent('message', {
|
|
587
|
+
data: {
|
|
588
|
+
__requestIframe__: 1,
|
|
589
|
+
type: 'ack',
|
|
590
|
+
requestId: msg.requestId,
|
|
591
|
+
path: msg.path,
|
|
592
|
+
timestamp: Date.now()
|
|
593
|
+
},
|
|
594
|
+
origin,
|
|
595
|
+
source: mockContentWindow as any
|
|
596
|
+
})
|
|
597
|
+
);
|
|
598
|
+
setTimeout(() => {
|
|
599
|
+
window.dispatchEvent(
|
|
600
|
+
new MessageEvent('message', {
|
|
601
|
+
data: {
|
|
602
|
+
__requestIframe__: 1,
|
|
603
|
+
type: 'response',
|
|
604
|
+
requestId: msg.requestId,
|
|
605
|
+
data: { success: true },
|
|
606
|
+
status: 200,
|
|
607
|
+
statusText: 'OK',
|
|
608
|
+
timestamp: Date.now()
|
|
609
|
+
},
|
|
610
|
+
origin,
|
|
611
|
+
source: mockContentWindow as any
|
|
612
|
+
})
|
|
613
|
+
);
|
|
614
|
+
}, 10);
|
|
615
|
+
}
|
|
616
|
+
})
|
|
617
|
+
};
|
|
618
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
619
|
+
value: mockContentWindow,
|
|
620
|
+
writable: true,
|
|
621
|
+
configurable: true
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
const handlers = {
|
|
625
|
+
'api/user': jest.fn((req, res) => res.send({ user: 'test' })),
|
|
626
|
+
'api/post': jest.fn((req, res) => res.send({ post: 'test' }))
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
renderHook(() => {
|
|
630
|
+
const server = useServer();
|
|
631
|
+
useServerHandlerMap(server, handlers);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Wait for handlers to be registered
|
|
635
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
636
|
+
|
|
637
|
+
// Send test requests
|
|
638
|
+
const client = requestIframeClient(iframe);
|
|
639
|
+
await client.send('api/user', {});
|
|
640
|
+
await client.send('api/post', {});
|
|
641
|
+
|
|
642
|
+
await waitFor(() => {
|
|
643
|
+
expect(handlers['api/user']).toHaveBeenCalled();
|
|
644
|
+
expect(handlers['api/post']).toHaveBeenCalled();
|
|
645
|
+
}, { timeout: 3000 });
|
|
646
|
+
|
|
647
|
+
client.destroy();
|
|
648
|
+
cleanupIframe(iframe);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it('should not register handlers when server is null', () => {
|
|
652
|
+
const handlers = {
|
|
653
|
+
'api/user': jest.fn()
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
renderHook(() => {
|
|
657
|
+
useServerHandlerMap(null, handlers);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// Should not throw
|
|
661
|
+
expect(handlers['api/user']).not.toHaveBeenCalled();
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it('should unregister all handlers on unmount', async () => {
|
|
665
|
+
const origin = 'https://example.com';
|
|
666
|
+
const iframe = createTestIframe(origin);
|
|
667
|
+
const mockContentWindow = {
|
|
668
|
+
postMessage: jest.fn()
|
|
669
|
+
};
|
|
670
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
671
|
+
value: mockContentWindow,
|
|
672
|
+
writable: true,
|
|
673
|
+
configurable: true
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
const handlers = {
|
|
677
|
+
'api/user': jest.fn(),
|
|
678
|
+
'api/post': jest.fn()
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
const { unmount } = renderHook(() => {
|
|
682
|
+
const server = useServer();
|
|
683
|
+
useServerHandlerMap(server, handlers);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
unmount();
|
|
687
|
+
|
|
688
|
+
// Handlers should be unregistered
|
|
689
|
+
const client = requestIframeClient(iframe);
|
|
690
|
+
|
|
691
|
+
// Try to send requests - should get METHOD_NOT_FOUND
|
|
692
|
+
client.send('api/user', {}).catch(() => {});
|
|
693
|
+
client.send('api/post', {}).catch(() => {});
|
|
694
|
+
|
|
695
|
+
await waitFor(() => {
|
|
696
|
+
expect(handlers['api/user']).not.toHaveBeenCalled();
|
|
697
|
+
expect(handlers['api/post']).not.toHaveBeenCalled();
|
|
698
|
+
}, { timeout: 1000 });
|
|
699
|
+
|
|
700
|
+
client.destroy();
|
|
701
|
+
cleanupIframe(iframe);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it('should re-register handlers when dependencies change', async () => {
|
|
705
|
+
const origin = 'https://example.com';
|
|
706
|
+
const iframe = createTestIframe(origin);
|
|
707
|
+
const mockContentWindow = {
|
|
708
|
+
postMessage: jest.fn((msg: any) => {
|
|
709
|
+
if (msg.type === 'request') {
|
|
710
|
+
window.dispatchEvent(
|
|
711
|
+
new MessageEvent('message', {
|
|
712
|
+
data: {
|
|
713
|
+
__requestIframe__: 1,
|
|
714
|
+
type: 'ack',
|
|
715
|
+
requestId: msg.requestId,
|
|
716
|
+
path: msg.path,
|
|
717
|
+
timestamp: Date.now()
|
|
718
|
+
},
|
|
719
|
+
origin,
|
|
720
|
+
source: mockContentWindow as any
|
|
721
|
+
})
|
|
722
|
+
);
|
|
723
|
+
setTimeout(() => {
|
|
724
|
+
window.dispatchEvent(
|
|
725
|
+
new MessageEvent('message', {
|
|
726
|
+
data: {
|
|
727
|
+
__requestIframe__: 1,
|
|
728
|
+
type: 'response',
|
|
729
|
+
requestId: msg.requestId,
|
|
730
|
+
data: { success: true },
|
|
731
|
+
status: 200,
|
|
732
|
+
statusText: 'OK',
|
|
733
|
+
timestamp: Date.now()
|
|
734
|
+
},
|
|
735
|
+
origin,
|
|
736
|
+
source: mockContentWindow as any
|
|
737
|
+
})
|
|
738
|
+
);
|
|
739
|
+
}, 10);
|
|
740
|
+
}
|
|
741
|
+
})
|
|
742
|
+
};
|
|
743
|
+
Object.defineProperty(iframe, 'contentWindow', {
|
|
744
|
+
value: mockContentWindow,
|
|
745
|
+
writable: true,
|
|
746
|
+
configurable: true
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
const handlers = {
|
|
750
|
+
'api/user': jest.fn((req, res) => res.send({}))
|
|
751
|
+
};
|
|
752
|
+
let userId = 1;
|
|
753
|
+
|
|
754
|
+
const { rerender } = renderHook(() => {
|
|
755
|
+
const server = useServer();
|
|
756
|
+
useServerHandlerMap(server, handlers, [userId]);
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// Wait for initial registration
|
|
760
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
761
|
+
|
|
762
|
+
const client = requestIframeClient(iframe);
|
|
763
|
+
await client.send('api/user', {});
|
|
764
|
+
|
|
765
|
+
await waitFor(() => {
|
|
766
|
+
expect(handlers['api/user']).toHaveBeenCalledTimes(1);
|
|
767
|
+
}, { timeout: 3000 });
|
|
768
|
+
|
|
769
|
+
// Change dependency
|
|
770
|
+
userId = 2;
|
|
771
|
+
rerender();
|
|
772
|
+
|
|
773
|
+
// Wait for re-registration
|
|
774
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
775
|
+
|
|
776
|
+
await client.send('api/user', {});
|
|
777
|
+
|
|
778
|
+
await waitFor(() => {
|
|
779
|
+
expect(handlers['api/user']).toHaveBeenCalledTimes(2);
|
|
780
|
+
}, { timeout: 3000 });
|
|
781
|
+
|
|
782
|
+
client.destroy();
|
|
783
|
+
cleanupIframe(iframe);
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
it('should handle empty handlers map', () => {
|
|
787
|
+
const handlers = {};
|
|
788
|
+
|
|
789
|
+
const { result } = renderHook(() => {
|
|
790
|
+
const server = useServer();
|
|
791
|
+
useServerHandlerMap(server, handlers);
|
|
792
|
+
return server;
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
// Should not throw
|
|
796
|
+
expect(result.current).toBeDefined();
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
});
|