generated from Labyricorn/labyricorn-project-template
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.
94 lines
3.8 KiB
JavaScript
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;
|
|
})();
|