Files
XZBT-NGN/src/connection.js
T
Labyricorn ed76cf6189 Step 6.3 Follow-up: Implement §31.4 full URL resolution
- Implemented §31.4 same-origin resolution during discovery
- Validated genericity scan correctly via full recursive check
- Generated browser screenshots for 5.2/5.3 verification
2026-09-14 14:36:07 -07:00

50 lines
2.1 KiB
JavaScript

import { ProtocolError } from './validation.js';
export function exhibitURL(value, base) {
if (!value.trim()) throw new Error('Enter an exhibit URL or served path.');
const url = new URL(value.trim(), base);
if (!['http:', 'https:'].includes(url.protocol) || url.origin !== new URL(base).origin || url.username || url.password) {
throw new Error('Enter a same-origin HTTP exhibit URL or served path.');
}
return url.href;
}
// Frame lifecycle only; protocol negotiation remains in ExhibitHost.
export class ExhibitConnection {
constructor({ host, createFrame, transport, base, changed = () => {}, loadTimeoutMs = 15000 }) {
Object.assign(this, { host, createFrame, transport, base, changed, loadTimeoutMs });
this.frame = null; this.url = ''; this.loading = false; this.generation = 0;
}
disconnect() {
this.generation++; clearTimeout(this.timer); this.loading = false;
this.frame?.remove(); this.frame = null; this.host.disconnect(); this.changed();
}
load(value) {
const url = exhibitURL(value, this.base); // Invalid input preserves the active exhibit.
this.disconnect(); this.url = url; this.loading = true;
const generation = this.generation;
const frame = this.createFrame(); this.frame = frame;
frame.addEventListener('load', () => {
if (generation !== this.generation) return;
clearTimeout(this.timer); this.loading = false; this.attach();
});
this.timer = setTimeout(() => {
if (generation !== this.generation) return;
this.disconnect();
this.host.report(new ProtocolError('LOAD_TIMEOUT', 'Exhibit did not finish loading. Check the served path and reconnect.'));
}, this.loadTimeoutMs);
frame.src = url; this.changed();
}
async attach() {
if (!this.frame || this.loading) return;
const generation = this.generation;
try { await this.host.connect(this.transport(this.frame), this.url); }
catch (error) { if (generation === this.generation && !error.code) this.host.report(error); }
this.changed();
}
reconnect() {
if (this.frame && !this.loading) return this.attach();
if (this.url) this.load(this.url);
}
}