iframe-bridge-kit 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +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
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,182 @@
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 you to call parent window methods from the iframe (child window) 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
+ * 🚀 **Zero Runtime Definition**: No need to manually define interfaces in the child window; simply import the generated bridge file to use.
11
+ * 📡 **RPC Style**: Call cross-window methods just like calling `async` functions.
12
+ * ⚡ **Event Mechanism**: Supports sending strongly-typed broadcast messages from the parent window to the child window.
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 Window (Host/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
+ ## 🧩 Type Support Details
125
+
126
+ The core magic of `iframe-bridge-kit` lies in how it handles types.
127
+
128
+ When you define methods:
129
+
130
+ ```typescript
131
+ getUserInfo(id: string): Promise<User>
132
+ ```
133
+
134
+ 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.
135
+
136
+ ### Supported Type Features
137
+
138
+ * Basic types (string, number, boolean)
139
+ * Interfaces & Type Aliases
140
+ * Generic Expansion
141
+ * Third-party library types (automatically handles import paths)
142
+
143
+ ## 🔌 API Reference
144
+
145
+ ### `defineBridge<TEmit>(name, methods)`
146
+
147
+ * **name**: `string` - Bridge name, determines the generated directory name.
148
+ * **methods**: `Object` - Collection of methods exposed to the child window.
149
+ * **TEmit**: `Generic` - (Optional) Defines the event type mapping for messages sent via `emit` from the parent.
150
+
151
+ Returns an object containing:
152
+
153
+ * `create(iframeEl, allowedOrigins?)`: Initializes the connection and returns an `{ emit }` object.
154
+
155
+ ### Vite Plugin Options (`IframeBridgeOptions`)
156
+
157
+ | Option | Type | Default | Description |
158
+ |:---|:---|:---|:---|
159
+ | `outDir` | `string` | `'bridges'` | Output directory for generated code. Recommended `'src/bridges'`. |
160
+ | `allowedOrigins` | `string[]` | `['*']` | List of allowed origin domains for communication. |
161
+ | `full` | `boolean` | `true` | Whether to generate code containing full dependencies. |
162
+ | `preserveModules` | `string[]` | `[]` | Preserve imports for specific modules instead of expanding types (e.g., `['vue']`). |
163
+
164
+ ### Generated Child API
165
+
166
+ Assuming `outDir` is `src/bridges` and the bridge name is `my-bridge`, you can import from `src/bridges/my-bridge`:
167
+
168
+ * **`default` (ParentApi)**: A proxy object containing all parent methods. All methods return a `Promise`.
169
+ * **`onMessage(type, callback, once?)`**: Listen for events sent by the parent.
170
+ * **`offMessage(type, callback?)`**: Remove an event listener.
171
+ * **`onInit(callback)`**: Triggered when the connection is successfully established.
172
+ * **`isInit()`**: Returns the current connection status.
173
+
174
+ ## ⚠️ Notes
175
+
176
+ 1. **Same-Origin Policy**: While Penpal simplifies postMessage, please ensure `allowedOrigins` is correctly configured for security.
177
+ 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.
178
+ 3. **JSON Serialization**: Data transmitted across windows must be JSON serializable (Functions, DOM nodes, etc., are not supported).
179
+
180
+ ## License
181
+
182
+ MIT
@@ -0,0 +1 @@
1
+ "use strict";var T=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var ee=Object.prototype.hasOwnProperty;var te=(e,t)=>{for(var r in t)T(e,r,{get:t[r],enumerable:!0})},re=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Z(t))!ee.call(e,s)&&s!==r&&T(e,s,{get:()=>t[s],enumerable:!(n=Q(t,s))||n.enumerable});return e};var ne=e=>re(T({},"__esModule",{value:!0}),e);var Fe={};te(Fe,{default:()=>be,isInit:()=>He,offMessage:()=>b,onInit:()=>xe,onMessage:()=>ke});module.exports=ne(Fe);var se=class extends Error{code;constructor(e,t){super(t),this.name="PenpalError",this.code=e}},p=se,ae=e=>({name:e.name,message:e.message,stack:e.stack,penpalCode:e instanceof p?e.code:void 0}),oe=({name:e,message:t,stack:r,penpalCode:n})=>{let s=n?new p(n,t):new Error(t);return s.name=e,s.stack=r,s},ie=Symbol("Reply"),de=class{value;transferables;#t=ie;constructor(e,t){this.value=e,this.transferables=t?.transferables}},le=de,f="penpal",S=e=>typeof e=="object"&&e!==null,V=e=>typeof e=="function",ce=e=>S(e)&&e.namespace===f,N=e=>e.type==="SYN",D=e=>e.type==="ACK1",O=e=>e.type==="ACK2",W=e=>e.type==="CALL",j=e=>e.type==="REPLY",he=e=>e.type==="DESTROY",z=(e,t=[])=>{let r=[];for(let n of Object.keys(e)){let s=e[n];V(s)?r.push([...t,n]):S(s)&&r.push(...z(s,[...t,n]))}return r},ue=(e,t)=>{let r=e.reduce((n,s)=>S(n)?n[s]:void 0,t);return V(r)?r:void 0},y=e=>e.join("."),F=(e,t,r)=>({namespace:f,channel:e,type:"REPLY",callId:t,isError:!0,...r instanceof Error?{value:ae(r),isSerializedErrorInstance:!0}:{value:r}}),fe=(e,t,r,n)=>{let s=!1,l=async u=>{if(s||!W(u))return;n?.(`Received ${y(u.methodPath)}() call`,u);let{methodPath:v,args:c,id:o}=u,a,M;try{let d=ue(v,t);if(!d)throw new p("METHOD_NOT_FOUND",`Method \`${y(v)}\` is not found.`);let h=await d(...c);h instanceof le&&(M=h.transferables,h=await h.value),a={namespace:f,channel:r,type:"REPLY",callId:o,value:h}}catch(d){a=F(r,o,d)}if(!s)try{n?.(`Sending ${y(v)}() reply`,a),e.sendMessage(a,M)}catch(d){throw d.name==="DataCloneError"&&(a=F(r,o,d),n?.(`Sending ${y(v)}() reply`,a),e.sendMessage(a)),d}};return e.addMessageHandler(l),()=>{s=!0,e.removeMessageHandler(l)}},pe=fe,K=crypto.randomUUID?.bind(crypto)??(()=>new Array(4).fill(0).map(()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16)).join("-")),ve=Symbol("CallOptions"),Me=class{transferables;timeout;#t=ve;constructor(e){this.transferables=e?.transferables,this.timeout=e?.timeout}},ye=Me,ge=new Set(["apply","call","bind"]),G=(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):G(e,t,[...r,s])},apply(n,s,l){return e(r,l)}}),U=e=>new p("CONNECTION_DESTROYED",`Method call ${y(e)}() failed due to destroyed connection`),Ee=(e,t,r)=>{let n=!1,s=new Map,l=c=>{if(!j(c))return;let{callId:o,value:a,isError:M,isSerializedErrorInstance:d}=c,h=s.get(o);h&&(s.delete(o),r?.(`Received ${y(h.methodPath)}() call`,c),M?h.reject(d?oe(a):a):h.resolve(a))};return e.addMessageHandler(l),{remoteProxy:G((c,o)=>{if(n)throw U(c);let a=K(),M=o[o.length-1],d=M instanceof ye,{timeout:h,transferables:_}=d?M:{},m=d?o.slice(0,-1):o;return new Promise((A,w)=>{let R=h!==void 0?window.setTimeout(()=>{s.delete(a),w(new p("METHOD_CALL_TIMEOUT",`Method call ${y(c)}() timed out after ${h}ms`))},h):void 0;s.set(a,{methodPath:c,resolve:A,reject:w,timeoutId:R});try{let E={namespace:f,channel:t,type:"CALL",id:a,methodPath:c,args:m};r?.(`Sending ${y(c)}() call`,E),e.sendMessage(E,_)}catch(E){w(new p("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(U(c));s.clear()}}},we=Ee,Ie=()=>{let e,t;return{promise:new Promise((n,s)=>{e=n,t=s}),resolve:e,reject:t}},me=Ie,Ae=class extends Error{constructor(e){super(`You've hit a bug in Penpal. Please file an issue with the following information: ${e}`)}},P=Ae,L="deprecated-penpal",Ce=e=>S(e)&&"penpal"in e,Ne=e=>e.split("."),Y=e=>e.join("."),B=e=>new P(`Unexpected message to translate: ${JSON.stringify(e)}`),Pe=e=>{if(e.penpal==="syn")return{namespace:f,channel:void 0,type:"SYN",participantId:L};if(e.penpal==="ack")return{namespace:f,channel:void 0,type:"ACK2"};if(e.penpal==="call")return{namespace:f,channel:void 0,type:"CALL",id:e.id,methodPath:Ne(e.methodName),args:e.args};if(e.penpal==="reply")return e.resolution==="fulfilled"?{namespace:f,channel:void 0,type:"REPLY",callId:e.id,value:e.returnValue}:{namespace:f,channel:void 0,type:"REPLY",callId:e.id,isError:!0,...e.returnValueIsError?{value:e.returnValue,isSerializedErrorInstance:!0}:{value:e.returnValue}};throw B(e)},Se=e=>{if(D(e))return{penpal:"synAck",methodNames:e.methodPaths.map(Y)};if(W(e))return{penpal:"call",id:e.id,methodName:Y(e.methodPath),args:e.args};if(j(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 B(e)},_e=({messenger:e,methods:t,timeout:r,channel:n,log:s})=>{let l=K(),u,v=[],c=!1,o=z(t),{promise:a,resolve:M,reject:d}=me(),h=r!==void 0?setTimeout(()=>{d(new p("CONNECTION_TIMEOUT",`Connection timed out after ${r}ms`))},r):void 0,_=()=>{for(let i of v)i()},m=()=>{if(c)return;v.push(pe(e,t,n,s));let{remoteProxy:i,destroy:g}=we(e,n,s);v.push(g),clearTimeout(h),c=!0,M({remoteProxy:i,destroy:_})},A=()=>{let i={namespace:f,type:"SYN",channel:n,participantId:l};s?.("Sending handshake SYN",i);try{e.sendMessage(i)}catch(g){d(new p("TRANSMISSION_FAILED",g.message))}},w=i=>{if(s?.("Received handshake SYN",i),i.participantId===u&&u!==L||(u=i.participantId,A(),!(l>u||u===L)))return;let C={namespace:f,channel:n,type:"ACK1",methodPaths:o};s?.("Sending handshake ACK1",C);try{e.sendMessage(C)}catch(q){d(new p("TRANSMISSION_FAILED",q.message));return}},R=i=>{s?.("Received handshake ACK1",i);let g={namespace:f,channel:n,type:"ACK2"};s?.("Sending handshake ACK2",g);try{e.sendMessage(g)}catch(C){d(new p("TRANSMISSION_FAILED",C.message));return}m()},E=i=>{s?.("Received handshake ACK2",i),m()},x=i=>{N(i)&&w(i),D(i)&&R(i),O(i)&&E(i)};return e.addMessageHandler(x),v.push(()=>e.removeMessageHandler(x)),A(),a},Re=_e,Te=e=>{let t=!1,r;return(...n)=>(t||(t=!0,r=e(...n)),r)},Oe=Te,$=new WeakSet,Le=({messenger:e,methods:t={},timeout:r,channel:n,log:s})=>{if(!e)throw new p("INVALID_ARGUMENT","messenger must be defined");if($.has(e))throw new p("INVALID_ARGUMENT","A messenger can only be used for a single connection");$.add(e);let l=[e.destroy],u=Oe(o=>{if(o){let a={namespace:f,channel:n,type:"DESTROY"};try{e.sendMessage(a)}catch{}}for(let a of l)a();s?.("Connection destroyed")}),v=o=>ce(o)&&o.channel===n;return{promise:(async()=>{try{e.initialize({log:s,validateReceivedMessage:v}),e.addMessageHandler(M=>{he(M)&&u(!1)});let{remoteProxy:o,destroy:a}=await Re({messenger:e,methods:t,timeout:r,channel:n,log:s});return l.push(a),o}catch(o){throw u(!0),o}})(),destroy:()=>{u(!0)}}},J=Le,De=class{#t;#n;#o;#i;#s;#r=new Set;#e;#a=!1;constructor({remoteWindow:e,allowedOrigins:t}){if(!e)throw new p("INVALID_ARGUMENT","remoteWindow must be defined");this.#t=e,this.#n=t?.length?t:[window.origin]}initialize=({log:e,validateReceivedMessage:t})=>{this.#o=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(D(e)||this.#a){let r=this.#a?Se(e):e,n=this.#d(e);this.#t.postMessage(r,{targetOrigin:n,transfer:t});return}if(O(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 P("Port is undefined")};addMessageHandler=e=>{this.#r.add(e)};removeMessageHandler=e=>{this.#r.delete(e)};destroy=()=>{window.removeEventListener("message",this.#h),this.#c(),this.#r.clear()};#u=e=>this.#n.some(t=>t instanceof RegExp?t.test(e):t===e||t==="*");#d=e=>{if(N(e))return"*";if(!this.#s)throw new P("Concrete remote origin not set");return this.#s==="null"&&this.#n.includes("*")?"*":this.#s};#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&&(Ce(n)&&(this.#o?.("Please upgrade the child window to the latest version of Penpal."),this.#a=!0,n=Pe(n)),!!this.#i?.(n))){if(!this.#u(t)){this.#o?.(`Received a message from origin \`${t}\` which did not match allowed origins \`[${this.#n.join(", ")}]\``);return}if(N(n)&&(this.#c(),this.#s=t),O(n)&&!this.#a){if(this.#e=r[0],!this.#e)throw new P("No port received on ACK2");this.#e.addEventListener("message",this.#l),this.#e.start()}for(let s of this.#r)s(n)}};#l=({data:e})=>{if(this.#i?.(e))for(let t of this.#r)t(e)}},X=De;var I=new Map,k=J({messenger:new X({remoteWindow:window.parent,allowedOrigins:["__AllowedOrigins__"]}),channel:"iframe-bridge-channel",methods:{onMessage(e,t){let r=I.get(e);if(r)return r.forEach(n=>n(t)),!0}}}),be=new Proxy({},{get(e,t){return async r=>await k.promise.then(n=>n[t](r))}}),ke=(e,t,r)=>{let n=r?(l=>{t(l),b(e,t)}):t,s=I.get(e);return s?s.add(n):I.set(e,new Set([n])),()=>b(e,n)},b=(e,t)=>{if(!t){I.delete(e);return}let r=I.get(e);r&&r.delete(t)},H=!1;k.promise.then(()=>{H=!0});var He=()=>H,xe=e=>{if(H){e();return}k.promise.then(()=>{e()})};0&&(module.exports={isInit,offMessage,onInit,onMessage});
@@ -0,0 +1 @@
1
+ var q=class extends Error{code;constructor(e,t){super(t),this.name="PenpalError",this.code=e}},p=q,Q=e=>({name:e.name,message:e.message,stack:e.stack,penpalCode:e instanceof p?e.code:void 0}),Z=({name:e,message:t,stack:r,penpalCode:n})=>{let s=n?new p(n,t):new Error(t);return s.name=e,s.stack=r,s},ee=Symbol("Reply"),te=class{value;transferables;#t=ee;constructor(e,t){this.value=e,this.transferables=t?.transferables}},re=te,f="penpal",S=e=>typeof e=="object"&&e!==null,Y=e=>typeof e=="function",ne=e=>S(e)&&e.namespace===f,N=e=>e.type==="SYN",L=e=>e.type==="ACK1",T=e=>e.type==="ACK2",$=e=>e.type==="CALL",V=e=>e.type==="REPLY",se=e=>e.type==="DESTROY",W=(e,t=[])=>{let r=[];for(let n of Object.keys(e)){let s=e[n];Y(s)?r.push([...t,n]):S(s)&&r.push(...W(s,[...t,n]))}return r},ae=(e,t)=>{let r=e.reduce((n,s)=>S(n)?n[s]:void 0,t);return Y(r)?r:void 0},y=e=>e.join("."),H=(e,t,r)=>({namespace:f,channel:e,type:"REPLY",callId:t,isError:!0,...r instanceof Error?{value:Q(r),isSerializedErrorInstance:!0}:{value:r}}),oe=(e,t,r,n)=>{let s=!1,l=async u=>{if(s||!$(u))return;n?.(`Received ${y(u.methodPath)}() call`,u);let{methodPath:v,args:c,id:o}=u,a,M;try{let d=ae(v,t);if(!d)throw new p("METHOD_NOT_FOUND",`Method \`${y(v)}\` is not found.`);let h=await d(...c);h instanceof re&&(M=h.transferables,h=await h.value),a={namespace:f,channel:r,type:"REPLY",callId:o,value:h}}catch(d){a=H(r,o,d)}if(!s)try{n?.(`Sending ${y(v)}() reply`,a),e.sendMessage(a,M)}catch(d){throw d.name==="DataCloneError"&&(a=H(r,o,d),n?.(`Sending ${y(v)}() reply`,a),e.sendMessage(a)),d}};return e.addMessageHandler(l),()=>{s=!0,e.removeMessageHandler(l)}},ie=oe,j=crypto.randomUUID?.bind(crypto)??(()=>new Array(4).fill(0).map(()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16)).join("-")),de=Symbol("CallOptions"),le=class{transferables;timeout;#t=de;constructor(e){this.transferables=e?.transferables,this.timeout=e?.timeout}},ce=le,he=new Set(["apply","call","bind"]),z=(e,t,r=[])=>new Proxy(r.length?()=>{}:Object.create(null),{get(n,s){if(s!=="then")return r.length&&he.has(s)?Reflect.get(n,s):z(e,t,[...r,s])},apply(n,s,l){return e(r,l)}}),x=e=>new p("CONNECTION_DESTROYED",`Method call ${y(e)}() failed due to destroyed connection`),ue=(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 ${y(h.methodPath)}() call`,c),M?h.reject(d?Z(a):a):h.resolve(a))};return e.addMessageHandler(l),{remoteProxy:z((c,o)=>{if(n)throw x(c);let a=j(),M=o[o.length-1],d=M instanceof ce,{timeout:h,transferables:_}=d?M:{},m=d?o.slice(0,-1):o;return new Promise((A,w)=>{let R=h!==void 0?window.setTimeout(()=>{s.delete(a),w(new p("METHOD_CALL_TIMEOUT",`Method call ${y(c)}() timed out after ${h}ms`))},h):void 0;s.set(a,{methodPath:c,resolve:A,reject:w,timeoutId:R});try{let E={namespace:f,channel:t,type:"CALL",id:a,methodPath:c,args:m};r?.(`Sending ${y(c)}() call`,E),e.sendMessage(E,_)}catch(E){w(new p("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()}}},fe=ue,pe=()=>{let e,t;return{promise:new Promise((n,s)=>{e=n,t=s}),resolve:e,reject:t}},ve=pe,Me=class extends Error{constructor(e){super(`You've hit a bug in Penpal. Please file an issue with the following information: ${e}`)}},P=Me,O="deprecated-penpal",ye=e=>S(e)&&"penpal"in e,ge=e=>e.split("."),F=e=>e.join("."),K=e=>new P(`Unexpected message to translate: ${JSON.stringify(e)}`),Ee=e=>{if(e.penpal==="syn")return{namespace:f,channel:void 0,type:"SYN",participantId:O};if(e.penpal==="ack")return{namespace:f,channel:void 0,type:"ACK2"};if(e.penpal==="call")return{namespace:f,channel:void 0,type:"CALL",id:e.id,methodPath:ge(e.methodName),args:e.args};if(e.penpal==="reply")return e.resolution==="fulfilled"?{namespace:f,channel:void 0,type:"REPLY",callId:e.id,value:e.returnValue}:{namespace:f,channel:void 0,type:"REPLY",callId:e.id,isError:!0,...e.returnValueIsError?{value:e.returnValue,isSerializedErrorInstance:!0}:{value:e.returnValue}};throw K(e)},we=e=>{if(L(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 K(e)},Ie=({messenger:e,methods:t,timeout:r,channel:n,log:s})=>{let l=j(),u,v=[],c=!1,o=W(t),{promise:a,resolve:M,reject:d}=ve(),h=r!==void 0?setTimeout(()=>{d(new p("CONNECTION_TIMEOUT",`Connection timed out after ${r}ms`))},r):void 0,_=()=>{for(let i of v)i()},m=()=>{if(c)return;v.push(ie(e,t,n,s));let{remoteProxy:i,destroy:g}=fe(e,n,s);v.push(g),clearTimeout(h),c=!0,M({remoteProxy:i,destroy:_})},A=()=>{let i={namespace:f,type:"SYN",channel:n,participantId:l};s?.("Sending handshake SYN",i);try{e.sendMessage(i)}catch(g){d(new p("TRANSMISSION_FAILED",g.message))}},w=i=>{if(s?.("Received handshake SYN",i),i.participantId===u&&u!==O||(u=i.participantId,A(),!(l>u||u===O)))return;let C={namespace:f,channel:n,type:"ACK1",methodPaths:o};s?.("Sending handshake ACK1",C);try{e.sendMessage(C)}catch(X){d(new p("TRANSMISSION_FAILED",X.message));return}},R=i=>{s?.("Received handshake ACK1",i);let g={namespace:f,channel:n,type:"ACK2"};s?.("Sending handshake ACK2",g);try{e.sendMessage(g)}catch(C){d(new p("TRANSMISSION_FAILED",C.message));return}m()},E=i=>{s?.("Received handshake ACK2",i),m()},k=i=>{N(i)&&w(i),L(i)&&R(i),T(i)&&E(i)};return e.addMessageHandler(k),v.push(()=>e.removeMessageHandler(k)),A(),a},me=Ie,Ae=e=>{let t=!1,r;return(...n)=>(t||(t=!0,r=e(...n)),r)},Ce=Ae,U=new WeakSet,Ne=({messenger:e,methods:t={},timeout:r,channel:n,log:s})=>{if(!e)throw new p("INVALID_ARGUMENT","messenger must be defined");if(U.has(e))throw new p("INVALID_ARGUMENT","A messenger can only be used for a single connection");U.add(e);let l=[e.destroy],u=Ce(o=>{if(o){let a={namespace:f,channel:n,type:"DESTROY"};try{e.sendMessage(a)}catch{}}for(let a of l)a();s?.("Connection destroyed")}),v=o=>ne(o)&&o.channel===n;return{promise:(async()=>{try{e.initialize({log:s,validateReceivedMessage:v}),e.addMessageHandler(M=>{se(M)&&u(!1)});let{remoteProxy:o,destroy:a}=await me({messenger:e,methods:t,timeout:r,channel:n,log:s});return l.push(a),o}catch(o){throw u(!0),o}})(),destroy:()=>{u(!0)}}},G=Ne,Pe=class{#t;#n;#o;#i;#s;#r=new Set;#e;#a=!1;constructor({remoteWindow:e,allowedOrigins:t}){if(!e)throw new p("INVALID_ARGUMENT","remoteWindow must be defined");this.#t=e,this.#n=t?.length?t:[window.origin]}initialize=({log:e,validateReceivedMessage:t})=>{this.#o=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.#a){let r=this.#a?we(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 P("Port is undefined")};addMessageHandler=e=>{this.#r.add(e)};removeMessageHandler=e=>{this.#r.delete(e)};destroy=()=>{window.removeEventListener("message",this.#h),this.#c(),this.#r.clear()};#u=e=>this.#n.some(t=>t instanceof RegExp?t.test(e):t===e||t==="*");#d=e=>{if(N(e))return"*";if(!this.#s)throw new P("Concrete remote origin not set");return this.#s==="null"&&this.#n.includes("*")?"*":this.#s};#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&&(ye(n)&&(this.#o?.("Please upgrade the child window to the latest version of Penpal."),this.#a=!0,n=Ee(n)),!!this.#i?.(n))){if(!this.#u(t)){this.#o?.(`Received a message from origin \`${t}\` which did not match allowed origins \`[${this.#n.join(", ")}]\``);return}if(N(n)&&(this.#c(),this.#s=t),T(n)&&!this.#a){if(this.#e=r[0],!this.#e)throw new P("No port received on ACK2");this.#e.addEventListener("message",this.#l),this.#e.start()}for(let s of this.#r)s(n)}};#l=({data:e})=>{if(this.#i?.(e))for(let t of this.#r)t(e)}},B=Pe;var I=new Map,D=G({messenger:new B({remoteWindow:window.parent,allowedOrigins:["__AllowedOrigins__"]}),channel:"iframe-bridge-channel",methods:{onMessage(e,t){let r=I.get(e);if(r)return r.forEach(n=>n(t)),!0}}}),be=new Proxy({},{get(e,t){return async r=>await D.promise.then(n=>n[t](r))}}),ke=(e,t,r)=>{let n=r?(l=>{t(l),J(e,t)}):t,s=I.get(e);return s?s.add(n):I.set(e,new Set([n])),()=>J(e,n)},J=(e,t)=>{if(!t){I.delete(e);return}let r=I.get(e);r&&r.delete(t)},b=!1;D.promise.then(()=>{b=!0});var He=()=>b,xe=e=>{if(b){e();return}D.promise.then(()=>{e()})};export{be as default,He as isInit,J as offMessage,xe as onInit,ke as onMessage};
@@ -0,0 +1,8 @@
1
+ /** 定义一个 bridge,暴露方法给 iframe 父/子窗口调用 */
2
+ declare const defineBridge: <TEmit extends Record<string, object | string | number | boolean | null | undefined>>(name: string, methods: Record<string, (...args: any[]) => any>) => {
3
+ create(iframe: HTMLIFrameElement, allowedOrigins?: string[]): Promise<{
4
+ emit<T extends keyof TEmit>(type: T, data: TEmit[T]): void;
5
+ }>;
6
+ };
7
+
8
+ export { defineBridge };
@@ -0,0 +1,8 @@
1
+ /** 定义一个 bridge,暴露方法给 iframe 父/子窗口调用 */
2
+ declare const defineBridge: <TEmit extends Record<string, object | string | number | boolean | null | undefined>>(name: string, methods: Record<string, (...args: any[]) => any>) => {
3
+ create(iframe: HTMLIFrameElement, allowedOrigins?: string[]): Promise<{
4
+ emit<T extends keyof TEmit>(type: T, data: TEmit[T]): void;
5
+ }>;
6
+ };
7
+
8
+ export { defineBridge };
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ defineBridge: () => defineBridge
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var import_penpal = require("penpal");
27
+ var defineBridge = (name, methods) => {
28
+ return {
29
+ async create(iframe, allowedOrigins = ["*"]) {
30
+ if (!iframe.contentWindow) {
31
+ throw new Error("iframe contentWindow is null");
32
+ }
33
+ const conn = (0, import_penpal.connect)({
34
+ messenger: new import_penpal.WindowMessenger({
35
+ remoteWindow: iframe.contentWindow,
36
+ allowedOrigins
37
+ }),
38
+ channel: "iframe-bridge-channel",
39
+ methods
40
+ });
41
+ const remote = await conn.promise;
42
+ return {
43
+ emit(type, data) {
44
+ remote.onMessage(type, data);
45
+ }
46
+ };
47
+ }
48
+ };
49
+ };
50
+ // Annotate the CommonJS export names for ESM import in node:
51
+ 0 && (module.exports = {
52
+ defineBridge
53
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,28 @@
1
+ // src/index.ts
2
+ import { WindowMessenger, connect } from "penpal";
3
+ var defineBridge = (name, methods) => {
4
+ return {
5
+ async create(iframe, allowedOrigins = ["*"]) {
6
+ if (!iframe.contentWindow) {
7
+ throw new Error("iframe contentWindow is null");
8
+ }
9
+ const conn = connect({
10
+ messenger: new WindowMessenger({
11
+ remoteWindow: iframe.contentWindow,
12
+ allowedOrigins
13
+ }),
14
+ channel: "iframe-bridge-channel",
15
+ methods
16
+ });
17
+ const remote = await conn.promise;
18
+ return {
19
+ emit(type, data) {
20
+ remote.onMessage(type, data);
21
+ }
22
+ };
23
+ }
24
+ };
25
+ };
26
+ export {
27
+ defineBridge
28
+ };
@@ -0,0 +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 t=>await g.promise.then(o=>o[e](t))}}),M=(n,e,t)=>{let o=t?(d=>{e(d),c(n,e)}):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});
@@ -0,0 +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 t=>await r.promise.then(o=>o[e](t))}}),w=(n,e,t)=>{let o=t?(g=>{e(g),c(n,e)}):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,19 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface IframeBridgeOptions {
4
+ /** 输出目录,默认为 'bridges' */
5
+ outDir?: string;
6
+ /** 完整模式,默认为true */
7
+ full?: boolean;
8
+ /** 脚本的允许域,默认为 ['*'] */
9
+ allowedOrigins?: string[];
10
+ /**
11
+ * 保留这些模块的类型导入而不是展开
12
+ * 例如: ['vue', '@vueuse/core']
13
+ * 使用这些模块的类型会生成 import type { X } from 'vue' 的形式
14
+ */
15
+ preserveModules?: string[];
16
+ }
17
+ declare function vitePluginIframeBridge(options?: IframeBridgeOptions): Plugin;
18
+
19
+ export { type IframeBridgeOptions, vitePluginIframeBridge as default };
package/dist/vite.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ interface IframeBridgeOptions {
4
+ /** 输出目录,默认为 'bridges' */
5
+ outDir?: string;
6
+ /** 完整模式,默认为true */
7
+ full?: boolean;
8
+ /** 脚本的允许域,默认为 ['*'] */
9
+ allowedOrigins?: string[];
10
+ /**
11
+ * 保留这些模块的类型导入而不是展开
12
+ * 例如: ['vue', '@vueuse/core']
13
+ * 使用这些模块的类型会生成 import type { X } from 'vue' 的形式
14
+ */
15
+ preserveModules?: string[];
16
+ }
17
+ declare function vitePluginIframeBridge(options?: IframeBridgeOptions): Plugin;
18
+
19
+ export { type IframeBridgeOptions, vitePluginIframeBridge as default };