signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
199 lines • 9.47 kB
JavaScript
;
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* WASM Resource Provider Support
*
* Handles resource provider registration and handler invocation for WASM plugins
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.wasmResourceProviders = void 0;
exports.callWasmResourceHandler = callWasmResourceHandler;
exports.updateResourceProviderInstance = updateResourceProviderInstance;
exports.cleanupResourceProviders = cleanupResourceProviders;
exports.createResourceProviderBinding = createResourceProviderBinding;
const debug_1 = __importDefault(require("debug"));
const debug = (0, debug_1.default)('signalk:wasm:resource-provider');
/**
* Registered resource providers from WASM plugins
* Key: pluginId:resourceType
*/
exports.wasmResourceProviders = new Map();
/**
* Call a WASM resource handler function
* Handles both AssemblyScript and Rust plugins
*/
function callWasmResourceHandler(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
const requestPtr = asLoader.exports.__newString(requestJson);
const resultPtr = asLoader.exports[handlerName](requestPtr);
return asLoader.exports.__getString(resultPtr);
}
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 resource handler ${handlerName}: ${error}`);
return null;
}
}
/**
* Update resource provider references with a newly loaded plugin instance
*/
function updateResourceProviderInstance(pluginId, pluginInstance) {
if (exports.wasmResourceProviders && exports.wasmResourceProviders.size > 0) {
exports.wasmResourceProviders.forEach((provider, key) => {
if (provider.pluginId === pluginId) {
provider.pluginInstance = pluginInstance;
debug(`Updated resource provider ${key} with plugin instance`);
}
});
}
}
/**
* Clean up resource provider registrations for a plugin
* @param pluginId The plugin ID
* @param app The Signal K app (optional, if provided will also unregister from ResourcesApi)
*/
function cleanupResourceProviders(pluginId, app) {
const keysToDelete = [];
exports.wasmResourceProviders.forEach((provider, key) => {
if (provider.pluginId === pluginId) {
keysToDelete.push(key);
}
});
keysToDelete.forEach((key) => {
debug(`Removing resource provider registration: ${key}`);
exports.wasmResourceProviders.delete(key);
});
// Also unregister from Signal K ResourcesApi
if (app &&
app.resourcesApi &&
typeof app.resourcesApi.unRegister === 'function') {
try {
app.resourcesApi.unRegister(pluginId);
debug(`Unregistered ${pluginId} from ResourcesApi`);
}
catch (error) {
debug(`Error unregistering from ResourcesApi: ${error}`);
}
}
}
/**
* Create the sk_register_resource_provider host binding
*/
function createResourceProviderBinding(pluginId, capabilities, app, readUtf8String) {
return (typePtr, typeLen) => {
try {
const resourceType = readUtf8String(typePtr, typeLen);
debug(`[${pluginId}] Registering as resource provider for: ${resourceType}`);
// Check if plugin has resourceProvider capability
if (!capabilities.resourceProvider) {
debug(`[${pluginId}] resourceProvider capability not granted`);
return 0; // Failure
}
// Check if app and resourcesApi are available
if (!app || !app.resourcesApi) {
debug(`[${pluginId}] app.resourcesApi not available`);
return 0;
}
// Store the registration (we'll update the pluginInstance reference after instance creation)
const key = `${pluginId}:${resourceType}`;
exports.wasmResourceProviders.set(key, {
pluginId,
resourceType,
pluginInstance: null // Will be set after full instance creation
});
// Create wrapper methods that call into WASM
// Note: resourceType is captured from closure scope
const providerMethods = {
listResources: async (query) => {
const provider = exports.wasmResourceProviders.get(key);
if (!provider || !provider.pluginInstance) {
debug(`[${pluginId}] Resource provider instance not ready`);
return {};
}
// Include resourceType so WASM knows which type to list
const queryJson = JSON.stringify({ ...query, resourceType });
const result = callWasmResourceHandler(provider.pluginInstance, 'resources_list_resources', queryJson);
return result ? JSON.parse(result) : {};
},
getResource: async (id, property) => {
const provider = exports.wasmResourceProviders.get(key);
if (!provider || !provider.pluginInstance) {
debug(`[${pluginId}] Resource provider instance not ready`);
return {};
}
// Include resourceType so WASM knows which storage to search
const requestJson = JSON.stringify({ id, property, resourceType });
const result = callWasmResourceHandler(provider.pluginInstance, 'resources_get_resource', requestJson);
return result ? JSON.parse(result) : {};
},
setResource: async (id, value) => {
const provider = exports.wasmResourceProviders.get(key);
if (!provider || !provider.pluginInstance) {
debug(`[${pluginId}] Resource provider instance not ready`);
return;
}
// Include resourceType so WASM knows which storage to update
const requestJson = JSON.stringify({ id, value, resourceType });
callWasmResourceHandler(provider.pluginInstance, 'resources_set_resource', requestJson);
},
deleteResource: async (id) => {
const provider = exports.wasmResourceProviders.get(key);
if (!provider || !provider.pluginInstance) {
debug(`[${pluginId}] Resource provider instance not ready`);
return;
}
// Include resourceType so WASM knows which storage to delete from
const requestJson = JSON.stringify({ id, resourceType });
callWasmResourceHandler(provider.pluginInstance, 'resources_delete_resource', requestJson);
}
};
// Register with Signal K ResourcesApi
app.resourcesApi.register(pluginId, {
type: resourceType,
methods: providerMethods
});
debug(`[${pluginId}] Successfully registered as ${resourceType} resource provider`);
return 1; // Success
}
catch (error) {
debug(`Plugin register resource provider error: ${error}`);
return 0;
}
};
}
//# sourceMappingURL=resource-provider.js.map