UNPKG

wechaty-puppet-padplus

Version:
467 lines 21.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const grpc_1 = __importDefault(require("grpc")); const util_1 = __importDefault(require("util")); const uuid_1 = require("uuid"); const config_1 = require("../config"); const PadPlusServer_grpc_pb_1 = require("./proto-ts/PadPlusServer_grpc_pb"); // add proto file from Gao const callbackHelper_1 = require("../utils/callbackHelper"); const PadPlusServer_pb_1 = require("./proto-ts/PadPlusServer_pb"); const events_1 = require("events"); const grpc_event_emitter_1 = require("./grpc-event-emitter"); const rx_queue_1 = require("rx-queue"); const PRE = 'GRPC_GATEWAY'; const NEED_CALLBACK_API_LIST = [ PadPlusServer_pb_1.ApiType.SEND_MESSAGE, PadPlusServer_pb_1.ApiType.SEND_FILE, PadPlusServer_pb_1.ApiType.GET_MESSAGE_MEDIA, PadPlusServer_pb_1.ApiType.SEARCH_CONTACT, PadPlusServer_pb_1.ApiType.ADD_CONTACT, PadPlusServer_pb_1.ApiType.CREATE_ROOM, PadPlusServer_pb_1.ApiType.GET_ROOM_ANNOUNCEMENT, PadPlusServer_pb_1.ApiType.SET_ROOM_ANNOUNCEMENT, PadPlusServer_pb_1.ApiType.HEARTBEAT, PadPlusServer_pb_1.ApiType.GET_ROOM_QRCODE, PadPlusServer_pb_1.ApiType.GET_CONTACT_SELF_QRCODE, PadPlusServer_pb_1.ApiType.SET_CONTACT_SELF_INFO, PadPlusServer_pb_1.ApiType.GET_CONTACT_SELF_INFO, PadPlusServer_pb_1.ApiType.LOGOUT, ]; class GrpcGateway extends events_1.EventEmitter { constructor(token, endpoint) { super(); this.token = token; this.endpoint = endpoint; this.eventEmitterMap = {}; this.stopping = false; this.client = new PadPlusServer_grpc_pb_1.PadPlusServerClient(this.endpoint, grpc_1.default.credentials.createInsecure()); this.isAlive = false; this.reconnectStatus = true; this.timeoutNumber = 0; this.startTime = Date.now(); } static get Instance() { return this._instance; } static init(token, endpoint, name) { return __awaiter(this, void 0, void 0, function* () { if (!this._instance) { const instance = new GrpcGateway(token, endpoint); yield instance.initSelf(); this._instance = instance; } else if (!this._instance.isAlive) { yield this._instance.stop(); const instance = new GrpcGateway(token, endpoint); yield instance.initSelf(); this._instance = instance; } return this._instance.addNewInstance(name); }); } keepHeartbeat() { return __awaiter(this, void 0, void 0, function* () { config_1.log.silly(PRE, `keepHeartbeat()`); try { const res = yield this.request(PadPlusServer_pb_1.ApiType.HEARTBEAT, ''); if (!res) { throw new Error(`no heartbeat response from grpc server`); } } catch (error) { config_1.log.error(`can not get heartbeat from grpc server`, error); Object.values(this.eventEmitterMap).map(emitter => { emitter.emit('reconnect'); }); } }); } static release() { return __awaiter(this, void 0, void 0, function* () { if (this._instance) { yield this._instance.stop(); this._instance = undefined; } }); } initSelf() { return __awaiter(this, void 0, void 0, function* () { this.debounceQueue = new rx_queue_1.DebounceQueue(30 * 1000); this.debounceQueueSubscription = this.debounceQueue.subscribe(() => __awaiter(this, void 0, void 0, function* () { try { yield this.keepHeartbeat(); } catch (e) { config_1.log.silly(PRE, `debounce error : ${util_1.default.inspect(e)}`); } })); this.throttleQueue = new rx_queue_1.ThrottleQueue(15 * 1000); this.throttleQueueSubscription = this.throttleQueue.subscribe((data) => { config_1.log.silly(PRE, `throttleQueue emit heartbeat.`); Object.values(this.eventEmitterMap).map(emitter => { emitter.emit('heartbeat', data.getRequestid()); }); }); yield this.initGrpcGateway(); this.isAlive = true; }); } addNewInstance(name) { const eventEmitter = new grpc_event_emitter_1.GrpcEventEmitter(name); this.eventEmitterMap[name] = eventEmitter; return eventEmitter; } emit(event, data) { return super.emit(event, data); } on(event, listener) { config_1.log.verbose(PRE, `on(${event}, ${typeof listener}) registered`); super.on(event, (data) => { try { listener.call(this, data); } catch (e) { config_1.log.error(PRE, `onFunction(${event}) listener exception: ${e}`); } }); return this; } checkTimeout(uin) { return __awaiter(this, void 0, void 0, function* () { if (this.timeoutNumber > 10 && Date.now() - this.startTime <= 3 * 60 * 1000) { yield this.request(PadPlusServer_pb_1.ApiType.RECONNECT, uin); this.startTime = Date.now(); this.timeoutNumber = 0; } else if (this.timeoutNumber === 0) { this.startTime = Date.now(); } else if (Date.now() - this.startTime > 3 * 60 * 1000) { this.startTime = Date.now(); this.timeoutNumber = 0; } this.timeoutNumber++; }); } request(apiType, uin, data) { return __awaiter(this, void 0, void 0, function* () { const request = new PadPlusServer_pb_1.RequestObject(); const requestId = uuid_1.v4(); config_1.log.silly(PRE, `GRPC Request ApiType: ${apiType}`); request.setToken(this.token); if (uin !== '') { request.setUin(uin); } request.setApitype(apiType); request.setParams(JSON.stringify(data)); request.setRequestid(requestId); const traceId = uuid_1.v4(); request.setTraceid(traceId); try { const result = yield this._request(request); if (result && NEED_CALLBACK_API_LIST.includes(apiType)) { return new Promise((resolve, reject) => { let timeoutMs = 5 * 1000; switch (apiType) { case PadPlusServer_pb_1.ApiType.SEND_MESSAGE: case PadPlusServer_pb_1.ApiType.SEND_FILE: timeoutMs = 3 * 60 * 1000; break; case PadPlusServer_pb_1.ApiType.GET_MESSAGE_MEDIA: timeoutMs = 5 * 60 * 1000; break; case PadPlusServer_pb_1.ApiType.SEARCH_CONTACT: case PadPlusServer_pb_1.ApiType.ADD_CONTACT: case PadPlusServer_pb_1.ApiType.CREATE_ROOM: case PadPlusServer_pb_1.ApiType.GET_ROOM_QRCODE: timeoutMs = 1 * 60 * 1000; break; default: timeoutMs = 5 * 1000; break; } const timeout = setTimeout(() => __awaiter(this, void 0, void 0, function* () { if (apiType !== PadPlusServer_pb_1.ApiType.HEARTBEAT) { yield this.checkTimeout(uin); } reject(new Error(`ApiType: ${apiType} request timeout, traceId: ${traceId}`)); }), timeoutMs); callbackHelper_1.CallbackPool.Instance.pushCallbackToPool(traceId, (data) => { const _traceId = data.getTraceid(); if (!_traceId) { config_1.log.error(PRE, `Can not get trace id by type: ${apiType}`); } if (traceId === _traceId) { clearTimeout(timeout); if (apiType !== PadPlusServer_pb_1.ApiType.HEARTBEAT) { this.timeoutNumber = 0; } resolve(data); } }); }); } } catch (err) { yield new Promise(resolve => setTimeout(resolve, 5000)); this.isAlive = false; this.client.close(); Object.values(this.eventEmitterMap).map(emitter => { emitter.emit('reconnect'); }); if (err.details === 'INVALID_TOKEN') { config_1.padplusToken(); } } return null; }); } _request(request) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { try { this.client.request(request, (err, response) => { if (err !== null) { reject(err); } else { const flag = response.getResult(); if (flag) { resolve(true); } else { reject(err); } } }); } catch (error) { throw new Error(`can not get response data of grpc request`); } }); }); } stop() { return __awaiter(this, void 0, void 0, function* () { config_1.log.silly(PRE, `stop()`); if (this.stream) { this.stream.destroy(); this.stream.removeAllListeners(); } this.client.close(); this.stopping = true; try { yield this.request(PadPlusServer_pb_1.ApiType.CLOSE, ''); } catch (error) { config_1.log.error(PRE, `error : ${util_1.default.inspect(error)}`); } if (!this.throttleQueueSubscription || !this.debounceQueueSubscription) { config_1.log.verbose(PRE, `releaseQueue() subscriptions have been released.`); } else { this.throttleQueueSubscription.unsubscribe(); this.debounceQueueSubscription.unsubscribe(); this.throttleQueueSubscription = undefined; this.debounceQueueSubscription = undefined; } if (!this.debounceQueue || !this.throttleQueue) { config_1.log.verbose(PRE, `releaseQueue() queues have been released.`); } else { this.debounceQueue.unsubscribe(); this.throttleQueue.unsubscribe(); this.debounceQueue = undefined; this.throttleQueue = undefined; } }); } initGrpcGateway() { return __awaiter(this, void 0, void 0, function* () { config_1.log.silly(PRE, `initGrpcGateway()`); const initConfig = new PadPlusServer_pb_1.InitConfig(); initConfig.setToken(this.token); const channel = this.client.getChannel(); if (channel) { yield new Promise((resolve, reject) => { channel.getConnectivityState(true); const beforeState = channel.getConnectivityState(false); channel.watchConnectivityState(beforeState, Date.now() + 5000, (err) => { if (err) { reject(new Error('Try to connect to server timeout.')); } else { const state = channel.getConnectivityState(false); if (state !== grpc_1.default.connectivityState.READY) { reject(new Error(`Failed to connect to server, state changed to ${state}`)); } else { resolve(); } } }); }); } else { throw new Error('No channel for grpc client.'); } const stream = this.client.init(initConfig); stream.on('error', (err) => __awaiter(this, void 0, void 0, function* () { config_1.log.error(PRE, `GRPC SERVER ERROR. ===================================================================== try to reconnect grpc server, waiting... ===================================================================== `); if (err.code === 14 || err.code === 13 || err.code === 2) { yield new Promise(resolve => setTimeout(resolve, 5000)); this.isAlive = false; Object.values(this.eventEmitterMap).map(emitter => { emitter.emit('reconnect'); }); } else { config_1.log.error(PRE, `stream error:`, util_1.default.inspect(err)); } })); stream.on('end', () => __awaiter(this, void 0, void 0, function* () { if (this.reconnectStatus) { config_1.log.error(PRE, `GRPC SERVER END. ===================================================================== try to reconnect grpc server, waiting... ===================================================================== `); yield new Promise(resolve => setTimeout(resolve, 5000)); this.isAlive = false; if (!this.stopping) { Object.values(this.eventEmitterMap).map(emitter => { emitter.emit('reconnect'); }); } } else { config_1.log.info(PRE, ` ===================================================================== DUPLICATE CONNECTED, THIS THREAD WILL EXIT NOW ===================================================================== `); process.exit(); } })); stream.on('close', () => __awaiter(this, void 0, void 0, function* () { config_1.log.error(PRE, 'GRPC SERVER CLOSE'); this.isAlive = false; Object.values(this.eventEmitterMap).map(emitter => { emitter.emit('reconnect'); }); })); stream.on('data', (data) => __awaiter(this, void 0, void 0, function* () { const traceId = data.getTraceid(); const responseType = data.getResponsetype(); if (responseType !== PadPlusServer_pb_1.ResponseType.LOGIN_QRCODE && responseType !== PadPlusServer_pb_1.ResponseType.ROOM_QRCODE && responseType !== PadPlusServer_pb_1.ResponseType.CONTACT_SELF_QRCODE_GET) { config_1.log.silly(`==P==A==D==P==L==U==S==<GRPC DATA>==P==A==D==P==L==U==S==`); config_1.log.silly(PRE, `responseType: ${responseType}, data : ${data.getData()}`); config_1.log.silly(`==P==A==D==P==L==U==S==<GRPC DATA>==P==A==D==P==L==U==S==\n`); } if (this.debounceQueue && this.throttleQueue) { this.debounceQueue.next(data); this.throttleQueue.next(data); } let message = ''; const _data = data.getData(); if (_data) { try { message = JSON.parse(_data).message; } catch (error) { config_1.log.error(`can not parse data`); } } if (message && message === 'Another instance connected, disconnected the current one.') { this.reconnectStatus = false; } else if (message && message === 'EXPIRED_TOKEN') { Object.values(this.eventEmitterMap).map(emitter => { this.reconnectStatus = false; emitter.emit('EXPIRED_TOKEN'); }); } else if (message && message === 'INVALID_TOKEN') { Object.values(this.eventEmitterMap).map(emitter => { this.reconnectStatus = false; emitter.emit('INVALID_TOKEN'); }); } if (traceId) { let callback = callbackHelper_1.CallbackPool.Instance.getCallback(traceId); if (callback) { callback(data); callbackHelper_1.CallbackPool.Instance.removeCallback(traceId); } else { yield new Promise(resolve => { setTimeout(resolve, 500); }); try { callback = callbackHelper_1.CallbackPool.Instance.getCallback(traceId); callback(data); callbackHelper_1.CallbackPool.Instance.removeCallback(traceId); } catch (error) { throw new Error(`can not find callback by traceId : ${traceId}`); } } } else { if (responseType === PadPlusServer_pb_1.ResponseType.LOGIN_QRCODE) { const name = Object.keys(this.eventEmitterMap).find(name => { const qrcodeId = this.eventEmitterMap[name].getQrcodeId(); const uin = this.eventEmitterMap[name].getUIN(); const userName = this.eventEmitterMap[name].getUserName(); config_1.log.silly(PRE, `uin : ${uin}, userName: ${userName}`); return qrcodeId === '' && uin === '' && userName === ''; }); if (name) { this.eventEmitterMap[name].emit('data', data); } } else { const uin = data.getUin(); try { const user = JSON.parse(data.getData()); const userName = user.userName; const emitter = Object.values(this.eventEmitterMap).find(em => em.getUIN() === uin || em.getQrcodeId() === userName); if (!emitter) { return; } if (responseType === PadPlusServer_pb_1.ResponseType.QRCODE_SCAN && user.status === 3 && user.qrcodeId !== emitter.getQrcodeId()) { return; } else { emitter.emit('data', data); } } catch (error) { throw new Error(`can not parse json data from grpc server.`); } } } })); this.stream = stream; }); } } exports.GrpcGateway = GrpcGateway; GrpcGateway._instance = undefined; //# sourceMappingURL=grpc-gateway.js.map