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/README.md CHANGED
@@ -74,7 +74,7 @@ In micro-frontend and iframe nesting scenarios, parent-child page communication
74
74
  - ⏱️ **Smart Timeout** - Three-stage timeout (connection/sync/async), automatically detects long tasks
75
75
  - 📦 **TypeScript** - Complete type definitions and IntelliSense
76
76
  - 🔒 **Message Isolation** - secretKey mechanism prevents message cross-talk between multiple instances
77
- - 📁 **File Transfer** - Support for base64-encoded file sending
77
+ - 📁 **File Transfer** - Support for file sending via stream (client→server)
78
78
  - 🌊 **Streaming** - Support for large file chunked transfer, supports async iterators
79
79
  - 🌍 **Internationalization** - Error messages can be customized for i18n
80
80
  - ✅ **Protocol Versioning** - Built-in version control for upgrade compatibility
@@ -143,7 +143,8 @@ In micro-frontend architecture, the main application needs to communicate with c
143
143
  const client = requestIframeClient(iframe, { secretKey: 'main-app' });
144
144
 
145
145
  // Get user info from child application
146
- const userInfo = await client.send('/api/user/info', {});
146
+ const userInfoResponse = await client.send('/api/user/info', {});
147
+ console.log(userInfoResponse.data); // User info data
147
148
 
148
149
  // Notify child application to refresh data
149
150
  await client.send('/api/data/refresh', { timestamp: Date.now() });
@@ -187,7 +188,8 @@ server.on('/api/data', async (req, res) => {
187
188
 
188
189
  // Parent page (cross-origin)
189
190
  const client = requestIframeClient(iframe, { secretKey: 'data-api' });
190
- const data = await client.send('/api/data', {}); // Successfully fetch cross-origin data
191
+ const response = await client.send('/api/data', {});
192
+ const data = response.data; // Successfully fetch cross-origin data
191
193
  ```
192
194
 
193
195
  ### File Preview and Download
@@ -209,8 +211,8 @@ server.on('/api/processFile', async (req, res) => {
209
211
 
210
212
  // Parent page: download file
211
213
  const response = await client.send('/api/processFile', { fileId: '123' });
212
- if (response.fileData) {
213
- downloadFile(response.fileData);
214
+ if (response.data instanceof File || response.data instanceof Blob) {
215
+ downloadFile(response.data);
214
216
  }
215
217
  ```
216
218
 
@@ -478,10 +480,12 @@ server.on('/api/logout', (req, res) => {
478
480
  await client.send('/api/login', { username: 'tom', password: '123' });
479
481
 
480
482
  // Client side: Subsequent request to /api/getUserInfo (automatically carries authToken and userId)
481
- const userInfo = await client.send('/api/getUserInfo', {});
483
+ const userInfoResponse = await client.send('/api/getUserInfo', {});
484
+ const userInfo = userInfoResponse.data;
482
485
 
483
486
  // Client side: Request root path (only carries userId, because authToken's path is /api)
484
- const rootData = await client.send('/other', {});
487
+ const rootResponse = await client.send('/other', {});
488
+ const rootData = rootResponse.data;
485
489
  ```
486
490
 
487
491
  #### Client Cookie Management API
@@ -540,6 +544,8 @@ server.on('/api/data', (req, res) => {
540
544
 
541
545
  ### File Transfer
542
546
 
547
+ #### Server → Client (Server sends file to client)
548
+
543
549
  ```typescript
544
550
  // Server side: Send file
545
551
  server.on('/api/download', async (req, res) => {
@@ -556,32 +562,60 @@ server.on('/api/download', async (req, res) => {
556
562
 
557
563
  // Client side: Receive
558
564
  const response = await client.send('/api/download', {});
559
- if (response.fileData) {
560
- const { content, mimeType, fileName } = response.fileData;
561
-
562
- // content is base64-encoded string
563
- const binaryString = atob(content);
564
- 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';
565
568
 
566
- // Download file
567
- const url = URL.createObjectURL(blob);
569
+ // Download file directly using File/Blob
570
+ const url = URL.createObjectURL(response.data);
568
571
  const a = document.createElement('a');
569
572
  a.href = url;
570
- a.download = fileName || 'download';
573
+ a.download = fileName;
571
574
  a.click();
575
+ URL.revokeObjectURL(url);
572
576
  }
573
577
  ```
574
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
+
575
607
  ### Streaming
576
608
 
577
609
  For large files or scenarios requiring chunked transfer, you can use streaming:
578
610
 
611
+ #### Server → Client (Server sends stream to client)
612
+
579
613
  ```typescript
580
614
  import {
581
615
  IframeWritableStream,
582
616
  IframeFileWritableStream,
583
617
  isIframeReadableStream,
584
- isIframeFileStream
618
+ isIframeFileReadableStream
585
619
  } from 'request-iframe';
586
620
 
587
621
  // Server side: Send data stream using iterator
@@ -629,6 +663,55 @@ if (isIframeReadableStream(response.stream)) {
629
663
  }
630
664
  ```
631
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
+
632
715
  **Stream Types:**
633
716
 
634
717
  | Type | Description |
@@ -743,14 +826,14 @@ Create a Server instance.
743
826
 
744
827
  #### client.send(path, body?, options?)
745
828
 
746
- Send a request.
829
+ Send a request. Automatically dispatches to `sendFile()` or `sendStream()` based on body type.
747
830
 
748
831
  **Parameters:**
749
832
 
750
833
  | Parameter | Type | Description |
751
834
  |-----------|------|-------------|
752
835
  | `path` | `string` | Request path |
753
- | `body` | `object` | Request data (optional) |
836
+ | `body` | `any` | Request data (optional). Can be plain object, File, Blob, or IframeWritableStream. Automatically dispatches: File/Blob → `sendFile()`, IframeWritableStream → `sendStream()` |
754
837
  | `options.ackTimeout` | `number` | ACK acknowledgment timeout (ms), default 1000 |
755
838
  | `options.timeout` | `number` | Request timeout (ms), default 5000 |
756
839
  | `options.asyncTimeout` | `number` | Async timeout (ms), default 120000 |
@@ -771,6 +854,69 @@ interface Response<T = any> {
771
854
  }
772
855
  ```
773
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
+
774
920
  #### client.isConnect()
775
921
 
776
922
  Detect if Server is reachable.
@@ -806,6 +952,71 @@ Register route handler.
806
952
  type ServerHandler = (req: ServerRequest, res: ServerResponse) => any | Promise<any>;
807
953
  ```
808
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
+
809
1020
  #### server.off(path)
810
1021
 
811
1022
  Remove route handler.