@gjsify/iframe 0.4.11 → 0.4.13

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.
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{MessageChannel as e,MessagePort as t}from"@gjsify/message-channel";const n=t,r=e;var BridgePortTransport=class{constructor(e){this._bridge=e}send(e,t){this._bridge._sendPortMessage(e,t)}close(e){this._bridge._closePort(e)}};export{BridgePortTransport,r as IFrameMessageChannel,n as IFrameMessagePort,e as MessageChannel,t as MessagePort};
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{EventTarget as e}from"@gjsify/dom-events";var IFrameWindowProxy=class extends e{constructor(e){super(),this._closed=!1,this._bridge=e}postMessage(e,t=`*`){this._closed||this._bridge.sendToWebView(e,t)}get location(){return this._bridge.getLocation()}get parent(){return globalThis}get top(){return globalThis}get self(){return this}get window(){return this}get closed(){return this._closed}_close(){this._closed=!0}get[Symbol.toStringTag](){return`IFrameWindowProxy`}};export{IFrameWindowProxy};
1
+ import"./_virtual/_rolldown/runtime.js";import{EventTarget as e}from"@gjsify/dom-events";var IFrameWindowProxy=class extends e{constructor(e){super(),this._closed=!1,this._bridge=e}postMessage(e,t=`*`,n){this._closed||this._bridge.sendToWebView(e,t,n)}get location(){return this._bridge.getLocation()}get parent(){return globalThis}get top(){return globalThis}get self(){return this}get window(){return this}get closed(){return this._closed}_close(){this._closed=!0}get[Symbol.toStringTag](){return`IFrameWindowProxy`}};export{IFrameWindowProxy};
package/lib/esm/index.js CHANGED
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{HTMLIFrameElement as e}from"./html-iframe-element.js";import{IFrameWindowProxy as t}from"./iframe-window-proxy.js";import{MessageBridge as n}from"./message-bridge.js";import{IFrameBridge as r}from"./iframe-bridge.js";import{Document as i}from"@gjsify/dom-elements";i.registerElementFactory(`iframe`,()=>new e),Object.defineProperty(globalThis,`HTMLIFrameElement`,{value:e,writable:!0,configurable:!0});export{e as HTMLIFrameElement,r as IFrameBridge,t as IFrameWindowProxy,n as MessageBridge};
1
+ import"./_virtual/_rolldown/runtime.js";import{HTMLIFrameElement as e}from"./html-iframe-element.js";import{IFrameWindowProxy as t}from"./iframe-window-proxy.js";import{IFrameMessageChannel as n,IFrameMessagePort as r,MessageChannel as i,MessagePort as a}from"./iframe-message-channel.js";import{GJS_HOST_ORIGIN as o,MessageBridge as s}from"./message-bridge.js";import{IFrameBridge as c}from"./iframe-bridge.js";import{Document as l}from"@gjsify/dom-elements";l.registerElementFactory(`iframe`,()=>new e),Object.defineProperty(globalThis,`HTMLIFrameElement`,{value:e,writable:!0,configurable:!0});export{o as GJS_HOST_ORIGIN,e as HTMLIFrameElement,c as IFrameBridge,n as IFrameMessageChannel,r as IFrameMessagePort,t as IFrameWindowProxy,s as MessageBridge,i as MessageChannel,a as MessagePort};
@@ -1,13 +1,123 @@
1
- import"./_virtual/_rolldown/runtime.js";import{MessageEvent as e}from"@gjsify/dom-events";import t from"gi://WebKit?version=6.0";import n from"gi://Gio?version=2.0";n._promisify(t.WebView.prototype,`evaluate_javascript`,`evaluate_javascript_finish`);const r=`gjsify-iframe`,i=`(function() {
2
- var handler = window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers['${r}'];
1
+ import"./_virtual/_rolldown/runtime.js";import{BridgePortTransport as e}from"./iframe-message-channel.js";import{BINARY_SERIALIZER_INJECTED_SRC as t,decodeBinariesFromJson as n,encodeBinariesForJson as r}from"./serialize.js";import{MessageEvent as i}from"@gjsify/dom-events";import a from"gi://WebKit?version=6.0";import o from"gi://Gio?version=2.0";o._promisify(a.WebView.prototype,`evaluate_javascript`,`evaluate_javascript_finish`);const s=`gjsify-iframe`,c=`https://gjsify.local`;function normaliseTargetOrigin(e){if(e===`*`)return`*`;if(e===`/`)return null;try{return new URL(e).origin}catch{let t=Error(`Invalid target origin '${e}'`);throw t.name=`SyntaxError`,t}}const BOOTSTRAP_SCRIPT_FOR_TEST=()=>l,l=`(function() {
2
+ var handler = window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers['${s}'];
3
3
  if (!handler) return;
