UNPKG

signalk-server

Version:

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

290 lines 11.8 kB
"use strict"; /* eslint-disable @typescript-eslint/no-explicit-any */ /** * WASM ServerAPI FFI Bridge * * Provides the FFI (Foreign Function Interface) bridge between WASM plugins * and the Signal K ServerAPI. Enforces capability restrictions and handles * serialization across the WASM boundary. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createServerAPIBridge = createServerAPIBridge; exports.createWasmImports = createWasmImports; exports.callWasmExport = callWasmExport; /// <reference lib="webworker" /> const debug_1 = __importDefault(require("debug")); const server_api_1 = require("@signalk/server-api"); const loader_1 = require("./loader"); const wasm_storage_1 = require("./wasm-storage"); const debug = (0, debug_1.default)('signalk:wasm:serverapi'); /** * Create ServerAPI FFI functions for a WASM plugin * * These functions will be imported by the WASM module and provide * access to Signal K server capabilities based on declared permissions. */ function createServerAPIBridge(app, pluginId, configPath) { const plugin = (0, loader_1.getWasmPlugin)(pluginId); if (!plugin) { throw new Error(`Plugin ${pluginId} not found`); } const capabilities = plugin.metadata.capabilities; return { // Delta Handler API 'delta-handler': { /** * Handle delta message from plugin * * @param pluginIdParam - Plugin identifier * @param deltaJson - Delta message as JSON string * @param version - Signal K version: 1 = v1 (default), 2 = v2 * * Plugins should use v1 for regular navigation data. * Use v2 for Course API paths and other v2-specific data. */ handleMessage: (pluginIdParam, deltaJson, version = 1) => { if (!capabilities.dataWrite) { throw new Error(`Plugin ${pluginId} lacks dataWrite capability`); } try { const delta = JSON.parse(deltaJson); const skVersion = version === 2 ? server_api_1.SKVersion.v2 : server_api_1.SKVersion.v1; debug(`Plugin ${pluginId} emitting delta (${skVersion}):`, delta); // Forward to server's handleMessage with version if (app.handleMessage) { app.handleMessage(pluginId, delta, skVersion); } else { debug('Warning: app.handleMessage not available'); } } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); debug(`Error handling delta from ${pluginId}: ${errorMsg}`); throw error; } } }, // Plugin Config API 'plugin-config': { /** * Read plugin configuration */ readPluginOptions: () => { const storagePaths = (0, wasm_storage_1.getPluginStoragePaths)(configPath, pluginId, plugin.packageName); const config = (0, wasm_storage_1.readPluginConfig)(storagePaths.configFile); return JSON.stringify(config.configuration || {}); }, /** * Save plugin configuration */ savePluginOptions: (configJson) => { try { const configuration = JSON.parse(configJson); const storagePaths = (0, wasm_storage_1.getPluginStoragePaths)(configPath, pluginId, plugin.packageName); const config = { enabled: plugin.enabled, configuration }; (0, wasm_storage_1.writePluginConfig)(storagePaths.configFile, config); plugin.configuration = configuration; debug(`Plugin ${pluginId} saved configuration`); return 0; // Success } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); debug(`Error saving config for ${pluginId}: ${errorMsg}`); return 1; // Error } }, /** * Get data directory path (VFS root from plugin perspective) */ getDataDirPath: () => { // Plugin sees "/" as its VFS root return '/'; } }, // Plugin Status API 'plugin-status': { /** * Set plugin status message */ setPluginStatus: (message) => { plugin.statusMessage = message; debug(`Plugin ${pluginId} status: ${message}`); // Update in app status if available if (app.setPluginStatus) { app.setPluginStatus(pluginId, message); } }, /** * Set plugin error message */ setPluginError: (message) => { plugin.errorMessage = message; plugin.status = 'error'; debug(`Plugin ${pluginId} error: ${message}`); // Update in app status if available if (app.setPluginError) { app.setPluginError(pluginId, message); } }, /** * Debug logging */ debug: (message) => { debug(`[${pluginId}] ${message}`); }, /** * Error logging */ error: (message) => { debug(`[${pluginId}] ERROR: ${message}`); } }, // Full Model API (Signal K full data model access) 'full-model': { /** * Get data from vessel.self path */ getSelfPath: (path) => { if (!capabilities.dataRead) { throw new Error(`Plugin ${pluginId} lacks dataRead capability`); } try { const value = app.getSelfPath ? app.getSelfPath(path) : undefined; return value !== undefined ? JSON.stringify(value) : null; } catch (error) { debug(`Error getting self path ${path} for ${pluginId}:`, error); return null; } }, /** * Get data from any context path */ getPath: (path) => { if (!capabilities.dataRead) { throw new Error(`Plugin ${pluginId} lacks dataRead capability`); } try { const value = app.getPath ? app.getPath(path) : undefined; return value !== undefined ? JSON.stringify(value) : null; } catch (error) { debug(`Error getting path ${path} for ${pluginId}:`, error); return null; } } } }; } /** * Create WASM import object with ServerAPI functions * * This generates the WebAssembly imports that will be available to the plugin. * In Phase 1, we use a simplified approach. Full WIT bindings will be added later. */ function createWasmImports(app, pluginId, configPath) { const bridge = createServerAPIBridge(app, pluginId, configPath); // Create flat import object for WASM // Note: This is a simplified version for Phase 1 // Full WIT integration will provide proper type-safe bindings return { env: { // Delta handling sk_handle_message: (deltaPtr, deltaLen, memory) => { const deltaJson = readStringFromMemory(memory, deltaPtr, deltaLen); bridge['delta-handler'].handleMessage(pluginId, deltaJson); }, // Configuration sk_read_config: (bufPtr, bufLen, memory) => { const configJson = bridge['plugin-config'].readPluginOptions(); return writeStringToMemory(memory, bufPtr, bufLen, configJson); }, sk_save_config: (configPtr, configLen, memory) => { const configJson = readStringFromMemory(memory, configPtr, configLen); return bridge['plugin-config'].savePluginOptions(configJson); }, // Status sk_set_status: (msgPtr, msgLen, memory) => { const message = readStringFromMemory(memory, msgPtr, msgLen); bridge['plugin-status'].setPluginStatus(message); }, sk_set_error: (msgPtr, msgLen, memory) => { const message = readStringFromMemory(memory, msgPtr, msgLen); bridge['plugin-status'].setPluginError(message); }, sk_debug: (msgPtr, msgLen, memory) => { const message = readStringFromMemory(memory, msgPtr, msgLen); bridge['plugin-status'].debug(message); }, // Data model sk_get_self_path: (pathPtr, pathLen, bufPtr, bufLen, memory) => { const path = readStringFromMemory(memory, pathPtr, pathLen); const value = bridge['full-model'].getSelfPath(path); if (value === null) { return 0; // Not found } return writeStringToMemory(memory, bufPtr, bufLen, value); }, sk_get_path: (pathPtr, pathLen, bufPtr, bufLen, memory) => { const path = readStringFromMemory(memory, pathPtr, pathLen); const value = bridge['full-model'].getPath(path); if (value === null) { return 0; // Not found } return writeStringToMemory(memory, bufPtr, bufLen, value); } } }; } /** * Read a string from WASM memory */ function readStringFromMemory(memory, ptr, len) { const buffer = new Uint8Array(memory.buffer, ptr, len); const decoder = new TextDecoder(); return decoder.decode(buffer); } /** * Write a string to WASM memory * Returns the number of bytes written, or 0 if buffer too small */ function writeStringToMemory(memory, ptr, maxLen, str) { const encoder = new TextEncoder(); const encoded = encoder.encode(str); if (encoded.length > maxLen) { debug(`Buffer too small: need ${encoded.length}, have ${maxLen}`); return 0; } const buffer = new Uint8Array(memory.buffer, ptr, maxLen); buffer.set(encoded); return encoded.length; } /** * Call a WASM plugin export with error handling */ function callWasmExport(pluginId, exportName, ...args) { const plugin = (0, loader_1.getWasmPlugin)(pluginId); if (!plugin || !plugin.instance) { throw new Error(`Plugin ${pluginId} not loaded`); } try { const exportFn = plugin.instance.exports[exportName]; if (typeof exportFn !== 'function') { throw new Error(`Export ${exportName} not found or not a function`); } return exportFn(...args); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); debug(`Error calling ${exportName} on ${pluginId}: ${errorMsg}`); throw error; } } //# sourceMappingURL=wasm-serverapi.js.map