request-iframe 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QUICKSTART.CN.md +35 -8
- package/QUICKSTART.md +35 -8
- package/README.CN.md +439 -36
- package/README.md +496 -30
- package/library/__tests__/channel.test.ts +420 -0
- package/library/__tests__/coverage-branches.test.ts +356 -0
- package/library/__tests__/debug.test.ts +588 -0
- package/library/__tests__/dispatcher.test.ts +481 -0
- package/library/__tests__/requestIframe.test.ts +3163 -185
- package/library/__tests__/server.test.ts +738 -0
- package/library/__tests__/stream.test.ts +46 -15
- package/library/api/client.d.ts.map +1 -1
- package/library/api/client.js +12 -6
- package/library/api/server.d.ts +4 -3
- package/library/api/server.d.ts.map +1 -1
- package/library/api/server.js +25 -7
- package/library/constants/index.d.ts +14 -4
- package/library/constants/index.d.ts.map +1 -1
- package/library/constants/index.js +15 -7
- package/library/constants/messages.d.ts +37 -0
- package/library/constants/messages.d.ts.map +1 -1
- package/library/constants/messages.js +38 -1
- package/library/core/client-server.d.ts +105 -0
- package/library/core/client-server.d.ts.map +1 -0
- package/library/core/client-server.js +289 -0
- package/library/core/client.d.ts +53 -10
- package/library/core/client.d.ts.map +1 -1
- package/library/core/client.js +529 -207
- package/library/core/request.d.ts +3 -1
- package/library/core/request.d.ts.map +1 -1
- package/library/core/request.js +2 -1
- package/library/core/response.d.ts +30 -4
- package/library/core/response.d.ts.map +1 -1
- package/library/core/response.js +176 -100
- package/library/core/server-client.d.ts +3 -1
- package/library/core/server-client.d.ts.map +1 -1
- package/library/core/server-client.js +19 -9
- package/library/core/server.d.ts +22 -1
- package/library/core/server.d.ts.map +1 -1
- package/library/core/server.js +304 -55
- package/library/index.d.ts +3 -2
- package/library/index.d.ts.map +1 -1
- package/library/index.js +34 -5
- package/library/interceptors/index.d.ts.map +1 -1
- package/library/message/channel.d.ts +3 -1
- package/library/message/channel.d.ts.map +1 -1
- package/library/message/dispatcher.d.ts +7 -2
- package/library/message/dispatcher.d.ts.map +1 -1
- package/library/message/dispatcher.js +48 -2
- package/library/message/index.d.ts.map +1 -1
- package/library/stream/file-stream.d.ts +5 -0
- package/library/stream/file-stream.d.ts.map +1 -1
- package/library/stream/file-stream.js +41 -12
- package/library/stream/index.d.ts +11 -1
- package/library/stream/index.d.ts.map +1 -1
- package/library/stream/index.js +21 -3
- package/library/stream/readable-stream.d.ts.map +1 -1
- package/library/stream/readable-stream.js +32 -30
- package/library/stream/types.d.ts +20 -2
- package/library/stream/types.d.ts.map +1 -1
- package/library/stream/writable-stream.d.ts +2 -1
- package/library/stream/writable-stream.d.ts.map +1 -1
- package/library/stream/writable-stream.js +13 -10
- package/library/types/index.d.ts +106 -32
- package/library/types/index.d.ts.map +1 -1
- package/library/utils/cache.d.ts +24 -0
- package/library/utils/cache.d.ts.map +1 -1
- package/library/utils/cache.js +76 -0
- package/library/utils/cookie.d.ts.map +1 -1
- package/library/utils/debug.d.ts.map +1 -1
- package/library/utils/debug.js +382 -20
- package/library/utils/index.d.ts +19 -0
- package/library/utils/index.d.ts.map +1 -1
- package/library/utils/index.js +113 -2
- package/library/utils/path-match.d.ts +16 -0
- package/library/utils/path-match.d.ts.map +1 -1
- package/library/utils/path-match.js +65 -0
- package/library/utils/protocol.d.ts.map +1 -1
- package/package.json +4 -1
- package/react/library/__tests__/index.test.tsx +274 -281
- package/react/library/index.d.ts +4 -3
- package/react/library/index.d.ts.map +1 -1
- package/react/library/index.js +225 -158
- package/react/package.json +7 -0
package/README.md
CHANGED
|
@@ -34,6 +34,13 @@ Communicate with iframes like sending HTTP requests! A cross-origin iframe commu
|
|
|
34
34
|
- [Trace Mode](#trace-mode)
|
|
35
35
|
- [Internationalization](#internationalization)
|
|
36
36
|
- [API Reference](#api-reference)
|
|
37
|
+
- [React Hooks](#react-hooks)
|
|
38
|
+
- [useClient](#useclienttargetfnorref-options-deps)
|
|
39
|
+
- [useServer](#useserveroptions-deps)
|
|
40
|
+
- [useServerHandler](#useserverhandlerserver-path-handler-deps)
|
|
41
|
+
- [useServerHandlerMap](#useserverhandlermapserver-map-deps)
|
|
42
|
+
- [Complete Example](#complete-example)
|
|
43
|
+
- [Best Practices](#best-practices)
|
|
37
44
|
- [Error Handling](#error-handling)
|
|
38
45
|
- [FAQ](#faq)
|
|
39
46
|
- [Development](#development)
|
|
@@ -67,7 +74,7 @@ In micro-frontend and iframe nesting scenarios, parent-child page communication
|
|
|
67
74
|
- ⏱️ **Smart Timeout** - Three-stage timeout (connection/sync/async), automatically detects long tasks
|
|
68
75
|
- 📦 **TypeScript** - Complete type definitions and IntelliSense
|
|
69
76
|
- 🔒 **Message Isolation** - secretKey mechanism prevents message cross-talk between multiple instances
|
|
70
|
-
- 📁 **File Transfer** - Support for
|
|
77
|
+
- 📁 **File Transfer** - Support for file sending via stream (client→server)
|
|
71
78
|
- 🌊 **Streaming** - Support for large file chunked transfer, supports async iterators
|
|
72
79
|
- 🌍 **Internationalization** - Error messages can be customized for i18n
|
|
73
80
|
- ✅ **Protocol Versioning** - Built-in version control for upgrade compatibility
|
|
@@ -136,7 +143,8 @@ In micro-frontend architecture, the main application needs to communicate with c
|
|
|
136
143
|
const client = requestIframeClient(iframe, { secretKey: 'main-app' });
|
|
137
144
|
|
|
138
145
|
// Get user info from child application
|
|
139
|
-
const
|
|
146
|
+
const userInfoResponse = await client.send('/api/user/info', {});
|
|
147
|
+
console.log(userInfoResponse.data); // User info data
|
|
140
148
|
|
|
141
149
|
// Notify child application to refresh data
|
|
142
150
|
await client.send('/api/data/refresh', { timestamp: Date.now() });
|
|
@@ -180,7 +188,8 @@ server.on('/api/data', async (req, res) => {
|
|
|
180
188
|
|
|
181
189
|
// Parent page (cross-origin)
|
|
182
190
|
const client = requestIframeClient(iframe, { secretKey: 'data-api' });
|
|
183
|
-
const
|
|
191
|
+
const response = await client.send('/api/data', {});
|
|
192
|
+
const data = response.data; // Successfully fetch cross-origin data
|
|
184
193
|
```
|
|
185
194
|
|
|
186
195
|
### File Preview and Download
|
|
@@ -202,8 +211,8 @@ server.on('/api/processFile', async (req, res) => {
|
|
|
202
211
|
|
|
203
212
|
// Parent page: download file
|
|
204
213
|
const response = await client.send('/api/processFile', { fileId: '123' });
|
|
205
|
-
if (response.
|
|
206
|
-
downloadFile(response.
|
|
214
|
+
if (response.data instanceof File || response.data instanceof Blob) {
|
|
215
|
+
downloadFile(response.data);
|
|
207
216
|
}
|
|
208
217
|
```
|
|
209
218
|
|
|
@@ -251,7 +260,7 @@ request-iframe uses a three-stage timeout strategy to intelligently adapt to dif
|
|
|
251
260
|
|
|
252
261
|
```typescript
|
|
253
262
|
client.send('/api/getData', data, {
|
|
254
|
-
ackTimeout:
|
|
263
|
+
ackTimeout: 1000, // Stage 1: ACK timeout (default 1000ms)
|
|
255
264
|
timeout: 5000, // Stage 2: Request timeout (default 5s)
|
|
256
265
|
asyncTimeout: 120000 // Stage 3: Async request timeout (default 120s)
|
|
257
266
|
});
|
|
@@ -291,7 +300,7 @@ Send REQUEST
|
|
|
291
300
|
|
|
292
301
|
| Stage | Timeout | Scenario |
|
|
293
302
|
|-------|---------|----------|
|
|
294
|
-
| ackTimeout | Short (
|
|
303
|
+
| ackTimeout | Short (1000ms) | Quickly detect if Server is online, avoid long waits for unreachable iframes. Increased from 500ms to accommodate slower environments or busy browsers |
|
|
295
304
|
| timeout | Medium (5s) | Suitable for simple synchronous processing, like reading data, parameter validation |
|
|
296
305
|
| asyncTimeout | Long (120s) | Suitable for complex async operations, like file processing, batch operations, third-party API calls |
|
|
297
306
|
|
|
@@ -471,10 +480,12 @@ server.on('/api/logout', (req, res) => {
|
|
|
471
480
|
await client.send('/api/login', { username: 'tom', password: '123' });
|
|
472
481
|
|
|
473
482
|
// Client side: Subsequent request to /api/getUserInfo (automatically carries authToken and userId)
|
|
474
|
-
const
|
|
483
|
+
const userInfoResponse = await client.send('/api/getUserInfo', {});
|
|
484
|
+
const userInfo = userInfoResponse.data;
|
|
475
485
|
|
|
476
486
|
// Client side: Request root path (only carries userId, because authToken's path is /api)
|
|
477
|
-
const
|
|
487
|
+
const rootResponse = await client.send('/other', {});
|
|
488
|
+
const rootData = rootResponse.data;
|
|
478
489
|
```
|
|
479
490
|
|
|
480
491
|
#### Client Cookie Management API
|
|
@@ -533,6 +544,8 @@ server.on('/api/data', (req, res) => {
|
|
|
533
544
|
|
|
534
545
|
### File Transfer
|
|
535
546
|
|
|
547
|
+
#### Server → Client (Server sends file to client)
|
|
548
|
+
|
|
536
549
|
```typescript
|
|
537
550
|
// Server side: Send file
|
|
538
551
|
server.on('/api/download', async (req, res) => {
|
|
@@ -549,32 +562,60 @@ server.on('/api/download', async (req, res) => {
|
|
|
549
562
|
|
|
550
563
|
// Client side: Receive
|
|
551
564
|
const response = await client.send('/api/download', {});
|
|
552
|
-
if (response.
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
// content is base64-encoded string
|
|
556
|
-
const binaryString = atob(content);
|
|
557
|
-
const blob = new Blob([binaryString], { type: mimeType });
|
|
565
|
+
if (response.data instanceof File || response.data instanceof Blob) {
|
|
566
|
+
const file = response.data instanceof File ? response.data : null;
|
|
567
|
+
const fileName = file?.name || 'download';
|
|
558
568
|
|
|
559
|
-
// Download file
|
|
560
|
-
const url = URL.createObjectURL(
|
|
569
|
+
// Download file directly using File/Blob
|
|
570
|
+
const url = URL.createObjectURL(response.data);
|
|
561
571
|
const a = document.createElement('a');
|
|
562
572
|
a.href = url;
|
|
563
|
-
a.download = fileName
|
|
573
|
+
a.download = fileName;
|
|
564
574
|
a.click();
|
|
575
|
+
URL.revokeObjectURL(url);
|
|
565
576
|
}
|
|
566
577
|
```
|
|
567
578
|
|
|
579
|
+
#### Client → Server (Client sends file to server)
|
|
580
|
+
|
|
581
|
+
Client sends file via stream only. Use `sendFile()` (or `send(path, file)`); server receives either `req.body` as File/Blob when `autoResolve: true` (default), or `req.stream` as `IframeFileReadableStream` when `autoResolve: false`.
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
// Client side: Send file (stream, autoResolve defaults to true)
|
|
585
|
+
const file = new File(['Hello Upload'], 'upload.txt', { type: 'text/plain' });
|
|
586
|
+
const response = await client.send('/api/upload', file);
|
|
587
|
+
|
|
588
|
+
// Or use sendFile explicitly
|
|
589
|
+
const blob = new Blob(['binary data'], { type: 'application/octet-stream' });
|
|
590
|
+
const response2 = await client.sendFile('/api/upload', blob, {
|
|
591
|
+
fileName: 'data.bin',
|
|
592
|
+
mimeType: 'application/octet-stream',
|
|
593
|
+
autoResolve: true // optional, default true: server gets File/Blob in req.body
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// Server side: Receive file (autoResolve true → req.body is File/Blob)
|
|
597
|
+
server.on('/api/upload', async (req, res) => {
|
|
598
|
+
const blob = req.body as Blob; // or File when client sent File
|
|
599
|
+
const text = await blob.text();
|
|
600
|
+
console.log('Received file content:', text);
|
|
601
|
+
res.send({ success: true, size: blob.size });
|
|
602
|
+
});
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**Note:** When using `client.send()` with a `File` or `Blob`, it automatically dispatches to `client.sendFile()`, which sends the file via stream. Server gets `req.body` as File/Blob when `autoResolve` is true (default), or `req.stream` / `req.body` as `IframeFileReadableStream` when `autoResolve` is false.
|
|
606
|
+
|
|
568
607
|
### Streaming
|
|
569
608
|
|
|
570
609
|
For large files or scenarios requiring chunked transfer, you can use streaming:
|
|
571
610
|
|
|
611
|
+
#### Server → Client (Server sends stream to client)
|
|
612
|
+
|
|
572
613
|
```typescript
|
|
573
614
|
import {
|
|
574
615
|
IframeWritableStream,
|
|
575
616
|
IframeFileWritableStream,
|
|
576
617
|
isIframeReadableStream,
|
|
577
|
-
|
|
618
|
+
isIframeFileReadableStream
|
|
578
619
|
} from 'request-iframe';
|
|
579
620
|
|
|
580
621
|
// Server side: Send data stream using iterator
|
|
@@ -622,6 +663,55 @@ if (isIframeReadableStream(response.stream)) {
|
|
|
622
663
|
}
|
|
623
664
|
```
|
|
624
665
|
|
|
666
|
+
#### Client → Server (Client sends stream to server)
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
import { IframeWritableStream } from 'request-iframe';
|
|
670
|
+
|
|
671
|
+
// Client side: Send stream to server
|
|
672
|
+
const stream = new IframeWritableStream({
|
|
673
|
+
chunked: true,
|
|
674
|
+
iterator: async function* () {
|
|
675
|
+
for (let i = 0; i < 5; i++) {
|
|
676
|
+
yield `Chunk ${i}`;
|
|
677
|
+
await new Promise(r => setTimeout(r, 50));
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// Use sendStream to send stream as request body
|
|
683
|
+
const response = await client.sendStream('/api/uploadStream', stream);
|
|
684
|
+
console.log('Upload result:', response.data);
|
|
685
|
+
|
|
686
|
+
// Or use send() - it automatically dispatches to sendStream for IframeWritableStream
|
|
687
|
+
const stream2 = new IframeWritableStream({
|
|
688
|
+
next: async () => ({ data: 'single chunk', done: true })
|
|
689
|
+
});
|
|
690
|
+
const response2 = await client.send('/api/uploadStream', stream2);
|
|
691
|
+
|
|
692
|
+
// Server side: Receive stream
|
|
693
|
+
server.on('/api/uploadStream', async (req, res) => {
|
|
694
|
+
// req.stream is available when client sends stream
|
|
695
|
+
if (req.stream) {
|
|
696
|
+
const chunks: string[] = [];
|
|
697
|
+
|
|
698
|
+
// Read stream chunk by chunk
|
|
699
|
+
for await (const chunk of req.stream) {
|
|
700
|
+
chunks.push(chunk);
|
|
701
|
+
console.log('Received chunk:', chunk);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
res.send({
|
|
705
|
+
success: true,
|
|
706
|
+
chunkCount: chunks.length,
|
|
707
|
+
chunks
|
|
708
|
+
});
|
|
709
|
+
} else {
|
|
710
|
+
res.status(400).send({ error: 'Expected stream body' });
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
```
|
|
714
|
+
|
|
625
715
|
**Stream Types:**
|
|
626
716
|
|
|
627
717
|
| Type | Description |
|
|
@@ -712,7 +802,7 @@ Create a Client instance.
|
|
|
712
802
|
| `target` | `HTMLIFrameElement \| Window` | Target iframe element or window object |
|
|
713
803
|
| `options.secretKey` | `string` | Message isolation identifier (optional) |
|
|
714
804
|
| `options.trace` | `boolean` | Whether to enable trace mode (optional) |
|
|
715
|
-
| `options.ackTimeout` | `number` | Global default ACK acknowledgment timeout (ms), default
|
|
805
|
+
| `options.ackTimeout` | `number` | Global default ACK acknowledgment timeout (ms), default 1000 |
|
|
716
806
|
| `options.timeout` | `number` | Global default request timeout (ms), default 5000 |
|
|
717
807
|
| `options.asyncTimeout` | `number` | Global default async timeout (ms), default 120000 |
|
|
718
808
|
|
|
@@ -728,7 +818,7 @@ Create a Server instance.
|
|
|
728
818
|
|-----------|------|-------------|
|
|
729
819
|
| `options.secretKey` | `string` | Message isolation identifier (optional) |
|
|
730
820
|
| `options.trace` | `boolean` | Whether to enable trace mode (optional) |
|
|
731
|
-
| `options.ackTimeout` | `number` | Wait for client acknowledgment timeout (ms), default
|
|
821
|
+
| `options.ackTimeout` | `number` | Wait for client acknowledgment timeout (ms), default 1000 |
|
|
732
822
|
|
|
733
823
|
**Returns:** `RequestIframeServer`
|
|
734
824
|
|
|
@@ -736,15 +826,15 @@ Create a Server instance.
|
|
|
736
826
|
|
|
737
827
|
#### client.send(path, body?, options?)
|
|
738
828
|
|
|
739
|
-
Send a request.
|
|
829
|
+
Send a request. Automatically dispatches to `sendFile()` or `sendStream()` based on body type.
|
|
740
830
|
|
|
741
831
|
**Parameters:**
|
|
742
832
|
|
|
743
833
|
| Parameter | Type | Description |
|
|
744
834
|
|-----------|------|-------------|
|
|
745
835
|
| `path` | `string` | Request path |
|
|
746
|
-
| `body` | `
|
|
747
|
-
| `options.ackTimeout` | `number` | ACK acknowledgment timeout (ms), default
|
|
836
|
+
| `body` | `any` | Request data (optional). Can be plain object, File, Blob, or IframeWritableStream. Automatically dispatches: File/Blob → `sendFile()`, IframeWritableStream → `sendStream()` |
|
|
837
|
+
| `options.ackTimeout` | `number` | ACK acknowledgment timeout (ms), default 1000 |
|
|
748
838
|
| `options.timeout` | `number` | Request timeout (ms), default 5000 |
|
|
749
839
|
| `options.asyncTimeout` | `number` | Async timeout (ms), default 120000 |
|
|
750
840
|
| `options.headers` | `object` | Request headers (optional) |
|
|
@@ -755,20 +845,78 @@ Send a request.
|
|
|
755
845
|
|
|
756
846
|
```typescript
|
|
757
847
|
interface Response<T = any> {
|
|
758
|
-
data: T; // Response data
|
|
848
|
+
data: T; // Response data (File/Blob for auto-resolved file streams)
|
|
759
849
|
status: number; // Status code
|
|
760
850
|
statusText: string; // Status text
|
|
761
851
|
requestId: string; // Request ID
|
|
762
852
|
headers?: Record<string, string | string[]>; // Response headers (Set-Cookie is array)
|
|
763
|
-
fileData?: { // File data (if any)
|
|
764
|
-
content: string; // base64-encoded content
|
|
765
|
-
mimeType?: string;
|
|
766
|
-
fileName?: string;
|
|
767
|
-
};
|
|
768
853
|
stream?: IIframeReadableStream<T>; // Stream response (if any)
|
|
769
854
|
}
|
|
770
855
|
```
|
|
771
856
|
|
|
857
|
+
**Examples:**
|
|
858
|
+
|
|
859
|
+
```typescript
|
|
860
|
+
// Send plain object (auto Content-Type: application/json)
|
|
861
|
+
await client.send('/api/data', { name: 'test' });
|
|
862
|
+
|
|
863
|
+
// Send string (auto Content-Type: text/plain)
|
|
864
|
+
await client.send('/api/text', 'Hello');
|
|
865
|
+
|
|
866
|
+
// Send File/Blob (auto-dispatches to sendFile)
|
|
867
|
+
const file = new File(['content'], 'test.txt');
|
|
868
|
+
await client.send('/api/upload', file);
|
|
869
|
+
|
|
870
|
+
// Send stream (auto-dispatches to sendStream)
|
|
871
|
+
const stream = new IframeWritableStream({ iterator: async function* () { yield 'data'; } });
|
|
872
|
+
await client.send('/api/uploadStream', stream);
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
#### client.sendFile(path, content, options?)
|
|
876
|
+
|
|
877
|
+
Send file as request body (via stream; server receives File/Blob when autoResolve is true).
|
|
878
|
+
|
|
879
|
+
**Parameters:**
|
|
880
|
+
|
|
881
|
+
| Parameter | Type | Description |
|
|
882
|
+
|-----------|------|-------------|
|
|
883
|
+
| `path` | `string` | Request path |
|
|
884
|
+
| `content` | `string \| Blob \| File` | File content to send |
|
|
885
|
+
| `options.mimeType` | `string` | File MIME type (optional, uses content.type if available) |
|
|
886
|
+
| `options.fileName` | `string` | File name (optional) |
|
|
887
|
+
| `options.autoResolve` | `boolean` | If true (default), server receives File/Blob in `req.body`; if false, server gets `req.stream` / `req.body` as `IframeFileReadableStream` |
|
|
888
|
+
| `options.ackTimeout` | `number` | ACK acknowledgment timeout (ms), default 1000 |
|
|
889
|
+
| `options.timeout` | `number` | Request timeout (ms), default 5000 |
|
|
890
|
+
| `options.asyncTimeout` | `number` | Async timeout (ms), default 120000 |
|
|
891
|
+
| `options.headers` | `object` | Request headers (optional) |
|
|
892
|
+
| `options.cookies` | `object` | Request cookies (optional) |
|
|
893
|
+
| `options.requestId` | `string` | Custom request ID (optional) |
|
|
894
|
+
|
|
895
|
+
**Returns:** `Promise<Response>`
|
|
896
|
+
|
|
897
|
+
**Note:** The file is sent via stream. When `autoResolve` is true (default), the server receives `req.body` as File/Blob; when false, the server receives `req.stream` / `req.body` as `IframeFileReadableStream`.
|
|
898
|
+
|
|
899
|
+
#### client.sendStream(path, stream, options?)
|
|
900
|
+
|
|
901
|
+
Send stream as request body (server receives readable stream).
|
|
902
|
+
|
|
903
|
+
**Parameters:**
|
|
904
|
+
|
|
905
|
+
| Parameter | Type | Description |
|
|
906
|
+
|-----------|------|-------------|
|
|
907
|
+
| `path` | `string` | Request path |
|
|
908
|
+
| `stream` | `IframeWritableStream` | Writable stream to send |
|
|
909
|
+
| `options.ackTimeout` | `number` | ACK acknowledgment timeout (ms), default 1000 |
|
|
910
|
+
| `options.timeout` | `number` | Request timeout (ms), default 5000 |
|
|
911
|
+
| `options.asyncTimeout` | `number` | Async timeout (ms), default 120000 |
|
|
912
|
+
| `options.headers` | `object` | Request headers (optional) |
|
|
913
|
+
| `options.cookies` | `object` | Request cookies (optional) |
|
|
914
|
+
| `options.requestId` | `string` | Custom request ID (optional) |
|
|
915
|
+
|
|
916
|
+
**Returns:** `Promise<Response>`
|
|
917
|
+
|
|
918
|
+
**Note:** On the server side, the stream is available as `req.stream` (an `IIframeReadableStream`). You can iterate over it using `for await (const chunk of req.stream)`.
|
|
919
|
+
|
|
772
920
|
#### client.isConnect()
|
|
773
921
|
|
|
774
922
|
Detect if Server is reachable.
|
|
@@ -804,6 +952,71 @@ Register route handler.
|
|
|
804
952
|
type ServerHandler = (req: ServerRequest, res: ServerResponse) => any | Promise<any>;
|
|
805
953
|
```
|
|
806
954
|
|
|
955
|
+
**ServerRequest interface:**
|
|
956
|
+
|
|
957
|
+
```typescript
|
|
958
|
+
interface ServerRequest {
|
|
959
|
+
body: any; // Request body (plain data, or File/Blob when client sendFile with autoResolve true)
|
|
960
|
+
stream?: IIframeReadableStream; // Request stream (when client sends via sendStream or sendFile with autoResolve false)
|
|
961
|
+
headers: Record<string, string>; // Request headers
|
|
962
|
+
cookies: Record<string, string>; // Request cookies
|
|
963
|
+
path: string; // Request path
|
|
964
|
+
params: Record<string, string>; // Path parameters extracted from route pattern (e.g., { id: '123' } for '/api/users/:id' and '/api/users/123')
|
|
965
|
+
requestId: string; // Request ID
|
|
966
|
+
origin: string; // Sender origin
|
|
967
|
+
source: Window; // Sender window
|
|
968
|
+
res: ServerResponse; // Response object
|
|
969
|
+
}
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
**Note:**
|
|
973
|
+
- When client sends a file via `sendFile()` (or `send(path, file)`), the file is sent via stream. If `autoResolve` is true (default), `req.body` is the resolved File/Blob; if false, `req.stream` / `req.body` is an `IIframeReadableStream` (e.g. `IframeFileReadableStream`).
|
|
974
|
+
- When client sends a stream via `sendStream()`, `req.stream` is available as an `IIframeReadableStream`. You can iterate over it using `for await (const chunk of req.stream)`.
|
|
975
|
+
- **Path parameters**: You can use Express-style route parameters (e.g., `/api/users/:id`) to extract path segments. The extracted parameters are available in `req.params`. For example, registering `/api/users/:id` and receiving `/api/users/123` will set `req.params.id` to `'123'`.
|
|
976
|
+
|
|
977
|
+
**Path Parameters Example:**
|
|
978
|
+
|
|
979
|
+
```typescript
|
|
980
|
+
// Register route with parameter
|
|
981
|
+
server.on('/api/users/:id', (req, res) => {
|
|
982
|
+
const userId = req.params.id; // '123' when path is '/api/users/123'
|
|
983
|
+
res.send({ userId });
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
// Multiple parameters
|
|
987
|
+
server.on('/api/users/:userId/posts/:postId', (req, res) => {
|
|
988
|
+
const { userId, postId } = req.params;
|
|
989
|
+
res.send({ userId, postId });
|
|
990
|
+
});
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
**Handler return value behavior**
|
|
994
|
+
|
|
995
|
+
- If your handler **does not call** `res.send()` / `res.json()` / `res.sendFile()` / `res.sendStream()`, but it **returns a value that is not `undefined`**, then the server will treat it as a successful result and automatically send it back to the client (equivalent to `res.send(returnValue)`).
|
|
996
|
+
- For **async handlers** (`Promise`): if the promise **resolves to a value that is not `undefined`** and no response has been sent yet, it will also be auto-sent.
|
|
997
|
+
- If the handler (or resolved promise) returns `undefined` **and** no response method was called, the server will respond with error code `NO_RESPONSE`.
|
|
998
|
+
|
|
999
|
+
Examples:
|
|
1000
|
+
|
|
1001
|
+
```typescript
|
|
1002
|
+
// Sync: auto-send return value
|
|
1003
|
+
server.on('/api/hello', () => {
|
|
1004
|
+
return { message: 'hello' };
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
// Async: auto-send resolved value
|
|
1008
|
+
server.on('/api/user', async (req) => {
|
|
1009
|
+
const user = await getUser(req.body.userId);
|
|
1010
|
+
return user; // auto-send if not undefined
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
// If you manually send, return value is ignored
|
|
1014
|
+
server.on('/api/manual', (req, res) => {
|
|
1015
|
+
res.send({ ok: true });
|
|
1016
|
+
return { ignored: true };
|
|
1017
|
+
});
|
|
1018
|
+
```
|
|
1019
|
+
|
|
807
1020
|
#### server.off(path)
|
|
808
1021
|
|
|
809
1022
|
Remove route handler.
|
|
@@ -840,6 +1053,259 @@ Destroy Server instance, remove all listeners.
|
|
|
840
1053
|
|
|
841
1054
|
---
|
|
842
1055
|
|
|
1056
|
+
## React Hooks
|
|
1057
|
+
|
|
1058
|
+
request-iframe provides React hooks for easy integration in React applications. Import hooks from `request-iframe/react`:
|
|
1059
|
+
|
|
1060
|
+
```typescript
|
|
1061
|
+
import { useClient, useServer, useServerHandler, useServerHandlerMap } from 'request-iframe/react';
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
### useClient(targetFnOrRef, options?, deps?)
|
|
1065
|
+
|
|
1066
|
+
React hook for using request-iframe client.
|
|
1067
|
+
|
|
1068
|
+
**Parameters:**
|
|
1069
|
+
|
|
1070
|
+
| Parameter | Type | Description |
|
|
1071
|
+
|-----------|------|-------------|
|
|
1072
|
+
| `targetFnOrRef` | `(() => HTMLIFrameElement \| Window \| null) \| RefObject<HTMLIFrameElement \| Window>` | Function that returns iframe element or Window object, or a React ref object |
|
|
1073
|
+
| `options` | `RequestIframeClientOptions` | Client options (optional) |
|
|
1074
|
+
| `deps` | `readonly unknown[]` | Dependency array (optional, for re-creating client when dependencies change) |
|
|
1075
|
+
|
|
1076
|
+
**Returns:** `RequestIframeClient | null`
|
|
1077
|
+
|
|
1078
|
+
**Example:**
|
|
1079
|
+
|
|
1080
|
+
```tsx
|
|
1081
|
+
import { useClient } from 'request-iframe/react';
|
|
1082
|
+
import { useRef } from 'react';
|
|
1083
|
+
|
|
1084
|
+
const MyComponent = () => {
|
|
1085
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
1086
|
+
const client = useClient(iframeRef, { secretKey: 'my-app' });
|
|
1087
|
+
|
|
1088
|
+
const handleClick = async () => {
|
|
1089
|
+
if (client) {
|
|
1090
|
+
const response = await client.send('/api/data', { id: 1 });
|
|
1091
|
+
console.log(response.data);
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
return (
|
|
1096
|
+
<div>
|
|
1097
|
+
<iframe ref={iframeRef} src="/iframe.html" />
|
|
1098
|
+
<button onClick={handleClick}>Send Request</button>
|
|
1099
|
+
</div>
|
|
1100
|
+
);
|
|
1101
|
+
};
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
**Using function instead of ref:**
|
|
1105
|
+
|
|
1106
|
+
```tsx
|
|
1107
|
+
const MyComponent = () => {
|
|
1108
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
1109
|
+
const client = useClient(() => iframeRef.current, { secretKey: 'my-app' });
|
|
1110
|
+
// ...
|
|
1111
|
+
};
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
### useServer(options?, deps?)
|
|
1115
|
+
|
|
1116
|
+
React hook for using request-iframe server.
|
|
1117
|
+
|
|
1118
|
+
**Parameters:**
|
|
1119
|
+
|
|
1120
|
+
| Parameter | Type | Description |
|
|
1121
|
+
|-----------|------|-------------|
|
|
1122
|
+
| `options` | `RequestIframeServerOptions` | Server options (optional) |
|
|
1123
|
+
| `deps` | `readonly unknown[]` | Dependency array (optional, for re-creating server when dependencies change) |
|
|
1124
|
+
|
|
1125
|
+
**Returns:** `RequestIframeServer | null`
|
|
1126
|
+
|
|
1127
|
+
**Example:**
|
|
1128
|
+
|
|
1129
|
+
```tsx
|
|
1130
|
+
import { useServer } from 'request-iframe/react';
|
|
1131
|
+
|
|
1132
|
+
const MyComponent = () => {
|
|
1133
|
+
const server = useServer({ secretKey: 'my-app' });
|
|
1134
|
+
|
|
1135
|
+
useEffect(() => {
|
|
1136
|
+
if (!server) return;
|
|
1137
|
+
|
|
1138
|
+
const off = server.on('/api/data', (req, res) => {
|
|
1139
|
+
res.send({ data: 'Hello' });
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
return off; // Cleanup on unmount
|
|
1143
|
+
}, [server]);
|
|
1144
|
+
|
|
1145
|
+
return <div>Server Component</div>;
|
|
1146
|
+
};
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
### useServerHandler(server, path, handler, deps?)
|
|
1150
|
+
|
|
1151
|
+
React hook for registering a single server handler with automatic cleanup and closure handling.
|
|
1152
|
+
|
|
1153
|
+
**Parameters:**
|
|
1154
|
+
|
|
1155
|
+
| Parameter | Type | Description |
|
|
1156
|
+
|-----------|------|-------------|
|
|
1157
|
+
| `server` | `RequestIframeServer \| null` | Server instance (from `useServer`) |
|
|
1158
|
+
| `path` | `string` | Route path |
|
|
1159
|
+
| `handler` | `ServerHandler` | Handler function |
|
|
1160
|
+
| `deps` | `readonly unknown[]` | Dependency array (optional, for re-registering when dependencies change) |
|
|
1161
|
+
|
|
1162
|
+
**Example:**
|
|
1163
|
+
|
|
1164
|
+
```tsx
|
|
1165
|
+
import { useServer, useServerHandler } from 'request-iframe/react';
|
|
1166
|
+
import { useState } from 'react';
|
|
1167
|
+
|
|
1168
|
+
const MyComponent = () => {
|
|
1169
|
+
const server = useServer();
|
|
1170
|
+
const [userId, setUserId] = useState(1);
|
|
1171
|
+
|
|
1172
|
+
// Handler automatically uses latest userId value
|
|
1173
|
+
useServerHandler(server, '/api/user', (req, res) => {
|
|
1174
|
+
res.send({ userId, data: 'Hello' });
|
|
1175
|
+
}, [userId]); // Re-register when userId changes
|
|
1176
|
+
|
|
1177
|
+
return <div>Server Component</div>;
|
|
1178
|
+
};
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
**Key Features:**
|
|
1182
|
+
- Automatically handles closure issues - always uses latest values from dependencies
|
|
1183
|
+
- Automatically unregisters handler on unmount or when dependencies change
|
|
1184
|
+
- No need to manually manage handler registration/cleanup
|
|
1185
|
+
|
|
1186
|
+
### useServerHandlerMap(server, map, deps?)
|
|
1187
|
+
|
|
1188
|
+
React hook for registering multiple server handlers at once with automatic cleanup.
|
|
1189
|
+
|
|
1190
|
+
**Parameters:**
|
|
1191
|
+
|
|
1192
|
+
| Parameter | Type | Description |
|
|
1193
|
+
|-----------|------|-------------|
|
|
1194
|
+
| `server` | `RequestIframeServer \| null` | Server instance (from `useServer`) |
|
|
1195
|
+
| `map` | `Record<string, ServerHandler>` | Map of route paths and handler functions |
|
|
1196
|
+
| `deps` | `readonly unknown[]` | Dependency array (optional, for re-registering when dependencies change) |
|
|
1197
|
+
|
|
1198
|
+
**Example:**
|
|
1199
|
+
|
|
1200
|
+
```tsx
|
|
1201
|
+
import { useServer, useServerHandlerMap } from 'request-iframe/react';
|
|
1202
|
+
import { useState } from 'react';
|
|
1203
|
+
|
|
1204
|
+
const MyComponent = () => {
|
|
1205
|
+
const server = useServer();
|
|
1206
|
+
const [userId, setUserId] = useState(1);
|
|
1207
|
+
|
|
1208
|
+
// Register multiple handlers at once
|
|
1209
|
+
useServerHandlerMap(server, {
|
|
1210
|
+
'/api/user': (req, res) => {
|
|
1211
|
+
res.send({ userId, data: 'User data' });
|
|
1212
|
+
},
|
|
1213
|
+
'/api/posts': (req, res) => {
|
|
1214
|
+
res.send({ userId, data: 'Posts data' });
|
|
1215
|
+
}
|
|
1216
|
+
}, [userId]); // Re-register all handlers when userId changes
|
|
1217
|
+
|
|
1218
|
+
return <div>Server Component</div>;
|
|
1219
|
+
};
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
**Key Features:**
|
|
1223
|
+
- Batch registration of multiple handlers
|
|
1224
|
+
- Automatically handles closure issues - always uses latest values from dependencies
|
|
1225
|
+
- Automatically unregisters all handlers on unmount or when dependencies change
|
|
1226
|
+
- Efficient - only re-registers when map keys change
|
|
1227
|
+
|
|
1228
|
+
### Complete Example
|
|
1229
|
+
|
|
1230
|
+
Here's a complete example showing how to use React hooks in a real application:
|
|
1231
|
+
|
|
1232
|
+
```tsx
|
|
1233
|
+
import { useClient, useServer, useServerHandler } from 'request-iframe/react';
|
|
1234
|
+
import { useRef, useState } from 'react';
|
|
1235
|
+
|
|
1236
|
+
// Parent Component (Client)
|
|
1237
|
+
const ParentComponent = () => {
|
|
1238
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
1239
|
+
const client = useClient(iframeRef, { secretKey: 'my-app' });
|
|
1240
|
+
const [data, setData] = useState(null);
|
|
1241
|
+
|
|
1242
|
+
const fetchData = async () => {
|
|
1243
|
+
if (!client) return;
|
|
1244
|
+
|
|
1245
|
+
try {
|
|
1246
|
+
const response = await client.send('/api/data', { id: 1 });
|
|
1247
|
+
setData(response.data);
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
console.error('Request failed:', error);
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
return (
|
|
1254
|
+
<div>
|
|
1255
|
+
<iframe ref={iframeRef} src="/iframe.html" />
|
|
1256
|
+
<button onClick={fetchData}>Fetch Data</button>
|
|
1257
|
+
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
|
|
1258
|
+
</div>
|
|
1259
|
+
);
|
|
1260
|
+
};
|
|
1261
|
+
|
|
1262
|
+
// Iframe Component (Server)
|
|
1263
|
+
const IframeComponent = () => {
|
|
1264
|
+
const server = useServer({ secretKey: 'my-app' });
|
|
1265
|
+
const [userId, setUserId] = useState(1);
|
|
1266
|
+
|
|
1267
|
+
// Register handler with automatic cleanup
|
|
1268
|
+
useServerHandler(server, '/api/data', async (req, res) => {
|
|
1269
|
+
// Handler always uses latest userId value
|
|
1270
|
+
const userData = await fetchUserData(userId);
|
|
1271
|
+
res.send(userData);
|
|
1272
|
+
}, [userId]);
|
|
1273
|
+
|
|
1274
|
+
return (
|
|
1275
|
+
<div>
|
|
1276
|
+
<p>User ID: {userId}</p>
|
|
1277
|
+
<button onClick={() => setUserId(userId + 1)}>Increment</button>
|
|
1278
|
+
</div>
|
|
1279
|
+
);
|
|
1280
|
+
};
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
### Best Practices
|
|
1284
|
+
|
|
1285
|
+
1. **Always check for null**: Client and server hooks may return `null` initially or when target is unavailable:
|
|
1286
|
+
```tsx
|
|
1287
|
+
const client = useClient(iframeRef);
|
|
1288
|
+
if (!client) return null; // Handle null case
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
2. **Use dependency arrays**: Pass dependencies to hooks to ensure handlers use latest values:
|
|
1292
|
+
```tsx
|
|
1293
|
+
useServerHandler(server, '/api/data', (req, res) => {
|
|
1294
|
+
res.send({ userId }); // Always uses latest userId
|
|
1295
|
+
}, [userId]); // Re-register when userId changes
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
3. **Cleanup is automatic**: Hooks automatically clean up on unmount, but you can also manually unregister:
|
|
1299
|
+
```tsx
|
|
1300
|
+
useEffect(() => {
|
|
1301
|
+
if (!server) return;
|
|
1302
|
+
const off = server.on('/api/data', handler);
|
|
1303
|
+
return off; // Manual cleanup (optional, hooks do this automatically)
|
|
1304
|
+
}, [server]);
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
---
|
|
1308
|
+
|
|
843
1309
|
## Error Handling
|
|
844
1310
|
|
|
845
1311
|
### Error Codes
|