mirror of
https://github.com/jambonz/jambonz-node.git
synced 2026-07-04 19:31:48 +00:00
add support for websockets api
This commit is contained in:
@@ -6,5 +6,12 @@ const initializer = (accountSid, apiKey, opts) => {
|
||||
|
||||
initializer.Jambonz = Jambonz;
|
||||
initializer.WebhookResponse = require('./jambonz/webhook-response');
|
||||
initializer.WsRouter = require('./jambonz/ws-router');
|
||||
initializer.WsSession = require('./jambonz/ws-session');
|
||||
initializer.handleProtocols = (protocols) => {
|
||||
if (!protocols.has('ws.jambonz.org')) return false;
|
||||
return 'ws.jambonz.org';
|
||||
};
|
||||
|
||||
|
||||
module.exports = initializer;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
const assert = require('assert');
|
||||
const Websocket = require('ws');
|
||||
const {WebhookResponse} = require('@jambonz/node-client');
|
||||
|
||||
module.exports = (ws, {logger, req}) => {
|
||||
assert(ws instanceof Websocket);
|
||||
ws.locals = {logger, req, url: req.url, originalUrl: req.url};
|
||||
|
||||
/* helper for sending an ack to jambonz */
|
||||
ws.ack = (msgid, res) => {
|
||||
let msg = {
|
||||
type: 'ack',
|
||||
msgid
|
||||
};
|
||||
if (res) msg = {...msg, data: res};
|
||||
try {
|
||||
logger.info({msg}, 'sending ack');
|
||||
ws.send(JSON.stringify(msg));
|
||||
} catch (err) {
|
||||
logger.error({err}, 'Error sending ack to jambonz');
|
||||
}
|
||||
};
|
||||
|
||||
ws.sendCommand = (command, call_sid, payload) => {
|
||||
payload = payload instanceof WebhookResponse ? payload.toJSON() : payload;
|
||||
assert.ok(typeof call_sid === 'string', 'invalid or missing call_sid');
|
||||
assert.ok(typeof payload === 'object' && Object.keys(payload).length > 0,
|
||||
'invalid or missing payload');
|
||||
//TODO: validate command
|
||||
|
||||
const msg = {
|
||||
type: 'command',
|
||||
command,
|
||||
call_sid,
|
||||
data: payload
|
||||
};
|
||||
try {
|
||||
logger.info({msg}, 'sending command');
|
||||
ws.send(JSON.stringify(msg));
|
||||
} catch (err) {
|
||||
logger.error({err}, `Error sending command ${command} to jambonz`);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
const assert = require('assert');
|
||||
const parseurl = require('parseurl');
|
||||
|
||||
class WsRouter {
|
||||
constructor() {
|
||||
this.routes = [];
|
||||
}
|
||||
|
||||
use(match, callback) {
|
||||
if (!callback) {
|
||||
callback = match;
|
||||
match = '*';
|
||||
}
|
||||
assert.ok(typeof callback === 'function' || callback instanceof WsRouter,
|
||||
'WsRouter.use - callback must be a function or a WsRouter instance');
|
||||
this.routes.push({match, callback});
|
||||
}
|
||||
|
||||
route(ws) {
|
||||
const {req} = ws.locals;
|
||||
const parsed = parseurl(req);
|
||||
const path = parsed.pathname;
|
||||
|
||||
const route = this.routes.find(({match}) => {
|
||||
/* wildcard */
|
||||
if ('*' === match) return true;
|
||||
|
||||
/* try matching by path */
|
||||
const urlChunks = path.split('/').filter((c) => c.length);
|
||||
const matchChunks = match.split('/').filter((c) => c.length);
|
||||
if (urlChunks.length >= matchChunks.length) {
|
||||
let idx = 0;
|
||||
do {
|
||||
if (urlChunks[idx] !== matchChunks[idx]) break;
|
||||
idx++;
|
||||
} while (idx < matchChunks.length);
|
||||
if (idx > 0) {
|
||||
req.url = urlChunks.slice(idx).join('/') + '/' + (parsed.search || '');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: try matching by param */
|
||||
|
||||
/* TODO : try matching by query args */
|
||||
});
|
||||
|
||||
if (!route) return false;
|
||||
|
||||
const {callback} = route;
|
||||
if (typeof callback === 'function') {
|
||||
callback(ws);
|
||||
return true;
|
||||
}
|
||||
return callback.route(ws);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WsRouter;
|
||||
@@ -0,0 +1,81 @@
|
||||
const Emitter = require('events');
|
||||
const assert = require('assert');
|
||||
const monkeyPatch = require('./monkey-patch-ws');
|
||||
|
||||
const noopLogger = {
|
||||
error: () => {},
|
||||
info: () => {},
|
||||
debug: () => {},
|
||||
child: () => this
|
||||
};
|
||||
|
||||
class WsSession extends Emitter {
|
||||
constructor({logger, router, ws, req}) {
|
||||
super();
|
||||
|
||||
assert(router);
|
||||
assert(ws);
|
||||
assert(req);
|
||||
|
||||
this.ws = ws;
|
||||
this.logger = logger || noopLogger;
|
||||
this.req = req;
|
||||
this.router = router;
|
||||
this._initialMsgRecvd = false;
|
||||
|
||||
monkeyPatch(this.ws, {logger, req});
|
||||
this._setHandlers();
|
||||
logger.info(`got websocket connection for url ${req.url}`);
|
||||
}
|
||||
|
||||
_setHandlers() {
|
||||
this.ws
|
||||
.on('close', this._onClose.bind(this))
|
||||
.on('message', this._onMessage.bind(this))
|
||||
.on('error', this._onError.bind(this));
|
||||
}
|
||||
|
||||
_onMessage(data, isBinary) {
|
||||
if (isBinary) {
|
||||
this.logger.info('discarding incoming binary message');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const {type, msgid, call_sid, hook, data:payload = {}} = JSON.parse(data);
|
||||
assert.ok(type, 'missing type property');
|
||||
assert.ok(msgid, 'missing msgid property');
|
||||
if (!this._initialMsgRecvd) {
|
||||
this._initialMsgRecvd = true;
|
||||
if (!this.router.route(this.ws)) {
|
||||
this.logger.info(`no route found for ${this.req.url}`);
|
||||
this.ws.removeAllListeners();
|
||||
this.ws.close();
|
||||
}
|
||||
}
|
||||
this.logger.debug({type, msgid, call_sid, payload}, 'Received message from jambonz');
|
||||
switch (type) {
|
||||
case 'session:new':
|
||||
case 'session:reconnect':
|
||||
case 'call:status':
|
||||
case 'verb:hook':
|
||||
case 'jambonz:error':
|
||||
this.ws.emit(type, {msgid, hook, payload});
|
||||
break;
|
||||
default:
|
||||
assert.ok(false, `invalid type ${type}`);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'Error handling incoming message');
|
||||
}
|
||||
}
|
||||
|
||||
_onClose() {
|
||||
this.emit('close');
|
||||
}
|
||||
|
||||
_onError(err) {
|
||||
this.emit('error', err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WsSession;
|
||||
Generated
+1017
-159
File diff suppressed because it is too large
Load Diff
+11
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jambonz/node-client",
|
||||
"version": "0.2.24",
|
||||
"version": "0.3.0",
|
||||
"description": "",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
@@ -15,13 +15,20 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pino": "^7.8.0",
|
||||
"ws": "^8.5.0",
|
||||
"bent": "^7.3.12",
|
||||
"debug": "^4.3.1",
|
||||
"parseurl": "^1.3.3",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tape": "^5.2.0",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"nyc": "^15.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tape": "^5.2.0"
|
||||
"optionalDependencies": {
|
||||
"bufferutil": "^4.0.6",
|
||||
"utf-8-validate": "^5.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user