request-iframe 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/QUICKSTART.CN.md +35 -8
  2. package/QUICKSTART.md +35 -8
  3. package/README.CN.md +170 -24
  4. package/README.md +230 -19
  5. package/library/__tests__/coverage-branches.test.ts +356 -0
  6. package/library/__tests__/requestIframe.test.ts +1008 -58
  7. package/library/__tests__/stream.test.ts +46 -15
  8. package/library/api/client.d.ts.map +1 -1
  9. package/library/api/client.js +1 -0
  10. package/library/constants/messages.d.ts +2 -0
  11. package/library/constants/messages.d.ts.map +1 -1
  12. package/library/constants/messages.js +2 -0
  13. package/library/core/client-server.d.ts +4 -0
  14. package/library/core/client-server.d.ts.map +1 -1
  15. package/library/core/client-server.js +45 -22
  16. package/library/core/client.d.ts +31 -4
  17. package/library/core/client.d.ts.map +1 -1
  18. package/library/core/client.js +471 -284
  19. package/library/core/request.d.ts +3 -1
  20. package/library/core/request.d.ts.map +1 -1
  21. package/library/core/request.js +2 -1
  22. package/library/core/response.d.ts +26 -4
  23. package/library/core/response.d.ts.map +1 -1
  24. package/library/core/response.js +142 -81
  25. package/library/core/server.d.ts +13 -0
  26. package/library/core/server.d.ts.map +1 -1
  27. package/library/core/server.js +211 -6
  28. package/library/index.d.ts +2 -1
  29. package/library/index.d.ts.map +1 -1
  30. package/library/index.js +32 -3
  31. package/library/message/dispatcher.d.ts.map +1 -1
  32. package/library/message/dispatcher.js +4 -3
  33. package/library/stream/index.d.ts +11 -1
  34. package/library/stream/index.d.ts.map +1 -1
  35. package/library/stream/index.js +21 -3
  36. package/library/stream/types.d.ts +2 -2
  37. package/library/stream/types.d.ts.map +1 -1
  38. package/library/stream/writable-stream.d.ts +1 -1
  39. package/library/stream/writable-stream.d.ts.map +1 -1
  40. package/library/stream/writable-stream.js +8 -10
  41. package/library/types/index.d.ts +26 -4
  42. package/library/types/index.d.ts.map +1 -1
  43. package/library/utils/index.d.ts +14 -0
  44. package/library/utils/index.d.ts.map +1 -1
  45. package/library/utils/index.js +99 -1
  46. package/library/utils/path-match.d.ts +16 -0
  47. package/library/utils/path-match.d.ts.map +1 -1
  48. package/library/utils/path-match.js +65 -0
  49. package/package.json +2 -1
  50. package/react/library/__tests__/index.test.tsx +44 -22
  51. package/react/library/index.d.ts.map +1 -1
  52. package/react/library/index.js +81 -23
  53. package/react/package.json +7 -0
