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.
- package/QUICKSTART.CN.md +269 -0
- package/QUICKSTART.md +269 -0
- package/README.CN.md +1369 -0
- package/README.md +1016 -0
- package/library/__tests__/interceptors.test.ts +124 -0
- package/library/__tests__/requestIframe.test.ts +2216 -0
- package/library/__tests__/stream.test.ts +650 -0
- package/library/__tests__/utils.test.ts +433 -0
- package/library/api/client.d.ts +16 -0
- package/library/api/client.d.ts.map +1 -0
- package/library/api/client.js +72 -0
- package/library/api/server.d.ts +16 -0
- package/library/api/server.d.ts.map +1 -0
- package/library/api/server.js +44 -0
- package/library/constants/index.d.ts +209 -0
- package/library/constants/index.d.ts.map +1 -0
- package/library/constants/index.js +260 -0
- package/library/constants/messages.d.ts +80 -0
- package/library/constants/messages.d.ts.map +1 -0
- package/library/constants/messages.js +123 -0
- package/library/core/client.d.ts +99 -0
- package/library/core/client.d.ts.map +1 -0
- package/library/core/client.js +440 -0
- package/library/core/message-handler.d.ts +110 -0
- package/library/core/message-handler.d.ts.map +1 -0
- package/library/core/message-handler.js +320 -0
- package/library/core/request-response.d.ts +59 -0
- package/library/core/request-response.d.ts.map +1 -0
- package/library/core/request-response.js +337 -0
- package/library/core/request.d.ts +17 -0
- package/library/core/request.d.ts.map +1 -0
- package/library/core/request.js +34 -0
- package/library/core/response.d.ts +51 -0
- package/library/core/response.d.ts.map +1 -0
- package/library/core/response.js +323 -0
- package/library/core/server-base.d.ts +86 -0
- package/library/core/server-base.d.ts.map +1 -0
- package/library/core/server-base.js +257 -0
- package/library/core/server-client.d.ts +99 -0
- package/library/core/server-client.d.ts.map +1 -0
- package/library/core/server-client.js +256 -0
- package/library/core/server.d.ts +82 -0
- package/library/core/server.d.ts.map +1 -0
- package/library/core/server.js +338 -0
- package/library/index.d.ts +16 -0
- package/library/index.d.ts.map +1 -0
- package/library/index.js +211 -0
- package/library/interceptors/index.d.ts +41 -0
- package/library/interceptors/index.d.ts.map +1 -0
- package/library/interceptors/index.js +126 -0
- package/library/message/channel.d.ts +107 -0
- package/library/message/channel.d.ts.map +1 -0
- package/library/message/channel.js +184 -0
- package/library/message/dispatcher.d.ts +119 -0
- package/library/message/dispatcher.d.ts.map +1 -0
- package/library/message/dispatcher.js +249 -0
- package/library/message/index.d.ts +5 -0
- package/library/message/index.d.ts.map +1 -0
- package/library/message/index.js +25 -0
- package/library/stream/file-stream.d.ts +48 -0
- package/library/stream/file-stream.d.ts.map +1 -0
- package/library/stream/file-stream.js +240 -0
- package/library/stream/index.d.ts +15 -0
- package/library/stream/index.d.ts.map +1 -0
- package/library/stream/index.js +83 -0
- package/library/stream/readable-stream.d.ts +83 -0
- package/library/stream/readable-stream.d.ts.map +1 -0
- package/library/stream/readable-stream.js +249 -0
- package/library/stream/types.d.ts +165 -0
- package/library/stream/types.d.ts.map +1 -0
- package/library/stream/types.js +5 -0
- package/library/stream/writable-stream.d.ts +60 -0
- package/library/stream/writable-stream.d.ts.map +1 -0
- package/library/stream/writable-stream.js +348 -0
- package/library/types/index.d.ts +408 -0
- package/library/types/index.d.ts.map +1 -0
- package/library/types/index.js +5 -0
- package/library/utils/cache.d.ts +19 -0
- package/library/utils/cache.d.ts.map +1 -0
- package/library/utils/cache.js +83 -0
- package/library/utils/cookie.d.ts +117 -0
- package/library/utils/cookie.d.ts.map +1 -0
- package/library/utils/cookie.js +365 -0
- package/library/utils/debug.d.ts +11 -0
- package/library/utils/debug.d.ts.map +1 -0
- package/library/utils/debug.js +162 -0
- package/library/utils/index.d.ts +13 -0
- package/library/utils/index.d.ts.map +1 -0
- package/library/utils/index.js +132 -0
- package/library/utils/path-match.d.ts +17 -0
- package/library/utils/path-match.d.ts.map +1 -0
- package/library/utils/path-match.js +90 -0
- package/library/utils/protocol.d.ts +61 -0
- package/library/utils/protocol.d.ts.map +1 -0
- package/library/utils/protocol.js +169 -0
- 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
|