request-iframe 0.0.1

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 (96) hide show
  1. package/QUICKSTART.CN.md +269 -0
  2. package/QUICKSTART.md +269 -0
  3. package/README.CN.md +1369 -0
  4. package/README.md +1016 -0
  5. package/library/__tests__/interceptors.test.ts +124 -0
  6. package/library/__tests__/requestIframe.test.ts +2216 -0
  7. package/library/__tests__/stream.test.ts +650 -0
  8. package/library/__tests__/utils.test.ts +433 -0
  9. package/library/api/client.d.ts +16 -0
  10. package/library/api/client.d.ts.map +1 -0
  11. package/library/api/client.js +72 -0
  12. package/library/api/server.d.ts +16 -0
  13. package/library/api/server.d.ts.map +1 -0
  14. package/library/api/server.js +44 -0
  15. package/library/constants/index.d.ts +209 -0
  16. package/library/constants/index.d.ts.map +1 -0
  17. package/library/constants/index.js +260 -0
  18. package/library/constants/messages.d.ts +80 -0
  19. package/library/constants/messages.d.ts.map +1 -0
  20. package/library/constants/messages.js +123 -0
  21. package/library/core/client.d.ts +99 -0
  22. package/library/core/client.d.ts.map +1 -0
  23. package/library/core/client.js +440 -0
  24. package/library/core/message-handler.d.ts +110 -0
  25. package/library/core/message-handler.d.ts.map +1 -0
  26. package/library/core/message-handler.js +320 -0
  27. package/library/core/request-response.d.ts +59 -0
  28. package/library/core/request-response.d.ts.map +1 -0
  29. package/library/core/request-response.js +337 -0
  30. package/library/core/request.d.ts +17 -0
  31. package/library/core/request.d.ts.map +1 -0
  32. package/library/core/request.js +34 -0
  33. package/library/core/response.d.ts +51 -0
  34. package/library/core/response.d.ts.map +1 -0
  35. package/library/core/response.js +323 -0
  36. package/library/core/server-base.d.ts +86 -0
  37. package/library/core/server-base.d.ts.map +1 -0
  38. package/library/core/server-base.js +257 -0
  39. package/library/core/server-client.d.ts +99 -0
  40. package/library/core/server-client.d.ts.map +1 -0
  41. package/library/core/server-client.js +256 -0
  42. package/library/core/server.d.ts +82 -0
  43. package/library/core/server.d.ts.map +1 -0
  44. package/library/core/server.js +338 -0
  45. package/library/index.d.ts +16 -0
  46. package/library/index.d.ts.map +1 -0
  47. package/library/index.js +211 -0
  48. package/library/interceptors/index.d.ts +41 -0
  49. package/library/interceptors/index.d.ts.map +1 -0
  50. package/library/interceptors/index.js +126 -0
  51. package/library/message/channel.d.ts +107 -0
  52. package/library/message/channel.d.ts.map +1 -0
  53. package/library/message/channel.js +184 -0
  54. package/library/message/dispatcher.d.ts +119 -0
  55. package/library/message/dispatcher.d.ts.map +1 -0
  56. package/library/message/dispatcher.js +249 -0
  57. package/library/message/index.d.ts +5 -0
  58. package/library/message/index.d.ts.map +1 -0
  59. package/library/message/index.js +25 -0
  60. package/library/stream/file-stream.d.ts +48 -0
  61. package/library/stream/file-stream.d.ts.map +1 -0
  62. package/library/stream/file-stream.js +240 -0
  63. package/library/stream/index.d.ts +15 -0
  64. package/library/stream/index.d.ts.map +1 -0
  65. package/library/stream/index.js +83 -0
  66. package/library/stream/readable-stream.d.ts +83 -0
  67. package/library/stream/readable-stream.d.ts.map +1 -0
  68. package/library/stream/readable-stream.js +249 -0
  69. package/library/stream/types.d.ts +165 -0
  70. package/library/stream/types.d.ts.map +1 -0
  71. package/library/stream/types.js +5 -0
  72. package/library/stream/writable-stream.d.ts +60 -0
  73. package/library/stream/writable-stream.d.ts.map +1 -0
  74. package/library/stream/writable-stream.js +348 -0
  75. package/library/types/index.d.ts +408 -0
  76. package/library/types/index.d.ts.map +1 -0
  77. package/library/types/index.js +5 -0
  78. package/library/utils/cache.d.ts +19 -0
  79. package/library/utils/cache.d.ts.map +1 -0
  80. package/library/utils/cache.js +83 -0
  81. package/library/utils/cookie.d.ts +117 -0
  82. package/library/utils/cookie.d.ts.map +1 -0
  83. package/library/utils/cookie.js +365 -0
  84. package/library/utils/debug.d.ts +11 -0
  85. package/library/utils/debug.d.ts.map +1 -0
  86. package/library/utils/debug.js +162 -0
  87. package/library/utils/index.d.ts +13 -0
  88. package/library/utils/index.d.ts.map +1 -0
  89. package/library/utils/index.js +132 -0
  90. package/library/utils/path-match.d.ts +17 -0
  91. package/library/utils/path-match.d.ts.map +1 -0
  92. package/library/utils/path-match.js +90 -0
  93. package/library/utils/protocol.d.ts +61 -0
  94. package/library/utils/protocol.d.ts.map +1 -0
  95. package/library/utils/protocol.js +169 -0
  96. package/package.json +58 -0
