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).

385 lines (361 loc) 12.6 kB
/* ------------------------------------------------------------------ * node-gotapi - gotapi-interface-4.js * * Copyright (c) 2017-2019, Futomi Hatano, All rights reserved. * Released under the MIT license * Date: 2019-10-19 * ---------------------------------------------------------------- */ 'use strict'; const mCrypto = require('crypto'); const mGotapiPluginLoader = require('./gotapi-plugin-loader.js'); /* ------------------------------------------------------------------ * Constructor: GotapiInterface4(config, returnCallback) * ---------------------------------------------------------------- */ let GotapiInterface4 = function (config, returnCallback) { this.config = config; this.returnCallback = returnCallback; this.plugins = {}; this.requests = {}; this.requests_approving = {}; this.approved_clients = {}; this.plugin_response_timeout_watch_timer_id = null; this.oncommunication = null; this.error_code_map = require('./error-code.json'); }; /* ------------------------------------------------------------------ * Method: start() * ---------------------------------------------------------------- */ GotapiInterface4.prototype.start = function () { var promise = new Promise((resolve, reject) => { // Find and load the GotAPI Plug-ins let plugin_loader = new mGotapiPluginLoader(this.config); plugin_loader.load((plugins) => { this.plugins = plugins; let list = []; for (let path in plugins) { let p = plugins[path]; list.push(p); // Set a listener for `message` event fired on the message channel for the Plug-In p.channel.on('message', this._receiveMessageFromPlugin.bind(this)); } // Start to watch the timeout of the response from the Plug-In this.plugin_response_timeout_watch_timer_id = setInterval(() => { this._startWatchPluginResponseTimeout(); }, 1000); resolve(list); }); }); return promise; }; GotapiInterface4.prototype._startWatchPluginResponseTimeout = function () { let now = Date.now(); for (let request_code in this.requests) { let data = this.requests[request_code]; let rtime = data['_plugin_req_time']; if (now - rtime > this.config.plugin_response_timeout * 1000) { // If the HTTP response code is `408 Request Timeout`, // Chrome retries the request troublingly. // Therefore, `500 Internal Server Error` is adopted // in this case. data['result'] = 500; data['statusCode'] = 500; data['errorCode'] = 500; data['errorMessage'] = 'TIMEOUT: The Plug-In did not respond the time limit (' + this.config.plugin_response_timeout + ' sec).'; this._returnResponseToGotapiServer(data); delete this.requests[request_code]; } } }; /* ------------------------------------------------------------------ * Method: postMessage(data) * This method is called by the GotAPI Server in order to send a * message to the Plug-In. * ---------------------------------------------------------------- */ GotapiInterface4.prototype.postMessage = function (data) { let request_code = this._createNewRequestCode(); data['requestCode'] = request_code; data['_plugin_req_time'] = Date.now(); let prof = data['profile']; //let attr = data['attribute']; if (prof === 'servicediscovery' || prof === 'serviceDiscovery') { data['profile'] = 'networkServiceDiscovery'; data['attribute'] = 'getNetworkServices'; this.requests[request_code] = data; this._requestServiceDiscovery(request_code); } else if (prof) { let client_id = data['_client']['client_id']; let service_id = data['params']['serviceId']; if (service_id) { if (this.approved_clients[client_id] && this.approved_clients[client_id][service_id]) { data['accessToken'] = this.approved_clients[client_id][service_id]; this.requests[request_code] = data; this._requestCommandToPlugin(request_code); } else { this.requests[request_code] = data; this._requestCreateClientToPlugin(request_code); } } else { this._returnErrorToGotapiServer( this.error_code_map['INVALID_SERVICE_ID'], 'No service ID was specified.', data ); } } else { this._returnErrorToGotapiServer( this.error_code_map['INVALID_PROFILE'], 'No profile was specified.', data ); } }; GotapiInterface4.prototype._createNewRequestCode = function () { let id = ''; id += mCrypto.randomBytes(32).toString('hex') + '_'; id += Date.now(); let sha256 = mCrypto.createHash('sha256'); sha256.update(id); id = sha256.digest('hex'); return id; }; GotapiInterface4.prototype._requestServiceDiscovery = function (request_code) { let data = this.requests[request_code]; let plugin_id_list = Object.keys(this.plugins); data['_plugin_id_list'] = plugin_id_list; data['services'] = []; this._requestServiceDiscoverySequentially(request_code); }; GotapiInterface4.prototype._requestServiceDiscoverySequentially = function (request_code) { let data = this.requests[request_code]; let plugin_id = data['_plugin_id_list'].shift(); if (plugin_id) { data['_plugin_req_time'] = Date.now(); this.plugins[plugin_id]['channel'].postMessage(data); } else { this._returnResponseToGotapiServer(data); delete this.requests[request_code]; } }; GotapiInterface4.prototype._requestCreateClientToPlugin = function (request_code) { let data = this.requests[request_code]; let pres = this._findPluginId(data); let target_plugin_id = pres['plugin_id']; if (!target_plugin_id) { this._returnErrorToGotapiServer( this.error_code_map[pres['error_name']], pres['error_message'], data ); delete this.requests[request_code]; return; } let new_request_code = this._createNewRequestCode(); let new_request_data = { receiver: data['receiver'], requestCode: new_request_code, api: data['api'], profile: 'authorization', attribute: 'createClient', package: data['package'], _plugin_req_time: Date.now() }; this.requests[new_request_code] = new_request_data; this.requests_approving[new_request_code] = data; this.plugins[target_plugin_id]['channel'].postMessage(new_request_data); }; GotapiInterface4.prototype._requestAccessTokenToPlugin = function (request_code) { let data = this.requests[request_code]; let pres = this._findPluginId(this.requests_approving[request_code]); let target_plugin_id = pres['plugin_id']; if (!target_plugin_id) { this._returnErrorToGotapiServer( this.error_code_map[pres['error_name']], pres['error_message'], data ); } data['profile'] = 'authorization'; data['attribute'] = 'requestAccessToken'; data['_plugin_req_time'] = Date.now(); this.plugins[target_plugin_id]['channel'].postMessage(data); }; GotapiInterface4.prototype._requestCommandToPlugin = function (request_code) { let data = this.requests[request_code]; let pres = this._findPluginId(data); let target_plugin_id = pres['plugin_id']; if (target_plugin_id) { this.plugins[target_plugin_id]['channel'].postMessage(data); } else { this._returnErrorToGotapiServer( this.error_code_map[pres['error_name']], pres['error_message'], data ); } }; GotapiInterface4.prototype._returnResponseToGotapiServer = function (data) { delete data['_plugin_req_time']; delete data['requestCode']; delete data['accessToken']; this.returnCallback(data); }; GotapiInterface4.prototype._findPluginId = function (data) { let profile = data['profile']; //let attribute = data['attribute']; let service_id = data['params']['serviceId']; let found_plugin_list = []; for (let id in this.plugins) { let plugin = this.plugins[id]; let service_list = plugin['info']['services']; for (let i = 0; i < service_list.length; i++) { let service = service_list[i]; if (service['serviceId'] === service_id) { found_plugin_list.push({ plugin_id: id, service: service }); } } } if (found_plugin_list.length === 0) { return { error_name: 'UNKNOWN_SERVICE', error_message: 'No service was found.' }; } let plugin_id = ''; for (let i = 0; i < found_plugin_list.length; i++) { let p = found_plugin_list[i]; if (profile === 'serviceinformation' || profile === 'serviceInformation') { if (p['service']['scopes'].indexOf('serviceinformation') >= 0 || p['service']['scopes'].indexOf('serviceInformation') >= 0) { plugin_id = p['plugin_id']; break; } } else if (p['service']['scopes'].indexOf(profile) >= 0) { plugin_id = p['plugin_id']; break; } } if (plugin_id) { return { plugin_id: plugin_id }; } else { return { error_name: 'OUT_OF_SCOPE', error_message: 'There is no Plug-In that the specified profile is in the scope.' }; } }; GotapiInterface4.prototype._receiveMessageFromPlugin = function (res) { if (res['action'] === 'EVENT') { this._receiveEventMessageFromPlugin(res); } else { this._receiveResponseMessageFromPlugin(res); } } GotapiInterface4.prototype._receiveEventMessageFromPlugin = function (res) { this._returnResponseToGotapiServer(res); }; GotapiInterface4.prototype._receiveResponseMessageFromPlugin = function (res) { let request_code = res['requestCode']; let req = this.requests[request_code]; if (!req) { return; } // Service Discovery if (req['profile'] === 'networkServiceDiscovery') { if (res['result'] === 0 && res['services'] && Array.isArray(res['services']) && res['services'].length > 0) { res['services'].forEach((s) => { req['services'].push(s); }); } this._requestServiceDiscoverySequentially(request_code); // Plug-In authorization } else if (req['profile'] === 'authorization') { let orig_request_code = this.requests_approving[request_code]['requestCode']; let orig_request_data = this.requests[orig_request_code]; if (orig_request_data) { // Created a client ID if (req['attribute'] === 'createClient') { if (res['result'] === 0 && res['clientId']) { req['clientId'] = res['clientId']; orig_request_data['clientId'] = res['clientId']; this._requestAccessTokenToPlugin(request_code); } else { let err_message = 'The Plug-In denied the request for a client ID.'; if (res['errorMessage']) { err_message = res['errorMessage']; } this._returnErrorToGotapiServer( this.error_code_map['INVALID_CLIENT_ID'], err_message, orig_request_data ); delete this.requests[orig_request_code]; delete this.requests[request_code]; delete this.requests_approving[request_code]; } // Created an access token } else if (req['attribute'] === 'requestAccessToken') { if (res['result'] === 0) { let client_id = orig_request_data['_client']['client_id']; if (!this.approved_clients[client_id]) { this.approved_clients[client_id] = {}; } let service_id = orig_request_data['params']['serviceId']; this.approved_clients[client_id][service_id] = res['accessToken']; orig_request_data['accessToken'] = res['accessToken']; req['accessToken'] = res['accessToken']; delete this.requests[request_code]; delete this.requests_approving[request_code]; this._requestCommandToPlugin(orig_request_code); } else { let err_message = 'The Plug-In denied the request for an access token.'; if (res['errorMessage']) { err_message = res['errorMessage']; } this._returnErrorToGotapiServer( this.error_code_map['INVALID_TOKEN'], err_message, orig_request_data ); delete this.requests[orig_request_code]; delete this.requests[request_code]; delete this.requests_approving[request_code]; } } else { this._returnErrorToGotapiServer( this.error_code_map['INVALID_ATTRIBUTE'], 'The specified attribute is not supported.', orig_request_data ); delete this.requests[orig_request_code]; delete this.requests[request_code]; delete this.requests_approving[request_code]; } } else { delete this.requests[request_code]; delete this.requests_approving[request_code]; this._returnErrorToGotapiServer( this.error_code_map['ERROR'], 'Unknown request.', res ); } } else { for (let k in req) { res[k] = req[k]; } this._returnResponseToGotapiServer(res); delete this.requests[request_code]; } }; GotapiInterface4.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._returnResponseToGotapiServer(data); }; module.exports = GotapiInterface4;