hostBridgeBootstrapJs function
Builds the bootstrap installed into the JS engine.
sendInvoke is a JS expression evaluating to a function, called with the
payload JSON string to ship a hostInvoke message to the host. It is
parenthesised at the call site: a bare function (p) {...} in statement
position parses as a declaration and is rejected for having no name.
Everything else is identical across platforms.
Implementation
String hostBridgeBootstrapJs(String sendInvoke) =>
'''
(function() {
if (globalThis.__hostBridgeReady) return;
globalThis.__hostBridgeReady = true;
globalThis.__hostPending = {};
globalThis.__hostNextUuid = 0;
globalThis.host = {};
globalThis.__hostResolve = function(uuid, jsonResult) {
var p = globalThis.__hostPending[uuid];
if (!p) return;
delete globalThis.__hostPending[uuid];
var v;
try { v = JSON.parse(jsonResult); } catch (e) { v = null; }
p.resolve(v);
};
globalThis.__hostReject = function(uuid, message) {
var p = globalThis.__hostPending[uuid];
if (!p) return;
delete globalThis.__hostPending[uuid];
p.reject(new Error(message));
};
// Arguments cross as JSON. What JSON cannot carry is not silently turned
// into null: it is listed with where it sat, and the host refuses the call.
// `undefined` crosses as null, and an object property holding it is
// omitted, as JSON.stringify does.
globalThis.__hostJsonArgs = function(args) {
var found = [];
var open = [];
function walk(v, path) {
var t = typeof v;
if (t === 'undefined') return null;
if (t === 'function' || t === 'symbol' || t === 'bigint') {
found.push({ path: path, kind: t });
return null;
}
if (t === 'number' && !isFinite(v)) {
found.push({ path: path, kind: String(v) });
return null;
}
if (v === null || t !== 'object') return v;
if (open.indexOf(v) >= 0) {
found.push({ path: path, kind: 'cycle' });
return null;
}
if (typeof v.toJSON === 'function') return walk(v.toJSON(), path);
open.push(v);
var out;
if (Array.isArray(v)) {
out = [];
for (var i = 0; i < v.length; i++) out.push(walk(v[i], path.concat([i])));
} else {
out = {};
for (var k in v) {
if (!Object.prototype.hasOwnProperty.call(v, k)) continue;
if (typeof v[k] === 'undefined') continue;
out[k] = walk(v[k], path.concat([k]));
}
}
open.pop();
return out;
}
var clean = [];
for (var j = 0; j < args.length; j++) clean.push(walk(args[j], [j]));
return { args: clean, nonJson: found };
};
globalThis.__hostCall = function(atom, verb, args) {
var uuid = '_h' + (++globalThis.__hostNextUuid);
return new Promise(function(resolve, reject) {
var prepared = globalThis.__hostJsonArgs(args || []);
globalThis.__hostPending[uuid] = { resolve: resolve, reject: reject };
($sendInvoke)(
JSON.stringify({
uuid: uuid, atom: atom, verb: verb,
args: prepared.args, nonJson: prepared.nonJson,
}),
);
});
};
})();
''';