package/README.md ADDED
@@ -0,0 +1,1016 @@
1
+ # request-iframe
2
+
3
+ Communicate with iframes like sending HTTP requests! A cross-origin iframe communication library based on `postMessage`.
4
+
5
+ <p align="center">
6
+ <img src="https://img.shields.io/badge/TypeScript-Ready-blue" alt="TypeScript Ready">
7
+ <img src="https://img.shields.io/badge/API-Express%20Like-green" alt="Express Like API">
8
+ <img src="https://img.shields.io/badge/License-MIT-yellow" alt="MIT License">
9
+ <img src="https://img.shields.io/badge/Test%20Coverage-76%25-brightgreen" alt="Test Coverage">
10
+ </p>
11
+
12
+ ## 📑 Table of Contents
13
+
14
+ - [Why request-iframe?](#why-request-iframe)
15
+ - [Features](#features)
16
+ - [Installation](#installation)
17
+ - [Quick Start](#quick-start)
18
+ - [Use Cases](#use-cases)
19
+ - [How It Works](#how-it-works)
20
+ - [Communication Protocol](#communication-protocol)
21
+ - [Message Types](#message-types)
22
+ - [Timeout Mechanism](#timeout-mechanism)
23
+ - [Protocol Version](#protocol-version)
24
+ - [Detailed Features](#detailed-features)
25
+ - [Interceptors](#interceptors)
26
+ - [Middleware](#middleware)
27
+ - [Headers and Cookies](#headers-and-cookies)
28
+ - [File Transfer](#file-transfer)
29
+ - [Streaming](#streaming)
30
+ - [Connection Detection](#connection-detection)
31
+ - [Response Acknowledgment](#response-acknowledgment)
32
+ - [Trace Mode](#trace-mode)
33
+ - [Internationalization](#internationalization)
34
+ - [API Reference](#api-reference)
35
+ - [Error Handling](#error-handling)
36
+ - [FAQ](#faq)
37
+ - [Development](#development)
38
+ - [License](#license)
39
+
40
+ ## Why request-iframe?
41
+
42
+ In micro-frontend and iframe nesting scenarios, parent-child page communication is a common requirement. Traditional `postMessage` communication has the following pain points:
43
+
44
+ | Pain Point | Traditional Way | request-iframe |
45
+ |------------|----------------|----------------|
46
+ | Request-Response Association | Manual requestId management | Automatic management, Promise style |
47
+ | Timeout Handling | Manual timer implementation | Built-in multi-stage timeout mechanism |
48
+ | Error Handling | Various edge cases | Standardized error codes |
49
+ | Message Isolation | Easy to cross-talk | secretKey automatic isolation |
50
+ | API Style | Event listener style | HTTP-like request/Express style |
51
+ | TypeScript | Need custom types | Full type support |
52
+ | Test Coverage | None | 76%+ test coverage |
53
+
54
+ **Core Advantages**:
55
+ - ✅ **Zero Learning Curve** - If you're familiar with axios and Express, you can get started immediately
56
+ - ✅ **Type Safe** - Full TypeScript support for a great development experience
57
+ - ✅ **Production Ready** - High test coverage, thoroughly tested
58
+ - ✅ **Feature Rich** - Interceptors, middleware, streaming, file transfer all included
59
+
60
+ ## Features
61
+
62
+ - 🚀 **HTTP-like Style** - Client sends requests, Server handles and responds, just like axios + express
63
+ - 🔌 **Interceptor Support** - Request/response interceptors for unified authentication, logging, etc.
64
+ - 🎭 **Middleware Mechanism** - Express-style middleware with path matching support
65
+ - ⏱️ **Smart Timeout** - Three-stage timeout (connection/sync/async), automatically detects long tasks
66
+ - 📦 **TypeScript** - Complete type definitions and IntelliSense
67
+ - 🔒 **Message Isolation** - secretKey mechanism prevents message cross-talk between multiple instances
68
+ - 📁 **File Transfer** - Support for base64-encoded file sending
69
+ - 🌊 **Streaming** - Support for large file chunked transfer, supports async iterators
70
+ - 🌍 **Internationalization** - Error messages can be customized for i18n
71
+ - ✅ **Protocol Versioning** - Built-in version control for upgrade compatibility
72
+
73
+ ## Installation
74
+
75
+ ```bash
76
+ npm install request-iframe
77
+ # or
78
+ yarn add request-iframe
79
+ # or
80
+ pnpm add request-iframe
81
+ ```
82
+
83
+ **Requirements**: Node.js >= 14
84
+
85
+ **TypeScript**: Built-in complete type definitions, no need to install `@types/request-iframe`
86
+
87
+ ## Quick Start
88
+
89
+ ### 1. Parent Page (Client Side)
90
+
91
+ ```typescript
92
+ import { requestIframeClient } from 'request-iframe';
93
+
94
+ // Get iframe element
95
+ const iframe = document.querySelector('iframe')!;
96
+
97
+ // Create client
98
+ const client = requestIframeClient(iframe, { secretKey: 'my-app' });
99
+
100
+ // Send request (just like axios)
101
+ const response = await client.send('/api/getUserInfo', { userId: 123 });
102
+ console.log(response.data); // { name: 'Tom', age: 18 }
103
+ ```
104
+
105
+ ### 2. iframe Page (Server Side)
106
+
107
+ ```typescript
108
+ import { requestIframeServer } from 'request-iframe';
109
+
110
+ // Create server
111
+ const server = requestIframeServer({ secretKey: 'my-app' });
112
+
113
+ // Register handler (just like express)
114
+ server.on('/api/getUserInfo', (req, res) => {
115
+ const { userId } = req.body;
116
+ res.send({ name: 'Tom', age: 18 });
117
+ });
118
+ ```
119
+
120
+ That's it! 🎉
121
+
122
+ > 💡 **Tip**: For more quick start guides, see [QUICKSTART.md](./QUICKSTART.md) or [QUICKSTART.CN.md](./QUICKSTART.CN.md) (中文)
123
+
124
+ ---
125
+
126
+ ## Use Cases
127
+
128
+ ### Micro-Frontend Communication
129
+
130
+ In micro-frontend architecture, the main application needs to communicate with child application iframes:
131
+
132
+ ```typescript
133
+ // Main application (parent page)
134
+ const client = requestIframeClient(iframe, { secretKey: 'main-app' });
135
+
136
+ // Get user info from child application
137
+ const userInfo = await client.send('/api/user/info', {});
138
+
139
+ // Notify child application to refresh data
140
+ await client.send('/api/data/refresh', { timestamp: Date.now() });
141
+ ```
142
+
143
+ ### Third-Party Component Integration
144
+
145
+ When integrating third-party components, isolate via iframe while maintaining communication:
146
+
147
+ ```typescript
148
+ // Parent page
149
+ const client = requestIframeClient(thirdPartyIframe, { secretKey: 'widget' });
150
+
151
+ // Configure component
152
+ await client.send('/config', {
153
+ theme: 'dark',
154
+ language: 'en-US'
155
+ });
156
+
157
+ // Listen to component events (via reverse communication)
158
+ const server = requestIframeServer({ secretKey: 'widget-events' });
159
+ server.on('/event', (req, res) => {
160
+ console.log('Component event:', req.body);
161
+ res.send({ received: true });
162
+ });
163
+ ```
164
+
165
+ ### Cross-Origin Data Fetching
166
+
167
+ When iframe and parent page are on different origins, use request-iframe to securely fetch data:
168
+
169
+ ```typescript
170
+ // Inside iframe (different origin)
171
+ const server = requestIframeServer({ secretKey: 'data-api' });
172
+
173
+ server.on('/api/data', async (req, res) => {
174
+ // Fetch data from same-origin API (iframe can access same-origin resources)
175
+ const data = await fetch('/api/internal/data').then(r => r.json());
176
+ res.send(data);
177
+ });
178
+
179
+ // Parent page (cross-origin)
180
+ const client = requestIframeClient(iframe, { secretKey: 'data-api' });
181
+ const data = await client.send('/api/data', {}); // Successfully fetch cross-origin data
182
+ ```
183
+
184
+ ### File Preview and Download
185
+
186
+ Process files in iframe, then transfer to parent page:
187
+
188
+ ```typescript
189
+ // Inside iframe: process file and return
190
+ server.on('/api/processFile', async (req, res) => {
191
+ const { fileId } = req.body;
192
+ const processedFile = await processFile(fileId);
193
+
194
+ // Return processed file
195
+ await res.sendFile(processedFile, {
196
+ mimeType: 'application/pdf',
197
+ fileName: `processed-${fileId}.pdf`
198
+ });
199
+ });
200
+
201
+ // Parent page: download file
202
+ const response = await client.send('/api/processFile', { fileId: '123' });
203
+ if (response.fileData) {
204
+ downloadFile(response.fileData);
205
+ }
206
+ ```
207
+
208
+ ---
209
+
210
+ ## How It Works
211
+
212
+ ### Communication Protocol
213
+
214
+ request-iframe implements an HTTP-like communication protocol on top of `postMessage`:
215
+
216
+ ```
217
+ Client (Parent Page) Server (iframe)
218
+ │ │
219
+ │ ──── REQUEST ────────────────────────> │ Send request
220
+ │ │
221
+ │ <──── ACK ─────────────────────────── │ Acknowledge receipt
222
+ │ │
223
+ │ │ Execute handler
224
+ │ │
225
+ │ <──── ASYNC (optional) ─────────────── │ If handler returns Promise
226
+ │ │
227
+ │ <──── RESPONSE ────────────────────── │ Return result
228
+ │ │
229
+ │ ──── RECEIVED (optional) ────────────> │ Acknowledge receipt of response
230
+ │ │
231
+ ```
232
+
233
+ ### Message Types
234
+
235
+ | Type | Direction | Description |
236
+ |------|-----------|-------------|
237
+ | `request` | Client → Server | Client initiates request |
238
+ | `ack` | Server → Client | Server acknowledges receipt of request |
239
+ | `async` | Server → Client | Notifies client this is an async task (sent when handler returns Promise) |
240
+ | `response` | Server → Client | Returns response data |
241
+ | `error` | Server → Client | Returns error information |
242
+ | `received` | Client → Server | Client acknowledges receipt of response (optional, controlled by `requireAck`) |
243
+ | `ping` | Client → Server | Connection detection (`isConnect()` method) |
244
+ | `pong` | Server → Client | Connection detection response |
245
+
246
+ ### Timeout Mechanism
247
+
248
+ request-iframe uses a three-stage timeout strategy to intelligently adapt to different scenarios:
249
+
250
+ ```typescript
251
+ client.send('/api/getData', data, {
252
+ ackTimeout: 500, // Stage 1: ACK timeout (default 500ms)
253
+ timeout: 5000, // Stage 2: Request timeout (default 5s)
254
+ asyncTimeout: 120000 // Stage 3: Async request timeout (default 120s)
255
+ });
256
+ ```
257
+
258
+ **Timeout Flow:**
259
+
260
+ ```
261
+ Send REQUEST
262
+
263
+
264
+ ┌───────────────────┐ timeout ┌─────────────────────────────┐
265
+ │ ackTimeout │ ────────────> │ Error: ACK_TIMEOUT │
266
+ │ (wait for ACK) │ │ "Connection failed, Server │
267
+ └───────────────────┘ │ not responding" │
268
+ │ └─────────────────────────────┘
269
+ │ ACK received
270
+
271
+ ┌───────────────────┐ timeout ┌─────────────────────────────┐
272
+ │ timeout │ ────────────> │ Error: TIMEOUT │
273
+ │ (wait for RESPONSE)│ │ "Request timeout" │
274
+ └───────────────────┘ └─────────────────────────────┘
275
+
276
+ │ ASYNC received (optional)
277
+
278
+ ┌───────────────────┐ timeout ┌─────────────────────────────┐
279
+ │ asyncTimeout │ ────────────> │ Error: ASYNC_TIMEOUT │
280
+ │ (wait for RESPONSE)│ │ "Async request timeout" │
281
+ └───────────────────┘ └─────────────────────────────┘
282
+
283
+ │ RESPONSE received
284
+
285
+ Request Complete ✓
286
+ ```
287
+
288
+ **Why This Design?**
289
+
290
+ | Stage | Timeout | Scenario |
291
+ |-------|---------|----------|
292
+ | ackTimeout | Short (500ms) | Quickly detect if Server is online, avoid long waits for unreachable iframes |
293
+ | timeout | Medium (5s) | Suitable for simple synchronous processing, like reading data, parameter validation |
294
+ | asyncTimeout | Long (120s) | Suitable for complex async operations, like file processing, batch operations, third-party API calls |
295
+
296
+ ### Protocol Version
297
+
298
+ Each message contains a `__requestIframe__` field identifying the protocol version, and a `timestamp` field recording message creation time:
299
+
300
+ ```typescript
301
+ {
302
+ __requestIframe__: 1, // Protocol version number
303
+ timestamp: 1704067200000, // Message creation timestamp (milliseconds)
304
+ type: 'request',
305
+ requestId: 'req_xxx',
306
+ path: '/api/getData',
307
+ body: { ... }
308
+ }
309
+ ```
310
+
311
+ This enables:
312
+ - Different library versions can handle compatibility
313
+ - New version Server can be compatible with old version Client
314
+ - Clear error messages when version is too low
315
+ - `timestamp` facilitates debugging message delays and analyzing communication performance
316
+
317
+ ---
318
+
319
+ ## Detailed Features
320
+
321
+ ### Interceptors
322
+
323
+ #### Request Interceptors
324
+
325
+ ```typescript
326
+ // Add request interceptor (unified token addition)
327
+ client.interceptors.request.use((config) => {
328
+ config.headers = {
329
+ ...config.headers,
330
+ 'Authorization': `Bearer ${getToken()}`
331
+ };
332
+ return config;
333
+ });
334
+
335
+ // Error handling
336
+ client.interceptors.request.use(
337
+ (config) => config,
338
+ (error) => {
339
+ console.error('Request config error:', error);
340
+ return Promise.reject(error);
341
+ }
342
+ );
343
+ ```
344
+
345
+ #### Response Interceptors
346
+
347
+ ```typescript
348
+ // Add response interceptor (unified data transformation)
349
+ client.interceptors.response.use((response) => {
350
+ // Assume backend returns { code: 0, data: {...} } format
351
+ if (response.data.code === 0) {
352
+ response.data = response.data.data;
353
+ }
354
+ return response;
355
+ });
356
+
357
+ // Error handling
358
+ client.interceptors.response.use(
359
+ (response) => response,
360
+ (error) => {
361
+ if (error.code === 'TIMEOUT') {
362
+ message.error('Request timeout, please retry');
363
+ }
364
+ return Promise.reject(error);
365
+ }
366
+ );
367
+ ```
368
+
369
+ ### Middleware
370
+
371
+ Server side supports Express-style middleware:
372
+
373
+ #### Global Middleware
374
+
375
+ ```typescript
376
+ // Logging middleware
377
+ server.use((req, res, next) => {
378
+ console.log(`[${new Date().toISOString()}] ${req.path}`, req.body);
379
+ next();
380
+ });
381
+
382
+ // Authentication middleware
383
+ server.use((req, res, next) => {
384
+ const token = req.headers['authorization'];
385
+ if (!token) {
386
+ return res.status(401).send({ error: 'Unauthorized' });
387
+ }
388
+ // Verify token...
389
+ next();
390
+ });
391
+ ```
392
+
393
+ #### Path-Matching Middleware
394
+
395
+ ```typescript
396
+ // Only applies to /api/* paths
397
+ server.use('/api/*', (req, res, next) => {
398
+ console.log('API request:', req.path);
399
+ next();
400
+ });
401
+
402
+ // Regex matching
403
+ server.use(/^\/admin\//, (req, res, next) => {
404
+ // Special handling for admin interfaces
405
+ next();
406
+ });
407
+
408
+ // Array matching
409
+ server.use(['/user', '/profile'], (req, res, next) => {
410
+ // User-related interfaces
411
+ next();
412
+ });
413
+ ```
414
+
415
+ ### Headers and Cookies
416
+
417
+ > **Note**: The `headers` and `cookies` here are not real browser HTTP Headers and Cookies, but a **message metadata passing mechanism** simulated by request-iframe in HTTP style. Data is passed between iframes via `postMessage` and does not affect the browser's real Cookie storage.
418
+
419
+ **Why This Design?**
420
+
421
+ | Design Purpose | Description |
422
+ |----------------|-------------|
423
+ | **API Style Consistency** | Consistent usage with HTTP requests (axios/fetch) and server-side (Express) |
424
+ | **Lower Learning Curve** | Developers familiar with HTTP can get started quickly without learning new APIs |
425
+ | **Third-Party Library Compatibility** | Easy to reuse or adapt Express middleware, authentication libraries, etc., with minimal changes |
426
+ | **Cross-iframe State Sharing** | Implement login state passing, permission validation between different iframes, solving state synchronization issues caused by iframe isolation |
427
+ | **Flexible Data Passing** | Provides additional metadata channels beyond body, facilitating layered processing (e.g., middleware reads headers, business logic reads body) |
428
+
429
+ #### Automatic Cookie Management
430
+
431
+ request-iframe simulates HTTP's automatic cookie management mechanism:
432
+
433
+ **How It Works (Similar to HTTP Set-Cookie):**
434
+
435
+ 1. **When Server sets cookie**: Generate `Set-Cookie` string via `res.cookie(name, value, options)`
436
+ 2. **When response returns**: All `Set-Cookie` stored in `headers['Set-Cookie']` array
437
+ 3. **After Client receives response**: Parse `Set-Cookie` header, save to Cookie storage based on Path and other attributes
438
+ 4. **When Client sends request**: Only carry **path-matched** cookies (similar to browser behavior)
439
+
440
+ ```typescript
441
+ // Server side: Set token on login (supports full Cookie options)
442
+ server.on('/api/login', (req, res) => {
443
+ const { username, password } = req.body;
444
+ // Verify user...
445
+
446
+ // Set cookie (supports path, expires, maxAge, httpOnly, etc.)
447
+ res.cookie('authToken', 'jwt_xxx', { path: '/api', httpOnly: true });
448
+ res.cookie('userId', '12345', { path: '/' });
449
+ res.send({ success: true });
450
+ });
451
+
452
+ // Server side: Read token in subsequent interfaces (client automatically carries path-matched cookies)
453
+ server.on('/api/getUserInfo', (req, res) => {
454
+ const token = req.cookies['authToken']; // Path matched, automatically carried
455
+ const userId = req.cookies['userId']; // Root path cookie, carried in all requests
456
+ // Verify token...
457
+ res.send({ name: 'Tom', age: 18 });
458
+ });
459
+
460
+ // Server side: Clear cookie
461
+ server.on('/api/logout', (req, res) => {
462
+ res.clearCookie('authToken', { path: '/api' });
463
+ res.send({ success: true });
464
+ });
465
+ ```
466
+
467
+ ```typescript
468
+ // Client side: Login
469
+ await client.send('/api/login', { username: 'tom', password: '123' });
470
+
471
+ // Client side: Subsequent request to /api/getUserInfo (automatically carries authToken and userId)
472
+ const userInfo = await client.send('/api/getUserInfo', {});
473
+
474
+ // Client side: Request root path (only carries userId, because authToken's path is /api)
475
+ const rootData = await client.send('/other', {});
476
+ ```
477
+
478
+ #### Client Cookie Management API
479
+
480
+ Client provides manual cookie management APIs with **path isolation** support:
481
+
482
+ ```typescript
483
+ // Get all cookies
484
+ client.getCookies(); // { authToken: 'jwt_xxx', userId: '12345' }
485
+
486
+ // Get cookies matching specified path
487
+ client.getCookies('/api'); // Only returns cookies matching /api
488
+
489
+ // Get specified cookie
490
+ client.getCookie('authToken'); // 'jwt_xxx'
491
+ client.getCookie('authToken', '/api'); // Get with specified path
492
+
493
+ // Manually set cookie (supports path options)
494
+ client.setCookie('theme', 'dark'); // Default path '/'
495
+ client.setCookie('apiConfig', 'v2', { path: '/api' }); // Specify path
496
+ client.setCookie('temp', 'xxx', { maxAge: 3600 }); // Expires in 1 hour
497
+
498
+ // Delete specified cookie
499
+ client.removeCookie('theme'); // Delete theme with path '/'
500
+ client.removeCookie('apiConfig', '/api'); // Delete cookie with specified path
501
+
502
+ // Clear all cookies (e.g., on logout)
503
+ client.clearCookies();
504
+ ```
505
+
506
+ #### Headers Usage Example
507
+
508
+ ```typescript
509
+ // Client side: Send custom headers
510
+ const response = await client.send('/api/data', {}, {
511
+ headers: {
512
+ 'X-Device-Id': 'device-123',
513
+ 'X-Platform': 'web',
514
+ 'Authorization': 'Bearer xxx' // Can also pass token via headers
515
+ }
516
+ });
517
+
518
+ // Server side: Read and set headers
519
+ server.on('/api/data', (req, res) => {
520
+ // Read request headers
521
+ const deviceId = req.headers['x-device-id'];
522
+ const platform = req.headers['x-platform'];
523
+
524
+ // Set response headers
525
+ res.setHeader('X-Request-Id', req.requestId);
526
+ res.set('X-Custom-Header', 'value'); // Chainable
527
+
528
+ res.send({ data: 'ok' });
529
+ });
530
+ ```
531
+
532
+ ### File Transfer
533
+
534
+ ```typescript
535
+ // Server side: Send file
536
+ server.on('/api/download', async (req, res) => {
537
+ // String content
538
+ await res.sendFile('Hello, World!', {
539
+ mimeType: 'text/plain',
540
+ fileName: 'hello.txt'
541
+ });
542
+
543
+ // Or Blob/File object
544
+ const blob = new Blob(['binary data'], { type: 'application/octet-stream' });
545
+ await res.sendFile(blob, { fileName: 'data.bin' });
546
+ });
547
+
548
+ // Client side: Receive
549
+ const response = await client.send('/api/download', {});
550
+ if (response.fileData) {
551
+ const { content, mimeType, fileName } = response.fileData;
552
+
553
+ // content is base64-encoded string
554
+ const binaryString = atob(content);
555
+ const blob = new Blob([binaryString], { type: mimeType });
556
+
557
+ // Download file
558
+ const url = URL.createObjectURL(blob);
559
+ const a = document.createElement('a');
560
+ a.href = url;
561
+ a.download = fileName || 'download';
562
+ a.click();
563
+ }
564
+ ```
565
+
566
+ ### Streaming
567
+
568
+ For large files or scenarios requiring chunked transfer, you can use streaming:
569
+
570
+ ```typescript
571
+ import {
572
+ IframeWritableStream,
573
+ IframeFileWritableStream,
574
+ isIframeReadableStream,
575
+ isIframeFileStream
576
+ } from 'request-iframe';
577
+
578
+ // Server side: Send data stream using iterator
579
+ server.on('/api/stream', async (req, res) => {
580
+ const stream = new IframeWritableStream({
581
+ type: 'data',
582
+ chunked: true,
583
+ // Generate data using async iterator
584
+ iterator: async function* () {
585
+ for (let i = 0; i < 10; i++) {
586
+ yield { chunk: i, data: `Data chunk ${i}` };
587
+ await new Promise(r => setTimeout(r, 100)); // Simulate delay
588
+ }
589
+ }
590
+ });
591
+
592
+ await res.sendStream(stream);
593
+ });
594
+
595
+ // Client side: Receive stream data
596
+ const response = await client.send('/api/stream', {});
597
+
598
+ // Check if it's a stream response
599
+ if (isIframeReadableStream(response.stream)) {
600
+ // Method 1: Read all data at once
601
+ const allData = await response.stream.read();
602
+
603
+ // Method 2: Read chunk by chunk using async iterator
604
+ for await (const chunk of response.stream) {
605
+ console.log('Received chunk:', chunk);
606
+ }
607
+
608
+ // Listen to stream end
609
+ response.stream.onEnd(() => {
610
+ console.log('Stream ended');
611
+ });
612
+
613
+ // Listen to stream error
614
+ response.stream.onError((error) => {
615
+ console.error('Stream error:', error);
616
+ });
617
+
618
+ // Cancel stream
619
+ response.stream.cancel('User cancelled');
620
+ }
621
+ ```
622
+
623
+ **Stream Types:**
624
+
625
+ | Type | Description |
626
+ |------|-------------|
627
+ | `IframeWritableStream` | Server-side writable stream for sending regular data |
628
+ | `IframeFileWritableStream` | Server-side file writable stream, automatically handles base64 encoding |
629
+ | `IframeReadableStream` | Client-side readable stream for receiving regular data |
630
+ | `IframeFileReadableStream` | Client-side file readable stream, automatically handles base64 decoding |
631
+
632
+ ### Connection Detection
633
+
634
+ ```typescript
635
+ // Detect if Server is reachable
636
+ const isConnected = await client.isConnect();
637
+ if (isConnected) {
638
+ console.log('Connection OK');
639
+ } else {
640
+ console.log('Connection failed');
641
+ }
642
+ ```
643
+
644
+ ### Response Acknowledgment
645
+
646
+ Server can require Client to acknowledge receipt of response:
647
+
648
+ ```typescript
649
+ server.on('/api/important', async (req, res) => {
650
+ // requireAck: true means client needs to acknowledge
651
+ const received = await res.send(data, { requireAck: true });
652
+
653
+ if (received) {
654
+ console.log('Client acknowledged receipt');
655
+ } else {
656
+ console.log('Client did not acknowledge (timeout)');
657
+ }
658
+ });
659
+ ```
660
+
661
+ ### Trace Mode
662
+
663
+ Enable trace mode to view detailed communication logs in console:
664
+
665
+ ```typescript
666
+ const client = requestIframeClient(iframe, {
667
+ secretKey: 'demo',
668
+ trace: true
669
+ });
670
+
671
+ const server = requestIframeServer({
672
+ secretKey: 'demo',
673
+ trace: true
674
+ });
675
+
676
+ // Console output:
677
+ // [request-iframe] [INFO] 📤 Request Start { path: '/api/getData', ... }
678
+ // [request-iframe] [INFO] 📨 ACK Received { requestId: '...' }
679
+ // [request-iframe] [INFO] ✅ Request Success { status: 200, data: {...} }
680
+ ```
681
+
682
+ ### Internationalization
683
+
684
+ ```typescript
685
+ import { setMessages } from 'request-iframe';
686
+
687
+ // Switch to Chinese
688
+ setMessages({
689
+ ACK_TIMEOUT: 'ACK acknowledgment timeout, waited {0} milliseconds',
690
+ REQUEST_TIMEOUT: 'Request timeout, waited {0} milliseconds',
691
+ REQUEST_FAILED: 'Request failed',
692
+ METHOD_NOT_FOUND: 'Method not found',
693
+ MIDDLEWARE_ERROR: 'Middleware error',
694
+ IFRAME_NOT_READY: 'iframe not ready'
695
+ });
696
+ ```
697
+
698
+ ---
699
+
700
+ ## API Reference
701
+
702
+ ### requestIframeClient(target, options?)
703
+
704
+ Create a Client instance.
705
+
706
+ **Parameters:**
707
+
708
+ | Parameter | Type | Description |
709
+ |-----------|------|-------------|
710
+ | `target` | `HTMLIFrameElement \| Window` | Target iframe element or window object |
711
+ | `options.secretKey` | `string` | Message isolation identifier (optional) |
712
+ | `options.trace` | `boolean` | Whether to enable trace mode (optional) |
713
+ | `options.ackTimeout` | `number` | Global default ACK acknowledgment timeout (ms), default 500 |
714
+ | `options.timeout` | `number` | Global default request timeout (ms), default 5000 |
715
+ | `options.asyncTimeout` | `number` | Global default async timeout (ms), default 120000 |
716
+
717
+ **Returns:** `RequestIframeClient`
718
+
719
+ ### requestIframeServer(options?)
720
+
721
+ Create a Server instance.
722
+
723
+ **Parameters:**
724
+
725
+ | Parameter | Type | Description |
726
+ |-----------|------|-------------|
727
+ | `options.secretKey` | `string` | Message isolation identifier (optional) |
728
+ | `options.trace` | `boolean` | Whether to enable trace mode (optional) |
729
+ | `options.ackTimeout` | `number` | Wait for client acknowledgment timeout (ms), default 5000 |
730
+
731
+ **Returns:** `RequestIframeServer`
732
+
733
+ ### Client API
734
+
735
+ #### client.send(path, body?, options?)
736
+
737
+ Send a request.
738
+
739
+ **Parameters:**
740
+
741
+ | Parameter | Type | Description |
742
+ |-----------|------|-------------|
743
+ | `path` | `string` | Request path |
744
+ | `body` | `object` | Request data (optional) |
745
+ | `options.ackTimeout` | `number` | ACK acknowledgment timeout (ms), default 500 |
746
+ | `options.timeout` | `number` | Request timeout (ms), default 5000 |
747
+ | `options.asyncTimeout` | `number` | Async timeout (ms), default 120000 |
748
+ | `options.headers` | `object` | Request headers (optional) |
749
+ | `options.cookies` | `object` | Request cookies (optional, merged with internally stored cookies, passed-in takes priority) |
750
+ | `options.requestId` | `string` | Custom request ID (optional) |
751
+
752
+ **Returns:** `Promise<Response>`
753
+
754
+ ```typescript
755
+ interface Response<T = any> {
756
+ data: T; // Response data
757
+ status: number; // Status code
758
+ statusText: string; // Status text
759
+ requestId: string; // Request ID
760
+ headers?: Record<string, string | string[]>; // Response headers (Set-Cookie is array)
761
+ fileData?: { // File data (if any)
762
+ content: string; // base64-encoded content
763
+ mimeType?: string;
764
+ fileName?: string;
765
+ };
766
+ stream?: IIframeReadableStream<T>; // Stream response (if any)
767
+ }
768
+ ```
769
+
770
+ #### client.isConnect()
771
+
772
+ Detect if Server is reachable.
773
+
774
+ **Returns:** `Promise<boolean>`
775
+
776
+ #### client.interceptors
777
+
778
+ Interceptor manager.
779
+
780
+ ```typescript
781
+ // Request interceptor
782
+ client.interceptors.request.use(onFulfilled, onRejected?);
783
+
784
+ // Response interceptor
785
+ client.interceptors.response.use(onFulfilled, onRejected?);
786
+ ```
787
+
788
+ ### Server API
789
+
790
+ #### server.on(path, handler)
791
+
792
+ Register route handler.
793
+
794
+ **Parameters:**
795
+
796
+ | Parameter | Type | Description |
797
+ |-----------|------|-------------|
798
+ | `path` | `string` | Request path |
799
+ | `handler` | `ServerHandler` | Handler function |
800
+
801
+ ```typescript
802
+ type ServerHandler = (req: ServerRequest, res: ServerResponse) => any | Promise<any>;
803
+ ```
804
+
805
+ #### server.off(path)
806
+
807
+ Remove route handler.
808
+
809
+ #### server.map(handlers)
810
+
811
+ Batch register handlers.
812
+
813
+ ```typescript
814
+ server.map({
815
+ '/api/users': (req, res) => res.send([...]),
816
+ '/api/posts': (req, res) => res.send([...])
817
+ });
818
+ ```
819
+
820
+ #### server.use(middleware)
821
+ #### server.use(path, middleware)
822
+
823
+ Register middleware.
824
+
825
+ ```typescript
826
+ // Global middleware
827
+ server.use((req, res, next) => { ... });
828
+
829
+ // Path-matching middleware
830
+ server.use('/api/*', (req, res, next) => { ... });
831
+ server.use(/^\/admin/, (req, res, next) => { ... });
832
+ server.use(['/a', '/b'], (req, res, next) => { ... });
833
+ ```
834
+
835
+ #### server.destroy()
836
+
837
+ Destroy Server instance, remove all listeners.
838
+
839
+ ---
840
+
841
+ ## Error Handling
842
+
843
+ ### Error Codes
844
+
845
+ | Error Code | Description |
846
+ |------------|-------------|
847
+ | `ACK_TIMEOUT` | ACK acknowledgment timeout (did not receive ACK) |
848
+ | `TIMEOUT` | Synchronous request timeout |
849
+ | `ASYNC_TIMEOUT` | Async request timeout |
850
+ | `REQUEST_ERROR` | Request processing error |
851
+ | `METHOD_NOT_FOUND` | Handler not found |
852
+ | `NO_RESPONSE` | Handler did not send response |
853
+ | `PROTOCOL_UNSUPPORTED` | Protocol version not supported |
854
+ | `IFRAME_NOT_READY` | iframe not ready |
855
+ | `STREAM_ERROR` | Stream transfer error |
856
+ | `STREAM_CANCELLED` | Stream cancelled |
857
+ | `STREAM_NOT_BOUND` | Stream not bound to request context |
858
+
859
+ ### Error Handling Example
860
+
861
+ ```typescript
862
+ try {
863
+ const response = await client.send('/api/getData', { id: 1 });
864
+ } catch (error) {
865
+ switch (error.code) {
866
+ case 'ACK_TIMEOUT':
867
+ console.error('Cannot connect to iframe');
868
+ break;
869
+ case 'TIMEOUT':
870
+ console.error('Request timeout');
871
+ break;
872
+ case 'METHOD_NOT_FOUND':
873
+ console.error('Interface does not exist');
874
+ break;
875
+ default:
876
+ console.error('Request failed:', error.message);
877
+ }
878
+ }
879
+ ```
880
+
881
+ ---
882
+
883
+ ## FAQ
884
+
885
+ ### 1. What is secretKey used for?
886
+
887
+ `secretKey` is used for message isolation. When there are multiple iframes or multiple request-iframe instances on a page, using different `secretKey` values can prevent message cross-talk:
888
+
889
+ ```typescript
890
+ // Communication for iframe A
891
+ const clientA = requestIframeClient(iframeA, { secretKey: 'app-a' });
892
+ const serverA = requestIframeServer({ secretKey: 'app-a' });
893
+
894
+ // Communication for iframe B
895
+ const clientB = requestIframeClient(iframeB, { secretKey: 'app-b' });
896
+ const serverB = requestIframeServer({ secretKey: 'app-b' });
897
+ ```
898
+
899
+ ### 2. Why is ACK acknowledgment needed?
900
+
901
+ ACK mechanism is similar to TCP handshake, used for:
902
+ 1. Quickly confirm if Server is online
903
+ 2. Distinguish between "connection failure" and "request timeout"
904
+ 3. Support timeout switching for async tasks
905
+
906
+ ### 3. How to handle iframe cross-origin?
907
+
908
+ `postMessage` itself supports cross-origin communication, request-iframe handles it automatically:
909
+
910
+ ```typescript
911
+ // Parent page (https://parent.com)
912
+ const client = requestIframeClient(iframe);
913
+
914
+ // Inside iframe (https://child.com)
915
+ const server = requestIframeServer();
916
+ ```
917
+
918
+ Just ensure both sides use the same `secretKey`.
919
+
920
+ ### 4. Can Server actively push messages?
921
+
922
+ request-iframe is request-response mode, Server cannot actively push. For bidirectional communication, you can create a Client inside the iframe:
923
+
924
+ ```typescript
925
+ // Inside iframe
926
+ const server = requestIframeServer({ secretKey: 'my-app' });
927
+ const client = requestIframeClient(window.parent, { secretKey: 'my-app-reverse' });
928
+
929
+ // Actively send message to parent page
930
+ await client.send('/notify', { event: 'data-changed' });
931
+ ```
932
+
933
+ ### 5. How to debug communication issues?
934
+
935
+ 1. **Enable trace mode**: View detailed communication logs
936
+ 2. **Check secretKey**: Ensure Client and Server use the same secretKey
937
+ 3. **Check iframe loading**: Ensure iframe is fully loaded
938
+ 4. **Check console**: Check for cross-origin errors
939
+
940
+ ---
941
+
942
+ ## Development
943
+
944
+ ### Requirements
945
+
946
+ - Node.js >= 14
947
+ - npm >= 6 or yarn >= 1.22
948
+
949
+ ### Development Commands
950
+
951
+ ```bash
952
+ # Install dependencies
953
+ npm install
954
+ # or
955
+ yarn install
956
+
957
+ # Run tests
958
+ npm test
959
+ # or
960
+ yarn test
961
+
962
+ # Run tests (watch mode)
963
+ npm run test:watch
964
+ # or
965
+ yarn test:watch
966
+
967
+ # Generate test coverage report
968
+ npm run test:coverage
969
+ # or
970
+ yarn test:coverage
971
+
972
+ # Code linting
973
+ npm run lint
974
+ # or
975
+ yarn lint
976
+
977
+ # Auto-fix code issues
978
+ npm run lint:fix
979
+ # or
980
+ yarn lint:fix
981
+
982
+ # Build project
983
+ npm run build
984
+ # or
985
+ yarn build
986
+ ```
987
+
988
+ ### Test Coverage
989
+
990
+ The project currently has **76.88%** test coverage, meeting production requirements:
991
+
992
+ - **Statement Coverage**: 76.88%
993
+ - **Branch Coverage**: 64.13%
994
+ - **Function Coverage**: 75%
995
+ - **Line Coverage**: 78.71%
996
+
997
+ Coverage reports are generated in the `coverage/` directory, view detailed coverage report via `coverage/index.html`.
998
+
999
+ ### Browser Compatibility
1000
+
1001
+ | Browser | Minimum Version | Notes |
1002
+ |---------|----------------|-------|
1003
+ | Chrome | 49+ | Full support |
1004
+ | Firefox | 45+ | Full support |
1005
+ | Safari | 10+ | Full support |
1006
+ | Edge | 12+ | Full support |
1007
+ | IE | Not supported | May support IE 11 with Babel transpilation, but not tested |
1008
+
1009
+ ### Related Projects
1010
+
1011
+ - [axios](https://github.com/axios/axios) - HTTP client library that inspired this project
1012
+ - [Express](https://expressjs.com/) - Server API design reference
1013
+
1014
+ ## License
1015
+
1016
+ MIT License