4
+
5
+ // Idempotency guard: WebKit auto-injects this script at INJECTION_TIME.START
6
+ // for every page load. On rapid reload, srcdoc remount, or
7
+ // history.replaceState shenanigans, the script may run twice in the
8
+ // same window without an intervening real navigation. If we
9
+ // overwrite our previous override blindly we'd capture OUR override
10
+ // as origPostMessage, losing the real one — and any consumer using
11
+ // __gjsifyBridge.origPostMessage would loop forever.
12
+ if (window.__gjsifyBridge && window.__gjsifyBridge.__bridgeVersion === 1) return;
13
+
14
+ var GJS_HOST_ORIGIN = '${c}';
15
+
16
+ ${t}
17
+
18
+ // Per-WebView registry of proxy ports created during GJS → WebView
19
+ // postMessage. Keyed by the GJS-allocated portId. Each entry has a
20
+ // .deliver(payload) hook the GJS host can invoke via evaluate_javascript
21
+ // to dispatch a 'message' event on the proxy port.
22
+ var __gjsifyPorts = {};
23
+
24
+ function __makeProxyPort(portId) {
25
+ var listeners = [];
26
+ var started = false;
27
+ var queued = [];
28
+ function dispatch(d) {
29
+ var ev = { data: d, type: 'message' };
30
+ for (var i = 0; i < listeners.length; i++) {
31
+ try { listeners[i].call(undefined, ev); } catch (_) {}
32
+ }
33
+ }
34
+ function drain() {
35
+ while (queued.length > 0) dispatch(queued.shift());
36
+ }
37
+ var port = {
38
+ postMessage: function(d) {
39
+ handler.postMessage(JSON.stringify({
40
+ __gjsifyPortMessage: portId,
41
+ payload: __encodeBin(d)
42
+ }));
43
+ },
44
+ addEventListener: function(type, fn) {
45
+ if (type !== 'message' || typeof fn !== 'function') return;
46
+ listeners.push(fn);
47
+ if (!started) { started = true; drain(); }
48
+ },
49
+ removeEventListener: function(type, fn) {
50
+ if (type !== 'message') return;
51
+ var i = listeners.indexOf(fn);
52
+ if (i !== -1) listeners.splice(i, 1);
53
+ },
54
+ start: function() { if (!started) { started = true; drain(); } },
55
+ close: function() {
56
+ handler.postMessage(JSON.stringify({ __gjsifyPortClose: portId }));
57
+ listeners = [];
58
+ queued = [];
59
+ delete __gjsifyPorts[portId];
60
+ },
61
+ };
62
+ Object.defineProperty(port, 'onmessage', {
63
+ get: function() { return port.__onmessage || null; },
64
+ set: function(fn) {
65
+ if (port.__onmessage) port.removeEventListener('message', port.__onmessage);
66
+ port.__onmessage = fn;
67
+ if (typeof fn === 'function') port.addEventListener('message', fn);
68
+ },
69
+ });
70
+ __gjsifyPorts[portId] = {
71
+ deliver: function(d) {
72
+ if (started) dispatch(d); else queued.push(d);
73
+ },
74
+ };
75
+ return port;
76
+ }
77
+
78
+ // Walk an incoming GJS → WebView payload for {__gjsifyPort: id}
79
+ // placeholders and replace each with a proxy port instance.
80
+ function __substitutePorts(v) {
81
+ if (v === null || typeof v !== 'object') return v;
82
+ if (typeof v.__gjsifyPort === 'number') return __makeProxyPort(v.__gjsifyPort);
83
+ if (Array.isArray(v)) { for (var i=0;i<v.length;i++) v[i]=__substitutePorts(v[i]); return v; }
84
+ if (__classOf(v) === 'Object') {
85
+ for (var k in v) if (Object.prototype.hasOwnProperty.call(v,k)) v[k]=__substitutePorts(v[k]);
86
+ return v;
87
+ }
88
+ return v;
89
+ }
90
+ window.__gjsifyPortRegistry = __gjsifyPorts;
91
+ window.__gjsifySubstitutePorts = __substitutePorts;
92
+
93
+ function normaliseOrigin(t) {
94
+ if (t === '*') return '*';
95
+ if (t === '/') return location.origin; // source own-origin shortcut
96
+ try { return new URL(t).origin; }
97
+ catch (_) {
98
+ var err = new SyntaxError("Invalid target origin '" + t + "'");
99
+ throw err;
100
+ }
101
+ }
102
+
4
103
  function bridgePostMessage(data, targetOrigin) {
104
+ var t = targetOrigin || '*';
105
+ var resolved = normaliseOrigin(t);
106
+ // Drop silently if the targetOrigin doesn't match GJS host origin.
107
+ // '*' matches anything; otherwise must equal GJS_HOST_ORIGIN.
108
+ if (resolved !== '*' && resolved !== GJS_HOST_ORIGIN) return;
5
109
  handler.postMessage(JSON.stringify({
6
- data: data,
7
- targetOrigin: targetOrigin || '*',
110
+ data: __encodeBin(data),
111
+ targetOrigin: resolved,
8
112
  origin: location.origin
9
113
  }));
10
114
  }