package/QUICKSTART.CN.md CHANGED
@@ -172,22 +172,49 @@ server.on('/api/download', async (req, res) => {
172
172
 
173
173
  // Client 端
174
174
  const response = await client.send('/api/download', {});
175
- if (response.fileData) {
175
+ if (response.data instanceof File || response.data instanceof Blob) {
176
+ const file = response.data instanceof File ? response.data : null;
177
+ const fileName = file?.name || 'download';
178
+
176
179
  // 创建下载链接
177
- const blob = new Blob(
178
- [atob(response.fileData.content)],
179
- { type: response.fileData.mimeType }
180
- );
181
- const url = URL.createObjectURL(blob);
182
-
180
+ const url = URL.createObjectURL(response.data);
181
+
183
182
  // 触发下载
184
183
  const a = document.createElement('a');
185
184
  a.href = url;
186
- a.download = response.fileData.fileName || 'download';
185
+ a.download = fileName;
187
186
  a.click();
187
+ URL.revokeObjectURL(url);
188
188
  }
189
189
  ```
190
190
 
191
+ ### 文件上传(Client → Server)
192
+
193
+ Client 向 Server 发送文件仅走**流式**。默认 `autoResolve: true`,Server 会在进入 handler 前把文件解析成 `File/Blob` 放到 `req.body`。
194
+
195
+ ```typescript
196
+ // Client 端
197
+ const file = new File(['Hello Upload'], 'upload.txt', { type: 'text/plain' });
198
+ await client.send('/api/upload', file); // File/Blob 会自动分发到 sendFile(走流式)
199
+
200
+ // Server 端
201
+ server.on('/api/upload', async (req, res) => {
202
+ const blob = req.body as Blob;
203
+ const text = await blob.text();
204
+ res.send({ ok: true, text });
205
+ });
206
+ ```
207
+
208
+ ### 路由参数(req.params)
209
+
210
+ 支持 Express 风格的 `:param` 路由参数,解析结果在 `req.params`。
211
+
212
+ ```typescript
213
+ server.on('/api/users/:id', (req, res) => {
214
+ res.send({ userId: req.params.id });
215
+ });
216
+ ```
217
+
191
218
  ### 调试模式
192
219
 
193
220
  开启 trace 模式查看详细日志:
package/QUICKSTART.md CHANGED
@@ -172,22 +172,49 @@ server.on('/api/download', async (req, res) => {
172
172
 
173
173
  // Client side
174
174
  const response = await client.send('/api/download', {});
175
- if (response.fileData) {
175
+ if (response.data instanceof File || response.data instanceof Blob) {
176
+ const file = response.data instanceof File ? response.data : null;
177
+ const fileName = file?.name || 'download';
178
+
176
179
  // Create download link
177
- const blob = new Blob(
178
- [atob(response.fileData.content)],
179
- { type: response.fileData.mimeType }
180
- );
181
- const url = URL.createObjectURL(blob);
182
-
180
+ const url = URL.createObjectURL(response.data);
181
+
183
182
  // Trigger download
184
183
  const a = document.createElement('a');
185
184
  a.href = url;
186
- a.download = response.fileData.fileName || 'download';
185
+ a.download = fileName;
187
186
  a.click();
187
+ URL.revokeObjectURL(url);
188
188
  }
189
189
  ```
190
190
 
191
+ ### File Upload (Client → Server)
192
+
193
+ Client sends files via stream only. By default `autoResolve: true`, server will resolve the file to `File/Blob` and put it into `req.body` before calling your handler.
194
+
195
+ ```typescript
196
+ // Client side
197
+ const file = new File(['Hello Upload'], 'upload.txt', { type: 'text/plain' });
198
+ await client.send('/api/upload', file); // File/Blob auto-dispatches to sendFile (stream)
199
+
200
+ // Server side
201
+ server.on('/api/upload', async (req, res) => {
202
+ const blob = req.body as Blob;
203
+ const text = await blob.text();
204
+ res.send({ ok: true, text });
205
+ });
206
+ ```
207
+
208
+ ### Route Params (req.params)
209
+
210
+ Supports Express-style `:param` route parameters. Extracted parameters are available in `req.params`.
211
+
212
+ ```typescript
213
+ server.on('/api/users/:id', (req, res) => {
214
+ res.send({ userId: req.params.id });
215
+ });
216
+ ```
217
+
191
218
  ### Debug Mode
192
219
 
193
220
  Enable trace mode to view detailed logs:
package/README.CN.md CHANGED
@@ -74,7 +74,7 @@
74
74
  - ⏱️ **智能超时** - 三阶段超时(连接/同步/异步),自动识别长任务
75
75
  - 📦 **TypeScript** - 完整的类型定义和智能提示
76
76
  - 🔒 **消息隔离** - secretKey 机制避免多实例消息串线
77
- - 📁 **文件传输** - 支持 base64 编码的文件发送
77
+ - 📁 **文件传输** - 支持文件通过流方式传输(Client→Server)
78
78
  - 🌊 **流式传输** - 支持大文件分块传输,支持异步迭代器
79
79
  - 🌍 **多语言** - 错误消息可自定义,便于国际化
80
80
  - ✅ **协议版本** - 内置版本控制,便于升级兼容
@@ -252,7 +252,8 @@ client.send('/api/getData', data, {
252
252
  const client = requestIframeClient(iframe, { secretKey: 'main-app' });
253
253
 
254
254
  // 获取子应用的用户信息
255
- const userInfo = await client.send('/api/user/info', {});
255
+ const userInfoResponse = await client.send('/api/user/info', {});
256
+ console.log(userInfoResponse.data); // 用户信息数据
256
257
 
257
258
  // 通知子应用更新数据
258
259
  await client.send('/api/data/refresh', { timestamp: Date.now() });
@@ -296,7 +297,8 @@ server.on('/api/data', async (req, res) => {
296
297
 
297
298
  // 父页面(跨域)
298
299
  const client = requestIframeClient(iframe, { secretKey: 'data-api' });
299
- const data = await client.send('/api/data', {}); // 成功获取跨域数据
300
+ const response = await client.send('/api/data', {});
301
+ const data = response.data; // 成功获取跨域数据
300
302
  ```
301
303
 
302
304
  ### 文件预览和下载
@@ -318,8 +320,8 @@ server.on('/api/processFile', async (req, res) => {
318
320
 
319
321
  // 父页面:下载文件
320
322
  const response = await client.send('/api/processFile', { fileId: '123' });
321
- if (response.fileData) {
322
- downloadFile(response.fileData);
323
+ if (response.data instanceof File || response.data instanceof Blob) {
324
+ downloadFile(response.data);
323
325
  }
324
326
  ```
325
327
 
@@ -505,10 +507,12 @@ server.on('/api/logout', (req, res) => {
505
507
  await client.send('/api/login', { username: 'tom', password: '123' });
506
508
 
507
509
  // Client 端:后续请求 /api/getUserInfo(自动携带 authToken 和 userId)
508
- const userInfo = await client.send('/api/getUserInfo', {});
510
+ const userInfoResponse = await client.send('/api/getUserInfo', {});
511
+ const userInfo = userInfoResponse.data;
509
512
 
510
513
  // Client 端:请求根路径(只携带 userId,因为 authToken 的 path 是 /api)
511
- const rootData = await client.send('/other', {});
514
+ const rootResponse = await client.send('/other', {});
515
+ const rootData = rootResponse.data;
512
516
  ```
513
517
 
514
518
  #### Client Cookie 管理 API
@@ -583,22 +587,48 @@ server.on('/api/download', async (req, res) => {
583
587
 
584
588
  // Client 端接收
585
589
  const response = await client.send('/api/download', {});
586
- if (response.fileData) {
587
- const { content, mimeType, fileName } = response.fileData;
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';
588
593
 
589
- // content base64 编码的字符串
590
- const binaryString = atob(content);
591
- const blob = new Blob([binaryString], { type: mimeType });
592
-
593
- // 下载文件
594
- const url = URL.createObjectURL(blob);
594
+ // 直接使用 File/Blob 下载文件
595
+ const url = URL.createObjectURL(response.data);
595
596
  const a = document.createElement('a');
596
597
  a.href = url;
597
- a.download = fileName || 'download';
598
+ a.download = fileName;
598
599
  a.click();
600
+ URL.revokeObjectURL(url);
599
601
  }
600
602
  ```
601
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
+
602
632
  ### 流式传输(Stream)
603
633
 
604
634
  对于大文件或需要分块传输的场景,可以使用流式传输:
@@ -608,7 +638,7 @@ import {
608
638
  IframeWritableStream,
609
639
  IframeFileWritableStream,
610
640
  isIframeReadableStream,
611
- isIframeFileStream
641
+ isIframeFileReadableStream
612
642
  } from 'request-iframe';
613
643
 
614
644
  // Server 端:使用迭代器发送数据流
@@ -692,7 +722,7 @@ if (isIframeReadableStream(response.stream)) {
692
722
  // Client 端:接收文件流
693
723
  const fileResponse = await client.send('/api/fileStream', {});
694
724
 
695
- if (isIframeFileStream(fileResponse.stream)) {
725
+ if (isIframeFileReadableStream(fileResponse.stream)) {
696
726
  // 读取为 Blob
697
727
  const blob = await fileResponse.stream.readAsBlob();
698
728
 
@@ -714,9 +744,9 @@ if (isIframeFileStream(fileResponse.stream)) {
714
744
  | 类型 | 说明 |
715
745
  |------|------|
716
746
  | `IframeWritableStream` | 服务端可写流,用于发送普通数据 |
717
- | `IframeFileWritableStream` | 服务端文件可写流,自动处理 base64 编码 |
747
+ | `IframeFileWritableStream` | 服务端文件可写流(文件流) |
718
748
  | `IframeReadableStream` | 客户端可读流,用于接收普通数据 |
719
- | `IframeFileReadableStream` | 客户端文件可读流,自动处理 base64 解码 |
749
+ | `IframeFileReadableStream` | 客户端文件可读流(文件流) |
720
750
 
721
751
  **流选项:**
722
752
 
@@ -852,14 +882,16 @@ await client.send('/api/longTask', {}, {
852
882
 
853
883
  #### client.send(path, body?, options?)
854
884
 
855
- 发送请求。
885
+ 发送请求。会根据 body 类型自动分发到 `sendFile()` 或 `sendStream()`:
886
+ - `File/Blob` → `sendFile()`
887
+ - `IframeWritableStream` → `sendStream()`
856
888
 
857
889
  **参数:**
858
890
 
859
891
  | 参数 | 类型 | 说明 |
860
892
  |------|------|------|
861
893
  | `path` | `string` | 请求路径 |
862
- | `body` | `object` | 请求数据(可选) |
894
+ | `body` | `any` | 请求数据(可选)。可以是普通对象、File、Blob、或 IframeWritableStream;会自动分发:File/Blob → `sendFile()`,IframeWritableStream → `sendStream()` |
863
895
  | `options.ackTimeout` | `number` | ACK 确认超时(ms),默认 1000 |
864
896
  | `options.timeout` | `number` | 请求超时(ms),默认 5000 |
865
897
  | `options.asyncTimeout` | `number` | 异步超时(ms),默认 120000 |
@@ -880,6 +912,69 @@ interface Response<T = any> {
880
912
  }
881
913
  ```
882
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
+
883
978
  #### client.isConnect()
884
979
 
885
980
  检测 Server 是否可达。
@@ -952,6 +1047,39 @@ client.interceptors.response.use(onFulfilled, onRejected?);
952
1047
  type ServerHandler = (req: ServerRequest, res: ServerResponse) => any | Promise<any>;
953
1048
  ```
954
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
+
955
1083
  #### server.off(path)
956
1084
 
957
1085
  移除路由处理器。
@@ -1245,10 +1373,12 @@ const IframeComponent = () => {
1245
1373
 
1246
1374
  ```typescript
1247
1375
  interface ServerRequest {
1248
- body: any; // 请求 body
1376
+ body: any; // 请求 body(普通数据;或 autoResolve=true 时的 File/Blob)
1377
+ stream?: IIframeReadableStream; // 请求流(sendStream;或 sendFile 且 autoResolve=false)
1249
1378
  headers: Record<string, string>; // 请求 headers
1250
1379
  cookies: Record<string, string>; // 请求 cookies
1251
- path: string; // 请求路径
1380
+ path: string; // 请求路径(实际请求路径)
1381
+ params: Record<string, string>; // 路由参数(由 server.on 注册的路径模式解析得出,如 /api/users/:id)
1252
1382
  requestId: string; // 请求 ID
1253
1383
  origin: string; // 来源 origin
1254
1384
  source: Window; // 来源 window
@@ -1256,6 +1386,22 @@ interface ServerRequest {
1256
1386
  }
1257
1387
  ```
1258
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
+
1259
1405
  ### ServerResponse 对象
1260
1406
 
1261
1407
  ```typescript