UNPKG

@signalk/resources-provider

Version:

Resources provider plugin for Signal K server.

391 lines (390 loc) 14.4 kB
"use strict"; 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const server_api_1 = require("@signalk/server-api"); const filestorage_1 = require("./lib/filestorage"); const openapi = __importStar(require("./openApi.json")); const CONFIG_SCHEMA = { properties: { standard: { type: 'object', title: 'Resources (standard)', description: 'ENABLE / DISABLE provider for the following SignalK resource types.', properties: { routes: { type: 'boolean', title: 'ROUTES' }, waypoints: { type: 'boolean', title: 'WAYPOINTS' }, notes: { type: 'boolean', title: 'NOTES' }, regions: { type: 'boolean', title: 'REGIONS' }, charts: { type: 'boolean', title: 'CHART SOURCES' } } }, custom: { type: 'array', title: 'Resources (custom)', description: 'Add provider for custom resource collections.', items: { type: 'object', required: ['name'], properties: { name: { type: 'string', title: 'Collection Name', description: '/signalk/v2/api/resources/{name}' }, description: { type: 'string', title: 'Description', description: 'Type of resource in this collection.' } } } } } }; const CONFIG_UISCHEMA = { standard: { routes: { 'ui:widget': 'checkbox', 'ui:title': ' ', 'ui:help': '/signalk/v2/api/resources/routes' }, waypoints: { 'ui:widget': 'checkbox', 'ui:title': ' ', 'ui:help': '/signalk/v2/api/resources/waypoints' }, notes: { 'ui:widget': 'checkbox', 'ui:title': ' ', 'ui:help': '/signalk/v2/api/resources/notes' }, regions: { 'ui:widget': 'checkbox', 'ui:title': ' ', 'ui:help': '/signalk/v2/api/resources/regions' }, charts: { 'ui:widget': 'checkbox', 'ui:title': ' ', 'ui:help': '/signalk/v2/api/resources/charts' } } }; module.exports = (server) => { let restart; const plugin = { id: 'resources-provider', name: 'Resources Provider (built-in)', schema: () => CONFIG_SCHEMA, uiSchema: () => CONFIG_UISCHEMA, start: (settings, restartPlugin) => { restart = restartPlugin; doStartup(settings); }, stop: () => { doShutdown(); }, registerWithRouter(router) { initMgtEndpoints(router); }, getOpenApi: () => openapi }; const db = new filestorage_1.FileStore(plugin.id, server.debug); let config; const doStartup = (settings) => { try { server.debug(`${plugin.name} starting.......`); config = cleanConfig(settings); server.debug(`Applied config: ${JSON.stringify(config)}`); // compile list of enabled resource types let apiProviderFor = []; Object.entries(config.standard).forEach((i) => { if (i[1]) { apiProviderFor.push(i[0]); } }); if (config.custom && Array.isArray(config.custom)) { const customTypes = config.custom.map((i) => { return i.name; }); apiProviderFor = apiProviderFor.concat(customTypes); } server.debug(`** Enabled resource types: ${JSON.stringify(apiProviderFor)}`); // register as provider for enabled resource types const result = registerProviders(apiProviderFor); if (result.length !== 0) { server.setPluginError(`Error registering providers: ${result.toString()}`); } else { server.setPluginStatus(`Providing: ${apiProviderFor.toString()}`); } // initialise resource storage db.init({ settings: config, basePath: server.getDataDirPath() }) .then((res) => { if (res.error) { const msg = `*** ERROR: ${res.message} ***`; server.error(msg); server.setPluginError(msg); } server.debug(`** ${plugin.name} started... ${!res.error ? 'OK' : 'with errors!'}`); }) .catch((e) => { server.debug(e.message); const msg = `Initialisation Error! See console for details.`; server.setPluginError(msg); }); } catch (error) { const msg = `Started with errors!`; server.setPluginError(msg); server.error('error: ' + error); } }; const doShutdown = () => { server.debug(`${plugin.name} stopping.......`); server.debug('** Un-registering Update Handler(s) **'); const msg = 'Stopped.'; server.setPluginStatus(msg); }; /** process changes in config schema */ const cleanConfig = (options) => { server.debug(`Check / Clean loaded settings...`); const defaultConfig = { standard: { routes: true, waypoints: true, notes: true, regions: true, charts: true }, custom: [] }; // set / save defaults if no saved settings if (!options?.standard) { server.savePluginOptions(defaultConfig, () => { server.debug(`Default configuration applied...`); }); return defaultConfig; } // check / clean settings if (!Array.isArray(options?.custom)) { options.custom = []; } options.custom.forEach((i) => { i.description = i.description ?? ''; }); server_api_1.SIGNALKRESOURCETYPES.forEach((r) => { if (!(r in options.standard)) { options.standard[r] = true; } }); options.custom = options.custom.filter((i) => !(i.name in defaultConfig.standard)); server.savePluginOptions(options, () => { server.debug(`Configuration cleaned and saved...`); }); return options; }; /** plugin management endpoints */ const initMgtEndpoints = (router) => { const ApiResponses = { ok: { state: 'COMPLETED', statusCode: 200, message: 'OK' }, invalid: { state: 'FAILED', statusCode: 400, message: `Invalid Data supplied!` }, notFound: { state: 'FAILED', statusCode: 400, message: `Entry not found!` }, unauthorised: { state: 'FAILED', statusCode: 403, message: 'Unauthorised' }, exists: { state: 'FAILED', statusCode: 400, message: 'Collection already exists!' }, errorCreate: { state: 'FAILED', statusCode: 500, message: 'Error creating collection!' } }; // add new resource collection router.post('/_config/:rescollection', async (req, res) => { server.debug('Add collection request...', req.params); if (!req.params.rescollection) { res.status(ApiResponses.invalid.statusCode).json(ApiResponses.invalid); return; } const e = config.custom.find((i) => i.name.toLowerCase() === req.params.rescollection.toLowerCase()); if (e || req.params.rescollection.toLowerCase() in config.standard) { res.status(ApiResponses.exists.statusCode).json(ApiResponses.exists); return; } server.debug('****** Creating collection ***'); const coll = {}; coll[req.params.rescollection] = true; const r = await db.createSavePaths(coll); if (r.error) { server.debug(r.message); res .status(ApiResponses.errorCreate.statusCode) .json(ApiResponses.errorCreate); } else { config.custom.push({ name: req.params.rescollection, description: req.body.description ?? '' }); server.savePluginOptions(config, () => { server.debug('settings saved...'); }); res.status(200).json(ApiResponses.ok); restart(config); } }); // remove resource collection config (does not remove folder of files.) router.delete('/_config/:rescollection', async (req, res) => { server.debug('Remove collection request...', req.params); if (!req.params.rescollection) { res.status(ApiResponses.invalid.statusCode).json(ApiResponses.invalid); return; } const e = config.custom.findIndex((i) => i.name.toLowerCase() === req.params.rescollection.toLowerCase()); if (e === -1) { res .status(ApiResponses.notFound.statusCode) .json(ApiResponses.notFound); return; } if (req.params.rescollection.toLowerCase() in config.standard) { res.status(ApiResponses.invalid.statusCode).json(ApiResponses.invalid); return; } server.debug('****** Removing collection ***'); config.custom.splice(e, 1); server.savePluginOptions(config, () => { server.debug('settings saved...'); }); res.status(200).json(ApiResponses.ok); restart(config); }); // get configuration router.get('/_config', (req, res) => { res.json(config); }); }; const getVesselPosition = () => { const p = server.getSelfPath('navigation.position'); return p && p.value ? [p.value.longitude, p.value.latitude] : null; }; const registerProviders = (resTypes) => { const failed = []; resTypes.forEach((resType) => { try { server.registerResourceProvider({ type: resType, methods: { listResources: (params) => { return apiGetResources(resType, params); }, getResource: (id, property) => { return db.getResource(resType, (0, filestorage_1.getUuid)(id), property); }, // eslint-disable-next-line @typescript-eslint/no-explicit-any setResource: (id, value) => { return apiSetResource(resType, id, value); }, deleteResource: (id) => { return apiSetResource(resType, id, null); } } }); } catch (_error) { failed.push(resType); } }); return failed; }; // Signal K server Resource Provider interface functions const apiGetResources = async (resType, // eslint-disable-next-line @typescript-eslint/no-explicit-any params = {} // eslint-disable-next-line @typescript-eslint/no-explicit-any ) => { if (typeof params.position === 'undefined') { params.position = getVesselPosition(); } server.debug(`*** apiGetResource: ${resType}, ${JSON.stringify(params)}`); return await db.getResources(resType, params); }; const apiSetResource = async (resType, id, // eslint-disable-next-line @typescript-eslint/no-explicit-any value) => { server.debug(`*** apiSetResource: ${resType}, ${id}, ${value}`); const r = { type: resType, id, value }; return await db.setResource(r); }; return plugin; };