UNPKG

@kikitrade/api-gateway-client

Version:

alibaba cloud open api gateway two way communication library

313 lines (281 loc) 10.6 kB
import {v4} from 'uuid'; import { Accept_JSON, Content_Type_Form_Data, Content_Type_Json_Data, FormData, seqInc, Util, WebSocketApiType } from './util'; import url, {parse} from 'url'; import Timeout = NodeJS.Timeout; interface EventListener { (msg: any): void } interface Config { url: string; registerPath?: string; unregisterPath?: string; authType: 'appCode' | 'accessKey' | 'none'; stage: 'TEST' | 'RELEASE'; appCode?: string; appKey?: string; appSecret?: string; } class WS { ws: WebSocket | null; registered = false; registerResp = false; hbStarted = false; autoConnect = true; timer: Timeout | null; registerPath?: string; unregisterPath?: string; host: string; config: Config; lastDeviceId?: string; constructor(config: Config) { this.config = config; this.timer = null; this.host = url.parse(config.url).host as string; if (config.registerPath) { this.registerPath = config.registerPath; } if (config.unregisterPath) { this.unregisterPath = config.unregisterPath; } this.ws = null; } do_send(msg: string){ if (this.ws && this.ws.readyState == WebSocket.OPEN){ try{ this.ws.send(msg); }catch(e){ // } } } register(update: EventListener, deviceId: string, bodyInJson?: string | FormData) { this.ws = new WebSocket(this.config.url); let that = this; this.ws.onopen = function open() { console.log('open:'); that.do_send('RG#' + deviceId); }; this.ws.onmessage = function incoming(event) { console.log('data:', event.data); if (event.data.startsWith('NF#')) { try { let msg = JSON.parse(event.data.substr(3)); if (msg) { console.log('receiving ' + msg); update(msg); } } catch (e) { //ignore error from update callback } return; } if (!that.hbStarted && event.data.startsWith('RO#')) { console.log('login successfully'); that.lastDeviceId = deviceId; if (!that.registered) { that.registered = true; let reg = that.regMsg(that.host, that.registerPath, bodyInJson); that.do_send(JSON.stringify(reg)); } that.hbStarted = true; that.timer = setInterval(function () { that.do_send('H1'); }, 15 * 1000); return; } if (event.data.startsWith('OS') || event.data.startsWith('CR')|| event.data.startsWith('HF')|| event.data.startsWith('CB') || event.data.startsWith('RF')) { try{ that.ws!.close(); that.reconnect(update, deviceId, bodyInJson); }catch (e) { that.reconnect(update, deviceId, bodyInJson); } return; } if (!that.registerResp) { try { const msg = JSON.parse(event.data); if (msg.status > 400) { that.ws!.close(); that.reconnect(update, deviceId, bodyInJson); } } catch (e) { //ignore } that.registerResp = true; //ignore register resp return; } }; this.ws.onclose = function (event) { console.log('ws closed:', event); that.reconnect(update, deviceId, bodyInJson); }; }; unregister(body?: string | FormData) { let reg = this.unregMsg(this.host, this.unregisterPath, body); this.autoConnect = false; if (this.timer) { clearInterval(this.timer); this.timer = null; } this.registered = false; this.registerResp = false; this.hbStarted = false; this.do_send(JSON.stringify(reg)); } send(method: string, path: string, webSocketApiType: WebSocketApiType = 'COMMON', body?: string | FormData): void { let data = ''; let contentType = Content_Type_Json_Data; //if form data, else json if (body && typeof (body) == 'object') { data = this.formDataString(path, body); contentType = Content_Type_Form_Data; } else { data = body as string; } if (this.config.authType === 'none' || this.config.authType === 'appCode') { const msg = this.createMsg(method, this.host, path, 'COMMON', contentType, Accept_JSON, data); this.do_send(JSON.stringify(msg)); return msg; } // else signature mode const util = new Util(this.config.appKey as string, this.config.appSecret as string, this.config.stage); const headers = util.createSignedRequestHeaders('POST', new URL(path, this.config.url).toString(), {}, contentType, Content_Type_Json_Data, webSocketApiType, body) const msg = { method: 'POST', host: this.host, headers: headers, path: path, isBase64: 0, body: data || body || '', }; this.do_send(JSON.stringify(msg)); return; } private reconnect(update: EventListener, deviceId: string, bodyInJson: string | FormData | undefined) { if (this.autoConnect) { if (this.ws) { //force close try { this.ws.close(); if (this.timer) { clearInterval(this.timer); this.timer = null; } this.registered = false; this.registerResp = false; this.hbStarted = false; } catch (e) { //slient } } this.register(update, deviceId, bodyInJson); } } private regMsg(host: string, registerPath = '/register', body?: string | FormData) { let data = ''; let contentType = Content_Type_Json_Data; //if form data, else json if (body && typeof (body) == 'object') { data = this.formDataString(registerPath, body); contentType = Content_Type_Form_Data; } else { data = body as string; } if (this.config.authType === 'none' || this.config.authType === 'appCode') { const msg = this.createMsg('POST', host, registerPath, 'REGISTER', contentType, Accept_JSON, data); return msg; } // else signature mode const util = new Util(this.config.appKey as string, this.config.appSecret as string, this.config.stage); const headers = util.createSignedRequestHeaders('POST', new URL(registerPath, this.config.url).toString(), {}, (body && body instanceof FormData) ? Content_Type_Form_Data : Content_Type_Json_Data, Content_Type_Json_Data, 'REGISTER', body) const msg = { method: 'POST', host: host, headers: headers, path: registerPath, isBase64: 0, body: data || body || '', }; return msg; } private createMsg(method: string, host: string, path: string, api_type = 'COMMON', content_type: string, accept: string, body?: string) { let headers: any = { 'content-type': [content_type], 'accept': [accept], 'x-ca-stage': [this.config.stage], 'x-ca-websocket_api_type': [api_type], 'x-ca-seq': [new Number(seqInc()).toString()], 'x-ca-nonce': [v4().toString()], 'date': [new Date().toUTCString()], 'x-ca-timestamp': [new Date().getTime().toString()], 'ca_version': ['1'], }; if (api_type === 'COMMON' && this.lastDeviceId) { headers['x-ca-deviceid'] = [this.lastDeviceId]; } const msg: any = { method: method, host: host, headers, path: path, 'isBase64': 0, }; if (this.config.appCode) { msg.headers['Authorization'] = [`APPCODE ${this.config.appCode as string}`]; } if (body) { msg.body = body; msg.headers['content-md5'] = [Util.md5(body)]; } return msg; } private unregMsg(host: string, unregisterPath: string = '/unregister', body?: string | FormData) { let data = ''; let contentType = Content_Type_Json_Data //处理body, 如果是form data, 则格式化为 a=1&b=2这样的字符串 //如果本身体是json string, 则需要添加header, 里面有md5 if (body && typeof (body) == 'object') { data = this.formDataString(unregisterPath, body); contentType = Content_Type_Form_Data; } if (this.config.authType === 'none' || this.config.authType === 'appCode') { const msg = this.createMsg('POST', host, unregisterPath, 'UNREGISTER', contentType, Accept_JSON, data); return msg; } // else signature mode const util = new Util(this.config.appKey as string, this.config.appSecret as string, this.config.stage); const headers = util.createSignedRequestHeaders('POST', new URL(unregisterPath, this.config.url).toString(), {}, contentType, Content_Type_Json_Data, 'UNREGISTER', body) const msg = { method: 'POST', host: host, headers: headers, path: unregisterPath, isBase64: 0, body: data || body || '', }; return msg; } private formDataString(urlValue: string, body: FormData): string { const parsedUrl = parse(urlValue, true); let fullQuery = Object.assign(parsedUrl.query, body); const parametersList = Util.buildParameters(fullQuery); if (parametersList.length > 0) { return parametersList.join('&'); } return ''; } } export {WS, EventListener}