signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
846 lines • 38.3 kB
JavaScript
;
/* 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