generated from Labyricorn/labyricorn-project-template
Post Step 4 Completion
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* XZBT Exhibit Contract 5.2 — 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;
|
||||
if (message.xzbt !== window.XZBTContractCore.VERSION) return;
|
||||
|
||||
/* 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;
|
||||
})();
|
||||
Reference in New Issue
Block a user