@signalk/resources-provider
Version:
Resources provider plugin for Signal K server.
391 lines (390 loc) • 14.4 kB
JavaScript
;
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;
};