shardy
Version:
Framework for online games and applications on Node.js
329 lines • 13.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Commander = exports.DisconnectReason = exports.CommanderMode = void 0;
const Block_1 = require("./Block");
const Payload_1 = require("./Payload");
const Protocol_1 = require("./Protocol");
const Logger_1 = require("./Logger");
const Tools_1 = require("./Tools");
const Pulse_1 = require("./Pulse");
const Validator_1 = require("./Validator");
const LOG_TAG = Tools_1.Tools.getTag(module);
const TIMEOUT_INTERVAL = 1000;
const TIMEOUT_ERROR = 'timeout';
var CommanderMode;
(function (CommanderMode) {
CommanderMode[CommanderMode["Service"] = 0] = "Service";
CommanderMode[CommanderMode["Bot"] = 1] = "Bot";
})(CommanderMode || (exports.CommanderMode = CommanderMode = {}));
var DisconnectReason;
(function (DisconnectReason) {
DisconnectReason[DisconnectReason["Normal"] = 0] = "Normal";
DisconnectReason[DisconnectReason["Timeout"] = 1] = "Timeout";
DisconnectReason[DisconnectReason["Handshake"] = 2] = "Handshake";
DisconnectReason[DisconnectReason["ServerDown"] = 3] = "ServerDown";
DisconnectReason[DisconnectReason["Unknown"] = 4] = "Unknown";
})(DisconnectReason || (exports.DisconnectReason = DisconnectReason = {}));
class Commander {
constructor(id, connection, service, options, log, mode = CommanderMode.Service) {
this.id = id;
this.connection = connection;
this.service = service;
this.options = options;
this.log = log;
this.mode = mode;
this.onDisconnect = () => { };
this.onReady = () => { };
this.cid = 'unknown';
this.counter = 1;
this.commands = new Map();
this.callbacks = new Map();
this.requests = new Map();
this.timeouts = new Map();
this.names = new Map();
this.reason = DisconnectReason.Normal;
this.cid = this.id;
this.protocol = new Protocol_1.Protocol(this.connection, this.log);
this.protocol.onBlock = (block) => this.onBlock(block);
this.protocol.onDisconnect = () => this.onClose();
this.pulse = new Pulse_1.Pulse(mode);
this.pulse.onPulse = () => this.onPulse();
this.timer = setInterval(() => this.onCheckTimeout(), TIMEOUT_INTERVAL);
this.counter = 0;
}
kick(reason) {
this.log.info(`-> kick: ${reason}`, Logger_1.LoggerScope.Debug);
this.protocol.kick(reason);
this.protocol.disconnect();
this.pulse.clear();
}
heartbeat() {
this.log.info(`-> heartbeat`, Logger_1.LoggerScope.Debug);
this.protocol.heartbeat();
}
handshake(data) {
this.log.info(`-> handshake`, Logger_1.LoggerScope.Debug);
this.protocol.handshake(data);
}
acknowledge(data) {
this.log.info(`-> acknowledge`, Logger_1.LoggerScope.Debug);
this.protocol.acknowledge(data);
}
disconnect() {
this.log.info(`-> disconnect`, Logger_1.LoggerScope.Debug);
this.protocol.disconnect();
}
command(command, data) {
this.log.info(`-> command: ${command}, data: ${data}`, Logger_1.LoggerScope.Debug);
const payload = Payload_1.Payload.encode(this.options.serializer, Payload_1.PayloadType.Command, command, 0, data);
this.protocol.send(payload);
}
fetch(request, data) {
this.log.info(`-> fetch: ${this.counter}.${request}, data: ${data}`, Logger_1.LoggerScope.Debug);
const payload = Payload_1.Payload.encode(this.options.serializer, Payload_1.PayloadType.Request, request, this.counter, data);
this.protocol.send(payload);
const callback = new Promise((resolve) => this.callbacks.set(this.counter, resolve));
this.names.set(this.counter, request);
this.timeouts.set(this.counter, Date.now());
this.counter++;
return callback;
}
request(request, callback, data) {
this.log.info(`-> request: ${this.counter}.${request}, data: ${data}`, Logger_1.LoggerScope.Debug);
const payload = Payload_1.Payload.encode(this.options.serializer, Payload_1.PayloadType.Request, request, this.counter, data);
this.protocol.send(payload);
this.callbacks.set(this.counter, callback);
this.names.set(this.counter, request);
this.timeouts.set(this.counter, Date.now());
return this.counter++;
}
response(request, data) {
this.log.info(`-> response: ${request.id}.${request.name}, data: ${data}`, Logger_1.LoggerScope.Debug);
const payload = Payload_1.Payload.encode(this.options.serializer, Payload_1.PayloadType.Response, request.name, request.id, data);
this.protocol.send(payload);
}
error(request, error, data) {
this.log.info(`-> error: ${request.id}.${request.name}, error: ${error}, data: ${data}`, Logger_1.LoggerScope.Debug);
const payload = Payload_1.Payload.encode(this.options.serializer, Payload_1.PayloadType.Response, request.name, request.id, data, error);
this.protocol.send(payload);
}
clear() {
clearInterval(this.timer);
this.pulse.clear();
this.names.clear();
this.timeouts.clear();
this.commands.clear();
this.requests.clear();
this.callbacks.clear();
}
cancelRequest(id) {
this.names.delete(id);
this.callbacks.delete(id);
this.timeouts.delete(id);
}
addCommand(command, callback) {
let list = new Array();
if (this.commands.has(command)) {
list = this.commands.get(command);
}
list.push(callback);
this.commands.set(command, list);
}
cancelCommand(command, callback) {
if (this.commands.has(command)) {
if (!callback) {
const list = new Array();
this.commands.set(command, list);
}
else {
const list = this.commands.get(command);
const index = list.indexOf(callback, 0);
if (index > -1) {
list.splice(index, 1);
}
this.commands.set(command, list);
}
}
}
addOnRequest(request, callback) {
if (this.requests.has(request)) {
this.log.warn(`request already exists: ${request}, method: ${this.requests.get(request)}`, Logger_1.LoggerScope.Debug);
return;
}
this.requests.set(request, callback);
}
cancelOnRequest(request) {
if (this.requests.has(request)) {
this.requests.delete(request);
}
}
onCheckTimeout() {
for (const [id, time] of this.timeouts) {
const diff = Date.now() - time;
if (diff > process.env.REQUEST_TIMEOUT) {
const payload = Payload_1.Payload.create(Payload_1.PayloadType.Response, this.names.get(id), id, undefined, TIMEOUT_ERROR);
this.onPayload(payload);
}
}
}
onBlock(block) {
this.log.info(`[${LOG_TAG}] block: ${block.type}, data: ${block.body}`, Logger_1.LoggerScope.Debug);
switch (block.type) {
case Block_1.BlockType.Handshake:
this.onHandshake(block);
break;
case Block_1.BlockType.Heartbeat:
this.onHeartbeat();
break;
case Block_1.BlockType.Kick:
this.onKick(block);
break;
case Block_1.BlockType.HandshakeAcknowledgement:
this.onAcknowledgement(block);
break;
case Block_1.BlockType.Data:
const payload = Payload_1.Payload.decode(this.options.serializer, block.body);
if (Payload_1.Payload.check(payload)) {
this.onPayload(payload);
}
else {
this.log.warn(`[${LOG_TAG}] invalid payload: ${payload}`, Logger_1.LoggerScope.Debug);
}
break;
default:
this.log.warn(`[${LOG_TAG}] not implemented block type: ${block.type}`, Logger_1.LoggerScope.Debug);
break;
}
}
onPayload(payload) {
this.pulse.reset();
if (this.mode === CommanderMode.Bot) {
this.heartbeat();
}
switch (payload.type) {
case Payload_1.PayloadType.Command:
this.log.info(`<- command: ${payload.name}, data: ${payload.data}`, Logger_1.LoggerScope.Debug);
if (this.mode === CommanderMode.Service) {
const command = this.options.commands.get(payload.name);
if (command) {
command(this, payload, this.service);
}
else {
this.log.warn(`[${LOG_TAG}] unknown command: ${payload.name}`, Logger_1.LoggerScope.Debug);
}
}
else {
const list = this.commands.get(payload.name);
if (list) {
for (const callback of list) {
callback(payload);
}
}
}
break;
case Payload_1.PayloadType.Request:
this.log.info(`<- request: ${payload.id}.${payload.name}, data: ${payload.data}`, Logger_1.LoggerScope.Debug);
if (this.mode === CommanderMode.Service) {
const request = this.options.commands.get(payload.name);
if (request) {
request(this, payload, this.service);
}
else {
this.log.warn(`[${LOG_TAG}] unknown request: ${payload.id}.${payload.name}`, Logger_1.LoggerScope.Debug);
}
}
else {
const callback = this.requests.get(payload.name);
if (callback) {
callback(payload);
}
}
break;
case Payload_1.PayloadType.Response:
if (payload.error.trim().length === 0) {
this.log.info(`<- response: ${payload.id}.${payload.name}, data: ${payload.data}`, Logger_1.LoggerScope.Debug);
}
else {
this.log.info(`<- error: ${payload.id}.${payload.name}, error: ${payload.error}, data: ${payload.data}`, Logger_1.LoggerScope.Debug);
}
const callback = this.callbacks.get(payload.id);
if (callback) {
callback(payload);
this.cancelRequest(payload.id);
}
else {
this.log.warn(`[${LOG_TAG}] unknown response: ${payload.id}.${payload.name}`, Logger_1.LoggerScope.Debug);
}
break;
default:
break;
}
}
onClose() {
this.log.info(`<- disconnect`, Logger_1.LoggerScope.Debug);
this.clear();
this.onDisconnect(this.reason);
}
onHandshake(block) {
this.log.info(`<- handshake`, Logger_1.LoggerScope.Debug);
this.pulse.reset();
const state = this.options.validator.verifyHandshake(block.body);
this.log.info(`[${LOG_TAG}] handshake validation state: ${state}, data: ${block.body}`, Logger_1.LoggerScope.Debug);
if (state === Validator_1.ValidatorState.Success) {
this.acknowledge(this.options.validator.acknowledgement(block.body));
}
else {
this.kick(DisconnectReason.Handshake);
}
}
onAcknowledgement(block) {
this.log.info(`<- acknowledge`, Logger_1.LoggerScope.Debug);
this.pulse.reset();
if (this.mode === CommanderMode.Bot) {
const state = this.options.validator.verifyAcknowledgement(block.body);
this.log.info(`[${LOG_TAG}] acknowledgement data: ${block.body}, validation state: ${state}`, Logger_1.LoggerScope.Debug);
if (state === Validator_1.ValidatorState.Success) {
this.acknowledge(this.options.validator.acknowledgement(block.body));
}
else {
this.reason = DisconnectReason.Handshake;
this.disconnect();
return;
}
}
else {
this.log.info(`[${LOG_TAG}] acknowledgement data: ${block.body}`, Logger_1.LoggerScope.Debug);
}
this.log.info(`ready to work`, Logger_1.LoggerScope.Debug);
this.onReady();
}
onHeartbeat() {
this.log.info(`<- heartbeat`, Logger_1.LoggerScope.Debug);
this.pulse.reset();
if (this.mode === CommanderMode.Service) {
this.heartbeat();
}
}
onKick(block) {
this.reason = block.body;
this.log.info(`<- kick: ${this.reason}`, Logger_1.LoggerScope.Debug);
this.pulse.reset();
}
onPulse() {
if (this.mode === CommanderMode.Bot) {
this.log.info(`[${LOG_TAG}] pulse timeout, send heartbeat`, Logger_1.LoggerScope.Debug);
this.heartbeat();
}
else {
this.log.info(`pulse timeout, send kick`, Logger_1.LoggerScope.Debug);
this.kick(DisconnectReason.Timeout);
}
}
destroy() {
this.log.info(`[${LOG_TAG}] destroy`, Logger_1.LoggerScope.Debug);
this.clear();
this.protocol.destroy();
}
}
exports.Commander = Commander;
//# sourceMappingURL=Commander.js.map