hostBridgeBootstrapJs function

String hostBridgeBootstrapJs(
  1. String sendInvoke
)

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,
        }),
      );
    });
  };
})();
''';