131 lines
3.3 KiB
JavaScript
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'));
|