115
+
116
+ // GJS → WebView messages come in via window.dispatchEvent(new MessageEvent(...))
117
+ // injected by evaluate_javascript. We can't intercept that path, so the
118
+ // injection itself decodes placeholders before constructing MessageEvent —
119
+ // see MessageBridge.sendToWebView in message-bridge.ts.
120
+
11
121
  // In a WebKit.WebView loaded via srcdoc, window.parent === window (no real iframe nesting).
12
122
  // window.parent is [LegacyUnforgeable] — cannot be redefined with defineProperty.
13
123
  // Instead, override window.postMessage directly. Since window.parent === window,
@@ -16,6 +126,11 @@ import"./_virtual/_rolldown/runtime.js";import{MessageEvent as e}from"@gjsify/do
16
126
  window.postMessage = function(data, targetOrigin) {
17
127
  bridgePostMessage(data, targetOrigin);
18
128
  };
19
- // Also expose on a safe namespace for explicit use
20
- window.__gjsifyBridge = { postMessage: bridgePostMessage, origPostMessage: origPostMessage };
21
- })();`;var MessageBridge=class{constructor(e){this._windowProxy=null,this._currentUri=`about:blank`,this._signalId=null,this._webView=e,this._userContentManager=e.get_user_content_manager(),this._setupReceiver(),this._injectBootstrapScript()}setWindowProxy(e){this._windowProxy=e}updateUri(e){this._currentUri=e}getLocation(){let e;try{e=new URL(this._currentUri).origin}catch{e=`null`}return{href:this._currentUri,origin:e}}sendToWebView(e,t){let n=JSON.stringify(e),r=JSON.stringify(`gjsify`),i=`window.dispatchEvent(new MessageEvent('message', { data: JSON.parse(${JSON.stringify(n)}), origin: ${r} }));`;this._webView.evaluate_javascript(i,-1,null,null,null).catch(()=>{})}destroy(){this._signalId!==null&&(this._userContentManager.disconnect(this._signalId),this._signalId=null),this._userContentManager.unregister_script_message_handler(r,null),this._windowProxy=null}_setupReceiver(){this._userContentManager.register_script_message_handler(r,null),this._signalId=this._userContentManager.connect(`script-message-received::${r}`,(t,n)=>{if(this._windowProxy)try{let t=n.to_string(),r=JSON.parse(t),i=new e(`message`,{data:r.data,origin:r.origin});this._windowProxy.dispatchEvent(i)}catch(e){console.error(`[IFrame MessageBridge] Error processing message:`,e)}})}_injectBootstrapScript(){let e=new t.UserScript(i,t.UserContentInjectedFrames.ALL_FRAMES,t.UserScriptInjectionTime.START,null,null);this._userContentManager.add_script(e)}};export{MessageBridge};
129
+ // Also expose on a safe namespace for explicit use. Version-tag
130
+ // the bridge so the idempotency guard above can detect a re-run.
131
+ window.__gjsifyBridge = {
132
+ __bridgeVersion: 1,
133
+ postMessage: bridgePostMessage,
134
+ origPostMessage: origPostMessage,
135
+ };
136
+ })();`;var MessageBridge=class{_getTransport(){return this._transport===null&&(this._transport=new e(this)),this._transport}constructor(e){this._windowProxy=null,this._currentUri=`about:blank`,this._signalId=null,this._ports=new Map,this._nextPortId=1,this._transport=null,this._webView=e,this._userContentManager=e.get_user_content_manager(),this._setupReceiver(),this._injectBootstrapScript()}setWindowProxy(e){this._windowProxy=e}updateUri(e){this._currentUri=e}getLocation(){let e;try{e=new URL(this._currentUri).origin}catch{e=`null`}return{href:this._currentUri,origin:e}}_registerTransferredPort(e){if(e._transferred)throw Error(`MessagePort: already transferred`);let t=e._partner;if(!t)throw Error(`MessagePort: partner missing — port already transferred or closed`);let n=this._nextPortId++;return e._transferred=!0,e._partner=null,t._partner=null,t._transport=this._getTransport(),t._portId=n,this._ports.set(n,t),n}_sendPortMessage(e,n){let i=r(n),a=JSON.stringify(i),o=`(function(){${t}var p = window.__gjsifyPortRegistry && window.__gjsifyPortRegistry[${e}]; if (p) p.deliver(__decodeBin(JSON.parse(${JSON.stringify(a)})));})();`;this._webView.evaluate_javascript(o,-1,null,null,null).catch(()=>{})}_closePort(e){this._ports.delete(e);let t=`(function(){ if (window.__gjsifyPortRegistry) delete window.__gjsifyPortRegistry[${e}]; })();`;this._webView.evaluate_javascript(t,-1,null,null,null).catch(()=>{})}sendToWebView(e,n,i){let a=normaliseTargetOrigin(n);if(a!==`*`){let e=this.getLocation().origin;if((a??`https://gjsify.local`)!==e)return}let o=e;if(i&&i.length>0){let t=new Map;for(let e of i){let n=this._registerTransferredPort(e);t.set(e,n)}o=substitutePorts(e,t)}let s=r(o),l=JSON.stringify(s),u=JSON.stringify(c),d=`(function(){${t}var d = __decodeBin(JSON.parse(${JSON.stringify(l)})); if (window.__gjsifySubstitutePorts) d = window.__gjsifySubstitutePorts(d); window.dispatchEvent(new MessageEvent('message', { data: d, origin: ${u} }));})();`;this._webView.evaluate_javascript(d,-1,null,null,null).catch(()=>{})}destroy(){this._signalId!==null&&(this._userContentManager.disconnect(this._signalId),this._signalId=null),this._userContentManager.unregister_script_message_handler(s,null),this._windowProxy=null}_setupReceiver(){this._userContentManager.register_script_message_handler(s,null),this._signalId=this._userContentManager.connect(`script-message-received::${s}`,(e,t)=>{if(this._windowProxy)try{let e=t.to_string(),r=JSON.parse(e);if(typeof r.__gjsifyPortMessage==`number`){let e=r,t=this._ports.get(e.__gjsifyPortMessage);t&&t._receive(n(e.payload));return}if(typeof r.__gjsifyPortClose==`number`){let e=r,t=this._ports.get(e.__gjsifyPortClose);t&&t.close(),this._ports.delete(e.__gjsifyPortClose);return}let a=r,o=a.targetOrigin;if(o!==`*`&&o!==`https://gjsify.local`)return;let s=new i(`message`,{data:n(a.data),origin:a.origin});this._windowProxy.dispatchEvent(s)}catch(e){console.error(`[IFrame MessageBridge] Error processing message:`,e)}})}_injectBootstrapScript(){let e=new a.UserScript(l,a.UserContentInjectedFrames.ALL_FRAMES,a.UserScriptInjectionTime.START,null,null);this._userContentManager.add_script(e)}};function substitutePorts(e,t){let n=new WeakMap;function walk(e){if(typeof e!=`object`||!e)return e;if(e[Symbol.toStringTag]===`MessagePort`&&t.has(e))return{__gjsifyPort:t.get(e)};if(n.has(e))return n.get(e);if(Array.isArray(e)){let t=[];n.set(e,t);for(let n=0;n<e.length;n++)t[n]=walk(e[n]);return t}if(Object.prototype.toString.call(e).slice(8,-1)===`Object`){let t={};n.set(e,t);for(let n of Object.keys(e))t[n]=walk(e[n]);return t}return e}return walk(e)}export{BOOTSTRAP_SCRIPT_FOR_TEST,c as GJS_HOST_ORIGIN,MessageBridge,normaliseTargetOrigin};
@@ -0,0 +1,58 @@
1
+ import"./_virtual/_rolldown/runtime.js";const e=[`ArrayBuffer`,`Uint8Array`,`Uint8ClampedArray`,`Int8Array`,`Uint16Array`,`Int16Array`,`Uint32Array`,`Int32Array`,`BigUint64Array`,`BigInt64Array`,`Float32Array`,`Float64Array`,`DataView`];function classOf(e){return Object.prototype.toString.call(e).slice(8,-1)}function isBinaryType(t){return e.includes(t)}function bytesToBase64(e){let t=e;if(typeof t.toBase64==`function`)return t.toBase64();let n=``;for(let t=0;t<e.length;t++)n+=String.fromCharCode(e[t]);return globalThis.btoa(n)}function base64ToBytes(e){let t=Uint8Array;if(typeof t.fromBase64==`function`)return t.fromBase64(e);let n=globalThis.atob(e),r=new Uint8Array(n.length);for(let e=0;e<n.length;e++)r[e]=n.charCodeAt(e);return r}function makePlaceholder(e){let t=classOf(e);if(e instanceof ArrayBuffer)return{__gjsifyBin:`b64`,type:t,data:bytesToBase64(new Uint8Array(e))};if(e instanceof DataView)return{__gjsifyBin:`b64`,type:`DataView`,data:bytesToBase64(new Uint8Array(e.buffer,e.byteOffset,e.byteLength))};let n=e;return{__gjsifyBin:`b64`,type:t,data:bytesToBase64(new Uint8Array(n.buffer,n.byteOffset,n.byteLength)),length:n.length}}function reconstructFromPlaceholder(e){let t=base64ToBytes(e.data);switch(e.type){case`ArrayBuffer`:return t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength);case`DataView`:return new DataView(t.buffer,t.byteOffset,t.byteLength);case`Uint8Array`:return t;case`Uint8ClampedArray`:return new Uint8ClampedArray(t.buffer,t.byteOffset,t.byteLength);case`Int8Array`:return new Int8Array(t.buffer,t.byteOffset,t.byteLength);case`Uint16Array`:return new Uint16Array(t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength));case`Int16Array`:return new Int16Array(t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength));case`Uint32Array`:return new Uint32Array(t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength));case`Int32Array`:return new Int32Array(t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength));case`BigUint64Array`:return new BigUint64Array(t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength));case`BigInt64Array`:return new BigInt64Array(t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength));case`Float32Array`:return new Float32Array(t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength));case`Float64Array`:return new Float64Array(t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength))}}function isPlaceholder(e){return typeof e==`object`&&!!e&&e.__gjsifyBin===`b64`&&typeof e.type==`string`&&typeof e.data==`string`}function encodeBinariesForJson(e){let t=new WeakMap;function walk(e){if(typeof e!=`object`||!e)return e;let n=classOf(e);if(isBinaryType(n))return makePlaceholder(e);if(t.has(e))return t.get(e);if(Array.isArray(e)){let n=[];t.set(e,n);for(let t=0;t<e.length;t++)n[t]=walk(e[t]);return n}if(n===`Object`){let n={};t.set(e,n);for(let t of Object.keys(e))n[t]=walk(e[t]);return n}return e}return walk(e)}function decodeBinariesFromJson(e){let t=new WeakSet;function walk(e){if(typeof e!=`object`||!e)return e;if(isPlaceholder(e))return reconstructFromPlaceholder(e);if(t.has(e))return e;if(t.add(e),Array.isArray(e)){for(let t=0;t<e.length;t++)e[t]=walk(e[t]);return e}if(classOf(e)===`Object`){for(let t of Object.keys(e))e[t]=walk(e[t]);return e}return e}return walk(e)}const t=`
2
+ var __binKey = '__gjsifyBin';
3
+ var __binVal = 'b64';
4
+ var __binCtors = ${JSON.stringify(e)};
5
+ function __classOf(v){ return Object.prototype.toString.call(v).slice(8,-1); }
6
+ function __isBin(n){ return __binCtors.indexOf(n) !== -1; }
7
+ function __b64enc(bytes){
8
+ if (typeof bytes.toBase64 === 'function') return bytes.toBase64();
9
+ var s=''; for (var i=0;i<bytes.length;i++) s+=String.fromCharCode(bytes[i]);
10
+ return btoa(s);
11
+ }
12
+ function __b64dec(s){
13
+ if (typeof Uint8Array.fromBase64 === 'function') return Uint8Array.fromBase64(s);
14
+ var bin = atob(s); var u = new Uint8Array(bin.length);
15
+ for (var i=0;i<bin.length;i++) u[i] = bin.charCodeAt(i);
16
+ return u;
17
+ }
18
+ function __mkPlaceholder(v){
19
+ var t = __classOf(v);
20
+ if (v instanceof ArrayBuffer) return {__gjsifyBin:__binVal, type:t, data:__b64enc(new Uint8Array(v))};
21
+ if (v instanceof DataView) return {__gjsifyBin:__binVal, type:'DataView', data:__b64enc(new Uint8Array(v.buffer, v.byteOffset, v.byteLength))};
22
+ var ta = v; return {__gjsifyBin:__binVal, type:t, data:__b64enc(new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength)), length:ta.length};
23
+ }
24
+ function __reconstruct(p){
25
+ var bytes = __b64dec(p.data);
26
+ switch (p.type) {
27
+ case 'ArrayBuffer': return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
28
+ case 'DataView': return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
29
+ case 'Uint8Array': return bytes;
30
+ case 'Uint8ClampedArray': return new Uint8ClampedArray(bytes.buffer, bytes.byteOffset, bytes.byteLength);
31
+ case 'Int8Array': return new Int8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
32
+ case 'Uint16Array': return new Uint16Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
33
+ case 'Int16Array': return new Int16Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
34
+ case 'Uint32Array': return new Uint32Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
35
+ case 'Int32Array': return new Int32Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
36
+ case 'BigUint64Array': return new BigUint64Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
37
+ case 'BigInt64Array': return new BigInt64Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
38
+ case 'Float32Array': return new Float32Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
39
+ case 'Float64Array': return new Float64Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
40
+ }
41
+ }
42
+ function __isPlaceholder(v){ return v && typeof v==='object' && v[__binKey]===__binVal && typeof v.type==='string' && typeof v.data==='string'; }
43
+ function __encodeBin(v){
44
+ if (v === null || typeof v !== 'object') return v;
45
+ var c = __classOf(v);
46
+ if (__isBin(c)) return __mkPlaceholder(v);
47
+ if (Array.isArray(v)) { var o=[]; for (var i=0;i<v.length;i++) o[i]=__encodeBin(v[i]); return o; }
48
+ if (c==='Object') { var o={}; for (var k in v) if (Object.prototype.hasOwnProperty.call(v,k)) o[k]=__encodeBin(v[k]); return o; }
49
+ return v;
50
+ }
51
+ function __decodeBin(v){
52
+ if (v === null || typeof v !== 'object') return v;
53
+ if (__isPlaceholder(v)) return __reconstruct(v);
54
+ if (Array.isArray(v)) { for (var i=0;i<v.length;i++) v[i]=__decodeBin(v[i]); return v; }
55
+ if (__classOf(v)==='Object') { for (var k in v) if (Object.prototype.hasOwnProperty.call(v,k)) v[k]=__decodeBin(v[k]); return v; }
56
+ return v;
57
+ }
58
+ `;export{t as BINARY_SERIALIZER_INJECTED_SRC,decodeBinariesFromJson,encodeBinariesForJson};
@@ -0,0 +1,28 @@
1
+ import { MessagePort, MessageChannel } from '@gjsify/message-channel';
2
+ import type { MessagePortTransport } from '@gjsify/message-channel';
3
+ import type { MessageBridge } from './message-bridge.js';
4
+ export { MessagePort, MessageChannel };
5
+ /** @deprecated Re-exported alias of `MessagePort` from
6
+ * `@gjsify/message-channel`. Use the standard name. */
7
+ export declare const IFrameMessagePort: typeof MessagePort;
8
+ export type IFrameMessagePort = MessagePort;
9
+ /** @deprecated Re-exported alias of `MessageChannel` from
10
+ * `@gjsify/message-channel`. Use the standard name. */
11
+ export declare const IFrameMessageChannel: typeof MessageChannel;
12
+ export type IFrameMessageChannel = MessageChannel;
13
+ /**
14
+ * MessagePortTransport adapter that routes a port's outbound messages
15
+ * through a `MessageBridge` instance. Used internally by the bridge
16
+ * when it picks up `MessagePort` instances from a `postMessage`
17
+ * transferList — the transferred port's partner is attached to a
18
+ * `BridgePortTransport` so its `.postMessage` flows over the WebKit
19
+ * IPC instead of the in-process partner (which has been detached).
20
+ *
21
+ * @internal
22
+ */
23
+ export declare class BridgePortTransport implements MessagePortTransport {
24
+ private _bridge;
25
+ constructor(_bridge: MessageBridge);
26
+ send(portId: number, data: unknown): void;
27
+ close(portId: number): void;
28
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -1,5 +1,6 @@
1
1
  import { EventTarget } from '@gjsify/dom-events';
2
2
  import type { MessageBridge } from './message-bridge.js';
3
+ import type { MessagePort } from '@gjsify/message-channel';
3
4
  /**
4
5
  * Lightweight Window-like proxy returned by `HTMLIFrameElement.contentWindow`.
5
6
  *
@@ -20,10 +21,16 @@ export declare class IFrameWindowProxy extends EventTarget {
20
21
  /**
21
22
  * Send a message to the iframe content.
22
23
  *
23
- * @param message - Data to send (must be JSON-serializable)
24
- * @param targetOrigin - Target origin for the message. Default: '*'
24
+ * @param message - Data to send (must be JSON-serializable + base64-encodable
25
+ * binaries see @gjsify/iframe/serialize for supported binary types).
26
+ * @param targetOrigin - Target origin for the message. Default: '*'.
27
+ * @param transfer - Optional list of `MessagePort` instances to
28
+ * transfer. Each transferred port is detached locally; its surviving
29
+ * partner becomes the GJS-side endpoint of a bidirectional channel
30
+ * routed through the bridge. The WebView receives proxy ports under
31
+ * `MessageEvent.data` wherever the original ports appeared in `message`.
25
32
  */
26
- postMessage(message: unknown, targetOrigin?: string): void;
33
+ postMessage(message: unknown, targetOrigin?: string, transfer?: MessagePort[]): void;
27
34
  /**
28
35
  * Read-only location reflecting the current WebView URI.
29
36
  */
@@ -1,5 +1,6 @@
1
1
  export { HTMLIFrameElement } from './html-iframe-element.js';
2
2
  export { IFrameBridge } from './iframe-bridge.js';
3
3
  export { IFrameWindowProxy } from './iframe-window-proxy.js';
4
- export { MessageBridge } from './message-bridge.js';
4
+ export { MessageBridge, GJS_HOST_ORIGIN } from './message-bridge.js';
5
+ export { MessageChannel, MessagePort, IFrameMessageChannel, IFrameMessagePort } from './iframe-message-channel.js';
5
6
  export type { IFrameBridgeOptions, IFrameReadyCallback, IFrameMessageData } from './types/index.js';
@@ -1,5 +1,46 @@
1
1
  import WebKit from 'gi://WebKit?version=6.0';
2
2
  import type { IFrameWindowProxy } from './iframe-window-proxy.js';
3
+ import type { MessagePort } from '@gjsify/message-channel';
4
+ /**
5
+ * Synthetic origin attached to messages travelling FROM the GJS host
6
+ * INTO the WebView. The WebView can use this in a targetOrigin filter to
7
+ * accept messages only when they originate from its hosting GJS process
8
+ * (vs. any other code that might inject script via developer tools).
9
+ *
10
+ * Uses the `https://` scheme so WHATWG URL parsing gives a real origin
11
+ * string (non-special schemes like `gjsify://` return `null` as origin
12
+ * per the URL spec, which would break the equality comparison the
13
+ * bridge does on every message). `.local` is the standard RFC 6762
14
+ * suffix for non-routable mDNS names, so it can't collide with a real
15
+ * site.
16
+ */
17
+ export declare const GJS_HOST_ORIGIN = "https://gjsify.local";
18
+ /**
19
+ * Per HTML spec for window.postMessage:
20
+ * - '*' → no origin restriction
21
+ * - URL string → only deliver if destination origin matches URL.origin
22
+ * - '/' → only deliver if destination origin matches source origin
23
+ * - other → throw SyntaxError
24
+ *
25
+ * Returns the canonical origin string (e.g. 'https://example.com'),
26
+ * `'*'`, or `null` if the input is `'/'`. Throws `SyntaxError` for
27
+ * malformed input.
28
+ */
29
+ export declare function normaliseTargetOrigin(targetOrigin: string): string | null;
30
+ /**
31
+ * Bootstrap script injected into every WebView page at document start.
32
+ * Provides the `window.parent.postMessage()` bridge from WebView content back to GJS.
33
+ *
34
+ * The script:
35
+ * 1. Gets the WebKit message handler registered under CHANNEL_NAME
36
+ * 2. Creates a parent proxy with a postMessage() that sends via the WebKit handler
37
+ * 3. Overrides window.parent to point to the proxy
38
+ */
39
+ /**
40
+ * @internal Exposed only for unit testing — verifies the bootstrap
41
+ * idempotency guard survives refactors. Not part of the public API.
42
+ */
43
+ export declare const BOOTSTRAP_SCRIPT_FOR_TEST: () => string;
3
44
  /**
4
45
  * Manages bidirectional postMessage communication between GJS and a WebKit.WebView.
5
46
  *
@@ -17,6 +58,13 @@ export declare class MessageBridge {
17
58
  private _windowProxy;
18
59
  private _currentUri;
19
60
  private _signalId;
61
+ /** GJS-side endpoints of transferred ports, keyed by per-bridge id.
62
+ * When the WebView sends a `{__gjsifyPortMessage: id, payload}` envelope,
63
+ * we route the payload to `_ports.get(id)._receive(decoded)`. */
64
+ private _ports;
65
+ private _nextPortId;
66
+ private _transport;
67
+ private _getTransport;
20
68
  constructor(webView: WebKit.WebView);
21
69
  /** Connect the IFrameWindowProxy that will receive messages from the WebView */
22
70
  setWindowProxy(proxy: IFrameWindowProxy): void;
@@ -31,7 +79,27 @@ export declare class MessageBridge {
31
79
  * Send a message from GJS to the WebView content.
32
80
  * Dispatches a standard MessageEvent on the WebView's window object.
33
81
  */
34
- sendToWebView(data: unknown, _targetOrigin: string): void;
82
+ /**
83
+ * Register a port pair for cross-bridge transfer. Called by
84
+ * IFrameWindowProxy.postMessage when it sees a `MessagePort` in the
85
+ * transferList. Returns the port-id placeholder that should be
86
+ * substituted into the outgoing payload.
87
+ *
88
+ * Marks the transferred port as detached locally and wires its
89
+ * surviving partner with a `BridgePortTransport` so the partner's
90
+ * postMessage routes back over the WebKit bridge instead of the
91
+ * (now-null) in-process partner.
92
+ */
93
+ _registerTransferredPort(port: MessagePort): number;
94
+ /** @internal Called by MessagePort.postMessage when its partner
95
+ * was transferred to the WebView. Dispatches the data onto the
96
+ * WebView-side proxy port via evaluate_javascript. */
97
+ _sendPortMessage(portId: number, data: unknown): void;
98
+ /** @internal Called by MessagePort.close to tear down the
99
+ * bridge-side registration. The WebView side keeps its proxy port
100
+ * alive in user-script land but subsequent .deliver calls go nowhere. */
101
+ _closePort(portId: number): void;
102
+ sendToWebView(data: unknown, targetOrigin: string, transfer?: MessagePort[]): void;
35
103
  /** Clean up signal handlers */
36
104
  destroy(): void;
37
105
  /**
@@ -39,6 +107,12 @@ export declare class MessageBridge {
39
107
  * Registers a script message handler and connects to the signal.
40
108
  */
41
109
  private _setupReceiver;
110
+ /**
111
+ * Walk the user payload and replace each MessagePort instance
112
+ * (transferred via the transferList) with a {__gjsifyPort: id}
113
+ * placeholder. Non-transferred ports are passed through untouched —
114
+ * matches W3C semantics where only ports in transferList are detached.
115
+ */
42
116
  /**
43
117
  * Inject the bootstrap script into the WebView so that
44
118
  * window.parent.postMessage() bridges back to GJS.
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Walk `value`, substituting any binary buffer/view with a base64
3
+ * placeholder. Returns the substituted tree (copy-on-write — input
4
+ * untouched). Cycles produce cycle-preserving output.
5
+ */
6
+ export declare function encodeBinariesForJson(value: unknown): unknown;
7
+ /**
8
+ * Reverse of `encodeBinariesForJson`. Walks the tree in place and
9
+ * replaces every placeholder with a reconstructed binary view. Returns
10
+ * the same tree reference (for parity with the encoder's contract).
11
+ */
12
+ export declare function decodeBinariesFromJson(value: unknown): unknown;
13
+ /**
14
+ * Inlined twin of the encode/decode pair as a string. Injected into the
15
+ * WebView bootstrap so the WebKit-side window.postMessage override can
16
+ * encode binaries before handing the JSON to GJS, and decode incoming
17
+ * GJS-originated placeholders back into typed arrays before dispatching
18
+ * MessageEvent.
19
+ *
20
+ * Kept in sync with the TS source above by manual review — small enough
21
+ * to audit at a glance, and the alternative (build-time string template)
22
+ * would tangle the bridge build pipeline for marginal gain.
23
+ */
24
+ export declare const BINARY_SERIALIZER_INJECTED_SRC: string;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/iframe",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "HTMLIFrameElement for GJS, backed by WebKit.WebView",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -37,12 +37,13 @@
37
37
  "@girs/gtk-4.0": "4.23.0-4.0.0-rc.15",
38
38
  "@girs/javascriptcore-6.0": "2.52.1-4.0.0-rc.15",
39
39
  "@girs/webkit-6.0": "2.52.1-4.0.0-rc.15",
40
- "@gjsify/dom-elements": "^0.4.11",
41
- "@gjsify/dom-events": "^0.4.11"
40
+ "@gjsify/dom-elements": "^0.4.13",
41
+ "@gjsify/dom-events": "^0.4.13",
42
+ "@gjsify/message-channel": "^0.4.13"
42
43
  },
43
44
  "devDependencies": {
44
- "@gjsify/cli": "^0.4.11",
45
- "@gjsify/unit": "^0.4.11",
45
+ "@gjsify/cli": "^0.4.13",
46
+ "@gjsify/unit": "^0.4.13",
46
47
  "@types/node": "^25.6.2",
47
48
  "typescript": "^6.0.3"
48
49
  }