@rivalis/core
Advanced tools
@@ -40,5 +40,5 @@ export default WSClient; | ||
| * @param {string} topic | ||
| * @param {Uint8Array|string} payload | ||
| * @param {Uint8Array|string} [payload] | ||
| */ | ||
| send(topic: string, payload: Uint8Array | string): void; | ||
| send(topic: string, payload?: Uint8Array | string): void; | ||
| /** @private */ | ||
@@ -45,0 +45,0 @@ private onOpen; |
+1
-1
@@ -1,2 +0,2 @@ | ||
| export type TopicListener = (actor: Actor, payload: Uint8Array) => void; | ||
| export type TopicListener = (actor: Actor, payload: Uint8Array, topic: string) => void; | ||
| import Rivalis from "./Rivalis"; | ||
@@ -3,0 +3,0 @@ export namespace Transports { |
+1
-1
@@ -1,1 +0,1 @@ | ||
| var e=require("@toolcase/base"),t=require("@toolcase/logging"),r=require("buffer");require("http");var o=require("url"),s=require("ws");function n(e,t,r,o){Object.defineProperty(e,t,{get:r,set:o,enumerable:!0,configurable:!0})}n(module.exports,"Transports",(()=>x)),n(module.exports,"Clients",(()=>A)),n(module.exports,"logging",(()=>$86ab48f13b64a4ed$export$45697122e627faf2)),n(module.exports,"Rivalis",(()=>E)),n(module.exports,"AuthMiddleware",(()=>i)),n(module.exports,"Room",(()=>$)),n(module.exports,"Actor",(()=>m));var i=class{async validateTicket(e){return!0}async extractPayload(e){return null}async getRoomId(e){throw new Error("AuthMiddleware#getRoomId not implemented")}};class a extends t.LoggerFactory{constructor(){super([new(0,t.ConsoleLogReporter)])}}a.Instance=new a;var l=a;const c=new(0,e.Serializer)("@toolcase/realtime"),h="realtime_message";c.define(h,[{key:"topic",type:"string",rule:"required"},{key:"payload",type:"bytes",rule:"required"}]);const d=(e,t)=>c.encode(h,{topic:e,payload:t}),g=e=>c.decode(h,e);class u extends e.Broadcast{logger=l.Instance.getLogger("transport layer");authMiddleware=null;getRoom=null;roomIds=new Map;constructor(e,t=null){super(),this.authMiddleware=e,this.getRoom=t}get connections(){return this.roomIds.size}on=(e,t,r,o)=>super.on(`${e}:${t}`,r,o);once=(e,t,r,o)=>super.once(`${e}:${t}`,r,o);emit=(e,t,r)=>super.emit(`${e}:${t}`,t,r);async grantAccess(t){if(!0!==await this.authMiddleware.validateTicket(t))throw new Error("invalid ticket");let r=await this.authMiddleware.extractPayload(t);if(null!==r&&"object"!=typeof r)throw new Error(`actor data can be an object or null, provided=${r}`);let o=await this.authMiddleware.getRoomId(t),s=this.getRoom(o);if(null===s)throw new Error(`room id=${o} does not exist`);let n=(0,e.generateId)(16);return this.roomIds.set(n,o),s.handleJoin(n,r),this.logger.info(`actor id=${n} join room id=${o}`),n}handleMessage(e,t){let r=g(t),o=this.roomIds.get(e);this.getRoom(o).handleMessage(e,r.topic,r.payload)}handleClose(e){let t=this.roomIds.get(e),r=this.getRoom(t);this.roomIds.delete(e);try{r.handleLeave(e)}catch(e){}this.logger.info(`actor id=${e} leave room id=${t}`)}send(e,t,r){let o=d(t,r);this.emit("message",e,o)}kick(e,t){this.emit("kick",e,t)}}var f=u;var p=class{transportLayer=null;onInitialize(e){}dispose(){}};var w=class{transports=null;authMiddleware=null;constructor(e={}){if("object"!=typeof e)throw new Error("config error: provided config is not an object");if(!Array.isArray(e.transports))throw new Error("config error: transports must be an array");for(let[t,r]of e.transports.entries())if(!(r instanceof p))throw new Error(`config error: transports[${t}] must be an instance of Transport`);if(!(e.authMiddleware instanceof i))throw new Error("config error: authMiddleware must be an instance of AuthMiddleware");this.transports=e.transports,this.authMiddleware=e.authMiddleware}};var m=class{id=null;data=null;joined=new Date;room=null;storage=new Map;constructor(e,t,r){this.id=e,this.data=t,this.room=r}send(e,t){this.room.send(this,e,t)}kick(e){this.room.kick(this,e)}save(e,t){this.storage.set(e,t)}get(e){let t=this.storage.get(e);return void 0===t?null:t}},y=r.Buffer;const k="invalid_message",v="room_destroyed";var $=class{id=null;logger=null;manager=null;transportLayer=null;topics=new Map;actors=new Map;constructor(e,t,r){this.id=e,this.logger=l.Instance.getLogger(`room=${e}`),this.manager=t,this.transportLayer=r,this.onCreate(),this.logger.info("created")}onCreate(){}onJoin(e){}onLeave(e){}onDestroy(){}listen(e,t,r=null){if("string"!=typeof e)throw new Error(`topic must be a string, ${e} provided`);if("function"!=typeof t)throw new Error(`topicListener must be a function, ${t} provided`);if(this.topics.has(e))throw new Error(`topic ${e} is already registered`);this.topics.set(e,t.bind(null===r?this:r))}send(e,t,r){if("string"!=typeof t)throw new Error(`send error: topic must be a sting, ${t} provided`);if(!(e instanceof m))throw new Error(`send error: actor=${e} must be an instance of Actor`);if(r instanceof Uint8Array)return this.transportLayer.send(e.id,t,r);if("string"==typeof r)return this.transportLayer.send(e.id,t,y.from(r,"utf-8"));throw new Error(`send error: invalid payload=${r}, must be a string or Buffer`)}broadcast(e,t){this.each((r=>this.send(r,e,t)))}each(e){this.actors.forEach(e)}kick(e,t=""){if(t instanceof Uint8Array)return this.transportLayer.kick(e.id,t);if("string"==typeof t)return this.transportLayer.kick(e.id,y.from(t,"utf-8"));throw new Error(`kick error: invalid payload=${t}, must be a string or Buffer`)}destroy(){this.manager.destroy(this.id)}handleDestroy(){this.each((e=>e.kick(v))),this.onDestroy(),this.actors.clear(),this.topics.clear(),this.transportLayer=null,this.manager=null,this.logger.info("destroyed"),this.logger=null}handleJoin(e,t=null){let r=new m(e,t||{},this);this.actors.set(e,r),this.onJoin(r)}handleMessage(e,t,r){let o=this.topics.get(t)||null,s=this.actors.get(e)||null;if(null===o)return this.logger.debug(`actor id=${e} is kicked, reason: sending message on non existing topic=${t}`),s.kick(k);o(s,r)}handleLeave(e){let t=this.actors.get(e);this.onLeave(t),this.actors.delete(e)}};class b extends e.Broadcast{transportLayer=null;defs=new Map;rooms=new Map;constructor(e){super(),this.transportLayer=e}get(e){return this.rooms.get(e)||null}define(e,t){if("string"!=typeof e)throw new Error(`room define error: definition key=(${e}) must be of the type string`);if("function"!=typeof t)throw new Error(`room define error: roomClass=(${t}) is not a class`);if(!(t.prototype instanceof $))throw new Error(`room define error: roomClass=(${t}) must extends Room`);if(this.defs.has(e))throw new Error(`room define error: definition key=(${e}) exists`);this.defs.set(e,t),this.emit("define",e)}create(t,r=null){if("string"!=typeof t)throw new Error(`room create error: type=(${type}) must be a string`);if(!this.defs.has(t))throw new Error(`room create error: type=(${t}) is not defined`);if(null===r)r=(0,e.generateId)(32);else if("string"!=typeof r)throw new Error(`room create error: room id=(${r}) must be a string`);if(this.rooms.has(r))throw new Error(`room create error: room id=(${r}) is taken`);let o=new(this.defs.get(t))(r,this,this.transportLayer);return this.rooms.set(r,o),this.emit("create",r),o}destroy(e){if(!this.rooms.has(e))throw new Error(`room destroy error: roomId=(${e}) does not exist`);let t=this.rooms.get(e);this.rooms.delete(e),t.handleDestroy(),this.emit("destroy",e)}}var L=b;var E=class{logging=l.Instance;config=null;transportLayer=null;getRoomByID=e=>this.rooms.get(e);rooms=null;constructor(e){this.config=new w(e),this.transportLayer=new f(e.authMiddleware,this.getRoomByID),this.rooms=new L(this.transportLayer);for(let e of this.config.transports)e.onInitialize(this.transportLayer)}get connections(){return this.transportLayer.connections}};var M=class extends p{logger=l.Instance.getLogger("transport:websocket");QUERY_TICKET_PARAM="ticket";ws=null;transportLayer=null;constructor(e,t=null){super(),"string"==typeof t&&(this.QUERY_TICKET_PARAM=t),this.ws=new(0,s.WebSocketServer)(e),this.ws.on("connection",this.handleReject)}onInitialize(e){this.transportLayer=e,this.ws.off("connection",this.handleReject),this.ws.on("connection",this.handleConnect),this.logger.info("initialized")}handleReject=(e,t)=>e.close(4001);handleConnect=async(e,t)=>{let r=this.extractTicket(t);if(null===r)return this.logger.debug("client disconected, invalid ticket",r),e.close(4001);let o=null;try{o=await this.transportLayer.grantAccess(r)}catch(t){return this.logger.debug(`grant access failure, ticket is not accepted, ticket=${r}, reason=${t.message}`),e.close(4001)}e.on("message",((t,s)=>{s||(this.logger.debug(`actor id=(${o}) ticket=(${r}) sent non-binary data`),e.close(4002)),this.transportLayer.handleMessage(o,t)})),e.once("close",(()=>this.transportLayer.handleClose(o))),this.transportLayer.on("message",o,((t,r)=>{e.send(r)})),this.transportLayer.on("kick",o,((t,r)=>{e.close(4003,r)}))};extractTicket(e){return new(0,o.URL)("https://kalevski.dev"+e.url).searchParams.get(this.QUERY_TICKET_PARAM)||null}},R=r.Buffer;class I extends e.Broadcast{baseURL=null;logger=l.Instance.getLogger("ws client");ws=null;constructor(e){super(),this.baseURL=e}get connected(){return null!==this.ws}connect(e=""){if(this.connected)return this.logger.warning("the client is already connected");if("string"!=typeof e)throw new Error(`ticket must be a sting, ${e} provided`);let t=new URL(this.baseURL);t.searchParams.append("ticket",e),this.ws=new(0,s.WebSocket)(t.toString()),this.ws.once("open",(()=>this.onOpen())),this.ws.once("close",((e,t)=>this.onClose(e,t)))}disconnect(){this.connected&&(this.ws.close(),this.ws.removeAllListeners(),this.ws=null,this.emit("client:disconnect",R.from("terminated")))}send(e,t){if(!this.connected)return this.logger.warning("send fail: connection is not established yet");if("string"!=typeof e)throw new Error(`topic must be a string, ${e} provided`);if(t instanceof Uint8Array)return this.ws.send(d(e,t));if("string"==typeof t)return this.ws.send(d(e,R.from(t,"utf-8")));throw new Error(`send error: invalid payload=${t}, must be a string or Buffer`)}onOpen(){this.ws.on("message",((e,t)=>this.onMessage(e,t))),this.emit("client:connect")}onMessage(e,t){let{topic:r,payload:o}=g(e);this.emit(r,o)}onClose(e,t){this.emit("client:disconnect",t),this.ws.removeAllListeners(),this.ws=null}emit(e,...t){if(0===this.events.listenerCount(e))return this.logger.warning(`event=${e} emitted, register listener to handle the event`);super.emit(e,...t)}}const x={WSTransport:M},A={WSClient:I}; | ||
| var e=require("@toolcase/base"),t=require("@toolcase/logging"),r=require("buffer");require("http");var s=require("url"),o=require("ws");function n(e,t,r,s){Object.defineProperty(e,t,{get:r,set:s,enumerable:!0,configurable:!0})}n(module.exports,"Transports",(()=>C)),n(module.exports,"Clients",(()=>T)),n(module.exports,"logging",(()=>$86ab48f13b64a4ed$export$45697122e627faf2)),n(module.exports,"Rivalis",(()=>M)),n(module.exports,"AuthMiddleware",(()=>i)),n(module.exports,"Room",(()=>$)),n(module.exports,"Actor",(()=>w));var i=class{async validateTicket(e){return!0}async extractPayload(e){return null}async getRoomId(e){throw new Error("AuthMiddleware#getRoomId not implemented")}};class a extends t.LoggerFactory{constructor(){super([new(0,t.ConsoleLogReporter)])}}a.Instance=new a;var l=a;const c=new(0,e.Serializer)("@rivalis/message"),h="realtime_message";c.define(h,[{key:"topic",type:"string",rule:"required"},{key:"payload",type:"bytes",rule:"required"}]);const d=(e,t)=>c.encode(h,{topic:e,payload:t}),u=e=>c.decode(h,e);class g extends e.Broadcast{logger=l.Instance.getLogger("transport layer");authMiddleware=null;getRoom=null;roomIds=new Map;constructor(e,t=null){super(),this.authMiddleware=e,this.getRoom=t}get connections(){return this.roomIds.size}on=(e,t,r,s)=>super.on(`${e}:${t}`,r,s);once=(e,t,r,s)=>super.once(`${e}:${t}`,r,s);emit=(e,t,r)=>super.emit(`${e}:${t}`,t,r);async grantAccess(t){if(!0!==await this.authMiddleware.validateTicket(t))throw new Error("invalid ticket");let r=await this.authMiddleware.extractPayload(t);if(null!==r&&"object"!=typeof r)throw new Error(`actor data can be an object or null, provided=${r}`);let s=await this.authMiddleware.getRoomId(t),o=this.getRoom(s);if(null===o)throw new Error(`room id=${s} does not exist`);let n=(0,e.generateId)(16);return this.roomIds.set(n,s),o.handleJoin(n,r),this.logger.info(`actor id=${n} join room id=${s}`),n}handleMessage(e,t){let r=u(t),s=this.roomIds.get(e);this.getRoom(s).handleMessage(e,r.topic,r.payload)}handleClose(e){let t=this.roomIds.get(e),r=this.getRoom(t);this.roomIds.delete(e);try{r.handleLeave(e)}catch(e){}this.logger.info(`actor id=${e} leave room id=${t}`)}send(e,t,r){let s=d(t,r);this.emit("message",e,s)}kick(e,t){this.emit("kick",e,t)}}var f=g;var p=class{transportLayer=null;onInitialize(e){}dispose(){}};var m=class{transports=null;authMiddleware=null;constructor(e={}){if("object"!=typeof e)throw new Error("config error: provided config is not an object");if(!Array.isArray(e.transports))throw new Error("config error: transports must be an array");for(let[t,r]of e.transports.entries())if(!(r instanceof p))throw new Error(`config error: transports[${t}] must be an instance of Transport`);if(!(e.authMiddleware instanceof i))throw new Error("config error: authMiddleware must be an instance of AuthMiddleware");this.transports=e.transports,this.authMiddleware=e.authMiddleware}};var w=class{id=null;data=null;joined=new Date;room=null;storage=new Map;constructor(e,t,r){this.id=e,this.data=t,this.room=r}send(e,t){this.room.send(this,e,t)}kick(e){this.room.kick(this,e)}save(e,t){this.storage.set(e,t)}get(e){let t=this.storage.get(e);return void 0===t?null:t}},y=r.Buffer;const v="invalid_message",k="room_destroyed";class b{id=null;logger=null;manager=null;transportLayer=null;topics=new Map;actors=new Map;constructor(e,t,r){this.id=e,this.logger=l.Instance.getLogger(`room=${e}`),this.manager=t,this.transportLayer=r,this.onCreate(),this.logger.info("created")}onCreate(){}onJoin(e){}onLeave(e){}onDestroy(){}bind(e,t,r=null){if("string"!=typeof e)throw new Error(`topic must be a string, ${e} provided`);if("function"!=typeof t)throw new Error(`topicListener must be a function, ${t} provided`);return!this.topics.has(e)&&(this.topics.set(e,t.bind(null===r?this:r)),!0)}unbind(e){if("string"!=typeof e)throw new Error(`topic must be a string, ${e} provided`);return this.topics.delete(e)}send(e,t,r){if("string"!=typeof t)throw new Error(`send error: topic must be a sting, ${t} provided`);if(!(e instanceof w))throw new Error(`send error: actor=${e} must be an instance of Actor`);if(r instanceof Uint8Array)return this.transportLayer.send(e.id,t,r);if("string"==typeof r)return this.transportLayer.send(e.id,t,y.from(r,"utf-8"));throw new Error(`send error: invalid payload=${r}, must be a string or Buffer`)}broadcast(e,t){this.each((r=>this.send(r,e,t)))}each(e){this.actors.forEach(e)}kick(e,t=""){if(t instanceof Uint8Array)return this.transportLayer.kick(e.id,t);if("string"==typeof t)return this.transportLayer.kick(e.id,y.from(t,"utf-8"));throw new Error(`kick error: invalid payload=${t}, must be a string or Buffer`)}destroy(){this.manager.destroy(this.id)}handleDestroy(){this.each((e=>e.kick(k))),this.onDestroy(),this.actors.clear(),this.topics.clear(),this.transportLayer=null,this.manager=null,this.logger.info("destroyed"),this.logger=null}handleJoin(e,t=null){let r=new w(e,t||{},this);this.actors.set(e,r),this.onJoin(r)}handleMessage(e,t,r){let s=this.topics.get(t)||null;null===s&&(s=this.topics.get(b.Any)||null);let o=this.actors.get(e)||null;if(null===s)return this.logger.debug(`actor id=${e} is kicked, reason: sending message on non existing topic=${t}`),o.kick(v);s(o,r,t)}handleLeave(e){let t=this.actors.get(e);this.onLeave(t),this.actors.delete(e)}}b.Any="*";var $=b;class L extends e.Broadcast{transportLayer=null;defs=new Map;rooms=new Map;constructor(e){super(),this.transportLayer=e}get count(){return this.rooms.size}keys(){return this.rooms.keys()}get(e){return this.rooms.get(e)||null}define(e,t){if("string"!=typeof e)throw new Error(`room define error: definition key=(${e}) must be of the type string`);if("function"!=typeof t)throw new Error(`room define error: roomClass=(${t}) is not a class`);if(!(t.prototype instanceof $))throw new Error(`room define error: roomClass=(${t}) must extends Room`);if(this.defs.has(e))throw new Error(`room define error: definition key=(${e}) exists`);this.defs.set(e,t),this.emit("define",e)}create(t,r=null){if("string"!=typeof t)throw new Error(`room create error: type=(${type}) must be a string`);if(!this.defs.has(t))throw new Error(`room create error: type=(${t}) is not defined`);if(null===r)r=(0,e.generateId)(32);else if("string"!=typeof r)throw new Error(`room create error: room id=(${r}) must be a string`);if(this.rooms.has(r))throw new Error(`room create error: room id=(${r}) is taken`);let s=new(this.defs.get(t))(r,this,this.transportLayer);return this.rooms.set(r,s),this.emit("create",r),s}destroy(e){if(!this.rooms.has(e))throw new Error(`room destroy error: roomId=(${e}) does not exist`);let t=this.rooms.get(e);this.rooms.delete(e),t.handleDestroy(),this.emit("destroy",e)}}var E=L;var M=class{logging=l.Instance;config=null;transportLayer=null;getRoomByID=e=>this.rooms.get(e);rooms=null;constructor(e){this.config=new m(e),this.transportLayer=new f(e.authMiddleware,this.getRoomByID),this.rooms=new E(this.transportLayer);for(let e of this.config.transports)e.onInitialize(this.transportLayer)}get connections(){return this.transportLayer.connections}};var R=class extends p{logger=l.Instance.getLogger("transport:websocket");QUERY_TICKET_PARAM="ticket";ws=null;transportLayer=null;constructor(e,t=null){super(),"string"==typeof t&&(this.QUERY_TICKET_PARAM=t),this.ws=new(0,o.WebSocketServer)(e),this.ws.on("connection",this.handleReject)}onInitialize(e){this.transportLayer=e,this.ws.off("connection",this.handleReject),this.ws.on("connection",this.handleConnect),this.logger.info("initialized")}handleReject=(e,t)=>e.close(4001);handleConnect=async(e,t)=>{let r=this.extractTicket(t);if(null===r)return this.logger.debug("client disconected, invalid ticket",r),e.close(4001);let s=null;try{s=await this.transportLayer.grantAccess(r)}catch(t){return this.logger.debug(`grant access failure, ticket is not accepted, ticket=${r}, reason=${t.message}`),e.close(4001)}e.on("message",((t,o)=>{if(!o)return this.logger.debug(`actor id=(${s}) ticket=(${r}) sent non-binary data`),e.close(4002);"verbose"===l.Instance.level&&this.logger.verbose("message received",t),this.transportLayer.handleMessage(s,t)})),e.once("close",(()=>this.transportLayer.handleClose(s))),this.transportLayer.on("message",s,((t,r)=>{e.send(r)})),this.transportLayer.on("kick",s,((t,r)=>{e.close(4003,r)}))};extractTicket(e){return new(0,s.URL)("https://kalevski.dev"+e.url).searchParams.get(this.QUERY_TICKET_PARAM)||null}},I=r.Buffer;const A=new Uint8Array;class x extends e.Broadcast{baseURL=null;logger=l.Instance.getLogger("ws client");ws=null;constructor(e){super(),this.baseURL=e}get connected(){return null!==this.ws}connect(e=""){if(this.connected)return this.logger.warning("the client is already connected");if("string"!=typeof e)throw new Error(`ticket must be a sting, ${e} provided`);let t=new URL(this.baseURL);t.searchParams.append("ticket",e),this.ws=new(0,o.WebSocket)(t.toString()),this.ws.once("open",(()=>this.onOpen())),this.ws.once("close",((e,t)=>this.onClose(e,t)))}disconnect(){this.connected&&(this.ws.close(),this.ws.removeAllListeners(),this.ws=null,this.emit("client:disconnect",I.from("terminated")))}send(e,t=A){if(!this.connected)return this.logger.warning("send fail: connection is not established yet");if("string"!=typeof e)throw new Error(`topic must be a string, ${e} provided`);if(t instanceof Uint8Array)return this.ws.send(d(e,t));if("string"==typeof t)return this.ws.send(d(e,I.from(t,"utf-8")));throw new Error(`send error: invalid payload=${t}, must be a string or Buffer`)}onOpen(){this.ws.on("message",((e,t)=>this.onMessage(e,t))),this.emit("client:connect")}onMessage(e,t){let{topic:r,payload:s}=u(e);this.emit(r,s)}onClose(e,t){this.emit("client:disconnect",t),this.ws.removeAllListeners(),this.ws=null}emit(e,...t){if(0===this.events.listenerCount(e))return this.logger.warning(`event=${e} emitted, register listener to handle the event`);super.emit(e,...t)}}const C={WSTransport:R},T={WSClient:x}; |
+1
-1
@@ -1,1 +0,1 @@ | ||
| import{generateId as e,Broadcast as t,Serializer as r}from"@toolcase/base";import{ConsoleLogReporter as s,LoggerFactory as o}from"@toolcase/logging";import{Buffer as n}from"buffer";import"http";import{URL as i}from"url";import{WebSocketServer as a,WebSocket as l}from"ws";var c=class{async validateTicket(e){return!0}async extractPayload(e){return null}async getRoomId(e){throw new Error("AuthMiddleware#getRoomId not implemented")}};class h extends o{constructor(){super([new s])}}h.Instance=new h;var d=h;const g=new r("@toolcase/realtime"),u="realtime_message";g.define(u,[{key:"topic",type:"string",rule:"required"},{key:"payload",type:"bytes",rule:"required"}]);const f=(e,t)=>g.encode(u,{topic:e,payload:t}),p=e=>g.decode(u,e);var m=class extends t{logger=d.Instance.getLogger("transport layer");authMiddleware=null;getRoom=null;roomIds=new Map;constructor(e,t=null){super(),this.authMiddleware=e,this.getRoom=t}get connections(){return this.roomIds.size}on=(e,t,r,s)=>super.on(`${e}:${t}`,r,s);once=(e,t,r,s)=>super.once(`${e}:${t}`,r,s);emit=(e,t,r)=>super.emit(`${e}:${t}`,t,r);async grantAccess(t){if(!0!==await this.authMiddleware.validateTicket(t))throw new Error("invalid ticket");let r=await this.authMiddleware.extractPayload(t);if(null!==r&&"object"!=typeof r)throw new Error(`actor data can be an object or null, provided=${r}`);let s=await this.authMiddleware.getRoomId(t),o=this.getRoom(s);if(null===o)throw new Error(`room id=${s} does not exist`);let n=e(16);return this.roomIds.set(n,s),o.handleJoin(n,r),this.logger.info(`actor id=${n} join room id=${s}`),n}handleMessage(e,t){let r=p(t),s=this.roomIds.get(e);this.getRoom(s).handleMessage(e,r.topic,r.payload)}handleClose(e){let t=this.roomIds.get(e),r=this.getRoom(t);this.roomIds.delete(e);try{r.handleLeave(e)}catch(e){}this.logger.info(`actor id=${e} leave room id=${t}`)}send(e,t,r){let s=f(t,r);this.emit("message",e,s)}kick(e,t){this.emit("kick",e,t)}};var w=class{transportLayer=null;onInitialize(e){}dispose(){}};var y=class{transports=null;authMiddleware=null;constructor(e={}){if("object"!=typeof e)throw new Error("config error: provided config is not an object");if(!Array.isArray(e.transports))throw new Error("config error: transports must be an array");for(let[t,r]of e.transports.entries())if(!(r instanceof w))throw new Error(`config error: transports[${t}] must be an instance of Transport`);if(!(e.authMiddleware instanceof c))throw new Error("config error: authMiddleware must be an instance of AuthMiddleware");this.transports=e.transports,this.authMiddleware=e.authMiddleware}};var k=class{id=null;data=null;joined=new Date;room=null;storage=new Map;constructor(e,t,r){this.id=e,this.data=t,this.room=r}send(e,t){this.room.send(this,e,t)}kick(e){this.room.kick(this,e)}save(e,t){this.storage.set(e,t)}get(e){let t=this.storage.get(e);return void 0===t?null:t}},$=n;const v="invalid_message",L="room_destroyed";var b=class{id=null;logger=null;manager=null;transportLayer=null;topics=new Map;actors=new Map;constructor(e,t,r){this.id=e,this.logger=d.Instance.getLogger(`room=${e}`),this.manager=t,this.transportLayer=r,this.onCreate(),this.logger.info("created")}onCreate(){}onJoin(e){}onLeave(e){}onDestroy(){}listen(e,t,r=null){if("string"!=typeof e)throw new Error(`topic must be a string, ${e} provided`);if("function"!=typeof t)throw new Error(`topicListener must be a function, ${t} provided`);if(this.topics.has(e))throw new Error(`topic ${e} is already registered`);this.topics.set(e,t.bind(null===r?this:r))}send(e,t,r){if("string"!=typeof t)throw new Error(`send error: topic must be a sting, ${t} provided`);if(!(e instanceof k))throw new Error(`send error: actor=${e} must be an instance of Actor`);if(r instanceof Uint8Array)return this.transportLayer.send(e.id,t,r);if("string"==typeof r)return this.transportLayer.send(e.id,t,$.from(r,"utf-8"));throw new Error(`send error: invalid payload=${r}, must be a string or Buffer`)}broadcast(e,t){this.each((r=>this.send(r,e,t)))}each(e){this.actors.forEach(e)}kick(e,t=""){if(t instanceof Uint8Array)return this.transportLayer.kick(e.id,t);if("string"==typeof t)return this.transportLayer.kick(e.id,$.from(t,"utf-8"));throw new Error(`kick error: invalid payload=${t}, must be a string or Buffer`)}destroy(){this.manager.destroy(this.id)}handleDestroy(){this.each((e=>e.kick(L))),this.onDestroy(),this.actors.clear(),this.topics.clear(),this.transportLayer=null,this.manager=null,this.logger.info("destroyed"),this.logger=null}handleJoin(e,t=null){let r=new k(e,t||{},this);this.actors.set(e,r),this.onJoin(r)}handleMessage(e,t,r){let s=this.topics.get(t)||null,o=this.actors.get(e)||null;if(null===s)return this.logger.debug(`actor id=${e} is kicked, reason: sending message on non existing topic=${t}`),o.kick(v);s(o,r)}handleLeave(e){let t=this.actors.get(e);this.onLeave(t),this.actors.delete(e)}};var E=class extends t{transportLayer=null;defs=new Map;rooms=new Map;constructor(e){super(),this.transportLayer=e}get(e){return this.rooms.get(e)||null}define(e,t){if("string"!=typeof e)throw new Error(`room define error: definition key=(${e}) must be of the type string`);if("function"!=typeof t)throw new Error(`room define error: roomClass=(${t}) is not a class`);if(!(t.prototype instanceof b))throw new Error(`room define error: roomClass=(${t}) must extends Room`);if(this.defs.has(e))throw new Error(`room define error: definition key=(${e}) exists`);this.defs.set(e,t),this.emit("define",e)}create(t,r=null){if("string"!=typeof t)throw new Error(`room create error: type=(${type}) must be a string`);if(!this.defs.has(t))throw new Error(`room create error: type=(${t}) is not defined`);if(null===r)r=e(32);else if("string"!=typeof r)throw new Error(`room create error: room id=(${r}) must be a string`);if(this.rooms.has(r))throw new Error(`room create error: room id=(${r}) is taken`);let s=new(this.defs.get(t))(r,this,this.transportLayer);return this.rooms.set(r,s),this.emit("create",r),s}destroy(e){if(!this.rooms.has(e))throw new Error(`room destroy error: roomId=(${e}) does not exist`);let t=this.rooms.get(e);this.rooms.delete(e),t.handleDestroy(),this.emit("destroy",e)}};var M=class{logging=d.Instance;config=null;transportLayer=null;getRoomByID=e=>this.rooms.get(e);rooms=null;constructor(e){this.config=new y(e),this.transportLayer=new m(e.authMiddleware,this.getRoomByID),this.rooms=new E(this.transportLayer);for(let e of this.config.transports)e.onInitialize(this.transportLayer)}get connections(){return this.transportLayer.connections}};var R=class extends w{logger=d.Instance.getLogger("transport:websocket");QUERY_TICKET_PARAM="ticket";ws=null;transportLayer=null;constructor(e,t=null){super(),"string"==typeof t&&(this.QUERY_TICKET_PARAM=t),this.ws=new a(e),this.ws.on("connection",this.handleReject)}onInitialize(e){this.transportLayer=e,this.ws.off("connection",this.handleReject),this.ws.on("connection",this.handleConnect),this.logger.info("initialized")}handleReject=(e,t)=>e.close(4001);handleConnect=async(e,t)=>{let r=this.extractTicket(t);if(null===r)return this.logger.debug("client disconected, invalid ticket",r),e.close(4001);let s=null;try{s=await this.transportLayer.grantAccess(r)}catch(t){return this.logger.debug(`grant access failure, ticket is not accepted, ticket=${r}, reason=${t.message}`),e.close(4001)}e.on("message",((t,o)=>{o||(this.logger.debug(`actor id=(${s}) ticket=(${r}) sent non-binary data`),e.close(4002)),this.transportLayer.handleMessage(s,t)})),e.once("close",(()=>this.transportLayer.handleClose(s))),this.transportLayer.on("message",s,((t,r)=>{e.send(r)})),this.transportLayer.on("kick",s,((t,r)=>{e.close(4003,r)}))};extractTicket(e){return new i("https://kalevski.dev"+e.url).searchParams.get(this.QUERY_TICKET_PARAM)||null}},I=n;const A={WSTransport:R},C={WSClient:class extends t{baseURL=null;logger=d.Instance.getLogger("ws client");ws=null;constructor(e){super(),this.baseURL=e}get connected(){return null!==this.ws}connect(e=""){if(this.connected)return this.logger.warning("the client is already connected");if("string"!=typeof e)throw new Error(`ticket must be a sting, ${e} provided`);let t=new URL(this.baseURL);t.searchParams.append("ticket",e),this.ws=new l(t.toString()),this.ws.once("open",(()=>this.onOpen())),this.ws.once("close",((e,t)=>this.onClose(e,t)))}disconnect(){this.connected&&(this.ws.close(),this.ws.removeAllListeners(),this.ws=null,this.emit("client:disconnect",I.from("terminated")))}send(e,t){if(!this.connected)return this.logger.warning("send fail: connection is not established yet");if("string"!=typeof e)throw new Error(`topic must be a string, ${e} provided`);if(t instanceof Uint8Array)return this.ws.send(f(e,t));if("string"==typeof t)return this.ws.send(f(e,I.from(t,"utf-8")));throw new Error(`send error: invalid payload=${t}, must be a string or Buffer`)}onOpen(){this.ws.on("message",((e,t)=>this.onMessage(e,t))),this.emit("client:connect")}onMessage(e,t){let{topic:r,payload:s}=p(e);this.emit(r,s)}onClose(e,t){this.emit("client:disconnect",t),this.ws.removeAllListeners(),this.ws=null}emit(e,...t){if(0===this.events.listenerCount(e))return this.logger.warning(`event=${e} emitted, register listener to handle the event`);super.emit(e,...t)}}};export{A as Transports,C as Clients,$ca9b6331ba7bfe4d$export$45697122e627faf2 as logging,M as Rivalis,c as AuthMiddleware,b as Room,k as Actor}; | ||
| import{generateId as e,Broadcast as t,Serializer as r}from"@toolcase/base";import{ConsoleLogReporter as s,LoggerFactory as o}from"@toolcase/logging";import{Buffer as n}from"buffer";import"http";import{URL as i}from"url";import{WebSocketServer as a,WebSocket as l}from"ws";var c=class{async validateTicket(e){return!0}async extractPayload(e){return null}async getRoomId(e){throw new Error("AuthMiddleware#getRoomId not implemented")}};class h extends o{constructor(){super([new s])}}h.Instance=new h;var d=h;const g=new r("@rivalis/message"),u="realtime_message";g.define(u,[{key:"topic",type:"string",rule:"required"},{key:"payload",type:"bytes",rule:"required"}]);const f=(e,t)=>g.encode(u,{topic:e,payload:t}),p=e=>g.decode(u,e);var m=class extends t{logger=d.Instance.getLogger("transport layer");authMiddleware=null;getRoom=null;roomIds=new Map;constructor(e,t=null){super(),this.authMiddleware=e,this.getRoom=t}get connections(){return this.roomIds.size}on=(e,t,r,s)=>super.on(`${e}:${t}`,r,s);once=(e,t,r,s)=>super.once(`${e}:${t}`,r,s);emit=(e,t,r)=>super.emit(`${e}:${t}`,t,r);async grantAccess(t){if(!0!==await this.authMiddleware.validateTicket(t))throw new Error("invalid ticket");let r=await this.authMiddleware.extractPayload(t);if(null!==r&&"object"!=typeof r)throw new Error(`actor data can be an object or null, provided=${r}`);let s=await this.authMiddleware.getRoomId(t),o=this.getRoom(s);if(null===o)throw new Error(`room id=${s} does not exist`);let n=e(16);return this.roomIds.set(n,s),o.handleJoin(n,r),this.logger.info(`actor id=${n} join room id=${s}`),n}handleMessage(e,t){let r=p(t),s=this.roomIds.get(e);this.getRoom(s).handleMessage(e,r.topic,r.payload)}handleClose(e){let t=this.roomIds.get(e),r=this.getRoom(t);this.roomIds.delete(e);try{r.handleLeave(e)}catch(e){}this.logger.info(`actor id=${e} leave room id=${t}`)}send(e,t,r){let s=f(t,r);this.emit("message",e,s)}kick(e,t){this.emit("kick",e,t)}};var w=class{transportLayer=null;onInitialize(e){}dispose(){}};var y=class{transports=null;authMiddleware=null;constructor(e={}){if("object"!=typeof e)throw new Error("config error: provided config is not an object");if(!Array.isArray(e.transports))throw new Error("config error: transports must be an array");for(let[t,r]of e.transports.entries())if(!(r instanceof w))throw new Error(`config error: transports[${t}] must be an instance of Transport`);if(!(e.authMiddleware instanceof c))throw new Error("config error: authMiddleware must be an instance of AuthMiddleware");this.transports=e.transports,this.authMiddleware=e.authMiddleware}};var k=class{id=null;data=null;joined=new Date;room=null;storage=new Map;constructor(e,t,r){this.id=e,this.data=t,this.room=r}send(e,t){this.room.send(this,e,t)}kick(e){this.room.kick(this,e)}save(e,t){this.storage.set(e,t)}get(e){let t=this.storage.get(e);return void 0===t?null:t}},v=n;const b="invalid_message",$="room_destroyed";class L{id=null;logger=null;manager=null;transportLayer=null;topics=new Map;actors=new Map;constructor(e,t,r){this.id=e,this.logger=d.Instance.getLogger(`room=${e}`),this.manager=t,this.transportLayer=r,this.onCreate(),this.logger.info("created")}onCreate(){}onJoin(e){}onLeave(e){}onDestroy(){}bind(e,t,r=null){if("string"!=typeof e)throw new Error(`topic must be a string, ${e} provided`);if("function"!=typeof t)throw new Error(`topicListener must be a function, ${t} provided`);return!this.topics.has(e)&&(this.topics.set(e,t.bind(null===r?this:r)),!0)}unbind(e){if("string"!=typeof e)throw new Error(`topic must be a string, ${e} provided`);return this.topics.delete(e)}send(e,t,r){if("string"!=typeof t)throw new Error(`send error: topic must be a sting, ${t} provided`);if(!(e instanceof k))throw new Error(`send error: actor=${e} must be an instance of Actor`);if(r instanceof Uint8Array)return this.transportLayer.send(e.id,t,r);if("string"==typeof r)return this.transportLayer.send(e.id,t,v.from(r,"utf-8"));throw new Error(`send error: invalid payload=${r}, must be a string or Buffer`)}broadcast(e,t){this.each((r=>this.send(r,e,t)))}each(e){this.actors.forEach(e)}kick(e,t=""){if(t instanceof Uint8Array)return this.transportLayer.kick(e.id,t);if("string"==typeof t)return this.transportLayer.kick(e.id,v.from(t,"utf-8"));throw new Error(`kick error: invalid payload=${t}, must be a string or Buffer`)}destroy(){this.manager.destroy(this.id)}handleDestroy(){this.each((e=>e.kick($))),this.onDestroy(),this.actors.clear(),this.topics.clear(),this.transportLayer=null,this.manager=null,this.logger.info("destroyed"),this.logger=null}handleJoin(e,t=null){let r=new k(e,t||{},this);this.actors.set(e,r),this.onJoin(r)}handleMessage(e,t,r){let s=this.topics.get(t)||null;null===s&&(s=this.topics.get(L.Any)||null);let o=this.actors.get(e)||null;if(null===s)return this.logger.debug(`actor id=${e} is kicked, reason: sending message on non existing topic=${t}`),o.kick(b);s(o,r,t)}handleLeave(e){let t=this.actors.get(e);this.onLeave(t),this.actors.delete(e)}}L.Any="*";var E=L;var M=class extends t{transportLayer=null;defs=new Map;rooms=new Map;constructor(e){super(),this.transportLayer=e}get count(){return this.rooms.size}keys(){return this.rooms.keys()}get(e){return this.rooms.get(e)||null}define(e,t){if("string"!=typeof e)throw new Error(`room define error: definition key=(${e}) must be of the type string`);if("function"!=typeof t)throw new Error(`room define error: roomClass=(${t}) is not a class`);if(!(t.prototype instanceof E))throw new Error(`room define error: roomClass=(${t}) must extends Room`);if(this.defs.has(e))throw new Error(`room define error: definition key=(${e}) exists`);this.defs.set(e,t),this.emit("define",e)}create(t,r=null){if("string"!=typeof t)throw new Error(`room create error: type=(${type}) must be a string`);if(!this.defs.has(t))throw new Error(`room create error: type=(${t}) is not defined`);if(null===r)r=e(32);else if("string"!=typeof r)throw new Error(`room create error: room id=(${r}) must be a string`);if(this.rooms.has(r))throw new Error(`room create error: room id=(${r}) is taken`);let s=new(this.defs.get(t))(r,this,this.transportLayer);return this.rooms.set(r,s),this.emit("create",r),s}destroy(e){if(!this.rooms.has(e))throw new Error(`room destroy error: roomId=(${e}) does not exist`);let t=this.rooms.get(e);this.rooms.delete(e),t.handleDestroy(),this.emit("destroy",e)}};var R=class{logging=d.Instance;config=null;transportLayer=null;getRoomByID=e=>this.rooms.get(e);rooms=null;constructor(e){this.config=new y(e),this.transportLayer=new m(e.authMiddleware,this.getRoomByID),this.rooms=new M(this.transportLayer);for(let e of this.config.transports)e.onInitialize(this.transportLayer)}get connections(){return this.transportLayer.connections}};var I=class extends w{logger=d.Instance.getLogger("transport:websocket");QUERY_TICKET_PARAM="ticket";ws=null;transportLayer=null;constructor(e,t=null){super(),"string"==typeof t&&(this.QUERY_TICKET_PARAM=t),this.ws=new a(e),this.ws.on("connection",this.handleReject)}onInitialize(e){this.transportLayer=e,this.ws.off("connection",this.handleReject),this.ws.on("connection",this.handleConnect),this.logger.info("initialized")}handleReject=(e,t)=>e.close(4001);handleConnect=async(e,t)=>{let r=this.extractTicket(t);if(null===r)return this.logger.debug("client disconected, invalid ticket",r),e.close(4001);let s=null;try{s=await this.transportLayer.grantAccess(r)}catch(t){return this.logger.debug(`grant access failure, ticket is not accepted, ticket=${r}, reason=${t.message}`),e.close(4001)}e.on("message",((t,o)=>{if(!o)return this.logger.debug(`actor id=(${s}) ticket=(${r}) sent non-binary data`),e.close(4002);"verbose"===d.Instance.level&&this.logger.verbose("message received",t),this.transportLayer.handleMessage(s,t)})),e.once("close",(()=>this.transportLayer.handleClose(s))),this.transportLayer.on("message",s,((t,r)=>{e.send(r)})),this.transportLayer.on("kick",s,((t,r)=>{e.close(4003,r)}))};extractTicket(e){return new i("https://kalevski.dev"+e.url).searchParams.get(this.QUERY_TICKET_PARAM)||null}},A=n;const C=new Uint8Array;const x={WSTransport:I},T={WSClient:class extends t{baseURL=null;logger=d.Instance.getLogger("ws client");ws=null;constructor(e){super(),this.baseURL=e}get connected(){return null!==this.ws}connect(e=""){if(this.connected)return this.logger.warning("the client is already connected");if("string"!=typeof e)throw new Error(`ticket must be a sting, ${e} provided`);let t=new URL(this.baseURL);t.searchParams.append("ticket",e),this.ws=new l(t.toString()),this.ws.once("open",(()=>this.onOpen())),this.ws.once("close",((e,t)=>this.onClose(e,t)))}disconnect(){this.connected&&(this.ws.close(),this.ws.removeAllListeners(),this.ws=null,this.emit("client:disconnect",A.from("terminated")))}send(e,t=C){if(!this.connected)return this.logger.warning("send fail: connection is not established yet");if("string"!=typeof e)throw new Error(`topic must be a string, ${e} provided`);if(t instanceof Uint8Array)return this.ws.send(f(e,t));if("string"==typeof t)return this.ws.send(f(e,A.from(t,"utf-8")));throw new Error(`send error: invalid payload=${t}, must be a string or Buffer`)}onOpen(){this.ws.on("message",((e,t)=>this.onMessage(e,t))),this.emit("client:connect")}onMessage(e,t){let{topic:r,payload:s}=p(e);this.emit(r,s)}onClose(e,t){this.emit("client:disconnect",t),this.ws.removeAllListeners(),this.ws=null}emit(e,...t){if(0===this.events.listenerCount(e))return this.logger.warning(`event=${e} emitted, register listener to handle the event`);super.emit(e,...t)}}};export{x as Transports,T as Clients,$ca9b6331ba7bfe4d$export$45697122e627faf2 as logging,R as Rivalis,c as AuthMiddleware,E as Room,k as Actor}; |
+10
-2
@@ -37,3 +37,3 @@ export default Room; | ||
| * @private | ||
| * @type {Map<string,TopicListener>} | ||
| * @type {Map<string,import('./main').TopicListener>} | ||
| */ | ||
@@ -66,5 +66,10 @@ private topics; | ||
| */ | ||
| protected listen(topic: string, topicListener: import('./main').TopicListener, context?: any | null): void; | ||
| protected bind(topic: string, topicListener: import('./main').TopicListener, context?: any | null): boolean; | ||
| /** | ||
| * | ||
| * @param {string} topic | ||
| */ | ||
| unbind(topic: string): boolean; | ||
| /** | ||
| * | ||
| * @param {Actor} actor | ||
@@ -114,2 +119,5 @@ * @param {string} topic | ||
| } | ||
| declare namespace Room { | ||
| const Any: string; | ||
| } | ||
| import Actor from "./Actor"; | ||
@@ -116,0 +124,0 @@ import { Logger } from "@toolcase/logging"; |
@@ -31,2 +31,4 @@ export default RoomManager; | ||
| private rooms; | ||
| get count(): number; | ||
| keys(): IterableIterator<string>; | ||
| /** | ||
@@ -33,0 +35,0 @@ * |
+1
-1
| { | ||
| "name": "@rivalis/core", | ||
| "version": "5.0.1", | ||
| "version": "5.0.2", | ||
| "description": "NodeJS framework for building real-time applications", | ||
@@ -5,0 +5,0 @@ "source": "src/main.js", |
| export default loggging; | ||
| declare const loggging: LoggerFactory; | ||
| import { LoggerFactory } from "@toolcase/logging"; |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
34726
2.16%561
1.26%17
-5.56%