UNPKG

nxkit

Version:

This is a collection of tools, independent of any other libraries

376 lines (375 loc) 14.2 kB
"use strict"; /* ***** BEGIN LICENSE BLOCK ***** * Distributed under the BSD license: * * Copyright (c) 2015, xuewen.chu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of xuewen.chu nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL xuewen.chu BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ***** END LICENSE BLOCK ***** */ Object.defineProperty(exports, "__esModule", { value: true }); const util_1 = require("../util"); const uuid_1 = require("../hash/uuid"); const fmtc_1 = require("./fmtc"); const path_1 = require("../path"); const errno_1 = require("../errno"); const service = require("./service"); const fnode = require("./node"); const OFFLINE_CACHE_TIME = 1e4; // 10s /** * @class Route */ class Route { constructor(host, id, uuid, time, fnodeId) { this.id = id; this.fnodeId = fnodeId; this.time = time; this.uuid = uuid; host.m_routeTable.set(id, this); host.m_markOffline.delete(id); } } exports.Route = Route; /** * @class FastMessageTransferCenter_IMPL */ class FastMessageTransferCenter_IMPL { constructor(host, server, fnodes = [], publish) { this.m_fnode_id = uuid_1.default(); // center server global id this.m_fnodes = {}; this.m_isRun = false; this.m_fnodesCfg = {}; this.m_fmtservice = new Map(); // client service handle this.m_routeTable = new Map(); // { 0_a: {fnodeId:'fnodeId-abcdefg-1',uuid,time} } this.m_connecting = new Set(); this.m_broadcastMark = new Set(); this.m_markOffline = new Map(); // Date.now() + OFFLINE_CACHE_TIME; this.m_host = host; this.m_server = server; this.m_publish_url = publish ? new path_1.URL(publish) : null; this.m_host.addEventListener('AddNode', e => { if (e.data.publish) this.addFnodeCfg(e.data.publish); if (util_1.default.debug) console.log('FastMessageTransferCenter_IMPL.onAddNode', e.data.publish); }); this.m_host.addEventListener('DeleteNode', e => { if (util_1.default.debug) console.log('FastMessageTransferCenter_IMPL.DeleteNode', e.data.fnodeId); }); this.m_host.addEventListener('_Login', e => { var { id, uuid, time, fnodeId } = e.data; var fmt = this.m_fmtservice.get(id); if (fmt) { if (uuid != fmt.uuid) { if (fmt.time <= time) fmt.forceLogout(); // force logout, offline if (time <= fmt.time) return; // Invalid login } } else { var route = this.m_routeTable.get(id); if (route) { if (time <= route.time) return; // Invalid login } } new Route(this, id, uuid, time, fnodeId); // trigger login event this.m_host.getNoticer('Login').trigger(e.data); for (let [, fmt] of this.m_fmtservice) { if (fmt.id != e.data.id) fmt.reportState('Login', id); } }); this.m_host.addEventListener('_Logout', e => { var { id, uuid } = e.data; var route = this.m_routeTable.get(id); if (route && route.uuid == uuid) { this.m_routeTable.delete(id); // trigger logout event this.m_host.getNoticer('Logout').trigger(e.data); for (var [, fmt] of this.m_fmtservice) { if (fmt.id != id) fmt.reportState('Logout', id); } } }); for (var cfg of fnodes) { this.addFnodeCfg(cfg, true); } fmtc_1.default._register(server, this); } get id() { return this.m_fnode_id; } get host() { return this.m_host; } get publishURL() { return this.m_publish_url; } get delegate() { return this.m_host.m_delegate; } get routeTable() { return this.m_routeTable; } addFnodeCfg(url, init = false) { if (url && !this.m_fnodesCfg.hasOwnProperty(url)) { if (!this.m_publish_url || url != this.m_publish_url.href) { this.m_fnodesCfg[url] = { url, init, retry: 0 }; } } } async run() { util_1.default.assert(!this.m_isRun); this.m_isRun = true; this.m_fnodes = {}; // init local node await (new fnode.FNodeLocal(this)).initialize(); // witch nodes while (fmtc_1.default._fmtc(this.m_server) === this) { await util_1.default.sleep(util_1.default.random(0, 4e3)); // 0-4s for (var cfg of Object.values(this.m_fnodesCfg)) { if (!this.getFnodeFrom(cfg.url)) { cfg.retry++; // console.log('FastMessageTransferCenter_IMPL.run(), connect', cfg.url); this.connect(cfg.url).catch(err => { if (err.code != errno_1.default.ERR_REPEAT_FNODE_CONNECT[0]) { if (cfg.retry >= 10 && !cfg.init) { // retry 10 count delete this.m_fnodesCfg[cfg.url]; } console.error(err); } else { console.warn(err); } }); } } await util_1.default.sleep(8e3); // 8s this.m_broadcastMark.clear(); // clear broadcast mark } for (var node of Object.values(this.m_fnodes)) { try { await node.destroy(); } catch (err) { console.error(err); } } this.m_fnodes = {}; } async connect(fNodePublishURL) { if (this.m_connecting.has(fNodePublishURL)) return; try { this.m_connecting.add(fNodePublishURL); console.log('FastMessageTransferCenter_IMPL.connect', fNodePublishURL); await (new fnode.FNodeRemoteClient(this, fNodePublishURL))._init(); console.log('FastMessageTransferCenter_IMPL, connect ok'); } finally { this.m_connecting.delete(fNodePublishURL); } } client(id) { return new service.FMTServerClient(this, id); } getFMTService(id) { var handle = this.m_fmtservice.get(id); util_1.default.assert(handle, errno_1.default.ERR_FMT_CLIENT_OFFLINE); return handle; } getFMTServiceNoError(id) { return this.m_fmtservice.get(id); } async hasOnline(id) { try { await this.exec(id); } catch (err) { return false; } return true; } user(id) { return this.exec(id, [], 'user'); } markOffline(id) { this.m_markOffline.set(id, Date.now() + OFFLINE_CACHE_TIME); } async exec(id, args = [], method) { var fnode; var route = this.m_routeTable.get(id); if (route) { fnode = this.m_fnodes[route.fnodeId]; if (fnode) { try { return method ? await fnode[method](id, ...args) : util_1.default.assert(await fnode.query(id), errno_1.default.ERR_FMT_CLIENT_OFFLINE); } catch (err) { if (err.code != errno_1.default.ERR_FMT_CLIENT_OFFLINE[0]) { throw err; } } } // Trigger again: this.m_host.getNoticer('_Logout').trigger({ id, uuid: route.uuid, fnodeId: route.fnodeId }); } var mark = this.m_markOffline.get(id); if (mark) { // OFFLINE mark util_1.default.assert(mark < Date.now(), errno_1.default.ERR_FMT_CLIENT_OFFLINE); } // random query status .. var fnodes = Object.values(this.m_fnodes); while (fnodes.length) { var i = util_1.default.random(0, fnodes.length - 1); if (util_1.default.debug) console.log('FastMessageTransferCenter_IMPL.exec', i, fnodes.length - 1, id); var _fnode = fnodes[i]; if (this.m_fnodes[_fnode.id]) { try { var { uuid, time } = await _fnode.query(id, true); fnode = _fnode; // ok break; } catch (e) { } } fnodes.splice(i, 1); } if (!fnode) { this.markOffline(id); // mark Offline throw Error.new(errno_1.default.ERR_FMT_CLIENT_OFFLINE); } // Trigger again this.m_host.getNoticer('_Login').trigger({ id, uuid, time, fnodeId: fnode.id }); if (method) { return await fnode[method](id, ...args); } } publish(event, data) { for (var fnode of Object.values(this.m_fnodes)) { fnode.publish(event, data).catch(e => console.error('FastMessageTransferCenter_IMPL.publish', e)); } } broadcast(event, data) { this._forwardBroadcast(event, data, util_1.default.hash(uuid_1.default())); } _forwardBroadcast(event, data, id, source) { if (!this.m_broadcastMark.has(id)) { this.m_broadcastMark.add(id); for (let f of Object.values(this.m_fnodes)) { if (!source || source !== f) { f.broadcast(event, data, id) .catch(e => console.error('FastMessageTransferCenter_IMPL._forwardBroadcast', 'cur', this.publishURL && this.publishURL.href, 'fnode', f.publishURL && f.publishURL.href, e)); } } } } /** * @func loginFrom() client login */ async loginFrom(fmtservice) { util_1.default.assert(fmtservice.id); var fmt = this.m_fmtservice.get(fmtservice.id); if (fmt) { util_1.default.assert(fmtservice.time <= fmt.time, errno_1.default.ERR_REPEAT_LOGIN_FMTC); fmt.forceLogout(); // force offline await this.logoutFrom(fmt); } this.m_fmtservice.set(fmtservice.id, fmtservice); this.publish('_Login', { id: fmtservice.id, uuid: fmtservice.uuid, time: fmtservice.time, fnodeId: this.id, }); if (util_1.default.debug) console.log('Login', fmtservice.id); } /** * @func logoutFrom() client logout */ async logoutFrom(fmtservice) { util_1.default.assert(fmtservice.id); if (!this.m_fmtservice.has(fmtservice.id)) return; this.m_fmtservice.delete(fmtservice.id); this.publish('_Logout', { id: fmtservice.id, uuid: fmtservice.uuid, fnodeId: this.id, }); if (util_1.default.debug) console.log('Logout', fmtservice.id); } /** * @func getFnodeFrom() */ getFnodeFrom(url) { return Object.values(this.m_fnodes) .find(e => e.publishURL && e.publishURL.href == url); } /** * @func addNode() */ async addNode(fnode) { // console.error(`Node with ID ${fnode.id} already exists`); var cur = this.m_fnodes[fnode.id]; if (cur) { if (fnode.initTime < cur.initTime) { delete this.m_fnodes[fnode.id]; await cur.destroy(); this.m_fnodes[fnode.id] = fnode; return; } else { throw Error.new(errno_1.default.ERR_REPEAT_FNODE_CONNECT); } } this.m_fnodes[fnode.id] = fnode; var publish = fnode.publishURL; if (publish) { if (!this.publishURL || this.publishURL.href != publish.href) { // this.addFnodeCfg(publish.href); var cfg = this.m_fnodesCfg[publish.href]; if (cfg) { cfg.retry = 0; } } } // this.m_host.getNoticer('AddNode').trigger({ fnodeId: fnode.id }); this.broadcast('AddNode', { fnodeId: fnode.id, publish: this.publishURL ? this.publishURL.href : null }); } /** * @func deleteNode() */ async deleteNode(fnode) { if (this.m_fnodes[fnode.id]) { delete this.m_fnodes[fnode.id]; this.m_host.getNoticer('DeleteNode').trigger({ fnodeId: fnode.id }); } } } exports.FastMessageTransferCenter_IMPL = FastMessageTransferCenter_IMPL;