Files
XZBT-NGN/test-fixtures/reference-exhibits/shared/host-transport.js
T
Labyricorn 745912e451 Steps 6.4-6.7B — Local surfaces, reference-exhibit validation, SciFi Observation surface
One commit for the work accumulated in the working tree since Step 6.3,
which had never been split into per-step commits:

- src/local-surfaces.js + src/surface-url.js (new); src/ui.js,
  src/validation.js, src/connection.js and public/index.html updated for
  local-surface hosting and generic surface rendering
- tests: local-surfaces (20), scifi-surfaces (24) and postmessage-interop (7)
  new; connection/museum-gallery/surface-validation suites updated
- reference exhibits: shared/contract-core.js defaults to Contract 5.3
  (major 5, minor 3, xzbt 5.3); museum-gallery advertises its surface
  catalog; aquarium/haunted-house/planetarium adapters updated
- SciFi-XZBT (Step 6.7A/6.7B): surface-mode.js + surface-bus.js,
  Observation-surface boot branch, local-change hooks, view.pillars /
  view.warp-flight targets; fixture byte-identical to G:/.vibe/SciFi-XZBT
- SciFi-XZBT contract adapter handshake fix: the inbound bridge filter no
  longer gates on an exact advisory xzbt value (Contract 5.3 §6.5), only on
  its presence/type, matching the host's own envelope validation; the
  adapter now advertises contract minor 3 / version 5.3.0, which it already
  implemented via the 5.3 surfaces field. Root cause of the five failing
  postmessage-interop tests (host hello was silently dropped).
- docs: architecture 6.4 and 6.7A, reference 6.6 and 6.7; evidence logs;
  test-fixtures/PROVENANCE.md resync record

Test results: NGN 154/154 (was 149/154); postmessage-interop 7/7 (was 2/7);
SciFi contract harness 21/21, real-adapter suite 32/32. git diff --check
clean for changed files; two pre-existing trailing-whitespace lines remain
in test-fixtures/reference-exhibits/scifi/index.html, copied verbatim from
the authoritative SciFi source.

Step 6.7 live verification (browser Observation, packaged standalone) is
still pending and is not claimed here.
2026-09-14 19:45:27 -07:00

94 lines
3.8 KiB
JavaScript

/*
* XZBT Exhibit Contract 5.3 — same-origin postMessage host transport.
*
* This is layer 6 of the Authoring Guide's recommended separation, and it is
* deliberately the thinnest file in the project. It knows how to move
* contract messages across one channel and nothing else: no exhibit
* semantics, no target knowledge, no state.
*
* It is also entirely optional. An exhibit that never receives a `hello`
* behaves exactly as it would with this file deleted — that is the
* standalone-first rule (Contract §2, Authoring Guide §B).
*
* Security (Contract §25): both `event.origin` and `event.source` are
* validated on every message. The transport also assigns `source = 'host'`
* itself; a `source` field inside an incoming message is ignored, never
* trusted (Contract §15).
*/
(function () {
'use strict';
/**
* @param {object} options
* @param {object} options.core an XZBTContractCore.ContractCore
* @param {string} [options.origin] expected host origin; defaults to the
* document's own origin (same-origin)
* @param {function} [options.onMessage] diagnostics hook
*/
function HostTransport(options) {
this.core = options.core;
this.expectedOrigin = options.origin || window.location.origin;
this.onMessage = options.onMessage || function () {};
this.connected = false;
this._bound = this._onMessage.bind(this);
/* Events are pushed, not polled. The core already emits every state
* change, action, and capability transition through its onEvent hook;
* the transport's job is to forward them to the host. Without this the
* host would see responses but never learn that anything changed
* (Contract §16). */
var self = this;
var coreOnEvent = this.core.onEvent;
this.core.onEvent = function (event) {
if (typeof coreOnEvent === 'function') coreOnEvent(event);
if (self.connected) self.send(event);
};
window.addEventListener('message', this._bound);
}
HostTransport.prototype._isTrusted = function (event) {
/* Same-origin only. `file://` documents report origin "null", so a
* file-opened exhibit simply never accepts host traffic — which is the
* correct standalone behaviour, not a failure. */
if (event.origin !== this.expectedOrigin) return false;
if (event.source !== window.parent) return false;
return true;
};
HostTransport.prototype._onMessage = function (event) {
if (!this._isTrusted(event)) return;
var message = event.data;
if (!message || typeof message !== 'object') return;
/* `xzbt` is advisory metadata, not a version gate (Contract §6.5): real
* compatibility is negotiated by ContractCore.handleRequest/handleHello
* via contract major/minor, not by string-matching the caller's
* advisory tag against this exhibit's own static module version here.
* (This used to compare against window.XZBTContractCore.VERSION, the
* shared module's fixed default -- never the per-instance version an
* exhibit actually negotiates -- so it silently discarded every
* message from any host whose advisory tag didn't happen to equal
* that hardcoded default, including well-formed hellos.) */
/* Source is assigned here, at the trusted receiving boundary. */
var response = this.core.handleRequest(message, 'host');
if (!response) return;
if (response.type === 'hello.result') this.connected = true;
this.onMessage(message, response);
this.send(response);
};
HostTransport.prototype.send = function (message) {
if (window.parent === window) return;
window.parent.postMessage(message, this.expectedOrigin);
};
HostTransport.prototype.destroy = function () {
window.removeEventListener('message', this._bound);
};
window.XZBTHostTransport = HostTransport;
})();