@hapi/nes Documentation

repository·master·Indexed 19 days ago

https://github.com/hapijs/nes

A WebSocket adapter plugin for the hapi web framework that provides real-time, bidirectional communication capabilities. It enables hapi routes to be invoked via WebSockets and supports a pub/sub model with subscriptions, publishing, and broadcasting. The plugin integrates with hapi's authentication system and provides a dedicated client for both server-side and browser-side usage.

Tokens
14K
Snippets
48
Records
57
Agent score
66%

What's inside @hapi/nes

  1. What is @hapi/nes?

    master
    @hapi/nes is a WebSocket adapter plugin designed for use with the hapi web framework. It enables WebSocket capabilities for hapi routes, allowing for real-time, bidirectional communication. While optimized for the hapi ecosystem, it can also be used as a standalone module or with other web frameworks.
  2. Understand the nes Protocol message types

    master

    The nes protocol uses JSON messages exchanged over WebSockets. Messages are identified by a type field.

    Client-to-Server message types:

    • 'ping': Heartbeat response.
    • 'hello': Connection initialization and authentication.
    • 'reauth': Authentication refresh.
    • 'request': Endpoint request.
    • 'sub': Subscribe to a path.
    • 'unsub': Unsubscribe from a path.
    • 'message': Send a custom message.

    Server-to-Client message types:

    • 'ping': Heartbeat request.
    • 'hello': Connection initialization and authentication.
    • 'reauth': Authentication refresh.
    • 'request': Endpoint request.
    • 'sub': Subscribe to a path.
    • 'unsub': Unsubscribe from a path.
    • 'message': Send custom message.
    • 'update': A custom message push from the server.
    • 'pub': A subscription update.
    • 'revoke': Server forcefully removed the client from a subscription.

    Chunking: If a message is too large for a single WebSocket update, it is chunked. Chunks are prefixed with '+', except for the final chunk which is prefixed with '!'.

  3. Use Subscriptions and Publishing in Nes

    master

    Nes supports a pub/sub model where clients can subscribe to specific paths on the server.

    1. On the Server: Use server.subscription(path) to enable subscriptions for a specific path pattern, and server.publish(path, payload) to send data to all clients subscribed to that path.
    2. On the Client: Use client.subscribe(path, handler) to listen for updates. The handler receives the update (the payload) and flags.

    Subscribers only receive messages that match the path they subscribed to.

    // Server
    await server.register(Nes);
    server.subscription('/item/{id}');
    await server.start();
    server.publish('/item/5', { id: 5, status: 'complete' });
    
    // Client
    const client = new Nes.Client('ws://localhost');
    await client.connect();
    const handler = (update, flags) => {
        // update -> { id: 5, status: 'complete' }
    };
    client.subscribe('/item/5', handler);
  4. Manage connection heartbeats

    master

    To ensure the connection is still active when TCP cannot detect it, the server uses a heartbeat mechanism.

    1. Server sends: A message with type: 'ping'.
    2. Client responds: A message with type: 'ping' and a unique id.

    Client Timeout Logic: The client should assume the connection is closed if it has not heard from the server within the period of heartbeat.interval + heartbeat.timeout (provided during the hello handshake).

    // Server heartbeat request
    {
        type: 'ping'
    }
    
    // Client heartbeat response
    {
        type: 'ping',
        id: 6
    }
  5. Integrate @hapi/nes into a Hapi server

    master

    To add native WebSocket support to your Hapi application, register the @hapi/nes plugin with your Hapi server instance. This allows you to use nes features like route invocation, subscriptions, and broadcasting within your existing Hapi architecture.

    Note that the nes protocol version is 2.4.x, which is distinct from the module version.

    const Hapi = require('@hapi/hapi');
    const Nes = require('@hapi/nes');
    
    const server = new Hapi.Server();
    
    const start = async () => {
        await server.register(Nes);
        // ... configure routes and start server
        await server.start();
    };
    
    start();
  6. Import the Nes client for browser usage

    master

    When using @hapi/nes in a browser environment, you should avoid importing the full module to prevent loading unnecessary server-side code.

    If you are using CommonJS, import only the client module: require('@hapi/nes/lib/client')

    // Use this for browser-side clients to keep bundle size small
    const Nes = require('@hapi/nes/lib/client');
  7. Authenticate Nes client connections

    master

    Nes integrates with Hapi's authentication system. You can protect routes or subscriptions by applying standard Hapi authentication strategies to them.

    When connecting via the client, pass the authentication credentials in the connect() method's options object under the auth.headers key.

    // Server: Configure route with authentication
    server.route({
        method: 'GET',
        path: '/h',
        config: {
            id: 'hello',
            handler: (request, h) => {
                return `Hello ${request.auth.credentials.name}`;
            }
        }
    });
    
    // Client: Connect with Basic Auth headers
    const client = new Nes.Client('ws://localhost');
    await client.connect({
        auth: {
            headers: { authorization: 'Basic am9objpzZWNyZXQ=' }
        }
    });
    const payload = await client.request('hello');
  8. Register the @hapi/nes plugin

    master

    The nes plugin is registered using the standard hapi server.register() method. You can provide several optional configuration options to control connection lifecycles, message handling, and security.

    await server.register({
        plugin: require('@hapi/nes'),
        options: {
            onConnection: (socket) => {
                console.log('New connection:', socket.id);
            },
            onDisconnection: (socket) => {
                console.log('Disconnected:', socket.id);
            },
            onMessage: async (socket, message) => {
                // Handle custom client messages
                return { status: 'received' };
            }
        }
    });
  9. Configure @hapi/nes authentication

    master

    The auth option in the plugin registration allows you to define how clients authenticate. Supported types include:

    • 'direct' (Default): The plugin creates an internal endpoint. Clients provide credentials directly during the connection process (e.g., via client.connect({ auth: credentials })). This is best when the application can safely expose credentials to the JavaScript layer.
    • 'cookie': The client must manually call a public HTTP endpoint to set a cookie before connecting. The browser then sends this cookie during the WebSocket handshake. This is ideal for browser-based applications to avoid exposing credentials to JS.
    • 'token': The client calls a public HTTP endpoint which returns an encrypted token. The client then passes this token to client.connect({ auth: token }). This is useful for non-browser clients.

    Key Authentication Options:

    • type: 'direct', 'cookie', or 'token'.
    • endpoint: The HTTP path for the auth endpoint (defaults to '/nes/auth').
    • id: The authentication endpoint identifier (defaults to nes.auth).
    • route: The hapi route config.auth settings for the auth endpoint.
    • password: The password used by iron to encrypt cookies/tokens. It is highly recommended to set this manually to ensure consistency across restarts and distributed systems.
    • cookie: The cookie name when using type: 'cookie' (defaults to 'nes').
    • isSecure, isHttpOnly, path, domain, ttl: Standard cookie configuration options.
    • index: If true, authenticated sockets with a user property in credentials are mapped for use in server.broadcast() calls.
    await server.register({
        plugin: require('@hapi/nes'),
        options: {
            auth: {
                type: 'cookie',
                endpoint: '/my/auth/path',
                cookie: 'my_session_cookie',
                password: 'a-very-secure-long-password'
            }
        }
    });
  10. Configure @hapi/nes heartbeat and connection limits

    master

    You can manage connection stability and resource usage using the following options during registration:

    Heartbeat (Keep-alive): Set heartbeat to false to disable, or provide an object:

    • interval: Time between heartbeat messages in ms (defaults to 15000).
    • timeout: Time to wait for a response before disconnecting the client (defaults to 5000).

    Connection Limits:

    • maxConnections: Limits the total number of simultaneous client connections (defaults to false).
    • maxConnectionsPerUser: Limits the number of connections per authenticated user (requires auth.index: true).
    • timeout: (Under auth) Milliseconds to wait for a 'hello' message after connection before disconnecting (defaults to 5000).
    await server.register({
        plugin: require('@hapi/nes'),
        options: {
            heartbeat: {
                interval: 10000,
                timeout: 2000
            },
            maxConnections: 100
        }
    });
  11. Understand NesError types

    master

    Errors thrown by the Nes client include a type property to help distinguish the source of the error. Common error types include:

    • timeout: The request or connection timed out.
    • disconnect: The server disconnected.
    • server: An error returned by the server (includes statusCode, data, headers, and path).
    • protocol: A violation of the Nes protocol.
    • ws: A WebSocket-level error.
    • user: A client-side error (e.g., invalid path or calling connect() while already connected).
  12. Register the Nes plugin with Hapi

    master

    To use Nes, register it as a plugin in your Hapi server. Nes provides real-time, bidirectional communication via WebSockets. Upon registration, it decorates the server object with methods for broadcasting and publishing, and the request object with access to the current socket.

    const Hapi = require('@hapi/hapi');
    const Nes = require('@hapi/nes');
    
    const init = async () => {
        const server = Hapi.server({
            port: 3000,
            host: 'localhost'
        });
    
        await server.register({
            plugin: Nes,
            options: {
                // Configuration options go here
            }
        });
    
        await server.start();
    };
    
    init();