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.CN.md
CHANGED
|
@@ -34,6 +34,13 @@
|
|
|
34
34
|
- [追踪模式](#追踪模式)
|
|
35
35
|
- [多语言支持](#多语言支持)
|
|
36
36
|
- [API 参考](#api-参考)
|
|
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
|
+
- [完整示例](#完整示例)
|
|
43
|
+
- [最佳实践](#最佳实践)
|
|
37
44
|
- [错误处理](#错误处理)
|
|
38
45
|
- [FAQ](#faq)
|
|
39
46
|
- [开发](#开发)
|
|
@@ -67,7 +74,7 @@
|
|
|
67
74
|
- ⏱️ **智能超时** - 三阶段超时(连接/同步/异步),自动识别长任务
|
|
68
75
|
- 📦 **TypeScript** - 完整的类型定义和智能提示
|
|
69
76
|
- 🔒 **消息隔离** - secretKey 机制避免多实例消息串线
|
|
70
|
-
- 📁 **文件传输** -
|
|
77
|
+
- 📁 **文件传输** - 支持文件通过流方式传输(Client→Server)
|
|
71
78
|
- 🌊 **流式传输** - 支持大文件分块传输,支持异步迭代器
|
|
72
79
|
- 🌍 **多语言** - 错误消息可自定义,便于国际化
|
|
73
80
|
- ✅ **协议版本** - 内置版本控制,便于升级兼容
|
|
@@ -167,7 +174,7 @@ request-iframe 采用三阶段超时策略,智能适应不同场景:
|
|
|
167
174
|
|
|
168
175
|
```typescript
|
|
169
176
|
client.send('/api/getData', data, {
|
|
170
|
-
ackTimeout:
|
|
177
|
+
ackTimeout: 1000, // 阶段1:等待 ACK 的超时时间(默认 1000ms)
|
|
171
178
|
timeout: 5000, // 阶段2:请求超时时间(默认 5s)
|
|
172
179
|
asyncTimeout: 120000 // 阶段3:异步请求超时时间(默认 120s)
|
|
173
180
|
});
|
|
@@ -207,7 +214,7 @@ client.send('/api/getData', data, {
|
|
|
207
214
|
|
|
208
215
|
| 阶段 | 超时时间 | 场景 |
|
|
209
216
|
|------|----------|------|
|
|
210
|
-
| ackTimeout | 较短(
|
|
217
|
+
| ackTimeout | 较短(1000ms) | 快速检测 Server 是否在线,避免长时间等待不可达的 iframe。从 500ms 增加到 1000ms,以适应性能较差的环境或浏览器繁忙的场景 |
|
|
211
218
|
| timeout | 中等(5s) | 适用于简单的同步处理,如读取数据、参数校验等 |
|
|
212
219
|
| asyncTimeout | 较长(120s) | 适用于复杂异步操作,如文件处理、批量操作、第三方 API 调用等 |
|
|
213
220
|
|
|
@@ -245,7 +252,8 @@ client.send('/api/getData', data, {
|
|
|
245
252
|
const client = requestIframeClient(iframe, { secretKey: 'main-app' });
|
|
246
253
|
|
|
247
254
|
// 获取子应用的用户信息
|
|
248
|
-
const
|
|
255
|
+
const userInfoResponse = await client.send('/api/user/info', {});
|
|
256
|
+
console.log(userInfoResponse.data); // 用户信息数据
|
|
249
257
|
|
|
250
258
|
// 通知子应用更新数据
|
|
251
259
|
await client.send('/api/data/refresh', { timestamp: Date.now() });
|
|
@@ -289,7 +297,8 @@ server.on('/api/data', async (req, res) => {
|
|
|
289
297
|
|
|
290
298
|
// 父页面(跨域)
|
|
291
299
|
const client = requestIframeClient(iframe, { secretKey: 'data-api' });
|
|
292
|
-
const
|
|
300
|
+
const response = await client.send('/api/data', {});
|
|
301
|
+
const data = response.data; // 成功获取跨域数据
|
|
293
302
|
```
|
|
294
303
|
|
|
295
304
|
### 文件预览和下载
|
|
@@ -311,8 +320,8 @@ server.on('/api/processFile', async (req, res) => {
|
|
|
311
320
|
|
|
312
321
|
// 父页面:下载文件
|
|
313
322
|
const response = await client.send('/api/processFile', { fileId: '123' });
|
|
314
|
-
if (response.
|
|
315
|
-
downloadFile(response.
|
|
323
|
+
if (response.data instanceof File || response.data instanceof Blob) {
|
|
324
|
+
downloadFile(response.data);
|
|
316
325
|
}
|
|
317
326
|
```
|
|
318
327
|
|
|
@@ -498,10 +507,12 @@ server.on('/api/logout', (req, res) => {
|
|
|
498
507
|
await client.send('/api/login', { username: 'tom', password: '123' });
|
|
499
508
|
|
|
500
509
|
// Client 端:后续请求 /api/getUserInfo(自动携带 authToken 和 userId)
|
|
501
|
-
const
|
|
510
|
+
const userInfoResponse = await client.send('/api/getUserInfo', {});
|
|
511
|
+
const userInfo = userInfoResponse.data;
|
|
502
512
|
|
|
503
513
|
// Client 端:请求根路径(只携带 userId,因为 authToken 的 path 是 /api)
|
|
504
|
-
const
|
|
514
|
+
const rootResponse = await client.send('/other', {});
|
|
515
|
+
const rootData = rootResponse.data;
|
|
505
516
|
```
|
|
506
517
|
|
|
507
518
|
#### Client Cookie 管理 API
|
|
@@ -576,22 +587,48 @@ server.on('/api/download', async (req, res) => {
|
|
|
576
587
|
|
|
577
588
|
// Client 端接收
|
|
578
589
|
const response = await client.send('/api/download', {});
|
|
579
|
-
if (response.
|
|
580
|
-
const
|
|
590
|
+
if (response.data instanceof File || response.data instanceof Blob) {
|
|
591
|
+
const file = response.data instanceof File ? response.data : null;
|
|
592
|
+
const fileName = file?.name || 'download';
|
|
581
593
|
|
|
582
|
-
//
|
|
583
|
-
const
|
|
584
|
-
const blob = new Blob([binaryString], { type: mimeType });
|
|
585
|
-
|
|
586
|
-
// 下载文件
|
|
587
|
-
const url = URL.createObjectURL(blob);
|
|
594
|
+
// 直接使用 File/Blob 下载文件
|
|
595
|
+
const url = URL.createObjectURL(response.data);
|
|
588
596
|
const a = document.createElement('a');
|
|
589
597
|
a.href = url;
|
|
590
|
-
a.download = fileName
|
|
598
|
+
a.download = fileName;
|
|
591
599
|
a.click();
|
|
600
|
+
URL.revokeObjectURL(url);
|
|
592
601
|
}
|
|
593
602
|
```
|
|
594
603
|
|
|
604
|
+
#### Client → Server(Client 向 Server 发送文件)
|
|
605
|
+
|
|
606
|
+
Client 端发送文件**仅走流式**。使用 `sendFile()`(或直接 `send(path, file)`);Server 端在 `autoResolve: true`(默认)时会把文件自动解析成 `File/Blob` 放到 `req.body`,当 `autoResolve: false` 时则通过 `req.stream` / `req.body` 暴露为 `IframeFileReadableStream`。
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
// Client 端:发送文件(stream,autoResolve 默认 true)
|
|
610
|
+
const file = new File(['Hello Upload'], 'upload.txt', { type: 'text/plain' });
|
|
611
|
+
const response = await client.send('/api/upload', file);
|
|
612
|
+
|
|
613
|
+
// 或显式使用 sendFile
|
|
614
|
+
const blob = new Blob(['binary data'], { type: 'application/octet-stream' });
|
|
615
|
+
const response2 = await client.sendFile('/api/upload', blob, {
|
|
616
|
+
fileName: 'data.bin',
|
|
617
|
+
mimeType: 'application/octet-stream',
|
|
618
|
+
autoResolve: true // 可选,默认 true:Server 在 req.body 里拿到 File/Blob
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// Server 端:接收文件(autoResolve true → req.body 是 File/Blob)
|
|
622
|
+
server.on('/api/upload', async (req, res) => {
|
|
623
|
+
const blob = req.body as Blob; // 如果 client 发送的是 File,这里也可能是 File
|
|
624
|
+
const text = await blob.text();
|
|
625
|
+
console.log('Received file content:', text);
|
|
626
|
+
res.send({ success: true, size: blob.size });
|
|
627
|
+
});
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
**提示**:当 `client.send()` 的 `body` 是 `File/Blob` 时,会自动分发到 `client.sendFile()`(走流式)。`autoResolve` 为 true(默认)时 Server 拿到 `req.body`(File/Blob),为 false 时拿到 `req.stream` / `req.body`(`IframeFileReadableStream`)。
|
|
631
|
+
|
|
595
632
|
### 流式传输(Stream)
|
|
596
633
|
|
|
597
634
|
对于大文件或需要分块传输的场景,可以使用流式传输:
|
|
@@ -601,7 +638,7 @@ import {
|
|
|
601
638
|
IframeWritableStream,
|
|
602
639
|
IframeFileWritableStream,
|
|
603
640
|
isIframeReadableStream,
|
|
604
|
-
|
|
641
|
+
isIframeFileReadableStream
|
|
605
642
|
} from 'request-iframe';
|
|
606
643
|
|
|
607
644
|
// Server 端:使用迭代器发送数据流
|
|
@@ -685,7 +722,7 @@ if (isIframeReadableStream(response.stream)) {
|
|
|
685
722
|
// Client 端:接收文件流
|
|
686
723
|
const fileResponse = await client.send('/api/fileStream', {});
|
|
687
724
|
|
|
688
|
-
if (
|
|
725
|
+
if (isIframeFileReadableStream(fileResponse.stream)) {
|
|
689
726
|
// 读取为 Blob
|
|
690
727
|
const blob = await fileResponse.stream.readAsBlob();
|
|
691
728
|
|
|
@@ -707,9 +744,9 @@ if (isIframeFileStream(fileResponse.stream)) {
|
|
|
707
744
|
| 类型 | 说明 |
|
|
708
745
|
|------|------|
|
|
709
746
|
| `IframeWritableStream` | 服务端可写流,用于发送普通数据 |
|
|
710
|
-
| `IframeFileWritableStream` |
|
|
747
|
+
| `IframeFileWritableStream` | 服务端文件可写流(文件流) |
|
|
711
748
|
| `IframeReadableStream` | 客户端可读流,用于接收普通数据 |
|
|
712
|
-
| `IframeFileReadableStream` |
|
|
749
|
+
| `IframeFileReadableStream` | 客户端文件可读流(文件流) |
|
|
713
750
|
|
|
714
751
|
**流选项:**
|
|
715
752
|
|
|
@@ -804,7 +841,7 @@ setMessages({
|
|
|
804
841
|
| `target` | `HTMLIFrameElement \| Window` | 目标 iframe 元素或 window 对象 |
|
|
805
842
|
| `options.secretKey` | `string` | 消息隔离标识(可选) |
|
|
806
843
|
| `options.trace` | `boolean` | 是否开启追踪模式(可选) |
|
|
807
|
-
| `options.ackTimeout` | `number` | 全局默认 ACK 确认超时(ms),默认
|
|
844
|
+
| `options.ackTimeout` | `number` | 全局默认 ACK 确认超时(ms),默认 1000 |
|
|
808
845
|
| `options.timeout` | `number` | 全局默认请求超时(ms),默认 5000 |
|
|
809
846
|
| `options.asyncTimeout` | `number` | 全局默认异步超时(ms),默认 120000 |
|
|
810
847
|
|
|
@@ -837,7 +874,7 @@ await client.send('/api/longTask', {}, {
|
|
|
837
874
|
|------|------|------|
|
|
838
875
|
| `options.secretKey` | `string` | 消息隔离标识(可选) |
|
|
839
876
|
| `options.trace` | `boolean` | 是否开启追踪模式(可选) |
|
|
840
|
-
| `options.ackTimeout` | `number` | 等待客户端确认超时(ms),默认
|
|
877
|
+
| `options.ackTimeout` | `number` | 等待客户端确认超时(ms),默认 1000 |
|
|
841
878
|
|
|
842
879
|
**返回值:** `RequestIframeServer`
|
|
843
880
|
|
|
@@ -845,15 +882,17 @@ await client.send('/api/longTask', {}, {
|
|
|
845
882
|
|
|
846
883
|
#### client.send(path, body?, options?)
|
|
847
884
|
|
|
848
|
-
|
|
885
|
+
发送请求。会根据 body 类型自动分发到 `sendFile()` 或 `sendStream()`:
|
|
886
|
+
- `File/Blob` → `sendFile()`
|
|
887
|
+
- `IframeWritableStream` → `sendStream()`
|
|
849
888
|
|
|
850
889
|
**参数:**
|
|
851
890
|
|
|
852
891
|
| 参数 | 类型 | 说明 |
|
|
853
892
|
|------|------|------|
|
|
854
893
|
| `path` | `string` | 请求路径 |
|
|
855
|
-
| `body` | `
|
|
856
|
-
| `options.ackTimeout` | `number` | ACK 确认超时(ms),默认
|
|
894
|
+
| `body` | `any` | 请求数据(可选)。可以是普通对象、File、Blob、或 IframeWritableStream;会自动分发:File/Blob → `sendFile()`,IframeWritableStream → `sendStream()` |
|
|
895
|
+
| `options.ackTimeout` | `number` | ACK 确认超时(ms),默认 1000 |
|
|
857
896
|
| `options.timeout` | `number` | 请求超时(ms),默认 5000 |
|
|
858
897
|
| `options.asyncTimeout` | `number` | 异步超时(ms),默认 120000 |
|
|
859
898
|
| `options.headers` | `object` | 请求 headers(可选) |
|
|
@@ -864,20 +903,78 @@ await client.send('/api/longTask', {}, {
|
|
|
864
903
|
|
|
865
904
|
```typescript
|
|
866
905
|
interface Response<T = any> {
|
|
867
|
-
data: T; //
|
|
906
|
+
data: T; // 响应数据(自动解析的文件流为 File/Blob)
|
|
868
907
|
status: number; // 状态码
|
|
869
908
|
statusText: string; // 状态文本
|
|
870
909
|
requestId: string; // 请求 ID
|
|
871
910
|
headers?: Record<string, string | string[]>; // 响应 headers(Set-Cookie 为数组)
|
|
872
|
-
fileData?: { // 文件数据(如果有)
|
|
873
|
-
content: string; // base64 编码内容
|
|
874
|
-
mimeType?: string;
|
|
875
|
-
fileName?: string;
|
|
876
|
-
};
|
|
877
911
|
stream?: IIframeReadableStream<T>; // 流响应(如果有)
|
|
878
912
|
}
|
|
879
913
|
```
|
|
880
914
|
|
|
915
|
+
**示例:**
|
|
916
|
+
|
|
917
|
+
```typescript
|
|
918
|
+
// 发送普通对象(自动 Content-Type: application/json)
|
|
919
|
+
await client.send('/api/data', { name: 'test' });
|
|
920
|
+
|
|
921
|
+
// 发送字符串(自动 Content-Type: text/plain)
|
|
922
|
+
await client.send('/api/text', 'Hello');
|
|
923
|
+
|
|
924
|
+
// 发送 File/Blob(自动分发到 sendFile)
|
|
925
|
+
const file = new File(['content'], 'test.txt');
|
|
926
|
+
await client.send('/api/upload', file);
|
|
927
|
+
|
|
928
|
+
// 发送流(自动分发到 sendStream)
|
|
929
|
+
const stream = new IframeWritableStream({ iterator: async function* () { yield 'data'; } });
|
|
930
|
+
await client.send('/api/uploadStream', stream);
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
#### client.sendFile(path, content, options?)
|
|
934
|
+
|
|
935
|
+
发送文件作为请求体(通过流传输;当 `autoResolve` 为 true 时,Server 在 `req.body` 中拿到 File/Blob)。
|
|
936
|
+
|
|
937
|
+
**参数:**
|
|
938
|
+
|
|
939
|
+
| 参数 | 类型 | 说明 |
|
|
940
|
+
|------|------|------|
|
|
941
|
+
| `path` | `string` | 请求路径 |
|
|
942
|
+
| `content` | `string \| Blob \| File` | 文件内容 |
|
|
943
|
+
| `options.mimeType` | `string` | MIME 类型(可选,优先使用 content.type) |
|
|
944
|
+
| `options.fileName` | `string` | 文件名(可选) |
|
|
945
|
+
| `options.autoResolve` | `boolean` | 为 true(默认)时 Server 在 `req.body` 中拿到 File/Blob;为 false 时 Server 在 `req.stream` / `req.body` 中拿到 `IframeFileReadableStream` |
|
|
946
|
+
| `options.ackTimeout` | `number` | ACK 确认超时(ms),默认 1000 |
|
|
947
|
+
| `options.timeout` | `number` | 请求超时(ms),默认 5000 |
|
|
948
|
+
| `options.asyncTimeout` | `number` | 异步超时(ms),默认 120000 |
|
|
949
|
+
| `options.headers` | `object` | 请求 headers(可选) |
|
|
950
|
+
| `options.cookies` | `object` | 请求 cookies(可选) |
|
|
951
|
+
| `options.requestId` | `string` | 自定义请求 ID(可选) |
|
|
952
|
+
|
|
953
|
+
**返回值:** `Promise<Response>`
|
|
954
|
+
|
|
955
|
+
**说明:** 文件通过流发送。`autoResolve` 为 true(默认)时 Server 收到 `req.body`(File/Blob);为 false 时 Server 收到 `req.stream` / `req.body`(`IframeFileReadableStream`)。
|
|
956
|
+
|
|
957
|
+
#### client.sendStream(path, stream, options?)
|
|
958
|
+
|
|
959
|
+
发送流作为请求体(Server 端收到可读流)。
|
|
960
|
+
|
|
961
|
+
**参数:**
|
|
962
|
+
|
|
963
|
+
| 参数 | 类型 | 说明 |
|
|
964
|
+
|------|------|------|
|
|
965
|
+
| `path` | `string` | 请求路径 |
|
|
966
|
+
| `stream` | `IframeWritableStream` | 要发送的可写流 |
|
|
967
|
+
| `options.ackTimeout` | `number` | ACK 确认超时(ms),默认 1000 |
|
|
968
|
+
| `options.timeout` | `number` | 请求超时(ms),默认 5000 |
|
|
969
|
+
| `options.asyncTimeout` | `number` | 异步超时(ms),默认 120000 |
|
|
970
|
+
| `options.headers` | `object` | 请求 headers(可选) |
|
|
971
|
+
| `options.cookies` | `object` | 请求 cookies(可选) |
|
|
972
|
+
| `options.requestId` | `string` | 自定义请求 ID(可选) |
|
|
973
|
+
|
|
974
|
+
**返回值:** `Promise<Response>`
|
|
975
|
+
|
|
976
|
+
**说明:** Server 端的流在 `req.stream`(`IIframeReadableStream`)中,可用 `for await (const chunk of req.stream)` 迭代读取。
|
|
977
|
+
|
|
881
978
|
#### client.isConnect()
|
|
882
979
|
|
|
883
980
|
检测 Server 是否可达。
|
|
@@ -950,6 +1047,39 @@ client.interceptors.response.use(onFulfilled, onRejected?);
|
|
|
950
1047
|
type ServerHandler = (req: ServerRequest, res: ServerResponse) => any | Promise<any>;
|
|
951
1048
|
```
|
|
952
1049
|
|
|
1050
|
+
**ServerRequest 接口:**
|
|
1051
|
+
|
|
1052
|
+
```typescript
|
|
1053
|
+
interface ServerRequest {
|
|
1054
|
+
body: any; // 请求 body(普通数据;或 autoResolve=true 时的 File/Blob)
|
|
1055
|
+
stream?: IIframeReadableStream; // 请求流(sendStream;或 sendFile 且 autoResolve=false)
|
|
1056
|
+
headers: Record<string, string>; // 请求 headers
|
|
1057
|
+
cookies: Record<string, string>; // 请求 cookies
|
|
1058
|
+
path: string; // 请求路径(实际请求路径)
|
|
1059
|
+
params: Record<string, string>; // 路由参数(由 server.on 注册的路径模式解析得出,如 /api/users/:id)
|
|
1060
|
+
requestId: string; // 请求 ID
|
|
1061
|
+
origin: string; // 来源 origin
|
|
1062
|
+
source: Window; // 来源 window
|
|
1063
|
+
res: ServerResponse; // 关联的 Response 对象
|
|
1064
|
+
}
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
**说明:**
|
|
1068
|
+
- Client 通过 `sendFile()`(或 `send(path, file)`)发送文件时:文件通过流传输;`autoResolve` 为 true(默认)时 Server 在 `req.body` 中拿到 File/Blob;为 false 时在 `req.stream` / `req.body` 中拿到 `IframeFileReadableStream`。
|
|
1069
|
+
- Client 通过 `sendStream()` 发送流时:Server 在 `req.stream` 中拿到 `IIframeReadableStream`,可用 `for await` 迭代读取。
|
|
1070
|
+
- **路径参数(类似 Express)**:支持 `/api/users/:id` 形式的路由参数,解析结果在 `req.params` 中。
|
|
1071
|
+
|
|
1072
|
+
```typescript
|
|
1073
|
+
server.on('/api/users/:id', (req, res) => {
|
|
1074
|
+
res.send({ userId: req.params.id });
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
server.on('/api/users/:userId/posts/:postId', (req, res) => {
|
|
1078
|
+
const { userId, postId } = req.params;
|
|
1079
|
+
res.send({ userId, postId });
|
|
1080
|
+
});
|
|
1081
|
+
```
|
|
1082
|
+
|
|
953
1083
|
#### server.off(path)
|
|
954
1084
|
|
|
955
1085
|
移除路由处理器。
|
|
@@ -984,14 +1114,271 @@ server.use(['/a', '/b'], (req, res, next) => { ... });
|
|
|
984
1114
|
|
|
985
1115
|
销毁 Server 实例,移除所有监听器。
|
|
986
1116
|
|
|
987
|
-
|
|
1117
|
+
---
|
|
1118
|
+
|
|
1119
|
+
## React Hooks
|
|
1120
|
+
|
|
1121
|
+
request-iframe 提供了 React hooks,方便在 React 应用中使用。从 `request-iframe/react` 导入 hooks:
|
|
1122
|
+
|
|
1123
|
+
```typescript
|
|
1124
|
+
import { useClient, useServer, useServerHandler, useServerHandlerMap } from 'request-iframe/react';
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
### useClient(targetFnOrRef, options?, deps?)
|
|
1128
|
+
|
|
1129
|
+
用于使用 request-iframe client 的 React hook。
|
|
1130
|
+
|
|
1131
|
+
**参数:**
|
|
1132
|
+
|
|
1133
|
+
| 参数 | 类型 | 说明 |
|
|
1134
|
+
|------|------|------|
|
|
1135
|
+
| `targetFnOrRef` | `(() => HTMLIFrameElement \| Window \| null) \| RefObject<HTMLIFrameElement \| Window>` | 返回 iframe 元素或 Window 对象的函数,或 React ref 对象 |
|
|
1136
|
+
| `options` | `RequestIframeClientOptions` | Client 选项(可选) |
|
|
1137
|
+
| `deps` | `readonly unknown[]` | 依赖数组(可选,当依赖变化时重新创建 client) |
|
|
1138
|
+
|
|
1139
|
+
**返回值:** `RequestIframeClient | null`
|
|
1140
|
+
|
|
1141
|
+
**示例:**
|
|
1142
|
+
|
|
1143
|
+
```tsx
|
|
1144
|
+
import { useClient } from 'request-iframe/react';
|
|
1145
|
+
import { useRef } from 'react';
|
|
1146
|
+
|
|
1147
|
+
const MyComponent = () => {
|
|
1148
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
1149
|
+
const client = useClient(iframeRef, { secretKey: 'my-app' });
|
|
1150
|
+
|
|
1151
|
+
const handleClick = async () => {
|
|
1152
|
+
if (client) {
|
|
1153
|
+
const response = await client.send('/api/data', { id: 1 });
|
|
1154
|
+
console.log(response.data);
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
|
|
1158
|
+
return (
|
|
1159
|
+
<div>
|
|
1160
|
+
<iframe ref={iframeRef} src="/iframe.html" />
|
|
1161
|
+
<button onClick={handleClick}>发送请求</button>
|
|
1162
|
+
</div>
|
|
1163
|
+
);
|
|
1164
|
+
};
|
|
1165
|
+
```
|
|
1166
|
+
|
|
1167
|
+
**使用函数而不是 ref:**
|
|
1168
|
+
|
|
1169
|
+
```tsx
|
|
1170
|
+
const MyComponent = () => {
|
|
1171
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
1172
|
+
const client = useClient(() => iframeRef.current, { secretKey: 'my-app' });
|
|
1173
|
+
// ...
|
|
1174
|
+
};
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
### useServer(options?, deps?)
|
|
1178
|
+
|
|
1179
|
+
用于使用 request-iframe server 的 React hook。
|
|
1180
|
+
|
|
1181
|
+
**参数:**
|
|
1182
|
+
|
|
1183
|
+
| 参数 | 类型 | 说明 |
|
|
1184
|
+
|------|------|------|
|
|
1185
|
+
| `options` | `RequestIframeServerOptions` | Server 选项(可选) |
|
|
1186
|
+
| `deps` | `readonly unknown[]` | 依赖数组(可选,当依赖变化时重新创建 server) |
|
|
1187
|
+
|
|
1188
|
+
**返回值:** `RequestIframeServer | null`
|
|
1189
|
+
|
|
1190
|
+
**示例:**
|
|
1191
|
+
|
|
1192
|
+
```tsx
|
|
1193
|
+
import { useServer } from 'request-iframe/react';
|
|
1194
|
+
|
|
1195
|
+
const MyComponent = () => {
|
|
1196
|
+
const server = useServer({ secretKey: 'my-app' });
|
|
1197
|
+
|
|
1198
|
+
useEffect(() => {
|
|
1199
|
+
if (!server) return;
|
|
1200
|
+
|
|
1201
|
+
const off = server.on('/api/data', (req, res) => {
|
|
1202
|
+
res.send({ data: 'Hello' });
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
return off; // 组件卸载时清理
|
|
1206
|
+
}, [server]);
|
|
1207
|
+
|
|
1208
|
+
return <div>Server 组件</div>;
|
|
1209
|
+
};
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
### useServerHandler(server, path, handler, deps?)
|
|
1213
|
+
|
|
1214
|
+
用于注册单个 server handler 的 React hook,自动处理清理和闭包问题。
|
|
1215
|
+
|
|
1216
|
+
**参数:**
|
|
1217
|
+
|
|
1218
|
+
| 参数 | 类型 | 说明 |
|
|
1219
|
+
|------|------|------|
|
|
1220
|
+
| `server` | `RequestIframeServer \| null` | Server 实例(来自 `useServer`) |
|
|
1221
|
+
| `path` | `string` | 路由路径 |
|
|
1222
|
+
| `handler` | `ServerHandler` | 处理函数 |
|
|
1223
|
+
| `deps` | `readonly unknown[]` | 依赖数组(可选,当依赖变化时重新注册) |
|
|
1224
|
+
|
|
1225
|
+
**示例:**
|
|
1226
|
+
|
|
1227
|
+
```tsx
|
|
1228
|
+
import { useServer, useServerHandler } from 'request-iframe/react';
|
|
1229
|
+
import { useState } from 'react';
|
|
1230
|
+
|
|
1231
|
+
const MyComponent = () => {
|
|
1232
|
+
const server = useServer();
|
|
1233
|
+
const [userId, setUserId] = useState(1);
|
|
1234
|
+
|
|
1235
|
+
// Handler 自动使用最新的 userId 值
|
|
1236
|
+
useServerHandler(server, '/api/user', (req, res) => {
|
|
1237
|
+
res.send({ userId, data: 'Hello' });
|
|
1238
|
+
}, [userId]); // 当 userId 变化时重新注册
|
|
1239
|
+
|
|
1240
|
+
return <div>Server 组件</div>;
|
|
1241
|
+
};
|
|
1242
|
+
```
|
|
1243
|
+
|
|
1244
|
+
**关键特性:**
|
|
1245
|
+
- 自动处理闭包问题 - 始终使用依赖项的最新值
|
|
1246
|
+
- 组件卸载或依赖变化时自动取消注册 handler
|
|
1247
|
+
- 无需手动管理 handler 的注册/清理
|
|
1248
|
+
|
|
1249
|
+
### useServerHandlerMap(server, map, deps?)
|
|
1250
|
+
|
|
1251
|
+
用于批量注册多个 server handlers 的 React hook,自动处理清理。
|
|
1252
|
+
|
|
1253
|
+
**参数:**
|
|
1254
|
+
|
|
1255
|
+
| 参数 | 类型 | 说明 |
|
|
1256
|
+
|------|------|------|
|
|
1257
|
+
| `server` | `RequestIframeServer \| null` | Server 实例(来自 `useServer`) |
|
|
1258
|
+
| `map` | `Record<string, ServerHandler>` | 路由路径和处理函数的映射 |
|
|
1259
|
+
| `deps` | `readonly unknown[]` | 依赖数组(可选,当依赖变化时重新注册) |
|
|
1260
|
+
|
|
1261
|
+
**示例:**
|
|
1262
|
+
|
|
1263
|
+
```tsx
|
|
1264
|
+
import { useServer, useServerHandlerMap } from 'request-iframe/react';
|
|
1265
|
+
import { useState } from 'react';
|
|
1266
|
+
|
|
1267
|
+
const MyComponent = () => {
|
|
1268
|
+
const server = useServer();
|
|
1269
|
+
const [userId, setUserId] = useState(1);
|
|
1270
|
+
|
|
1271
|
+
// 一次性注册多个 handlers
|
|
1272
|
+
useServerHandlerMap(server, {
|
|
1273
|
+
'/api/user': (req, res) => {
|
|
1274
|
+
res.send({ userId, data: '用户数据' });
|
|
1275
|
+
},
|
|
1276
|
+
'/api/posts': (req, res) => {
|
|
1277
|
+
res.send({ userId, data: '文章数据' });
|
|
1278
|
+
}
|
|
1279
|
+
}, [userId]); // 当 userId 变化时重新注册所有 handlers
|
|
1280
|
+
|
|
1281
|
+
return <div>Server 组件</div>;
|
|
1282
|
+
};
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
**关键特性:**
|
|
1286
|
+
- 批量注册多个 handlers
|
|
1287
|
+
- 自动处理闭包问题 - 始终使用依赖项的最新值
|
|
1288
|
+
- 组件卸载或依赖变化时自动取消注册所有 handlers
|
|
1289
|
+
- 高效 - 仅在 map 的键变化时重新注册
|
|
1290
|
+
|
|
1291
|
+
### 完整示例
|
|
1292
|
+
|
|
1293
|
+
以下是一个完整的示例,展示如何在真实应用中使用 React hooks:
|
|
1294
|
+
|
|
1295
|
+
```tsx
|
|
1296
|
+
import { useClient, useServer, useServerHandler } from 'request-iframe/react';
|
|
1297
|
+
import { useRef, useState } from 'react';
|
|
1298
|
+
|
|
1299
|
+
// 父组件(Client)
|
|
1300
|
+
const ParentComponent = () => {
|
|
1301
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
1302
|
+
const client = useClient(iframeRef, { secretKey: 'my-app' });
|
|
1303
|
+
const [data, setData] = useState(null);
|
|
1304
|
+
|
|
1305
|
+
const fetchData = async () => {
|
|
1306
|
+
if (!client) return;
|
|
1307
|
+
|
|
1308
|
+
try {
|
|
1309
|
+
const response = await client.send('/api/data', { id: 1 });
|
|
1310
|
+
setData(response.data);
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
console.error('请求失败:', error);
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
return (
|
|
1317
|
+
<div>
|
|
1318
|
+
<iframe ref={iframeRef} src="/iframe.html" />
|
|
1319
|
+
<button onClick={fetchData}>获取数据</button>
|
|
1320
|
+
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
|
|
1321
|
+
</div>
|
|
1322
|
+
);
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
// Iframe 组件(Server)
|
|
1326
|
+
const IframeComponent = () => {
|
|
1327
|
+
const server = useServer({ secretKey: 'my-app' });
|
|
1328
|
+
const [userId, setUserId] = useState(1);
|
|
1329
|
+
|
|
1330
|
+
// 注册 handler,自动清理
|
|
1331
|
+
useServerHandler(server, '/api/data', async (req, res) => {
|
|
1332
|
+
// Handler 始终使用最新的 userId 值
|
|
1333
|
+
const userData = await fetchUserData(userId);
|
|
1334
|
+
res.send(userData);
|
|
1335
|
+
}, [userId]);
|
|
1336
|
+
|
|
1337
|
+
return (
|
|
1338
|
+
<div>
|
|
1339
|
+
<p>用户 ID: {userId}</p>
|
|
1340
|
+
<button onClick={() => setUserId(userId + 1)}>增加</button>
|
|
1341
|
+
</div>
|
|
1342
|
+
);
|
|
1343
|
+
};
|
|
1344
|
+
```
|
|
1345
|
+
|
|
1346
|
+
### 最佳实践
|
|
1347
|
+
|
|
1348
|
+
1. **始终检查 null**:Client 和 server hooks 在初始时或目标不可用时可能返回 `null`:
|
|
1349
|
+
```tsx
|
|
1350
|
+
const client = useClient(iframeRef);
|
|
1351
|
+
if (!client) return null; // 处理 null 情况
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
2. **使用依赖数组**:向 hooks 传递依赖项,确保 handlers 使用最新值:
|
|
1355
|
+
```tsx
|
|
1356
|
+
useServerHandler(server, '/api/data', (req, res) => {
|
|
1357
|
+
res.send({ userId }); // 始终使用最新的 userId
|
|
1358
|
+
}, [userId]); // 当 userId 变化时重新注册
|
|
1359
|
+
```
|
|
1360
|
+
|
|
1361
|
+
3. **自动清理**:Hooks 在组件卸载时自动清理,但你也可以手动取消注册:
|
|
1362
|
+
```tsx
|
|
1363
|
+
useEffect(() => {
|
|
1364
|
+
if (!server) return;
|
|
1365
|
+
const off = server.on('/api/data', handler);
|
|
1366
|
+
return off; // 手动清理(可选,hooks 会自动处理)
|
|
1367
|
+
}, [server]);
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
---
|
|
1371
|
+
|
|
1372
|
+
## 错误处理
|
|
988
1373
|
|
|
989
1374
|
```typescript
|
|
990
1375
|
interface ServerRequest {
|
|
991
|
-
body: any; // 请求 body
|
|
1376
|
+
body: any; // 请求 body(普通数据;或 autoResolve=true 时的 File/Blob)
|
|
1377
|
+
stream?: IIframeReadableStream; // 请求流(sendStream;或 sendFile 且 autoResolve=false)
|
|
992
1378
|
headers: Record<string, string>; // 请求 headers
|
|
993
1379
|
cookies: Record<string, string>; // 请求 cookies
|
|
994
|
-
path: string; //
|
|
1380
|
+
path: string; // 请求路径(实际请求路径)
|
|
1381
|
+
params: Record<string, string>; // 路由参数(由 server.on 注册的路径模式解析得出,如 /api/users/:id)
|
|
995
1382
|
requestId: string; // 请求 ID
|
|
996
1383
|
origin: string; // 来源 origin
|
|
997
1384
|
source: Window; // 来源 window
|
|
@@ -999,6 +1386,22 @@ interface ServerRequest {
|
|
|
999
1386
|
}
|
|
1000
1387
|
```
|
|
1001
1388
|
|
|
1389
|
+
**路径参数(类似 Express)**:
|
|
1390
|
+
|
|
1391
|
+
支持使用 `:param` 形式声明路由参数,解析结果在 `req.params` 中。
|
|
1392
|
+
|
|
1393
|
+
```typescript
|
|
1394
|
+
server.on('/api/users/:id', (req, res) => {
|
|
1395
|
+
// 请求 /api/users/123 时:req.params.id === '123'
|
|
1396
|
+
res.send({ userId: req.params.id });
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
server.on('/api/users/:userId/posts/:postId', (req, res) => {
|
|
1400
|
+
const { userId, postId } = req.params;
|
|
1401
|
+
res.send({ userId, postId });
|
|
1402
|
+
});
|
|
1403
|
+
```
|
|
1404
|
+
|
|
1002
1405
|
### ServerResponse 对象
|
|
1003
1406
|
|
|
1004
1407
|
```typescript
|