OpusBike/webapp/js/wsRelay.js

134 lines
3.7 KiB
JavaScript
Raw Normal View History

/**
* WebSocket Relay Client
* Bridge mode: sends decoded BLE telemetry to VPS over 5G/WiFi
* Viewer mode: receives telemetry broadcast from VPS
*/
const WSRelay = (() => {
let ws = null;
let onDataCallback = null;
let onStatusCallback = null;
let reconnectTimer = null;
let isSource = false; // true if this client is the BLE bridge
const RECONNECT_DELAY = 2000;
/**
* Get the WebSocket URL based on current location
*/
function getWsUrl() {
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
return `${proto}//${location.host}/opusbike/ws`;
}
/**
* Connect to the WebSocket relay server
*/
function connect(asSource = false) {
isSource = asSource;
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
return;
}
clearTimeout(reconnectTimer);
try {
ws = new WebSocket(getWsUrl());
ws.onopen = () => {
console.log('WS relay connected (source:', isSource, ')');
// Register role with server
ws.send(JSON.stringify({ type: 'register', role: isSource ? 'source' : 'viewer' }));
if (onStatusCallback) onStatusCallback('connected');
};
ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data);
// Handle both live telemetry and initial state snapshot
if ((msg.type === 'telemetry' || msg.type === 'state') && onDataCallback) {
onDataCallback(msg.data);
} else if (msg.type === 'source_connected' && onStatusCallback) {
onStatusCallback('source_connected');
} else if (msg.type === 'source_disconnected' && onStatusCallback) {
onStatusCallback('source_disconnected');
}
} catch (e) {
// Ignore non-JSON messages
}
};
ws.onclose = () => {
console.log('WS relay disconnected');
if (onStatusCallback) onStatusCallback('disconnected');
scheduleReconnect();
};
ws.onerror = (err) => {
console.warn('WS relay error:', err);
};
} catch (e) {
console.warn('WS connect failed:', e);
scheduleReconnect();
}
}
/**
* Schedule a reconnection attempt
*/
function scheduleReconnect() {
clearTimeout(reconnectTimer);
reconnectTimer = setTimeout(() => connect(isSource), RECONNECT_DELAY);
}
/**
* Send telemetry data to relay (bridge mode phone uploading to VPS)
*/
function sendTelemetry(data) {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'telemetry', source: 'ble', data }));
}
}
/**
* Disconnect from relay
*/
function disconnect() {
clearTimeout(reconnectTimer);
if (ws) {
ws.close();
ws = null;
}
}
/**
* Set callback for incoming telemetry (viewer mode)
*/
function onData(callback) {
onDataCallback = callback;
}
/**
* Set callback for connection status changes
*/
function onStatus(callback) {
onStatusCallback = callback;
}
/**
* Check if connected
*/
function isConnected() {
return ws && ws.readyState === WebSocket.OPEN;
}
return {
connect,
disconnect,
sendTelemetry,
onData,
onStatus,
isConnected
};
})();