UNPKG

signalk-server

Version:

An implementation of a [Signal K](http://signalk.org) server for boats.

135 lines 6.94 kB
"use strict"; /** * Binary Stream WebSocket Endpoints * * Provides WebSocket endpoints for high-frequency binary data streaming * from WASM plugins (radar spokes, AIS targets, etc.) */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.binaryStreamManager = void 0; exports.initializeBinaryStreams = initializeBinaryStreams; const debug_1 = __importDefault(require("debug")); const ws_1 = __importDefault(require("ws")); const cookie_1 = __importDefault(require("cookie")); const binary_stream_manager_1 = require("./binary-stream-manager"); Object.defineProperty(exports, "binaryStreamManager", { enumerable: true, get: function () { return binary_stream_manager_1.binaryStreamManager; } }); const debug = (0, debug_1.default)('signalk:streams'); /** * Initialize binary stream WebSocket endpoints * * @param app - SignalK application instance */ function initializeBinaryStreams(app) { debug('initializeBinaryStreams called, app.server exists: %s', !!app.server); if (!app.server) { debug('HTTP server not available, skipping binary stream initialization'); return; } debug('Initializing binary stream WebSocket endpoints'); // Handle WebSocket upgrade requests for binary streams // Note: This listener is added to app.server which should be an HTTP server debug('Adding upgrade listener to server'); app.server.on('upgrade', (request, socket, head) => { debug('Upgrade request received: %s, headers.host: %s', request.url, request.headers.host); try { const url = new URL(request.url, `http://${request.headers.host}`); const pathname = url.pathname; debug('Pathname: %s', pathname); // Match: /signalk/v2/api/streams/:streamId (support path segments in streamId) const streamMatch = pathname.match(/^\/signalk\/v2\/api\/streams\/(.+)$/); // Match: /signalk/v2/api/vessels/self/radars/:id/stream (convenience alias) const radarMatch = pathname.match(/^\/signalk\/v2\/api\/vessels\/self\/radars\/([^\/]+)\/stream$/); let streamId = null; if (streamMatch) { streamId = decodeURIComponent(streamMatch[1]); debug('Matched stream pattern: %s', streamId); } else if (radarMatch) { // Alias: map radar endpoint to radars/{radarId} stream const radarId = radarMatch[1]; streamId = `radars/${radarId}`; debug('Matched radar stream pattern: %s', streamId); } else { // Not a binary stream endpoint, let other handlers process debug('No match for path, ignoring: %s', pathname); return; } debug('Processing WebSocket upgrade for stream: %s', streamId); // Authenticate the request (if security is enabled) let principal = { identifier: 'unknown' }; const authRequest = request; // Upgrade requests skip Express, so parse the cookie header and query // string onto the request the way the middleware chain would. Without // this authorizeWS() finds no token and rejects every browser // connection — the HttpOnly JAUTHENTICATION cookie the browser sends // on a same-origin upgrade would otherwise be ignored. Mirrors the // v1 stream path in interfaces/ws.ts. const cookieHeader = request.headers.cookie; if (typeof cookieHeader === 'string') { authRequest.cookies = cookie_1.default.parse(cookieHeader); } authRequest.query = Object.fromEntries(url.searchParams); // Check if security is enabled if (app.securityStrategy && typeof app.securityStrategy.shouldAllowWrite === 'function') { try { // Security is enabled, perform authentication if (app.securityStrategy.authorizeWS) { app.securityStrategy.authorizeWS(request); principal = authRequest.skPrincipal || { identifier: 'unknown' }; } else { // Fallback: use shouldAllowWrite for basic auth check if (!app.securityStrategy.shouldAllowWrite(request, 'streams')) { throw new Error('Unauthorized'); } principal = authRequest.skPrincipal || { identifier: 'unknown' }; } } catch (error) { debug(`Authentication failed for stream ${streamId}: ${error}`); socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); socket.destroy(); return; } } else { // Security is disabled, allow connection without authentication debug(`Security disabled, allowing unauthenticated connection to stream: ${streamId}`); principal = { identifier: 'unauthenticated' }; } // Create WebSocket connection debug('Creating WebSocket server for stream: %s', streamId); const wss = new ws_1.default.Server({ noServer: true }); wss.handleUpgrade(request, socket, head, (ws) => { debug('WebSocket connected to stream: %s', streamId); // Register client with stream manager binary_stream_manager_1.binaryStreamManager.addClient(streamId, ws, principal); ws.on('close', () => { debug(`WebSocket disconnected from stream: ${streamId}`); binary_stream_manager_1.binaryStreamManager.removeClient(streamId, ws); }); ws.on('error', (err) => { debug(`WebSocket error for stream ${streamId}: ${err}`); binary_stream_manager_1.binaryStreamManager.removeClient(streamId, ws); }); // Binary streams are one-way (server → client), ignore client messages ws.on('message', () => { debug(`Ignoring message from client on binary stream ${streamId}`); }); }); } catch (error) { debug(`Error handling WebSocket upgrade: ${error}`); console.error('[signalk:streams] WebSocket upgrade error:', error); socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n'); socket.destroy(); } }); debug('Binary stream WebSocket endpoints initialized'); } //# sourceMappingURL=index.js.map