Skip to main content

HOST_API_BRIDGE_SCRIPT

Constant HOST_API_BRIDGE_SCRIPT 

Source
pub const HOST_API_BRIDGE_SCRIPT: &str = "\n(function() {\n    \'use strict\';\n    if (window.__hostApiBridge) { return; }\n    window.__hostApiBridge = true;\n    window.__HOST_WEBVIEW_MARK__ = true;\n    var ch = new MessageChannel();\n    window.__HOST_API_PORT__ = ch.port2;\n    ch.port2.start();\n    var port1 = ch.port1;\n    port1.start();\n    if (!window.host) { window.host = {}; }\n    if (!window.host.storage) { window.host.storage = {}; }\n    port1.onmessage = function(ev) {\n        var data = ev.data;\n        if (!data) { console.warn(\'[host-bridge] data is falsy, dropping\'); return; }\n        var bytes;\n        if (data instanceof Uint8Array) { bytes = data; }\n        else if (data instanceof ArrayBuffer) { bytes = new Uint8Array(data); }\n        else if (ArrayBuffer.isView(data)) { bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); }\n        else { console.warn(\'[host-bridge] unknown data type: \' + typeof data + \' constructor=\' + (data.constructor ? data.constructor.name : \'?\') + \', dropping\'); return; }\n        var binary = \'\';\n        for (var i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); }\n        try {\n            window.webkit.messageHandlers.hostApi.postMessage(btoa(binary));\n        } catch(e) {\n            console.error(\'[host-bridge] postMessage to hostApi FAILED:\', e.message);\n        }\n    };\n    window.__hostApiReply = function(b64) {\n        try {\n            var binary = atob(b64);\n            var bytes = new Uint8Array(binary.length);\n            for (var i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); }\n            port1.postMessage(bytes);\n        } catch(e) { console.error(\'[host-bridge] reply failed:\', e.message); }\n    };\n    // --- Storage bridge (shared between native and wry bridges) ---\n    var storagePending = new Map();\n    var nextStorageCallId = 1;\n    var storageTextEncoder = typeof TextEncoder !== \'undefined\' ? new TextEncoder() : null;\n    var storageTextDecoder = typeof TextDecoder !== \'undefined\' ? new TextDecoder() : null;\n\n    function makeBridgeError(code, message) {\n        var error = new Error(message);\n        error.code = code;\n        return error;\n    }\n\n    function toUint8Array(data) {\n        if (data instanceof Uint8Array) return data;\n        if (data instanceof ArrayBuffer) return new Uint8Array(data);\n        if (ArrayBuffer.isView(data))\n            return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n        return null;\n    }\n\n    function concatBytes(parts) {\n        var total = 0;\n        for (var i = 0; i < parts.length; i++) total += parts[i].length;\n        var out = new Uint8Array(total);\n        var offset = 0;\n        for (var j = 0; j < parts.length; j++) {\n            out.set(parts[j], offset);\n            offset += parts[j].length;\n        }\n        return out;\n    }\n\n    function encodeCompactU32(value) {\n        if (value < 0x40) return new Uint8Array([(value << 2) & 0xff]);\n        if (value < 0x4000) {\n            var twoByte = (value << 2) | 0x01;\n            return new Uint8Array([twoByte & 0xff, (twoByte >>> 8) & 0xff]);\n        }\n        if (value < 0x40000000) {\n            var fourByte = ((value << 2) | 0x02) >>> 0;\n            return new Uint8Array([\n                fourByte & 0xff,\n                (fourByte >>> 8) & 0xff,\n                (fourByte >>> 16) & 0xff,\n                (fourByte >>> 24) & 0xff\n            ]);\n        }\n        return new Uint8Array([\n            0x03,\n            value & 0xff,\n            (value >>> 8) & 0xff,\n            (value >>> 16) & 0xff,\n            (value >>> 24) & 0xff\n        ]);\n    }\n\n    function readCompactU32(bytes, offset) {\n        var first = bytes[offset++];\n        switch (first & 0x03) {\n            case 0x00:\n                return { value: first >> 2, offset: offset };\n            case 0x01:\n                if (offset >= bytes.length) throw makeBridgeError(\'ERR_INVALID_ARGUMENT\', \'unexpected end of compact integer\');\n                return { value: ((first | (bytes[offset++] << 8)) >> 2) & 0x3fff, offset: offset };\n            case 0x02:\n                if (offset + 2 >= bytes.length) throw makeBridgeError(\'ERR_INVALID_ARGUMENT\', \'unexpected end of compact integer\');\n                var value = (first | (bytes[offset] << 8) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 24)) >>> 0;\n                offset += 3;\n                return { value: value >>> 2, offset: offset };\n            default:\n                var byteCount = (first >> 2) + 4;\n                if (byteCount > 4 || offset + byteCount > bytes.length) {\n                    throw makeBridgeError(\'ERR_INVALID_ARGUMENT\', \'compact integer exceeds bridge limit\');\n                }\n                var large = 0;\n                for (var i = 0; i < byteCount; i++) {\n                    large |= bytes[offset++] << (i * 8);\n                }\n                return { value: large >>> 0, offset: offset };\n        }\n    }\n\n    function encodeStringValue(value) {\n        if (!storageTextEncoder) {\n            throw makeBridgeError(\'ERR_HOST_RUNTIME_UNAVAILABLE\', \'TextEncoder is unavailable\');\n        }\n        var bytes = storageTextEncoder.encode(String(value));\n        return concatBytes([encodeCompactU32(bytes.length), bytes]);\n    }\n\n    function encodeVarBytes(bytes) {\n        return concatBytes([encodeCompactU32(bytes.length), bytes]);\n    }\n\n    function readStringValue(bytes, offset) {\n        if (!storageTextDecoder) {\n            throw makeBridgeError(\'ERR_HOST_RUNTIME_UNAVAILABLE\', \'TextDecoder is unavailable\');\n        }\n        var length = readCompactU32(bytes, offset);\n        offset = length.offset;\n        if (offset + length.value > bytes.length) {\n            throw makeBridgeError(\'ERR_INVALID_ARGUMENT\', \'unexpected end of string payload\');\n        }\n        var value = storageTextDecoder.decode(bytes.slice(offset, offset + length.value));\n        return { value: value, offset: offset + length.value };\n    }\n\n    function readVarBytes(bytes, offset) {\n        var length = readCompactU32(bytes, offset);\n        offset = length.offset;\n        if (offset + length.value > bytes.length) {\n            throw makeBridgeError(\'ERR_INVALID_ARGUMENT\', \'unexpected end of byte payload\');\n        }\n        return { value: bytes.slice(offset, offset + length.value), offset: offset + length.value };\n    }\n\n    function buildStorageRequest(tag, requestId, key, valueBytes) {\n        var parts = [encodeStringValue(requestId), new Uint8Array([tag, 0]), encodeStringValue(key)];\n        if (valueBytes) parts.push(encodeVarBytes(valueBytes));\n        return concatBytes(parts);\n    }\n\n    function decodeStorageResponse(bytes, requestId) {\n        var cursor = readStringValue(bytes, 0);\n        if (cursor.value !== requestId) {\n            throw makeBridgeError(\'ERR_INVALID_ARGUMENT\', \'host storage response requestId mismatch\');\n        }\n        var offset = cursor.offset;\n        if (offset + 3 > bytes.length) {\n            throw makeBridgeError(\'ERR_INVALID_ARGUMENT\', \'host storage response is truncated\');\n        }\n        var tag = bytes[offset++];\n        offset += 1; // v1\n        var resultTag = bytes[offset++];\n        if (resultTag !== 0) {\n            var reason = \'host storage request failed\';\n            try {\n                offset += 1; // skip error variant tag\n                var reasonResult = readStringValue(bytes, offset);\n                if (reasonResult.value) reason = reasonResult.value;\n            } catch (e) { /* use default if reason cannot be decoded */ }\n            throw makeBridgeError(\'ERR_HOST_REJECTED\', reason);\n        }\n        if (tag === 13) {\n            if (offset >= bytes.length) {\n                throw makeBridgeError(\'ERR_INVALID_ARGUMENT\', \'host storage read response is truncated\');\n            }\n            var optionTag = bytes[offset++];\n            if (optionTag === 0) return null;\n            if (optionTag !== 1) {\n                throw makeBridgeError(\'ERR_INVALID_ARGUMENT\', \'invalid host storage option tag\');\n            }\n            var data = readVarBytes(bytes, offset);\n            if (!storageTextDecoder) {\n                throw makeBridgeError(\'ERR_HOST_RUNTIME_UNAVAILABLE\', \'TextDecoder is unavailable\');\n            }\n            return storageTextDecoder.decode(data.value);\n        }\n        if (tag === 15 || tag === 17) return true;\n        throw makeBridgeError(\'ERR_INVALID_ARGUMENT\', \'unexpected host storage response tag: \' + tag);\n    }\n\n    function sendStorageRequest(tag, key, valueBytes) {\n        return new Promise(function(resolve, reject) {\n            if (!window.__HOST_API_PORT__) {\n                reject(makeBridgeError(\'ERR_BINARY_BRIDGE_UNAVAILABLE\', \'Binary host-api bridge is unavailable\'));\n                return;\n            }\n            var requestId = \'host-storage-\' + (nextStorageCallId++);\n            storagePending.set(requestId, { resolve: resolve, reject: reject });\n            try {\n                window.__HOST_API_PORT__.postMessage(buildStorageRequest(tag, requestId, key, valueBytes));\n            } catch (e) {\n                storagePending.delete(requestId);\n                reject(e);\n            }\n        });\n    }\n\n    function handleStorageMessage(ev) {\n        if (storagePending.size === 0) return;\n        var bytes = toUint8Array(ev.data);\n        if (!bytes) return;\n        var requestId;\n        try {\n            requestId = readStringValue(bytes, 0).value;\n        } catch (e) {\n            return;\n        }\n        var entry = storagePending.get(requestId);\n        if (!entry) return;\n        storagePending.delete(requestId);\n        try {\n            entry.resolve(decodeStorageResponse(bytes, requestId));\n        } catch (e) {\n            entry.reject(e);\n        }\n    }\n\n    if (window.__HOST_API_PORT__.addEventListener) {\n        window.__HOST_API_PORT__.addEventListener(\'message\', handleStorageMessage);\n    }\n\n    if (typeof window.host.storage.get !== \'function\') {\n        window.host.storage.get = function(key) {\n            return sendStorageRequest(12, key);\n        };\n    }\n    if (typeof window.host.storage.set !== \'function\') {\n        window.host.storage.set = function(key, value) {\n            if (!storageTextEncoder) {\n                return Promise.reject(makeBridgeError(\'ERR_HOST_RUNTIME_UNAVAILABLE\', \'TextEncoder is unavailable\'));\n            }\n            return sendStorageRequest(14, key, storageTextEncoder.encode(String(value)));\n        };\n    }\n    if (typeof window.host.storage.remove !== \'function\') {\n        window.host.storage.remove = function(key) {\n            return sendStorageRequest(16, key);\n        };\n    }\n\n})();\n";
Expand description

JavaScript injected before the Polkadot app loads. Sets up:

  1. window.__HOST_WEBVIEW_MARK__ = true — SDK webview detection
  2. MessageChannel with port2 as window.__HOST_API_PORT__
  3. Binary message forwarding between port1 and native (base64)