npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@jambonz/node-client-ws

v0.1.61

Published

A simple Node.js library for building websocket server applications for [jambonz](https://jambonz.org)

Downloads

828

Readme

@jambonz/node-client-ws

A simple Node.js library for building websocket server applications for jambonz

Installing

npm install --save @jambonz/node-client-ws

Alternatively, to scaffold a full websocket application use the create-jambonz-ws-app utility:

npx create-jambonz-ws-app

Usage

Overview

jambonz can interact with applications using either HTTP webhooks or a websocket connection. This library provides support for the latter method of operation (see @jambonz/node-client for the companion Node.js library to use for building applications using HTTP webhooks).

To use this library, an application must first create an http(s) server. This library exposes a function, createEndpoint that is then invoked to attach a websocket server to that http server.

Doing so exposes a makeService function that can then be called one or more times to create different services corresponding to different request paths (e.g. '/hello-world' would be one service that routes to one application, '/echo' would be a different service that routes to different logic).

Calling makeService with a path returns a service client associated with that path. The client emits a session:new event when an incoming call has been received for the specified path, providing the application with a Session object which is then used to act on that call.

Sounds a bit involved but it is pretty simple as this sample code should make clear.

/* create an http/s server in your application however you like */
const {createServer} = require('http');
const server = createServer();
server.listen(3000);

/* require the library and call the returned function with your server */
const {createEndpoint} = require('@jambonz/node-client-ws');
const makeService = createEndpoint({server});

/* create a jambonz application listeng for requests with URL path '/hello-world' */
const svc = makeService({path: '/hello-world'});

/* listen for new calls to that service */
svc.on('session:new', (session) => {
  /* the 'session' object has all of the properties of the incoming call */
  console.log({session}, `new incoming call: ${session.call_sid}`);

  /* set up some event handlers for this session */
  session
    .on('close', onClose.bind(null, session))
    .on('error', onError.bind(null, session));

  /* all of the jambonz verbs are available as methods on the session object 
     https://www.jambonz.org/docs/webhooks/overview/
  */
  session
    .pause({length: 1.5})
    .say({text})
    .pause({length: 0.5})
    .hangup()
    .send(); // sends the queued verbs to jambonz
});

const onClose = (session, code, reason) => {
  console.log({session, code, reason}, `session ${session.call_sid} closed`);
};

const onError = (session, err) => {
  console.log({err}, `session ${session.call_sid} received error`);
};

A word on actionHooks

Many jambonz verbs provide asynchronous notification of events; e.g. the gather verb sends a notification when a speech transcript is received. When building the app using webhooks these events are sent as individual HTTP requests, but how is this handled in the case of a websocket application?

Very similarly, is the answer. You still define actionHooks and eventHooks in the same way, but now instead of a new http request you get a corresponding event emitted by the session object.

session
  .pause({length: 1.5})
  .gather({
    say: {text: 'Please say something and we will echo it back to you.'},
    input: ['speech'],
    actionHook: '/transcript',
    timeout: 15
  })
  .send();

session.on('/transcript', (evt) => {/* respond to transcript here... */});

replying to actionHooks

When receiving actionHooks or eventHooks from jambonz it is possible to respond with a new set of verbs for jambonz to begin executing. For instance, based on the transcript returned from a gather verb, your app may decide say something back to the user.

When working with webhooks this is accomplished by including a json payload in the 200 OK response of the HTTP webhook. In this library, we use session.reply() instead.

If you want to response to an actionHook by sending new jambonz verbs to execute, simply do this in your event handler for the actionHook:

session
 .say({text: 'goodbye'})
 .hangup()
 .reply();

However, even if you do not want to supply any new verbs, you must reply, e.g.

session.reply();

This is necessary so that jambonz does not block on its end of the websocket to see if you have any new verbs before it continues with execution of the current stack of verbs.

If you do not reply() to an actionHook or eventHook you may notice a 5-second delay before jambonz continues executing.

send versus reply

There are two main ways for your webhook to send commands to jambonz:

  • session.send()
  • session.reply()

The rule of thumb is that when responding to an actionHook or an eventHook (i.e. in a session callback for an event), use reply(), otherwise use send().

If within an actionHook event handler you first want to reply with a new set of verbs, then later in the same handler want to send yet another set of new verbs use send() for the second set. Only call reply() once per event callback.

Multiple servers sharing a single HTTP(s) server

Typically this module is used in a single-purpose application that is a jambonz websocket application. However, perhaps you want to handle other sorts of websocket traffic, outside of jambonz, through the same server. You can do so by passing an array of "external websocket servers" to the createEndpoint method, as show in the example below.

Simply pass an array of objects in the optional externalWss property, where each element contains a path and a WebSocketServer instance. When a connection is made to the path, your code will receive the connect event and the messages on that websocket and is responsible for responding.

Here is a simple example of an application that handles /foo and /bar in separate WebSocketServers from that used for the messaging with jambonz.

const {createServer} = require('http');
const {createEndpoint} = require('@jambonz/node-client-ws');
const server = createServer();
const { WebSocketServer } = require('ws');

// create two external websocket servers on this http server
const wssFoo = new WebSocketServer({ noServer: true });
const wssBar = new WebSocketServer({ noServer: true });

// paths /foo and /bar should come to us, node-client-ws will handle the rest
const makeService = createEndpoint({
  server,
  externalWss: [
    {
      path: '/foo',
      wss: wssFoo
    },
    {
      path: '/bar',
      wss: wssBar
    }
  ]
});

const logger = require('pino')({level: process.env.LOGLEVEL || 'info'});
const port = process.env.WS_PORT || 3000;

require('./lib/routes')({logger, makeService});

server.listen(port, () => {
  logger.info(`jambonz websocket server listening at http://localhost:${port}`);
});

// Handle connections and messages for /foo WebSocket server
wssFoo.on('connection', (ws) => {
  logger.info('connection to /foo');
  ws.on('message', (message) => {
    logger.info(`received message on /foo: ${message}`);
    ws.send('foo'); // Reply with 'foo' text frame
  });
});

// Handle connections and messages for /bar WebSocket server
wssBar.on('connection', (ws) => {
  logger.info('connection to /bar');
  ws.on('message', (message) => {
    logger.info(`received message on /bar: ${message}`);
    ws.send('bar'); // Reply with 'bar' text frame
  });
});

API

Function: createEndpoint

This function is the main export of the library.

createEndpoint({server, port, logger})

  • server {http.Server} A pre-created Node.js HTTP/S server.
  • port {Number} Optional, the port to listen on. If not provided it is assumed the application will call listen on the server object
  • logger {pinoLogger} Optional, a pino to use for logging.
  • externalWss {Array} Optional, an array of external WebSocket servers that you want to attach to this HTTP server (see example above)

This function returns a function makeService which the application can use to associate different services within the websocket server to different request URL paths.

Function: makeService

Creates a jambonz service, aka application.

makeService({path})

  • path {String} Identifies a request URL path for incoming connections which should route to this service.

This function returns an instance of a Client (short for Service Client).

Class: Client

This class represents a service client, or more generally a jambonz application.

Event: 'session:new'

Emitted when a new incoming call for this service has arrived.

  • session {Session} a single call or session that is being controlled on the jambonz server
  • path - {String} the request URL path of the incoming HTTP request

Class: Session

This class represents a unique call in progress on the jambonz server.

Event: 'close'

Emitted when the websocket has been closed from the jambonz end.

  • code {Number} the websocket close code
  • reason - {String} the reason, if supplied

Event: 'error'

Emitted when the underlying websocket connection has an error.

  • err {Error} the websocket error

Event: action hooks

As described above, each webhook that you configure in verbs that you send to jambonz will be emitted as events

  • evt {Object} event data

session.[verb](data)

All of the jambonz verbs are available as methods on the Session class. Please review the documentation for each verb on the jambonz.org website.

Calling these methods does not immediately send that verb to jambonz. The verbs are queued and sent only when send() or reply() is called.

session.send({execImmediate})

  • execImmediate {Boolean} When true, this instructs jambonz to begin executing these verbs immediately, flushing any verbs that were already queued or in progress. When false, the new verbs are appended to the current execution stack within jambonz. Default: true.

Sends currently queued verbs to jambonz.

session.reply()

Sends currently queued verbs to jambonz, associating it as an ack response to the last received actionHook.