UNPKG

signalk-server

Version:

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

268 lines 12.7 kB
"use strict"; /* eslint-disable @typescript-eslint/no-explicit-any */ /** * WASM Weather Provider Support * * Handles weather provider registration and handler invocation for WASM plugins. * Integrates with Signal K's Weather API at /signalk/v2/api/weather */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.wasmWeatherProviders = void 0; exports.callWasmWeatherHandler = callWasmWeatherHandler; exports.updateWeatherProviderInstance = updateWeatherProviderInstance; exports.cleanupWeatherProviders = cleanupWeatherProviders; exports.createWeatherProviderBinding = createWeatherProviderBinding; const debug_1 = __importDefault(require("debug")); const debug = (0, debug_1.default)('signalk:wasm:weather-provider'); /** * Registered weather providers from WASM plugins * Key: pluginId */ exports.wasmWeatherProviders = new Map(); /** * Call a WASM weather handler function * Handles both AssemblyScript and Rust plugins with Asyncify support for async operations */ async function callWasmWeatherHandler(pluginInstance, handlerName, requestJson) { try { const asLoader = pluginInstance.asLoader; const rawExports = pluginInstance.instance?.exports; if (asLoader && typeof asLoader.exports[handlerName] === 'function') { // AssemblyScript: allocate string in WASM memory, pass pointer, get string pointer back // Need to handle Asyncify for handlers that call fetchSync const requestPtr = asLoader.exports.__newString(requestJson); // Set up Asyncify resume handling let resumePromiseResolve = null; const resumePromise = new Promise((resolve) => { resumePromiseResolve = resolve; }); // Store the result pointer from the handler call let handlerResultPtr = null; if (pluginInstance.setAsyncifyResume) { pluginInstance.setAsyncifyResume(() => { debug(`Re-calling ${handlerName} to resume from rewind state`); const resumeResultPtr = asLoader.exports[handlerName](requestPtr); const result = asLoader.exports.__getString(resumeResultPtr); if (resumePromiseResolve) { resumePromiseResolve(result); } return resumeResultPtr; }); } // Call the handler handlerResultPtr = asLoader.exports[handlerName](requestPtr); // Check if we're in Asyncify unwind state if (typeof asLoader.exports.asyncify_get_state === 'function') { const state = asLoader.exports.asyncify_get_state(); debug(`Asyncify state after ${handlerName}: ${state}`); if (state === 1) { // State 1 = unwound, waiting for async operation debug(`${handlerName} is in unwound state - waiting for async operation to complete`); const result = await resumePromise; debug(`${handlerName} async operation completed`); if (pluginInstance.setAsyncifyResume) { pluginInstance.setAsyncifyResume(null); } return result; } else { // Not in async state, clean up if (pluginInstance.setAsyncifyResume) { pluginInstance.setAsyncifyResume(null); } } } // Normal synchronous return return asLoader.exports.__getString(handlerResultPtr); } else if (rawExports && typeof rawExports[handlerName] === 'function') { // Rust: buffer-based string passing if (typeof rawExports.allocate !== 'function') { debug(`Plugin ${pluginInstance.pluginId} missing allocate export`); return null; } const requestBytes = Buffer.from(requestJson, 'utf8'); const requestPtr = rawExports.allocate(requestBytes.length); const responseMaxLen = 65536; // 64KB response buffer const responsePtr = rawExports.allocate(responseMaxLen); // Write request to WASM memory const memory = rawExports.memory; const memView = new Uint8Array(memory.buffer); memView.set(requestBytes, requestPtr); // Call handler: (request_ptr, request_len, response_ptr, response_max_len) -> written_len const writtenLen = rawExports[handlerName](requestPtr, requestBytes.length, responsePtr, responseMaxLen); // Read response from WASM memory const responseBytes = new Uint8Array(memory.buffer, responsePtr, writtenLen); const responseJson = new TextDecoder('utf-8').decode(responseBytes); // Deallocate buffers if (typeof rawExports.deallocate === 'function') { rawExports.deallocate(requestPtr, requestBytes.length); rawExports.deallocate(responsePtr, responseMaxLen); } return responseJson; } debug(`Handler ${handlerName} not found in plugin ${pluginInstance.pluginId}`); return null; } catch (error) { debug(`Error calling weather handler ${handlerName}: ${error}`); return null; } } /** * Update weather provider references with a newly loaded plugin instance */ function updateWeatherProviderInstance(pluginId, pluginInstance) { const provider = exports.wasmWeatherProviders.get(pluginId); if (provider) { provider.pluginInstance = pluginInstance; debug(`Updated weather provider ${pluginId} with plugin instance`); } } /** * Clean up weather provider registrations for a plugin * @param pluginId The plugin ID * @param app The Signal K app (optional, if provided will also unregister from WeatherApi) */ function cleanupWeatherProviders(pluginId, app) { if (exports.wasmWeatherProviders.has(pluginId)) { debug(`Removing weather provider registration: ${pluginId}`); exports.wasmWeatherProviders.delete(pluginId); } // Also unregister from Signal K WeatherApi if (app && app.weatherApi && typeof app.weatherApi.unRegister === 'function') { try { app.weatherApi.unRegister(pluginId); debug(`Unregistered ${pluginId} from WeatherApi`); } catch (error) { debug(`Error unregistering from WeatherApi: ${error}`); } } } /** * Create the sk_register_weather_provider host binding * * WASM plugins call this to register as a weather provider. * The plugin must export handler functions: * - weather_get_observations(requestJson) -> responseJson * - weather_get_forecasts(requestJson) -> responseJson * - weather_get_warnings(requestJson) -> responseJson */ function createWeatherProviderBinding(pluginId, capabilities, app, readUtf8String) { return (namePtr, nameLen) => { try { const providerName = readUtf8String(namePtr, nameLen); debug(`[${pluginId}] Registering as weather provider: ${providerName}`); // Check if plugin has weatherProvider capability if (!capabilities.weatherProvider) { debug(`[${pluginId}] weatherProvider capability not granted`); return 0; // Failure } // Check if app and weatherApi are available if (!app || !app.weatherApi) { debug(`[${pluginId}] app.weatherApi not available`); return 0; } // Store the registration (we'll update the pluginInstance reference after instance creation) exports.wasmWeatherProviders.set(pluginId, { pluginId, providerName, pluginInstance: null // Will be set after full instance creation }); // Create WeatherProvider object that calls into WASM handlers const weatherProvider = { name: providerName, methods: { pluginId: pluginId, /** * Get weather observations for a position * @param position {latitude, longitude} * @param options {maxCount?, startDate?, custom?} */ getObservations: async (position, options) => { const provider = exports.wasmWeatherProviders.get(pluginId); if (!provider || !provider.pluginInstance) { debug(`[${pluginId}] Weather provider instance not ready`); return []; } const requestJson = JSON.stringify({ position, options }); const result = await callWasmWeatherHandler(provider.pluginInstance, 'weather_get_observations', requestJson); if (result) { try { return JSON.parse(result); } catch (e) { debug(`[${pluginId}] Failed to parse observations response: ${e}`); return []; } } return []; }, /** * Get weather forecasts for a position * @param position {latitude, longitude} * @param type 'daily' | 'point' * @param options {maxCount?, startDate?, custom?} */ getForecasts: async (position, type, options) => { const provider = exports.wasmWeatherProviders.get(pluginId); if (!provider || !provider.pluginInstance) { debug(`[${pluginId}] Weather provider instance not ready`); return []; } const requestJson = JSON.stringify({ position, type, options }); const result = await callWasmWeatherHandler(provider.pluginInstance, 'weather_get_forecasts', requestJson); if (result) { try { return JSON.parse(result); } catch (e) { debug(`[${pluginId}] Failed to parse forecasts response: ${e}`); return []; } } return []; }, /** * Get weather warnings for a position * @param position {latitude, longitude} */ getWarnings: async (position) => { const provider = exports.wasmWeatherProviders.get(pluginId); if (!provider || !provider.pluginInstance) { debug(`[${pluginId}] Weather provider instance not ready`); return []; } const requestJson = JSON.stringify({ position }); const result = await callWasmWeatherHandler(provider.pluginInstance, 'weather_get_warnings', requestJson); if (result) { try { return JSON.parse(result); } catch (e) { debug(`[${pluginId}] Failed to parse warnings response: ${e}`); return []; } } return []; } } }; // Register with Signal K WeatherApi app.weatherApi.register(pluginId, weatherProvider); debug(`[${pluginId}] Successfully registered as weather provider: ${providerName}`); return 1; // Success } catch (error) { debug(`Plugin register weather provider error: ${error}`); return 0; } }; } //# sourceMappingURL=weather-provider.js.map