ziron-state
Version:
1 lines • 7.17 kB
JavaScript
;var __awaiter=this&&this.__awaiter||function(e,t,r,i){return new(r||(r=Promise))((function(s,o){function n(e){try{h(i.next(e))}catch(e){o(e)}}function a(e){try{h(i.throw(e))}catch(e){o(e)}}function h(e){var t;e.done?s(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(n,a)}h((i=i.apply(e,t||[])).next())}))};Object.defineProperty(exports,"__esModule",{value:!0}),exports.StateServer=void 0;const ziron_server_1=require("ziron-server"),Logger_1=require("./Logger"),Crypto_1=require("./Crypto"),uniqId=require("uniqid"),ip=require("ip"),isIp=require("is-ip"),Object_1=require("./Object"),Errors_1=require("./Errors"),CLUSTER_VERSION=1;class StateServer{get id(){return this.server.id}get joinedWorkers(){return Object.values(this._joinedWorkers)}get joinedBrokers(){return Object.values(this._joinedBrokers)}get workerLeader(){return this._workerLeader}constructor(e={}){this._options={logLevel:1,secret:(0,Crypto_1.generateSecret)().substring(0,32),port:7777,path:"/",scaleDelay:100,initScaleDelay:4e3},this._workerLeader=null,this._joinedWorkers={},this._joinedBrokers={},this._clusterSession=null,this.procedures={},this.receivers={},this._initScaleDelayActive=!0,this._handleWorkerJoin=(e,t,r,i)=>{if(this._joinedWorkers[e.node.id]===e)return r({session:this._clusterSession,brokers:this.getJoinedBrokersState()});"object"!=typeof t&&(t={});let{shared:s,payload:o}=t;if("object"!=typeof o&&(o={}),this.workerJoinMiddleware)try{this.workerJoinMiddleware(e,o)}catch(e){return i(e instanceof ziron_server_1.Block?e:new Error("Join was blocked by the join middleware"))}if(this._isNodeIdInUse(e.node.id))return i(new Errors_1.IdAlreadyUsedInClusterError(e.node.id));0===Object.keys(this._joinedWorkers).length&&this._createClusterSession(s),e.node.joinPayload=o,this._joinedWorkers[e.node.id]=e,e.join("JoinedWorkers"),this._selectWorkerLeader(),r({session:this._clusterSession,brokers:this._initScaleDelayActive?null:this.getJoinedBrokersState()}),this._logRunningState()},this._handleWorkerLeave=e=>{const t=e.node.id;this._joinedWorkers[t]===e&&(delete this._joinedWorkers[t],e.leave("JoinedWorkers"),this._workerLeader===e&&(this._workerLeader=null,e.node.leader=!1,this._selectWorkerLeader()),0===Object.keys(this._joinedWorkers).length&&this._resetClusterSession(),this._logRunningState())},this._handleBrokerJoin=(e,t,r,i)=>{const s=e.node.id;return this._joinedBrokers[s]===e?r():this._isNodeIdInUse(s)?i(new Errors_1.IdAlreadyUsedInClusterError(s)):(this._joinedBrokers[s]=e,this._scaleOut(),r(),void this._logRunningState())},this._handleBrokerLeave=e=>{const t=e.node.id;this._joinedBrokers[t]===e&&(delete this._joinedBrokers[e.node.id],this._scaleBack(),this._logRunningState())},this._workerLeaderSelectionPromise=Promise.resolve(),this._options=(0,Object_1.buildOptions)(this._options,e),this._logger=new Logger_1.default(this._options.logLevel),this.joinToken=this._getJoinToken(),this.server=new ziron_server_1.Server({port:this._options.port,pingInterval:1e3,path:this._options.path}),this._initServer()}_startInitScaleDelayTicker(){setTimeout(()=>{this._initScaleDelayActive=!1,this._updateWorkersBrokerState()},this._options.initScaleDelay)}listen(){return __awaiter(this,void 0,void 0,(function*(){if(!this.server.isListening())try{this._logger.logBusy("Launching state server..."),yield this.server.listen(),this._startInitScaleDelayTicker(),this._logger.logActive(`State server launched successfully on port: ${this._options.port}.`),this._logRunningState()}catch(e){throw e instanceof ziron_server_1.FailedToListenError&&this._logger.logFailed(`Failed to listen on port: ${this._options.port}. Maybe the port is already in use.`),e}}))}_logRunningState(){this._logger.logRunningState(Object.values(this._joinedWorkers),Object.values(this._joinedBrokers),this.joinToken)}_getJoinToken(){const e=ip.address(),t=""===this._options.path||"/"===this._options.path?"":this._options.path.startsWith("/")?this._options.path:"/"+this._options.path;return`${this._options.secret?this._options.secret+"@":""}ws://${isIp.v6(e)?`[${e}]`:e}:${this._options.port}${t}`}_initServer(){this.server.upgradeMiddleware=e=>{const t=e.attachment;if("object"!=typeof t)throw new ziron_server_1.Block(400,"Invalid attachment structure");if(t.secret!==this._options.secret)throw new ziron_server_1.Block(403,"Permission denied");if(1!==t.clusterVersion)throw new ziron_server_1.Block(412,"Incompatible cluster versions")},this.server.socketMiddleware=e=>{const t=e.handshakeAttachment.node;if("object"!=typeof t||0!==t.type&&1!==t.type||"string"!=typeof t.id||"number"!=typeof t.port||"string"!=typeof t.path)throw new ziron_server_1.Block(4005,"Invalid attachment structure");{const r=e.remoteAddress;let i;if(r&&isIp(r))i=r;else{if("string"!=typeof t.ip||!isIp(t.ip))throw new ziron_server_1.Block(4012,"Could not detect node IP address");i=t.ip}if(""!==t.path&&!t.path.startsWith("/"))throw new ziron_server_1.Block(4005,"Invalid node path");e.node={id:t.id,type:t.type,ip:i,port:t.port,path:t.path,uri:`ws://${isIp.v6(i)?`[${i}]`:i}:${t.port}${t.path}`,leader:!1}}},this.server.connectionHandler=e=>{const t=e.node.type;return e.on("disconnect",0===t?()=>this._handleWorkerLeave(e):()=>this._handleBrokerLeave(e)),(0,ziron_server_1.applyStandaloneProcedures)(e,this.procedures),(0,ziron_server_1.applyStandaloneReceivers)(e,this.receivers),e.procedures["#leave"]=0===t?(t,r)=>{this._handleWorkerLeave(e),r()}:(t,r)=>{this._handleBrokerLeave(e),r()},e.procedures["#join"]=0===t?(...t)=>this._handleWorkerJoin(e,...t):(...t)=>this._handleBrokerJoin(e,...t),this.id}}_createClusterSession(e){this._clusterSession={id:uniqId(),shared:e}}_resetClusterSession(){this._clusterSession=null}static _getJoinedState(e){return{time:Date.now(),uris:Object.values(e).map(e=>e.node.uri)}}getJoinedBrokersState(){return StateServer._getJoinedState(this._joinedBrokers)}getJoinedWorkersState(){return StateServer._getJoinedState(this._joinedWorkers)}_getRandomWorker(){return(0,Crypto_1.getRandomArrayItem)(Object.values(this._joinedWorkers))}_selectWorkerLeader(){this._workerLeaderSelectionPromise=this._workerLeaderSelectionPromise.then(()=>this._selectWorkerLeaderProcess())}_selectWorkerLeaderProcess(){return __awaiter(this,void 0,void 0,(function*(){let e;if(null==this._workerLeader&&void 0!==(e=this._getRandomWorker())){try{yield e.invoke("addLeadership")}catch(e){return yield this._selectWorkerLeaderProcess()}e.node.leader=!0,this._workerLeader=e,this._logRunningState()}}))}_scaleOut(){this._setScaleTimeout(()=>this._updateWorkersBrokerState())}_scaleBack(){this._setScaleTimeout(()=>this._updateWorkersBrokerState())}_setScaleTimeout(e){null!=this._scaleTimeout&&clearTimeout(this._scaleTimeout),this._scaleTimeout=setTimeout(e,this._options.scaleDelay)}_updateWorkersBrokerState(){this.server.transmitToGroup("JoinedWorkers","updateBrokers",this.getJoinedBrokersState())}_isNodeIdInUse(e){return this._joinedWorkers.hasOwnProperty(e)||this._joinedBrokers.hasOwnProperty(e)||e===this.server.id}terminate(){this._workerLeader=null,this._joinedWorkers={},this._joinedBrokers={},this.server.terminate()}}exports.StateServer=StateServer;