UNPKG

signalk-server

Version:

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

846 lines 38.3 kB
"use strict"; /* eslint-disable @typescript-eslint/no-explicit-any */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createUtf8Reader = createUtf8Reader; exports.createEnvImports = createEnvImports; /** * WASM Environment Imports (Host Bindings) * * Provides the Signal K API functions that WASM plugins can import */ const debug_1 = __importDefault(require("debug")); const server_api_1 = require("@signalk/server-api"); const resource_provider_1 = require("./resource-provider"); const weather_provider_1 = require("./weather-provider"); const radar_provider_1 = require("./radar-provider"); const binary_stream_1 = require("./binary-stream"); const socket_manager_1 = require("./socket-manager"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const atomicWrite_1 = require("../../atomicWrite"); const debug = (0, debug_1.default)('signalk:wasm:bindings'); /** * Helper to read UTF-8 strings from WASM memory */ function createUtf8Reader(memoryRef) { return (ptr, len) => { if (!memoryRef.current) { throw new Error('AssemblyScript module memory not initialized'); } const bytes = new Uint8Array(memoryRef.current.buffer, ptr, len); const decoder = new TextDecoder('utf-8'); return decoder.decode(bytes); }; } /** * Create environment imports for a WASM plugin */ function createEnvImports(options) { const { pluginId, capabilities, app, memoryRef, rawExports, asLoaderInstance, configPath, packageName: _packageName } = options; const readUtf8String = createUtf8Reader(memoryRef); const readBinaryData = (0, binary_stream_1.createBinaryDataReader)(memoryRef); const envImports = { // AssemblyScript runtime requirements abort: (msg, file, line, column) => { debug(`WASM abort called: ${msg} at ${file}:${line}:${column}`); }, seed: () => { return Date.now() * Math.random(); }, 'console.log': (ptr, len) => { try { const message = readUtf8String(ptr, len); debug(`[${pluginId}] ${message}`); } catch (error) { debug(`WASM console.log error: ${error}`); } }, // Signal K API functions sk_debug: (ptr, len) => { try { const message = readUtf8String(ptr, len); debug(`[${pluginId}] ${message}`); } catch (error) { debug(`Plugin debug error: ${error}`); } }, sk_set_status: (ptr, len) => { try { const message = readUtf8String(ptr, len); debug(`[${pluginId}] Status: ${message}`); if (app && app.setPluginStatus) { app.setPluginStatus(pluginId, message); } } catch (error) { debug(`Plugin set status error: ${error}`); } }, sk_set_error: (ptr, len) => { try { const message = readUtf8String(ptr, len); debug(`[${pluginId}] Error: ${message}`); if (app && app.setPluginError) { app.setPluginError(pluginId, message); } } catch (error) { debug(`Plugin set error error: ${error}`); } }, // Get value from vessels.self path sk_get_self_path: (pathPtr, pathLen, bufPtr, bufMaxLen) => { try { const path = readUtf8String(pathPtr, pathLen); debug(`[${pluginId}] getSelfPath: ${path}`); if (!app || !app.getSelfPath) { debug(`[${pluginId}] app.getSelfPath not available`); return 0; } const value = app.getSelfPath(path); if (value === undefined || value === null) { return 0; } // Serialize value to JSON const jsonStr = JSON.stringify(value); const jsonBytes = Buffer.from(jsonStr, 'utf8'); if (jsonBytes.length > bufMaxLen) { debug(`[${pluginId}] getSelfPath buffer too small: need ${jsonBytes.length}, have ${bufMaxLen}`); return 0; } // Write to WASM memory if (memoryRef.current) { const memView = new Uint8Array(memoryRef.current.buffer); memView.set(jsonBytes, bufPtr); return jsonBytes.length; } return 0; } catch (error) { debug(`[${pluginId}] getSelfPath error: ${error}`); return 0; } }, /** * Emit a delta message to the Signal K server * * @param ptr - Pointer to delta JSON string in WASM memory * @param len - Length of delta JSON string * @param version - Signal K version: 1 = v1 (default), 2 = v2 * * Plugins should use v1 for regular navigation data (the default). * Use v2 for Course API paths and other v2-specific data to prevent * v2 data from being mixed into the v1 full data model. * * This mirrors the TypeScript plugin API where handleMessage accepts * an optional skVersion parameter. */ sk_handle_message: (ptr, len, version = 1) => { try { const deltaJson = readUtf8String(ptr, len); debug(`[${pluginId}] Emitting delta (v${version === 2 ? '2' : '1'}): ${deltaJson.substring(0, 200)}...`); if (app && app.handleMessage) { try { const delta = JSON.parse(deltaJson); const skVersion = version === 2 ? server_api_1.SKVersion.v2 : server_api_1.SKVersion.v1; app.handleMessage(pluginId, delta, skVersion); debug(`[${pluginId}] Delta processed by server (${skVersion})`); } catch (parseError) { debug(`[${pluginId}] Failed to parse/process delta: ${parseError}`); } } else { debug(`[${pluginId}] Warning: app.handleMessage not available, delta not processed`); } } catch (error) { debug(`Plugin handle message error: ${error}`); } }, /** * Publish a SignalK notification (v6) * * @param pathPtr - Pointer to notification path string (e.g., "notifications.navigation.closestApproach.radar:1:target:5") * @param pathLen - Length of path string * @param valuePtr - Pointer to notification value JSON * @param valueLen - Length of value JSON * @returns 0 on success, -1 on error */ sk_publish_notification: (pathPtr, pathLen, valuePtr, valueLen) => { try { const path = readUtf8String(pathPtr, pathLen); const valueJson = readUtf8String(valuePtr, valueLen); debug(`[${pluginId}] Publishing notification: ${path}`); if (!app || !app.handleMessage) { debug(`[${pluginId}] app.handleMessage not available`); return -1; } // Parse and validate the notification value let notificationValue; try { notificationValue = JSON.parse(valueJson); } catch (e) { debug(`[${pluginId}] Invalid notification JSON: ${e}`); return -1; } // Validate required notification fields per SignalK spec // Notifications must have: state, method, message if (!notificationValue.state) { debug(`[${pluginId}] Notification missing required 'state' field`); return -1; } const validStates = ['normal', 'alert', 'warn', 'alarm', 'emergency']; if (!validStates.includes(notificationValue.state)) { debug(`[${pluginId}] Invalid notification state: ${notificationValue.state}`); return -1; } // Build the delta message for the notification const delta = { updates: [ { values: [ { path: path, value: notificationValue } ] } ] }; // Notifications should be processed normally (version 1) app.handleMessage(pluginId, delta); debug(`[${pluginId}] Notification published: ${path} state=${notificationValue.state}`); return 0; // Success } catch (error) { debug(`[${pluginId}] sk_publish_notification error: ${error}`); return -1; } }, // ========================================================================== // Plugin Configuration API // ========================================================================== /** * Read plugin configuration from plugin-config-data * Uses: ~/.signalk/plugin-config-data/{pluginId}.json * * This matches the storage location used by JS plugins. * * @param bufPtr - Buffer to write config JSON into * @param bufMaxLen - Maximum buffer size * @returns Number of bytes written, or 0 if no config / error */ sk_read_config: (bufPtr, bufMaxLen) => { try { const cfgPath = configPath || app?.config?.configPath; if (!cfgPath) { debug(`[${pluginId}] sk_read_config: configPath not available`); return 0; } // Plugin config path: plugin-config-data/{pluginId}.json (same as JS plugins) const configFile = path.join(cfgPath, 'plugin-config-data', `${pluginId}.json`); let configJson = '{}'; if (fs.existsSync(configFile)) { try { const rawConfig = fs.readFileSync(configFile, 'utf8'); const parsed = JSON.parse(rawConfig); // Return just the configuration object (not enabled/enableLogging flags) configJson = JSON.stringify(parsed.configuration || {}); } catch (e) { debug(`[${pluginId}] Could not read config: ${e}`); } } debug(`[${pluginId}] Reading config from ${configFile}: ${configJson.substring(0, 100)}...`); const encoder = new TextEncoder(); const configBytes = encoder.encode(configJson); if (configBytes.length > bufMaxLen) { debug(`[${pluginId}] Config buffer too small: need ${configBytes.length}, have ${bufMaxLen}`); return 0; } if (!memoryRef.current) return 0; const memView = new Uint8Array(memoryRef.current.buffer); memView.set(configBytes, bufPtr); return configBytes.length; } catch (error) { debug(`[${pluginId}] sk_read_config error: ${error}`); return 0; } }, /** * Save plugin configuration to plugin-config-data * Uses: ~/.signalk/plugin-config-data/{pluginId}.json * * This matches the storage location used by JS plugins. * * @param configPtr - Pointer to config JSON string * @param configLen - Length of config JSON * @returns 0 on success, negative on error */ sk_save_config: (configPtr, configLen) => { try { const cfgPath = configPath || app?.config?.configPath; if (!cfgPath) { debug(`[${pluginId}] sk_save_config: configPath not available`); return -1; } const configJson = readUtf8String(configPtr, configLen); debug(`[${pluginId}] Saving config: ${configJson.substring(0, 100)}...`); // Validate JSON const configuration = JSON.parse(configJson); // Plugin config path: plugin-config-data/{pluginId}.json (same as JS plugins) const configDataDir = path.join(cfgPath, 'plugin-config-data'); const configFile = path.join(configDataDir, `${pluginId}.json`); // Create directory if needed if (!fs.existsSync(configDataDir)) { fs.mkdirSync(configDataDir, { recursive: true }); } // Read existing config to preserve enabled/enableLogging flags let existingConfig = { enabled: true }; if (fs.existsSync(configFile)) { try { existingConfig = JSON.parse(fs.readFileSync(configFile, 'utf8')); } catch (e) { debug(`[${pluginId}] Could not read existing config: ${e}`); } } // Update configuration while preserving other fields existingConfig.configuration = configuration; (0, atomicWrite_1.atomicWriteFileSync)(configFile, JSON.stringify(existingConfig, null, 2)); debug(`[${pluginId}] Config saved to ${configFile}`); return 0; } catch (error) { debug(`[${pluginId}] sk_save_config error: ${error}`); return -1; } }, // Capability checking sk_has_capability: (capPtr, capLen) => { try { const capability = readUtf8String(capPtr, capLen); debug(`[${pluginId}] Checking capability: ${capability}`); if (capability === 'network') { return capabilities.network ? 1 : 0; } if (capability === 'rawSockets') { return capabilities.rawSockets ? 1 : 0; } return 0; } catch (error) { debug(`Plugin capability check error: ${error}`); return 0; } }, // PUT Handler Registration sk_register_put_handler: (contextPtr, contextLen, pathPtr, pathLen) => { try { const context = readUtf8String(contextPtr, contextLen); const path = readUtf8String(pathPtr, pathLen); debug(`[${pluginId}] Registering PUT handler: context=${context}, path=${path}`); if (!capabilities.putHandlers) { debug(`[${pluginId}] PUT handlers capability not granted`); return 0; } debug(`[${pluginId}] app available: ${!!app}, app.registerActionHandler available: ${!!(app && app.registerActionHandler)}`); if (app && app.registerActionHandler) { // Send meta message to indicate this path supports PUT if (app.handleMessage) { app.handleMessage(pluginId, { updates: [ { meta: [ { path: path, value: { supportsPut: true } } ] } ] }); debug(`[${pluginId}] Sent supportsPut meta for ${path}`); } const callback = (cbContext, cbPath, value, cb) => { debug.enabled && debug(`[${pluginId}] PUT request received: ${cbContext}.${cbPath} = ${JSON.stringify(value)}`); const handlerName = `handle_put_${cbContext.replace(/\./g, '_')}_${cbPath.replace(/\./g, '_')}`; const exports = asLoaderInstance.current?.exports || rawExports.current; const handlerFunc = exports?.[handlerName]; if (handlerFunc) { debug(`[${pluginId}] Calling WASM handler: ${handlerName}`); const valueJson = JSON.stringify(value); try { let responseJson; if (asLoaderInstance.current) { responseJson = handlerFunc(valueJson); } else if (rawExports.current?.allocate) { // Rust library plugin: buffer-based string passing const valueBytes = Buffer.from(valueJson, 'utf8'); const valuePtr = rawExports.current.allocate(valueBytes.length); const responseMaxLen = 8192; const responsePtr = rawExports.current.allocate(responseMaxLen); const memory = rawExports.current.memory; const memView = new Uint8Array(memory.buffer); memView.set(valueBytes, valuePtr); const writtenLen = handlerFunc(valuePtr, valueBytes.length, responsePtr, responseMaxLen); const responseBytes = new Uint8Array(memory.buffer, responsePtr, writtenLen); responseJson = new TextDecoder('utf-8').decode(responseBytes); if (rawExports.current.deallocate) { rawExports.current.deallocate(valuePtr, valueBytes.length); rawExports.current.deallocate(responsePtr, responseMaxLen); } } else { throw new Error('Unknown plugin type for PUT handler'); } const response = JSON.parse(responseJson); debug.enabled && debug(`[${pluginId}] PUT handler response: ${JSON.stringify(response)}`); cb(response); } catch (error) { debug(`[${pluginId}] PUT handler error: ${error}`); cb({ state: 'COMPLETED', statusCode: 500, message: `Handler error: ${error}` }); } } else { debug(`[${pluginId}] Warning: Handler function not found: ${handlerName}`); cb({ state: 'COMPLETED', statusCode: 501, message: 'Handler not implemented' }); } }; app.registerActionHandler(context, path, pluginId, callback); debug(`[${pluginId}] PUT handler registered successfully via registerActionHandler`); return 1; } else { debug(`[${pluginId}] app.registerActionHandler not available`); return 0; } } catch (error) { debug(`Plugin register PUT handler error: ${error}`); return 0; } }, // Resource Provider Registration sk_register_resource_provider: (0, resource_provider_1.createResourceProviderBinding)(pluginId, capabilities, app, readUtf8String), // Weather Provider Registration sk_register_weather_provider: (0, weather_provider_1.createWeatherProviderBinding)(pluginId, capabilities, app, readUtf8String), // Radar Provider Registration sk_register_radar_provider: (0, radar_provider_1.createRadarProviderBinding)(pluginId, capabilities, app, readUtf8String), // ========================================================================== // Binary Stream API (for high-frequency data streaming) // ========================================================================== /** * Emit binary data to a stream * General-purpose binary streaming for any plugin * @param streamIdPtr - Pointer to stream ID string * @param streamIdLen - Length of stream ID * @param dataPtr - Pointer to binary data * @param dataLen - Length of binary data * @returns 1 on success, 0 on failure */ sk_emit_binary_stream: (0, binary_stream_1.createBinaryStreamBinding)(pluginId, app, readUtf8String, readBinaryData), /** * Emit radar spoke data * Convenience wrapper for radar providers * @param radarIdPtr - Pointer to radar ID string * @param radarIdLen - Length of radar ID * @param spokeDataPtr - Pointer to binary spoke data (protobuf) * @param spokeDataLen - Length of spoke data * @returns 1 on success, 0 on failure */ sk_radar_emit_spokes: (0, radar_provider_1.createRadarEmitSpokesBinding)(pluginId, capabilities, app, readUtf8String, readBinaryData), // ========================================================================== // Raw Socket API (for radar, NMEA, etc.) // Requires rawSockets capability // ========================================================================== /** * Create a UDP socket * @param type - 0 for udp4, 1 for udp6 * @returns Socket ID (>0), or -1 on error */ sk_udp_create: (type) => { if (!capabilities.rawSockets) { debug(`[${pluginId}] rawSockets capability not granted`); return -1; } const socketType = type === 1 ? 'udp6' : 'udp4'; return socket_manager_1.socketManager.createSocket(pluginId, socketType); }, /** * Bind socket to a port * @param socketId - Socket ID from sk_udp_create * @param port - Port number (0 for any available) * @returns 0 on success, -1 on error */ sk_udp_bind: (socketId, port) => { if (!capabilities.rawSockets) return -1; // Note: bind is async but we return immediately and let it complete // The socket will be ready by the time we try to receive socket_manager_1.socketManager.bind(socketId, port).catch((err) => { debug(`[${pluginId}] Async bind error: ${err}`); }); return 0; }, /** * Join a multicast group * @param socketId - Socket ID * @param addrPtr - Pointer to multicast address string * @param addrLen - Length of address string * @param ifacePtr - Pointer to interface address (0 for default) * @param ifaceLen - Length of interface string * @returns 0 on success, -1 on error */ sk_udp_join_multicast: (socketId, addrPtr, addrLen, ifacePtr, ifaceLen) => { if (!capabilities.rawSockets) return -1; try { const multicastAddr = readUtf8String(addrPtr, addrLen); const interfaceAddr = ifaceLen > 0 ? readUtf8String(ifacePtr, ifaceLen) : undefined; debug(`[${pluginId}] Joining multicast ${multicastAddr} on interface ${interfaceAddr || 'default'}`); return socket_manager_1.socketManager.joinMulticast(socketId, multicastAddr, interfaceAddr); } catch (error) { debug(`[${pluginId}] Join multicast error: ${error}`); return -1; } }, /** * Leave a multicast group */ sk_udp_leave_multicast: (socketId, addrPtr, addrLen, ifacePtr, ifaceLen) => { if (!capabilities.rawSockets) return -1; try { const multicastAddr = readUtf8String(addrPtr, addrLen); const interfaceAddr = ifaceLen > 0 ? readUtf8String(ifacePtr, ifaceLen) : undefined; return socket_manager_1.socketManager.leaveMulticast(socketId, multicastAddr, interfaceAddr); } catch (error) { debug(`[${pluginId}] Leave multicast error: ${error}`); return -1; } }, /** * Set multicast TTL */ sk_udp_set_multicast_ttl: (socketId, ttl) => { if (!capabilities.rawSockets) return -1; return socket_manager_1.socketManager.setMulticastTTL(socketId, ttl); }, /** * Enable/disable multicast loopback */ sk_udp_set_multicast_loopback: (socketId, enabled) => { if (!capabilities.rawSockets) return -1; return socket_manager_1.socketManager.setMulticastLoopback(socketId, enabled !== 0); }, /** * Enable/disable broadcast */ sk_udp_set_broadcast: (socketId, enabled) => { if (!capabilities.rawSockets) return -1; return socket_manager_1.socketManager.setBroadcast(socketId, enabled !== 0); }, /** * Send data via UDP * @param socketId - Socket ID * @param addrPtr - Destination address pointer * @param addrLen - Destination address length * @param port - Destination port * @param dataPtr - Data pointer * @param dataLen - Data length * @returns Bytes sent, or -1 on error */ sk_udp_send: (socketId, addrPtr, addrLen, port, dataPtr, dataLen) => { if (!capabilities.rawSockets) return -1; try { const address = readUtf8String(addrPtr, addrLen); if (!memoryRef.current) return -1; const data = Buffer.from(new Uint8Array(memoryRef.current.buffer, dataPtr, dataLen)); // Send is async, but we return 0 immediately and let it complete socket_manager_1.socketManager.send(socketId, data, address, port).catch((err) => { debug(`[${pluginId}] Async send error: ${err}`); }); return dataLen; // Optimistically return bytes "sent" } catch (error) { debug(`[${pluginId}] Send error: ${error}`); return -1; } }, /** * Receive data from UDP socket (non-blocking) * @param socketId - Socket ID * @param bufPtr - Buffer to write data into * @param bufMaxLen - Maximum buffer size * @param addrOutPtr - Buffer to write source address (at least 46 bytes for IPv6) * @param portOutPtr - Pointer to write source port (u16) * @returns Bytes received, 0 if no data, -1 on error */ sk_udp_recv: (socketId, bufPtr, bufMaxLen, addrOutPtr, portOutPtr) => { if (!capabilities.rawSockets) return -1; try { const datagram = socket_manager_1.socketManager.receive(socketId); if (!datagram) { return 0; // No data available } if (!memoryRef.current) return -1; const memory = memoryRef.current; const memView = new Uint8Array(memory.buffer); // Copy data to buffer const bytesToCopy = Math.min(datagram.data.length, bufMaxLen); memView.set(datagram.data.slice(0, bytesToCopy), bufPtr); // Write source address (null-terminated string) const addrBytes = Buffer.from(datagram.address + '\0', 'utf8'); memView.set(addrBytes, addrOutPtr); // Write source port (u16, little-endian) const portView = new DataView(memory.buffer); portView.setUint16(portOutPtr, datagram.port, true); return bytesToCopy; } catch (error) { debug(`[${pluginId}] Recv error: ${error}`); return -1; } }, /** * Get number of buffered datagrams waiting to be received */ sk_udp_pending: (socketId) => { if (!capabilities.rawSockets) return -1; return socket_manager_1.socketManager.getBufferedCount(socketId); }, /** * Close a socket */ sk_udp_close: (socketId) => { if (!capabilities.rawSockets) return; socket_manager_1.socketManager.close(socketId); }, // ========================================================================== // TCP Socket API (for protocols requiring persistent connections) // Requires rawSockets capability // ========================================================================== /** * Create a TCP socket * @returns Socket ID (>0), or -1 on error */ sk_tcp_create: () => { if (!capabilities.rawSockets) { debug(`[${pluginId}] rawSockets capability not granted`); return -1; } return socket_manager_1.tcpSocketManager.createSocket(pluginId); }, /** * Connect TCP socket to remote host * @param socketId - Socket ID from sk_tcp_create * @param addrPtr - Pointer to host address string * @param addrLen - Length of address string * @param port - Remote port number * @returns 0 if connection initiated, -1 on error */ sk_tcp_connect: (socketId, addrPtr, addrLen, port) => { if (!capabilities.rawSockets) return -1; try { const address = readUtf8String(addrPtr, addrLen); debug(`[${pluginId}] TCP connecting to ${address}:${port}`); return socket_manager_1.tcpSocketManager.connect(socketId, address, port); } catch (error) { debug(`[${pluginId}] TCP connect error: ${error}`); return -1; } }, /** * Check if TCP socket is connected * @param socketId - Socket ID * @returns 1 if connected, 0 if not, -1 if socket not found */ sk_tcp_connected: (socketId) => { if (!capabilities.rawSockets) return -1; return socket_manager_1.tcpSocketManager.isConnected(socketId); }, /** * Set TCP socket buffering mode * @param socketId - Socket ID * @param lineBuffering - 1 for line-buffered (text), 0 for raw (binary) * @returns 0 on success, -1 on error */ sk_tcp_set_line_buffering: (socketId, lineBuffering) => { if (!capabilities.rawSockets) return -1; return socket_manager_1.tcpSocketManager.setLineBuffering(socketId, lineBuffering !== 0); }, /** * Send data via TCP * @param socketId - Socket ID * @param dataPtr - Data pointer * @param dataLen - Data length * @returns Bytes sent, or -1 on error */ sk_tcp_send: (socketId, dataPtr, dataLen) => { if (!capabilities.rawSockets) return -1; try { if (!memoryRef.current) return -1; const data = Buffer.from(new Uint8Array(memoryRef.current.buffer, dataPtr, dataLen)); // Send is async, but we return immediately socket_manager_1.tcpSocketManager.send(socketId, data).catch((err) => { debug(`[${pluginId}] Async TCP send error: ${err}`); }); return dataLen; } catch (error) { debug(`[${pluginId}] TCP send error: ${error}`); return -1; } }, /** * Receive a complete line from TCP socket (non-blocking) * Only works in line-buffered mode * @param socketId - Socket ID * @param bufPtr - Buffer to write line into (without line ending) * @param bufMaxLen - Maximum buffer size * @returns Bytes received, 0 if no complete line, -1 on error */ sk_tcp_recv_line: (socketId, bufPtr, bufMaxLen) => { if (!capabilities.rawSockets) return -1; try { const line = socket_manager_1.tcpSocketManager.receiveLine(socketId); if (!line) { return 0; // No complete line available } if (!memoryRef.current) return -1; const memory = memoryRef.current; const memView = new Uint8Array(memory.buffer); // Convert line to bytes and copy to buffer const lineBytes = Buffer.from(line, 'utf8'); const bytesToCopy = Math.min(lineBytes.length, bufMaxLen); memView.set(lineBytes.slice(0, bytesToCopy), bufPtr); return bytesToCopy; } catch (error) { debug(`[${pluginId}] TCP recv line error: ${error}`); return -1; } }, /** * Receive raw data from TCP socket (non-blocking) * Only works in raw mode * @param socketId - Socket ID * @param bufPtr - Buffer to write data into * @param bufMaxLen - Maximum buffer size * @returns Bytes received, 0 if no data, -1 on error */ sk_tcp_recv_raw: (socketId, bufPtr, bufMaxLen) => { if (!capabilities.rawSockets) return -1; try { const data = socket_manager_1.tcpSocketManager.receiveRaw(socketId); if (!data) { return 0; // No data available } if (!memoryRef.current) return -1; const memory = memoryRef.current; const memView = new Uint8Array(memory.buffer); const bytesToCopy = Math.min(data.length, bufMaxLen); memView.set(data.slice(0, bytesToCopy), bufPtr); return bytesToCopy; } catch (error) { debug(`[${pluginId}] TCP recv raw error: ${error}`); return -1; } }, /** * Get number of buffered items waiting to be received */ sk_tcp_pending: (socketId) => { if (!capabilities.rawSockets) return -1; return socket_manager_1.tcpSocketManager.getBufferedCount(socketId); }, /** * Close a TCP socket */ sk_tcp_close: (socketId) => { if (!capabilities.rawSockets) return; socket_manager_1.tcpSocketManager.close(socketId); } }; return envImports; } //# sourceMappingURL=env-imports.js.map