UNPKG

@fairmint/canton-node-sdk

Version:
117 lines 4.82 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebSocketClient = void 0; const ws_1 = __importDefault(require("ws")); const WebSocketErrorUtils_1 = require("./WebSocketErrorUtils"); /** * Minimal WebSocket helper that: * * - Upgrades http(s) URL to ws(s) * - Auth via Authorization header (standard approach) * - Uses 'daml-ledger-api' subprotocol * - Sends an initial request message * - Dispatches parsed messages to user handlers */ class WebSocketClient { constructor(client) { this.client = client; } async connect(path, requestMessage, handlers) { const baseUrl = this.client.getApiUrl(); const wsUrl = this.buildWsUrl(baseUrl, path); const token = await this.client.authenticate(); // Use standard WebSocket auth pattern: // - JWT in Authorization header // - Application protocol in subprotocol (daml.ws.auth) const protocols = ['daml.ws.auth']; const socket = new ws_1.default(wsUrl, protocols, { handshakeTimeout: 30000, headers: token ? { Authorization: `Bearer ${token}` } : undefined, }); const logger = this.client.getLogger(); const log = async (event, payload) => { if (logger) { await logger.logRequestResponse(wsUrl, { event, requestMessage }, payload); } }; await log('connect', { headers: token ? { Authorization: '[REDACTED]' } : undefined, protocols }); socket.on('open', () => { void (async () => { try { socket.send(JSON.stringify(requestMessage)); await log('send', requestMessage); if (handlers.onOpen) handlers.onOpen(); } catch (err) { await log('send_error', err instanceof Error ? { message: err.message } : String(err)); if (handlers.onError) handlers.onError(err); socket.close(); } })(); }); socket.on('message', (rawData) => { // Convert RawData to string safely let dataString; if (Buffer.isBuffer(rawData)) { dataString = rawData.toString('utf8'); } else if (Array.isArray(rawData)) { dataString = Buffer.concat(rawData).toString('utf8'); } else { dataString = new TextDecoder().decode(rawData); } void (async () => { try { const parsed = WebSocketErrorUtils_1.WebSocketErrorUtils.safeJsonParse(dataString, 'WebSocket message'); await log('message', parsed); handlers.onMessage(parsed); } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err); await log('parse_error', { raw: dataString, error: errorMessage }); // Close connection on JSON parse failure to prevent inconsistent state socket.close(1003, 'Invalid JSON received'); if (handlers.onError) handlers.onError(err); } })(); }); socket.on('error', (err) => { void (async () => { await log('socket_error', { message: err.message }); if (handlers.onError) handlers.onError(err); })(); }); socket.on('close', (code, reason) => { void (async () => { await log('close', { code, reason: reason.toString() }); if (handlers.onClose) handlers.onClose(code, reason.toString()); })(); }); return { close: () => { // Remove all event listeners to prevent memory leaks socket.removeAllListeners(); socket.close(); }, isConnected: () => socket.readyState === ws_1.default.OPEN, getConnectionState: () => socket.readyState, }; } buildWsUrl(base, path) { const hasProtocol = base.startsWith('http://') || base.startsWith('https://'); const normalizedBase = hasProtocol ? base : `https://${base}`; const wsBase = normalizedBase.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:'); return `${wsBase}${path}`; } } exports.WebSocketClient = WebSocketClient; //# sourceMappingURL=WebSocketClient.js.map