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