├── index.js ├── release ├── interfaces.js ├── roles.js ├── errors.js ├── util.js ├── transactions.js ├── server.js ├── protocols.js ├── message.js ├── topics.js ├── procedures.js ├── router.js ├── session-manager.js ├── session.js └── handlers.js ├── src ├── roles.ts ├── errors.ts ├── util.ts ├── protocols.ts ├── transactions.ts ├── server.ts ├── message.ts ├── router.ts ├── topics.ts ├── procedures.ts ├── session-manager.ts ├── session.ts ├── interfaces.ts └── handlers.ts ├── example └── server.ts ├── .gitignore ├── test ├── mocks │ ├── request.ts │ └── socket.ts └── all │ ├── errors.ts │ ├── roles.ts │ ├── server.ts │ ├── message.ts │ ├── transaction.ts │ ├── util.ts │ ├── protocols.ts │ ├── topics.ts │ ├── procedures.ts │ ├── session-manager.ts │ ├── wamp.ts │ ├── router.ts │ └── session.ts ├── benchmark ├── server │ └── index.js ├── clients │ ├── two │ │ └── index.js │ └── one │ │ └── index.js └── stress-test.js ├── .travis.yml ├── wallaby.js ├── tsconfig.json ├── tslint.json ├── LICENSE ├── gulpfile.js ├── package.json └── readme.md /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./release/server').default; 2 | -------------------------------------------------------------------------------- /release/interfaces.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); -------------------------------------------------------------------------------- /src/roles.ts: -------------------------------------------------------------------------------- 1 | const ROLES = { 2 | roles: { 3 | broker: {}, 4 | dealer: {}, 5 | }, 6 | }; 7 | 8 | export default ROLES; 9 | -------------------------------------------------------------------------------- /example/server.ts: -------------------------------------------------------------------------------- 1 | import Server from '../src/server'; 2 | const SERVER = new Server({ 3 | port: 8000, 4 | realms: ['com.example.inge'], 5 | }); 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | example/*.js 3 | coverage/ 4 | 5 | .vscode 6 | .idea 7 | 8 | typings/ 9 | 10 | *.log 11 | .DS_Store 12 | 13 | node_modules/ 14 | -------------------------------------------------------------------------------- /test/mocks/request.ts: -------------------------------------------------------------------------------- 1 | class Request { 2 | 3 | public connection = { 4 | remoteAddress: '::ffff:127.0.0.1', 5 | }; 6 | 7 | } 8 | 9 | export default Request; 10 | -------------------------------------------------------------------------------- /release/roles.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | const ROLES = { 6 | roles: { 7 | broker: {}, 8 | dealer: {}, 9 | }, 10 | }; 11 | exports.default = ROLES; -------------------------------------------------------------------------------- /test/all/errors.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import errors from '../../src/errors'; 3 | 4 | describe('errors', () => { 5 | it('Should have 7 different types of errors', () => { 6 | expect(Object.keys(errors).length).equal(7); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let join = require('path').join; 3 | let server = require(join(__dirname, '..', '..')); 4 | const SERVER = new server({ 5 | port: 8000, 6 | realms: ['com.example.inge'], 7 | }); 8 | console.log('Server is running on port: 8000'); 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7.5.0" 4 | - "9.5.0" 5 | sudo: false 6 | env: 7 | - CXX=g++-4.8 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | packages: 13 | - g++-4.8 14 | after_success: 15 | - npm run coverage 16 | - npm run coveralls 17 | -------------------------------------------------------------------------------- /test/all/roles.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import roles from '../../src/roles'; 3 | 4 | describe('roles', () => { 5 | it('Should match', () => { 6 | const ROLES = { 7 | roles: { 8 | broker: {}, 9 | dealer: {}, 10 | }, 11 | }; 12 | expect(ROLES).deep.equal(roles); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | module.exports = function(w) { 2 | return { 3 | files: [ 4 | 'src/**/*.ts', 5 | 'test/mocks/*.ts', 6 | ], 7 | 8 | tests: [ 9 | 'test/all/*.ts' 10 | ], 11 | env: { 12 | type: 'node' 13 | }, 14 | compilers: { 15 | '**/*.ts': w.compilers.typeScript({ module: 'commonjs' }) 16 | } 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | const ERRORS = { 2 | general: 'wamp.errors.invalid_argument', 3 | goodbye: 'wamp.error.goodbye_and_out', 4 | hello: 'wamp.error.no_such_realm', 5 | procedure: 'wamp.error.no_such_procedure', 6 | register: 'wamp.error.procedure_already_exists', 7 | unregister: 'wamp.error.no_such_registration', 8 | uri: 'wamp.error.invalid_uri', 9 | }; 10 | 11 | export default ERRORS; 12 | -------------------------------------------------------------------------------- /test/mocks/socket.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import { SocketInterface } from '../../src/interfaces'; 3 | 4 | class Socket extends EventEmitter implements SocketInterface { 5 | 6 | public id = 0; 7 | public readyState = 0; 8 | 9 | constructor() { 10 | super(); 11 | } 12 | 13 | public close() { 14 | // ... 15 | } 16 | public send() { 17 | // ... 18 | } 19 | 20 | } 21 | 22 | export default Socket; 23 | -------------------------------------------------------------------------------- /release/errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | const ERRORS = { 6 | general: 'wamp.errors.invalid_argument', 7 | goodbye: 'wamp.error.goodbye_and_out', 8 | hello: 'wamp.error.no_such_realm', 9 | procedure: 'wamp.error.no_such_procedure', 10 | register: 'wamp.error.procedure_already_exists', 11 | unregister: 'wamp.error.no_such_registration', 12 | uri: 'wamp.error.invalid_uri', 13 | }; 14 | exports.default = ERRORS; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "outDir": "bin", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "removeComments": false, 11 | "noImplicitAny": true, 12 | "noEmitOnError": false, 13 | "suppressImplicitAnyIndexErrors": true 14 | }, 15 | "exclude": [ 16 | "node_modules", 17 | "bin" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /benchmark/clients/two/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let autobahn = require('autobahn'); 3 | let connection = new autobahn.Connection({ 4 | url: 'ws://127.0.0.1:8000/', 5 | realm: 'com.example.inge' 6 | }); 7 | 8 | connection.onopen = function (session) { 9 | 10 | function addTwo(args) { 11 | return args[0] + args[1]; 12 | } 13 | 14 | session.register('com.myapp.addTwo', addTwo) 15 | 16 | }; 17 | 18 | connection.onclose = function(e) { 19 | console.error(e) 20 | } 21 | 22 | connection.open(); 23 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 4 | * @export 5 | * @returns {number} 6 | */ 7 | export function makeID(): number { 8 | return Math.floor(Math.random() * 9007199254740992); // 2^53 9 | } 10 | 11 | /** 12 | * 13 | * 14 | * @export 15 | * @param {string} realm 16 | * @returns {boolean} 17 | */ 18 | export function isValidRealm(realm: string): boolean { 19 | const PATTERN = /^([^\s\.#]+\.)*([^\s\.#]+)$/; 20 | if (!realm || !PATTERN.test(realm) || realm.indexOf('wamp.') === 0) { 21 | return false; 22 | } 23 | return true; 24 | } 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:latest", 3 | "rules": { 4 | "semicolon": true, 5 | "quotemark": [true, "single"], 6 | "indent": [true, "spaces"], 7 | "interface-name": [true, "never-prefix"], 8 | "eofline": true, 9 | "use-strict": ["check-module", "check-function"], 10 | "use-isnan": true, 11 | "switch-default": true, 12 | "no-implicit-dependencies": [true, "dev"], 13 | "triple-equals": true, 14 | "no-empty-interface": false, 15 | "one-variable-per-declaration": true, 16 | "max-line-length": [true, 80] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /benchmark/clients/one/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let autobahn = require('autobahn'); 3 | let connection = new autobahn.Connection({ 4 | url: 'ws://127.0.0.1:8000/', 5 | realm: 'com.example.inge' 6 | }); 7 | 8 | connection.onopen = function (session) { 9 | 10 | function onevent(args) {} 11 | function addThree(args) { 12 | return args[0] + args[1] + args[2]; 13 | } 14 | 15 | session.register('com.myapp.addThree', addThree) 16 | session.subscribe('com.myapp.event', onevent); 17 | 18 | }; 19 | 20 | connection.onclose = function(e) { 21 | console.error(e) 22 | } 23 | 24 | connection.open(); 25 | -------------------------------------------------------------------------------- /release/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | /** 6 | * 7 | * 8 | * @export 9 | * @returns {number} 10 | */ 11 | function makeID() { 12 | return Math.floor(Math.random() * 9007199254740992); // 2^53 13 | } 14 | exports.makeID = makeID; 15 | /** 16 | * 17 | * 18 | * @export 19 | * @param {string} realm 20 | * @returns {boolean} 21 | */ 22 | function isValidRealm(realm) { 23 | const PATTERN = /^([^\s\.#]+\.)*([^\s\.#]+)$/; 24 | if (!realm || !PATTERN.test(realm) || realm.indexOf('wamp.') === 0) { 25 | return false; 26 | } 27 | return true; 28 | } 29 | exports.isValidRealm = isValidRealm; -------------------------------------------------------------------------------- /test/all/server.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import Server from '../../src/server'; 3 | 4 | describe('Server', () => { 5 | 6 | it('Should create a server for a single realm', () => { 7 | const server = new Server({ 8 | port: 7896, 9 | realms: 'com.some.server', 10 | }); 11 | expect(server).to.be.instanceOf(Server); 12 | server.close(); 13 | }); 14 | 15 | it('Should create a server for a multiple realms', () => { 16 | const server = new Server({ 17 | port: 7897, 18 | realms: ['com.some.server1', 'com.some.server2'], 19 | }); 20 | expect(server).to.be.instanceOf(Server); 21 | server.close(); 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /release/transactions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | const Debug = require("debug"); 6 | const DEBUG = Debug('wamp:transactions'); 7 | const transactions = new Map(); 8 | /** 9 | * 10 | * 11 | * @class Transaction 12 | */ 13 | class Transaction { 14 | /** 15 | * 16 | * 17 | * @static 18 | * @param {number} id 19 | * @param {number} sessionID 20 | */ 21 | static add(id, sessionID) { 22 | DEBUG('setting transaction with ID: %s for sessionID: %s', id, sessionID); 23 | transactions.set(id, sessionID); 24 | } 25 | /** 26 | * 27 | * 28 | * @static 29 | * @param {number} id 30 | * @returns {number} 31 | */ 32 | static get(id) { 33 | DEBUG('getting transaction with ID: %s', id); 34 | return transactions.get(id); 35 | } 36 | /** 37 | * 38 | * 39 | * @static 40 | * @param {number} id 41 | */ 42 | static delete(id) { 43 | DEBUG('deleting transaction with ID: %s', id); 44 | transactions.delete(id); 45 | } 46 | } 47 | exports.default = Transaction; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ivaylo Ivanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /release/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | const Debug = require("debug"); 6 | const ws_1 = require("ws"); 7 | const session_manager_1 = require("./session-manager"); 8 | const DEBUG = Debug('wamp:server'); 9 | /** 10 | * 11 | * 12 | * @class Server 13 | */ 14 | class Server { 15 | /** 16 | * Creates an instance of Server. 17 | * 18 | * @param {OptionsInterface} options 19 | */ 20 | constructor(options) { 21 | this.options = options; 22 | const REALMS = options.realms; 23 | this.port = options.port; 24 | this.wss = new ws_1.Server({ 25 | port: this.port 26 | }); 27 | session_manager_1.default.registerRealms(Array.isArray(REALMS) ? REALMS : [REALMS]); 28 | this.listen(); 29 | } 30 | /** 31 | * 32 | */ 33 | close() { 34 | this.wss.close(); 35 | } 36 | /** 37 | * 38 | * 39 | * @private 40 | */ 41 | listen() { 42 | DEBUG('listening on port %s', this.port); 43 | this.wss.on('connection', session_manager_1.default.createSession); 44 | } 45 | } 46 | exports.default = Server; -------------------------------------------------------------------------------- /test/all/message.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { SocketMessageInterface } from '../../src/interfaces'; 3 | import Message from '../../src/message'; 4 | 5 | describe('Message', () => { 6 | 7 | it('Should get parsed message', () => { 8 | const ar: any[] = [ 9 | 1, 10 | 'com.some.realm', 11 | {}, 12 | ]; 13 | const msg: string = JSON.stringify(ar); 14 | const message: SocketMessageInterface = new Message(msg).getMessage(); 15 | 16 | expect(message).to.be.a('object'); 17 | expect(message).to.has.property('type'); 18 | expect(message).to.has.property('id'); 19 | expect(message).to.has.property('incoming'); 20 | expect(message.incoming).to.be.a('array'); 21 | }); 22 | 23 | it('Should not fail if the Message is init. with an empty string', () => { 24 | const message: SocketMessageInterface = new Message('').getMessage(); 25 | 26 | expect(message).to.be.a('object'); 27 | expect(message).to.has.property('type'); 28 | expect(message).to.has.property('id'); 29 | expect(message).to.has.property('incoming'); 30 | expect(message.incoming).to.be.a('array'); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /src/protocols.ts: -------------------------------------------------------------------------------- 1 | export const incomingChannel = { 2 | 1: 'HELLO', 3 | 2: 'WELCOME', 4 | 3: 'ABORT', 5 | 4: 'CHALLENGE', 6 | 5: 'AUTHENTICATE', 7 | 6: 'GOODBYE', 8 | 7: 'HEARTBEAT', 9 | 8: 'ERROR', 10 | 16: 'PUBLISH', 11 | 17: 'PUBLISHED', 12 | 32: 'SUBSCRIBE', 13 | 33: 'SUBSCRIBED', 14 | 34: 'UNSUBSCRIBE', 15 | 35: 'UNSUBSCRIBED', 16 | 36: 'EVENT', 17 | 48: 'CALL', 18 | 49: 'CANCEL', 19 | 50: 'RESULT', 20 | 64: 'REGISTER', 21 | 65: 'REGISTERED', 22 | 66: 'UNREGISTER', 23 | 67: 'UNREGISTERED', 24 | 68: 'INVOCATION', 25 | 69: 'INTERRUPT', 26 | 70: 'YIELD', 27 | }; 28 | 29 | export const outgoingChannel = { 30 | ABORT: 3, 31 | AUTHENTICATE: 5, 32 | CALL: 48, 33 | CANCEL: 49, 34 | CHALLENGE: 4, 35 | ERROR: 8, 36 | EVENT: 36, 37 | GOODBYE: 6, 38 | HEARTBEAT: 7, 39 | HELLO: 1, 40 | INTERRUPT: 69, 41 | INVOCATION: 68, 42 | PUBLISH: 16, 43 | PUBLISHED: 17, 44 | REGISTER: 64, 45 | REGISTERED: 65, 46 | RESULT: 50, 47 | SUBSCRIBE: 32, 48 | SUBSCRIBED: 33, 49 | UNREGISTER: 66, 50 | UNREGISTERED: 67, 51 | UNSUBSCRIBE: 34, 52 | UNSUBSCRIBED: 35, 53 | WELCOME: 2, 54 | YIELD: 70, 55 | }; 56 | -------------------------------------------------------------------------------- /src/transactions.ts: -------------------------------------------------------------------------------- 1 | import * as Debug from 'debug'; 2 | import { 3 | MapInterface, 4 | } from './interfaces'; 5 | 6 | const DEBUG = Debug('wamp:transactions'); 7 | const transactions: MapInterface = new Map(); 8 | 9 | /** 10 | * 11 | * 12 | * @class Transaction 13 | */ 14 | class Transaction { 15 | 16 | /** 17 | * 18 | * 19 | * @static 20 | * @param {number} id 21 | * @param {number} sessionID 22 | */ 23 | public static add(id: number, sessionID: number): void { 24 | DEBUG('setting transaction with ID: %s for sessionID: %s', id, sessionID); 25 | transactions.set(id, sessionID); 26 | } 27 | 28 | /** 29 | * 30 | * 31 | * @static 32 | * @param {number} id 33 | * @returns {number} 34 | */ 35 | public static get(id: number): number { 36 | DEBUG('getting transaction with ID: %s', id); 37 | return transactions.get(id); 38 | } 39 | 40 | /** 41 | * 42 | * 43 | * @static 44 | * @param {number} id 45 | */ 46 | public static delete(id: number): void { 47 | DEBUG('deleting transaction with ID: %s', id); 48 | transactions.delete(id); 49 | } 50 | 51 | } 52 | 53 | export default Transaction; 54 | -------------------------------------------------------------------------------- /release/protocols.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | exports.incomingChannel = { 6 | 1: 'HELLO', 7 | 2: 'WELCOME', 8 | 3: 'ABORT', 9 | 4: 'CHALLENGE', 10 | 5: 'AUTHENTICATE', 11 | 6: 'GOODBYE', 12 | 7: 'HEARTBEAT', 13 | 8: 'ERROR', 14 | 16: 'PUBLISH', 15 | 17: 'PUBLISHED', 16 | 32: 'SUBSCRIBE', 17 | 33: 'SUBSCRIBED', 18 | 34: 'UNSUBSCRIBE', 19 | 35: 'UNSUBSCRIBED', 20 | 36: 'EVENT', 21 | 48: 'CALL', 22 | 49: 'CANCEL', 23 | 50: 'RESULT', 24 | 64: 'REGISTER', 25 | 65: 'REGISTERED', 26 | 66: 'UNREGISTER', 27 | 67: 'UNREGISTERED', 28 | 68: 'INVOCATION', 29 | 69: 'INTERRUPT', 30 | 70: 'YIELD', 31 | }; 32 | exports.outgoingChannel = { 33 | ABORT: 3, 34 | AUTHENTICATE: 5, 35 | CALL: 48, 36 | CANCEL: 49, 37 | CHALLENGE: 4, 38 | ERROR: 8, 39 | EVENT: 36, 40 | GOODBYE: 6, 41 | HEARTBEAT: 7, 42 | HELLO: 1, 43 | INTERRUPT: 69, 44 | INVOCATION: 68, 45 | PUBLISH: 16, 46 | PUBLISHED: 17, 47 | REGISTER: 64, 48 | REGISTERED: 65, 49 | RESULT: 50, 50 | SUBSCRIBE: 32, 51 | SUBSCRIBED: 33, 52 | UNREGISTER: 66, 53 | UNREGISTERED: 67, 54 | UNSUBSCRIBE: 34, 55 | UNSUBSCRIBED: 35, 56 | WELCOME: 2, 57 | YIELD: 70, 58 | }; -------------------------------------------------------------------------------- /test/all/transaction.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import Transactions from '../../src/transactions'; 3 | 4 | describe('Transaction', () => { 5 | 6 | it('Should add a transaction', () => { 7 | const id: number = 123456; 8 | const sessionID: number = 654321; 9 | 10 | expect(Transactions.add(id, sessionID)).to.be.a('undefined'); 11 | }); 12 | 13 | it('Should get a transaction', () => { 14 | const id: number = 123456; 15 | const sessionID: number = Transactions.get(id); 16 | 17 | expect(sessionID).equal(654321); 18 | }); 19 | 20 | // tslint:disable-next-line:max-line-length 21 | it('Should get an undefined as a session id for non existing transaction', () => { 22 | const id: number = 99999999999999; 23 | const sessionID: number = Transactions.get(id); 24 | 25 | expect(sessionID).equal(undefined); 26 | }); 27 | 28 | it('Should delete an existing transaction', () => { 29 | const id: number = 123456; 30 | 31 | expect(Transactions.delete(id)).equal(undefined); 32 | expect(Transactions.get(id)).equal(undefined); 33 | }); 34 | 35 | it('Should not fail when trying to delete a non existing transaction', () => { 36 | const id: number = 0; 37 | 38 | expect(Transactions.delete(id)).equal(undefined); 39 | expect(Transactions.get(id)).equal(undefined); 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import * as Debug from 'debug'; 2 | import { Server as WsServer } from 'ws'; 3 | import { OptionsInterface } from './interfaces'; 4 | import SessionManager from './session-manager'; 5 | 6 | const DEBUG = Debug('wamp:server'); 7 | 8 | /** 9 | * 10 | * 11 | * @class Server 12 | */ 13 | class Server { 14 | 15 | /** 16 | * 17 | * 18 | * @private 19 | * @type {number} 20 | */ 21 | private port: number; 22 | /** 23 | * 24 | * 25 | * @private 26 | * @type {(any | WsServer)} 27 | */ 28 | private wss: any | WsServer; 29 | 30 | /** 31 | * Creates an instance of Server. 32 | * 33 | * @param {OptionsInterface} options 34 | */ 35 | constructor(private options: OptionsInterface) { 36 | const REALMS: string | string[] = options.realms; 37 | this.port = options.port; 38 | this.wss = new WsServer({ port: this.port }); 39 | SessionManager.registerRealms(Array.isArray(REALMS) ? REALMS : [REALMS]); 40 | this.listen(); 41 | } 42 | 43 | /** 44 | * 45 | */ 46 | public close(): void { 47 | this.wss.close(); 48 | } 49 | 50 | /** 51 | * 52 | * 53 | * @private 54 | */ 55 | private listen(): void { 56 | DEBUG('listening on port %s', this.port); 57 | this.wss.on('connection', SessionManager.createSession); 58 | } 59 | 60 | } 61 | 62 | export default Server; 63 | -------------------------------------------------------------------------------- /release/message.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | /** 6 | * 7 | * 8 | * @export 9 | * @class Message 10 | */ 11 | class Message { 12 | /** 13 | * Creates an instance of Message. 14 | * 15 | * @param {string} incomingMessage 16 | */ 17 | constructor(incomingMessage) { 18 | this.serialize(incomingMessage); 19 | } 20 | /** 21 | * 22 | * 23 | * @returns {SocketMessageInterface} 24 | */ 25 | getMessage() { 26 | return this.message; 27 | } 28 | /** 29 | * 30 | * 31 | * @private 32 | * @param {string} incomingMessage 33 | */ 34 | serialize(incomingMessage) { 35 | this.message = { 36 | incoming: this.parseMessage(incomingMessage), 37 | }; 38 | this.setMessageType(this.message); 39 | this.setMessageID(this.message); 40 | } 41 | /** 42 | * 43 | * 44 | * @private 45 | * @param {string} message 46 | * @returns {any[]} 47 | */ 48 | parseMessage(message) { 49 | try { 50 | return JSON.parse(message); 51 | } catch (e) { 52 | return []; 53 | } 54 | } 55 | /** 56 | * 57 | * 58 | * @private 59 | * @param {SocketMessageInterface} message 60 | */ 61 | setMessageType(message) { 62 | message.type = message.incoming[0]; 63 | } 64 | /** 65 | * 66 | * 67 | * @private 68 | * @param {SocketMessageInterface} message 69 | */ 70 | setMessageID(message) { 71 | message.id = message.incoming[1]; 72 | } 73 | } 74 | exports.default = Message; -------------------------------------------------------------------------------- /test/all/util.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | isValidRealm, 4 | makeID, 5 | } from '../../src/util'; 6 | 7 | describe('util makeID', () => { 8 | it('Should get ID', () => { 9 | const id = makeID(); 10 | expect(id).to.be.a('number'); 11 | }); 12 | it('Should have length of 15 or 16', () => { 13 | const id = makeID(); 14 | expect(String(id)).to.have.length.within(10, 16); 15 | }); 16 | }); 17 | 18 | describe('util isValidRealm', () => { 19 | it('Should return true for "com.example.inge" ', () => { 20 | // tslint:disable-next-line:no-unused-expression 21 | expect(isValidRealm('com.example.inge')).to.be.true; 22 | }); 23 | it('Should return true for "com.example.inge123" ', () => { 24 | // tslint:disable-next-line:no-unused-expression 25 | expect(isValidRealm('com.example.inge123')).to.be.true; 26 | }); 27 | it('Should return false for "com.example. inge" ', () => { 28 | // tslint:disable-next-line:no-unused-expression 29 | expect(isValidRealm('com.example. ing')).to.be.false; 30 | }); 31 | it('Should return false for "com.#example.inge" ', () => { 32 | // tslint:disable-next-line:no-unused-expression 33 | expect(isValidRealm('com.#example.inge')).to.be.false; 34 | }); 35 | it('Should return false for "wamp.example.inge" ', () => { 36 | // tslint:disable-next-line:no-unused-expression 37 | expect(isValidRealm('wamp.example.inge')).to.be.false; 38 | }); 39 | it('Should return false for " " ', () => { 40 | // tslint:disable-next-line:no-unused-expression 41 | expect(isValidRealm(' ')).to.be.false; 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/all/protocols.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { incomingChannel, outgoingChannel} from '../../src/protocols'; 3 | 4 | describe('protocols', () => { 5 | it('Should match incomingChannel', () => { 6 | const INCOMMING_CHANNEL = { 7 | 1: 'HELLO', 8 | 2: 'WELCOME', 9 | 3: 'ABORT', 10 | 4: 'CHALLENGE', 11 | 5: 'AUTHENTICATE', 12 | 6: 'GOODBYE', 13 | 7: 'HEARTBEAT', 14 | 8: 'ERROR', 15 | 16: 'PUBLISH', 16 | 17: 'PUBLISHED', 17 | 32: 'SUBSCRIBE', 18 | 33: 'SUBSCRIBED', 19 | 34: 'UNSUBSCRIBE', 20 | 35: 'UNSUBSCRIBED', 21 | 36: 'EVENT', 22 | 48: 'CALL', 23 | 49: 'CANCEL', 24 | 50: 'RESULT', 25 | 64: 'REGISTER', 26 | 65: 'REGISTERED', 27 | 66: 'UNREGISTER', 28 | 67: 'UNREGISTERED', 29 | 68: 'INVOCATION', 30 | 69: 'INTERRUPT', 31 | 70: 'YIELD', 32 | }; 33 | expect(INCOMMING_CHANNEL).deep.equal(incomingChannel); 34 | }); 35 | 36 | it('Should match outgoingChannel', () => { 37 | const OUTGOING_CHANNEL = { 38 | ABORT: 3, 39 | AUTHENTICATE: 5, 40 | CALL: 48, 41 | CANCEL: 49, 42 | CHALLENGE: 4, 43 | ERROR: 8, 44 | EVENT: 36, 45 | GOODBYE: 6, 46 | HEARTBEAT: 7, 47 | HELLO: 1, 48 | INTERRUPT: 69, 49 | INVOCATION: 68, 50 | PUBLISH: 16, 51 | PUBLISHED: 17, 52 | REGISTER: 64, 53 | REGISTERED: 65, 54 | RESULT: 50, 55 | SUBSCRIBE: 32, 56 | SUBSCRIBED: 33, 57 | UNREGISTER: 66, 58 | UNREGISTERED: 67, 59 | UNSUBSCRIBE: 34, 60 | UNSUBSCRIBED: 35, 61 | WELCOME: 2, 62 | YIELD: 70, 63 | }; 64 | expect(OUTGOING_CHANNEL).deep.equal(outgoingChannel); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/message.ts: -------------------------------------------------------------------------------- 1 | import { SocketMessageInterface } from './interfaces'; 2 | 3 | /** 4 | * 5 | * 6 | * @export 7 | * @class Message 8 | */ 9 | export default class Message { 10 | 11 | /** 12 | * 13 | * 14 | * @private 15 | * @type {SocketMessageInterface} 16 | */ 17 | private message: SocketMessageInterface; 18 | 19 | /** 20 | * Creates an instance of Message. 21 | * 22 | * @param {string} incomingMessage 23 | */ 24 | constructor(incomingMessage: string) { 25 | this.serialize(incomingMessage); 26 | } 27 | 28 | /** 29 | * 30 | * 31 | * @returns {SocketMessageInterface} 32 | */ 33 | public getMessage(): SocketMessageInterface { 34 | return this.message; 35 | } 36 | 37 | /** 38 | * 39 | * 40 | * @private 41 | * @param {string} incomingMessage 42 | */ 43 | private serialize(incomingMessage: string) { 44 | this.message = { 45 | incoming: this.parseMessage(incomingMessage), 46 | }; 47 | this.setMessageType(this.message); 48 | this.setMessageID(this.message); 49 | } 50 | 51 | /** 52 | * 53 | * 54 | * @private 55 | * @param {string} message 56 | * @returns {any[]} 57 | */ 58 | private parseMessage(message: string): any[] { 59 | try { 60 | return JSON.parse(message); 61 | } catch (e) { 62 | return []; 63 | } 64 | } 65 | 66 | /** 67 | * 68 | * 69 | * @private 70 | * @param {SocketMessageInterface} message 71 | */ 72 | private setMessageType(message: SocketMessageInterface): void { 73 | message.type = message.incoming[0]; 74 | } 75 | 76 | /** 77 | * 78 | * 79 | * @private 80 | * @param {SocketMessageInterface} message 81 | */ 82 | private setMessageID(message: SocketMessageInterface): void { 83 | message.id = message.incoming[1]; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /release/topics.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | const Debug = require("debug"); 6 | const DEBUG = Debug('wamp:topics'); 7 | const topics = {}; 8 | /** 9 | * 10 | * 11 | * @class Topics 12 | */ 13 | class Topics { 14 | /** 15 | * 16 | * 17 | * @static 18 | * @param {string} realm 19 | * @param {string} topic 20 | * @returns {TopicInterface} 21 | */ 22 | static get(realm, topic) { 23 | return topics[realm][topic]; 24 | } 25 | /** 26 | * 27 | * 28 | * @static 29 | * @param {string} realm 30 | */ 31 | static registerRealm(realm) { 32 | topics[realm] = {}; 33 | } 34 | /** 35 | * 36 | * 37 | * @static 38 | * @param {string} realm 39 | * @param {string} topic 40 | * @param {number} subscriptionID 41 | * @param {SessionInterface} session 42 | */ 43 | static subscribe(realm, topic, subscriptionID, session) { 44 | DEBUG('subscribing with id: %s, realm: %s, topic: %s, sessionID: %s', subscriptionID, realm, topic, session.getID()); 45 | if (!topics[realm][topic]) { 46 | const TOPIC = { 47 | sessions: [], 48 | subscriptionID, 49 | }; 50 | topics[realm][topic] = TOPIC; 51 | } 52 | topics[realm][topic].sessions.push(session); 53 | } 54 | /** 55 | * 56 | * 57 | * @static 58 | * @param {string} realm 59 | * @param {string} topic 60 | * @param {number} sessionID 61 | */ 62 | static unsubscribe(realm, topic, sessionID) { 63 | DEBUG('unsubscribing sessionID: %s for topic: %s', sessionID, topic); 64 | const SESSIONS = topics[realm][topic].sessions; 65 | const LENGTH = SESSIONS.length; 66 | for (let i = 0; i < LENGTH; i++) { 67 | if (SESSIONS[i].getID() === sessionID) { 68 | SESSIONS.splice(i, 1); 69 | break; 70 | } 71 | } 72 | } 73 | } 74 | exports.default = Topics; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let gulp = require('gulp'); 3 | let ts = require('gulp-typescript'); 4 | let beautify = require('gulp-jsbeautify'); 5 | let sourcemaps = require('gulp-sourcemaps'); 6 | let runSequence = require('run-sequence'); 7 | let clean = require('gulp-clean'); 8 | let options = require('./tsconfig.json'); 9 | let tsProject = ts.createProject('tsconfig.json'); 10 | 11 | gulp.task('build-dev', () => { 12 | let result = tsProject.src() 13 | .pipe(sourcemaps.init()) 14 | .pipe(ts(tsProject())); 15 | 16 | return result.js 17 | .pipe(sourcemaps.write('./', { 18 | includeContent: false, 19 | mapSources: (sourcePath) => { 20 | return '../../' + sourcePath; 21 | } 22 | })) 23 | .pipe(beautify({indent_size: 2})) 24 | .pipe(gulp.dest('bin')); 25 | }); 26 | 27 | gulp.task('build-prod', () => { 28 | return gulp.src(['./src/**/*.ts', 'typings/**/*.d.ts']) 29 | .pipe(ts( 30 | options.compilerOptions 31 | )) 32 | .pipe(beautify({indent_size: 2})) 33 | .pipe(gulp.dest('release')); 34 | }); 35 | 36 | gulp.task('build-tests', () => { 37 | let result = tsProject.src() 38 | .pipe(ts(tsProject(), ts.reporter.nullReporter())); 39 | 40 | return result.js 41 | .pipe(gulp.dest('./')); 42 | }); 43 | 44 | gulp.task('clean', () => { 45 | runSequence('clean-tests', 'clean-source'); 46 | }) 47 | 48 | gulp.task('clean-tests', () => { 49 | return gulp.src('./test/**/*.js', {read: false}) 50 | .pipe(clean()); 51 | }); 52 | 53 | gulp.task('clean-source', () => { 54 | return gulp.src('./src/**/*.js', {read: false}) 55 | .pipe(clean()); 56 | }); 57 | 58 | gulp.task('clean-release', () => { 59 | return gulp.src('./release/**/*.js', {read: false}) 60 | .pipe(clean()); 61 | }); 62 | 63 | gulp.task('release', () => { 64 | runSequence('clean-release', 'build-prod'); 65 | }); 66 | 67 | gulp.task('watch', ['build-dev'], () => { 68 | gulp.watch('./src/**/*.ts', () => gulp.start('build-dev')); 69 | }); 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wamp-server", 3 | "version": "0.0.9", 4 | "description": "WAMP Router Server", 5 | "main": "index.js", 6 | "author": "Ivaylo Ivanov ", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/ivaylopivanov/wamp-server" 10 | }, 11 | "license": "MIT", 12 | "keywords": [ 13 | "wamp", 14 | "server", 15 | "publish", 16 | "subscribe", 17 | "rpc", 18 | "router", 19 | "dealer", 20 | "broker", 21 | "typescript" 22 | ], 23 | "scripts": { 24 | "build-tests": "node_modules/.bin/gulp build-tests", 25 | "clean-tests": "node_modules/.bin/gulp clean", 26 | "dev": "node_modules/.bin/gulp watch", 27 | "coverage": "npm run build-tests && istanbul cover node_modules/.bin/_mocha --test ./test/**/*.js && npm run clean-tests", 28 | "coveralls": "npm run build-tests && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", 29 | "release": "node_modules/.bin/gulp release", 30 | "lint": "./node_modules/.bin/tslint --project tsconfig.json", 31 | "test": "npm run lint && npm run build-tests && node_modules/.bin/mocha --exit ./test/**/*.js && npm run clean-tests" 32 | }, 33 | "engines": { 34 | "node": ">= 7.5.0" 35 | }, 36 | "devDependencies": { 37 | "@types/autobahn": "0.9.40", 38 | "@types/chai": "4.1.6", 39 | "@types/debug": "0.0.31", 40 | "@types/mocha": "5.2.5", 41 | "@types/ws": "6.0.2", 42 | "autobahn": "18.3.2", 43 | "chai": "4.2.0", 44 | "coveralls": "3.0.2", 45 | "gulp": "3.9.1", 46 | "gulp-clean": "0.4.0", 47 | "gulp-jsbeautify": "0.1.1", 48 | "gulp-sourcemaps": "2.6.4", 49 | "gulp-typescript": "5.0.0-alpha.3", 50 | "istanbul": "0.4.5", 51 | "mocha": "5.2.0", 52 | "run-sequence": "2.2.1", 53 | "tslint": "5.11.0", 54 | "typescript": "3.1.1" 55 | }, 56 | "dependencies": { 57 | "debug": "4.0.1", 58 | "ws": "6.1.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /release/procedures.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | const Debug = require("debug"); 6 | const DEBUG = Debug('wamp:procedure'); 7 | const procedures = {}; 8 | class Procedures { 9 | /** 10 | * 11 | * 12 | * @static 13 | * @param {string} realm 14 | */ 15 | static registerRealm(realm) { 16 | procedures[realm] = {}; 17 | } 18 | /** 19 | * 20 | * 21 | * @static 22 | * @param {string} realm 23 | * @param {string} uri 24 | * @param {number} sessionID 25 | * @param {number} procedureID 26 | */ 27 | static add(realm, uri, sessionID, procedureID) { 28 | DEBUG('registering procedure %s', uri); 29 | DEBUG('procedure id: %s', procedureID); 30 | DEBUG('session id: %s', sessionID); 31 | const PROCEDURE = { 32 | procedureID, 33 | sessionID, 34 | uri, 35 | }; 36 | procedures[realm][uri] = PROCEDURE; 37 | } 38 | /** 39 | * 40 | * 41 | * @static 42 | * @param {string} realm 43 | * @param {string} uri 44 | * @returns {ProcedureInterface} 45 | */ 46 | static get(realm, uri) { 47 | DEBUG('getting %s: ', uri); 48 | return procedures[realm][uri]; 49 | } 50 | /** 51 | * 52 | * 53 | * @static 54 | * @param {string} realm 55 | * @param {number} id 56 | * @returns {ProcedureInterface} 57 | */ 58 | static getByID(realm, id) { 59 | const KEYS = Object.keys(procedures[realm]); 60 | const LENGTH = KEYS.length; 61 | for (let i = 0; i < LENGTH; i++) { 62 | const PROCEDURE = procedures[realm][KEYS[i]]; 63 | if (PROCEDURE.procedureID === id) { 64 | DEBUG('getByID id: %s, uri: %s', PROCEDURE.procedureID, PROCEDURE.uri); 65 | return PROCEDURE; 66 | } 67 | } 68 | } 69 | /** 70 | * 71 | * 72 | * @static 73 | * @param {string} realm 74 | * @param {string} uri 75 | */ 76 | static remove(realm, uri) { 77 | DEBUG('removing %s: ', uri); 78 | procedures[realm][uri] = undefined; 79 | } 80 | /** 81 | * 82 | * 83 | * @static 84 | * @param {string} realm 85 | * @param {string} uri 86 | * @returns {boolean} 87 | */ 88 | static canAdd(realm, uri) { 89 | return Procedures.get(realm, uri) ? false : true; 90 | } 91 | } 92 | exports.default = Procedures; -------------------------------------------------------------------------------- /src/router.ts: -------------------------------------------------------------------------------- 1 | import * as Debug from 'debug'; 2 | import Handlers from './handlers'; 3 | import { 4 | SessionInterface, 5 | SocketMessageInterface, 6 | } from './interfaces'; 7 | import { incomingChannel, outgoingChannel } from './protocols'; 8 | 9 | const DEBUG = Debug('wamp:router'); 10 | 11 | /** 12 | * 13 | * 14 | * @export 15 | * @class Router 16 | */ 17 | export default class Router { 18 | 19 | /** 20 | * 21 | * 22 | * @static 23 | * @param {SessionInterface} session 24 | * @param {SocketMessageInterface} message 25 | * @returns {string} 26 | */ 27 | public static dispatch(session: SessionInterface, 28 | message: SocketMessageInterface): string { 29 | DEBUG('switching: %s, %s', message.type, incomingChannel[message.type]); 30 | if (!session.getRealm() && message.type !== outgoingChannel.HELLO) { 31 | Handlers.abort(session); 32 | return incomingChannel[3]; 33 | } 34 | switch (message.type) { 35 | case outgoingChannel.CALL: 36 | Handlers.call(session, message); 37 | return incomingChannel[message.type]; 38 | case outgoingChannel.ERROR: 39 | Handlers.error(session, message); 40 | return incomingChannel[message.type]; 41 | case outgoingChannel.GOODBYE: 42 | Handlers.goodbye(session, message); 43 | return incomingChannel[message.type]; 44 | case outgoingChannel.HELLO: 45 | Handlers.hello(session, message); 46 | return incomingChannel[message.type]; 47 | case outgoingChannel.PUBLISH: 48 | Handlers.publish(session, message); 49 | return incomingChannel[message.type]; 50 | case outgoingChannel.REGISTER: 51 | Handlers.register(session, message); 52 | return incomingChannel[message.type]; 53 | case outgoingChannel.SUBSCRIBE: 54 | Handlers.subscribe(session, message); 55 | return incomingChannel[message.type]; 56 | case outgoingChannel.UNREGISTER: 57 | Handlers.unregister(session, message); 58 | return incomingChannel[message.type]; 59 | case outgoingChannel.UNSUBSCRIBE: 60 | Handlers.unsubscribe(session, message); 61 | return incomingChannel[message.type]; 62 | case outgoingChannel.YIELD: 63 | Handlers.yield(session, message); 64 | return incomingChannel[message.type]; 65 | default: 66 | Handlers.goodbye(session, message); 67 | return incomingChannel[6]; 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /test/all/topics.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | SessionInterface, 4 | TopicInterface, 5 | } from '../../src/interfaces'; 6 | import Session from '../../src/session'; 7 | import Topics from '../../src/topics'; 8 | import Request from '../mocks/request'; 9 | import Socket from '../mocks/socket'; 10 | 11 | describe('Topics', () => { 12 | 13 | it('Should register realm', () => { 14 | const realm = 'com.some.thing'; 15 | expect(Topics.registerRealm(realm)).to.be.a('undefined'); 16 | }); 17 | 18 | it('Should subscribe for topic', () => { 19 | const REALM = 'com.some.thing'; 20 | const TOPIC = 'my.event'; 21 | const SUB_ID = 123456789012345; 22 | const IP = new Request().connection.remoteAddress; 23 | 24 | const SESSION: SessionInterface = new Session(new Socket(), IP); 25 | const EXPECTED = Topics.subscribe(REALM, TOPIC, SUB_ID, SESSION); 26 | 27 | expect(EXPECTED).to.be.a('undefined'); 28 | }); 29 | 30 | it('Should get subscription', () => { 31 | const realm = 'com.some.thing'; 32 | const topic = 'my.event'; 33 | 34 | const subscription: TopicInterface = Topics.get(realm, topic); 35 | 36 | expect(subscription).to.has.property('subscriptionID'); 37 | expect(subscription).to.has.property('sessions'); 38 | expect(subscription.sessions).to.be.a('array'); 39 | expect(subscription.sessions).to.has.lengthOf(1); 40 | }); 41 | 42 | it('Should not fail if try to unsubscribe not existing session', () => { 43 | const realm = 'com.some.thing'; 44 | const topic = 'my.event'; 45 | 46 | expect(Topics.unsubscribe(realm, topic, undefined)).to.be.a('undefined'); 47 | }); 48 | 49 | it('Should unsubscribe', () => { 50 | const realm = 'com.some.thing'; 51 | const topic = 'my.event'; 52 | 53 | const subscriptionBefore: TopicInterface = Topics.get(realm, topic); 54 | const session: SessionInterface = subscriptionBefore.sessions[0]; 55 | // tslint:disable-next-line:max-line-length 56 | expect(Topics.unsubscribe(realm, topic, session.getID())).to.be.a('undefined'); 57 | 58 | const subscriptionAfter: TopicInterface = Topics.get(realm, topic); 59 | expect(subscriptionAfter).to.has.property('subscriptionID'); 60 | expect(subscriptionAfter).to.has.property('sessions'); 61 | expect(subscriptionAfter.sessions).to.be.a('array'); 62 | expect(subscriptionAfter.sessions).to.has.lengthOf(0); 63 | }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /src/topics.ts: -------------------------------------------------------------------------------- 1 | import * as Debug from 'debug'; 2 | import { 3 | SessionInterface, 4 | TopicInterface, 5 | } from './interfaces'; 6 | 7 | const DEBUG = Debug('wamp:topics'); 8 | const topics = {}; 9 | 10 | /** 11 | * 12 | * 13 | * @class Topics 14 | */ 15 | class Topics { 16 | 17 | /** 18 | * 19 | * 20 | * @static 21 | * @param {string} realm 22 | * @param {string} topic 23 | * @returns {TopicInterface} 24 | */ 25 | public static get(realm: string, topic: string): TopicInterface { 26 | return topics[realm][topic]; 27 | } 28 | 29 | /** 30 | * 31 | * 32 | * @static 33 | * @param {string} realm 34 | */ 35 | public static registerRealm(realm: string): void { 36 | topics[realm] = {}; 37 | } 38 | 39 | /** 40 | * 41 | * 42 | * @static 43 | * @param {string} realm 44 | * @param {string} topic 45 | * @param {number} subscriptionID 46 | * @param {SessionInterface} session 47 | */ 48 | public static subscribe(realm: string, 49 | topic: string, 50 | subscriptionID: number, 51 | session: SessionInterface): void { 52 | DEBUG('subscribing with id: %s, realm: %s, topic: %s, sessionID: %s', 53 | subscriptionID, 54 | realm, 55 | topic, 56 | session.getID()); 57 | if (!topics[realm][topic]) { 58 | const TOPIC: TopicInterface = { 59 | sessions: [], 60 | subscriptionID, 61 | }; 62 | topics[realm][topic] = TOPIC; 63 | } 64 | topics[realm][topic].sessions.push(session); 65 | } 66 | 67 | /** 68 | * 69 | * 70 | * @static 71 | * @param {string} realm 72 | * @param {string} topic 73 | * @param {number} sessionID 74 | */ 75 | public static unsubscribe(realm: string, 76 | topic: string, 77 | sessionID: number): void { 78 | DEBUG('unsubscribing sessionID: %s for topic: %s', sessionID, topic); 79 | const SESSIONS = topics[realm][topic].sessions; 80 | const LENGTH = SESSIONS.length; 81 | for (let i = 0; i < LENGTH; i++) { 82 | if (SESSIONS[i].getID() === sessionID) { 83 | SESSIONS.splice(i, 1); 84 | break; 85 | } 86 | } 87 | } 88 | 89 | } 90 | 91 | export default Topics; 92 | -------------------------------------------------------------------------------- /test/all/procedures.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { ProcedureInterface } from '../../src/interfaces'; 3 | import Procedures from '../../src/procedures'; 4 | 5 | describe('Procedures', () => { 6 | 7 | it('Should register realm', () => { 8 | const realm = 'test'; 9 | expect(Procedures.registerRealm(realm)).to.be.a('undefined'); 10 | }); 11 | 12 | it('Should get true on canAdd for non existing procedure', () => { 13 | const realm = 'test'; 14 | const uri = 'com.uri.non-test'; 15 | // tslint:disable-next-line:no-unused-expression 16 | expect(Procedures.canAdd(realm, uri)).to.be.true; 17 | }); 18 | 19 | it('Should add procedure', () => { 20 | const realm = 'test'; 21 | const uri = 'com.uri.test'; 22 | const sessionID = 999999999999998; 23 | const procedureID = 999999999999999; 24 | expect(Procedures.add(realm, uri, sessionID, procedureID)) 25 | .to.be.a('undefined'); 26 | }); 27 | 28 | it('Should get by URI', () => { 29 | const realm = 'test'; 30 | const uri = 'com.uri.test'; 31 | const expected: ProcedureInterface = { 32 | procedureID: 999999999999999, 33 | sessionID: 999999999999998, 34 | uri, 35 | }; 36 | expect(Procedures.get(realm, uri)).deep.equal(expected); 37 | }); 38 | 39 | it('Should get undefined for a non existing URI', () => { 40 | const realm = 'test'; 41 | const uri = 'com.uri.not-existing'; 42 | expect(Procedures.get(realm, uri)).to.be.a('undefined'); 43 | }); 44 | 45 | it('Should get false on canAdd for an existing procedure', () => { 46 | const realm = 'test'; 47 | const uri = 'com.uri.test'; 48 | // tslint:disable-next-line:no-unused-expression 49 | expect(Procedures.canAdd(realm, uri)).to.be.false; 50 | }); 51 | 52 | it('Should get by ID', () => { 53 | const realm = 'test'; 54 | const id = 999999999999999; 55 | const expected: ProcedureInterface = { 56 | procedureID: id, 57 | sessionID: id - 1, 58 | uri: 'com.uri.test', 59 | }; 60 | expect(Procedures.getByID(realm, id)).deep.equal(expected); 61 | }); 62 | 63 | it('Should not fail when trying to get for non existing ID', () => { 64 | const realm = 'test'; 65 | const id = 32131231231; 66 | expect(Procedures.getByID(realm, id)).to.be.a('undefined'); 67 | }); 68 | 69 | it('Should delete procedure', () => { 70 | const realm = 'test'; 71 | const uri = 'com.uri.test'; 72 | expect(Procedures.remove(realm, uri)).to.be.a('undefined'); 73 | // tslint:disable-next-line:no-unused-expression 74 | expect(Procedures.canAdd(realm, uri)).to.be.true; 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /src/procedures.ts: -------------------------------------------------------------------------------- 1 | import * as Debug from 'debug'; 2 | import { 3 | ProcedureInterface, 4 | } from './interfaces'; 5 | 6 | const DEBUG = Debug('wamp:procedure'); 7 | const procedures = {}; 8 | 9 | class Procedures { 10 | 11 | /** 12 | * 13 | * 14 | * @static 15 | * @param {string} realm 16 | */ 17 | public static registerRealm(realm: string) { 18 | procedures[realm] = {}; 19 | } 20 | 21 | /** 22 | * 23 | * 24 | * @static 25 | * @param {string} realm 26 | * @param {string} uri 27 | * @param {number} sessionID 28 | * @param {number} procedureID 29 | */ 30 | public static add(realm: string, 31 | uri: string, 32 | sessionID: number, 33 | procedureID: number): void { 34 | DEBUG('registering procedure %s', uri); 35 | DEBUG('procedure id: %s', procedureID); 36 | DEBUG('session id: %s', sessionID); 37 | const PROCEDURE: ProcedureInterface = { 38 | procedureID, 39 | sessionID, 40 | uri, 41 | }; 42 | procedures[realm][uri] = PROCEDURE; 43 | } 44 | 45 | /** 46 | * 47 | * 48 | * @static 49 | * @param {string} realm 50 | * @param {string} uri 51 | * @returns {ProcedureInterface} 52 | */ 53 | public static get(realm: string, uri: string): ProcedureInterface { 54 | DEBUG('getting %s: ', uri); 55 | return procedures[realm][uri]; 56 | } 57 | 58 | /** 59 | * 60 | * 61 | * @static 62 | * @param {string} realm 63 | * @param {number} id 64 | * @returns {ProcedureInterface} 65 | */ 66 | public static getByID(realm: string, id: number): ProcedureInterface { 67 | const KEYS = Object.keys(procedures[realm]); 68 | const LENGTH = KEYS.length; 69 | for (let i = 0; i < LENGTH; i++) { 70 | const PROCEDURE: ProcedureInterface = procedures[realm][KEYS[i]]; 71 | if (PROCEDURE.procedureID === id) { 72 | DEBUG('getByID id: %s, uri: %s', PROCEDURE.procedureID, PROCEDURE.uri); 73 | return PROCEDURE; 74 | } 75 | } 76 | } 77 | 78 | /** 79 | * 80 | * 81 | * @static 82 | * @param {string} realm 83 | * @param {string} uri 84 | */ 85 | public static remove(realm: string, uri: string): void { 86 | DEBUG('removing %s: ', uri); 87 | procedures[realm][uri] = undefined; 88 | } 89 | 90 | /** 91 | * 92 | * 93 | * @static 94 | * @param {string} realm 95 | * @param {string} uri 96 | * @returns {boolean} 97 | */ 98 | public static canAdd(realm: string, uri: string): boolean { 99 | return Procedures.get(realm, uri) ? false : true; 100 | } 101 | 102 | } 103 | 104 | export default Procedures; 105 | -------------------------------------------------------------------------------- /release/router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | const Debug = require("debug"); 6 | const handlers_1 = require("./handlers"); 7 | const protocols_1 = require("./protocols"); 8 | const DEBUG = Debug('wamp:router'); 9 | /** 10 | * 11 | * 12 | * @export 13 | * @class Router 14 | */ 15 | class Router { 16 | /** 17 | * 18 | * 19 | * @static 20 | * @param {SessionInterface} session 21 | * @param {SocketMessageInterface} message 22 | * @returns {string} 23 | */ 24 | static dispatch(session, message) { 25 | DEBUG('switching: %s, %s', message.type, protocols_1.incomingChannel[message.type]); 26 | if (!session.getRealm() && message.type !== protocols_1.outgoingChannel.HELLO) { 27 | handlers_1.default.abort(session); 28 | return protocols_1.incomingChannel[3]; 29 | } 30 | switch (message.type) { 31 | case protocols_1.outgoingChannel.CALL: 32 | handlers_1.default.call(session, message); 33 | return protocols_1.incomingChannel[message.type]; 34 | case protocols_1.outgoingChannel.ERROR: 35 | handlers_1.default.error(session, message); 36 | return protocols_1.incomingChannel[message.type]; 37 | case protocols_1.outgoingChannel.GOODBYE: 38 | handlers_1.default.goodbye(session, message); 39 | return protocols_1.incomingChannel[message.type]; 40 | case protocols_1.outgoingChannel.HELLO: 41 | handlers_1.default.hello(session, message); 42 | return protocols_1.incomingChannel[message.type]; 43 | case protocols_1.outgoingChannel.PUBLISH: 44 | handlers_1.default.publish(session, message); 45 | return protocols_1.incomingChannel[message.type]; 46 | case protocols_1.outgoingChannel.REGISTER: 47 | handlers_1.default.register(session, message); 48 | return protocols_1.incomingChannel[message.type]; 49 | case protocols_1.outgoingChannel.SUBSCRIBE: 50 | handlers_1.default.subscribe(session, message); 51 | return protocols_1.incomingChannel[message.type]; 52 | case protocols_1.outgoingChannel.UNREGISTER: 53 | handlers_1.default.unregister(session, message); 54 | return protocols_1.incomingChannel[message.type]; 55 | case protocols_1.outgoingChannel.UNSUBSCRIBE: 56 | handlers_1.default.unsubscribe(session, message); 57 | return protocols_1.incomingChannel[message.type]; 58 | case protocols_1.outgoingChannel.YIELD: 59 | handlers_1.default.yield(session, message); 60 | return protocols_1.incomingChannel[message.type]; 61 | default: 62 | handlers_1.default.goodbye(session, message); 63 | return protocols_1.incomingChannel[6]; 64 | } 65 | } 66 | } 67 | exports.default = Router; -------------------------------------------------------------------------------- /benchmark/stress-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let autobahn = require('autobahn'); 3 | let join = require('path').join; 4 | let fork = require('child_process').fork 5 | let config = { 6 | calls: 500, // number of clients 7 | running: 0, 8 | wamp: { 9 | url: 'ws://127.0.0.1:8000/', 10 | realm: 'com.example.inge' 11 | } 12 | }; 13 | let cycle; 14 | 15 | fork(join(__dirname, './server/index.js')); 16 | fork(join(__dirname, './clients/one/index.js')); 17 | fork(join(__dirname, './clients/two/index.js')); 18 | 19 | setTimeout(() => { 20 | start(); 21 | }, 100); 22 | 23 | function timer(name) { 24 | let start = new Date(); 25 | return { 26 | stop: function () { 27 | let end = new Date(); 28 | let time = end.getTime() - start.getTime(); 29 | console.log('Timer:', name, 'finished in', time, 'ms'); 30 | } 31 | } 32 | } 33 | 34 | function start() { 35 | cycle = timer('cycle'); 36 | for (let index = 0; index < config.calls; index++) { 37 | config.running++; 38 | create(index); 39 | } 40 | } 41 | 42 | function leave() { 43 | if (!--config.running) { 44 | cycle.stop(); 45 | start(); 46 | } 47 | } 48 | 49 | function onevent(e) { } 50 | 51 | function create(index) { 52 | let connection = new autobahn.Connection(config.wamp); 53 | 54 | connection.onopen = function (session) { 55 | 56 | if (index % 20 === 0) { 57 | session.call('com.myapp.addThree', [2, 3, 4]) 58 | .then(r => console.assert(r === 9)) 59 | .catch(e => console.log(e)); 60 | } else { 61 | session.call('com.myapp.addTwo', [2, 3]) 62 | .then(r => console.assert(r === 5)) 63 | .catch(e => console.log(e)); 64 | } 65 | 66 | if (index % 2 === 0) { 67 | let st1 = timer(index) 68 | session.subscribe('com.myapp.event', onevent) 69 | .then(r => { 70 | let args = [['Hello, world!'], { from: 'pubsub' }, { acknowledge: true }]; 71 | session.publish('com.myapp.event', ...args) 72 | .then(r => { 73 | connection.close(); 74 | st1.stop(); 75 | leave(); 76 | }) 77 | }) 78 | .catch(e => { 79 | connection.close(); 80 | st1.stop(); 81 | leave(); 82 | }); 83 | } else { 84 | let st2 = timer(index) 85 | let args = [['Hello, world!'], { from: 'pub' }, { acknowledge: true }] 86 | session.publish('com.myapp.event', ...args) 87 | .then(r => { 88 | connection.close(); 89 | st2.stop(); 90 | leave(); 91 | }) 92 | .catch(e => { 93 | connection.close(); 94 | st2.stop(); 95 | leave(); 96 | }); 97 | } 98 | }; 99 | connection.open(); 100 | } 101 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # WAMP Router Server 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Build Status][travis-image]][travis-url] 5 | [![Test coverage][coveralls-image]][coveralls-url] 6 | [![Greenkeeper badge](https://badges.greenkeeper.io/ivaylopivanov/wamp-server.svg)](https://greenkeeper.io/) 7 | 8 | # Implemented by following the [WAMP](https://tools.ietf.org/html/draft-oberstet-hybi-tavendo-wamp-02) standards. 9 | 10 | ## Compatible with [Autobahn JS](http://autobahn.ws/js/) 11 | 12 | ## Currently only the [Basic Profile](https://tools.ietf.org/html/draft-oberstet-hybi-tavendo-wamp-02#page-7) is implemented. 13 | - hello 14 | - welcome 15 | - abort 16 | - goodbye 17 | - error 18 | - publish 19 | - published 20 | - subscribe 21 | - subscribed 22 | - unsubscribe 23 | - unsubscribed 24 | - event 25 | - call 26 | - result 27 | - register 28 | - registered 29 | - unregister 30 | - unregistered 31 | - invocation 32 | - yield 33 | 34 | ## Why 35 | The other two node [implementations](https://wamp-proto.org/implementations.html#routers) have memory leaks. 36 | 37 | ## Note 38 | The current [source](https://github.com/ivaylopivanov/wamp-server/tree/master/src) is written in [Typescript](https://www.typescriptlang.org/) and the [release](https://github.com/ivaylopivanov/wamp-server/tree/master/release) is the compiled javascript version. Once [ES6 modules](http://www.ecma-international.org/ecma-262/6.0/#sec-imports) land in node, the only significant difference between the `src/` and the `release/` will be the types. At some point, the implementation may be moved to node completely. 39 | 40 | ## Installation 41 | 42 | ```bash 43 | $ npm install wamp-server 44 | ``` 45 | 46 | ## Usage 47 | 48 | ```js 49 | 'use strict'; 50 | const WAMP_SERVER = require('wamp-server'); 51 | const SERVER = new WAMP_SERVER({ 52 | port: 8000, 53 | realms: ['com.example.inge'], // array or string 54 | }); 55 | // to close the server - SERVER.close(); 56 | ``` 57 | 58 | ## Debugging 59 | 60 | For debugging you can use the `DEBUG` variable - `DEBUG=wamp:*` 61 | 62 | ## Contributing 63 | 64 | Any contribution will be highly appreciated 65 | 66 | ## Development 67 | 68 | ```bash 69 | $ npm install 70 | $ typings install 71 | $ npm run dev 72 | ``` 73 | 74 | ## Tests 75 | 76 | To run the test suite, first install the dependencies, then run `npm test`: 77 | 78 | ```bash 79 | $ npm install 80 | $ npm test 81 | ``` 82 | 83 | ## License 84 | 85 | [MIT](https://github.com/ivaylopivanov/wamp-server/blob/master/LICENSE) 86 | 87 | [npm-image]: https://badge.fury.io/js/wamp-server.svg 88 | [npm-url]: https://npmjs.org/package/wamp-server 89 | [travis-image]: https://travis-ci.org/ivaylopivanov/wamp-server.svg?branch=master 90 | [travis-url]: https://travis-ci.org/ivaylopivanov/wamp-server 91 | [coveralls-image]: https://coveralls.io/repos/ivaylopivanov/wamp-server/badge.svg 92 | [coveralls-url]: https://coveralls.io/r/ivaylopivanov/wamp-server 93 | -------------------------------------------------------------------------------- /release/session-manager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | const Debug = require("debug"); 6 | const errors_1 = require("./errors"); 7 | const procedures_1 = require("./procedures"); 8 | const session_1 = require("./session"); 9 | const topics_1 = require("./topics"); 10 | const util_1 = require("./util"); 11 | const DEBUG = Debug('wamp:session-manager'); 12 | const containers = new Map(); 13 | /** 14 | * 15 | * 16 | * @class SessionManager 17 | */ 18 | class SessionManager { 19 | /** 20 | * 21 | * 22 | * @static 23 | * @param {string} realm 24 | * @returns {boolean} 25 | */ 26 | static realmExists(realm) { 27 | return containers.get(realm) ? true : false; 28 | } 29 | /** 30 | * 31 | * 32 | * @static 33 | * @param {SocketInterface} socket 34 | */ 35 | static createSession(socket, req) { 36 | const IP = req.connection.remoteAddress; 37 | const SESSION = new session_1.default(socket, IP); 38 | DEBUG('creating session for ip: %s with id: %s', SESSION.getIP(), SESSION.getID()); 39 | } 40 | /** 41 | * 42 | * 43 | * @static 44 | * @param {string} realm 45 | * @param {number} id 46 | * @returns {SessionInterface} 47 | */ 48 | static getSession(realm, id) { 49 | DEBUG('getting session for id: %s', id); 50 | const SESSIONS = containers.get(realm); 51 | if (SESSIONS) { 52 | return SESSIONS.get(id); 53 | } 54 | } 55 | /** 56 | * 57 | * 58 | * @static 59 | * @param {string} realm 60 | * @param {SessionInterface} session 61 | */ 62 | static registerSession(realm, session) { 63 | DEBUG('registering session with id: %s for realm: %s', session.getID(), realm); 64 | containers.get(realm).set(session.getID(), session); 65 | DEBUG('number of sessions: %s', SessionManager.getSessionsAmount(realm)); 66 | } 67 | /** 68 | * 69 | * 70 | * @static 71 | * @param {string} realm 72 | * @param {number} id 73 | */ 74 | static removeSession(realm, id) { 75 | DEBUG('removing session: %s for realm: %s', id, realm); 76 | containers.get(realm).delete(id); 77 | } 78 | /** 79 | * 80 | * 81 | * @static 82 | * @param {string} realm 83 | * @returns {number} 84 | */ 85 | static getSessionsAmount(realm) { 86 | return containers.get(realm).size; 87 | } 88 | /** 89 | * 90 | * 91 | * @static 92 | * @param {string[]} realms 93 | */ 94 | static registerRealms(realms) { 95 | realms.forEach((realm) => { 96 | DEBUG(`registering realm: ${realm}`); 97 | if (util_1.isValidRealm(realm)) { 98 | containers.set(realm, new Map()); 99 | procedures_1.default.registerRealm(realm); 100 | topics_1.default.registerRealm(realm); 101 | } else { 102 | throw new Error(`${errors_1.default.uri}: "${realm}"`); 103 | } 104 | }); 105 | } 106 | } 107 | exports.default = SessionManager; -------------------------------------------------------------------------------- /test/all/session-manager.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import Session from '../../src/session'; 3 | import SessionManager from '../../src/session-manager'; 4 | import Request from '../mocks/request'; 5 | import Socket from '../mocks/socket'; 6 | 7 | let id: number; 8 | 9 | describe('SessionManager', () => { 10 | 11 | it('Should register a realms', () => { 12 | const realm = 'com.some.realm'; 13 | expect(SessionManager.registerRealms([realm])).to.be.a('undefined'); 14 | }); 15 | 16 | it('Should fail register a realm', () => { 17 | const realm = 'wamp.com.realm'; 18 | expect(() => { 19 | expect(SessionManager.registerRealms([realm])); 20 | }).to.throw('wamp.error.invalid_uri: "wamp.com.realm"'); 21 | }); 22 | 23 | it('Should return true that realm is registered', () => { 24 | const realm = 'com.some.realm'; 25 | // tslint:disable-next-line:no-unused-expression 26 | expect(SessionManager.realmExists(realm)).to.be.true; 27 | }); 28 | 29 | it('Should return false that realm is registered', () => { 30 | const realm = 'wamp.com.realm'; 31 | // tslint:disable-next-line:no-unused-expression 32 | expect(SessionManager.realmExists(realm)).to.be.false; 33 | }); 34 | 35 | it('Should return undefined for non existing session', () => { 36 | const realm = 'com.some.realm'; 37 | const ID = 0; 38 | expect(SessionManager.getSession(realm, ID)).to.be.a('undefined'); 39 | }); 40 | 41 | it('Should return 0 for the amount of registered sessions', () => { 42 | const realm = 'com.some.realm'; 43 | expect(SessionManager.getSessionsAmount(realm)).equal(0); 44 | }); 45 | 46 | it('Should create a session', () => { 47 | const RESULT = SessionManager.createSession(new Socket(), new Request()); 48 | expect(RESULT).to.be.a('undefined'); 49 | }); 50 | 51 | it('Should register a session', () => { 52 | const REALM = 'com.some.realm'; 53 | const IP = new Request().connection.remoteAddress; 54 | const SESSION = new Session(new Socket(), IP); 55 | id = SESSION.getID(); 56 | expect(SessionManager.registerSession(REALM, SESSION)).to.be.a('undefined'); 57 | }); 58 | 59 | it('Should return 1 for the amount of registered sessions', () => { 60 | const realm = 'com.some.realm'; 61 | expect(SessionManager.getSessionsAmount(realm)).equal(1); 62 | }); 63 | 64 | it('Should session by ID', () => { 65 | const realm = 'com.some.realm'; 66 | expect(SessionManager.getSession(realm, id)).to.be.instanceOf(Session); 67 | }); 68 | 69 | it('Should not fail to remove non existing session', () => { 70 | const realm = 'com.some.realm'; 71 | expect(SessionManager.removeSession(realm, 0)).to.be.a('undefined'); 72 | expect(SessionManager.getSessionsAmount(realm)).equal(1); 73 | }); 74 | 75 | it('Should remove session', () => { 76 | const realm = 'com.some.realm'; 77 | expect(SessionManager.removeSession(realm, id)).to.be.a('undefined'); 78 | expect(SessionManager.getSessionsAmount(realm)).equal(0); 79 | }); 80 | 81 | }); 82 | -------------------------------------------------------------------------------- /test/all/wamp.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from 'autobahn'; 2 | import { expect } from 'chai'; 3 | import Server from '../../src/server'; 4 | 5 | let server: Server; 6 | const finished: any = { 7 | ack: false, 8 | event: false, 9 | invocation: false, 10 | unregistered: false, 11 | unsubscribed: false, 12 | yield: false, 13 | }; 14 | 15 | describe('wamp', function() { 16 | 17 | this.timeout(3000); 18 | 19 | beforeEach(() => { 20 | server = new Server({ 21 | port: 8000, 22 | realms: 'com.test.autobahn', 23 | }); 24 | }); 25 | 26 | afterEach(() => server.close()); 27 | 28 | // tslint:disable-next-line:max-line-length 29 | it('Should create two clients, make RPC and pub / sub', (done: () => void) => { 30 | 31 | createClient((clientOne: any, closeClientOne: () => void) => { 32 | let subscription: any; 33 | let registration: any; 34 | 35 | const onevent = (args: any) => { 36 | finished.event = true; 37 | clientOne.unsubscribe(subscription) 38 | .then(() => { 39 | finished.unsubscribed = true; 40 | }); 41 | }; 42 | 43 | const add = (args: any) => { 44 | setTimeout(() => { 45 | clientOne.unregister(registration) 46 | .then(() => { 47 | finished.unregistered = true; 48 | closeClientOne(); 49 | }); 50 | }, 1); 51 | finished.invocation = true; 52 | return args[0] + args[1]; 53 | }; 54 | 55 | clientOne.subscribe('com.test.event', onevent) 56 | .then((res: any) => { 57 | subscription = res; 58 | }); 59 | 60 | clientOne.register('com.test.add', add) 61 | .then((res: any) => { 62 | registration = res; 63 | }); 64 | 65 | createClient((clientTwo: any, closeClientTwo: () => void) => { 66 | 67 | clientTwo.call('com.test.add', [2, 3]) 68 | .then((res: any) => { 69 | expect(res).equal(5); 70 | finished.yield = true; 71 | onFinish(done, closeClientTwo); 72 | }); 73 | 74 | clientTwo.publish('com.test.event', ['Hello!'], {}, {acknowledge: true}) 75 | .then(() => { 76 | finished.ack = true; 77 | onFinish(done, closeClientTwo); 78 | }); 79 | 80 | }); 81 | 82 | }); 83 | 84 | }); 85 | 86 | }); 87 | 88 | // tslint:disable-next-line:max-line-length 89 | const createClient = (callback: (session: any, closeSession: () => void) => void) => { 90 | const connection = new Connection({ 91 | realm: 'com.test.autobahn', 92 | url: 'ws://127.0.0.1:8000/', 93 | }); 94 | connection.onopen = (session: any) => { 95 | callback(session, () => connection.close()); 96 | }; 97 | connection.open(); 98 | }; 99 | 100 | const onFinish = (done: () => void, close: () => void) => { 101 | if (finished.ack && 102 | finished.event && 103 | finished.invocation && 104 | finished.yield && 105 | finished.unregistered && 106 | finished.unsubscribed) { 107 | close(); 108 | done(); 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /test/all/router.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { SessionInterface } from '../../src/interfaces'; 3 | import Message from '../../src/message'; 4 | import { incomingChannel } from '../../src/protocols'; 5 | import Router from '../../src/router'; 6 | import Session from '../../src/session'; 7 | import SessionManager from '../../src/session-manager'; 8 | import Request from '../mocks/request'; 9 | import Socket from '../mocks/socket'; 10 | 11 | const realm = 'com.some.realm'; 12 | let session: SessionInterface; 13 | const message = new Message(JSON.stringify([ 14 | 1, 15 | realm, 16 | {}, 17 | ])).getMessage(); 18 | 19 | describe('Router', () => { 20 | 21 | beforeEach(() => { 22 | SessionManager.registerRealms([realm]); 23 | SessionManager.createSession(new Socket(), new Request()); 24 | session = new Session(new Socket(), new Request().connection.remoteAddress); 25 | session.setRealm(realm); 26 | }); 27 | 28 | it('Should switch to abort', () => { 29 | session.setRealm(''); 30 | message.type = 48; 31 | const type = Router.dispatch(session, message); 32 | expect(type).equal(incomingChannel[3]); 33 | }); 34 | 35 | it('Should switch to call', () => { 36 | message.type = 48; 37 | const type = Router.dispatch(session, message); 38 | expect(type).equal(incomingChannel[message.type]); 39 | }); 40 | 41 | it('Should switch to goodbye', () => { 42 | message.type = 6; 43 | const type = Router.dispatch(session, message); 44 | expect(type).equal(incomingChannel[message.type]); 45 | }); 46 | 47 | it('Should switch to goodbye', () => { 48 | message.type = 100; 49 | const type = Router.dispatch(session, message); 50 | expect(type).equal(incomingChannel[6]); 51 | }); 52 | 53 | it('Should switch to hello', () => { 54 | message.type = 1; 55 | const type = Router.dispatch(session, message); 56 | expect(type).equal(incomingChannel[message.type]); 57 | }); 58 | 59 | it('Should switch to publish', () => { 60 | message.type = 16; 61 | const type = Router.dispatch(session, message); 62 | expect(type).equal(incomingChannel[message.type]); 63 | }); 64 | 65 | it('Should switch to register', () => { 66 | message.type = 64; 67 | const type = Router.dispatch(session, message); 68 | expect(type).equal(incomingChannel[message.type]); 69 | }); 70 | 71 | it('Should switch to subscribe', () => { 72 | message.type = 32; 73 | message.incoming[3] = 'my.event'; 74 | const type = Router.dispatch(session, message); 75 | expect(type).equal(incomingChannel[message.type]); 76 | }); 77 | 78 | it('Should switch to unregister', () => { 79 | message.type = 66; 80 | const type = Router.dispatch(session, message); 81 | expect(type).equal(incomingChannel[message.type]); 82 | }); 83 | 84 | it('Should switch to unsubscribe', () => { 85 | message.type = 34; 86 | message.incoming[3] = 'my.event'; 87 | const type = Router.dispatch(session, message); 88 | expect(type).equal(incomingChannel[message.type]); 89 | }); 90 | 91 | it('Should switch to yield', () => { 92 | message.type = 70; 93 | const type = Router.dispatch(session, message); 94 | expect(type).equal(incomingChannel[message.type]); 95 | }); 96 | 97 | }); 98 | -------------------------------------------------------------------------------- /src/session-manager.ts: -------------------------------------------------------------------------------- 1 | import * as Debug from 'debug'; 2 | import errors from './errors'; 3 | import { 4 | MapInterface, 5 | SessionInterface, 6 | SocketInterface, 7 | } from './interfaces'; 8 | import Procedures from './procedures'; 9 | import Session from './session'; 10 | import Topics from './topics'; 11 | import { isValidRealm } from './util'; 12 | 13 | const DEBUG = Debug('wamp:session-manager'); 14 | const containers = new Map(); 15 | 16 | /** 17 | * 18 | * 19 | * @class SessionManager 20 | */ 21 | class SessionManager { 22 | 23 | /** 24 | * 25 | * 26 | * @static 27 | * @param {string} realm 28 | * @returns {boolean} 29 | */ 30 | public static realmExists(realm: string): boolean { 31 | return containers.get(realm) ? true : false; 32 | } 33 | 34 | /** 35 | * 36 | * 37 | * @static 38 | * @param {SocketInterface} socket 39 | */ 40 | public static createSession(socket: SocketInterface, req: any): void { 41 | const IP = req.connection.remoteAddress; 42 | const SESSION: SessionInterface = new Session(socket, IP); 43 | DEBUG('creating session for ip: %s with id: %s', 44 | SESSION.getIP(), 45 | SESSION.getID()); 46 | } 47 | 48 | /** 49 | * 50 | * 51 | * @static 52 | * @param {string} realm 53 | * @param {number} id 54 | * @returns {SessionInterface} 55 | */ 56 | public static getSession(realm: string, id: number): SessionInterface { 57 | DEBUG('getting session for id: %s', id); 58 | const SESSIONS: MapInterface = containers.get(realm); 59 | if (SESSIONS) { 60 | return SESSIONS.get(id); 61 | } 62 | } 63 | 64 | /** 65 | * 66 | * 67 | * @static 68 | * @param {string} realm 69 | * @param {SessionInterface} session 70 | */ 71 | public static registerSession(realm: string, 72 | session: SessionInterface): void { 73 | DEBUG('registering session with id: %s for realm: %s', 74 | session.getID(), 75 | realm); 76 | containers.get(realm).set(session.getID(), session); 77 | DEBUG('number of sessions: %s', SessionManager.getSessionsAmount(realm)); 78 | } 79 | 80 | /** 81 | * 82 | * 83 | * @static 84 | * @param {string} realm 85 | * @param {number} id 86 | */ 87 | public static removeSession(realm: string, id: number): void { 88 | DEBUG('removing session: %s for realm: %s', id, realm); 89 | containers.get(realm).delete(id); 90 | } 91 | 92 | /** 93 | * 94 | * 95 | * @static 96 | * @param {string} realm 97 | * @returns {number} 98 | */ 99 | public static getSessionsAmount(realm: string): number { 100 | return containers.get(realm).size; 101 | } 102 | 103 | /** 104 | * 105 | * 106 | * @static 107 | * @param {string[]} realms 108 | */ 109 | public static registerRealms(realms: string[]): void { 110 | realms.forEach((realm) => { 111 | DEBUG(`registering realm: ${realm}`); 112 | if (isValidRealm(realm)) { 113 | containers.set(realm, new Map()); 114 | Procedures.registerRealm(realm); 115 | Topics.registerRealm(realm); 116 | } else { 117 | throw new Error(`${errors.uri}: "${realm}"`); 118 | } 119 | }); 120 | } 121 | 122 | } 123 | 124 | export default SessionManager; 125 | -------------------------------------------------------------------------------- /test/all/session.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { SessionInterface } from '../../src/interfaces'; 3 | import Session from '../../src/session'; 4 | import SessionManager from '../../src/session-manager'; 5 | import Topics from '../../src/topics'; 6 | import Request from '../mocks/request'; 7 | import Socket from '../mocks/socket'; 8 | 9 | const request = new Request(); 10 | const socket = new Socket(); 11 | const IP = request.connection.remoteAddress; 12 | socket.readyState = 1; 13 | const session: SessionInterface = new Session(socket, IP); 14 | const subscriptionID = 123456789; 15 | const realm = 'com.some.session'; 16 | 17 | describe('Session', () => { 18 | 19 | it('Should set a realm', () => { 20 | SessionManager.registerRealms([realm]); 21 | expect(session.setRealm(realm)).to.be.a('undefined'); 22 | }); 23 | 24 | it('Should get a realm', () => { 25 | expect(session.getRealm()).equal('com.some.session'); 26 | }); 27 | 28 | it('Should get an id', () => { 29 | expect(session.getID()).to.be.a('number'); 30 | }); 31 | 32 | it('Should get an ip', () => { 33 | expect(session.getIP()).equal(IP); 34 | }); 35 | 36 | it('Should push procedures.ts', () => { 37 | const procedure1 = 'com.some.session'; 38 | const procedure2 = 'com.some.session'; 39 | expect(session.pushProcedure(procedure1)).to.be.a('undefined'); 40 | expect(session.pushProcedure(procedure2)).to.be.a('undefined'); 41 | }); 42 | 43 | it('Should not fail if you try to remove non existing procedure', () => { 44 | const procedure = 'com.some.not-existing'; 45 | expect(session.removeProcedure(procedure)).to.be.a('undefined'); 46 | }); 47 | 48 | it('Should remove procedure', () => { 49 | const procedure = 'com.some.session'; 50 | expect(session.removeProcedure(procedure)).to.be.a('undefined'); 51 | }); 52 | 53 | it('Should remove procedures', () => { 54 | expect(session.removeProcedures()).to.be.a('undefined'); 55 | }); 56 | 57 | it('Should push subscription', () => { 58 | const subscription = 'com.some.event'; 59 | Topics.subscribe(realm, subscription, subscriptionID, session); 60 | const expected = session.pushSubscription(subscriptionID, subscription); 61 | expect(expected).to.be.a('undefined'); 62 | }); 63 | 64 | it('Should get subscription ID', () => { 65 | const subscription = 'com.some.event'; 66 | expect(session.getSubscriptionID(subscription)).equal(subscriptionID); 67 | }); 68 | 69 | it('Should remove subscription', () => { 70 | expect(session.removeSubsubscription(subscriptionID)).to.be.a('object'); 71 | }); 72 | 73 | it('Should not fail if try to get subscription ID of non existing', () => { 74 | const subscription = 'com.some.non-existing'; 75 | expect(session.getSubscriptionID(subscription)).to.be.a('undefined'); 76 | }); 77 | 78 | it('Should remove subscriptions', () => { 79 | const subscription = 'com.some.event'; 80 | Topics.subscribe(realm, subscription, subscriptionID, session); 81 | session.pushSubscription(subscriptionID, subscription); 82 | expect(session.removeSubscriptions()).to.be.a('undefined'); 83 | }); 84 | 85 | it('Should close the session', () => { 86 | expect(session.close()).to.be.a('undefined'); 87 | }); 88 | 89 | it('Should send a message', () => { 90 | const message: any[] = [ 91 | 1, 92 | 2, 93 | {}, 94 | [], 95 | ]; 96 | expect(session.send(message)).to.be.a('undefined'); 97 | }); 98 | 99 | it('Should handle incoming message', () => { 100 | const message: any[] = [ 101 | 1, 102 | 2, 103 | {}, 104 | [], 105 | ]; 106 | socket.emit('message', JSON.stringify(message)); 107 | }); 108 | 109 | it('Should handle closing of the socket', () => { 110 | const message: any[] = [ 111 | 1, 112 | 2, 113 | {}, 114 | [], 115 | ]; 116 | socket.emit('close', JSON.stringify(message)); 117 | }); 118 | 119 | }); 120 | -------------------------------------------------------------------------------- /release/session.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | const Debug = require("debug"); 6 | const message_1 = require("./message"); 7 | const procedures_1 = require("./procedures"); 8 | const router_1 = require("./router"); 9 | const session_manager_1 = require("./session-manager"); 10 | const topics_1 = require("./topics"); 11 | const util_1 = require("./util"); 12 | const DEBUG = Debug('wamp:session'); 13 | /** 14 | * 15 | * 16 | * @class Session 17 | */ 18 | class Session { 19 | /** 20 | * Creates an instance of Session. 21 | * 22 | * @param {SocketInterface} socket 23 | */ 24 | constructor(socket, ip) { 25 | this.socket = socket; 26 | this.ip = ip; 27 | /** 28 | * 29 | * 30 | * @private 31 | * @type {number} 32 | */ 33 | this.id = util_1.makeID(); 34 | /** 35 | * 36 | * 37 | * @private 38 | * @type {string[]} 39 | */ 40 | this.procedures = []; 41 | /** 42 | * 43 | * 44 | * @private 45 | * @type {SubscriptionInterface[]} 46 | */ 47 | this.subscriptions = []; 48 | this.addSocketEventListener(); 49 | } 50 | /** 51 | * 52 | * 53 | * @param {string} realm 54 | */ 55 | setRealm(realm) { 56 | this.realm = realm; 57 | } 58 | /** 59 | * 60 | * 61 | * @returns {string} 62 | */ 63 | getRealm() { 64 | return this.realm; 65 | } 66 | /** 67 | * 68 | * 69 | * @returns {string} 70 | */ 71 | getIP() { 72 | return this.ip; 73 | } 74 | /** 75 | * 76 | * 77 | * @returns {number} 78 | */ 79 | getID() { 80 | return this.id; 81 | } 82 | /** 83 | * 84 | * 85 | * @param {string} uri 86 | */ 87 | pushProcedure(uri) { 88 | this.procedures.push(uri); 89 | } 90 | /** 91 | * 92 | * 93 | * @param {string} uri 94 | */ 95 | removeProcedure(uri) { 96 | const LENGTH = this.procedures.length; 97 | for (let i = 0; i < LENGTH; i++) { 98 | if (this.procedures[i] === uri) { 99 | DEBUG('removing procedure: %s', uri); 100 | this.procedures.splice(i, 1); 101 | break; 102 | } 103 | } 104 | } 105 | /** 106 | * 107 | */ 108 | removeProcedures() { 109 | const LENGTH = this.procedures.length; 110 | for (let i = 0; i < LENGTH; i++) { 111 | procedures_1.default.remove(this.realm, this.procedures[i]); 112 | } 113 | } 114 | /** 115 | * 116 | * 117 | * @param {number} subscriptionID 118 | * @param {string} topic 119 | */ 120 | pushSubscription(subscriptionID, topic) { 121 | this.subscriptions.push({ 122 | subscriptionID, 123 | topic, 124 | }); 125 | } 126 | /** 127 | * 128 | * 129 | * @param {number} subscriptionID 130 | * @returns {SubscriptionInterface} 131 | */ 132 | removeSubsubscription(subscriptionID) { 133 | const LENGTH = this.subscriptions.length; 134 | for (let i = 0; i < LENGTH; i++) { 135 | if (this.subscriptions[i].subscriptionID === subscriptionID) { 136 | DEBUG('removing subscription: %s', subscriptionID); 137 | let subscription; 138 | subscription = this.subscriptions.splice(i, 1); 139 | return subscription[0]; 140 | } 141 | } 142 | } 143 | /** 144 | * 145 | */ 146 | removeSubscriptions() { 147 | const LENGTH = this.subscriptions.length; 148 | for (let i = 0; i < LENGTH; i++) { 149 | topics_1.default.unsubscribe(this.realm, this.subscriptions[i].topic, this.id); 150 | } 151 | } 152 | /** 153 | * 154 | * 155 | * @param {string} topic 156 | * @returns {number} 157 | */ 158 | getSubscriptionID(topic) { 159 | const LENGTH = this.subscriptions.length; 160 | for (let i = 0; i < LENGTH; i++) { 161 | if (this.subscriptions[i].topic === topic) { 162 | return this.subscriptions[i].subscriptionID; 163 | } 164 | } 165 | } 166 | /** 167 | * 168 | * 169 | * @param {SocketMessageInterface} messae 170 | * @param {ErrorMessageInterface} error 171 | */ 172 | error(messae, error) { 173 | const ERROR_MESSAGE = [ 174 | error.errorNumber, 175 | error.requestTypeNumber, 176 | error.messageID, 177 | {}, 178 | error.errorMessage, 179 | error.args, 180 | ]; 181 | this.send(ERROR_MESSAGE); 182 | } 183 | /** 184 | * 185 | * 186 | * @param {any[]} message 187 | */ 188 | send(message) { 189 | if (this.socket.readyState === 1) { 190 | const RESPONSE = JSON.stringify(message); 191 | DEBUG('outgoing message: %s', RESPONSE); 192 | this.socket.send(RESPONSE); 193 | } 194 | } 195 | /** 196 | * 197 | */ 198 | close() { 199 | DEBUG('cleaning...'); 200 | this.removeSubscriptions(); 201 | this.removeProcedures(); 202 | session_manager_1.default.removeSession(this.realm, this.id); 203 | this.socket.close(); 204 | } 205 | /** 206 | * 207 | * 208 | * @private 209 | */ 210 | addSocketEventListener() { 211 | this.socket.on('message', (incomingMessage) => { 212 | DEBUG('incoming message %s for sessionID: %s', incomingMessage, this.id); 213 | router_1.default.dispatch(this, new message_1.default(incomingMessage).getMessage()); 214 | }); 215 | this.socket.on('close', () => { 216 | DEBUG('closing session for ID: %s and IP: %s', this.id, this.ip); 217 | if (this.realm) { 218 | this.close(); 219 | } 220 | }); 221 | this.socket.on('error', (err) => { 222 | const MSG = err.message; 223 | DEBUG('socket error "%s" for ID: %s and IP: %s', MSG, this.id, this.ip); 224 | if (MSG.indexOf('ECONNRESET') > -1) { 225 | this.close(); 226 | } 227 | }); 228 | } 229 | } 230 | exports.default = Session; -------------------------------------------------------------------------------- /src/session.ts: -------------------------------------------------------------------------------- 1 | import * as Debug from 'debug'; 2 | import { 3 | ErrorMessageInterface, 4 | SocketInterface, 5 | SocketMessageInterface, 6 | SubscriptionInterface, 7 | } from './interfaces'; 8 | import Message from './message'; 9 | import Procedures from './procedures'; 10 | import Router from './router'; 11 | import SessionManager from './session-manager'; 12 | import Topics from './topics'; 13 | import { makeID } from './util'; 14 | 15 | const DEBUG = Debug('wamp:session'); 16 | 17 | /** 18 | * 19 | * 20 | * @class Session 21 | */ 22 | class Session { 23 | 24 | /** 25 | * 26 | * 27 | * @private 28 | * @type {number} 29 | */ 30 | private id: number = makeID(); 31 | /** 32 | * 33 | * 34 | * @private 35 | * @type {string[]} 36 | */ 37 | private procedures: string[] = []; 38 | /** 39 | * 40 | * 41 | * @private 42 | * @type {string} 43 | */ 44 | private realm: string; 45 | /** 46 | * 47 | * 48 | * @private 49 | * @type {SubscriptionInterface[]} 50 | */ 51 | private subscriptions: SubscriptionInterface[] = []; 52 | 53 | /** 54 | * Creates an instance of Session. 55 | * 56 | * @param {SocketInterface} socket 57 | */ 58 | constructor(private socket: SocketInterface, private ip: string) { 59 | this.addSocketEventListener(); 60 | } 61 | 62 | /** 63 | * 64 | * 65 | * @param {string} realm 66 | */ 67 | public setRealm(realm: string): void { 68 | this.realm = realm; 69 | } 70 | 71 | /** 72 | * 73 | * 74 | * @returns {string} 75 | */ 76 | public getRealm(): string { 77 | return this.realm; 78 | } 79 | 80 | /** 81 | * 82 | * 83 | * @returns {string} 84 | */ 85 | public getIP(): string { 86 | return this.ip; 87 | } 88 | 89 | /** 90 | * 91 | * 92 | * @returns {number} 93 | */ 94 | public getID(): number { 95 | return this.id; 96 | } 97 | 98 | /** 99 | * 100 | * 101 | * @param {string} uri 102 | */ 103 | public pushProcedure(uri: string): void { 104 | this.procedures.push(uri); 105 | } 106 | 107 | /** 108 | * 109 | * 110 | * @param {string} uri 111 | */ 112 | public removeProcedure(uri: string): void { 113 | const LENGTH: number = this.procedures.length; 114 | for (let i = 0; i < LENGTH; i++) { 115 | if (this.procedures[i] === uri) { 116 | DEBUG('removing procedure: %s', uri); 117 | this.procedures.splice(i, 1); 118 | break; 119 | } 120 | } 121 | } 122 | 123 | /** 124 | * 125 | */ 126 | public removeProcedures(): void { 127 | const LENGTH: number = this.procedures.length; 128 | for (let i = 0; i < LENGTH; i++) { 129 | Procedures.remove(this.realm, this.procedures[i]); 130 | } 131 | } 132 | 133 | /** 134 | * 135 | * 136 | * @param {number} subscriptionID 137 | * @param {string} topic 138 | */ 139 | public pushSubscription(subscriptionID: number, topic: string): void { 140 | this.subscriptions.push({ 141 | subscriptionID, 142 | topic, 143 | }); 144 | } 145 | 146 | /** 147 | * 148 | * 149 | * @param {number} subscriptionID 150 | * @returns {SubscriptionInterface} 151 | */ 152 | public removeSubsubscription(subscriptionID: number): SubscriptionInterface { 153 | const LENGTH: number = this.subscriptions.length; 154 | for (let i = 0; i < LENGTH; i++) { 155 | if (this.subscriptions[i].subscriptionID === subscriptionID) { 156 | DEBUG('removing subscription: %s', subscriptionID); 157 | let subscription: SubscriptionInterface[]; 158 | subscription = this.subscriptions.splice(i, 1); 159 | return subscription[0]; 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * 166 | */ 167 | public removeSubscriptions(): void { 168 | const LENGTH: number = this.subscriptions.length; 169 | for (let i = 0; i < LENGTH; i++) { 170 | Topics.unsubscribe(this.realm, this.subscriptions[i].topic, this.id); 171 | } 172 | } 173 | 174 | /** 175 | * 176 | * 177 | * @param {string} topic 178 | * @returns {number} 179 | */ 180 | public getSubscriptionID(topic: string): number { 181 | const LENGTH: number = this.subscriptions.length; 182 | for (let i = 0; i < LENGTH; i++) { 183 | if (this.subscriptions[i].topic === topic) { 184 | return this.subscriptions[i].subscriptionID; 185 | } 186 | } 187 | } 188 | 189 | /** 190 | * 191 | * 192 | * @param {SocketMessageInterface} messae 193 | * @param {ErrorMessageInterface} error 194 | */ 195 | public error(messae: SocketMessageInterface, 196 | error: ErrorMessageInterface): void { 197 | const ERROR_MESSAGE: any[] = [ 198 | error.errorNumber, 199 | error.requestTypeNumber, 200 | error.messageID, 201 | {}, 202 | error.errorMessage, 203 | error.args, 204 | ]; 205 | this.send(ERROR_MESSAGE); 206 | } 207 | 208 | /** 209 | * 210 | * 211 | * @param {any[]} message 212 | */ 213 | public send(message: any[]): void { 214 | if (this.socket.readyState === 1) { 215 | const RESPONSE: string = JSON.stringify(message); 216 | DEBUG('outgoing message: %s', RESPONSE); 217 | this.socket.send(RESPONSE); 218 | } 219 | } 220 | 221 | /** 222 | * 223 | */ 224 | public close(): void { 225 | DEBUG('cleaning...'); 226 | this.removeSubscriptions(); 227 | this.removeProcedures(); 228 | SessionManager.removeSession(this.realm, this.id); 229 | this.socket.close(); 230 | } 231 | 232 | /** 233 | * 234 | * 235 | * @private 236 | */ 237 | private addSocketEventListener(): void { 238 | this.socket.on('message', (incomingMessage: string) => { 239 | DEBUG('incoming message %s for sessionID: %s', incomingMessage, this.id); 240 | Router.dispatch(this, new Message(incomingMessage).getMessage()); 241 | }); 242 | this.socket.on('close', () => { 243 | DEBUG('closing session for ID: %s and IP: %s', this.id, this.ip); 244 | if (this.realm) { 245 | this.close(); 246 | } 247 | }); 248 | this.socket.on('error', (err: Error) => { 249 | const MSG: string = err.message; 250 | DEBUG('socket error "%s" for ID: %s and IP: %s', MSG, this.id, this.ip); 251 | if (MSG.indexOf('ECONNRESET') > -1) { 252 | this.close(); 253 | } 254 | }); 255 | } 256 | } 257 | 258 | export default Session; 259 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import Session from './session'; 2 | 3 | /** 4 | * 5 | * 6 | * @export 7 | * @interface ErrorMessageInterface 8 | */ 9 | export interface ErrorMessageInterface { 10 | /** 11 | * 12 | * 13 | * @type {number} 14 | */ 15 | errorNumber: number; 16 | /** 17 | * 18 | * 19 | * @type {string} 20 | */ 21 | errorMessage: string; 22 | /** 23 | * 24 | * 25 | * @type {number} 26 | */ 27 | messageID: number; 28 | /** 29 | * 30 | * 31 | * @type {number} 32 | */ 33 | requestTypeNumber: number; 34 | /** 35 | * 36 | * 37 | * @type {any} 38 | */ 39 | args?: any; 40 | } 41 | 42 | /** 43 | * 44 | * 45 | * @export 46 | * @interface EventEmitter 47 | */ 48 | export interface EventEmitter { 49 | /** 50 | * 51 | * 52 | * @param {string} event 53 | * @param {Function} listener 54 | * @returns {this} 55 | */ 56 | addListener(event: string, listener: () => void): this; 57 | /** 58 | * 59 | * 60 | * @param {string} event 61 | * @param {Function} listener 62 | * @returns {this} 63 | */ 64 | on(event: string, listener: (message: any) => void): this; 65 | /** 66 | * 67 | * 68 | * @param {string} event 69 | * @param {Function} listener 70 | * @returns {this} 71 | */ 72 | once(event: string, listener: () => void): this; 73 | /** 74 | * 75 | * 76 | * @param {string} event 77 | * @param {Function} listener 78 | * @returns {this} 79 | */ 80 | removeListener(event: string, listener: () => void): this; 81 | /** 82 | * 83 | * 84 | * @param {string} [event] 85 | * @returns {this} 86 | */ 87 | removeAllListeners(event?: string): this; 88 | /** 89 | * 90 | * 91 | * @param {number} n 92 | * @returns {this} 93 | */ 94 | setMaxListeners(n: number): this; 95 | /** 96 | * 97 | * 98 | * @returns {number} 99 | */ 100 | getMaxListeners(): number; 101 | /** 102 | * 103 | * 104 | * @param {string} event 105 | * @returns {Function[]} 106 | */ 107 | listeners(event: string): () => void[]; 108 | /** 109 | * 110 | * 111 | * @param {string} event 112 | * @param {...any[]} args 113 | * @returns {boolean} 114 | */ 115 | emit(event: string, ...args: any[]): boolean; 116 | /** 117 | * 118 | * 119 | * @param {string} type 120 | * @returns {number} 121 | */ 122 | listenerCount(type: string): number; 123 | } 124 | 125 | /** 126 | * 127 | * 128 | * @export 129 | * @interface Events 130 | * @extends {EventEmitter} 131 | */ 132 | export interface Events extends EventEmitter { } 133 | 134 | /** 135 | * 136 | * 137 | * @export 138 | * @interface MapInterface 139 | */ 140 | export interface MapInterface { 141 | /** 142 | * 143 | * 144 | * @type {number} 145 | */ 146 | size: number; 147 | /** 148 | * 149 | * 150 | * @param {*} key 151 | * @returns {boolean} 152 | */ 153 | delete(key: any): boolean; 154 | /** 155 | * 156 | * 157 | * @param {*} key 158 | * @returns {*} 159 | */ 160 | get(key: any): any; 161 | /** 162 | * 163 | * 164 | * @param {*} key 165 | * @returns {boolean} 166 | */ 167 | has(key: any): boolean; 168 | /** 169 | * 170 | * 171 | * @param {*} key 172 | * @param {*} value 173 | * @returns {MapInterface} 174 | */ 175 | set(key: any, value: any): MapInterface; 176 | } 177 | 178 | /** 179 | * 180 | * 181 | * @export 182 | * @interface NotifyEvent 183 | */ 184 | export interface NotifyEvent { 185 | /** 186 | * 187 | * 188 | * @type {number} 189 | */ 190 | currentIndex: number; 191 | /** 192 | * 193 | * 194 | * @type {number} 195 | */ 196 | length: number; 197 | /** 198 | * 199 | * 200 | * @type {SocketMessageInterface} 201 | */ 202 | message: SocketMessageInterface; 203 | /** 204 | * 205 | * 206 | * @type {TopicInterface} 207 | */ 208 | subscription: TopicInterface; 209 | /** 210 | * 211 | * 212 | * @type {Session} 213 | */ 214 | session: Session; 215 | /** 216 | * 217 | * 218 | * @type {string} 219 | */ 220 | topic: string; 221 | } 222 | 223 | /** 224 | * 225 | * 226 | * @export 227 | * @interface OptionsInterface 228 | */ 229 | export interface OptionsInterface { 230 | /** 231 | * 232 | * 233 | * @type {number} 234 | */ 235 | port: number; 236 | /** 237 | * 238 | * 239 | * @type {(string | string[])} 240 | */ 241 | realms: string | string[]; 242 | } 243 | 244 | /** 245 | * 246 | * 247 | * @export 248 | * @interface ProcedureInterface 249 | */ 250 | export interface ProcedureInterface { 251 | /** 252 | * 253 | * 254 | * @type {number} 255 | */ 256 | procedureID: number; 257 | /** 258 | * 259 | * 260 | * @type {number} 261 | */ 262 | sessionID: number; 263 | /** 264 | * 265 | * 266 | * @type {string} 267 | */ 268 | uri: string; 269 | } 270 | 271 | /** 272 | * 273 | * 274 | * @export 275 | * @interface SocketInterface 276 | * @extends {EventEmitter} 277 | */ 278 | export interface SocketInterface extends EventEmitter { 279 | /** 280 | * 281 | * 282 | * @type {(number | string)} 283 | */ 284 | id: number | string; 285 | /** 286 | * 287 | * 288 | * @type {string} 289 | */ 290 | ip?: string; 291 | /** 292 | * 293 | * 294 | * @type {*} 295 | */ 296 | connection?: any; 297 | /** 298 | * 299 | * 300 | * @type {number} 301 | */ 302 | readyState: number; 303 | /** 304 | * 305 | * 306 | * @param {string} message 307 | */ 308 | send(message: string): void; 309 | /** 310 | * 311 | */ 312 | close(): void; 313 | } 314 | 315 | /** 316 | * 317 | * 318 | * @export 319 | * @interface SocketMessageInterface 320 | */ 321 | export interface SocketMessageInterface { 322 | /** 323 | * 324 | * 325 | * @type {number} 326 | */ 327 | id?: number; 328 | /** 329 | * 330 | * 331 | * @type {number} 332 | */ 333 | type?: number; 334 | /** 335 | * 336 | * 337 | * @type {any[]} 338 | */ 339 | incoming?: any[]; 340 | /** 341 | * 342 | * 343 | * @type {*} 344 | */ 345 | details?: any; 346 | /** 347 | * 348 | * 349 | * @type {any[]} 350 | */ 351 | args?: any[]; 352 | /** 353 | * 354 | * 355 | * @type {{}} 356 | */ 357 | kwargs?: {}; 358 | } 359 | 360 | /** 361 | * 362 | * 363 | * @export 364 | * @interface SessionInterface 365 | * @extends {Session} 366 | */ 367 | export interface SessionInterface extends Session {} 368 | 369 | /** 370 | * 371 | * 372 | * @export 373 | * @interface SubscriptionInterface 374 | */ 375 | export interface SubscriptionInterface { 376 | /** 377 | * 378 | * 379 | * @type {number} 380 | */ 381 | subscriptionID: number; 382 | /** 383 | * 384 | * 385 | * @type {string} 386 | */ 387 | topic: string; 388 | } 389 | 390 | /** 391 | * 392 | * 393 | * @export 394 | * @interface TopicInterface 395 | */ 396 | export interface TopicInterface { 397 | /** 398 | * 399 | * 400 | * @type {number} 401 | */ 402 | subscriptionID: number; 403 | /** 404 | * 405 | * 406 | * @type {SessionInterface[]} 407 | */ 408 | sessions: SessionInterface[]; 409 | } 410 | -------------------------------------------------------------------------------- /release/handlers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { 3 | value: true 4 | }); 5 | const errors_1 = require("./errors"); 6 | const procedures_1 = require("./procedures"); 7 | const protocols_1 = require("./protocols"); 8 | const roles_1 = require("./roles"); 9 | const session_manager_1 = require("./session-manager"); 10 | const topics_1 = require("./topics"); 11 | const transactions_1 = require("./transactions"); 12 | const util_1 = require("./util"); 13 | const Debug = require("debug"); 14 | const DEBUG = Debug('wamp:handlers'); 15 | /** 16 | * 17 | * 18 | * @class Handlers 19 | */ 20 | class Handlers { 21 | /** 22 | * 23 | * 24 | * @static 25 | * @param {SessionInterface} session 26 | */ 27 | static abort(session) { 28 | session.send([ 29 | protocols_1.outgoingChannel.ABORT, 30 | errors_1.default.hello, 31 | roles_1.default, 32 | ]); 33 | } 34 | /** 35 | * 36 | * 37 | * @static 38 | * @param {SessionInterface} session 39 | * @param {SocketMessageInterface} message 40 | * @returns {void} 41 | */ 42 | static call(session, message) { 43 | const URI = message.incoming[3]; 44 | const REALM = session.getRealm(); 45 | const PROCEDURE = procedures_1.default.get(REALM, URI); 46 | if (!PROCEDURE) { 47 | const ERROR = { 48 | errorMessage: errors_1.default.procedure, 49 | errorNumber: protocols_1.outgoingChannel.ERROR, 50 | messageID: message.id, 51 | requestTypeNumber: protocols_1.outgoingChannel.CALL, 52 | }; 53 | return session.error(message, ERROR); 54 | } 55 | let CALLEE; 56 | CALLEE = session_manager_1.default.getSession(session.getRealm(), PROCEDURE.sessionID); 57 | message.incoming[3] = PROCEDURE.procedureID; 58 | transactions_1.default.add(message.id, session.getID()); 59 | Handlers.invocation(CALLEE, message); 60 | } 61 | /** 62 | * 63 | * 64 | * @static 65 | * @param {SessionInterface} session 66 | * @param {SocketMessageInterface} message 67 | */ 68 | static error(session, message) { 69 | const KEY = message.incoming[2]; 70 | const SESSION_ID = transactions_1.default.get(KEY); 71 | const REALM = session.getRealm(); 72 | transactions_1.default.delete(KEY); 73 | let SESSION; 74 | SESSION = session_manager_1.default.getSession(REALM, SESSION_ID); 75 | if (SESSION) { 76 | const ERROR = { 77 | args: message.incoming[5], 78 | errorMessage: message.incoming[4], 79 | errorNumber: protocols_1.outgoingChannel.ERROR, 80 | messageID: KEY, 81 | requestTypeNumber: protocols_1.outgoingChannel.CALL, 82 | }; 83 | return SESSION.error(message, ERROR); 84 | } 85 | } 86 | /** 87 | * 88 | * 89 | * @static 90 | * @param {SessionInterface} session 91 | * @param {SocketMessageInterface} message 92 | */ 93 | static goodbye(session, message) { 94 | session.send([ 95 | protocols_1.outgoingChannel.GOODBYE, 96 | {}, 97 | errors_1.default.goodbye, 98 | ]); 99 | session.close(); 100 | } 101 | /** 102 | * 103 | * 104 | * @static 105 | * @param {SessionInterface} session 106 | * @param {SocketMessageInterface} message 107 | * @returns {void} 108 | */ 109 | static hello(session, message) { 110 | const REALM = String(message.id); 111 | if (session_manager_1.default.realmExists(REALM)) { 112 | session.setRealm(REALM); 113 | session_manager_1.default.registerSession(REALM, session); 114 | Handlers.welcome(session); 115 | return; 116 | } 117 | Handlers.abort(session); 118 | } 119 | /** 120 | * 121 | * 122 | * @static 123 | * @param {SessionInterface} session 124 | * @param {SocketMessageInterface} message 125 | */ 126 | static invocation(session, message) { 127 | const DETAILS = message.incoming[2]; 128 | const PROCEDURE_ID = message.incoming[3]; 129 | const ARGS = message.incoming[4]; 130 | const KWARGS = message.incoming[5]; 131 | const RESPONSE = [ 132 | protocols_1.outgoingChannel.INVOCATION, 133 | message.id, 134 | PROCEDURE_ID, 135 | DETAILS, 136 | ARGS, 137 | KWARGS, 138 | ]; 139 | session.send(RESPONSE); 140 | } 141 | /** 142 | * 143 | * 144 | * @static 145 | * @param {NotifyEvent} event 146 | */ 147 | static notify(event) { 148 | const INDEX = event.currentIndex; 149 | const SESSION = event.subscription.sessions[INDEX]; 150 | if (SESSION && INDEX < event.length) { 151 | if (SESSION.getID() !== event.session.getID()) { 152 | DEBUG('notifying sessionID: %s', SESSION.getID()); 153 | const DETAILS = { 154 | topic: event.message.incoming[3] 155 | }; 156 | const ARGS = event.message.incoming[4]; 157 | const KWARGS = event.message.incoming[5]; 158 | SESSION.send([ 159 | protocols_1.outgoingChannel.EVENT, 160 | SESSION.getSubscriptionID(event.topic), 161 | event.subscription.subscriptionID, 162 | DETAILS, 163 | ARGS, 164 | KWARGS, 165 | ]); 166 | } 167 | event.currentIndex++; 168 | setImmediate(Handlers.notify, event); 169 | } else { 170 | Handlers.published(event.session, event.message); 171 | } 172 | } 173 | /** 174 | * 175 | * 176 | * @static 177 | * @param {SessionInterface} session 178 | * @param {SocketMessageInterface} message 179 | */ 180 | static publish(session, message) { 181 | const TOPIC = message.incoming[3]; 182 | const SUBSCRIPTION = topics_1.default.get(session.getRealm(), TOPIC); 183 | if (SUBSCRIPTION) { 184 | const LENGTH = SUBSCRIPTION.sessions.length; 185 | const EVENT = { 186 | currentIndex: 0, 187 | length: LENGTH, 188 | message, 189 | session, 190 | subscription: SUBSCRIPTION, 191 | topic: TOPIC, 192 | }; 193 | Handlers.notify(EVENT); 194 | } 195 | } 196 | /** 197 | * 198 | * 199 | * @static 200 | * @param {SessionInterface} session 201 | * @param {SocketMessageInterface} message 202 | */ 203 | static published(session, message) { 204 | if (message.incoming[2] && message.incoming[2].acknowledge) { 205 | session.send([ 206 | protocols_1.outgoingChannel.PUBLISHED, 207 | message.id, 208 | util_1.makeID(), 209 | ]); 210 | } 211 | } 212 | /** 213 | * 214 | * 215 | * @static 216 | * @param {SessionInterface} session 217 | * @param {SocketMessageInterface} message 218 | * @returns {void} 219 | */ 220 | static register(session, message) { 221 | const PROCEDURE = message.incoming[3]; 222 | const REALM = session.getRealm(); 223 | if (!procedures_1.default.canAdd(REALM, PROCEDURE)) { 224 | const ERROR = { 225 | errorMessage: errors_1.default.register, 226 | errorNumber: protocols_1.outgoingChannel.ERROR, 227 | messageID: message.id, 228 | requestTypeNumber: protocols_1.outgoingChannel.REGISTER, 229 | }; 230 | return session.error(message, ERROR); 231 | } 232 | const PROCEDURE_ID = util_1.makeID(); 233 | session.pushProcedure(PROCEDURE); 234 | procedures_1.default.add(REALM, PROCEDURE, session.getID(), PROCEDURE_ID); 235 | Handlers.registered(session, message, PROCEDURE_ID); 236 | } 237 | /** 238 | * 239 | * 240 | * @static 241 | * @param {SessionInterface} session 242 | * @param {SocketMessageInterface} message 243 | * @param {number} procedureID 244 | */ 245 | static registered(session, message, procedureID) { 246 | session.send([ 247 | protocols_1.outgoingChannel.REGISTERED, 248 | message.id, 249 | procedureID, 250 | ]); 251 | } 252 | /** 253 | * 254 | * 255 | * @static 256 | * @param {SessionInterface} session 257 | * @param {SocketMessageInterface} message 258 | */ 259 | static result(session, message) { 260 | session.send([ 261 | protocols_1.outgoingChannel.RESULT, 262 | message.id, 263 | {}, 264 | message.incoming[3], 265 | ]); 266 | } 267 | /** 268 | * 269 | * 270 | * @static 271 | * @param {SessionInterface} session 272 | * @param {SocketMessageInterface} message 273 | * @returns {void} 274 | */ 275 | static subscribe(session, message) { 276 | const TOPIC = message.incoming[3]; 277 | if (!TOPIC) { 278 | const ERROR = { 279 | errorMessage: errors_1.default.general, 280 | errorNumber: protocols_1.outgoingChannel.ERROR, 281 | messageID: message.id, 282 | requestTypeNumber: protocols_1.outgoingChannel.SUBSCRIBE, 283 | }; 284 | return session.error(message, ERROR); 285 | } 286 | const SUBSCRIPTION_ID = util_1.makeID(); 287 | message.incoming[3] = SUBSCRIPTION_ID; 288 | session.pushSubscription(SUBSCRIPTION_ID, TOPIC); 289 | topics_1.default.subscribe(session.getRealm(), TOPIC, SUBSCRIPTION_ID, session); 290 | Handlers.subscribed(session, message); 291 | } 292 | /** 293 | * 294 | * 295 | * @static 296 | * @param {SessionInterface} session 297 | * @param {SocketMessageInterface} message 298 | */ 299 | static subscribed(session, message) { 300 | session.send([ 301 | protocols_1.outgoingChannel.SUBSCRIBED, 302 | message.id, 303 | message.incoming[3], 304 | ]); 305 | } 306 | /** 307 | * 308 | * 309 | * @static 310 | * @param {SessionInterface} session 311 | * @param {SocketMessageInterface} message 312 | * @returns {void} 313 | */ 314 | static unregister(session, message) { 315 | const REALM = session.getRealm(); 316 | const ID = message.incoming[2]; 317 | const PROCEDURE = procedures_1.default.getByID(REALM, ID); 318 | if (PROCEDURE && PROCEDURE.uri) { 319 | session.removeProcedure(PROCEDURE.uri); 320 | procedures_1.default.remove(REALM, PROCEDURE.uri); 321 | return Handlers.unregistered(session, message); 322 | } 323 | const ERROR = { 324 | errorMessage: errors_1.default.unregister, 325 | errorNumber: protocols_1.outgoingChannel.ERROR, 326 | messageID: message.id, 327 | requestTypeNumber: protocols_1.outgoingChannel.UNREGISTER, 328 | }; 329 | session.error(message, ERROR); 330 | } 331 | /** 332 | * 333 | * 334 | * @static 335 | * @param {SessionInterface} session 336 | * @param {SocketMessageInterface} message 337 | */ 338 | static unregistered(session, message) { 339 | session.send([ 340 | protocols_1.outgoingChannel.UNREGISTERED, 341 | message.id, 342 | ]); 343 | } 344 | /** 345 | * 346 | * 347 | * @static 348 | * @param {SessionInterface} session 349 | * @param {SocketMessageInterface} message 350 | */ 351 | static unsubscribe(session, message) { 352 | const SUBSCRIPTION_ID = message.incoming[2]; 353 | const SUBSCRIPTION = session 354 | .removeSubsubscription(SUBSCRIPTION_ID); 355 | if (SUBSCRIPTION && SUBSCRIPTION.topic) { 356 | topics_1.default.unsubscribe(session.getRealm(), SUBSCRIPTION.topic, session.getID()); 357 | Handlers.unsubscribed(session, message); 358 | } 359 | } 360 | /** 361 | * 362 | * 363 | * @static 364 | * @param {SessionInterface} session 365 | * @param {SocketMessageInterface} message 366 | */ 367 | static unsubscribed(session, message) { 368 | session.send([ 369 | protocols_1.outgoingChannel.UNSUBSCRIBED, 370 | message.id, 371 | ]); 372 | } 373 | /** 374 | * 375 | * 376 | * @static 377 | * @param {SessionInterface} session 378 | */ 379 | static welcome(session) { 380 | session.send([ 381 | protocols_1.outgoingChannel.WELCOME, 382 | session.getID(), 383 | roles_1.default, 384 | ]); 385 | } 386 | /** 387 | * 388 | * 389 | * @static 390 | * @param {SessionInterface} session 391 | * @param {SocketMessageInterface} message 392 | */ 393 | static yield(session, message) { 394 | const KEY = message.id; 395 | const SESSION_ID = transactions_1.default.get(KEY); 396 | const REALM = session.getRealm(); 397 | transactions_1.default.delete(KEY); 398 | let SESSION; 399 | SESSION = session_manager_1.default.getSession(REALM, SESSION_ID); 400 | if (SESSION) { 401 | Handlers.result(SESSION, message); 402 | } 403 | } 404 | } 405 | exports.default = Handlers; -------------------------------------------------------------------------------- /src/handlers.ts: -------------------------------------------------------------------------------- 1 | import errors from './errors'; 2 | import { 3 | ErrorMessageInterface, 4 | NotifyEvent, 5 | ProcedureInterface, 6 | SessionInterface, 7 | SocketMessageInterface, 8 | SubscriptionInterface, 9 | TopicInterface, 10 | } from './interfaces'; 11 | import Procedures from './procedures'; 12 | import { outgoingChannel } from './protocols'; 13 | import roles from './roles'; 14 | import SessionManager from './session-manager'; 15 | import Topics from './topics'; 16 | import Transactions from './transactions'; 17 | import { makeID } from './util'; 18 | 19 | import * as Debug from 'debug'; 20 | 21 | const DEBUG = Debug('wamp:handlers'); 22 | 23 | /** 24 | * 25 | * 26 | * @class Handlers 27 | */ 28 | class Handlers { 29 | 30 | /** 31 | * 32 | * 33 | * @static 34 | * @param {SessionInterface} session 35 | */ 36 | public static abort(session: SessionInterface): void { 37 | session.send([ 38 | outgoingChannel.ABORT, 39 | errors.hello, 40 | roles, 41 | ]); 42 | } 43 | 44 | /** 45 | * 46 | * 47 | * @static 48 | * @param {SessionInterface} session 49 | * @param {SocketMessageInterface} message 50 | * @returns {void} 51 | */ 52 | public static call(session: SessionInterface, 53 | message: SocketMessageInterface): void { 54 | const URI: string = message.incoming[3]; 55 | const REALM: string = session.getRealm(); 56 | const PROCEDURE: ProcedureInterface = Procedures.get(REALM, URI); 57 | if (!PROCEDURE) { 58 | const ERROR: ErrorMessageInterface = { 59 | errorMessage: errors.procedure, 60 | errorNumber: outgoingChannel.ERROR, 61 | messageID: message.id, 62 | requestTypeNumber: outgoingChannel.CALL, 63 | }; 64 | return session.error(message, ERROR); 65 | } 66 | let CALLEE: SessionInterface; 67 | CALLEE = SessionManager.getSession(session.getRealm(), PROCEDURE.sessionID); 68 | message.incoming[3] = PROCEDURE.procedureID; 69 | Transactions.add(message.id, session.getID()); 70 | Handlers.invocation(CALLEE, message); 71 | } 72 | 73 | /** 74 | * 75 | * 76 | * @static 77 | * @param {SessionInterface} session 78 | * @param {SocketMessageInterface} message 79 | */ 80 | public static error(session: SessionInterface, 81 | message: SocketMessageInterface): void { 82 | const KEY: number = message.incoming[2]; 83 | const SESSION_ID = Transactions.get(KEY); 84 | const REALM: string = session.getRealm(); 85 | Transactions.delete(KEY); 86 | let SESSION: SessionInterface; 87 | SESSION = SessionManager.getSession(REALM, SESSION_ID); 88 | if (SESSION) { 89 | const ERROR: ErrorMessageInterface = { 90 | args: message.incoming[5], 91 | errorMessage: message.incoming[4], 92 | errorNumber: outgoingChannel.ERROR, 93 | messageID: KEY, 94 | requestTypeNumber: outgoingChannel.CALL, 95 | }; 96 | return SESSION.error(message, ERROR); 97 | } 98 | } 99 | 100 | /** 101 | * 102 | * 103 | * @static 104 | * @param {SessionInterface} session 105 | * @param {SocketMessageInterface} message 106 | */ 107 | public static goodbye(session: SessionInterface, 108 | message: SocketMessageInterface): void { 109 | session.send([ 110 | outgoingChannel.GOODBYE, 111 | {}, 112 | errors.goodbye, 113 | ]); 114 | session.close(); 115 | } 116 | 117 | /** 118 | * 119 | * 120 | * @static 121 | * @param {SessionInterface} session 122 | * @param {SocketMessageInterface} message 123 | * @returns {void} 124 | */ 125 | public static hello(session: SessionInterface, 126 | message: SocketMessageInterface): void { 127 | const REALM: string = String(message.id); 128 | if (SessionManager.realmExists(REALM)) { 129 | session.setRealm(REALM); 130 | SessionManager.registerSession(REALM, session); 131 | Handlers.welcome(session); 132 | return; 133 | } 134 | Handlers.abort(session); 135 | } 136 | 137 | /** 138 | * 139 | * 140 | * @static 141 | * @param {SessionInterface} session 142 | * @param {SocketMessageInterface} message 143 | */ 144 | public static invocation(session: SessionInterface, 145 | message: SocketMessageInterface): void { 146 | const DETAILS: {} = message.incoming[2]; 147 | const PROCEDURE_ID: number = message.incoming[3]; 148 | const ARGS: any[] = message.incoming[4]; 149 | const KWARGS: {} = message.incoming[5]; 150 | const RESPONSE: any[] = [ 151 | outgoingChannel.INVOCATION, 152 | message.id, 153 | PROCEDURE_ID, 154 | DETAILS, 155 | ARGS, 156 | KWARGS, 157 | ]; 158 | session.send(RESPONSE); 159 | } 160 | 161 | /** 162 | * 163 | * 164 | * @static 165 | * @param {NotifyEvent} event 166 | */ 167 | public static notify(event: NotifyEvent): void { 168 | const INDEX: number = event.currentIndex; 169 | const SESSION: SessionInterface = event.subscription.sessions[INDEX]; 170 | if (SESSION && INDEX < event.length) { 171 | if (SESSION.getID() !== event.session.getID()) { 172 | DEBUG('notifying sessionID: %s', SESSION.getID()); 173 | const DETAILS: {} = {topic: event.message.incoming[3]}; 174 | const ARGS: any[] = event.message.incoming[4]; 175 | const KWARGS: {} = event.message.incoming[5]; 176 | SESSION.send([ 177 | outgoingChannel.EVENT, 178 | SESSION.getSubscriptionID(event.topic), 179 | event.subscription.subscriptionID, 180 | DETAILS, 181 | ARGS, 182 | KWARGS, 183 | ]); 184 | } 185 | event.currentIndex++; 186 | setImmediate(Handlers.notify, event); 187 | } else { 188 | Handlers.published(event.session, event.message); 189 | } 190 | } 191 | 192 | /** 193 | * 194 | * 195 | * @static 196 | * @param {SessionInterface} session 197 | * @param {SocketMessageInterface} message 198 | */ 199 | public static publish(session: SessionInterface, 200 | message: SocketMessageInterface): void { 201 | const TOPIC: string = message.incoming[3]; 202 | const SUBSCRIPTION: TopicInterface = Topics.get(session.getRealm(), TOPIC); 203 | if (SUBSCRIPTION) { 204 | const LENGTH: number = SUBSCRIPTION.sessions.length; 205 | const EVENT: NotifyEvent = { 206 | currentIndex: 0, 207 | length: LENGTH, 208 | message, 209 | session, 210 | subscription: SUBSCRIPTION, 211 | topic: TOPIC, 212 | }; 213 | Handlers.notify(EVENT); 214 | } 215 | } 216 | 217 | /** 218 | * 219 | * 220 | * @static 221 | * @param {SessionInterface} session 222 | * @param {SocketMessageInterface} message 223 | */ 224 | public static published(session: SessionInterface, 225 | message: SocketMessageInterface): void { 226 | if (message.incoming[2] && message.incoming[2].acknowledge) { 227 | session.send([ 228 | outgoingChannel.PUBLISHED, 229 | message.id, 230 | makeID(), 231 | ]); 232 | } 233 | } 234 | 235 | /** 236 | * 237 | * 238 | * @static 239 | * @param {SessionInterface} session 240 | * @param {SocketMessageInterface} message 241 | * @returns {void} 242 | */ 243 | public static register(session: SessionInterface, 244 | message: SocketMessageInterface): void { 245 | const PROCEDURE: string = message.incoming[3]; 246 | const REALM: string = session.getRealm(); 247 | if (!Procedures.canAdd(REALM, PROCEDURE)) { 248 | const ERROR: ErrorMessageInterface = { 249 | errorMessage: errors.register, 250 | errorNumber: outgoingChannel.ERROR, 251 | messageID: message.id, 252 | requestTypeNumber: outgoingChannel.REGISTER, 253 | }; 254 | return session.error(message, ERROR); 255 | } 256 | const PROCEDURE_ID: number = makeID(); 257 | session.pushProcedure(PROCEDURE); 258 | Procedures.add(REALM, PROCEDURE, session.getID(), PROCEDURE_ID); 259 | Handlers.registered(session, message, PROCEDURE_ID); 260 | } 261 | 262 | /** 263 | * 264 | * 265 | * @static 266 | * @param {SessionInterface} session 267 | * @param {SocketMessageInterface} message 268 | * @param {number} procedureID 269 | */ 270 | public static registered(session: SessionInterface, 271 | message: SocketMessageInterface, 272 | procedureID: number): void { 273 | session.send([ 274 | outgoingChannel.REGISTERED, 275 | message.id, 276 | procedureID, 277 | ]); 278 | } 279 | 280 | /** 281 | * 282 | * 283 | * @static 284 | * @param {SessionInterface} session 285 | * @param {SocketMessageInterface} message 286 | */ 287 | public static result(session: SessionInterface, 288 | message: SocketMessageInterface): void { 289 | session.send([ 290 | outgoingChannel.RESULT, 291 | message.id, 292 | {}, 293 | message.incoming[3], 294 | ]); 295 | } 296 | 297 | /** 298 | * 299 | * 300 | * @static 301 | * @param {SessionInterface} session 302 | * @param {SocketMessageInterface} message 303 | * @returns {void} 304 | */ 305 | public static subscribe(session: SessionInterface, 306 | message: SocketMessageInterface): void { 307 | const TOPIC: string = message.incoming[3]; 308 | if (!TOPIC) { 309 | const ERROR: ErrorMessageInterface = { 310 | errorMessage: errors.general, 311 | errorNumber: outgoingChannel.ERROR, 312 | messageID: message.id, 313 | requestTypeNumber: outgoingChannel.SUBSCRIBE, 314 | }; 315 | return session.error(message, ERROR); 316 | } 317 | const SUBSCRIPTION_ID: number = makeID(); 318 | message.incoming[3] = SUBSCRIPTION_ID; 319 | session.pushSubscription(SUBSCRIPTION_ID, TOPIC); 320 | Topics.subscribe(session.getRealm(), TOPIC, SUBSCRIPTION_ID, session); 321 | Handlers.subscribed(session, message); 322 | } 323 | 324 | /** 325 | * 326 | * 327 | * @static 328 | * @param {SessionInterface} session 329 | * @param {SocketMessageInterface} message 330 | */ 331 | public static subscribed(session: SessionInterface, 332 | message: SocketMessageInterface): void { 333 | session.send([ 334 | outgoingChannel.SUBSCRIBED, 335 | message.id, 336 | message.incoming[3], 337 | ]); 338 | } 339 | 340 | /** 341 | * 342 | * 343 | * @static 344 | * @param {SessionInterface} session 345 | * @param {SocketMessageInterface} message 346 | * @returns {void} 347 | */ 348 | public static unregister(session: SessionInterface, 349 | message: SocketMessageInterface): void { 350 | const REALM: string = session.getRealm(); 351 | const ID: number = message.incoming[2]; 352 | const PROCEDURE: ProcedureInterface = Procedures.getByID(REALM, ID); 353 | if (PROCEDURE && PROCEDURE.uri) { 354 | session.removeProcedure(PROCEDURE.uri); 355 | Procedures.remove(REALM, PROCEDURE.uri); 356 | return Handlers.unregistered(session, message); 357 | } 358 | const ERROR: ErrorMessageInterface = { 359 | errorMessage: errors.unregister, 360 | errorNumber: outgoingChannel.ERROR, 361 | messageID: message.id, 362 | requestTypeNumber: outgoingChannel.UNREGISTER, 363 | }; 364 | session.error(message, ERROR); 365 | } 366 | 367 | /** 368 | * 369 | * 370 | * @static 371 | * @param {SessionInterface} session 372 | * @param {SocketMessageInterface} message 373 | */ 374 | public static unregistered(session: SessionInterface, 375 | message: SocketMessageInterface): void { 376 | session.send([ 377 | outgoingChannel.UNREGISTERED, 378 | message.id, 379 | ]); 380 | } 381 | 382 | /** 383 | * 384 | * 385 | * @static 386 | * @param {SessionInterface} session 387 | * @param {SocketMessageInterface} message 388 | */ 389 | public static unsubscribe(session: SessionInterface, 390 | message: SocketMessageInterface): void { 391 | const SUBSCRIPTION_ID: number = message.incoming[2]; 392 | const SUBSCRIPTION: SubscriptionInterface = session 393 | .removeSubsubscription(SUBSCRIPTION_ID); 394 | if (SUBSCRIPTION && SUBSCRIPTION.topic) { 395 | Topics.unsubscribe(session.getRealm(), 396 | SUBSCRIPTION.topic, 397 | session.getID()); 398 | Handlers.unsubscribed(session, message); 399 | } 400 | } 401 | 402 | /** 403 | * 404 | * 405 | * @static 406 | * @param {SessionInterface} session 407 | * @param {SocketMessageInterface} message 408 | */ 409 | public static unsubscribed(session: SessionInterface, 410 | message: SocketMessageInterface): void { 411 | session.send([ 412 | outgoingChannel.UNSUBSCRIBED, 413 | message.id, 414 | ]); 415 | } 416 | 417 | /** 418 | * 419 | * 420 | * @static 421 | * @param {SessionInterface} session 422 | */ 423 | public static welcome(session: SessionInterface): void { 424 | session.send([ 425 | outgoingChannel.WELCOME, 426 | session.getID(), 427 | roles, 428 | ]); 429 | } 430 | 431 | /** 432 | * 433 | * 434 | * @static 435 | * @param {SessionInterface} session 436 | * @param {SocketMessageInterface} message 437 | */ 438 | public static yield(session: SessionInterface, 439 | message: SocketMessageInterface): void { 440 | const KEY: number = message.id; 441 | const SESSION_ID = Transactions.get(KEY); 442 | const REALM: string = session.getRealm(); 443 | Transactions.delete(KEY); 444 | let SESSION: SessionInterface; 445 | SESSION = SessionManager.getSession(REALM, SESSION_ID); 446 | if (SESSION) { 447 | Handlers.result(SESSION, message); 448 | } 449 | } 450 | 451 | } 452 | 453 | export default Handlers; 454 | --------------------------------------------------------------------------------