UNPKG

@hms-networks/kolibri-js-client

Version:

Kolibri Consumer API client for building kolibri based applications

324 lines 12.7 kB
/* * Copyright 2021 HMS Industrial Networks AB * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http: //www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Url from 'url-parse'; import { Subscription } from './subscription'; import { KolibriConnection } from './kolibri_connection'; import { DefaultKolibriResponse, errorcode, errorFromCode, isJsonRpcError, isJsonRpcFailure, isJsonRpcRequest, isJsonRpcSuccess, isKolibriRpcError, isKolibriRpcRequest, isKolibriRpcSuccess, KolibriErrorResponse, KolibriRequestMethods, KolibriRpcErrorResponse, KolibriRpcSuccessResponse } from '@hms-networks/kolibri-js-core'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { getRequestError } from '../error/kolibri_request_error'; export class BaseClient { constructor(config) { this.config = config; this.rpcId = 1; this.tId = 1; this.subscription = new Subscription(); this.writeTransactions = new Map(); this.consumerRpcs = new Map(); this.kolibriRpcs = new Map(); const urlStr = `${config.host}/${config.project}${config.path}`; const brokerUrl = new Url(urlStr); if (config.port) { brokerUrl.set('port', config.port.toString()); } const connectionOptions = { url: brokerUrl, protocol: this.getKolibriProtocol(), tlsOptions: config.tls, reconnectOptions: config.reconnect, clientMessageListener: (jsonrpc) => this.clientEventDispatcher(jsonrpc) }; if (config.proxy) { const agent = new HttpsProxyAgent(config.proxy); connectionOptions.requestOptions = { agent: agent }; } this.connection = new KolibriConnection(connectionOptions); this.initBuildInConsumerRpcs(); this.registerKolibriRpc(KolibriRequestMethods.GetRpcInfoRequestMethod, this.handleGetRpcInfoRequest.bind(this)); if (this.config.auth) { this.loginParams = this.config.auth; } if (this.config.reconnect) { this.initReconnectHandler(); } this.connection.addOnDisconnectListener(async (event) => { var _a; await ((_a = this.onDisconnectListener) === null || _a === void 0 ? void 0 : _a.call(this, event)); }); } getNextRpcId() { return this.rpcId++; } getNextTid() { return this.tId++; } storeLoginData(params, result) { this.loginParams = params; this.loginResult = result; } storeSubscribeData(params, result) { this.subscribeParams = params; this.subscribeResult = result; } clearSubscribeData() { this.subscribeParams = undefined; this.subscribeResult = undefined; } storeUserSubscribeData(params, result) { this.userSubscribeParams = params; this.userSubscribeResult = result; } clearUserSubscribeData() { this.userSubscribeParams = undefined; this.userSubscribeResult = undefined; } addOnUnsubscribed(listener) { this.subscription.onUnsubscribed = listener; } addOnWriteListener(listener) { this.subscription.onWrite = listener; } addOnUserNotifyListener(listener) { this.subscription.onUserNotify = listener; } addOnErrorListener(listener) { this.subscription.onError = listener; } addOnReconnectListener(listener) { this.onReconnectListener = listener; } addOnDisconnectListener(listener) { this.onDisconnectListener = listener; } async connect() { return this.connection.connect(); } async disconnect() { this.rpcId = 1; this.tId = 1; this.connection.disableReconnect(); await this.connection.close(); } async sendKolibriRpcRequest(request) { const response = await this.connection.sendRequest(request); if (isKolibriRpcSuccess(response)) { return response; } else if (isKolibriRpcError(response)) { throw getRequestError(response.error.code, response.error.data); } else { throw new Error('Unknown response type!'); } } async sendKolibriRequest(kolibriRequest) { const response = await this.connection.sendRequest(kolibriRequest); if (isJsonRpcSuccess(response)) { return response; } else if (isJsonRpcFailure(response)) { throw getRequestError(response.error.code, response.error.data); } else { throw new Error('Unknown response type!'); } } registerKolibriRpc(method, handler) { this.kolibriRpcs.set(method, handler.bind(this)); } async handleGetRpcInfoRequest(_args) { return Array.from(this.kolibriRpcs.keys()); } async handleWriteRequest(request) { var _a, _b, _c, _d; const writeRequest = request; const combinedWriteResult = writeRequest.params.nodes .map((node) => { const nodeProperties = this.subscription.fetchSubscribedNode(node.path); if (!nodeProperties) return; return { ...nodeProperties, ...node }; }) .filter((elem) => elem !== undefined); try { if (writeRequest.params.tid) { this.writeTransactions.set(writeRequest.params.tid, combinedWriteResult); } else if (combinedWriteResult.length !== 0) { (_b = (_a = this.subscription).onWrite) === null || _b === void 0 ? void 0 : _b.call(_a, combinedWriteResult); } const response = new DefaultKolibriResponse(writeRequest.id, 0); await this.connection.sendResponse(response); } catch (e) { (_d = (_c = this.subscription).onError) === null || _d === void 0 ? void 0 : _d.call(_c, e); } } async handleUnsubscribedRequest(request) { var _a, _b, _c, _d; const unsubscribedRequest = request; this.subscription.deleteSubscribedNodes(unsubscribedRequest.params); try { const response = new DefaultKolibriResponse(request.id, 0); await this.connection.sendResponse(response); (_b = (_a = this.subscription).onUnsubscribed) === null || _b === void 0 ? void 0 : _b.call(_a, unsubscribedRequest.params); } catch (e) { (_d = (_c = this.subscription).onError) === null || _d === void 0 ? void 0 : _d.call(_c, e); } } async handleUserNotifyRequest(request) { var _a, _b, _c, _d; const userNotifyRequest = request; try { const response = new DefaultKolibriResponse(request.id, 0); await this.connection.sendResponse(response); (_b = (_a = this.subscription).onUserNotify) === null || _b === void 0 ? void 0 : _b.call(_a, userNotifyRequest.params); } catch (e) { (_d = (_c = this.subscription).onError) === null || _d === void 0 ? void 0 : _d.call(_c, e); } } async handleCommitRequest(request) { var _a, _b, _c, _d; const commitRequest = request; const pendingWriteResult = this.writeTransactions .get(commitRequest.params.tid); if (pendingWriteResult) { (_b = (_a = this.subscription).onWrite) === null || _b === void 0 ? void 0 : _b.call(_a, pendingWriteResult); } else { // could not match commit with pending transaction. Respond to broker anyway. } try { const response = new DefaultKolibriResponse(commitRequest.id, 0); await this.connection.sendResponse(response); } catch (e) { (_d = (_c = this.subscription).onError) === null || _d === void 0 ? void 0 : _d.call(_c, e); } finally { this.writeTransactions.delete(commitRequest.params.tid); } } async clientEventDispatcher(jsonrpc) { if (!isJsonRpcRequest(jsonrpc)) { return; } if (isKolibriRpcRequest(jsonrpc)) { await this.handleKolibriRpcRequest(jsonrpc); } else { await this.handleConsumerRpcRequest(jsonrpc); } } async handleConsumerRpcRequest(jsonrpc) { const handlerFunction = this.consumerRpcs.get(jsonrpc.method); if (!handlerFunction) { const error = errorFromCode(errorcode.METHOD_NOT_FOUND.code); error.data = jsonrpc.method; const response = new class extends KolibriErrorResponse { }(jsonrpc.id, error); await this.connection.sendResponse(response); return; } await handlerFunction(jsonrpc); } async handleKolibriRpcRequest(jsonrpc) { const handlerFunction = this.kolibriRpcs.get(jsonrpc.method); let response; if (!handlerFunction) { const error = errorFromCode(errorcode.METHOD_NOT_FOUND.code); error.data = jsonrpc.method; response = new KolibriRpcErrorResponse(jsonrpc.id, jsonrpc._server, error); await this.connection.sendResponse(response); return; } let result; try { result = await handlerFunction(jsonrpc.params); } catch (e) { try { if (isJsonRpcError(e)) { response = new KolibriRpcErrorResponse(jsonrpc.id, jsonrpc._server, e); } else { response = new KolibriRpcErrorResponse(jsonrpc.id, jsonrpc._server, errorFromCode(errorcode.KOLIBRI_RPC_ERROR.code)); } await this.connection.sendResponse(response); return; } catch (ex) { throw new Error('Failed to send error!'); } } try { response = new KolibriRpcSuccessResponse(jsonrpc.id, jsonrpc._server, result); await this.connection.sendResponse(response); } catch (e) { throw new Error('Failed to send result!'); } } initBuildInConsumerRpcs() { this.consumerRpcs.set(KolibriRequestMethods.WriteRequestMethod, this.handleWriteRequest.bind(this)); this.consumerRpcs.set(KolibriRequestMethods.UnsubscribedRequestMethod, this.handleUnsubscribedRequest.bind(this)); this.consumerRpcs.set(KolibriRequestMethods.UserNotifyRequestMethod, this.handleUserNotifyRequest.bind(this)); this.consumerRpcs.set(KolibriRequestMethods.CommitRequestMethod, this.handleCommitRequest.bind(this)); } initReconnectHandler() { this.connection.addOnReconnectListener(async () => { var _a, _b; // Reconnected try { await this.restoreLogin(); if ((_a = this.config.reconnect) === null || _a === void 0 ? void 0 : _a.resumeSubscriptions) { // Restoring Subscriptions await this.restoreSubscriptions(); } } catch (e) { throw new Error('Restoring client state failed!'); } try { await ((_b = this.onReconnectListener) === null || _b === void 0 ? void 0 : _b.call(this)); } catch (e) { // ignore errors on the listener } }); } async restoreLogin() { if (this.loginParams && this.loginResult) { this.loginParams.client = this.loginResult.client; await this.login(this.loginParams); } else if (this.loginParams) { await this.login(this.loginParams); } else { throw new Error('Unknown client state!'); } } async restoreSubscriptions() { if (this.subscribeParams && this.subscribeResult) { await this.subscribe(this.subscribeParams); } if (this.userSubscribeParams && this.userSubscribeResult) { await this.userSubscribe(this.userSubscribeParams); } } } //# sourceMappingURL=base_client.js.map