wechaty-puppet-service
Version:
Puppet Service for Wechaty
341 lines • 16.7 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventStreamManager = void 0;
/**
* Wechaty Open Source Software - https://github.com/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* 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.
*
*/
const wechaty_grpc_1 = require("wechaty-grpc");
const PUPPET = __importStar(require("wechaty-puppet"));
const config_js_1 = require("../config.js");
const event_type_rev_js_1 = require("../event-type-rev.js");
class EventStreamManager {
puppet;
eventStream;
puppetListening = false;
constructor(puppet) {
this.puppet = puppet;
config_js_1.log.verbose('EventStreamManager', 'constructor(%s)', puppet);
}
busy() {
return !!this.eventStream;
}
start(stream) {
config_js_1.log.verbose('EventStreamManager', 'start(stream)');
if (this.eventStream) {
throw new Error('can not set twice');
}
this.eventStream = stream;
const removeAllListeners = this.connectPuppetEventToStreamingCall();
this.onStreamingCallEnd(removeAllListeners);
/**
* Huan(202108):
* We emit a hearbeat at the beginning of the connect
* to identicate that the connection is successeed.
*
* Our client (wechaty-puppet-service client) will wait for the heartbeat
* when it connect to the server.
*
* If the server does not send the heartbeat,
* then the client will wait for a 5 seconds timeout
* for compatible the community gRPC puppet service providers like paimon.
*/
const connectSuccessHeartbeatPayload = {
data: 'Wechaty Puppet gRPC stream connect successfully',
};
this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_HEARTBEAT, connectSuccessHeartbeatPayload);
/**
* We emit the login event if current the puppet is logged in.
*/
if (this.puppet.isLoggedIn) {
config_js_1.log.verbose('EventStreamManager', 'start() puppet is logged in, emit a login event for downstream');
const payload = {
contactId: this.puppet.currentUserId,
};
this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_LOGIN, payload);
}
if (this.puppet.readyIndicator.value()) {
config_js_1.log.verbose('EventStreamManager', 'start() puppet is ready, emit a ready event for downstream');
const payload = {
data: 'ready',
};
this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_READY, payload);
}
}
stop() {
config_js_1.log.verbose('EventStreamManager', 'stop()');
if (!this.eventStream) {
throw new Error('no this.eventStream');
}
this.eventStream.end();
this.eventStream = undefined;
}
grpcEmit(type, // https://stackoverflow.com/a/49286056/1123955
obj) {
config_js_1.log.verbose('EventStreamManager', 'grpcEmit(%s[%s], %s)', event_type_rev_js_1.EventTypeRev[type], type, JSON.stringify(obj));
const response = new wechaty_grpc_1.puppet.EventResponse();
response.setType(type);
response.setPayload(JSON.stringify(obj));
if (this.eventStream) {
this.eventStream.write(response);
}
else {
/**
* Huan(202108): TODO: add a queue for store a maximum number of responses before the stream get connected
*/
config_js_1.log.warn('EventStreamManager', 'grpcEmit(%s, %s) this.eventStream is undefined.', type, JSON.stringify(obj));
}
}
connectPuppetEventToStreamingCall() {
config_js_1.log.verbose('EventStreamManager', 'connectPuppetEventToStreamingCall() for %s', this.puppet);
const offCallbackList = [];
const offAll = () => {
config_js_1.log.verbose('EventStreamManager', 'connectPuppetEventToStreamingCall() offAll() %s callbacks', offCallbackList.length);
offCallbackList.forEach(off => off());
this.puppetListening = false;
};
const eventNameList = Object.keys(PUPPET.types.PUPPET_EVENT_DICT);
for (const eventName of eventNameList) {
config_js_1.log.verbose('EventStreamManager', 'connectPuppetEventToStreamingCall() this.puppet.on(%s) (listenerCount:%s) registering...', eventName, this.puppet.listenerCount(eventName));
switch (eventName) {
case 'dong': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_DONG, payload);
this.puppet.on('dong', listener);
const off = () => this.puppet.off('dong', listener);
offCallbackList.push(off);
break;
}
case 'dirty': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_DIRTY, payload);
this.puppet.on('dirty', listener);
const off = () => this.puppet.off('dirty', listener);
offCallbackList.push(off);
break;
}
case 'error': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_ERROR, payload);
this.puppet.on('error', listener);
const off = () => this.puppet.off('error', listener);
offCallbackList.push(off);
break;
}
case 'heartbeat': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_HEARTBEAT, payload);
this.puppet.on('heartbeat', listener);
const off = () => this.puppet.off('heartbeat', listener);
offCallbackList.push(off);
break;
}
case 'friendship': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_FRIENDSHIP, payload);
this.puppet.on('friendship', listener);
const off = () => this.puppet.off('friendship', listener);
offCallbackList.push(off);
break;
}
case 'login': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_LOGIN, payload);
this.puppet.on('login', listener);
const off = () => this.puppet.off('login', listener);
offCallbackList.push(off);
break;
}
case 'logout': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_LOGOUT, payload);
this.puppet.on('logout', listener);
const off = () => this.puppet.off('logout', listener);
offCallbackList.push(off);
break;
}
case 'message': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_MESSAGE, payload);
this.puppet.on('message', listener);
const off = () => this.puppet.off('message', listener);
offCallbackList.push(off);
break;
}
case 'post': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_POST, payload);
this.puppet.on('post', listener);
const off = () => this.puppet.off('post', listener);
offCallbackList.push(off);
break;
}
case 'ready': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_READY, payload);
this.puppet.on('ready', listener);
const off = () => this.puppet.off('ready', listener);
offCallbackList.push(off);
break;
}
case 'room-invite': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_ROOM_INVITE, payload);
this.puppet.on('room-invite', listener);
const off = () => this.puppet.off('room-invite', listener);
offCallbackList.push(off);
break;
}
case 'room-join': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_ROOM_JOIN, payload);
this.puppet.on('room-join', listener);
const off = () => this.puppet.off('room-join', listener);
offCallbackList.push(off);
break;
}
case 'room-leave': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_ROOM_LEAVE, payload);
this.puppet.on('room-leave', listener);
const off = () => this.puppet.off('room-leave', listener);
offCallbackList.push(off);
break;
}
case 'room-topic': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_ROOM_TOPIC, payload);
this.puppet.on('room-topic', listener);
const off = () => this.puppet.off('room-topic', listener);
offCallbackList.push(off);
break;
}
case 'scan': {
const listener = (payload) => this.grpcEmit(wechaty_grpc_1.puppet.EventType.EVENT_TYPE_SCAN, payload);
this.puppet.on('scan', listener);
const off = () => this.puppet.off('scan', listener);
offCallbackList.push(off);
break;
}
case 'reset':
// the `reset` event should be dealed internally, should not send out
break;
default:
// Huan(202003): in default, the `eventName` type should be `never`, please check.
throw new Error('eventName ' + eventName + ' unsupported!');
}
}
this.puppetListening = true;
return offAll;
}
/**
* Detect if the streaming call was gone (GRPC disconnects)
* https://github.com/grpc/grpc/issues/8117#issuecomment-362198092
*/
onStreamingCallEnd(removePuppetListeners) {
config_js_1.log.verbose('EventStreamManager', 'onStreamingCallEnd(callback)');
if (!this.eventStream) {
throw new Error('no this.eventStream found');
}
/**
* Huan(202110): useful log messages
*
* ServiceCtl<PuppetServiceMixin> stop() super.stop() ... done
* StateSwitch <PuppetServiceMixin> inactive(true) <- (pending)
* EventStreamManager this.onStreamingCallEnd() this.eventStream.on(finish) fired
* EventStreamManager connectPuppetEventToStreamingCall() offAll() 14 callbacks
* EventStreamManager this.onStreamingCallEnd() this.eventStream.on(finish) eventStream is undefined
* EventStreamManager this.onStreamingCallEnd() this.eventStream.on(close) fired
* EventStreamManager this.onStreamingCallEnd() this.eventStream.on(close) eventStream is undefined
* EventStreamManager this.onStreamingCallEnd() this.eventStream.on(cancelled) fired with arguments: {}
* EventStreamManager this.onStreamingCallEnd() this.eventStream.on(cancelled) eventStream is undefined
* GrpcClient stop() stop client ... done
*/
this.eventStream.on('cancelled', () => {
config_js_1.log.verbose('EventStreamManager', 'this.onStreamingCallEnd() this.eventStream.on(cancelled) fired with arguments: %s', JSON.stringify(arguments));
if (this.puppetListening) {
removePuppetListeners();
}
if (this.eventStream) {
this.eventStream = undefined;
}
else {
config_js_1.log.warn('EventStreamManager', 'this.onStreamingCallEnd() this.eventStream.on(cancelled) eventStream is undefined');
}
});
this.eventStream.on('error', err => {
config_js_1.log.verbose('EventStreamManager', 'this.onStreamingCallEnd() this.eventStream.on(error) fired: %s', err);
if (this.puppetListening) {
removePuppetListeners();
}
if (this.eventStream) {
this.eventStream = undefined;
}
else {
config_js_1.log.warn('EventStreamManager', 'this.onStreamingCallEnd() this.eventStream.on(error) eventStream is undefined');
}
});
this.eventStream.on('finish', () => {
config_js_1.log.verbose('EventStreamManager', 'this.onStreamingCallEnd() this.eventStream.on(finish) fired');
if (this.puppetListening) {
removePuppetListeners();
}
if (this.eventStream) {
this.eventStream = undefined;
}
else {
config_js_1.log.warn('EventStreamManager', 'this.onStreamingCallEnd() this.eventStream.on(finish) eventStream is undefined');
}
});
this.eventStream.on('end', () => {
config_js_1.log.verbose('EventStreamManager', 'this.onStreamingCallEnd() this.eventStream.on(end) fired');
if (this.puppetListening) {
removePuppetListeners();
}
if (this.eventStream) {
this.eventStream = undefined;
}
else {
config_js_1.log.warn('EventStreamManager', 'this.onStreamingCallEnd() this.eventStream.on(end) eventStream is undefined');
}
});
this.eventStream.on('close', () => {
config_js_1.log.verbose('EventStreamManager', 'this.onStreamingCallEnd() this.eventStream.on(close) fired');
if (this.puppetListening) {
removePuppetListeners();
}
if (this.eventStream) {
this.eventStream = undefined;
}
else {
config_js_1.log.warn('EventStreamManager', 'this.onStreamingCallEnd() this.eventStream.on(close) eventStream is undefined');
}
});
}
}
exports.EventStreamManager = EventStreamManager;
//# sourceMappingURL=event-stream-manager.js.map