UNPKG

node-gotapi

Version:

The node-gotapi is a Node.js implementation of the Generic Open Terminal API Framework (GotAPI) developed by the Open Mobile Alliance (OMA).

493 lines (463 loc) 15.4 kB
/* ------------------------------------------------------------------ * node-gotapi - gotapi-plugin-util.js * * This module is used by Plug-Ins for communication with the GotAPI * Server on the GotAPI-4 Interface. * * Copyright (c) 2017-2019, Futomi Hatano, All rights reserved. * Released under the MIT license * Date: 2019-10-20 * ---------------------------------------------------------------- */ 'use strict'; let mCrypto = require('crypto'); let mConsole = require('./console-util.js'); /* ------------------------------------------------------------------ * Constructor: GotapiPluginUtil(port) * ---------------------------------------------------------------- */ let GotapiPluginUtil = function (port) { // The MessagePort object which communicates with the GotAPI // Server through the GotAPI-4 Interface this.port = port; // Client ID this.client_id_map = {}; // Information of the Plug-In this.info = null; // Callback function which is called when a message comes from the GotAPI Server this.onservicediscoverry = null; this.onclinetid = null; this.onaccesstoken = null; this.onmessage = null; // this.requests = {}; this.error_code_map = require('./error-code.json'); }; /* ------------------------------------------------------------------ * Method: GotapiPluginUtil.init(message) * ---------------------------------------------------------------- */ GotapiPluginUtil.prototype.init = function (info) { let err = this._checkErrorInformationObject(info); if (err) { throw new Error(err); } else { this.info = info; this.port.on('message', this._receiveMessage.bind(this)); } }; GotapiPluginUtil.prototype._checkErrorInformationObject = function (info) { let err = ''; if (typeof (info) !== 'object') { return 'The `PluginInformation` object must be an Object object.'; } let name = info['name']; if (!name) { return 'The `name` property is required in the `PluginInformation` object.'; } else if (typeof (name) !== 'string') { return 'The value of the `name` property in the `PluginInformaton` object must be an string.'; } let services = info['services']; if (!services) { err = 'The `services` property is requierd in the `PluginInformation` object.'; } else if (!Array.isArray(services)) { err = 'The value of the `services` property in the `PluginInformation` object must be an Array object.'; } else if (services.length === 0) { err = 'The number of elements in the `PluginInformation.services` must be grater than 0.' } let property_types = { 'serviceId': 'string', 'name': 'string', 'online': 'boolean', 'scopes': 'array', 'manufacturer': 'string', 'version': 'string', 'type': 'string' }; let required_property_list = [ 'serviceId', 'name', 'online', 'scopes' ]; for (let i = 0; i < services.length; i++) { let s = services[i]; if (typeof (s) !== 'object') { return 'The value of an element in the `PluginInformation.services` must be an Object object.'; } for (let i = 0; i < required_property_list.length; i++) { let name = required_property_list[i]; if (!(name in s)) { return 'The `' + name + '` property is required in the `PluginInformation.services`.' } } for (let name in property_types) { if (!(name in s)) { continue } let type = property_types[name]; let value = s[name]; if (type === 'string' || type === 'boolean') { if (typeof (value) !== type) { return 'The type of the `' + name + '` property in an element of the `PluginInformation.services` must be ' + type + '.'; } } else if (type === 'array') { if (!Array.isArray(value)) { return 'The value of the `' + name + '` property in an element of the `PluginInformation.services` must be an Array object.'; } else if (value.length === 0) { return 'The value of the `' + name + '` property in an element of the `PluginInformation.services` must be an Array object containing at least an element.' } } } } return ''; }; GotapiPluginUtil.prototype._receiveMessage = function (message_orig) { let message = JSON.parse(JSON.stringify(message_orig)); this.requests[message['requestCode']] = message; // Plug-In Discovery (Service Discovery) if (message.profile === 'networkServiceDiscovery') { if (message.attribute === 'getNetworkServices') { this._getNetworkServices(message); } // Plug-In Approval } else if (message.profile === 'authorization') { // Request for registration of application if (message.attribute === 'createClient') { this._createClient(message); // Request for an access token } else if (message.attribute === 'requestAccessToken') { this._requestAccessToken(message); } // Plug-In functions } else { if (this.onmessage && typeof (this.onmessage) === 'function') { try { this.onmessage(message); } catch (error) { mConsole.error('[Plug-In ERROR]', error); this._returnErrorToGotapiServer( this.error_code_map['ERROR'], `The Plug-In caused an error: ` + error.message, message ); } } else { this._returnErrorToGotapiServer( this.error_code_map['ERROR'], 'The Plug-In forgets to attach a callback to reply messages.', message ); } } }; // Plug-In Discovery (Service Discovery) GotapiPluginUtil.prototype._getNetworkServices = function (message) { /* * message = { * receiver: "com.github.futomi.node-gotapi", // The application ID of the GotAPI Server * requestCode: "xxxxxxxxxxxxxxxxxx", // A request code, * api: "gotapi", * profile: "networkServiceDiscovery", * attribute: "getNetworkServices" * } */ if (this.onservicediscoverry && typeof (this.onservicediscoverry) === 'function') { this.onservicediscoverry(message); } else { message['result'] = 0; message['services'] = JSON.parse(JSON.stringify(this.info.services)); this.port.postMessage(message); let request_code = message['requestCode']; if(this.requests[request_code]) { delete this.requests[request_code]; } } }; /* ------------------------------------------------------------------ * Method: GotapiPluginUtil.returnServiceDiscovery(message); * ---------------------------------------------------------------- */ GotapiPluginUtil.prototype.returnServiceDiscovery = function (message) { let request_code = message['requestCode']; let res = this.requests[request_code]; if (!res) { return; } let services = []; if (('services' in message) && Array.isArray(message['services'])) { services = message['services']; } res['result'] = 0; res['services'] = services; this.port.postMessage(res); delete this.requests[request_code]; }; // Plug-In Approval GotapiPluginUtil.prototype._createClient = function (message) { /* * message = { * receiver: "com.github.futomi.node-gotapi", // The application ID of the GotAPI Server * requestCode: 1, // A request code, * api: "gotapi", * profile: "authorization", * attribute: "createClient", * package: "http://localhost:8080" // The origin of the web app * } */ message['accept'] = true; if (this.onclinetid && typeof (this.onclinetid) === 'function') { this.onclinetid(message); } else { this.returnClientIdRequest(message); } }; /* ------------------------------------------------------------------ * Method: GotapiPluginUtil.returnClientIdRequest(message); * ---------------------------------------------------------------- */ GotapiPluginUtil.prototype.returnClientIdRequest = function (message) { let request_code = message['requestCode']; let res = this.requests[request_code]; if (!res) { return; } if (message && message['accept'] === true) { // Create an client ID let client_id = ''; client_id += mCrypto.randomBytes(8).toString('hex') + '_'; client_id += res.package + '_' + Date.now(); let client_id_sha256 = mCrypto.createHash('sha256'); client_id_sha256.update(client_id); client_id = client_id_sha256.digest('hex'); this.client_id_map[client_id] = { client_id: client_id }; // Return the result res['result'] = 0; res['clientId'] = client_id; this.port.postMessage(res); } else { let error_message = 'The Plug-In denied the request for a client ID.'; if (message['errorMessage']) { error_message = message['errorMessage']; } this._returnErrorToGotapiServer( this.error_code_map['NOT_AUTHORIZED'], error_message, res ); } delete this.requests[request_code]; }; // Plug-In Approval GotapiPluginUtil.prototype._requestAccessToken = function (message) { /* * message = { * receiver: "com.github.futomi.node-gotapi", // The application ID of the GotAPI Server * requestCode: 1, // A request code, * api: "gotapi", * profile: "authorization", * attribute: "requestAccessToken", * package: "http://localhost:8080" // The origin of the web app * clientId: "xxxxxxxxxxxxxxx" * } */ // This is an web application identifirer let app_id = message.package; // the web application id let client_id = message.clientId; let error_message = ''; if (!app_id) { error_message = 'A package ID (application ID) is required.'; } else if (!client_id) { error_message = 'A client ID is required.'; } else if (!this.client_id_map[client_id]) { error_message = 'The specified cliend ID has not been registered or has been expired.' } if (error_message) { // Return the result this._returnErrorToGotapiServer( this.error_code_map['NOT_AUTHORIZED'], error_message, message ); } else { message['accept'] = true; if (this.onaccesstoken && typeof (this.onaccesstoken) === 'function') { this.onaccesstoken(message); } else { this.returnAccessTokenRequest(message); } } }; /* ------------------------------------------------------------------ * Method: GotapiPluginUtil.returnAccessTokenRequest(message); * ---------------------------------------------------------------- */ GotapiPluginUtil.prototype.returnAccessTokenRequest = function (message) { let request_code = message['requestCode']; let res = this.requests[request_code]; if (!res) { return; } if (message && message['accept'] === true) { let client_id = res['clientId']; // Create an access token let access_token = ''; access_token += mCrypto.randomBytes(8).toString('hex') + '_'; access_token += client_id + '_'; access_token += Date.now(); let access_token_sha256 = mCrypto.createHash('sha256'); access_token_sha256.update(access_token); access_token = access_token_sha256.digest('hex'); this.client_id_map[client_id]['accessToken'] = access_token; // Return the result res['result'] = 0; res['accessToken'] = access_token; this.port.postMessage(res); } else { let error_message = 'The Plug-In denied the request for an access token.'; if (message['errorMessage']) { error_message = message['errorMessage']; } this._returnErrorToGotapiServer( this.error_code_map['NOT_AUTHORIZED'], error_message, res ); } delete this.requests[request_code]; }; /* ------------------------------------------------------------------ * Method: GotapiPluginUtil.returnMessage(message); * message = { * requestCode: "xxxxxxxxxxxx", * result: 401, * data: null, * errorMessage: "" * } * ---------------------------------------------------------------- */ GotapiPluginUtil.prototype.returnMessage = function (message) { message['action'] = 'RESPONSE'; this._postMessage(message); }; GotapiPluginUtil.prototype._postMessage = function (message) { if (!message) { return; } let request_code = message['requestCode']; if (!request_code || !this.requests[request_code]) { return; } let request = this.requests[request_code]; let res = JSON.parse(JSON.stringify(request)); res['action'] = message['action']; if ('result' in message) { let result = message['result']; if (typeof (result) === 'string' && result.match(/^\d+$/)) { result = parseInt(result, 10); } if (typeof (result) === 'number' && result % 1 === 0) { res['result'] = result; } else { res['statusCode'] = 500; res['errorMessage'] = 'An invalid result code was specified by the Plug-In: ' + message['result'].toString() } } else { res['result'] = 0; } if (res['result'] !== 0) { if ('errorCode' in message) { let error_code = message['errorCode']; if (typeof (error_code) !== 'string') { error_code = error_code.toString(); } res['errorCode'] = error_code; } else { res['errorCode'] = res['result'].toString(); } if ('errorMessage' in message) { let error_message = message['errorMessage']; if (typeof (error_message) !== 'string') { error_message = error_message.toString(); } res['errorMessage'] = error_message; } else { res['errorMessage'] = ''; } } if ('statusCode' in message) { let status_code = message['statusCode']; if (typeof (status_code) === 'string' && status_code.match(/^\d+$/)) { status_code = parseInt(status_code, 10); } if (typeof (status_code) === 'number') { let status_type = parseInt(status_code / 100, 10); if (res['result'] === 0) { if (status_type === 2) { res['statusCode'] = status_code; } else { res['statusCode'] = 200; } } else { if (status_type.toString().match(/^(4|5)$/)) { res['statusCode'] = status_code; } else { res['statusCode'] = 400; } } } else { res['result'] = 1; res['errorCode'] = '1'; res['statusCode'] = 500; res['errorMessage'] = 'The Plug-In module specified an invalid HTTP status code: ' + status_code.toString(); } } else { if (res['result'] === 0) { res['statusCode'] = 200; } else { let ecode = message['errorCode']; if (ecode && typeof (ecode) === 'number' && ecode % 1 === 0 && parseInt(ecode / 100).toString().match(/^(2|4|5)$/)) { // This is for backward compatibility. // The node-gotapi v0.2.2 and earlier used the `errorCode` for a HTTP status code. // This work-around will be deleted sometime in the future. res['statusCode'] = ecode; } else { res['statusCode'] = 400; } } } for (let k in message) { if (!(k in res)) { res[k] = message[k]; } } if (res['result'] === 0) { delete res['errorCode']; delete res['errorMessage']; } this.port.postMessage(res); delete this.requests[request_code]; }; GotapiPluginUtil.prototype._returnErrorToGotapiServer = function (error_code_obj, message, data) { data['result'] = error_code_obj['result']; data['errorCode'] = error_code_obj['errorCode']; data['errorMessage'] = '[' + error_code_obj['errorName'] + '] ' + message; data['statusCode'] = error_code_obj['statusCode']; this.port.postMessage(data); }; /* ------------------------------------------------------------------ * Method: GotapiPluginUtil.pushMessage(message); * message = { * result: 0, * data: 'something' * } * ---------------------------------------------------------------- */ GotapiPluginUtil.prototype.pushMessage = function (message) { if (!message || typeof (message) !== 'object' || !('result' in message)) { return; } let result = message['result']; if (typeof (result) === 'string' && result.match(/^d+$/)) { result = parseInt(result, 10); } if (typeof (result) !== 'number' || result % 1 !== 0) { return; } let res = JSON.parse(JSON.stringify(message)); res['action'] = 'EVENT'; this.port.postMessage(res); }; module.exports = GotapiPluginUtil;