/** * JavaScript string injected into WebViews via `injectedJavaScriptBeforeContentLoaded`. * Runs before Bolt's iframe code loads, patching the environment so the WebView * behaves like an iframe hosted by a parent window. * * What it patches: * 1. window.parent — returns a fake Window whose postMessage() routes through RN bridge * 2. window.addEventListener('message') — intercepts to inject correct event.origin * 3. isIframe() detection — ensures window.location !== window.parent.location returns true * 4. Virtual MessagePort — emulates MessageChannel/MessagePort for RPC channels * * Envelope format for bridge messages: * { * __boltBridge: true, * direction: 'inbound' | 'outbound', * type: 'postMessage' | 'portMessage' | 'bridgeReady', * data?: unknown, * virtualPortId?: string, * } * * CRITICAL: Bolt's serialization layer (libs/base/messaging/Serialization.ts) sends * JSON strings, not objects. The bridge must preserve this — data payloads within * envelopes are JSON strings when coming from Bolt code. */ export declare const INJECTED_BRIDGE_JS = "\n(function() {\n 'use strict';\n\n // Only install the bridge in the top-level WebView frame.\n // Bolt's credit-card-input loads sub-iframes for each field (PCI compliance).\n // Those sub-frames must communicate with their real parent (the main frame),\n // not with React Native. Check BEFORE we patch window.parent.\n if (window.parent !== window) return;\n\n // Guard against double-injection\n if (window.__boltBridgeInitialized) return;\n window.__boltBridgeInitialized = true;\n\n var BOLT_ORIGIN = window.location.origin;\n var virtualPorts = {};\n var pendingMessages = [];\n var bridgeReady = false;\n\n // \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n function sendToNative(envelope) {\n if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {\n window.ReactNativeWebView.postMessage(JSON.stringify(envelope));\n }\n }\n\n function createEnvelope(type, data, virtualPortId) {\n return {\n __boltBridge: true,\n direction: 'outbound',\n type: type,\n data: data,\n virtualPortId: virtualPortId || undefined\n };\n }\n\n // \u2500\u2500 Virtual MessagePort \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n function VirtualMessagePort(portId) {\n this.portId = portId;\n this.onmessage = null;\n this._started = false;\n this._queue = [];\n this._listeners = [];\n virtualPorts[portId] = this;\n }\n\n VirtualMessagePort.prototype.postMessage = function(data) {\n sendToNative(createEnvelope('portMessage', data, this.portId));\n };\n\n VirtualMessagePort.prototype.start = function() {\n this._started = true;\n // Flush queued messages\n var queue = this._queue;\n this._queue = [];\n for (var i = 0; i < queue.length; i++) {\n this._dispatchMessage(queue[i]);\n }\n };\n\n VirtualMessagePort.prototype.close = function() {\n delete virtualPorts[this.portId];\n };\n\n // EventTarget-style API: storm's RPC layer (rpc.ts) registers handlers via\n // port.addEventListener('message', ...), so we must support it in addition\n // to the .onmessage property assignment path.\n VirtualMessagePort.prototype.addEventListener = function(type, listener) {\n if (type !== 'message' || typeof listener !== 'function') return;\n if (this._listeners.indexOf(listener) === -1) {\n this._listeners.push(listener);\n }\n };\n\n VirtualMessagePort.prototype.removeEventListener = function(type, listener) {\n if (type !== 'message') return;\n var idx = this._listeners.indexOf(listener);\n if (idx !== -1) this._listeners.splice(idx, 1);\n };\n\n VirtualMessagePort.prototype._dispatchMessage = function(data) {\n // Real MessagePort message events report origin === '' (per spec).\n // storm's defineRPCHandlers filters by portOrigin: '' so we must match.\n var event = { data: data, origin: '', source: null };\n if (this.onmessage) {\n try { this.onmessage(event); } catch (e) { console.error('[BoltBridge] port onmessage error:', e); }\n }\n for (var i = 0; i < this._listeners.length; i++) {\n try { this._listeners[i](event); } catch (e) { console.error('[BoltBridge] port listener error:', e); }\n }\n };\n\n VirtualMessagePort.prototype._receive = function(data) {\n if (this._started) {\n this._dispatchMessage(data);\n } else {\n this._queue.push(data);\n }\n };\n\n // \u2500\u2500 Fake parent window \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n var fakeParentLocation = { href: 'https://merchant-app.example.com', origin: 'https://merchant-app.example.com' };\n\n var fakeParent = {\n postMessage: function(data, targetOrigin, transfer) {\n var envelope = createEnvelope('postMessage', data);\n\n // If a MessagePort is being transferred, create a virtual port\n if (transfer && transfer.length > 0) {\n var portId = 'vp_' + Math.random().toString(36).substr(2, 9);\n var vPort = new VirtualMessagePort(portId);\n envelope.virtualPortId = portId;\n // The transfer array in the real API would contain MessagePort objects.\n // We replace them with virtual ports so the iframe code can use them.\n // Store reference so the iframe's port variable points to our virtual port.\n if (transfer[0] && typeof transfer[0] === 'object') {\n // Copy virtual port methods onto the transferred port object\n transfer[0].__virtualPortId = portId;\n }\n }\n\n sendToNative(envelope);\n },\n location: fakeParentLocation,\n // The parent should look like a Window\n window: undefined, // will be set below\n document: { referrer: '' },\n frames: [],\n length: 0,\n closed: false,\n opener: null,\n self: undefined // will be set below\n };\n fakeParent.window = fakeParent;\n fakeParent.self = fakeParent;\n\n // \u2500\u2500 Patch window.parent \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // libs/base/utils/Parent.ts checks window.location !== window.parent.location\n // to determine isIframe(). We need this to return true.\n\n try {\n Object.defineProperty(window, 'parent', {\n get: function() { return fakeParent; },\n configurable: true\n });\n } catch (e) {\n // Fallback: if defineProperty fails (some WebView engines), set directly\n window.parent = fakeParent;\n }\n\n // \u2500\u2500 Patch addEventListener('message') \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Bolt's Listener.ts validates event.origin. We intercept message handlers\n // to create synthetic MessageEvents with the correct origin.\n\n var originalAddEventListener = window.addEventListener.bind(window);\n var originalRemoveEventListener = window.removeEventListener.bind(window);\n var messageListeners = [];\n\n window.addEventListener = function(type, listener, options) {\n if (type === 'message' && typeof listener === 'function') {\n // Prevent duplicate registrations of the same listener\n if (messageListeners.indexOf(listener) === -1) {\n messageListeners.push(listener);\n }\n return;\n }\n return originalAddEventListener(type, listener, options);\n };\n\n window.removeEventListener = function(type, listener, options) {\n if (type === 'message' && typeof listener === 'function') {\n var idx = messageListeners.indexOf(listener);\n if (idx !== -1) {\n messageListeners.splice(idx, 1);\n }\n return;\n }\n return originalRemoveEventListener(type, listener, options);\n };\n\n // Dispatch a synthetic message event to all registered listeners\n function dispatchBridgeMessage(data, ports) {\n var event = {\n data: data,\n origin: BOLT_ORIGIN,\n source: fakeParent,\n ports: ports || [],\n isTrusted: true,\n type: 'message',\n // Minimal Event interface\n preventDefault: function() {},\n stopPropagation: function() {},\n stopImmediatePropagation: function() {}\n };\n\n for (var i = 0; i < messageListeners.length; i++) {\n try {\n messageListeners[i](event);\n } catch (err) {\n console.error('[BoltBridge] Error in message listener:', err);\n }\n }\n }\n\n // \u2500\u2500 Forward real DOM message events from sub-iframes \u2500\u2500\u2500\u2500\n // Sub-iframes (e.g., Cardinal Commerce DDC) use real postMessage to\n // communicate with the top frame. Since we intercepted addEventListener,\n // those events won't reach the captured listeners unless we forward them.\n originalAddEventListener('message', function(event) {\n // Skip bridge envelopes \u2014 those are handled by __boltBridgeReceive\n if (event.data && typeof event.data === 'object' && event.data.__boltBridge) return;\n if (typeof event.data === 'string') {\n try {\n var parsed = JSON.parse(event.data);\n if (parsed && parsed.__boltBridge) return;\n } catch(e) {}\n }\n\n // Forward the real event to all captured message listeners\n for (var i = 0; i < messageListeners.length; i++) {\n try {\n messageListeners[i](event);\n } catch (err) {\n console.error('[BoltBridge] Error in message listener (forwarded):', err);\n }\n }\n });\n\n // \u2500\u2500 Receive messages from React Native \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // React Native sends messages by evaluating JS on the WebView.\n // We expose a global function the native side calls.\n window.__boltBridgeReceive = function(envelopeStr) {\n try {\n var envelope = typeof envelopeStr === 'string' ? JSON.parse(envelopeStr) : envelopeStr;\n\n if (!envelope || !envelope.__boltBridge) return;\n\n if (envelope.type === 'postMessage') {\n var ports = [];\n if (envelope.virtualPortId) {\n var port = virtualPorts[envelope.virtualPortId] || new VirtualMessagePort(envelope.virtualPortId);\n ports.push(port);\n }\n dispatchBridgeMessage(envelope.data, ports);\n } else if (envelope.type === 'portMessage' && envelope.virtualPortId) {\n var targetPort = virtualPorts[envelope.virtualPortId];\n if (targetPort) {\n targetPort._receive(envelope.data);\n }\n }\n } catch (err) {\n console.error('[BoltBridge] Error receiving message:', err);\n }\n };\n\n // \u2500\u2500 Signal bridge ready \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Since this script runs via injectedJavaScriptBeforeContentLoaded, we\n // wait for DOMContentLoaded so Bolt's iframe code has loaded and\n // registered its message listeners before we flush any queued messages.\n\n function signalReady() {\n if (bridgeReady) return;\n bridgeReady = true;\n sendToNative(createEnvelope('bridgeReady', null));\n }\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', signalReady);\n } else {\n signalReady();\n }\n})();\ntrue; // Required for injectedJavaScriptBeforeContentLoaded\n"; //# sourceMappingURL=injectedBridge.d.ts.map