@duyquangnvx/iframe-rpc 0.1.0
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/LICENSE +21 -0
- package/README.md +180 -0
- package/dist/index.cjs +271 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +177 -0
- package/dist/index.d.ts +177 -0
- package/dist/index.js +264 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# @duyquangnvx/iframe-rpc
|
|
2
|
+
|
|
3
|
+
Type-safe bidirectional RPC communication between parent window and iframe.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-safe RPC**: Full TypeScript inference for method parameters and return types
|
|
8
|
+
- **Bidirectional**: Both parent and iframe can call methods on each other
|
|
9
|
+
- **Fire-and-forget**: Support for one-way notifications without waiting for response
|
|
10
|
+
- **Timeout handling**: Configurable timeouts with automatic cleanup
|
|
11
|
+
- **Channel isolation**: Multiple independent bridges on the same page
|
|
12
|
+
- **Zero dependencies**: Lightweight with no runtime dependencies
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @duyquangnvx/iframe-rpc
|
|
18
|
+
# or
|
|
19
|
+
pnpm add @duyquangnvx/iframe-rpc
|
|
20
|
+
# or
|
|
21
|
+
yarn add @duyquangnvx/iframe-rpc
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### 1. Define your API contracts
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// Shared types (e.g., shared/types.ts)
|
|
30
|
+
type ParentMethods = {
|
|
31
|
+
getUser: (id: string) => Promise<{ name: string; age: number }>;
|
|
32
|
+
notify: (message: string) => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type IframeMethods = {
|
|
36
|
+
initialize: (config: { theme: string }) => Promise<void>;
|
|
37
|
+
getStatus: () => Promise<'ready' | 'loading'>;
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Set up the parent window
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { createParentBridge } from '@duyquangnvx/iframe-rpc';
|
|
45
|
+
|
|
46
|
+
const iframe = document.getElementById('my-iframe') as HTMLIFrameElement;
|
|
47
|
+
|
|
48
|
+
const bridge = createParentBridge<ParentMethods, IframeMethods>(iframe, {
|
|
49
|
+
getUser: async (id) => ({ name: 'John', age: 30 }),
|
|
50
|
+
notify: (message) => console.log('Notification:', message),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Call iframe methods with full type safety
|
|
54
|
+
const status = await bridge.call.getStatus();
|
|
55
|
+
await bridge.call.initialize({ theme: 'dark' });
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Set up the iframe
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { createIframeBridge } from '@duyquangnvx/iframe-rpc';
|
|
62
|
+
|
|
63
|
+
const bridge = createIframeBridge<IframeMethods, ParentMethods>({
|
|
64
|
+
initialize: async (config) => {
|
|
65
|
+
document.body.className = config.theme;
|
|
66
|
+
},
|
|
67
|
+
getStatus: async () => 'ready',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Call parent methods with full type safety
|
|
71
|
+
const user = await bridge.call.getUser('123');
|
|
72
|
+
bridge.notify('logEvent', 'iframe-loaded'); // Fire-and-forget
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## API Reference
|
|
76
|
+
|
|
77
|
+
### `createParentBridge<TLocal, TRemote>(iframe, handlers, options?)`
|
|
78
|
+
|
|
79
|
+
Creates a bridge in the parent window to communicate with an iframe.
|
|
80
|
+
|
|
81
|
+
**Parameters:**
|
|
82
|
+
- `iframe`: `HTMLIFrameElement` - The iframe to communicate with
|
|
83
|
+
- `handlers`: `TLocal` - Object containing methods the iframe can call
|
|
84
|
+
- `options?`: `BridgeOptions` - Configuration options
|
|
85
|
+
|
|
86
|
+
**Returns:** `Bridge<TLocal, TRemote>`
|
|
87
|
+
|
|
88
|
+
### `createIframeBridge<TLocal, TRemote>(handlers, options?)`
|
|
89
|
+
|
|
90
|
+
Creates a bridge in the iframe to communicate with the parent window.
|
|
91
|
+
|
|
92
|
+
**Parameters:**
|
|
93
|
+
- `handlers`: `TLocal` - Object containing methods the parent can call
|
|
94
|
+
- `options?`: `BridgeOptions` - Configuration options
|
|
95
|
+
|
|
96
|
+
**Returns:** `Bridge<TLocal, TRemote>`
|
|
97
|
+
|
|
98
|
+
### `BridgeOptions`
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
interface BridgeOptions {
|
|
102
|
+
timeout?: number; // RPC timeout in ms (default: 30000)
|
|
103
|
+
targetOrigin?: string; // postMessage target origin (default: '*')
|
|
104
|
+
channel?: string; // Channel name for isolation (default: 'default')
|
|
105
|
+
debug?: boolean; // Enable debug logging (default: false)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `Bridge<TLocal, TRemote>`
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
interface Bridge<TLocal, TRemote> {
|
|
113
|
+
call: CallProxy<TRemote>; // Type-safe proxy for calling remote methods
|
|
114
|
+
invoke: (method, ...args) => Promise; // Call by method name (for dynamic calls)
|
|
115
|
+
notify: (method, ...args) => void; // Fire-and-forget calls
|
|
116
|
+
destroy: () => void; // Clean up and stop listening
|
|
117
|
+
isActive: () => boolean; // Check if bridge is active
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Calling Methods
|
|
122
|
+
|
|
123
|
+
Two ways to call remote methods:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// 1. Proxy API (recommended) - best IDE support
|
|
127
|
+
const user = await bridge.call.getUser('123');
|
|
128
|
+
|
|
129
|
+
// 2. Invoke API - for dynamic method names
|
|
130
|
+
const methodName = 'getUser';
|
|
131
|
+
const user = await bridge.invoke(methodName, '123');
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Both are fully type-safe. Use `call` for static calls, `invoke` when method name is dynamic.
|
|
135
|
+
|
|
136
|
+
## Error Handling
|
|
137
|
+
|
|
138
|
+
The library provides typed error classes:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { RpcError, RpcTimeoutError, RpcMethodNotFoundError } from '@duyquangnvx/iframe-rpc';
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
await bridge.call.someMethod();
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (error instanceof RpcTimeoutError) {
|
|
147
|
+
console.log('Call timed out');
|
|
148
|
+
} else if (error instanceof RpcMethodNotFoundError) {
|
|
149
|
+
console.log('Method not found on remote side');
|
|
150
|
+
} else if (error instanceof RpcError) {
|
|
151
|
+
console.log('RPC error:', error.message, error.code);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Channel Isolation
|
|
157
|
+
|
|
158
|
+
Run multiple independent bridges on the same page:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// Widget A
|
|
162
|
+
const bridgeA = createParentBridge(iframeA, handlersA, { channel: 'widget-a' });
|
|
163
|
+
|
|
164
|
+
// Widget B (won't interfere with Widget A)
|
|
165
|
+
const bridgeB = createParentBridge(iframeB, handlersB, { channel: 'widget-b' });
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Security Considerations
|
|
169
|
+
|
|
170
|
+
By default, `targetOrigin` is set to `'*'` which allows communication with any origin. For production, you should specify the exact origin:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const bridge = createParentBridge(iframe, handlers, {
|
|
174
|
+
targetOrigin: 'https://trusted-domain.com',
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
var MESSAGE_TYPE = {
|
|
5
|
+
REQUEST: "iframe-rpc:request",
|
|
6
|
+
RESPONSE: "iframe-rpc:response",
|
|
7
|
+
ERROR: "iframe-rpc:error",
|
|
8
|
+
FIRE_AND_FORGET: "iframe-rpc:fire-and-forget"
|
|
9
|
+
};
|
|
10
|
+
var RpcError = class extends Error {
|
|
11
|
+
constructor(message, code, originalStack) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.originalStack = originalStack;
|
|
15
|
+
this.name = "RpcError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var RpcTimeoutError = class extends RpcError {
|
|
19
|
+
constructor(method, timeout) {
|
|
20
|
+
super(`RPC call to "${method}" timed out after ${timeout}ms`, "TIMEOUT");
|
|
21
|
+
this.name = "RpcTimeoutError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var RpcMethodNotFoundError = class extends RpcError {
|
|
25
|
+
constructor(method) {
|
|
26
|
+
super(`Method "${method}" not found`, "METHOD_NOT_FOUND");
|
|
27
|
+
this.name = "RpcMethodNotFoundError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var DEFAULT_OPTIONS = {
|
|
31
|
+
timeout: 3e4,
|
|
32
|
+
targetOrigin: "*",
|
|
33
|
+
channel: "default",
|
|
34
|
+
debug: false,
|
|
35
|
+
includeStackTraces: false
|
|
36
|
+
};
|
|
37
|
+
function generateId() {
|
|
38
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
39
|
+
return crypto.randomUUID();
|
|
40
|
+
}
|
|
41
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
42
|
+
const array = new Uint32Array(2);
|
|
43
|
+
crypto.getRandomValues(array);
|
|
44
|
+
return `${array[0].toString(16)}-${array[1].toString(16)}-${Date.now().toString(36)}`;
|
|
45
|
+
}
|
|
46
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
47
|
+
}
|
|
48
|
+
var VALID_MESSAGE_TYPES = new Set(Object.values(MESSAGE_TYPE));
|
|
49
|
+
function isRpcMessage(data) {
|
|
50
|
+
return typeof data === "object" && data !== null && "__iframeRpc" in data && data.__iframeRpc === true && "type" in data && VALID_MESSAGE_TYPES.has(data.type);
|
|
51
|
+
}
|
|
52
|
+
function warnIfInsecureOrigin(options) {
|
|
53
|
+
if (options.debug && options.targetOrigin === "*") {
|
|
54
|
+
console.warn('[iframe-rpc] Using targetOrigin:"*" is insecure for production. Consider specifying an exact origin.');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function createLogger(debug, prefix) {
|
|
58
|
+
return {
|
|
59
|
+
log: (...args) => {
|
|
60
|
+
if (debug) console.log(`[${prefix}]`, ...args);
|
|
61
|
+
},
|
|
62
|
+
error: (...args) => {
|
|
63
|
+
if (debug) console.error(`[${prefix}]`, ...args);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function createBridge(target, handlers, options, side) {
|
|
68
|
+
const pendingRequests = /* @__PURE__ */ new Map();
|
|
69
|
+
let isDestroyed = false;
|
|
70
|
+
const logger = createLogger(options.debug, `iframe-rpc:${side}`);
|
|
71
|
+
const handleMessage = (event) => {
|
|
72
|
+
if (isDestroyed) return;
|
|
73
|
+
if (options.targetOrigin !== "*" && event.origin !== options.targetOrigin) {
|
|
74
|
+
logger.log("Rejected message from untrusted origin:", event.origin);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const data = event.data;
|
|
78
|
+
if (!isRpcMessage(data)) return;
|
|
79
|
+
if (data.channel !== options.channel) return;
|
|
80
|
+
logger.log("Received message:", data);
|
|
81
|
+
switch (data.type) {
|
|
82
|
+
case MESSAGE_TYPE.REQUEST:
|
|
83
|
+
if (!event.source) {
|
|
84
|
+
logger.error("Request received with null source");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
handleRequest(data, event.source);
|
|
88
|
+
break;
|
|
89
|
+
case MESSAGE_TYPE.RESPONSE:
|
|
90
|
+
handleResponse(data);
|
|
91
|
+
break;
|
|
92
|
+
case MESSAGE_TYPE.ERROR:
|
|
93
|
+
handleError(data);
|
|
94
|
+
break;
|
|
95
|
+
case MESSAGE_TYPE.FIRE_AND_FORGET:
|
|
96
|
+
handleFireAndForget(data);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const handleRequest = async (message, source) => {
|
|
101
|
+
const { id, method, args } = message;
|
|
102
|
+
const handler = handlers[method];
|
|
103
|
+
if (!handler) {
|
|
104
|
+
sendError(source, id, new RpcMethodNotFoundError(method));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const result = await handler(...args);
|
|
109
|
+
sendResponse(source, id, result);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
sendError(source, id, error instanceof Error ? error : new Error(String(error)));
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const handleResponse = (message) => {
|
|
115
|
+
const pending = pendingRequests.get(message.id);
|
|
116
|
+
if (!pending) {
|
|
117
|
+
logger.error("No pending request for response:", message.id);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
clearTimeout(pending.timeoutId);
|
|
121
|
+
pendingRequests.delete(message.id);
|
|
122
|
+
pending.resolve(message.result);
|
|
123
|
+
};
|
|
124
|
+
const handleError = (message) => {
|
|
125
|
+
const pending = pendingRequests.get(message.id);
|
|
126
|
+
if (!pending) {
|
|
127
|
+
logger.error("No pending request for error:", message.id);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
clearTimeout(pending.timeoutId);
|
|
131
|
+
pendingRequests.delete(message.id);
|
|
132
|
+
pending.reject(
|
|
133
|
+
new RpcError(message.error.message, message.error.code, message.error.stack)
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
const handleFireAndForget = (message) => {
|
|
137
|
+
const handler = handlers[message.method];
|
|
138
|
+
if (!handler) {
|
|
139
|
+
logger.error("Handler not found for fire-and-forget:", message.method);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
Promise.resolve().then(() => handler(...message.args)).catch((error) => logger.error("Error in fire-and-forget handler:", error));
|
|
143
|
+
};
|
|
144
|
+
const sendMessage = (targetWindow, message) => {
|
|
145
|
+
logger.log("Sending message:", message);
|
|
146
|
+
targetWindow.postMessage(message, options.targetOrigin);
|
|
147
|
+
};
|
|
148
|
+
const sendResponse = (targetWindow, id, result) => {
|
|
149
|
+
const message = {
|
|
150
|
+
__iframeRpc: true,
|
|
151
|
+
type: MESSAGE_TYPE.RESPONSE,
|
|
152
|
+
channel: options.channel,
|
|
153
|
+
id,
|
|
154
|
+
result
|
|
155
|
+
};
|
|
156
|
+
sendMessage(targetWindow, message);
|
|
157
|
+
};
|
|
158
|
+
const sendError = (targetWindow, id, error) => {
|
|
159
|
+
const message = {
|
|
160
|
+
__iframeRpc: true,
|
|
161
|
+
type: MESSAGE_TYPE.ERROR,
|
|
162
|
+
channel: options.channel,
|
|
163
|
+
id,
|
|
164
|
+
error: {
|
|
165
|
+
message: error.message,
|
|
166
|
+
...error instanceof RpcError && error.code ? { code: error.code } : {},
|
|
167
|
+
// Only include stack traces if explicitly enabled (security consideration)
|
|
168
|
+
...options.includeStackTraces && error.stack ? { stack: error.stack } : {}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
sendMessage(targetWindow, message);
|
|
172
|
+
};
|
|
173
|
+
const callMethod = (method, args) => {
|
|
174
|
+
if (isDestroyed) {
|
|
175
|
+
return Promise.reject(new RpcError("Bridge has been destroyed", "DESTROYED"));
|
|
176
|
+
}
|
|
177
|
+
return new Promise((resolve, reject) => {
|
|
178
|
+
const id = generateId();
|
|
179
|
+
const timeoutId = setTimeout(() => {
|
|
180
|
+
pendingRequests.delete(id);
|
|
181
|
+
reject(new RpcTimeoutError(String(method), options.timeout));
|
|
182
|
+
}, options.timeout);
|
|
183
|
+
pendingRequests.set(id, {
|
|
184
|
+
resolve,
|
|
185
|
+
reject,
|
|
186
|
+
timeoutId
|
|
187
|
+
});
|
|
188
|
+
const message = {
|
|
189
|
+
__iframeRpc: true,
|
|
190
|
+
type: MESSAGE_TYPE.REQUEST,
|
|
191
|
+
channel: options.channel,
|
|
192
|
+
id,
|
|
193
|
+
method,
|
|
194
|
+
args
|
|
195
|
+
};
|
|
196
|
+
sendMessage(target, message);
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
const notify = (method, ...args) => {
|
|
200
|
+
if (isDestroyed) {
|
|
201
|
+
logger.error("Cannot notify: bridge has been destroyed");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const message = {
|
|
205
|
+
__iframeRpc: true,
|
|
206
|
+
type: MESSAGE_TYPE.FIRE_AND_FORGET,
|
|
207
|
+
channel: options.channel,
|
|
208
|
+
method,
|
|
209
|
+
args
|
|
210
|
+
};
|
|
211
|
+
sendMessage(target, message);
|
|
212
|
+
};
|
|
213
|
+
const call = new Proxy({}, {
|
|
214
|
+
get(_, prop) {
|
|
215
|
+
if (typeof prop === "symbol") return void 0;
|
|
216
|
+
return (...args) => callMethod(prop, args);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
window.addEventListener("message", handleMessage);
|
|
220
|
+
return {
|
|
221
|
+
call,
|
|
222
|
+
invoke: (method, ...args) => callMethod(method, args),
|
|
223
|
+
notify,
|
|
224
|
+
destroy: () => {
|
|
225
|
+
isDestroyed = true;
|
|
226
|
+
window.removeEventListener("message", handleMessage);
|
|
227
|
+
for (const [, pending] of pendingRequests) {
|
|
228
|
+
clearTimeout(pending.timeoutId);
|
|
229
|
+
pending.reject(new RpcError("Bridge destroyed", "DESTROYED"));
|
|
230
|
+
}
|
|
231
|
+
pendingRequests.clear();
|
|
232
|
+
logger.log("Bridge destroyed");
|
|
233
|
+
},
|
|
234
|
+
isActive: () => !isDestroyed
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function createParentBridge(iframe, handlers, options = {}) {
|
|
238
|
+
if (!iframe.contentWindow) {
|
|
239
|
+
throw new Error("Iframe contentWindow is not available. Make sure the iframe is loaded.");
|
|
240
|
+
}
|
|
241
|
+
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
242
|
+
warnIfInsecureOrigin(mergedOptions);
|
|
243
|
+
return createBridge(
|
|
244
|
+
iframe.contentWindow,
|
|
245
|
+
handlers,
|
|
246
|
+
mergedOptions,
|
|
247
|
+
"parent"
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
function createIframeBridge(handlers, options = {}) {
|
|
251
|
+
if (!window.parent || window.parent === window) {
|
|
252
|
+
throw new Error("Not running inside an iframe");
|
|
253
|
+
}
|
|
254
|
+
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
255
|
+
warnIfInsecureOrigin(mergedOptions);
|
|
256
|
+
return createBridge(
|
|
257
|
+
window.parent,
|
|
258
|
+
handlers,
|
|
259
|
+
mergedOptions,
|
|
260
|
+
"iframe"
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
exports.MESSAGE_TYPE = MESSAGE_TYPE;
|
|
265
|
+
exports.RpcError = RpcError;
|
|
266
|
+
exports.RpcMethodNotFoundError = RpcMethodNotFoundError;
|
|
267
|
+
exports.RpcTimeoutError = RpcTimeoutError;
|
|
268
|
+
exports.createIframeBridge = createIframeBridge;
|
|
269
|
+
exports.createParentBridge = createParentBridge;
|
|
270
|
+
//# sourceMappingURL=index.cjs.map
|
|
271
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\r\n * Type-safe bidirectional RPC communication between parent window and iframe\r\n * \r\n * @example\r\n * // Define your API contract\r\n * type ParentMethods = {\r\n * getUser: (id: string) => Promise<{ name: string; age: number }>;\r\n * notify: (message: string) => void;\r\n * };\r\n * \r\n * type IframeMethods = {\r\n * initialize: (config: { theme: string }) => Promise<void>;\r\n * getStatus: () => Promise<'ready' | 'loading'>;\r\n * };\r\n * \r\n * // In parent\r\n * const bridge = createParentBridge<ParentMethods, IframeMethods>(iframe, {\r\n * getUser: async (id) => ({ name: 'John', age: 30 }),\r\n * notify: (message) => console.log(message),\r\n * });\r\n * const status = await bridge.call.getStatus();\r\n * \r\n * // In iframe\r\n * const bridge = createIframeBridge<IframeMethods, ParentMethods>({\r\n * initialize: async (config) => { ... },\r\n * getStatus: async () => 'ready',\r\n * });\r\n * const user = await bridge.call.getUser('123');\r\n */\r\n\r\n// ============================================================================\r\n// Type Utilities\r\n// ============================================================================\r\n\r\n/** Extract the return type, unwrapping Promise if needed */\r\ntype UnwrapPromise<T> = T extends Promise<infer U> ? U : T;\r\n\r\n/** Check if a type is a function */\r\ntype IsFunction<T> = T extends (...args: any[]) => any ? true : false;\r\n\r\n/** Method definition - can be sync or async */\r\ntype AnyMethod = (...args: any[]) => any;\r\n\r\n/** Contract for RPC methods */\r\ntype MethodContract = Record<string, AnyMethod>;\r\n\r\n/** Extract methods that return void (fire-and-forget) */\r\ntype VoidMethods<T extends MethodContract> = {\r\n [K in keyof T]: UnwrapPromise<ReturnType<T[K]>> extends void ? K : never;\r\n}[keyof T];\r\n\r\n/** Extract methods that return a value (request-response) */\r\ntype ValueMethods<T extends MethodContract> = Exclude<keyof T, VoidMethods<T>>;\r\n\r\n// ============================================================================\r\n// Message Types\r\n// ============================================================================\r\n\r\nconst MESSAGE_TYPE = {\r\n REQUEST: 'iframe-rpc:request',\r\n RESPONSE: 'iframe-rpc:response',\r\n ERROR: 'iframe-rpc:error',\r\n FIRE_AND_FORGET: 'iframe-rpc:fire-and-forget',\r\n} as const;\r\n\r\ninterface BaseMessage {\r\n __iframeRpc: true;\r\n channel?: string;\r\n}\r\n\r\ninterface RequestMessage<T extends MethodContract = MethodContract> extends BaseMessage {\r\n type: typeof MESSAGE_TYPE.REQUEST;\r\n id: string;\r\n method: keyof T & string;\r\n args: unknown[];\r\n}\r\n\r\ninterface ResponseMessage extends BaseMessage {\r\n type: typeof MESSAGE_TYPE.RESPONSE;\r\n id: string;\r\n result: unknown;\r\n}\r\n\r\ninterface ErrorMessage extends BaseMessage {\r\n type: typeof MESSAGE_TYPE.ERROR;\r\n id: string;\r\n error: {\r\n message: string;\r\n code?: string;\r\n stack?: string;\r\n };\r\n}\r\n\r\ninterface FireAndForgetMessage<T extends MethodContract = MethodContract> extends BaseMessage {\r\n type: typeof MESSAGE_TYPE.FIRE_AND_FORGET;\r\n method: keyof T & string;\r\n args: unknown[];\r\n}\r\n\r\ntype RpcMessage<T extends MethodContract = MethodContract> =\r\n | RequestMessage<T>\r\n | ResponseMessage\r\n | ErrorMessage\r\n | FireAndForgetMessage<T>;\r\n\r\n// ============================================================================\r\n// Error Types\r\n// ============================================================================\r\n\r\nexport class RpcError extends Error {\r\n constructor(\r\n message: string,\r\n public readonly code?: string,\r\n public readonly originalStack?: string\r\n ) {\r\n super(message);\r\n this.name = 'RpcError';\r\n }\r\n}\r\n\r\nexport class RpcTimeoutError extends RpcError {\r\n constructor(method: string, timeout: number) {\r\n super(`RPC call to \"${method}\" timed out after ${timeout}ms`, 'TIMEOUT');\r\n this.name = 'RpcTimeoutError';\r\n }\r\n}\r\n\r\nexport class RpcMethodNotFoundError extends RpcError {\r\n constructor(method: string) {\r\n super(`Method \"${method}\" not found`, 'METHOD_NOT_FOUND');\r\n this.name = 'RpcMethodNotFoundError';\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Configuration\r\n// ============================================================================\r\n\r\nexport interface BridgeOptions {\r\n /** Timeout for RPC calls in milliseconds. Default: 30000 */\r\n timeout?: number;\r\n /** Target origin for postMessage. Default: '*' (consider security implications) */\r\n targetOrigin?: string;\r\n /** Optional channel name to isolate multiple bridges */\r\n channel?: string;\r\n /** Enable debug logging */\r\n debug?: boolean;\r\n /** Include stack traces in error responses. Default: false (security) */\r\n includeStackTraces?: boolean;\r\n}\r\n\r\nconst DEFAULT_OPTIONS: Required<BridgeOptions> = {\r\n timeout: 30000,\r\n targetOrigin: '*',\r\n channel: 'default',\r\n debug: false,\r\n includeStackTraces: false,\r\n};\r\n\r\n// ============================================================================\r\n// Utility Functions\r\n// ============================================================================\r\n\r\nfunction generateId(): string {\r\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\r\n return crypto.randomUUID();\r\n }\r\n // Fallback with better entropy using getRandomValues when available\r\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\r\n const array = new Uint32Array(2);\r\n crypto.getRandomValues(array);\r\n return `${array[0].toString(16)}-${array[1].toString(16)}-${Date.now().toString(36)}`;\r\n }\r\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\r\n}\r\n\r\n// Cache valid message types for performance\r\nconst VALID_MESSAGE_TYPES = new Set(Object.values(MESSAGE_TYPE));\r\n\r\nfunction isRpcMessage(data: unknown): data is RpcMessage {\r\n return (\r\n typeof data === 'object' &&\r\n data !== null &&\r\n '__iframeRpc' in data &&\r\n (data as any).__iframeRpc === true &&\r\n 'type' in data &&\r\n VALID_MESSAGE_TYPES.has((data as any).type)\r\n );\r\n}\r\n\r\nfunction warnIfInsecureOrigin(options: Required<BridgeOptions>): void {\r\n if (options.debug && options.targetOrigin === '*') {\r\n console.warn('[iframe-rpc] Using targetOrigin:\"*\" is insecure for production. Consider specifying an exact origin.');\r\n }\r\n}\r\n\r\nfunction createLogger(debug: boolean, prefix: string) {\r\n return {\r\n log: (...args: unknown[]) => {\r\n if (debug) console.log(`[${prefix}]`, ...args);\r\n },\r\n error: (...args: unknown[]) => {\r\n if (debug) console.error(`[${prefix}]`, ...args);\r\n },\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Core Bridge Implementation\r\n// ============================================================================\r\n\r\ninterface PendingRequest {\r\n resolve: (value: unknown) => void;\r\n reject: (error: Error) => void;\r\n timeoutId: ReturnType<typeof setTimeout>;\r\n}\r\n\r\ntype CallProxy<T extends MethodContract> = {\r\n [K in keyof T]: T[K] extends (...args: infer A) => infer R\r\n ? (...args: A) => Promise<UnwrapPromise<R>>\r\n : never;\r\n};\r\n\r\n/**\r\n * Bridge interface for bidirectional RPC communication\r\n * @typeParam _TLocal - Local method handlers (unused, retained for API symmetry)\r\n * @typeParam TRemote - Remote methods available to call\r\n */\r\nexport interface Bridge<\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n _TLocal extends MethodContract,\r\n TRemote extends MethodContract\r\n> {\r\n /** Proxy object to call remote methods with full type safety */\r\n call: CallProxy<TRemote>;\r\n\r\n /** Call remote method by name (for dynamic method calls) */\r\n invoke: <K extends keyof TRemote>(\r\n method: K,\r\n ...args: Parameters<TRemote[K]>\r\n ) => Promise<UnwrapPromise<ReturnType<TRemote[K]>>>;\r\n\r\n /** Fire-and-forget call (no response expected) */\r\n notify: <K extends VoidMethods<TRemote>>(\r\n method: K,\r\n ...args: Parameters<TRemote[K]>\r\n ) => void;\r\n\r\n /** Destroy the bridge and clean up resources */\r\n destroy: () => void;\r\n\r\n /** Check if the bridge is still active */\r\n isActive: () => boolean;\r\n}\r\n\r\nfunction createBridge<\r\n TLocal extends MethodContract,\r\n TRemote extends MethodContract\r\n>(\r\n target: Window,\r\n handlers: TLocal,\r\n options: Required<BridgeOptions>,\r\n side: 'parent' | 'iframe'\r\n): Bridge<TLocal, TRemote> {\r\n const pendingRequests = new Map<string, PendingRequest>();\r\n let isDestroyed = false;\r\n const logger = createLogger(options.debug, `iframe-rpc:${side}`);\r\n\r\n // Handle incoming messages\r\n const handleMessage = (event: MessageEvent) => {\r\n if (isDestroyed) return;\r\n\r\n // Validate origin when targetOrigin is specified\r\n if (options.targetOrigin !== '*' && event.origin !== options.targetOrigin) {\r\n logger.log('Rejected message from untrusted origin:', event.origin);\r\n return;\r\n }\r\n\r\n const data = event.data;\r\n if (!isRpcMessage(data)) return;\r\n if (data.channel !== options.channel) return;\r\n\r\n logger.log('Received message:', data);\r\n\r\n switch (data.type) {\r\n case MESSAGE_TYPE.REQUEST:\r\n if (!event.source) {\r\n logger.error('Request received with null source');\r\n return;\r\n }\r\n handleRequest(data as RequestMessage, event.source as Window);\r\n break;\r\n case MESSAGE_TYPE.RESPONSE:\r\n handleResponse(data as ResponseMessage);\r\n break;\r\n case MESSAGE_TYPE.ERROR:\r\n handleError(data as ErrorMessage);\r\n break;\r\n case MESSAGE_TYPE.FIRE_AND_FORGET:\r\n handleFireAndForget(data as FireAndForgetMessage);\r\n break;\r\n }\r\n };\r\n\r\n const handleRequest = async (message: RequestMessage, source: Window) => {\r\n const { id, method, args } = message;\r\n const handler = handlers[method as keyof TLocal];\r\n\r\n if (!handler) {\r\n sendError(source, id, new RpcMethodNotFoundError(method));\r\n return;\r\n }\r\n\r\n try {\r\n const result = await (handler as AnyMethod)(...args);\r\n sendResponse(source, id, result);\r\n } catch (error) {\r\n sendError(source, id, error instanceof Error ? error : new Error(String(error)));\r\n }\r\n };\r\n\r\n const handleResponse = (message: ResponseMessage) => {\r\n const pending = pendingRequests.get(message.id);\r\n if (!pending) {\r\n logger.error('No pending request for response:', message.id);\r\n return;\r\n }\r\n\r\n clearTimeout(pending.timeoutId);\r\n pendingRequests.delete(message.id);\r\n pending.resolve(message.result);\r\n };\r\n\r\n const handleError = (message: ErrorMessage) => {\r\n const pending = pendingRequests.get(message.id);\r\n if (!pending) {\r\n logger.error('No pending request for error:', message.id);\r\n return;\r\n }\r\n\r\n clearTimeout(pending.timeoutId);\r\n pendingRequests.delete(message.id);\r\n pending.reject(\r\n new RpcError(message.error.message, message.error.code, message.error.stack)\r\n );\r\n };\r\n\r\n const handleFireAndForget = (message: FireAndForgetMessage) => {\r\n const handler = handlers[message.method as keyof TLocal];\r\n if (!handler) {\r\n logger.error('Handler not found for fire-and-forget:', message.method);\r\n return;\r\n }\r\n\r\n // Handle both sync and async handlers, catching any rejections\r\n Promise.resolve()\r\n .then(() => (handler as AnyMethod)(...message.args))\r\n .catch((error) => logger.error('Error in fire-and-forget handler:', error));\r\n };\r\n\r\n const sendMessage = (targetWindow: Window, message: RpcMessage) => {\r\n logger.log('Sending message:', message);\r\n targetWindow.postMessage(message, options.targetOrigin);\r\n };\r\n\r\n const sendResponse = (targetWindow: Window, id: string, result: unknown) => {\r\n const message: ResponseMessage = {\r\n __iframeRpc: true,\r\n type: MESSAGE_TYPE.RESPONSE,\r\n channel: options.channel,\r\n id,\r\n result,\r\n };\r\n sendMessage(targetWindow, message);\r\n };\r\n\r\n const sendError = (targetWindow: Window, id: string, error: Error) => {\r\n const message: ErrorMessage = {\r\n __iframeRpc: true,\r\n type: MESSAGE_TYPE.ERROR,\r\n channel: options.channel,\r\n id,\r\n error: {\r\n message: error.message,\r\n ...(error instanceof RpcError && error.code ? { code: error.code } : {}),\r\n // Only include stack traces if explicitly enabled (security consideration)\r\n ...(options.includeStackTraces && error.stack ? { stack: error.stack } : {}),\r\n },\r\n };\r\n sendMessage(targetWindow, message);\r\n };\r\n\r\n const callMethod = <K extends keyof TRemote>(\r\n method: K,\r\n args: unknown[]\r\n ): Promise<UnwrapPromise<ReturnType<TRemote[K]>>> => {\r\n if (isDestroyed) {\r\n return Promise.reject(new RpcError('Bridge has been destroyed', 'DESTROYED'));\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const id = generateId();\r\n\r\n const timeoutId = setTimeout(() => {\r\n pendingRequests.delete(id);\r\n reject(new RpcTimeoutError(String(method), options.timeout));\r\n }, options.timeout);\r\n\r\n pendingRequests.set(id, {\r\n resolve: resolve as (value: unknown) => void,\r\n reject,\r\n timeoutId\r\n });\r\n\r\n const message: RequestMessage = {\r\n __iframeRpc: true,\r\n type: MESSAGE_TYPE.REQUEST,\r\n channel: options.channel,\r\n id,\r\n method: method as string,\r\n args,\r\n };\r\n\r\n sendMessage(target, message);\r\n });\r\n };\r\n\r\n const notify = <K extends VoidMethods<TRemote>>(\r\n method: K,\r\n ...args: Parameters<TRemote[K]>\r\n ) => {\r\n if (isDestroyed) {\r\n logger.error('Cannot notify: bridge has been destroyed');\r\n return;\r\n }\r\n\r\n const message: FireAndForgetMessage = {\r\n __iframeRpc: true,\r\n type: MESSAGE_TYPE.FIRE_AND_FORGET,\r\n channel: options.channel,\r\n method: method as string,\r\n args,\r\n };\r\n\r\n sendMessage(target, message);\r\n };\r\n\r\n // Create call proxy with type safety\r\n const call = new Proxy({} as CallProxy<TRemote>, {\r\n get(_, prop) {\r\n // Handle Symbol properties (e.g., Symbol.toStringTag, Symbol.iterator)\r\n if (typeof prop === 'symbol') return undefined;\r\n return (...args: unknown[]) => callMethod(prop as keyof TRemote, args);\r\n },\r\n });\r\n\r\n // Set up message listener\r\n window.addEventListener('message', handleMessage);\r\n\r\n return {\r\n call,\r\n invoke: <K extends keyof TRemote>(method: K, ...args: Parameters<TRemote[K]>) =>\r\n callMethod(method, args),\r\n notify,\r\n destroy: () => {\r\n isDestroyed = true;\r\n window.removeEventListener('message', handleMessage);\r\n\r\n // Reject all pending requests\r\n for (const [, pending] of pendingRequests) {\r\n clearTimeout(pending.timeoutId);\r\n pending.reject(new RpcError('Bridge destroyed', 'DESTROYED'));\r\n }\r\n pendingRequests.clear();\r\n\r\n logger.log('Bridge destroyed');\r\n },\r\n isActive: () => !isDestroyed,\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Public API\r\n// ============================================================================\r\n\r\n/**\r\n * Create a bridge in the parent window to communicate with an iframe\r\n * \r\n * @param iframe - The iframe element to communicate with\r\n * @param handlers - Object containing methods that the iframe can call\r\n * @param options - Bridge configuration options\r\n * @returns Bridge instance with type-safe call proxy\r\n * \r\n * @example\r\n * const bridge = createParentBridge<ParentMethods, IframeMethods>(\r\n * document.getElementById('my-iframe') as HTMLIFrameElement,\r\n * {\r\n * getData: async (key) => localStorage.getItem(key),\r\n * setData: async (key, value) => localStorage.setItem(key, value),\r\n * }\r\n * );\r\n * \r\n * // Call iframe methods with full type safety\r\n * const result = await bridge.call.iframeMethod(arg1, arg2);\r\n */\r\nexport function createParentBridge<\r\n TLocal extends MethodContract,\r\n TRemote extends MethodContract\r\n>(\r\n iframe: HTMLIFrameElement,\r\n handlers: TLocal,\r\n options: BridgeOptions = {}\r\n): Bridge<TLocal, TRemote> {\r\n if (!iframe.contentWindow) {\r\n throw new Error('Iframe contentWindow is not available. Make sure the iframe is loaded.');\r\n }\r\n\r\n const mergedOptions = { ...DEFAULT_OPTIONS, ...options };\r\n warnIfInsecureOrigin(mergedOptions);\r\n\r\n return createBridge<TLocal, TRemote>(\r\n iframe.contentWindow,\r\n handlers,\r\n mergedOptions,\r\n 'parent'\r\n );\r\n}\r\n\r\n/**\r\n * Create a bridge in the iframe to communicate with the parent window\r\n * \r\n * @param handlers - Object containing methods that the parent can call\r\n * @param options - Bridge configuration options\r\n * @returns Bridge instance with type-safe call proxy\r\n * \r\n * @example\r\n * const bridge = createIframeBridge<IframeMethods, ParentMethods>({\r\n * getStatus: async () => 'ready',\r\n * initialize: async (config) => {\r\n * console.log('Initialized with:', config);\r\n * },\r\n * });\r\n * \r\n * // Call parent methods with full type safety\r\n * const data = await bridge.call.getData('user');\r\n */\r\nexport function createIframeBridge<\r\n TLocal extends MethodContract,\r\n TRemote extends MethodContract\r\n>(\r\n handlers: TLocal,\r\n options: BridgeOptions = {}\r\n): Bridge<TLocal, TRemote> {\r\n if (!window.parent || window.parent === window) {\r\n throw new Error('Not running inside an iframe');\r\n }\r\n\r\n const mergedOptions = { ...DEFAULT_OPTIONS, ...options };\r\n warnIfInsecureOrigin(mergedOptions);\r\n\r\n return createBridge<TLocal, TRemote>(\r\n window.parent,\r\n handlers,\r\n mergedOptions,\r\n 'iframe'\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Type Helpers for Contract Definition\r\n// ============================================================================\r\n\r\n/**\r\n * Helper type to define RPC method contracts\r\n * Ensures all methods are properly typed\r\n */\r\nexport type DefineContract<T extends MethodContract> = T;\r\n\r\n/**\r\n * Extract parameter types from a method contract\r\n */\r\nexport type ParamsOf<T extends MethodContract, K extends keyof T> = Parameters<T[K]>;\r\n\r\n/**\r\n * Extract return type from a method contract\r\n */\r\nexport type ReturnOf<T extends MethodContract, K extends keyof T> = UnwrapPromise<ReturnType<T[K]>>;\r\n\r\n// ============================================================================\r\n// Re-exports\r\n// ============================================================================\r\n\r\nexport { MESSAGE_TYPE };\r\nexport type {\r\n MethodContract,\r\n CallProxy,\r\n VoidMethods,\r\n ValueMethods,\r\n IsFunction,\r\n RpcMessage,\r\n RequestMessage,\r\n ResponseMessage,\r\n ErrorMessage,\r\n FireAndForgetMessage,\r\n};"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe bidirectional RPC communication between parent window and iframe
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* // Define your API contract
|
|
6
|
+
* type ParentMethods = {
|
|
7
|
+
* getUser: (id: string) => Promise<{ name: string; age: number }>;
|
|
8
|
+
* notify: (message: string) => void;
|
|
9
|
+
* };
|
|
10
|
+
*
|
|
11
|
+
* type IframeMethods = {
|
|
12
|
+
* initialize: (config: { theme: string }) => Promise<void>;
|
|
13
|
+
* getStatus: () => Promise<'ready' | 'loading'>;
|
|
14
|
+
* };
|
|
15
|
+
*
|
|
16
|
+
* // In parent
|
|
17
|
+
* const bridge = createParentBridge<ParentMethods, IframeMethods>(iframe, {
|
|
18
|
+
* getUser: async (id) => ({ name: 'John', age: 30 }),
|
|
19
|
+
* notify: (message) => console.log(message),
|
|
20
|
+
* });
|
|
21
|
+
* const status = await bridge.call.getStatus();
|
|
22
|
+
*
|
|
23
|
+
* // In iframe
|
|
24
|
+
* const bridge = createIframeBridge<IframeMethods, ParentMethods>({
|
|
25
|
+
* initialize: async (config) => { ... },
|
|
26
|
+
* getStatus: async () => 'ready',
|
|
27
|
+
* });
|
|
28
|
+
* const user = await bridge.call.getUser('123');
|
|
29
|
+
*/
|
|
30
|
+
/** Extract the return type, unwrapping Promise if needed */
|
|
31
|
+
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
|
|
32
|
+
/** Check if a type is a function */
|
|
33
|
+
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
|
|
34
|
+
/** Method definition - can be sync or async */
|
|
35
|
+
type AnyMethod = (...args: any[]) => any;
|
|
36
|
+
/** Contract for RPC methods */
|
|
37
|
+
type MethodContract = Record<string, AnyMethod>;
|
|
38
|
+
/** Extract methods that return void (fire-and-forget) */
|
|
39
|
+
type VoidMethods<T extends MethodContract> = {
|
|
40
|
+
[K in keyof T]: UnwrapPromise<ReturnType<T[K]>> extends void ? K : never;
|
|
41
|
+
}[keyof T];
|
|
42
|
+
/** Extract methods that return a value (request-response) */
|
|
43
|
+
type ValueMethods<T extends MethodContract> = Exclude<keyof T, VoidMethods<T>>;
|
|
44
|
+
declare const MESSAGE_TYPE: {
|
|
45
|
+
readonly REQUEST: "iframe-rpc:request";
|
|
46
|
+
readonly RESPONSE: "iframe-rpc:response";
|
|
47
|
+
readonly ERROR: "iframe-rpc:error";
|
|
48
|
+
readonly FIRE_AND_FORGET: "iframe-rpc:fire-and-forget";
|
|
49
|
+
};
|
|
50
|
+
interface BaseMessage {
|
|
51
|
+
__iframeRpc: true;
|
|
52
|
+
channel?: string;
|
|
53
|
+
}
|
|
54
|
+
interface RequestMessage<T extends MethodContract = MethodContract> extends BaseMessage {
|
|
55
|
+
type: typeof MESSAGE_TYPE.REQUEST;
|
|
56
|
+
id: string;
|
|
57
|
+
method: keyof T & string;
|
|
58
|
+
args: unknown[];
|
|
59
|
+
}
|
|
60
|
+
interface ResponseMessage extends BaseMessage {
|
|
61
|
+
type: typeof MESSAGE_TYPE.RESPONSE;
|
|
62
|
+
id: string;
|
|
63
|
+
result: unknown;
|
|
64
|
+
}
|
|
65
|
+
interface ErrorMessage extends BaseMessage {
|
|
66
|
+
type: typeof MESSAGE_TYPE.ERROR;
|
|
67
|
+
id: string;
|
|
68
|
+
error: {
|
|
69
|
+
message: string;
|
|
70
|
+
code?: string;
|
|
71
|
+
stack?: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
interface FireAndForgetMessage<T extends MethodContract = MethodContract> extends BaseMessage {
|
|
75
|
+
type: typeof MESSAGE_TYPE.FIRE_AND_FORGET;
|
|
76
|
+
method: keyof T & string;
|
|
77
|
+
args: unknown[];
|
|
78
|
+
}
|
|
79
|
+
type RpcMessage<T extends MethodContract = MethodContract> = RequestMessage<T> | ResponseMessage | ErrorMessage | FireAndForgetMessage<T>;
|
|
80
|
+
declare class RpcError extends Error {
|
|
81
|
+
readonly code?: string | undefined;
|
|
82
|
+
readonly originalStack?: string | undefined;
|
|
83
|
+
constructor(message: string, code?: string | undefined, originalStack?: string | undefined);
|
|
84
|
+
}
|
|
85
|
+
declare class RpcTimeoutError extends RpcError {
|
|
86
|
+
constructor(method: string, timeout: number);
|
|
87
|
+
}
|
|
88
|
+
declare class RpcMethodNotFoundError extends RpcError {
|
|
89
|
+
constructor(method: string);
|
|
90
|
+
}
|
|
91
|
+
interface BridgeOptions {
|
|
92
|
+
/** Timeout for RPC calls in milliseconds. Default: 30000 */
|
|
93
|
+
timeout?: number;
|
|
94
|
+
/** Target origin for postMessage. Default: '*' (consider security implications) */
|
|
95
|
+
targetOrigin?: string;
|
|
96
|
+
/** Optional channel name to isolate multiple bridges */
|
|
97
|
+
channel?: string;
|
|
98
|
+
/** Enable debug logging */
|
|
99
|
+
debug?: boolean;
|
|
100
|
+
/** Include stack traces in error responses. Default: false (security) */
|
|
101
|
+
includeStackTraces?: boolean;
|
|
102
|
+
}
|
|
103
|
+
type CallProxy<T extends MethodContract> = {
|
|
104
|
+
[K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<UnwrapPromise<R>> : never;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Bridge interface for bidirectional RPC communication
|
|
108
|
+
* @typeParam _TLocal - Local method handlers (unused, retained for API symmetry)
|
|
109
|
+
* @typeParam TRemote - Remote methods available to call
|
|
110
|
+
*/
|
|
111
|
+
interface Bridge<_TLocal extends MethodContract, TRemote extends MethodContract> {
|
|
112
|
+
/** Proxy object to call remote methods with full type safety */
|
|
113
|
+
call: CallProxy<TRemote>;
|
|
114
|
+
/** Call remote method by name (for dynamic method calls) */
|
|
115
|
+
invoke: <K extends keyof TRemote>(method: K, ...args: Parameters<TRemote[K]>) => Promise<UnwrapPromise<ReturnType<TRemote[K]>>>;
|
|
116
|
+
/** Fire-and-forget call (no response expected) */
|
|
117
|
+
notify: <K extends VoidMethods<TRemote>>(method: K, ...args: Parameters<TRemote[K]>) => void;
|
|
118
|
+
/** Destroy the bridge and clean up resources */
|
|
119
|
+
destroy: () => void;
|
|
120
|
+
/** Check if the bridge is still active */
|
|
121
|
+
isActive: () => boolean;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Create a bridge in the parent window to communicate with an iframe
|
|
125
|
+
*
|
|
126
|
+
* @param iframe - The iframe element to communicate with
|
|
127
|
+
* @param handlers - Object containing methods that the iframe can call
|
|
128
|
+
* @param options - Bridge configuration options
|
|
129
|
+
* @returns Bridge instance with type-safe call proxy
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* const bridge = createParentBridge<ParentMethods, IframeMethods>(
|
|
133
|
+
* document.getElementById('my-iframe') as HTMLIFrameElement,
|
|
134
|
+
* {
|
|
135
|
+
* getData: async (key) => localStorage.getItem(key),
|
|
136
|
+
* setData: async (key, value) => localStorage.setItem(key, value),
|
|
137
|
+
* }
|
|
138
|
+
* );
|
|
139
|
+
*
|
|
140
|
+
* // Call iframe methods with full type safety
|
|
141
|
+
* const result = await bridge.call.iframeMethod(arg1, arg2);
|
|
142
|
+
*/
|
|
143
|
+
declare function createParentBridge<TLocal extends MethodContract, TRemote extends MethodContract>(iframe: HTMLIFrameElement, handlers: TLocal, options?: BridgeOptions): Bridge<TLocal, TRemote>;
|
|
144
|
+
/**
|
|
145
|
+
* Create a bridge in the iframe to communicate with the parent window
|
|
146
|
+
*
|
|
147
|
+
* @param handlers - Object containing methods that the parent can call
|
|
148
|
+
* @param options - Bridge configuration options
|
|
149
|
+
* @returns Bridge instance with type-safe call proxy
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* const bridge = createIframeBridge<IframeMethods, ParentMethods>({
|
|
153
|
+
* getStatus: async () => 'ready',
|
|
154
|
+
* initialize: async (config) => {
|
|
155
|
+
* console.log('Initialized with:', config);
|
|
156
|
+
* },
|
|
157
|
+
* });
|
|
158
|
+
*
|
|
159
|
+
* // Call parent methods with full type safety
|
|
160
|
+
* const data = await bridge.call.getData('user');
|
|
161
|
+
*/
|
|
162
|
+
declare function createIframeBridge<TLocal extends MethodContract, TRemote extends MethodContract>(handlers: TLocal, options?: BridgeOptions): Bridge<TLocal, TRemote>;
|
|
163
|
+
/**
|
|
164
|
+
* Helper type to define RPC method contracts
|
|
165
|
+
* Ensures all methods are properly typed
|
|
166
|
+
*/
|
|
167
|
+
type DefineContract<T extends MethodContract> = T;
|
|
168
|
+
/**
|
|
169
|
+
* Extract parameter types from a method contract
|
|
170
|
+
*/
|
|
171
|
+
type ParamsOf<T extends MethodContract, K extends keyof T> = Parameters<T[K]>;
|
|
172
|
+
/**
|
|
173
|
+
* Extract return type from a method contract
|
|
174
|
+
*/
|
|
175
|
+
type ReturnOf<T extends MethodContract, K extends keyof T> = UnwrapPromise<ReturnType<T[K]>>;
|
|
176
|
+
|
|
177
|
+
export { type Bridge, type BridgeOptions, type CallProxy, type DefineContract, type ErrorMessage, type FireAndForgetMessage, type IsFunction, MESSAGE_TYPE, type MethodContract, type ParamsOf, type RequestMessage, type ResponseMessage, type ReturnOf, RpcError, type RpcMessage, RpcMethodNotFoundError, RpcTimeoutError, type ValueMethods, type VoidMethods, createIframeBridge, createParentBridge };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe bidirectional RPC communication between parent window and iframe
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* // Define your API contract
|
|
6
|
+
* type ParentMethods = {
|
|
7
|
+
* getUser: (id: string) => Promise<{ name: string; age: number }>;
|
|
8
|
+
* notify: (message: string) => void;
|
|
9
|
+
* };
|
|
10
|
+
*
|
|
11
|
+
* type IframeMethods = {
|
|
12
|
+
* initialize: (config: { theme: string }) => Promise<void>;
|
|
13
|
+
* getStatus: () => Promise<'ready' | 'loading'>;
|
|
14
|
+
* };
|
|
15
|
+
*
|
|
16
|
+
* // In parent
|
|
17
|
+
* const bridge = createParentBridge<ParentMethods, IframeMethods>(iframe, {
|
|
18
|
+
* getUser: async (id) => ({ name: 'John', age: 30 }),
|
|
19
|
+
* notify: (message) => console.log(message),
|
|
20
|
+
* });
|
|
21
|
+
* const status = await bridge.call.getStatus();
|
|
22
|
+
*
|
|
23
|
+
* // In iframe
|
|
24
|
+
* const bridge = createIframeBridge<IframeMethods, ParentMethods>({
|
|
25
|
+
* initialize: async (config) => { ... },
|
|
26
|
+
* getStatus: async () => 'ready',
|
|
27
|
+
* });
|
|
28
|
+
* const user = await bridge.call.getUser('123');
|
|
29
|
+
*/
|
|
30
|
+
/** Extract the return type, unwrapping Promise if needed */
|
|
31
|
+
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
|
|
32
|
+
/** Check if a type is a function */
|
|
33
|
+
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
|
|
34
|
+
/** Method definition - can be sync or async */
|
|
35
|
+
type AnyMethod = (...args: any[]) => any;
|
|
36
|
+
/** Contract for RPC methods */
|
|
37
|
+
type MethodContract = Record<string, AnyMethod>;
|
|
38
|
+
/** Extract methods that return void (fire-and-forget) */
|
|
39
|
+
type VoidMethods<T extends MethodContract> = {
|
|
40
|
+
[K in keyof T]: UnwrapPromise<ReturnType<T[K]>> extends void ? K : never;
|
|
41
|
+
}[keyof T];
|
|
42
|
+
/** Extract methods that return a value (request-response) */
|
|
43
|
+
type ValueMethods<T extends MethodContract> = Exclude<keyof T, VoidMethods<T>>;
|
|
44
|
+
declare const MESSAGE_TYPE: {
|
|
45
|
+
readonly REQUEST: "iframe-rpc:request";
|
|
46
|
+
readonly RESPONSE: "iframe-rpc:response";
|
|
47
|
+
readonly ERROR: "iframe-rpc:error";
|
|
48
|
+
readonly FIRE_AND_FORGET: "iframe-rpc:fire-and-forget";
|
|
49
|
+
};
|
|
50
|
+
interface BaseMessage {
|
|
51
|
+
__iframeRpc: true;
|
|
52
|
+
channel?: string;
|
|
53
|
+
}
|
|
54
|
+
interface RequestMessage<T extends MethodContract = MethodContract> extends BaseMessage {
|
|
55
|
+
type: typeof MESSAGE_TYPE.REQUEST;
|
|
56
|
+
id: string;
|
|
57
|
+
method: keyof T & string;
|
|
58
|
+
args: unknown[];
|
|
59
|
+
}
|
|
60
|
+
interface ResponseMessage extends BaseMessage {
|
|
61
|
+
type: typeof MESSAGE_TYPE.RESPONSE;
|
|
62
|
+
id: string;
|
|
63
|
+
result: unknown;
|
|
64
|
+
}
|
|
65
|
+
interface ErrorMessage extends BaseMessage {
|
|
66
|
+
type: typeof MESSAGE_TYPE.ERROR;
|
|
67
|
+
id: string;
|
|
68
|
+
error: {
|
|
69
|
+
message: string;
|
|
70
|
+
code?: string;
|
|
71
|
+
stack?: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
interface FireAndForgetMessage<T extends MethodContract = MethodContract> extends BaseMessage {
|
|
75
|
+
type: typeof MESSAGE_TYPE.FIRE_AND_FORGET;
|
|
76
|
+
method: keyof T & string;
|
|
77
|
+
args: unknown[];
|
|
78
|
+
}
|
|
79
|
+
type RpcMessage<T extends MethodContract = MethodContract> = RequestMessage<T> | ResponseMessage | ErrorMessage | FireAndForgetMessage<T>;
|
|
80
|
+
declare class RpcError extends Error {
|
|
81
|
+
readonly code?: string | undefined;
|
|
82
|
+
readonly originalStack?: string | undefined;
|
|
83
|
+
constructor(message: string, code?: string | undefined, originalStack?: string | undefined);
|
|
84
|
+
}
|
|
85
|
+
declare class RpcTimeoutError extends RpcError {
|
|
86
|
+
constructor(method: string, timeout: number);
|
|
87
|
+
}
|
|
88
|
+
declare class RpcMethodNotFoundError extends RpcError {
|
|
89
|
+
constructor(method: string);
|
|
90
|
+
}
|
|
91
|
+
interface BridgeOptions {
|
|
92
|
+
/** Timeout for RPC calls in milliseconds. Default: 30000 */
|
|
93
|
+
timeout?: number;
|
|
94
|
+
/** Target origin for postMessage. Default: '*' (consider security implications) */
|
|
95
|
+
targetOrigin?: string;
|
|
96
|
+
/** Optional channel name to isolate multiple bridges */
|
|
97
|
+
channel?: string;
|
|
98
|
+
/** Enable debug logging */
|
|
99
|
+
debug?: boolean;
|
|
100
|
+
/** Include stack traces in error responses. Default: false (security) */
|
|
101
|
+
includeStackTraces?: boolean;
|
|
102
|
+
}
|
|
103
|
+
type CallProxy<T extends MethodContract> = {
|
|
104
|
+
[K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<UnwrapPromise<R>> : never;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Bridge interface for bidirectional RPC communication
|
|
108
|
+
* @typeParam _TLocal - Local method handlers (unused, retained for API symmetry)
|
|
109
|
+
* @typeParam TRemote - Remote methods available to call
|
|
110
|
+
*/
|
|
111
|
+
interface Bridge<_TLocal extends MethodContract, TRemote extends MethodContract> {
|
|
112
|
+
/** Proxy object to call remote methods with full type safety */
|
|
113
|
+
call: CallProxy<TRemote>;
|
|
114
|
+
/** Call remote method by name (for dynamic method calls) */
|
|
115
|
+
invoke: <K extends keyof TRemote>(method: K, ...args: Parameters<TRemote[K]>) => Promise<UnwrapPromise<ReturnType<TRemote[K]>>>;
|
|
116
|
+
/** Fire-and-forget call (no response expected) */
|
|
117
|
+
notify: <K extends VoidMethods<TRemote>>(method: K, ...args: Parameters<TRemote[K]>) => void;
|
|
118
|
+
/** Destroy the bridge and clean up resources */
|
|
119
|
+
destroy: () => void;
|
|
120
|
+
/** Check if the bridge is still active */
|
|
121
|
+
isActive: () => boolean;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Create a bridge in the parent window to communicate with an iframe
|
|
125
|
+
*
|
|
126
|
+
* @param iframe - The iframe element to communicate with
|
|
127
|
+
* @param handlers - Object containing methods that the iframe can call
|
|
128
|
+
* @param options - Bridge configuration options
|
|
129
|
+
* @returns Bridge instance with type-safe call proxy
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* const bridge = createParentBridge<ParentMethods, IframeMethods>(
|
|
133
|
+
* document.getElementById('my-iframe') as HTMLIFrameElement,
|
|
134
|
+
* {
|
|
135
|
+
* getData: async (key) => localStorage.getItem(key),
|
|
136
|
+
* setData: async (key, value) => localStorage.setItem(key, value),
|
|
137
|
+
* }
|
|
138
|
+
* );
|
|
139
|
+
*
|
|
140
|
+
* // Call iframe methods with full type safety
|
|
141
|
+
* const result = await bridge.call.iframeMethod(arg1, arg2);
|
|
142
|
+
*/
|
|
143
|
+
declare function createParentBridge<TLocal extends MethodContract, TRemote extends MethodContract>(iframe: HTMLIFrameElement, handlers: TLocal, options?: BridgeOptions): Bridge<TLocal, TRemote>;
|
|
144
|
+
/**
|
|
145
|
+
* Create a bridge in the iframe to communicate with the parent window
|
|
146
|
+
*
|
|
147
|
+
* @param handlers - Object containing methods that the parent can call
|
|
148
|
+
* @param options - Bridge configuration options
|
|
149
|
+
* @returns Bridge instance with type-safe call proxy
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* const bridge = createIframeBridge<IframeMethods, ParentMethods>({
|
|
153
|
+
* getStatus: async () => 'ready',
|
|
154
|
+
* initialize: async (config) => {
|
|
155
|
+
* console.log('Initialized with:', config);
|
|
156
|
+
* },
|
|
157
|
+
* });
|
|
158
|
+
*
|
|
159
|
+
* // Call parent methods with full type safety
|
|
160
|
+
* const data = await bridge.call.getData('user');
|
|
161
|
+
*/
|
|
162
|
+
declare function createIframeBridge<TLocal extends MethodContract, TRemote extends MethodContract>(handlers: TLocal, options?: BridgeOptions): Bridge<TLocal, TRemote>;
|
|
163
|
+
/**
|
|
164
|
+
* Helper type to define RPC method contracts
|
|
165
|
+
* Ensures all methods are properly typed
|
|
166
|
+
*/
|
|
167
|
+
type DefineContract<T extends MethodContract> = T;
|
|
168
|
+
/**
|
|
169
|
+
* Extract parameter types from a method contract
|
|
170
|
+
*/
|
|
171
|
+
type ParamsOf<T extends MethodContract, K extends keyof T> = Parameters<T[K]>;
|
|
172
|
+
/**
|
|
173
|
+
* Extract return type from a method contract
|
|
174
|
+
*/
|
|
175
|
+
type ReturnOf<T extends MethodContract, K extends keyof T> = UnwrapPromise<ReturnType<T[K]>>;
|
|
176
|
+
|
|
177
|
+
export { type Bridge, type BridgeOptions, type CallProxy, type DefineContract, type ErrorMessage, type FireAndForgetMessage, type IsFunction, MESSAGE_TYPE, type MethodContract, type ParamsOf, type RequestMessage, type ResponseMessage, type ReturnOf, RpcError, type RpcMessage, RpcMethodNotFoundError, RpcTimeoutError, type ValueMethods, type VoidMethods, createIframeBridge, createParentBridge };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var MESSAGE_TYPE = {
|
|
3
|
+
REQUEST: "iframe-rpc:request",
|
|
4
|
+
RESPONSE: "iframe-rpc:response",
|
|
5
|
+
ERROR: "iframe-rpc:error",
|
|
6
|
+
FIRE_AND_FORGET: "iframe-rpc:fire-and-forget"
|
|
7
|
+
};
|
|
8
|
+
var RpcError = class extends Error {
|
|
9
|
+
constructor(message, code, originalStack) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.originalStack = originalStack;
|
|
13
|
+
this.name = "RpcError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var RpcTimeoutError = class extends RpcError {
|
|
17
|
+
constructor(method, timeout) {
|
|
18
|
+
super(`RPC call to "${method}" timed out after ${timeout}ms`, "TIMEOUT");
|
|
19
|
+
this.name = "RpcTimeoutError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var RpcMethodNotFoundError = class extends RpcError {
|
|
23
|
+
constructor(method) {
|
|
24
|
+
super(`Method "${method}" not found`, "METHOD_NOT_FOUND");
|
|
25
|
+
this.name = "RpcMethodNotFoundError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var DEFAULT_OPTIONS = {
|
|
29
|
+
timeout: 3e4,
|
|
30
|
+
targetOrigin: "*",
|
|
31
|
+
channel: "default",
|
|
32
|
+
debug: false,
|
|
33
|
+
includeStackTraces: false
|
|
34
|
+
};
|
|
35
|
+
function generateId() {
|
|
36
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
37
|
+
return crypto.randomUUID();
|
|
38
|
+
}
|
|
39
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
40
|
+
const array = new Uint32Array(2);
|
|
41
|
+
crypto.getRandomValues(array);
|
|
42
|
+
return `${array[0].toString(16)}-${array[1].toString(16)}-${Date.now().toString(36)}`;
|
|
43
|
+
}
|
|
44
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
45
|
+
}
|
|
46
|
+
var VALID_MESSAGE_TYPES = new Set(Object.values(MESSAGE_TYPE));
|
|
47
|
+
function isRpcMessage(data) {
|
|
48
|
+
return typeof data === "object" && data !== null && "__iframeRpc" in data && data.__iframeRpc === true && "type" in data && VALID_MESSAGE_TYPES.has(data.type);
|
|
49
|
+
}
|
|
50
|
+
function warnIfInsecureOrigin(options) {
|
|
51
|
+
if (options.debug && options.targetOrigin === "*") {
|
|
52
|
+
console.warn('[iframe-rpc] Using targetOrigin:"*" is insecure for production. Consider specifying an exact origin.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function createLogger(debug, prefix) {
|
|
56
|
+
return {
|
|
57
|
+
log: (...args) => {
|
|
58
|
+
if (debug) console.log(`[${prefix}]`, ...args);
|
|
59
|
+
},
|
|
60
|
+
error: (...args) => {
|
|
61
|
+
if (debug) console.error(`[${prefix}]`, ...args);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function createBridge(target, handlers, options, side) {
|
|
66
|
+
const pendingRequests = /* @__PURE__ */ new Map();
|
|
67
|
+
let isDestroyed = false;
|
|
68
|
+
const logger = createLogger(options.debug, `iframe-rpc:${side}`);
|
|
69
|
+
const handleMessage = (event) => {
|
|
70
|
+
if (isDestroyed) return;
|
|
71
|
+
if (options.targetOrigin !== "*" && event.origin !== options.targetOrigin) {
|
|
72
|
+
logger.log("Rejected message from untrusted origin:", event.origin);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const data = event.data;
|
|
76
|
+
if (!isRpcMessage(data)) return;
|
|
77
|
+
if (data.channel !== options.channel) return;
|
|
78
|
+
logger.log("Received message:", data);
|
|
79
|
+
switch (data.type) {
|
|
80
|
+
case MESSAGE_TYPE.REQUEST:
|
|
81
|
+
if (!event.source) {
|
|
82
|
+
logger.error("Request received with null source");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
handleRequest(data, event.source);
|
|
86
|
+
break;
|
|
87
|
+
case MESSAGE_TYPE.RESPONSE:
|
|
88
|
+
handleResponse(data);
|
|
89
|
+
break;
|
|
90
|
+
case MESSAGE_TYPE.ERROR:
|
|
91
|
+
handleError(data);
|
|
92
|
+
break;
|
|
93
|
+
case MESSAGE_TYPE.FIRE_AND_FORGET:
|
|
94
|
+
handleFireAndForget(data);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const handleRequest = async (message, source) => {
|
|
99
|
+
const { id, method, args } = message;
|
|
100
|
+
const handler = handlers[method];
|
|
101
|
+
if (!handler) {
|
|
102
|
+
sendError(source, id, new RpcMethodNotFoundError(method));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const result = await handler(...args);
|
|
107
|
+
sendResponse(source, id, result);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
sendError(source, id, error instanceof Error ? error : new Error(String(error)));
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const handleResponse = (message) => {
|
|
113
|
+
const pending = pendingRequests.get(message.id);
|
|
114
|
+
if (!pending) {
|
|
115
|
+
logger.error("No pending request for response:", message.id);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
clearTimeout(pending.timeoutId);
|
|
119
|
+
pendingRequests.delete(message.id);
|
|
120
|
+
pending.resolve(message.result);
|
|
121
|
+
};
|
|
122
|
+
const handleError = (message) => {
|
|
123
|
+
const pending = pendingRequests.get(message.id);
|
|
124
|
+
if (!pending) {
|
|
125
|
+
logger.error("No pending request for error:", message.id);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
clearTimeout(pending.timeoutId);
|
|
129
|
+
pendingRequests.delete(message.id);
|
|
130
|
+
pending.reject(
|
|
131
|
+
new RpcError(message.error.message, message.error.code, message.error.stack)
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
const handleFireAndForget = (message) => {
|
|
135
|
+
const handler = handlers[message.method];
|
|
136
|
+
if (!handler) {
|
|
137
|
+
logger.error("Handler not found for fire-and-forget:", message.method);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
Promise.resolve().then(() => handler(...message.args)).catch((error) => logger.error("Error in fire-and-forget handler:", error));
|
|
141
|
+
};
|
|
142
|
+
const sendMessage = (targetWindow, message) => {
|
|
143
|
+
logger.log("Sending message:", message);
|
|
144
|
+
targetWindow.postMessage(message, options.targetOrigin);
|
|
145
|
+
};
|
|
146
|
+
const sendResponse = (targetWindow, id, result) => {
|
|
147
|
+
const message = {
|
|
148
|
+
__iframeRpc: true,
|
|
149
|
+
type: MESSAGE_TYPE.RESPONSE,
|
|
150
|
+
channel: options.channel,
|
|
151
|
+
id,
|
|
152
|
+
result
|
|
153
|
+
};
|
|
154
|
+
sendMessage(targetWindow, message);
|
|
155
|
+
};
|
|
156
|
+
const sendError = (targetWindow, id, error) => {
|
|
157
|
+
const message = {
|
|
158
|
+
__iframeRpc: true,
|
|
159
|
+
type: MESSAGE_TYPE.ERROR,
|
|
160
|
+
channel: options.channel,
|
|
161
|
+
id,
|
|
162
|
+
error: {
|
|
163
|
+
message: error.message,
|
|
164
|
+
...error instanceof RpcError && error.code ? { code: error.code } : {},
|
|
165
|
+
// Only include stack traces if explicitly enabled (security consideration)
|
|
166
|
+
...options.includeStackTraces && error.stack ? { stack: error.stack } : {}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
sendMessage(targetWindow, message);
|
|
170
|
+
};
|
|
171
|
+
const callMethod = (method, args) => {
|
|
172
|
+
if (isDestroyed) {
|
|
173
|
+
return Promise.reject(new RpcError("Bridge has been destroyed", "DESTROYED"));
|
|
174
|
+
}
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
const id = generateId();
|
|
177
|
+
const timeoutId = setTimeout(() => {
|
|
178
|
+
pendingRequests.delete(id);
|
|
179
|
+
reject(new RpcTimeoutError(String(method), options.timeout));
|
|
180
|
+
}, options.timeout);
|
|
181
|
+
pendingRequests.set(id, {
|
|
182
|
+
resolve,
|
|
183
|
+
reject,
|
|
184
|
+
timeoutId
|
|
185
|
+
});
|
|
186
|
+
const message = {
|
|
187
|
+
__iframeRpc: true,
|
|
188
|
+
type: MESSAGE_TYPE.REQUEST,
|
|
189
|
+
channel: options.channel,
|
|
190
|
+
id,
|
|
191
|
+
method,
|
|
192
|
+
args
|
|
193
|
+
};
|
|
194
|
+
sendMessage(target, message);
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
const notify = (method, ...args) => {
|
|
198
|
+
if (isDestroyed) {
|
|
199
|
+
logger.error("Cannot notify: bridge has been destroyed");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const message = {
|
|
203
|
+
__iframeRpc: true,
|
|
204
|
+
type: MESSAGE_TYPE.FIRE_AND_FORGET,
|
|
205
|
+
channel: options.channel,
|
|
206
|
+
method,
|
|
207
|
+
args
|
|
208
|
+
};
|
|
209
|
+
sendMessage(target, message);
|
|
210
|
+
};
|
|
211
|
+
const call = new Proxy({}, {
|
|
212
|
+
get(_, prop) {
|
|
213
|
+
if (typeof prop === "symbol") return void 0;
|
|
214
|
+
return (...args) => callMethod(prop, args);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
window.addEventListener("message", handleMessage);
|
|
218
|
+
return {
|
|
219
|
+
call,
|
|
220
|
+
invoke: (method, ...args) => callMethod(method, args),
|
|
221
|
+
notify,
|
|
222
|
+
destroy: () => {
|
|
223
|
+
isDestroyed = true;
|
|
224
|
+
window.removeEventListener("message", handleMessage);
|
|
225
|
+
for (const [, pending] of pendingRequests) {
|
|
226
|
+
clearTimeout(pending.timeoutId);
|
|
227
|
+
pending.reject(new RpcError("Bridge destroyed", "DESTROYED"));
|
|
228
|
+
}
|
|
229
|
+
pendingRequests.clear();
|
|
230
|
+
logger.log("Bridge destroyed");
|
|
231
|
+
},
|
|
232
|
+
isActive: () => !isDestroyed
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function createParentBridge(iframe, handlers, options = {}) {
|
|
236
|
+
if (!iframe.contentWindow) {
|
|
237
|
+
throw new Error("Iframe contentWindow is not available. Make sure the iframe is loaded.");
|
|
238
|
+
}
|
|
239
|
+
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
240
|
+
warnIfInsecureOrigin(mergedOptions);
|
|
241
|
+
return createBridge(
|
|
242
|
+
iframe.contentWindow,
|
|
243
|
+
handlers,
|
|
244
|
+
mergedOptions,
|
|
245
|
+
"parent"
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
function createIframeBridge(handlers, options = {}) {
|
|
249
|
+
if (!window.parent || window.parent === window) {
|
|
250
|
+
throw new Error("Not running inside an iframe");
|
|
251
|
+
}
|
|
252
|
+
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
253
|
+
warnIfInsecureOrigin(mergedOptions);
|
|
254
|
+
return createBridge(
|
|
255
|
+
window.parent,
|
|
256
|
+
handlers,
|
|
257
|
+
mergedOptions,
|
|
258
|
+
"iframe"
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export { MESSAGE_TYPE, RpcError, RpcMethodNotFoundError, RpcTimeoutError, createIframeBridge, createParentBridge };
|
|
263
|
+
//# sourceMappingURL=index.js.map
|
|
264
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Type-safe bidirectional RPC communication between parent window and iframe\r\n * \r\n * @example\r\n * // Define your API contract\r\n * type ParentMethods = {\r\n * getUser: (id: string) => Promise<{ name: string; age: number }>;\r\n * notify: (message: string) => void;\r\n * };\r\n * \r\n * type IframeMethods = {\r\n * initialize: (config: { theme: string }) => Promise<void>;\r\n * getStatus: () => Promise<'ready' | 'loading'>;\r\n * };\r\n * \r\n * // In parent\r\n * const bridge = createParentBridge<ParentMethods, IframeMethods>(iframe, {\r\n * getUser: async (id) => ({ name: 'John', age: 30 }),\r\n * notify: (message) => console.log(message),\r\n * });\r\n * const status = await bridge.call.getStatus();\r\n * \r\n * // In iframe\r\n * const bridge = createIframeBridge<IframeMethods, ParentMethods>({\r\n * initialize: async (config) => { ... },\r\n * getStatus: async () => 'ready',\r\n * });\r\n * const user = await bridge.call.getUser('123');\r\n */\r\n\r\n// ============================================================================\r\n// Type Utilities\r\n// ============================================================================\r\n\r\n/** Extract the return type, unwrapping Promise if needed */\r\ntype UnwrapPromise<T> = T extends Promise<infer U> ? U : T;\r\n\r\n/** Check if a type is a function */\r\ntype IsFunction<T> = T extends (...args: any[]) => any ? true : false;\r\n\r\n/** Method definition - can be sync or async */\r\ntype AnyMethod = (...args: any[]) => any;\r\n\r\n/** Contract for RPC methods */\r\ntype MethodContract = Record<string, AnyMethod>;\r\n\r\n/** Extract methods that return void (fire-and-forget) */\r\ntype VoidMethods<T extends MethodContract> = {\r\n [K in keyof T]: UnwrapPromise<ReturnType<T[K]>> extends void ? K : never;\r\n}[keyof T];\r\n\r\n/** Extract methods that return a value (request-response) */\r\ntype ValueMethods<T extends MethodContract> = Exclude<keyof T, VoidMethods<T>>;\r\n\r\n// ============================================================================\r\n// Message Types\r\n// ============================================================================\r\n\r\nconst MESSAGE_TYPE = {\r\n REQUEST: 'iframe-rpc:request',\r\n RESPONSE: 'iframe-rpc:response',\r\n ERROR: 'iframe-rpc:error',\r\n FIRE_AND_FORGET: 'iframe-rpc:fire-and-forget',\r\n} as const;\r\n\r\ninterface BaseMessage {\r\n __iframeRpc: true;\r\n channel?: string;\r\n}\r\n\r\ninterface RequestMessage<T extends MethodContract = MethodContract> extends BaseMessage {\r\n type: typeof MESSAGE_TYPE.REQUEST;\r\n id: string;\r\n method: keyof T & string;\r\n args: unknown[];\r\n}\r\n\r\ninterface ResponseMessage extends BaseMessage {\r\n type: typeof MESSAGE_TYPE.RESPONSE;\r\n id: string;\r\n result: unknown;\r\n}\r\n\r\ninterface ErrorMessage extends BaseMessage {\r\n type: typeof MESSAGE_TYPE.ERROR;\r\n id: string;\r\n error: {\r\n message: string;\r\n code?: string;\r\n stack?: string;\r\n };\r\n}\r\n\r\ninterface FireAndForgetMessage<T extends MethodContract = MethodContract> extends BaseMessage {\r\n type: typeof MESSAGE_TYPE.FIRE_AND_FORGET;\r\n method: keyof T & string;\r\n args: unknown[];\r\n}\r\n\r\ntype RpcMessage<T extends MethodContract = MethodContract> =\r\n | RequestMessage<T>\r\n | ResponseMessage\r\n | ErrorMessage\r\n | FireAndForgetMessage<T>;\r\n\r\n// ============================================================================\r\n// Error Types\r\n// ============================================================================\r\n\r\nexport class RpcError extends Error {\r\n constructor(\r\n message: string,\r\n public readonly code?: string,\r\n public readonly originalStack?: string\r\n ) {\r\n super(message);\r\n this.name = 'RpcError';\r\n }\r\n}\r\n\r\nexport class RpcTimeoutError extends RpcError {\r\n constructor(method: string, timeout: number) {\r\n super(`RPC call to \"${method}\" timed out after ${timeout}ms`, 'TIMEOUT');\r\n this.name = 'RpcTimeoutError';\r\n }\r\n}\r\n\r\nexport class RpcMethodNotFoundError extends RpcError {\r\n constructor(method: string) {\r\n super(`Method \"${method}\" not found`, 'METHOD_NOT_FOUND');\r\n this.name = 'RpcMethodNotFoundError';\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Configuration\r\n// ============================================================================\r\n\r\nexport interface BridgeOptions {\r\n /** Timeout for RPC calls in milliseconds. Default: 30000 */\r\n timeout?: number;\r\n /** Target origin for postMessage. Default: '*' (consider security implications) */\r\n targetOrigin?: string;\r\n /** Optional channel name to isolate multiple bridges */\r\n channel?: string;\r\n /** Enable debug logging */\r\n debug?: boolean;\r\n /** Include stack traces in error responses. Default: false (security) */\r\n includeStackTraces?: boolean;\r\n}\r\n\r\nconst DEFAULT_OPTIONS: Required<BridgeOptions> = {\r\n timeout: 30000,\r\n targetOrigin: '*',\r\n channel: 'default',\r\n debug: false,\r\n includeStackTraces: false,\r\n};\r\n\r\n// ============================================================================\r\n// Utility Functions\r\n// ============================================================================\r\n\r\nfunction generateId(): string {\r\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\r\n return crypto.randomUUID();\r\n }\r\n // Fallback with better entropy using getRandomValues when available\r\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\r\n const array = new Uint32Array(2);\r\n crypto.getRandomValues(array);\r\n return `${array[0].toString(16)}-${array[1].toString(16)}-${Date.now().toString(36)}`;\r\n }\r\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\r\n}\r\n\r\n// Cache valid message types for performance\r\nconst VALID_MESSAGE_TYPES = new Set(Object.values(MESSAGE_TYPE));\r\n\r\nfunction isRpcMessage(data: unknown): data is RpcMessage {\r\n return (\r\n typeof data === 'object' &&\r\n data !== null &&\r\n '__iframeRpc' in data &&\r\n (data as any).__iframeRpc === true &&\r\n 'type' in data &&\r\n VALID_MESSAGE_TYPES.has((data as any).type)\r\n );\r\n}\r\n\r\nfunction warnIfInsecureOrigin(options: Required<BridgeOptions>): void {\r\n if (options.debug && options.targetOrigin === '*') {\r\n console.warn('[iframe-rpc] Using targetOrigin:\"*\" is insecure for production. Consider specifying an exact origin.');\r\n }\r\n}\r\n\r\nfunction createLogger(debug: boolean, prefix: string) {\r\n return {\r\n log: (...args: unknown[]) => {\r\n if (debug) console.log(`[${prefix}]`, ...args);\r\n },\r\n error: (...args: unknown[]) => {\r\n if (debug) console.error(`[${prefix}]`, ...args);\r\n },\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Core Bridge Implementation\r\n// ============================================================================\r\n\r\ninterface PendingRequest {\r\n resolve: (value: unknown) => void;\r\n reject: (error: Error) => void;\r\n timeoutId: ReturnType<typeof setTimeout>;\r\n}\r\n\r\ntype CallProxy<T extends MethodContract> = {\r\n [K in keyof T]: T[K] extends (...args: infer A) => infer R\r\n ? (...args: A) => Promise<UnwrapPromise<R>>\r\n : never;\r\n};\r\n\r\n/**\r\n * Bridge interface for bidirectional RPC communication\r\n * @typeParam _TLocal - Local method handlers (unused, retained for API symmetry)\r\n * @typeParam TRemote - Remote methods available to call\r\n */\r\nexport interface Bridge<\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n _TLocal extends MethodContract,\r\n TRemote extends MethodContract\r\n> {\r\n /** Proxy object to call remote methods with full type safety */\r\n call: CallProxy<TRemote>;\r\n\r\n /** Call remote method by name (for dynamic method calls) */\r\n invoke: <K extends keyof TRemote>(\r\n method: K,\r\n ...args: Parameters<TRemote[K]>\r\n ) => Promise<UnwrapPromise<ReturnType<TRemote[K]>>>;\r\n\r\n /** Fire-and-forget call (no response expected) */\r\n notify: <K extends VoidMethods<TRemote>>(\r\n method: K,\r\n ...args: Parameters<TRemote[K]>\r\n ) => void;\r\n\r\n /** Destroy the bridge and clean up resources */\r\n destroy: () => void;\r\n\r\n /** Check if the bridge is still active */\r\n isActive: () => boolean;\r\n}\r\n\r\nfunction createBridge<\r\n TLocal extends MethodContract,\r\n TRemote extends MethodContract\r\n>(\r\n target: Window,\r\n handlers: TLocal,\r\n options: Required<BridgeOptions>,\r\n side: 'parent' | 'iframe'\r\n): Bridge<TLocal, TRemote> {\r\n const pendingRequests = new Map<string, PendingRequest>();\r\n let isDestroyed = false;\r\n const logger = createLogger(options.debug, `iframe-rpc:${side}`);\r\n\r\n // Handle incoming messages\r\n const handleMessage = (event: MessageEvent) => {\r\n if (isDestroyed) return;\r\n\r\n // Validate origin when targetOrigin is specified\r\n if (options.targetOrigin !== '*' && event.origin !== options.targetOrigin) {\r\n logger.log('Rejected message from untrusted origin:', event.origin);\r\n return;\r\n }\r\n\r\n const data = event.data;\r\n if (!isRpcMessage(data)) return;\r\n if (data.channel !== options.channel) return;\r\n\r\n logger.log('Received message:', data);\r\n\r\n switch (data.type) {\r\n case MESSAGE_TYPE.REQUEST:\r\n if (!event.source) {\r\n logger.error('Request received with null source');\r\n return;\r\n }\r\n handleRequest(data as RequestMessage, event.source as Window);\r\n break;\r\n case MESSAGE_TYPE.RESPONSE:\r\n handleResponse(data as ResponseMessage);\r\n break;\r\n case MESSAGE_TYPE.ERROR:\r\n handleError(data as ErrorMessage);\r\n break;\r\n case MESSAGE_TYPE.FIRE_AND_FORGET:\r\n handleFireAndForget(data as FireAndForgetMessage);\r\n break;\r\n }\r\n };\r\n\r\n const handleRequest = async (message: RequestMessage, source: Window) => {\r\n const { id, method, args } = message;\r\n const handler = handlers[method as keyof TLocal];\r\n\r\n if (!handler) {\r\n sendError(source, id, new RpcMethodNotFoundError(method));\r\n return;\r\n }\r\n\r\n try {\r\n const result = await (handler as AnyMethod)(...args);\r\n sendResponse(source, id, result);\r\n } catch (error) {\r\n sendError(source, id, error instanceof Error ? error : new Error(String(error)));\r\n }\r\n };\r\n\r\n const handleResponse = (message: ResponseMessage) => {\r\n const pending = pendingRequests.get(message.id);\r\n if (!pending) {\r\n logger.error('No pending request for response:', message.id);\r\n return;\r\n }\r\n\r\n clearTimeout(pending.timeoutId);\r\n pendingRequests.delete(message.id);\r\n pending.resolve(message.result);\r\n };\r\n\r\n const handleError = (message: ErrorMessage) => {\r\n const pending = pendingRequests.get(message.id);\r\n if (!pending) {\r\n logger.error('No pending request for error:', message.id);\r\n return;\r\n }\r\n\r\n clearTimeout(pending.timeoutId);\r\n pendingRequests.delete(message.id);\r\n pending.reject(\r\n new RpcError(message.error.message, message.error.code, message.error.stack)\r\n );\r\n };\r\n\r\n const handleFireAndForget = (message: FireAndForgetMessage) => {\r\n const handler = handlers[message.method as keyof TLocal];\r\n if (!handler) {\r\n logger.error('Handler not found for fire-and-forget:', message.method);\r\n return;\r\n }\r\n\r\n // Handle both sync and async handlers, catching any rejections\r\n Promise.resolve()\r\n .then(() => (handler as AnyMethod)(...message.args))\r\n .catch((error) => logger.error('Error in fire-and-forget handler:', error));\r\n };\r\n\r\n const sendMessage = (targetWindow: Window, message: RpcMessage) => {\r\n logger.log('Sending message:', message);\r\n targetWindow.postMessage(message, options.targetOrigin);\r\n };\r\n\r\n const sendResponse = (targetWindow: Window, id: string, result: unknown) => {\r\n const message: ResponseMessage = {\r\n __iframeRpc: true,\r\n type: MESSAGE_TYPE.RESPONSE,\r\n channel: options.channel,\r\n id,\r\n result,\r\n };\r\n sendMessage(targetWindow, message);\r\n };\r\n\r\n const sendError = (targetWindow: Window, id: string, error: Error) => {\r\n const message: ErrorMessage = {\r\n __iframeRpc: true,\r\n type: MESSAGE_TYPE.ERROR,\r\n channel: options.channel,\r\n id,\r\n error: {\r\n message: error.message,\r\n ...(error instanceof RpcError && error.code ? { code: error.code } : {}),\r\n // Only include stack traces if explicitly enabled (security consideration)\r\n ...(options.includeStackTraces && error.stack ? { stack: error.stack } : {}),\r\n },\r\n };\r\n sendMessage(targetWindow, message);\r\n };\r\n\r\n const callMethod = <K extends keyof TRemote>(\r\n method: K,\r\n args: unknown[]\r\n ): Promise<UnwrapPromise<ReturnType<TRemote[K]>>> => {\r\n if (isDestroyed) {\r\n return Promise.reject(new RpcError('Bridge has been destroyed', 'DESTROYED'));\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const id = generateId();\r\n\r\n const timeoutId = setTimeout(() => {\r\n pendingRequests.delete(id);\r\n reject(new RpcTimeoutError(String(method), options.timeout));\r\n }, options.timeout);\r\n\r\n pendingRequests.set(id, {\r\n resolve: resolve as (value: unknown) => void,\r\n reject,\r\n timeoutId\r\n });\r\n\r\n const message: RequestMessage = {\r\n __iframeRpc: true,\r\n type: MESSAGE_TYPE.REQUEST,\r\n channel: options.channel,\r\n id,\r\n method: method as string,\r\n args,\r\n };\r\n\r\n sendMessage(target, message);\r\n });\r\n };\r\n\r\n const notify = <K extends VoidMethods<TRemote>>(\r\n method: K,\r\n ...args: Parameters<TRemote[K]>\r\n ) => {\r\n if (isDestroyed) {\r\n logger.error('Cannot notify: bridge has been destroyed');\r\n return;\r\n }\r\n\r\n const message: FireAndForgetMessage = {\r\n __iframeRpc: true,\r\n type: MESSAGE_TYPE.FIRE_AND_FORGET,\r\n channel: options.channel,\r\n method: method as string,\r\n args,\r\n };\r\n\r\n sendMessage(target, message);\r\n };\r\n\r\n // Create call proxy with type safety\r\n const call = new Proxy({} as CallProxy<TRemote>, {\r\n get(_, prop) {\r\n // Handle Symbol properties (e.g., Symbol.toStringTag, Symbol.iterator)\r\n if (typeof prop === 'symbol') return undefined;\r\n return (...args: unknown[]) => callMethod(prop as keyof TRemote, args);\r\n },\r\n });\r\n\r\n // Set up message listener\r\n window.addEventListener('message', handleMessage);\r\n\r\n return {\r\n call,\r\n invoke: <K extends keyof TRemote>(method: K, ...args: Parameters<TRemote[K]>) =>\r\n callMethod(method, args),\r\n notify,\r\n destroy: () => {\r\n isDestroyed = true;\r\n window.removeEventListener('message', handleMessage);\r\n\r\n // Reject all pending requests\r\n for (const [, pending] of pendingRequests) {\r\n clearTimeout(pending.timeoutId);\r\n pending.reject(new RpcError('Bridge destroyed', 'DESTROYED'));\r\n }\r\n pendingRequests.clear();\r\n\r\n logger.log('Bridge destroyed');\r\n },\r\n isActive: () => !isDestroyed,\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Public API\r\n// ============================================================================\r\n\r\n/**\r\n * Create a bridge in the parent window to communicate with an iframe\r\n * \r\n * @param iframe - The iframe element to communicate with\r\n * @param handlers - Object containing methods that the iframe can call\r\n * @param options - Bridge configuration options\r\n * @returns Bridge instance with type-safe call proxy\r\n * \r\n * @example\r\n * const bridge = createParentBridge<ParentMethods, IframeMethods>(\r\n * document.getElementById('my-iframe') as HTMLIFrameElement,\r\n * {\r\n * getData: async (key) => localStorage.getItem(key),\r\n * setData: async (key, value) => localStorage.setItem(key, value),\r\n * }\r\n * );\r\n * \r\n * // Call iframe methods with full type safety\r\n * const result = await bridge.call.iframeMethod(arg1, arg2);\r\n */\r\nexport function createParentBridge<\r\n TLocal extends MethodContract,\r\n TRemote extends MethodContract\r\n>(\r\n iframe: HTMLIFrameElement,\r\n handlers: TLocal,\r\n options: BridgeOptions = {}\r\n): Bridge<TLocal, TRemote> {\r\n if (!iframe.contentWindow) {\r\n throw new Error('Iframe contentWindow is not available. Make sure the iframe is loaded.');\r\n }\r\n\r\n const mergedOptions = { ...DEFAULT_OPTIONS, ...options };\r\n warnIfInsecureOrigin(mergedOptions);\r\n\r\n return createBridge<TLocal, TRemote>(\r\n iframe.contentWindow,\r\n handlers,\r\n mergedOptions,\r\n 'parent'\r\n );\r\n}\r\n\r\n/**\r\n * Create a bridge in the iframe to communicate with the parent window\r\n * \r\n * @param handlers - Object containing methods that the parent can call\r\n * @param options - Bridge configuration options\r\n * @returns Bridge instance with type-safe call proxy\r\n * \r\n * @example\r\n * const bridge = createIframeBridge<IframeMethods, ParentMethods>({\r\n * getStatus: async () => 'ready',\r\n * initialize: async (config) => {\r\n * console.log('Initialized with:', config);\r\n * },\r\n * });\r\n * \r\n * // Call parent methods with full type safety\r\n * const data = await bridge.call.getData('user');\r\n */\r\nexport function createIframeBridge<\r\n TLocal extends MethodContract,\r\n TRemote extends MethodContract\r\n>(\r\n handlers: TLocal,\r\n options: BridgeOptions = {}\r\n): Bridge<TLocal, TRemote> {\r\n if (!window.parent || window.parent === window) {\r\n throw new Error('Not running inside an iframe');\r\n }\r\n\r\n const mergedOptions = { ...DEFAULT_OPTIONS, ...options };\r\n warnIfInsecureOrigin(mergedOptions);\r\n\r\n return createBridge<TLocal, TRemote>(\r\n window.parent,\r\n handlers,\r\n mergedOptions,\r\n 'iframe'\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Type Helpers for Contract Definition\r\n// ============================================================================\r\n\r\n/**\r\n * Helper type to define RPC method contracts\r\n * Ensures all methods are properly typed\r\n */\r\nexport type DefineContract<T extends MethodContract> = T;\r\n\r\n/**\r\n * Extract parameter types from a method contract\r\n */\r\nexport type ParamsOf<T extends MethodContract, K extends keyof T> = Parameters<T[K]>;\r\n\r\n/**\r\n * Extract return type from a method contract\r\n */\r\nexport type ReturnOf<T extends MethodContract, K extends keyof T> = UnwrapPromise<ReturnType<T[K]>>;\r\n\r\n// ============================================================================\r\n// Re-exports\r\n// ============================================================================\r\n\r\nexport { MESSAGE_TYPE };\r\nexport type {\r\n MethodContract,\r\n CallProxy,\r\n VoidMethods,\r\n ValueMethods,\r\n IsFunction,\r\n RpcMessage,\r\n RequestMessage,\r\n ResponseMessage,\r\n ErrorMessage,\r\n FireAndForgetMessage,\r\n};"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@duyquangnvx/iframe-rpc",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Type-safe bidirectional RPC communication between parent window and iframe",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"dev": "tsup --watch",
|
|
29
|
+
"test": "vitest",
|
|
30
|
+
"test:run": "vitest run",
|
|
31
|
+
"test:coverage": "vitest run --coverage",
|
|
32
|
+
"lint": "eslint src --ext .ts",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"prepublishOnly": "pnpm run build",
|
|
35
|
+
"demo": "pnpm build && npx serve . -p 3000",
|
|
36
|
+
"demo:open": "pnpm build && npx serve . -p 3000 -o /examples/demo/parent.html"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"iframe",
|
|
40
|
+
"postMessage",
|
|
41
|
+
"rpc",
|
|
42
|
+
"bridge",
|
|
43
|
+
"communication",
|
|
44
|
+
"type-safe",
|
|
45
|
+
"typescript",
|
|
46
|
+
"micro-frontend"
|
|
47
|
+
],
|
|
48
|
+
"author": "duyquangnvx",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/duyquangnvx/iframe-rpc.git"
|
|
53
|
+
},
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/duyquangnvx/iframe-rpc/issues"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/duyquangnvx/iframe-rpc#readme",
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=18.0.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/node": "^20.10.0",
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
67
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
68
|
+
"@vitest/coverage-v8": "^1.2.0",
|
|
69
|
+
"eslint": "^8.56.0",
|
|
70
|
+
"jsdom": "^24.0.0",
|
|
71
|
+
"tsup": "^8.0.0",
|
|
72
|
+
"typescript": "^5.3.0",
|
|
73
|
+
"vitest": "^1.2.0"
|
|
74
|
+
}
|
|
75
|
+
}
|