Files

131 lines
3.3 KiB
JavaScript

/**
* The Workshop — Three.js scene bootstrap
*
* Initializes the 3D world where Timmy lives.
* Handles WebSocket connection to tower-hermes backend with
* timeout, retry, and clear status display.
*
* See: #242 (3D world), #243 (WebSocket bridge), #265 (presence schema)
*/
// Future: import * as THREE from 'three';
const HERMES_WS_URL = (location.protocol === 'https:' ? 'wss://' : 'ws://') +
location.host + '/ws/tower';
const CONNECT_TIMEOUT_MS = 5000;
const RETRY_DELAY_MS = 3000;
const MAX_AUTO_RETRIES = 3;
const Status = { CONNECTING: 'connecting', ONLINE: 'online', OFFLINE: 'offline' };
const dom = {
dot: document.getElementById('status-dot'),
text: document.getElementById('status-text'),
agents: document.getElementById('agent-count'),
retryBtn: document.getElementById('retry-btn'),
fallback: document.getElementById('fallback-link'),
};
// Signal to inline fallback script that main.js loaded successfully
window.__workshopBooted = true;
let ws = null;
let autoRetries = 0;
let connectTimer = null;
function setStatus(state, message) {
dom.dot.className = 'dot ' + state;
dom.text.textContent = message;
var isOffline = state === Status.OFFLINE;
dom.retryBtn.style.display = isOffline ? 'block' : 'none';
if (dom.fallback) dom.fallback.style.display = isOffline ? 'block' : 'none';
}
function setAgentCount(n) {
dom.agents.textContent = n;
}
function cleanup() {
clearTimeout(connectTimer);
if (ws) {
ws.onopen = null;
ws.onclose = null;
ws.onerror = null;
ws.onmessage = null;
if (ws.readyState <= WebSocket.OPEN) ws.close();
ws = null;
}
}
function connect() {
cleanup();
setStatus(Status.CONNECTING, 'CONNECTING\u2026');
setAgentCount(0);
try {
ws = new WebSocket(HERMES_WS_URL);
} catch (err) {
console.error('[Workshop] WebSocket creation failed:', err);
onFail();
return;
}
connectTimer = setTimeout(function () {
console.warn('[Workshop] Connection timeout after ' + CONNECT_TIMEOUT_MS + 'ms');
cleanup();
onFail();
}, CONNECT_TIMEOUT_MS);
ws.onopen = function () {
clearTimeout(connectTimer);
autoRetries = 0;
setStatus(Status.ONLINE, 'ONLINE');
console.log('[Workshop] Connected to tower-hermes');
};
ws.onmessage = function (evt) {
try {
var msg = JSON.parse(evt.data);
if (typeof msg.agents === 'number') setAgentCount(msg.agents);
} catch (_) {
// non-JSON messages are ignored
}
};
ws.onclose = function () {
clearTimeout(connectTimer);
console.log('[Workshop] Connection closed');
onFail();
};
ws.onerror = function () {
clearTimeout(connectTimer);
console.error('[Workshop] WebSocket error');
// onclose will fire after this, which calls onFail
};
}
function onFail() {
if (autoRetries < MAX_AUTO_RETRIES) {
autoRetries++;
setStatus(Status.CONNECTING, 'RETRYING (' + autoRetries + '/' + MAX_AUTO_RETRIES + ')\u2026');
setTimeout(connect, RETRY_DELAY_MS);
} else {
setStatus(Status.OFFLINE, 'WORKSHOP OFFLINE');
}
}
// Manual retry resets the counter
dom.retryBtn.addEventListener('click', function () {
autoRetries = 0;
connect();
});
// Boot
export function initWorkshop(container) {
console.log('[Workshop] Scene container ready:', container.id);
connect();
}
initWorkshop(document.getElementById('scene'));