iframe-bridge-kit 1.0.1 → 1.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 +20 -20
- package/README.md +256 -181
- package/dist/full/core.js +1 -1
- package/dist/full/core.mjs +1 -1
- package/dist/full/parent-core.js +1 -0
- package/dist/full/parent-core.mjs +1 -0
- package/dist/index.d.mts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +28 -3
- package/dist/index.mjs +26 -2
- package/dist/mini/core.js +1 -1
- package/dist/mini/core.mjs +1 -1
- package/dist/mini/parent-core.js +1 -0
- package/dist/mini/parent-core.mjs +1 -0
- package/dist/vite.js +67 -10
- package/dist/vite.mjs +67 -10
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 ZhangSan<2306860505@qq.com>
|
|
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
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ZhangSan<2306860505@qq.com>
|
|
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
21
|
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,182 +1,257 @@
|
|
|
1
|
-
# iframe-bridge-kit
|
|
2
|
-
|
|
3
|
-
[English](./README.md) | [简体中文](https://github.com/mchao123/iframe-bridge-kit/blob/master/README_zh.md)
|
|
4
|
-
|
|
5
|
-
`iframe-bridge-kit` is an iframe communication library based on [Vite](https://vitejs.dev/) and [Penpal](https://www.google.com/search?q=https://github.com/google/penpal). It uses a Vite plugin to automatically generate type definitions, allowing
|
|
6
|
-
|
|
7
|
-
## ✨ Features
|
|
8
|
-
|
|
9
|
-
* 🔒 **Type Safe**: Automatically generates `.d.ts` based on source code; parent and child windows share identical types.
|
|
10
|
-
* 🚀 **
|
|
11
|
-
* 📡 **RPC Style**: Call cross-window methods just like calling `async` functions.
|
|
12
|
-
* ⚡ **Event Mechanism**: Supports
|
|
13
|
-
* 🛠 **Vite Integration**: Designed specifically for the Vite ecosystem with HMR support.
|
|
14
|
-
|
|
15
|
-
## 📦 Installation
|
|
16
|
-
|
|
17
|
-
You need to install both `iframe-bridge-kit` and its peer dependency `penpal`.
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npm install iframe-bridge-kit penpal
|
|
21
|
-
# or
|
|
22
|
-
pnpm add iframe-bridge-kit penpal
|
|
23
|
-
# or
|
|
24
|
-
yarn add iframe-bridge-kit penpal
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## ⚙️ Configuration
|
|
28
|
-
|
|
29
|
-
Import the plugin in your `vite.config.ts`.
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
// vite.config.ts
|
|
33
|
-
import { defineConfig } from 'vite'
|
|
34
|
-
import vue from '@vitejs/plugin-vue' // or other framework plugins
|
|
35
|
-
import vitePluginIframeBridge from 'iframe-bridge-kit/vite'
|
|
36
|
-
|
|
37
|
-
export default defineConfig({
|
|
38
|
-
plugins: [
|
|
39
|
-
vue(),
|
|
40
|
-
vitePluginIframeBridge({
|
|
41
|
-
// Output directory, default is 'src/bridges' (recommended to place under src for easy import)
|
|
42
|
-
outDir: 'src/bridges',
|
|
43
|
-
// Whether to generate full code (including Penpal dependency), default is true
|
|
44
|
-
full: true
|
|
45
|
-
})
|
|
46
|
-
]
|
|
47
|
-
})
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## 📖 Usage Guide
|
|
51
|
-
|
|
52
|
-
### 1\. Parent
|
|
53
|
-
|
|
54
|
-
In the parent window, use `defineBridge` to define the methods and event types exposed to the iframe.
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// src/views/Parent.vue (or other .ts files)
|
|
58
|
-
import { defineBridge } from 'iframe-bridge-kit'
|
|
59
|
-
import { ref, onMounted } from 'vue'
|
|
60
|
-
|
|
61
|
-
// Define event types sent from Parent to Child
|
|
62
|
-
interface EmitMap {
|
|
63
|
-
'theme-change': { mode: 'dark' | 'light' }
|
|
64
|
-
'user-logout': void
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 1. Define Bridge
|
|
68
|
-
// The first argument 'app-bridge' is the bridge name, used for folder generation
|
|
69
|
-
export const mainBridge = defineBridge<EmitMap>('app-bridge', {
|
|
70
|
-
// Methods exposed to the iframe
|
|
71
|
-
async getUserInfo(id: string) {
|
|
72
|
-
return { id, name: 'John Doe', role: 'admin' }
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
updateTitle(title: string) {
|
|
76
|
-
document.title = title
|
|
77
|
-
return true
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
// 2. Bind iframe
|
|
82
|
-
const iframeRef = ref<HTMLIFrameElement>()
|
|
83
|
-
|
|
84
|
-
onMounted(async () => {
|
|
85
|
-
if (iframeRef.value) {
|
|
86
|
-
const child = await mainBridge.create(iframeRef.value)
|
|
87
|
-
|
|
88
|
-
// Send message to iframe
|
|
89
|
-
child.emit('theme-change', { mode: 'dark' })
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
> **Note**: After saving the file, the Vite plugin will automatically scan for `defineBridge` and generate the corresponding type definitions and runtime code under `src/bridges/app-bridge/`.
|
|
95
|
-
|
|
96
|
-
### 2\. Child Window (Iframe/Child)
|
|
97
|
-
|
|
98
|
-
In the iframe project, **directly import the file generated by the plugin**. All API methods have strict type inference.
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
// src/views/IframeChild.vue
|
|
102
|
-
// Import from the generated directory (path depends on your outDir config)
|
|
103
|
-
import ParentApi, { onMessage, onInit } from '../bridges/app-bridge'
|
|
104
|
-
|
|
105
|
-
// Wait for connection initialization (optional)
|
|
106
|
-
onInit(() => {
|
|
107
|
-
console.log('Bridge connected!')
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
// 1. Call parent window methods (RPC)
|
|
111
|
-
async function fetchUser() {
|
|
112
|
-
// ✅ Full type hints for id and return value here!
|
|
113
|
-
const user = await ParentApi.getUserInfo('123')
|
|
114
|
-
console.log(user.name)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 2. Listen for parent window messages
|
|
118
|
-
// ✅ Type hints for 'theme-change' and callback data
|
|
119
|
-
onMessage('theme-change', (data) => {
|
|
120
|
-
console.log('New theme:', data.mode)
|
|
121
|
-
})
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
1
|
+
# iframe-bridge-kit
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | [简体中文](https://github.com/mchao123/iframe-bridge-kit/blob/master/README_zh.md)
|
|
4
|
+
|
|
5
|
+
`iframe-bridge-kit` is an iframe communication library based on [Vite](https://vitejs.dev/) and [Penpal](https://www.google.com/search?q=https://github.com/google/penpal). It uses a Vite plugin to automatically generate type definitions, allowing the parent window and iframe child window to expose RPC methods to each other with **100% TypeScript type hints**, just like calling local functions.
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
* 🔒 **Type Safe**: Automatically generates `.d.ts` based on source code; parent and child windows share identical types.
|
|
10
|
+
* 🚀 **No Duplicate Type Definitions**: The other window does not need to manually sync interfaces; simply import the generated bridge file to use.
|
|
11
|
+
* 📡 **RPC Style**: Call cross-window methods just like calling `async` functions.
|
|
12
|
+
* ⚡ **Event Mechanism**: Supports strongly-typed broadcast messages from both parent and iframe child windows.
|
|
13
|
+
* 🛠 **Vite Integration**: Designed specifically for the Vite ecosystem with HMR support.
|
|
14
|
+
|
|
15
|
+
## 📦 Installation
|
|
16
|
+
|
|
17
|
+
You need to install both `iframe-bridge-kit` and its peer dependency `penpal`.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install iframe-bridge-kit penpal
|
|
21
|
+
# or
|
|
22
|
+
pnpm add iframe-bridge-kit penpal
|
|
23
|
+
# or
|
|
24
|
+
yarn add iframe-bridge-kit penpal
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## ⚙️ Configuration
|
|
28
|
+
|
|
29
|
+
Import the plugin in your `vite.config.ts`.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// vite.config.ts
|
|
33
|
+
import { defineConfig } from 'vite'
|
|
34
|
+
import vue from '@vitejs/plugin-vue' // or other framework plugins
|
|
35
|
+
import vitePluginIframeBridge from 'iframe-bridge-kit/vite'
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
38
|
+
plugins: [
|
|
39
|
+
vue(),
|
|
40
|
+
vitePluginIframeBridge({
|
|
41
|
+
// Output directory, default is 'src/bridges' (recommended to place under src for easy import)
|
|
42
|
+
outDir: 'src/bridges',
|
|
43
|
+
// Whether to generate full code (including Penpal dependency), default is true
|
|
44
|
+
full: true
|
|
45
|
+
})
|
|
46
|
+
]
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 📖 Usage Guide
|
|
51
|
+
|
|
52
|
+
### 1\. Parent Defines APIs, Iframe Calls Parent
|
|
53
|
+
|
|
54
|
+
In the parent window, use `defineBridge` to define the methods and event types exposed to the iframe.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// src/views/Parent.vue (or other .ts files)
|
|
58
|
+
import { defineBridge } from 'iframe-bridge-kit'
|
|
59
|
+
import { ref, onMounted } from 'vue'
|
|
60
|
+
|
|
61
|
+
// Define event types sent from Parent to Child
|
|
62
|
+
interface EmitMap {
|
|
63
|
+
'theme-change': { mode: 'dark' | 'light' }
|
|
64
|
+
'user-logout': void
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 1. Define Bridge
|
|
68
|
+
// The first argument 'app-bridge' is the bridge name, used for folder generation
|
|
69
|
+
export const mainBridge = defineBridge<EmitMap>('app-bridge', {
|
|
70
|
+
// Methods exposed to the iframe
|
|
71
|
+
async getUserInfo(id: string) {
|
|
72
|
+
return { id, name: 'John Doe', role: 'admin' }
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
updateTitle(title: string) {
|
|
76
|
+
document.title = title
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// 2. Bind iframe
|
|
82
|
+
const iframeRef = ref<HTMLIFrameElement>()
|
|
83
|
+
|
|
84
|
+
onMounted(async () => {
|
|
85
|
+
if (iframeRef.value) {
|
|
86
|
+
const child = await mainBridge.create(iframeRef.value)
|
|
87
|
+
|
|
88
|
+
// Send message to iframe
|
|
89
|
+
child.emit('theme-change', { mode: 'dark' })
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> **Note**: After saving the file, the Vite plugin will automatically scan for `defineBridge` and generate the corresponding type definitions and runtime code under `src/bridges/app-bridge/`.
|
|
95
|
+
|
|
96
|
+
### 2\. Child Window (Iframe/Child)
|
|
97
|
+
|
|
98
|
+
In the iframe project, **directly import the file generated by the plugin**. All API methods have strict type inference.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// src/views/IframeChild.vue
|
|
102
|
+
// Import from the generated directory (path depends on your outDir config)
|
|
103
|
+
import ParentApi, { onMessage, onInit } from '../bridges/app-bridge'
|
|
104
|
+
|
|
105
|
+
// Wait for connection initialization (optional)
|
|
106
|
+
onInit(() => {
|
|
107
|
+
console.log('Bridge connected!')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// 1. Call parent window methods (RPC)
|
|
111
|
+
async function fetchUser() {
|
|
112
|
+
// ✅ Full type hints for id and return value here!
|
|
113
|
+
const user = await ParentApi.getUserInfo('123')
|
|
114
|
+
console.log(user.name)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 2. Listen for parent window messages
|
|
118
|
+
// ✅ Type hints for 'theme-change' and callback data
|
|
119
|
+
onMessage('theme-change', (data) => {
|
|
120
|
+
console.log('New theme:', data.mode)
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 3\. Iframe Defines APIs, Parent Calls Iframe
|
|
125
|
+
|
|
126
|
+
If your main use case is to build an iframe page first and let the parent window integrate with it, use `defineIframeBridge` inside the iframe project. The plugin will generate a parent-side client from the methods exposed by the iframe.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// src/iframeBridge.ts (inside the iframe project)
|
|
130
|
+
import { defineIframeBridge } from 'iframe-bridge-kit'
|
|
131
|
+
|
|
132
|
+
interface ChildEmitMap {
|
|
133
|
+
'ready': { url: string }
|
|
134
|
+
'height-change': { height: number }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const childBridge = defineIframeBridge<ChildEmitMap>('child-app', {
|
|
138
|
+
async getPageInfo() {
|
|
139
|
+
return {
|
|
140
|
+
title: document.title,
|
|
141
|
+
url: location.href
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
setTheme(mode: 'dark' | 'light') {
|
|
146
|
+
document.documentElement.dataset.theme = mode
|
|
147
|
+
return true
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
childBridge.connect().then((parent) => {
|
|
152
|
+
parent.emit('ready', { url: location.href })
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
After saving, the plugin generates `src/bridges/child-app/`. The parent project can import that directory and call `create(iframe)` to get strongly-typed methods exposed by the iframe.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// src/views/Parent.vue (inside the parent project)
|
|
160
|
+
import ChildBridge from '../bridges/child-app'
|
|
161
|
+
|
|
162
|
+
const iframe = document.querySelector<HTMLIFrameElement>('#child-app')!
|
|
163
|
+
const child = ChildBridge.create(iframe)
|
|
164
|
+
|
|
165
|
+
child.onInit(async () => {
|
|
166
|
+
const info = await child.api.getPageInfo()
|
|
167
|
+
console.log(info.title)
|
|
168
|
+
|
|
169
|
+
await child.api.setTheme('dark')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
child.onMessage('height-change', ({ height }) => {
|
|
173
|
+
iframe.style.height = `${height}px`
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 🧩 Type Support Details
|
|
178
|
+
|
|
179
|
+
The core magic of `iframe-bridge-kit` lies in how it handles types.
|
|
180
|
+
|
|
181
|
+
When you define methods:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
getUserInfo(id: string): Promise<User>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The plugin extracts the `User` interface (even types imported from `node_modules`) and **copies** it into the generated `index.d.ts`. This means the child window does not need access to the parent's source code or dependencies to get perfect type hints.
|
|
188
|
+
|
|
189
|
+
### Supported Type Features
|
|
190
|
+
|
|
191
|
+
* Basic types (string, number, boolean)
|
|
192
|
+
* Interfaces & Type Aliases
|
|
193
|
+
* Generic Expansion
|
|
194
|
+
* Third-party library types (automatically handles import paths)
|
|
195
|
+
|
|
196
|
+
## 🔌 API Reference
|
|
197
|
+
|
|
198
|
+
### `defineBridge<TEmit>(name, methods)`
|
|
199
|
+
|
|
200
|
+
* **name**: `string` - Bridge name, determines the generated directory name.
|
|
201
|
+
* **methods**: `Object` - Collection of methods exposed to the child window.
|
|
202
|
+
* **TEmit**: `Generic` - (Optional) Defines the event type mapping for messages sent via `emit` from the parent.
|
|
203
|
+
|
|
204
|
+
Returns an object containing:
|
|
205
|
+
|
|
206
|
+
* `create(iframeEl, allowedOrigins?)`: Initializes the connection and returns an `{ emit }` object.
|
|
207
|
+
|
|
208
|
+
### `defineIframeBridge<TEmit>(name, methods)`
|
|
209
|
+
|
|
210
|
+
* **name**: `string` - Bridge name, determines the generated directory name.
|
|
211
|
+
* **methods**: `Object` - Collection of methods exposed from the iframe to the parent window.
|
|
212
|
+
* **TEmit**: `Generic` - (Optional) Defines the event type mapping for messages sent via `emit` from the iframe.
|
|
213
|
+
|
|
214
|
+
Returns an object containing:
|
|
215
|
+
|
|
216
|
+
* `connect(allowedOrigins?)`: Connects to the parent window from inside the iframe and returns an `{ emit, destroy }` object.
|
|
217
|
+
|
|
218
|
+
### Vite Plugin Options (`IframeBridgeOptions`)
|
|
219
|
+
|
|
220
|
+
| Option | Type | Default | Description |
|
|
221
|
+
|:---|:---|:---|:---|
|
|
222
|
+
| `outDir` | `string` | `'bridges'` | Output directory for generated code. Recommended `'src/bridges'`. |
|
|
223
|
+
| `allowedOrigins` | `string[]` | `['*']` | List of allowed origin domains for communication. |
|
|
224
|
+
| `full` | `boolean` | `true` | Whether to generate code containing full dependencies. |
|
|
225
|
+
| `preserveModules` | `string[]` | `[]` | Preserve imports for specific modules instead of expanding types (e.g., `['vue']`). |
|
|
226
|
+
|
|
227
|
+
### Generated Child API
|
|
228
|
+
|
|
229
|
+
Assuming `outDir` is `src/bridges` and the bridge name is `my-bridge`, you can import from `src/bridges/my-bridge`:
|
|
230
|
+
|
|
231
|
+
* **`default` (ParentApi)**: A proxy object containing all parent methods. All methods return a `Promise`.
|
|
232
|
+
* **`onMessage(type, callback, once?)`**: Listen for events sent by the parent.
|
|
233
|
+
* **`offMessage(type, callback?)`**: Remove an event listener.
|
|
234
|
+
* **`onInit(callback)`**: Triggered when the connection is successfully established.
|
|
235
|
+
* **`isInit()`**: Returns the current connection status.
|
|
236
|
+
|
|
237
|
+
### Generated Parent API
|
|
238
|
+
|
|
239
|
+
When the bridge comes from `defineIframeBridge`, the same generated directory exports a parent-side client:
|
|
240
|
+
|
|
241
|
+
* **`create(iframeEl, allowedOrigins?)`**: Initializes the connection to the iframe and returns a `BridgeConnection`.
|
|
242
|
+
* **`connection.api`**: A strongly-typed proxy for methods exposed by the iframe. All methods return a `Promise`.
|
|
243
|
+
* **`connection.onMessage(type, callback, once?)`**: Listen for events sent by the iframe.
|
|
244
|
+
* **`connection.offMessage(type, callback?)`**: Remove an event listener.
|
|
245
|
+
* **`connection.onInit(callback)`**: Triggered when the connection is successfully established.
|
|
246
|
+
* **`connection.isInit()`**: Returns the current connection status.
|
|
247
|
+
* **`connection.destroy()`**: Disconnects the current connection.
|
|
248
|
+
|
|
249
|
+
## ⚠️ Notes
|
|
250
|
+
|
|
251
|
+
1. **Same-Origin Policy**: While Penpal simplifies postMessage, please ensure `allowedOrigins` is correctly configured for security.
|
|
252
|
+
2. **Build Order**: During production builds, ensure files containing `defineBridge` are correctly processed. Usually, as long as these files are within your source tree (referenced via import), the Vite plugin will scan them.
|
|
253
|
+
3. **JSON Serialization**: Data transmitted across windows must be JSON serializable (Functions, DOM nodes, etc., are not supported).
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
182
257
|
MIT
|
package/dist/full/core.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var R=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var Z=Object.prototype.hasOwnProperty;var ee=(e,t)=>{for(var r in t)R(e,r,{get:t[r],enumerable:!0})},te=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Q(t))!Z.call(e,s)&&s!==r&&R(e,s,{get:()=>t[s],enumerable:!(n=B(t,s))||n.enumerable});return e};var re=e=>te(R({},"__esModule",{value:!0}),e);var Fe={};ee(Fe,{default:()=>De,isInit:()=>ke,offMessage:()=>D,onInit:()=>He,onMessage:()=>be});module.exports=re(Fe);var ne=class extends Error{code;constructor(e,t){super(t),this.name="PenpalError",this.code=e}},u=ne,se=e=>({name:e.name,message:e.message,stack:e.stack,penpalCode:e instanceof u?e.code:void 0}),ae=({name:e,message:t,stack:r,penpalCode:n})=>{let s=n?new u(n,t):new Error(t);return s.name=e,s.stack=r,s},oe=Symbol("Reply"),ie=class{value;transferables;#t=oe;constructor(e,t){this.value=e,this.transferables=t?.transferables}},de=ie,p="penpal",C=e=>typeof e=="object"&&e!==null,V=e=>typeof e=="function",le=e=>C(e)&&e.namespace===p,N=e=>e.type==="SYN",L=e=>e.type==="ACK1",T=e=>e.type==="ACK2",W=e=>e.type==="CALL",Y=e=>e.type==="REPLY",ce=e=>e.type==="DESTROY",j=(e,t=[])=>{let r=[];for(let n of Object.keys(e)){let s=e[n];V(s)?r.push([...t,n]):C(s)&&r.push(...j(s,[...t,n]))}return r},he=(e,t)=>{let r=e.reduce((n,s)=>C(n)?n[s]:void 0,t);return V(r)?r:void 0},g=e=>e.join("."),F=(e,t,r)=>({namespace:p,channel:e,type:"REPLY",callId:t,isError:!0,...r instanceof Error?{value:se(r),isSerializedErrorInstance:!0}:{value:r}}),ue=(e,t,r,n)=>{let s=!1,l=async f=>{if(s||!W(f))return;n?.(`Received ${g(f.methodPath)}() call`,f);let{methodPath:v,args:c,id:o}=f,a,M;try{let d=he(v,t);if(!d)throw new u("METHOD_NOT_FOUND",`Method \`${g(v)}\` is not found.`);let h=await d(...c);h instanceof de&&(M=h.transferables,h=await h.value),a={namespace:p,channel:r,type:"REPLY",callId:o,value:h}}catch(d){a=F(r,o,d)}if(!s)try{n?.(`Sending ${g(v)}() reply`,a),e.sendMessage(a,M)}catch(d){throw d.name==="DataCloneError"&&(a=F(r,o,d),n?.(`Sending ${g(v)}() reply`,a),e.sendMessage(a)),d}};return e.addMessageHandler(l),()=>{s=!0,e.removeMessageHandler(l)}},fe=ue,z=crypto.randomUUID?.bind(crypto)??(()=>new Array(4).fill(0).map(()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16)).join("-")),pe=Symbol("CallOptions"),ve=class{transferables;timeout;#t=pe;constructor(e){this.transferables=e?.transferables,this.timeout=e?.timeout}},Me=ve,ge=new Set(["apply","call","bind"]),K=(e,t,r=[])=>new Proxy(r.length?()=>{}:Object.create(null),{get(n,s){if(s!=="then")return r.length&&ge.has(s)?Reflect.get(n,s):K(e,t,[...r,s])},apply(n,s,l){return e(r,l)}}),x=e=>new u("CONNECTION_DESTROYED",`Method call ${g(e)}() failed due to destroyed connection`),ye=(e,t,r)=>{let n=!1,s=new Map,l=c=>{if(!Y(c))return;let{callId:o,value:a,isError:M,isSerializedErrorInstance:d}=c,h=s.get(o);h&&(s.delete(o),r?.(`Received ${g(h.methodPath)}() call`,c),M?h.reject(d?ae(a):a):h.resolve(a))};return e.addMessageHandler(l),{remoteProxy:K((c,o)=>{if(n)throw x(c);let a=z(),M=o[o.length-1],d=M instanceof Me,{timeout:h,transferables:_}=d?M:{},A=d?o.slice(0,-1):o;return new Promise((S,I)=>{let P=h!==void 0?window.setTimeout(()=>{s.delete(a),I(new u("METHOD_CALL_TIMEOUT",`Method call ${g(c)}() timed out after ${h}ms`))},h):void 0;s.set(a,{methodPath:c,resolve:S,reject:I,timeoutId:P});try{let E={namespace:p,channel:t,type:"CALL",id:a,methodPath:c,args:A};r?.(`Sending ${g(c)}() call`,E),e.sendMessage(E,_)}catch(E){I(new u("TRANSMISSION_FAILED",E.message))}})},r),destroy:()=>{n=!0,e.removeMessageHandler(l);for(let{methodPath:c,reject:o,timeoutId:a}of s.values())clearTimeout(a),o(x(c));s.clear()}}},Ee=ye,Ie=()=>{let e,t;return{promise:new Promise((n,s)=>{e=n,t=s}),resolve:e,reject:t}},we=Ie,O="deprecated-penpal",Ae=e=>C(e)&&"penpal"in e,Se=e=>e.split("."),U=e=>e.join("."),me=e=>{try{return JSON.stringify(e)}catch{return String(e)}},G=e=>new u("TRANSMISSION_FAILED",`Unexpected message to translate: ${me(e)}`),Ne=e=>{if(e.penpal==="syn")return{namespace:p,channel:void 0,type:"SYN",participantId:O};if(e.penpal==="ack")return{namespace:p,channel:void 0,type:"ACK2"};if(e.penpal==="call")return{namespace:p,channel:void 0,type:"CALL",id:e.id,methodPath:Se(e.methodName),args:e.args};if(e.penpal==="reply")return e.resolution==="fulfilled"?{namespace:p,channel:void 0,type:"REPLY",callId:e.id,value:e.returnValue}:{namespace:p,channel:void 0,type:"REPLY",callId:e.id,isError:!0,...e.returnValueIsError?{value:e.returnValue,isSerializedErrorInstance:!0}:{value:e.returnValue}};throw G(e)},Ce=e=>{if(L(e))return{penpal:"synAck",methodNames:e.methodPaths.map(U)};if(W(e))return{penpal:"call",id:e.id,methodName:U(e.methodPath),args:e.args};if(Y(e))return e.isError?{penpal:"reply",id:e.callId,resolution:"rejected",...e.isSerializedErrorInstance?{returnValue:e.value,returnValueIsError:!0}:{returnValue:e.value}}:{penpal:"reply",id:e.callId,resolution:"fulfilled",returnValue:e.value};throw G(e)},_e=({messenger:e,methods:t,timeout:r,channel:n,log:s})=>{let l=z(),f,v=[],c=!1,o=j(t),{promise:a,resolve:M,reject:d}=we(),h=r!==void 0?setTimeout(()=>{d(new u("CONNECTION_TIMEOUT",`Connection timed out after ${r}ms`))},r):void 0,_=()=>{for(let i of v)i()},A=()=>{if(c)return;v.push(fe(e,t,n,s));let{remoteProxy:i,destroy:y}=Ee(e,n,s);v.push(y),clearTimeout(h),c=!0,M({remoteProxy:i,destroy:_})},S=()=>{let i={namespace:p,type:"SYN",channel:n,participantId:l};s?.("Sending handshake SYN",i);try{e.sendMessage(i)}catch(y){d(new u("TRANSMISSION_FAILED",y.message))}},I=i=>{if(s?.("Received handshake SYN",i),i.participantId===f&&f!==O||(f=i.participantId,S(),!(l>f||f===O)))return;let m={namespace:p,channel:n,type:"ACK1",methodPaths:o};s?.("Sending handshake ACK1",m);try{e.sendMessage(m)}catch(q){d(new u("TRANSMISSION_FAILED",q.message));return}},P=i=>{s?.("Received handshake ACK1",i);let y={namespace:p,channel:n,type:"ACK2"};s?.("Sending handshake ACK2",y);try{e.sendMessage(y)}catch(m){d(new u("TRANSMISSION_FAILED",m.message));return}A()},E=i=>{s?.("Received handshake ACK2",i),A()},H=i=>{N(i)&&I(i),L(i)&&P(i),T(i)&&E(i)};return e.addMessageHandler(H),v.push(()=>e.removeMessageHandler(H)),S(),a},Pe=_e,Re=e=>{let t=!1,r;return(...n)=>(t||(t=!0,r=e(...n)),r)},Te=Re,$=new WeakSet,Oe=({messenger:e,methods:t={},timeout:r,channel:n,log:s})=>{if(!e)throw new u("INVALID_ARGUMENT","messenger must be defined");if($.has(e))throw new u("INVALID_ARGUMENT","A messenger can only be used for a single connection");$.add(e);let l=[e.destroy],f=Te(o=>{if(o){let a={namespace:p,channel:n,type:"DESTROY"};try{e.sendMessage(a)}catch{}}for(let a of l)a();s?.("Connection destroyed")}),v=o=>le(o)&&o.channel===n;return{promise:(async()=>{try{e.initialize({log:s,validateReceivedMessage:v}),e.addMessageHandler(M=>{ce(M)&&f(!1)});let{remoteProxy:o,destroy:a}=await Pe({messenger:e,methods:t,timeout:r,channel:n,log:s});return l.push(a),o}catch(o){throw f(!0),o}})(),destroy:()=>{f(!0)}}},J=Oe,Le=class{#t;#s;#r;#i;#a;#n=new Set;#e;#o=!1;constructor({remoteWindow:e,allowedOrigins:t}){if(!e)throw new u("INVALID_ARGUMENT","remoteWindow must be defined");this.#t=e,this.#s=t?.length?t:[window.origin]}initialize=({log:e,validateReceivedMessage:t})=>{this.#r=e,this.#i=t,window.addEventListener("message",this.#h)};sendMessage=(e,t)=>{if(N(e)){let r=this.#d(e);this.#t.postMessage(e,{targetOrigin:r,transfer:t});return}if(L(e)||this.#o){let r=this.#o?Ce(e):e,n=this.#d(e);this.#t.postMessage(r,{targetOrigin:n,transfer:t});return}if(T(e)){let{port1:r,port2:n}=new MessageChannel;this.#e=r,r.addEventListener("message",this.#l),r.start();let s=[n,...t||[]],l=this.#d(e);this.#t.postMessage(e,{targetOrigin:l,transfer:s});return}if(this.#e){this.#e.postMessage(e,{transfer:t});return}throw new u("TRANSMISSION_FAILED","Cannot send message because the MessagePort is not connected")};addMessageHandler=e=>{this.#n.add(e)};removeMessageHandler=e=>{this.#n.delete(e)};destroy=()=>{window.removeEventListener("message",this.#h),this.#c(),this.#n.clear()};#u=e=>this.#s.some(t=>t instanceof RegExp?t.test(e):t===e||t==="*");#d=e=>{if(N(e))return"*";if(!this.#a)throw new u("TRANSMISSION_FAILED","Cannot send message because the remote origin is not established");return this.#a==="null"&&this.#s.includes("*")?"*":this.#a};#c=()=>{this.#e?.removeEventListener("message",this.#l),this.#e?.close(),this.#e=void 0};#h=({source:e,origin:t,ports:r,data:n})=>{if(e===this.#t){if(Ae(n)){this.#r?.("Please upgrade the child window to the latest version of Penpal."),this.#o=!0;try{n=Ne(n)}catch(s){this.#r?.(`Failed to translate deprecated message: ${s.message}`);return}}if(this.#i?.(n)){if(!this.#u(t)){this.#r?.(`Received a message from origin \`${t}\` which did not match allowed origins \`[${this.#s.join(", ")}]\``);return}if(N(n)&&(this.#c(),this.#a=t),T(n)&&!this.#o){if(this.#e=r[0],!this.#e){this.#r?.("Ignoring ACK2 because it did not include a MessagePort");return}this.#e.addEventListener("message",this.#l),this.#e.start()}for(let s of this.#n)s(n)}}};#l=({data:e})=>{if(this.#i?.(e))for(let t of this.#n)t(e)}},X=Le;var w=new Map,b=J({messenger:new X({remoteWindow:window.parent,allowedOrigins:["__AllowedOrigins__"]}),channel:"iframe-bridge-channel",methods:{onMessage(e,t){let r=w.get(e);if(r)return r.forEach(n=>n(t)),!0}}}),De=new Proxy({},{get(e,t){return async(...r)=>await b.promise.then(n=>n[t](...r))}}),be=(e,t,r)=>{let n=r?(l=>{t(l),D(e,n)}):t,s=w.get(e);return s?s.add(n):w.set(e,new Set([n])),()=>D(e,n)},D=(e,t)=>{if(!t){w.delete(e);return}let r=w.get(e);r&&r.delete(t)},k=!1;b.promise.then(()=>{k=!0});var ke=()=>k,He=e=>{if(k){e();return}b.promise.then(()=>{e()})};0&&(module.exports={isInit,offMessage,onInit,onMessage});
|
package/dist/full/core.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var q=class extends Error{code;constructor(e,t){super(t),this.name="PenpalError",this.code=e}},
|
|
1
|
+
var q=class extends Error{code;constructor(e,t){super(t),this.name="PenpalError",this.code=e}},u=q,B=e=>({name:e.name,message:e.message,stack:e.stack,penpalCode:e instanceof u?e.code:void 0}),Q=({name:e,message:t,stack:r,penpalCode:n})=>{let s=n?new u(n,t):new Error(t);return s.name=e,s.stack=r,s},Z=Symbol("Reply"),ee=class{value;transferables;#t=Z;constructor(e,t){this.value=e,this.transferables=t?.transferables}},te=ee,p="penpal",C=e=>typeof e=="object"&&e!==null,U=e=>typeof e=="function",re=e=>C(e)&&e.namespace===p,N=e=>e.type==="SYN",O=e=>e.type==="ACK1",R=e=>e.type==="ACK2",$=e=>e.type==="CALL",V=e=>e.type==="REPLY",ne=e=>e.type==="DESTROY",W=(e,t=[])=>{let r=[];for(let n of Object.keys(e)){let s=e[n];U(s)?r.push([...t,n]):C(s)&&r.push(...W(s,[...t,n]))}return r},se=(e,t)=>{let r=e.reduce((n,s)=>C(n)?n[s]:void 0,t);return U(r)?r:void 0},g=e=>e.join("."),k=(e,t,r)=>({namespace:p,channel:e,type:"REPLY",callId:t,isError:!0,...r instanceof Error?{value:B(r),isSerializedErrorInstance:!0}:{value:r}}),ae=(e,t,r,n)=>{let s=!1,l=async f=>{if(s||!$(f))return;n?.(`Received ${g(f.methodPath)}() call`,f);let{methodPath:v,args:c,id:o}=f,a,M;try{let d=se(v,t);if(!d)throw new u("METHOD_NOT_FOUND",`Method \`${g(v)}\` is not found.`);let h=await d(...c);h instanceof te&&(M=h.transferables,h=await h.value),a={namespace:p,channel:r,type:"REPLY",callId:o,value:h}}catch(d){a=k(r,o,d)}if(!s)try{n?.(`Sending ${g(v)}() reply`,a),e.sendMessage(a,M)}catch(d){throw d.name==="DataCloneError"&&(a=k(r,o,d),n?.(`Sending ${g(v)}() reply`,a),e.sendMessage(a)),d}};return e.addMessageHandler(l),()=>{s=!0,e.removeMessageHandler(l)}},oe=ae,Y=crypto.randomUUID?.bind(crypto)??(()=>new Array(4).fill(0).map(()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16)).join("-")),ie=Symbol("CallOptions"),de=class{transferables;timeout;#t=ie;constructor(e){this.transferables=e?.transferables,this.timeout=e?.timeout}},le=de,ce=new Set(["apply","call","bind"]),j=(e,t,r=[])=>new Proxy(r.length?()=>{}:Object.create(null),{get(n,s){if(s!=="then")return r.length&&ce.has(s)?Reflect.get(n,s):j(e,t,[...r,s])},apply(n,s,l){return e(r,l)}}),H=e=>new u("CONNECTION_DESTROYED",`Method call ${g(e)}() failed due to destroyed connection`),he=(e,t,r)=>{let n=!1,s=new Map,l=c=>{if(!V(c))return;let{callId:o,value:a,isError:M,isSerializedErrorInstance:d}=c,h=s.get(o);h&&(s.delete(o),r?.(`Received ${g(h.methodPath)}() call`,c),M?h.reject(d?Q(a):a):h.resolve(a))};return e.addMessageHandler(l),{remoteProxy:j((c,o)=>{if(n)throw H(c);let a=Y(),M=o[o.length-1],d=M instanceof le,{timeout:h,transferables:_}=d?M:{},A=d?o.slice(0,-1):o;return new Promise((S,I)=>{let P=h!==void 0?window.setTimeout(()=>{s.delete(a),I(new u("METHOD_CALL_TIMEOUT",`Method call ${g(c)}() timed out after ${h}ms`))},h):void 0;s.set(a,{methodPath:c,resolve:S,reject:I,timeoutId:P});try{let E={namespace:p,channel:t,type:"CALL",id:a,methodPath:c,args:A};r?.(`Sending ${g(c)}() call`,E),e.sendMessage(E,_)}catch(E){I(new u("TRANSMISSION_FAILED",E.message))}})},r),destroy:()=>{n=!0,e.removeMessageHandler(l);for(let{methodPath:c,reject:o,timeoutId:a}of s.values())clearTimeout(a),o(H(c));s.clear()}}},ue=he,fe=()=>{let e,t;return{promise:new Promise((n,s)=>{e=n,t=s}),resolve:e,reject:t}},pe=fe,T="deprecated-penpal",ve=e=>C(e)&&"penpal"in e,Me=e=>e.split("."),F=e=>e.join("."),ge=e=>{try{return JSON.stringify(e)}catch{return String(e)}},z=e=>new u("TRANSMISSION_FAILED",`Unexpected message to translate: ${ge(e)}`),ye=e=>{if(e.penpal==="syn")return{namespace:p,channel:void 0,type:"SYN",participantId:T};if(e.penpal==="ack")return{namespace:p,channel:void 0,type:"ACK2"};if(e.penpal==="call")return{namespace:p,channel:void 0,type:"CALL",id:e.id,methodPath:Me(e.methodName),args:e.args};if(e.penpal==="reply")return e.resolution==="fulfilled"?{namespace:p,channel:void 0,type:"REPLY",callId:e.id,value:e.returnValue}:{namespace:p,channel:void 0,type:"REPLY",callId:e.id,isError:!0,...e.returnValueIsError?{value:e.returnValue,isSerializedErrorInstance:!0}:{value:e.returnValue}};throw z(e)},Ee=e=>{if(O(e))return{penpal:"synAck",methodNames:e.methodPaths.map(F)};if($(e))return{penpal:"call",id:e.id,methodName:F(e.methodPath),args:e.args};if(V(e))return e.isError?{penpal:"reply",id:e.callId,resolution:"rejected",...e.isSerializedErrorInstance?{returnValue:e.value,returnValueIsError:!0}:{returnValue:e.value}}:{penpal:"reply",id:e.callId,resolution:"fulfilled",returnValue:e.value};throw z(e)},Ie=({messenger:e,methods:t,timeout:r,channel:n,log:s})=>{let l=Y(),f,v=[],c=!1,o=W(t),{promise:a,resolve:M,reject:d}=pe(),h=r!==void 0?setTimeout(()=>{d(new u("CONNECTION_TIMEOUT",`Connection timed out after ${r}ms`))},r):void 0,_=()=>{for(let i of v)i()},A=()=>{if(c)return;v.push(oe(e,t,n,s));let{remoteProxy:i,destroy:y}=ue(e,n,s);v.push(y),clearTimeout(h),c=!0,M({remoteProxy:i,destroy:_})},S=()=>{let i={namespace:p,type:"SYN",channel:n,participantId:l};s?.("Sending handshake SYN",i);try{e.sendMessage(i)}catch(y){d(new u("TRANSMISSION_FAILED",y.message))}},I=i=>{if(s?.("Received handshake SYN",i),i.participantId===f&&f!==T||(f=i.participantId,S(),!(l>f||f===T)))return;let m={namespace:p,channel:n,type:"ACK1",methodPaths:o};s?.("Sending handshake ACK1",m);try{e.sendMessage(m)}catch(X){d(new u("TRANSMISSION_FAILED",X.message));return}},P=i=>{s?.("Received handshake ACK1",i);let y={namespace:p,channel:n,type:"ACK2"};s?.("Sending handshake ACK2",y);try{e.sendMessage(y)}catch(m){d(new u("TRANSMISSION_FAILED",m.message));return}A()},E=i=>{s?.("Received handshake ACK2",i),A()},b=i=>{N(i)&&I(i),O(i)&&P(i),R(i)&&E(i)};return e.addMessageHandler(b),v.push(()=>e.removeMessageHandler(b)),S(),a},we=Ie,Ae=e=>{let t=!1,r;return(...n)=>(t||(t=!0,r=e(...n)),r)},Se=Ae,x=new WeakSet,me=({messenger:e,methods:t={},timeout:r,channel:n,log:s})=>{if(!e)throw new u("INVALID_ARGUMENT","messenger must be defined");if(x.has(e))throw new u("INVALID_ARGUMENT","A messenger can only be used for a single connection");x.add(e);let l=[e.destroy],f=Se(o=>{if(o){let a={namespace:p,channel:n,type:"DESTROY"};try{e.sendMessage(a)}catch{}}for(let a of l)a();s?.("Connection destroyed")}),v=o=>re(o)&&o.channel===n;return{promise:(async()=>{try{e.initialize({log:s,validateReceivedMessage:v}),e.addMessageHandler(M=>{ne(M)&&f(!1)});let{remoteProxy:o,destroy:a}=await we({messenger:e,methods:t,timeout:r,channel:n,log:s});return l.push(a),o}catch(o){throw f(!0),o}})(),destroy:()=>{f(!0)}}},K=me,Ne=class{#t;#s;#r;#i;#a;#n=new Set;#e;#o=!1;constructor({remoteWindow:e,allowedOrigins:t}){if(!e)throw new u("INVALID_ARGUMENT","remoteWindow must be defined");this.#t=e,this.#s=t?.length?t:[window.origin]}initialize=({log:e,validateReceivedMessage:t})=>{this.#r=e,this.#i=t,window.addEventListener("message",this.#h)};sendMessage=(e,t)=>{if(N(e)){let r=this.#d(e);this.#t.postMessage(e,{targetOrigin:r,transfer:t});return}if(O(e)||this.#o){let r=this.#o?Ee(e):e,n=this.#d(e);this.#t.postMessage(r,{targetOrigin:n,transfer:t});return}if(R(e)){let{port1:r,port2:n}=new MessageChannel;this.#e=r,r.addEventListener("message",this.#l),r.start();let s=[n,...t||[]],l=this.#d(e);this.#t.postMessage(e,{targetOrigin:l,transfer:s});return}if(this.#e){this.#e.postMessage(e,{transfer:t});return}throw new u("TRANSMISSION_FAILED","Cannot send message because the MessagePort is not connected")};addMessageHandler=e=>{this.#n.add(e)};removeMessageHandler=e=>{this.#n.delete(e)};destroy=()=>{window.removeEventListener("message",this.#h),this.#c(),this.#n.clear()};#u=e=>this.#s.some(t=>t instanceof RegExp?t.test(e):t===e||t==="*");#d=e=>{if(N(e))return"*";if(!this.#a)throw new u("TRANSMISSION_FAILED","Cannot send message because the remote origin is not established");return this.#a==="null"&&this.#s.includes("*")?"*":this.#a};#c=()=>{this.#e?.removeEventListener("message",this.#l),this.#e?.close(),this.#e=void 0};#h=({source:e,origin:t,ports:r,data:n})=>{if(e===this.#t){if(ve(n)){this.#r?.("Please upgrade the child window to the latest version of Penpal."),this.#o=!0;try{n=ye(n)}catch(s){this.#r?.(`Failed to translate deprecated message: ${s.message}`);return}}if(this.#i?.(n)){if(!this.#u(t)){this.#r?.(`Received a message from origin \`${t}\` which did not match allowed origins \`[${this.#s.join(", ")}]\``);return}if(N(n)&&(this.#c(),this.#a=t),R(n)&&!this.#o){if(this.#e=r[0],!this.#e){this.#r?.("Ignoring ACK2 because it did not include a MessagePort");return}this.#e.addEventListener("message",this.#l),this.#e.start()}for(let s of this.#n)s(n)}}};#l=({data:e})=>{if(this.#i?.(e))for(let t of this.#n)t(e)}},G=Ne;var w=new Map,L=K({messenger:new G({remoteWindow:window.parent,allowedOrigins:["__AllowedOrigins__"]}),channel:"iframe-bridge-channel",methods:{onMessage(e,t){let r=w.get(e);if(r)return r.forEach(n=>n(t)),!0}}}),Le=new Proxy({},{get(e,t){return async(...r)=>await L.promise.then(n=>n[t](...r))}}),De=(e,t,r)=>{let n=r?(l=>{t(l),J(e,n)}):t,s=w.get(e);return s?s.add(n):w.set(e,new Set([n])),()=>J(e,n)},J=(e,t)=>{if(!t){w.delete(e);return}let r=w.get(e);r&&r.delete(t)},D=!1;L.promise.then(()=>{D=!0});var be=()=>D,ke=e=>{if(D){e();return}L.promise.then(()=>{e()})};export{Le as default,be as isInit,J as offMessage,ke as onInit,De as onMessage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var P=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var B=(e,t)=>{for(var n in t)P(e,n,{get:t[n],enumerable:!0})},Q=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of X(t))!q.call(e,r)&&r!==n&&P(e,r,{get:()=>t[r],enumerable:!(s=J(t,r))||s.enumerable});return e};var Z=e=>Q(P({},"__esModule",{value:!0}),e);var Le={};B(Le,{create:()=>K,default:()=>Oe});module.exports=Z(Le);var ee=class extends Error{code;constructor(e,t){super(t),this.name="PenpalError",this.code=e}},M=ee,te=e=>({name:e.name,message:e.message,stack:e.stack,penpalCode:e instanceof M?e.code:void 0}),re=({name:e,message:t,stack:n,penpalCode:s})=>{let r=s?new M(s,t):new Error(t);return r.name=e,r.stack=n,r},ne=Symbol("Reply"),se=class{value;transferables;#t=ne;constructor(e,t){this.value=e,this.transferables=t?.transferables}},ae=se,v="penpal",N=e=>typeof e=="object"&&e!==null,F=e=>typeof e=="function",oe=e=>N(e)&&e.namespace===v,S=e=>e.type==="SYN",O=e=>e.type==="ACK1",R=e=>e.type==="ACK2",x=e=>e.type==="CALL",U=e=>e.type==="REPLY",ie=e=>e.type==="DESTROY",W=(e,t=[])=>{let n=[];for(let s of Object.keys(e)){let r=e[s];F(r)?n.push([...t,s]):N(r)&&n.push(...W(r,[...t,s]))}return n},de=(e,t)=>{let n=e.reduce((s,r)=>N(s)?s[r]:void 0,t);return F(n)?n:void 0},g=e=>e.join("."),b=(e,t,n)=>({namespace:v,channel:e,type:"REPLY",callId:t,isError:!0,...n instanceof Error?{value:te(n),isSerializedErrorInstance:!0}:{value:n}}),le=(e,t,n,s)=>{let r=!1,o=async i=>{if(r||!x(i))return;s?.(`Received ${g(i.methodPath)}() call`,i);let{methodPath:h,args:l,id:a}=i,d,p;try{let u=de(h,t);if(!u)throw new M("METHOD_NOT_FOUND",`Method \`${g(h)}\` is not found.`);let f=await u(...l);f instanceof ae&&(p=f.transferables,f=await f.value),d={namespace:v,channel:n,type:"REPLY",callId:a,value:f}}catch(u){d=b(n,a,u)}if(!r)try{s?.(`Sending ${g(h)}() reply`,d),e.sendMessage(d,p)}catch(u){throw u.name==="DataCloneError"&&(d=b(n,a,u),s?.(`Sending ${g(h)}() reply`,d),e.sendMessage(d)),u}};return e.addMessageHandler(o),()=>{r=!0,e.removeMessageHandler(o)}},ce=le,$=crypto.randomUUID?.bind(crypto)??(()=>new Array(4).fill(0).map(()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16)).join("-")),he=Symbol("CallOptions"),ue=class{transferables;timeout;#t=he;constructor(e){this.transferables=e?.transferables,this.timeout=e?.timeout}},fe=ue,Me=new Set(["apply","call","bind"]),V=(e,t,n=[])=>new Proxy(n.length?()=>{}:Object.create(null),{get(s,r){if(r!=="then")return n.length&&Me.has(r)?Reflect.get(s,r):V(e,t,[...n,r])},apply(s,r,o){return e(n,o)}}),D=e=>new M("CONNECTION_DESTROYED",`Method call ${g(e)}() failed due to destroyed connection`),pe=(e,t,n)=>{let s=!1,r=new Map,o=l=>{if(!U(l))return;let{callId:a,value:d,isError:p,isSerializedErrorInstance:u}=l,f=r.get(a);f&&(r.delete(a),n?.(`Received ${g(f.methodPath)}() call`,l),p?f.reject(u?re(d):d):f.resolve(d))};return e.addMessageHandler(o),{remoteProxy:V((l,a)=>{if(s)throw D(l);let d=$(),p=a[a.length-1],u=p instanceof fe,{timeout:f,transferables:C}=u?p:{},w=u?a.slice(0,-1):a;return new Promise((m,I)=>{let _=f!==void 0?window.setTimeout(()=>{r.delete(d),I(new M("METHOD_CALL_TIMEOUT",`Method call ${g(l)}() timed out after ${f}ms`))},f):void 0;r.set(d,{methodPath:l,resolve:m,reject:I,timeoutId:_});try{let E={namespace:v,channel:t,type:"CALL",id:d,methodPath:l,args:w};n?.(`Sending ${g(l)}() call`,E),e.sendMessage(E,C)}catch(E){I(new M("TRANSMISSION_FAILED",E.message))}})},n),destroy:()=>{s=!0,e.removeMessageHandler(o);for(let{methodPath:l,reject:a,timeoutId:d}of r.values())clearTimeout(d),a(D(l));r.clear()}}},ve=pe,ge=()=>{let e,t;return{promise:new Promise((s,r)=>{e=s,t=r}),resolve:e,reject:t}},ye=ge,T="deprecated-penpal",Ee=e=>N(e)&&"penpal"in e,Ie=e=>e.split("."),k=e=>e.join("."),we=e=>{try{return JSON.stringify(e)}catch{return String(e)}},Y=e=>new M("TRANSMISSION_FAILED",`Unexpected message to translate: ${we(e)}`),me=e=>{if(e.penpal==="syn")return{namespace:v,channel:void 0,type:"SYN",participantId:T};if(e.penpal==="ack")return{namespace:v,channel:void 0,type:"ACK2"};if(e.penpal==="call")return{namespace:v,channel:void 0,type:"CALL",id:e.id,methodPath:Ie(e.methodName),args:e.args};if(e.penpal==="reply")return e.resolution==="fulfilled"?{namespace:v,channel:void 0,type:"REPLY",callId:e.id,value:e.returnValue}:{namespace:v,channel:void 0,type:"REPLY",callId:e.id,isError:!0,...e.returnValueIsError?{value:e.returnValue,isSerializedErrorInstance:!0}:{value:e.returnValue}};throw Y(e)},Ae=e=>{if(O(e))return{penpal:"synAck",methodNames:e.methodPaths.map(k)};if(x(e))return{penpal:"call",id:e.id,methodName:k(e.methodPath),args:e.args};if(U(e))return e.isError?{penpal:"reply",id:e.callId,resolution:"rejected",...e.isSerializedErrorInstance?{returnValue:e.value,returnValueIsError:!0}:{returnValue:e.value}}:{penpal:"reply",id:e.callId,resolution:"fulfilled",returnValue:e.value};throw Y(e)},Se=({messenger:e,methods:t,timeout:n,channel:s,log:r})=>{let o=$(),i,h=[],l=!1,a=W(t),{promise:d,resolve:p,reject:u}=ye(),f=n!==void 0?setTimeout(()=>{u(new M("CONNECTION_TIMEOUT",`Connection timed out after ${n}ms`))},n):void 0,C=()=>{for(let c of h)c()},w=()=>{if(l)return;h.push(ce(e,t,s,r));let{remoteProxy:c,destroy:y}=ve(e,s,r);h.push(y),clearTimeout(f),l=!0,p({remoteProxy:c,destroy:C})},m=()=>{let c={namespace:v,type:"SYN",channel:s,participantId:o};r?.("Sending handshake SYN",c);try{e.sendMessage(c)}catch(y){u(new M("TRANSMISSION_FAILED",y.message))}},I=c=>{if(r?.("Received handshake SYN",c),c.participantId===i&&i!==T||(i=c.participantId,m(),!(o>i||i===T)))return;let A={namespace:v,channel:s,type:"ACK1",methodPaths:a};r?.("Sending handshake ACK1",A);try{e.sendMessage(A)}catch(G){u(new M("TRANSMISSION_FAILED",G.message));return}},_=c=>{r?.("Received handshake ACK1",c);let y={namespace:v,channel:s,type:"ACK2"};r?.("Sending handshake ACK2",y);try{e.sendMessage(y)}catch(A){u(new M("TRANSMISSION_FAILED",A.message));return}w()},E=c=>{r?.("Received handshake ACK2",c),w()},L=c=>{S(c)&&I(c),O(c)&&_(c),R(c)&&E(c)};return e.addMessageHandler(L),h.push(()=>e.removeMessageHandler(L)),m(),d},Ne=Se,Ce=e=>{let t=!1,n;return(...s)=>(t||(t=!0,n=e(...s)),n)},_e=Ce,H=new WeakSet,Pe=({messenger:e,methods:t={},timeout:n,channel:s,log:r})=>{if(!e)throw new M("INVALID_ARGUMENT","messenger must be defined");if(H.has(e))throw new M("INVALID_ARGUMENT","A messenger can only be used for a single connection");H.add(e);let o=[e.destroy],i=_e(a=>{if(a){let d={namespace:v,channel:s,type:"DESTROY"};try{e.sendMessage(d)}catch{}}for(let d of o)d();r?.("Connection destroyed")}),h=a=>oe(a)&&a.channel===s;return{promise:(async()=>{try{e.initialize({log:r,validateReceivedMessage:h}),e.addMessageHandler(p=>{ie(p)&&i(!1)});let{remoteProxy:a,destroy:d}=await Ne({messenger:e,methods:t,timeout:n,channel:s,log:r});return o.push(d),a}catch(a){throw i(!0),a}})(),destroy:()=>{i(!0)}}},j=Pe,Re=class{#t;#s;#r;#i;#a;#n=new Set;#e;#o=!1;constructor({remoteWindow:e,allowedOrigins:t}){if(!e)throw new M("INVALID_ARGUMENT","remoteWindow must be defined");this.#t=e,this.#s=t?.length?t:[window.origin]}initialize=({log:e,validateReceivedMessage:t})=>{this.#r=e,this.#i=t,window.addEventListener("message",this.#h)};sendMessage=(e,t)=>{if(S(e)){let n=this.#d(e);this.#t.postMessage(e,{targetOrigin:n,transfer:t});return}if(O(e)||this.#o){let n=this.#o?Ae(e):e,s=this.#d(e);this.#t.postMessage(n,{targetOrigin:s,transfer:t});return}if(R(e)){let{port1:n,port2:s}=new MessageChannel;this.#e=n,n.addEventListener("message",this.#l),n.start();let r=[s,...t||[]],o=this.#d(e);this.#t.postMessage(e,{targetOrigin:o,transfer:r});return}if(this.#e){this.#e.postMessage(e,{transfer:t});return}throw new M("TRANSMISSION_FAILED","Cannot send message because the MessagePort is not connected")};addMessageHandler=e=>{this.#n.add(e)};removeMessageHandler=e=>{this.#n.delete(e)};destroy=()=>{window.removeEventListener("message",this.#h),this.#c(),this.#n.clear()};#u=e=>this.#s.some(t=>t instanceof RegExp?t.test(e):t===e||t==="*");#d=e=>{if(S(e))return"*";if(!this.#a)throw new M("TRANSMISSION_FAILED","Cannot send message because the remote origin is not established");return this.#a==="null"&&this.#s.includes("*")?"*":this.#a};#c=()=>{this.#e?.removeEventListener("message",this.#l),this.#e?.close(),this.#e=void 0};#h=({source:e,origin:t,ports:n,data:s})=>{if(e===this.#t){if(Ee(s)){this.#r?.("Please upgrade the child window to the latest version of Penpal."),this.#o=!0;try{s=me(s)}catch(r){this.#r?.(`Failed to translate deprecated message: ${r.message}`);return}}if(this.#i?.(s)){if(!this.#u(t)){this.#r?.(`Received a message from origin \`${t}\` which did not match allowed origins \`[${this.#s.join(", ")}]\``);return}if(S(s)&&(this.#c(),this.#a=t),R(s)&&!this.#o){if(this.#e=n[0],!this.#e){this.#r?.("Ignoring ACK2 because it did not include a MessagePort");return}this.#e.addEventListener("message",this.#l),this.#e.start()}for(let r of this.#n)r(s)}}};#l=({data:e})=>{if(this.#i?.(e))for(let t of this.#n)t(e)}},z=Re;var Te=()=>{let e=new Map,t=(r,o,i)=>{let h=i?(a=>{o(a),n(r,h)}):o,l=e.get(r);return l?l.add(h):e.set(r,new Set([h])),()=>n(r,h)},n=(r,o)=>{if(!o){e.delete(r);return}let i=e.get(r);i&&i.delete(o)};return{onMessage:t,offMessage:n,emitMessage:(r,o)=>{let i=e.get(r);if(i)return i.forEach(h=>h(o)),!0}}},K=(e,t=["__AllowedOrigins__"])=>{if(!e.contentWindow)throw new Error("iframe contentWindow is null");let{onMessage:n,offMessage:s,emitMessage:r}=Te(),o=j({messenger:new z({remoteWindow:e.contentWindow,allowedOrigins:t}),channel:"iframe-bridge-channel",methods:{onMessage(l,a){return r(l,a)}}}),i=!1;return o.promise.then(()=>{i=!0}),{api:new Proxy({},{get(l,a){return async(...d)=>await o.promise.then(p=>p[a](...d))}}),onMessage:n,offMessage:s,isInit:()=>i,onInit(l){if(i){l();return}o.promise.then(()=>{l()})},destroy:o.destroy}},Oe={create:K};0&&(module.exports={create});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var K=class extends Error{code;constructor(e,r){super(r),this.name="PenpalError",this.code=e}},M=K,G=e=>({name:e.name,message:e.message,stack:e.stack,penpalCode:e instanceof M?e.code:void 0}),J=({name:e,message:r,stack:n,penpalCode:s})=>{let t=s?new M(s,r):new Error(r);return t.name=e,t.stack=n,t},X=Symbol("Reply"),q=class{value;transferables;#t=X;constructor(e,r){this.value=e,this.transferables=r?.transferables}},B=q,v="penpal",N=e=>typeof e=="object"&&e!==null,H=e=>typeof e=="function",Q=e=>N(e)&&e.namespace===v,S=e=>e.type==="SYN",T=e=>e.type==="ACK1",P=e=>e.type==="ACK2",F=e=>e.type==="CALL",x=e=>e.type==="REPLY",Z=e=>e.type==="DESTROY",U=(e,r=[])=>{let n=[];for(let s of Object.keys(e)){let t=e[s];H(t)?n.push([...r,s]):N(t)&&n.push(...U(t,[...r,s]))}return n},ee=(e,r)=>{let n=e.reduce((s,t)=>N(s)?s[t]:void 0,r);return H(n)?n:void 0},g=e=>e.join("."),L=(e,r,n)=>({namespace:v,channel:e,type:"REPLY",callId:r,isError:!0,...n instanceof Error?{value:G(n),isSerializedErrorInstance:!0}:{value:n}}),te=(e,r,n,s)=>{let t=!1,o=async i=>{if(t||!F(i))return;s?.(`Received ${g(i.methodPath)}() call`,i);let{methodPath:h,args:l,id:a}=i,d,p;try{let u=ee(h,r);if(!u)throw new M("METHOD_NOT_FOUND",`Method \`${g(h)}\` is not found.`);let f=await u(...l);f instanceof B&&(p=f.transferables,f=await f.value),d={namespace:v,channel:n,type:"REPLY",callId:a,value:f}}catch(u){d=L(n,a,u)}if(!t)try{s?.(`Sending ${g(h)}() reply`,d),e.sendMessage(d,p)}catch(u){throw u.name==="DataCloneError"&&(d=L(n,a,u),s?.(`Sending ${g(h)}() reply`,d),e.sendMessage(d)),u}};return e.addMessageHandler(o),()=>{t=!0,e.removeMessageHandler(o)}},re=te,W=crypto.randomUUID?.bind(crypto)??(()=>new Array(4).fill(0).map(()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16)).join("-")),ne=Symbol("CallOptions"),se=class{transferables;timeout;#t=ne;constructor(e){this.transferables=e?.transferables,this.timeout=e?.timeout}},ae=se,oe=new Set(["apply","call","bind"]),$=(e,r,n=[])=>new Proxy(n.length?()=>{}:Object.create(null),{get(s,t){if(t!=="then")return n.length&&oe.has(t)?Reflect.get(s,t):$(e,r,[...n,t])},apply(s,t,o){return e(n,o)}}),b=e=>new M("CONNECTION_DESTROYED",`Method call ${g(e)}() failed due to destroyed connection`),ie=(e,r,n)=>{let s=!1,t=new Map,o=l=>{if(!x(l))return;let{callId:a,value:d,isError:p,isSerializedErrorInstance:u}=l,f=t.get(a);f&&(t.delete(a),n?.(`Received ${g(f.methodPath)}() call`,l),p?f.reject(u?J(d):d):f.resolve(d))};return e.addMessageHandler(o),{remoteProxy:$((l,a)=>{if(s)throw b(l);let d=W(),p=a[a.length-1],u=p instanceof ae,{timeout:f,transferables:C}=u?p:{},w=u?a.slice(0,-1):a;return new Promise((m,I)=>{let _=f!==void 0?window.setTimeout(()=>{t.delete(d),I(new M("METHOD_CALL_TIMEOUT",`Method call ${g(l)}() timed out after ${f}ms`))},f):void 0;t.set(d,{methodPath:l,resolve:m,reject:I,timeoutId:_});try{let E={namespace:v,channel:r,type:"CALL",id:d,methodPath:l,args:w};n?.(`Sending ${g(l)}() call`,E),e.sendMessage(E,C)}catch(E){I(new M("TRANSMISSION_FAILED",E.message))}})},n),destroy:()=>{s=!0,e.removeMessageHandler(o);for(let{methodPath:l,reject:a,timeoutId:d}of t.values())clearTimeout(d),a(b(l));t.clear()}}},de=ie,le=()=>{let e,r;return{promise:new Promise((s,t)=>{e=s,r=t}),resolve:e,reject:r}},ce=le,R="deprecated-penpal",he=e=>N(e)&&"penpal"in e,ue=e=>e.split("."),D=e=>e.join("."),fe=e=>{try{return JSON.stringify(e)}catch{return String(e)}},V=e=>new M("TRANSMISSION_FAILED",`Unexpected message to translate: ${fe(e)}`),Me=e=>{if(e.penpal==="syn")return{namespace:v,channel:void 0,type:"SYN",participantId:R};if(e.penpal==="ack")return{namespace:v,channel:void 0,type:"ACK2"};if(e.penpal==="call")return{namespace:v,channel:void 0,type:"CALL",id:e.id,methodPath:ue(e.methodName),args:e.args};if(e.penpal==="reply")return e.resolution==="fulfilled"?{namespace:v,channel:void 0,type:"REPLY",callId:e.id,value:e.returnValue}:{namespace:v,channel:void 0,type:"REPLY",callId:e.id,isError:!0,...e.returnValueIsError?{value:e.returnValue,isSerializedErrorInstance:!0}:{value:e.returnValue}};throw V(e)},pe=e=>{if(T(e))return{penpal:"synAck",methodNames:e.methodPaths.map(D)};if(F(e))return{penpal:"call",id:e.id,methodName:D(e.methodPath),args:e.args};if(x(e))return e.isError?{penpal:"reply",id:e.callId,resolution:"rejected",...e.isSerializedErrorInstance?{returnValue:e.value,returnValueIsError:!0}:{returnValue:e.value}}:{penpal:"reply",id:e.callId,resolution:"fulfilled",returnValue:e.value};throw V(e)},ve=({messenger:e,methods:r,timeout:n,channel:s,log:t})=>{let o=W(),i,h=[],l=!1,a=U(r),{promise:d,resolve:p,reject:u}=ce(),f=n!==void 0?setTimeout(()=>{u(new M("CONNECTION_TIMEOUT",`Connection timed out after ${n}ms`))},n):void 0,C=()=>{for(let c of h)c()},w=()=>{if(l)return;h.push(re(e,r,s,t));let{remoteProxy:c,destroy:y}=de(e,s,t);h.push(y),clearTimeout(f),l=!0,p({remoteProxy:c,destroy:C})},m=()=>{let c={namespace:v,type:"SYN",channel:s,participantId:o};t?.("Sending handshake SYN",c);try{e.sendMessage(c)}catch(y){u(new M("TRANSMISSION_FAILED",y.message))}},I=c=>{if(t?.("Received handshake SYN",c),c.participantId===i&&i!==R||(i=c.participantId,m(),!(o>i||i===R)))return;let A={namespace:v,channel:s,type:"ACK1",methodPaths:a};t?.("Sending handshake ACK1",A);try{e.sendMessage(A)}catch(z){u(new M("TRANSMISSION_FAILED",z.message));return}},_=c=>{t?.("Received handshake ACK1",c);let y={namespace:v,channel:s,type:"ACK2"};t?.("Sending handshake ACK2",y);try{e.sendMessage(y)}catch(A){u(new M("TRANSMISSION_FAILED",A.message));return}w()},E=c=>{t?.("Received handshake ACK2",c),w()},O=c=>{S(c)&&I(c),T(c)&&_(c),P(c)&&E(c)};return e.addMessageHandler(O),h.push(()=>e.removeMessageHandler(O)),m(),d},ge=ve,ye=e=>{let r=!1,n;return(...s)=>(r||(r=!0,n=e(...s)),n)},Ee=ye,k=new WeakSet,Ie=({messenger:e,methods:r={},timeout:n,channel:s,log:t})=>{if(!e)throw new M("INVALID_ARGUMENT","messenger must be defined");if(k.has(e))throw new M("INVALID_ARGUMENT","A messenger can only be used for a single connection");k.add(e);let o=[e.destroy],i=Ee(a=>{if(a){let d={namespace:v,channel:s,type:"DESTROY"};try{e.sendMessage(d)}catch{}}for(let d of o)d();t?.("Connection destroyed")}),h=a=>Q(a)&&a.channel===s;return{promise:(async()=>{try{e.initialize({log:t,validateReceivedMessage:h}),e.addMessageHandler(p=>{Z(p)&&i(!1)});let{remoteProxy:a,destroy:d}=await ge({messenger:e,methods:r,timeout:n,channel:s,log:t});return o.push(d),a}catch(a){throw i(!0),a}})(),destroy:()=>{i(!0)}}},Y=Ie,we=class{#t;#s;#r;#i;#a;#n=new Set;#e;#o=!1;constructor({remoteWindow:e,allowedOrigins:r}){if(!e)throw new M("INVALID_ARGUMENT","remoteWindow must be defined");this.#t=e,this.#s=r?.length?r:[window.origin]}initialize=({log:e,validateReceivedMessage:r})=>{this.#r=e,this.#i=r,window.addEventListener("message",this.#h)};sendMessage=(e,r)=>{if(S(e)){let n=this.#d(e);this.#t.postMessage(e,{targetOrigin:n,transfer:r});return}if(T(e)||this.#o){let n=this.#o?pe(e):e,s=this.#d(e);this.#t.postMessage(n,{targetOrigin:s,transfer:r});return}if(P(e)){let{port1:n,port2:s}=new MessageChannel;this.#e=n,n.addEventListener("message",this.#l),n.start();let t=[s,...r||[]],o=this.#d(e);this.#t.postMessage(e,{targetOrigin:o,transfer:t});return}if(this.#e){this.#e.postMessage(e,{transfer:r});return}throw new M("TRANSMISSION_FAILED","Cannot send message because the MessagePort is not connected")};addMessageHandler=e=>{this.#n.add(e)};removeMessageHandler=e=>{this.#n.delete(e)};destroy=()=>{window.removeEventListener("message",this.#h),this.#c(),this.#n.clear()};#u=e=>this.#s.some(r=>r instanceof RegExp?r.test(e):r===e||r==="*");#d=e=>{if(S(e))return"*";if(!this.#a)throw new M("TRANSMISSION_FAILED","Cannot send message because the remote origin is not established");return this.#a==="null"&&this.#s.includes("*")?"*":this.#a};#c=()=>{this.#e?.removeEventListener("message",this.#l),this.#e?.close(),this.#e=void 0};#h=({source:e,origin:r,ports:n,data:s})=>{if(e===this.#t){if(he(s)){this.#r?.("Please upgrade the child window to the latest version of Penpal."),this.#o=!0;try{s=Me(s)}catch(t){this.#r?.(`Failed to translate deprecated message: ${t.message}`);return}}if(this.#i?.(s)){if(!this.#u(r)){this.#r?.(`Received a message from origin \`${r}\` which did not match allowed origins \`[${this.#s.join(", ")}]\``);return}if(S(s)&&(this.#c(),this.#a=r),P(s)&&!this.#o){if(this.#e=n[0],!this.#e){this.#r?.("Ignoring ACK2 because it did not include a MessagePort");return}this.#e.addEventListener("message",this.#l),this.#e.start()}for(let t of this.#n)t(s)}}};#l=({data:e})=>{if(this.#i?.(e))for(let r of this.#n)r(e)}},j=we;var me=()=>{let e=new Map,r=(t,o,i)=>{let h=i?(a=>{o(a),n(t,h)}):o,l=e.get(t);return l?l.add(h):e.set(t,new Set([h])),()=>n(t,h)},n=(t,o)=>{if(!o){e.delete(t);return}let i=e.get(t);i&&i.delete(o)};return{onMessage:r,offMessage:n,emitMessage:(t,o)=>{let i=e.get(t);if(i)return i.forEach(h=>h(o)),!0}}},Ae=(e,r=["__AllowedOrigins__"])=>{if(!e.contentWindow)throw new Error("iframe contentWindow is null");let{onMessage:n,offMessage:s,emitMessage:t}=me(),o=Y({messenger:new j({remoteWindow:e.contentWindow,allowedOrigins:r}),channel:"iframe-bridge-channel",methods:{onMessage(l,a){return t(l,a)}}}),i=!1;return o.promise.then(()=>{i=!0}),{api:new Proxy({},{get(l,a){return async(...d)=>await o.promise.then(p=>p[a](...d))}}),onMessage:n,offMessage:s,isInit:()=>i,onInit(l){if(i){l();return}o.promise.then(()=>{l()})},destroy:o.destroy}},Te={create:Ae};export{Ae as create,Te as default};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type BridgeEventArgs<T> = [T] extends [void] ? [data?: T] : [data: T];
|
|
2
|
+
type BridgeMethods = Record<string, (...args: any[]) => any>;
|
|
3
|
+
/** 在父窗口定义 bridge,暴露方法给 iframe 调用 */
|
|
4
|
+
declare const defineBridge: <TEmit extends object = Record<string, unknown>>(name: string, methods: BridgeMethods) => {
|
|
3
5
|
create(iframe: HTMLIFrameElement, allowedOrigins?: string[]): Promise<{
|
|
4
|
-
emit<T extends keyof TEmit>(type: T,
|
|
6
|
+
emit<T extends keyof TEmit>(type: T, ...args: BridgeEventArgs<TEmit[T]>): void;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
9
|
+
/** 在 iframe 中定义 bridge,暴露方法给父窗口调用 */
|
|
10
|
+
declare const defineIframeBridge: <TEmit extends object = Record<string, unknown>>(name: string, methods: BridgeMethods) => {
|
|
11
|
+
connect(allowedOrigins?: string[]): Promise<{
|
|
12
|
+
emit<T extends keyof TEmit>(type: T, ...args: BridgeEventArgs<TEmit[T]>): void;
|
|
13
|
+
destroy: () => void;
|
|
5
14
|
}>;
|
|
6
15
|
};
|
|
7
16
|
|
|
8
|
-
export { defineBridge };
|
|
17
|
+
export { defineBridge, defineIframeBridge };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type BridgeEventArgs<T> = [T] extends [void] ? [data?: T] : [data: T];
|
|
2
|
+
type BridgeMethods = Record<string, (...args: any[]) => any>;
|
|
3
|
+
/** 在父窗口定义 bridge,暴露方法给 iframe 调用 */
|
|
4
|
+
declare const defineBridge: <TEmit extends object = Record<string, unknown>>(name: string, methods: BridgeMethods) => {
|
|
3
5
|
create(iframe: HTMLIFrameElement, allowedOrigins?: string[]): Promise<{
|
|
4
|
-
emit<T extends keyof TEmit>(type: T,
|
|
6
|
+
emit<T extends keyof TEmit>(type: T, ...args: BridgeEventArgs<TEmit[T]>): void;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
9
|
+
/** 在 iframe 中定义 bridge,暴露方法给父窗口调用 */
|
|
10
|
+
declare const defineIframeBridge: <TEmit extends object = Record<string, unknown>>(name: string, methods: BridgeMethods) => {
|
|
11
|
+
connect(allowedOrigins?: string[]): Promise<{
|
|
12
|
+
emit<T extends keyof TEmit>(type: T, ...args: BridgeEventArgs<TEmit[T]>): void;
|
|
13
|
+
destroy: () => void;
|
|
5
14
|
}>;
|
|
6
15
|
};
|
|
7
16
|
|
|
8
|
-
export { defineBridge };
|
|
17
|
+
export { defineBridge, defineIframeBridge };
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
defineBridge: () => defineBridge
|
|
23
|
+
defineBridge: () => defineBridge,
|
|
24
|
+
defineIframeBridge: () => defineIframeBridge
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(index_exports);
|
|
26
27
|
var import_penpal = require("penpal");
|
|
@@ -40,14 +41,38 @@ var defineBridge = (name, methods) => {
|
|
|
40
41
|
});
|
|
41
42
|
const remote = await conn.promise;
|
|
42
43
|
return {
|
|
43
|
-
emit(type,
|
|
44
|
+
emit(type, ...args) {
|
|
45
|
+
const [data] = args;
|
|
44
46
|
remote.onMessage(type, data);
|
|
45
47
|
}
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
};
|
|
49
51
|
};
|
|
52
|
+
var defineIframeBridge = (name, methods) => {
|
|
53
|
+
return {
|
|
54
|
+
async connect(allowedOrigins = ["*"]) {
|
|
55
|
+
const conn = (0, import_penpal.connect)({
|
|
56
|
+
messenger: new import_penpal.WindowMessenger({
|
|
57
|
+
remoteWindow: window.parent,
|
|
58
|
+
allowedOrigins
|
|
59
|
+
}),
|
|
60
|
+
channel: "iframe-bridge-channel",
|
|
61
|
+
methods
|
|
62
|
+
});
|
|
63
|
+
const remote = await conn.promise;
|
|
64
|
+
return {
|
|
65
|
+
emit(type, ...args) {
|
|
66
|
+
const [data] = args;
|
|
67
|
+
remote.onMessage(type, data);
|
|
68
|
+
},
|
|
69
|
+
destroy: conn.destroy
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
};
|
|
50
74
|
// Annotate the CommonJS export names for ESM import in node:
|
|
51
75
|
0 && (module.exports = {
|
|
52
|
-
defineBridge
|
|
76
|
+
defineBridge,
|
|
77
|
+
defineIframeBridge
|
|
53
78
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -16,13 +16,37 @@ var defineBridge = (name, methods) => {
|
|
|
16
16
|
});
|
|
17
17
|
const remote = await conn.promise;
|
|
18
18
|
return {
|
|
19
|
-
emit(type,
|
|
19
|
+
emit(type, ...args) {
|
|
20
|
+
const [data] = args;
|
|
20
21
|
remote.onMessage(type, data);
|
|
21
22
|
}
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
25
|
};
|
|
25
26
|
};
|
|
27
|
+
var defineIframeBridge = (name, methods) => {
|
|
28
|
+
return {
|
|
29
|
+
async connect(allowedOrigins = ["*"]) {
|
|
30
|
+
const conn = connect({
|
|
31
|
+
messenger: new WindowMessenger({
|
|
32
|
+
remoteWindow: window.parent,
|
|
33
|
+
allowedOrigins
|
|
34
|
+
}),
|
|
35
|
+
channel: "iframe-bridge-channel",
|
|
36
|
+
methods
|
|
37
|
+
});
|
|
38
|
+
const remote = await conn.promise;
|
|
39
|
+
return {
|
|
40
|
+
emit(type, ...args) {
|
|
41
|
+
const [data] = args;
|
|
42
|
+
remote.onMessage(type, data);
|
|
43
|
+
},
|
|
44
|
+
destroy: conn.destroy
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
};
|
|
26
49
|
export {
|
|
27
|
-
defineBridge
|
|
50
|
+
defineBridge,
|
|
51
|
+
defineIframeBridge
|
|
28
52
|
};
|
package/dist/mini/core.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var a=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var m=(n,e)=>{for(var t in e)a(n,t,{get:e[t],enumerable:!0})},h=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of u(e))!w.call(n,s)&&s!==t&&a(n,s,{get:()=>e[s],enumerable:!(o=l(e,s))||o.enumerable});return n};var p=n=>h(a({},"__esModule",{value:!0}),n);var I={};m(I,{default:()=>x,isInit:()=>_,offMessage:()=>c,onInit:()=>F,onMessage:()=>M});module.exports=p(I);var i=require("penpal"),r=new Map,g=(0,i.connect)({messenger:new i.WindowMessenger({remoteWindow:window.parent,allowedOrigins:["__AllowedOrigins__"]}),channel:"iframe-bridge-channel",methods:{onMessage(n,e){let t=r.get(n);if(t)return t.forEach(o=>o(e)),!0}}}),x=new Proxy({},{get(n,e){return async
|
|
1
|
+
"use strict";var a=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var u=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var m=(n,e)=>{for(var t in e)a(n,t,{get:e[t],enumerable:!0})},h=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of u(e))!w.call(n,s)&&s!==t&&a(n,s,{get:()=>e[s],enumerable:!(o=l(e,s))||o.enumerable});return n};var p=n=>h(a({},"__esModule",{value:!0}),n);var I={};m(I,{default:()=>x,isInit:()=>_,offMessage:()=>c,onInit:()=>F,onMessage:()=>M});module.exports=p(I);var i=require("penpal"),r=new Map,g=(0,i.connect)({messenger:new i.WindowMessenger({remoteWindow:window.parent,allowedOrigins:["__AllowedOrigins__"]}),channel:"iframe-bridge-channel",methods:{onMessage(n,e){let t=r.get(n);if(t)return t.forEach(o=>o(e)),!0}}}),x=new Proxy({},{get(n,e){return async(...t)=>await g.promise.then(o=>o[e](...t))}}),M=(n,e,t)=>{let o=t?(d=>{e(d),c(n,o)}):e,s=r.get(n);return s?s.add(o):r.set(n,new Set([o])),()=>c(n,o)},c=(n,e)=>{if(!e){r.delete(n);return}let t=r.get(n);t&&t.delete(e)},f=!1;g.promise.then(()=>{f=!0});var _=()=>f,F=n=>{if(f){n();return}g.promise.then(()=>{n()})};0&&(module.exports={isInit,offMessage,onInit,onMessage});
|
package/dist/mini/core.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{WindowMessenger as f,connect as d}from"penpal";var s=new Map,r=d({messenger:new f({remoteWindow:window.parent,allowedOrigins:["__AllowedOrigins__"]}),channel:"iframe-bridge-channel",methods:{onMessage(n,e){let t=s.get(n);if(t)return t.forEach(o=>o(e)),!0}}}),u=new Proxy({},{get(n,e){return async
|
|
1
|
+
import{WindowMessenger as f,connect as d}from"penpal";var s=new Map,r=d({messenger:new f({remoteWindow:window.parent,allowedOrigins:["__AllowedOrigins__"]}),channel:"iframe-bridge-channel",methods:{onMessage(n,e){let t=s.get(n);if(t)return t.forEach(o=>o(e)),!0}}}),u=new Proxy({},{get(n,e){return async(...t)=>await r.promise.then(o=>o[e](...t))}}),w=(n,e,t)=>{let o=t?(g=>{e(g),c(n,o)}):e,a=s.get(n);return a?a.add(o):s.set(n,new Set([o])),()=>c(n,o)},c=(n,e)=>{if(!e){s.delete(n);return}let t=s.get(n);t&&t.delete(e)},i=!1;r.promise.then(()=>{i=!0});var m=()=>i,h=n=>{if(i){n();return}r.promise.then(()=>{n()})};export{u as default,m as isInit,c as offMessage,h as onInit,w as onMessage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var u=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var p=(n,t)=>{for(var r in t)u(n,r,{get:t[r],enumerable:!0})},y=(n,t,r,c)=>{if(t&&typeof t=="object"||typeof t=="function")for(let e of M(t))!h.call(n,e)&&e!==r&&u(n,e,{get:()=>t[e],enumerable:!(c=w(t,e))||c.enumerable});return n};var F=n=>y(u({},"__esModule",{value:!0}),n);var x={};p(x,{create:()=>d,default:()=>_});module.exports=F(x);var f=require("penpal"),W=()=>{let n=new Map,t=(e,s,o)=>{let a=o?(g=>{s(g),r(e,a)}):s,i=n.get(e);return i?i.add(a):n.set(e,new Set([a])),()=>r(e,a)},r=(e,s)=>{if(!s){n.delete(e);return}let o=n.get(e);o&&o.delete(s)};return{onMessage:t,offMessage:r,emitMessage:(e,s)=>{let o=n.get(e);if(o)return o.forEach(a=>a(s)),!0}}},d=(n,t=["__AllowedOrigins__"])=>{if(!n.contentWindow)throw new Error("iframe contentWindow is null");let{onMessage:r,offMessage:c,emitMessage:e}=W(),s=(0,f.connect)({messenger:new f.WindowMessenger({remoteWindow:n.contentWindow,allowedOrigins:t}),channel:"iframe-bridge-channel",methods:{onMessage(i,g){return e(i,g)}}}),o=!1;return s.promise.then(()=>{o=!0}),{api:new Proxy({},{get(i,g){return async(...m)=>await s.promise.then(l=>l[g](...m))}}),onMessage:r,offMessage:c,isInit:()=>o,onInit(i){if(o){i();return}s.promise.then(()=>{i()})},destroy:s.destroy}},_={create:d};0&&(module.exports={create});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{WindowMessenger as d,connect as m}from"penpal";var l=()=>{let s=new Map,c=(n,e,t)=>{let r=t?(a=>{e(a),i(n,r)}):e,o=s.get(n);return o?o.add(r):s.set(n,new Set([r])),()=>i(n,r)},i=(n,e)=>{if(!e){s.delete(n);return}let t=s.get(n);t&&t.delete(e)};return{onMessage:c,offMessage:i,emitMessage:(n,e)=>{let t=s.get(n);if(t)return t.forEach(r=>r(e)),!0}}},w=(s,c=["__AllowedOrigins__"])=>{if(!s.contentWindow)throw new Error("iframe contentWindow is null");let{onMessage:i,offMessage:g,emitMessage:n}=l(),e=m({messenger:new d({remoteWindow:s.contentWindow,allowedOrigins:c}),channel:"iframe-bridge-channel",methods:{onMessage(o,a){return n(o,a)}}}),t=!1;return e.promise.then(()=>{t=!0}),{api:new Proxy({},{get(o,a){return async(...f)=>await e.promise.then(u=>u[a](...f))}}),onMessage:i,offMessage:g,isInit:()=>t,onInit(o){if(t){o();return}e.promise.then(()=>{o()})},destroy:e.destroy}},h={create:w};export{w as create,h as default};
|
package/dist/vite.js
CHANGED
|
@@ -40,7 +40,7 @@ var fs = __toESM(require("fs"));
|
|
|
40
40
|
// package.json
|
|
41
41
|
var package_default = {
|
|
42
42
|
name: "iframe-bridge-kit",
|
|
43
|
-
version: "1.
|
|
43
|
+
version: "1.1.0",
|
|
44
44
|
description: "A type-safe communication bridge for iframes. Define strongly typed RPC APIs for cross-window messaging with ease.",
|
|
45
45
|
main: "dist/index.js",
|
|
46
46
|
module: "dist/index.mjs",
|
|
@@ -248,7 +248,7 @@ function parseBridgeFromCode(code, filePath) {
|
|
|
248
248
|
collectTypeImports(stmt);
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
|
-
const
|
|
251
|
+
const bridgeFactories = /* @__PURE__ */ new Map();
|
|
252
252
|
for (const stmt of sourceFile.statements) {
|
|
253
253
|
if (ts.isImportDeclaration(stmt) && ts.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text === packageName) {
|
|
254
254
|
const namedBindings = stmt.importClause?.namedBindings;
|
|
@@ -256,17 +256,21 @@ function parseBridgeFromCode(code, filePath) {
|
|
|
256
256
|
for (const element of namedBindings.elements) {
|
|
257
257
|
const originalName = element.propertyName?.text ?? element.name.text;
|
|
258
258
|
if (originalName === "defineBridge") {
|
|
259
|
-
|
|
259
|
+
bridgeFactories.set(element.name.text, "child");
|
|
260
|
+
}
|
|
261
|
+
if (originalName === "defineIframeBridge") {
|
|
262
|
+
bridgeFactories.set(element.name.text, "parent");
|
|
260
263
|
}
|
|
261
264
|
}
|
|
262
265
|
}
|
|
263
266
|
}
|
|
264
267
|
}
|
|
265
|
-
if (
|
|
268
|
+
if (bridgeFactories.size === 0) {
|
|
266
269
|
return { results, cleanup };
|
|
267
270
|
}
|
|
268
271
|
const visit = (node) => {
|
|
269
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) &&
|
|
272
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && bridgeFactories.has(node.expression.text)) {
|
|
273
|
+
const target = bridgeFactories.get(node.expression.text);
|
|
270
274
|
const [nameArg, methodsArg] = node.arguments;
|
|
271
275
|
if (!nameArg || !ts.isStringLiteral(nameArg)) {
|
|
272
276
|
return;
|
|
@@ -363,8 +367,12 @@ function parseBridgeFromCode(code, filePath) {
|
|
|
363
367
|
}
|
|
364
368
|
}
|
|
365
369
|
const emitMap = [];
|
|
370
|
+
let emitTypeName;
|
|
366
371
|
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
367
372
|
const emitTypeNode = node.typeArguments[0];
|
|
373
|
+
if (ts.isTypeReferenceNode(emitTypeNode) && ts.isIdentifier(emitTypeNode.typeName)) {
|
|
374
|
+
emitTypeName = emitTypeNode.typeName.text;
|
|
375
|
+
}
|
|
368
376
|
const emitType = checker.getTypeFromTypeNode(emitTypeNode);
|
|
369
377
|
emitType.getProperties().forEach((prop) => {
|
|
370
378
|
const name = prop.getName();
|
|
@@ -378,11 +386,13 @@ function parseBridgeFromCode(code, filePath) {
|
|
|
378
386
|
mergeImports(bridgeImports, typeImports);
|
|
379
387
|
results.push({
|
|
380
388
|
name: bridgeName,
|
|
389
|
+
target,
|
|
381
390
|
methods,
|
|
382
391
|
sourceFile: filePath,
|
|
383
392
|
typeDeclarations,
|
|
384
393
|
imports: bridgeImports,
|
|
385
|
-
emitMap
|
|
394
|
+
emitMap,
|
|
395
|
+
emitTypeName
|
|
386
396
|
});
|
|
387
397
|
}
|
|
388
398
|
ts.forEachChild(node, visit);
|
|
@@ -554,6 +564,52 @@ function generateDtsContent(info, outDir, preserveModules = []) {
|
|
|
554
564
|
lines.push("");
|
|
555
565
|
}
|
|
556
566
|
}
|
|
567
|
+
const localTypeDeclarations = info.typeDeclarations.filter(({ name }) => name !== info.emitTypeName && name !== "EmitMap");
|
|
568
|
+
if (localTypeDeclarations.length > 0) {
|
|
569
|
+
lines.push("// Local type definitions");
|
|
570
|
+
for (const { content } of localTypeDeclarations) {
|
|
571
|
+
lines.push(content);
|
|
572
|
+
lines.push("");
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (info.target === "parent") {
|
|
576
|
+
if (processedEmitTypes.length > 0) {
|
|
577
|
+
lines.push("export interface EmitMap {");
|
|
578
|
+
processedEmitTypes.forEach((e) => {
|
|
579
|
+
lines.push(` "${e.name}": ${e.type};`);
|
|
580
|
+
});
|
|
581
|
+
lines.push("}");
|
|
582
|
+
lines.push("");
|
|
583
|
+
}
|
|
584
|
+
lines.push("export interface BridgeConnection {");
|
|
585
|
+
lines.push(" api: {");
|
|
586
|
+
lines.push(
|
|
587
|
+
...processedMethods.map(
|
|
588
|
+
(m) => (m.jsdoc ? ` ${m.jsdoc}
|
|
589
|
+
` : "") + ` ${m.name}: (${m.params}) => ${m.returnType},`
|
|
590
|
+
)
|
|
591
|
+
);
|
|
592
|
+
lines.push(" };");
|
|
593
|
+
if (processedEmitTypes.length > 0) {
|
|
594
|
+
lines.push(" onMessage<K extends keyof EmitMap>(type: K, cb: (data: EmitMap[K]) => void, once?: boolean): () => void;");
|
|
595
|
+
lines.push(" onMessage(type: string, cb: Function, once?: boolean): () => void;");
|
|
596
|
+
lines.push(" offMessage<K extends keyof EmitMap>(type: K, fn?: (data: EmitMap[K]) => void): void;");
|
|
597
|
+
lines.push(" offMessage(type: string, fn?: Function): void;");
|
|
598
|
+
} else {
|
|
599
|
+
lines.push(" onMessage(type: string, cb: Function, once?: boolean): () => void;");
|
|
600
|
+
lines.push(" offMessage(type: string, fn?: Function): void;");
|
|
601
|
+
}
|
|
602
|
+
lines.push(" isInit(): boolean;");
|
|
603
|
+
lines.push(" onInit(cb: Function): void;");
|
|
604
|
+
lines.push(" destroy(): void;");
|
|
605
|
+
lines.push("}");
|
|
606
|
+
lines.push("");
|
|
607
|
+
lines.push("export declare function create(iframe: HTMLIFrameElement, allowedOrigins?: string[]): BridgeConnection;");
|
|
608
|
+
lines.push("declare const bridge: { create: typeof create };");
|
|
609
|
+
lines.push("export default bridge;");
|
|
610
|
+
lines.push("");
|
|
611
|
+
return lines.join("\n");
|
|
612
|
+
}
|
|
557
613
|
if (processedEmitTypes.length > 0) {
|
|
558
614
|
lines.push("export interface EmitMap {");
|
|
559
615
|
processedEmitTypes.forEach((e) => {
|
|
@@ -606,7 +662,7 @@ function processFile(filePath, code, options) {
|
|
|
606
662
|
const { results: bridges, cleanup } = parseBridgeFromCode(code, filePath);
|
|
607
663
|
for (const bridge of bridges) {
|
|
608
664
|
writeDtsFile(bridge, options);
|
|
609
|
-
writeBridgeRuntime(bridge
|
|
665
|
+
writeBridgeRuntime(bridge, options);
|
|
610
666
|
}
|
|
611
667
|
cleanup();
|
|
612
668
|
return bridges;
|
|
@@ -615,7 +671,7 @@ function processFile(filePath, code, options) {
|
|
|
615
671
|
return [];
|
|
616
672
|
}
|
|
617
673
|
}
|
|
618
|
-
function writeBridgeRuntime(
|
|
674
|
+
function writeBridgeRuntime(info, options) {
|
|
619
675
|
const isFull = options.full !== false;
|
|
620
676
|
const allowedOrigins = options.allowedOrigins || ["*"];
|
|
621
677
|
const outDir = options.outDir || "bridges";
|
|
@@ -636,14 +692,15 @@ function writeBridgeRuntime(bridgeName, options) {
|
|
|
636
692
|
if (!coreDir) {
|
|
637
693
|
return;
|
|
638
694
|
}
|
|
639
|
-
const absoluteOutDir = path.resolve(process.cwd(), outDir,
|
|
695
|
+
const absoluteOutDir = path.resolve(process.cwd(), outDir, info.name);
|
|
640
696
|
if (!fs.existsSync(absoluteOutDir)) {
|
|
641
697
|
fs.mkdirSync(absoluteOutDir, { recursive: true });
|
|
642
698
|
}
|
|
643
699
|
const extensions = ["js", "mjs"];
|
|
644
700
|
const replaceContent = JSON.stringify(allowedOrigins).slice(1, -1);
|
|
701
|
+
const runtimeName = info.target === "parent" ? "parent-core" : "core";
|
|
645
702
|
for (const ext of extensions) {
|
|
646
|
-
const srcFile = path.join(coreDir,
|
|
703
|
+
const srcFile = path.join(coreDir, `${runtimeName}.${ext}`);
|
|
647
704
|
if (fs.existsSync(srcFile)) {
|
|
648
705
|
try {
|
|
649
706
|
let content = fs.readFileSync(srcFile, "utf-8");
|
package/dist/vite.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import * as fs from "fs";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "iframe-bridge-kit",
|
|
9
|
-
version: "1.
|
|
9
|
+
version: "1.1.0",
|
|
10
10
|
description: "A type-safe communication bridge for iframes. Define strongly typed RPC APIs for cross-window messaging with ease.",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
module: "dist/index.mjs",
|
|
@@ -214,7 +214,7 @@ function parseBridgeFromCode(code, filePath) {
|
|
|
214
214
|
collectTypeImports(stmt);
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
-
const
|
|
217
|
+
const bridgeFactories = /* @__PURE__ */ new Map();
|
|
218
218
|
for (const stmt of sourceFile.statements) {
|
|
219
219
|
if (ts.isImportDeclaration(stmt) && ts.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text === packageName) {
|
|
220
220
|
const namedBindings = stmt.importClause?.namedBindings;
|
|
@@ -222,17 +222,21 @@ function parseBridgeFromCode(code, filePath) {
|
|
|
222
222
|
for (const element of namedBindings.elements) {
|
|
223
223
|
const originalName = element.propertyName?.text ?? element.name.text;
|
|
224
224
|
if (originalName === "defineBridge") {
|
|
225
|
-
|
|
225
|
+
bridgeFactories.set(element.name.text, "child");
|
|
226
|
+
}
|
|
227
|
+
if (originalName === "defineIframeBridge") {
|
|
228
|
+
bridgeFactories.set(element.name.text, "parent");
|
|
226
229
|
}
|
|
227
230
|
}
|
|
228
231
|
}
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
|
-
if (
|
|
234
|
+
if (bridgeFactories.size === 0) {
|
|
232
235
|
return { results, cleanup };
|
|
233
236
|
}
|
|
234
237
|
const visit = (node) => {
|
|
235
|
-
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) &&
|
|
238
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && bridgeFactories.has(node.expression.text)) {
|
|
239
|
+
const target = bridgeFactories.get(node.expression.text);
|
|
236
240
|
const [nameArg, methodsArg] = node.arguments;
|
|
237
241
|
if (!nameArg || !ts.isStringLiteral(nameArg)) {
|
|
238
242
|
return;
|
|
@@ -329,8 +333,12 @@ function parseBridgeFromCode(code, filePath) {
|
|
|
329
333
|
}
|
|
330
334
|
}
|
|
331
335
|
const emitMap = [];
|
|
336
|
+
let emitTypeName;
|
|
332
337
|
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
333
338
|
const emitTypeNode = node.typeArguments[0];
|
|
339
|
+
if (ts.isTypeReferenceNode(emitTypeNode) && ts.isIdentifier(emitTypeNode.typeName)) {
|
|
340
|
+
emitTypeName = emitTypeNode.typeName.text;
|
|
341
|
+
}
|
|
334
342
|
const emitType = checker.getTypeFromTypeNode(emitTypeNode);
|
|
335
343
|
emitType.getProperties().forEach((prop) => {
|
|
336
344
|
const name = prop.getName();
|
|
@@ -344,11 +352,13 @@ function parseBridgeFromCode(code, filePath) {
|
|
|
344
352
|
mergeImports(bridgeImports, typeImports);
|
|
345
353
|
results.push({
|
|
346
354
|
name: bridgeName,
|
|
355
|
+
target,
|
|
347
356
|
methods,
|
|
348
357
|
sourceFile: filePath,
|
|
349
358
|
typeDeclarations,
|
|
350
359
|
imports: bridgeImports,
|
|
351
|
-
emitMap
|
|
360
|
+
emitMap,
|
|
361
|
+
emitTypeName
|
|
352
362
|
});
|
|
353
363
|
}
|
|
354
364
|
ts.forEachChild(node, visit);
|
|
@@ -520,6 +530,52 @@ function generateDtsContent(info, outDir, preserveModules = []) {
|
|
|
520
530
|
lines.push("");
|
|
521
531
|
}
|
|
522
532
|
}
|
|
533
|
+
const localTypeDeclarations = info.typeDeclarations.filter(({ name }) => name !== info.emitTypeName && name !== "EmitMap");
|
|
534
|
+
if (localTypeDeclarations.length > 0) {
|
|
535
|
+
lines.push("// Local type definitions");
|
|
536
|
+
for (const { content } of localTypeDeclarations) {
|
|
537
|
+
lines.push(content);
|
|
538
|
+
lines.push("");
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (info.target === "parent") {
|
|
542
|
+
if (processedEmitTypes.length > 0) {
|
|
543
|
+
lines.push("export interface EmitMap {");
|
|
544
|
+
processedEmitTypes.forEach((e) => {
|
|
545
|
+
lines.push(` "${e.name}": ${e.type};`);
|
|
546
|
+
});
|
|
547
|
+
lines.push("}");
|
|
548
|
+
lines.push("");
|
|
549
|
+
}
|
|
550
|
+
lines.push("export interface BridgeConnection {");
|
|
551
|
+
lines.push(" api: {");
|
|
552
|
+
lines.push(
|
|
553
|
+
...processedMethods.map(
|
|
554
|
+
(m) => (m.jsdoc ? ` ${m.jsdoc}
|
|
555
|
+
` : "") + ` ${m.name}: (${m.params}) => ${m.returnType},`
|
|
556
|
+
)
|
|
557
|
+
);
|
|
558
|
+
lines.push(" };");
|
|
559
|
+
if (processedEmitTypes.length > 0) {
|
|
560
|
+
lines.push(" onMessage<K extends keyof EmitMap>(type: K, cb: (data: EmitMap[K]) => void, once?: boolean): () => void;");
|
|
561
|
+
lines.push(" onMessage(type: string, cb: Function, once?: boolean): () => void;");
|
|
562
|
+
lines.push(" offMessage<K extends keyof EmitMap>(type: K, fn?: (data: EmitMap[K]) => void): void;");
|
|
563
|
+
lines.push(" offMessage(type: string, fn?: Function): void;");
|
|
564
|
+
} else {
|
|
565
|
+
lines.push(" onMessage(type: string, cb: Function, once?: boolean): () => void;");
|
|
566
|
+
lines.push(" offMessage(type: string, fn?: Function): void;");
|
|
567
|
+
}
|
|
568
|
+
lines.push(" isInit(): boolean;");
|
|
569
|
+
lines.push(" onInit(cb: Function): void;");
|
|
570
|
+
lines.push(" destroy(): void;");
|
|
571
|
+
lines.push("}");
|
|
572
|
+
lines.push("");
|
|
573
|
+
lines.push("export declare function create(iframe: HTMLIFrameElement, allowedOrigins?: string[]): BridgeConnection;");
|
|
574
|
+
lines.push("declare const bridge: { create: typeof create };");
|
|
575
|
+
lines.push("export default bridge;");
|
|
576
|
+
lines.push("");
|
|
577
|
+
return lines.join("\n");
|
|
578
|
+
}
|
|
523
579
|
if (processedEmitTypes.length > 0) {
|
|
524
580
|
lines.push("export interface EmitMap {");
|
|
525
581
|
processedEmitTypes.forEach((e) => {
|
|
@@ -572,7 +628,7 @@ function processFile(filePath, code, options) {
|
|
|
572
628
|
const { results: bridges, cleanup } = parseBridgeFromCode(code, filePath);
|
|
573
629
|
for (const bridge of bridges) {
|
|
574
630
|
writeDtsFile(bridge, options);
|
|
575
|
-
writeBridgeRuntime(bridge
|
|
631
|
+
writeBridgeRuntime(bridge, options);
|
|
576
632
|
}
|
|
577
633
|
cleanup();
|
|
578
634
|
return bridges;
|
|
@@ -581,7 +637,7 @@ function processFile(filePath, code, options) {
|
|
|
581
637
|
return [];
|
|
582
638
|
}
|
|
583
639
|
}
|
|
584
|
-
function writeBridgeRuntime(
|
|
640
|
+
function writeBridgeRuntime(info, options) {
|
|
585
641
|
const isFull = options.full !== false;
|
|
586
642
|
const allowedOrigins = options.allowedOrigins || ["*"];
|
|
587
643
|
const outDir = options.outDir || "bridges";
|
|
@@ -602,14 +658,15 @@ function writeBridgeRuntime(bridgeName, options) {
|
|
|
602
658
|
if (!coreDir) {
|
|
603
659
|
return;
|
|
604
660
|
}
|
|
605
|
-
const absoluteOutDir = path.resolve(process.cwd(), outDir,
|
|
661
|
+
const absoluteOutDir = path.resolve(process.cwd(), outDir, info.name);
|
|
606
662
|
if (!fs.existsSync(absoluteOutDir)) {
|
|
607
663
|
fs.mkdirSync(absoluteOutDir, { recursive: true });
|
|
608
664
|
}
|
|
609
665
|
const extensions = ["js", "mjs"];
|
|
610
666
|
const replaceContent = JSON.stringify(allowedOrigins).slice(1, -1);
|
|
667
|
+
const runtimeName = info.target === "parent" ? "parent-core" : "core";
|
|
611
668
|
for (const ext of extensions) {
|
|
612
|
-
const srcFile = path.join(coreDir,
|
|
669
|
+
const srcFile = path.join(coreDir, `${runtimeName}.${ext}`);
|
|
613
670
|
if (fs.existsSync(srcFile)) {
|
|
614
671
|
try {
|
|
615
672
|
let content = fs.readFileSync(srcFile, "utf-8");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iframe-bridge-kit",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A type-safe communication bridge for iframes. Define strongly typed RPC APIs for cross-window messaging with ease.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|