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

ws-low-level

v1.0.0

Published

A Promise-based, low-level WebSocket server library

Downloads

11

Readme

ws-low-level

ws-low-level provides low-level functions to create a WebSocket server in Node.JS. It is promise-based and easy to get started with -- just use the example below.

The library is flexible and low-level enough to make your own customizations like:

  • customized messages on a connection close
  • adding a rate limiter
  • routes and endpoints (e.g., by parsingrequest.url)
  • adding support for WebSocket extension(s)
  • inspecting a ping message

Example WebSocket server

Here is a complete example of a Node.JS WebSocket server using ws-low-level. For example, create a file named websocket-server.js, place this code in the file, and run node websocket-server.js.

// websocket-server.js
import http from 'http';
import {
    sendHandshake,
    getMessagesFactory,
    sendFactory,
    prepareWebsocketFrame,
    prepareCloseFramePayload
} from 'ws-low-level';

const httpServer = http.createServer(
    function (
        request /* <http.IncomingMessage> */,
        response /* <http.ServerResponse> */
    ) {
        // Handle HTTP messages by verbs like GET and POST (not part of ws-low-level)
    }
);

httpServer.on('upgrade', function (
    request /* <http.IncomingMessage> */,
    socket /* <stream.Duplex> */ ,
    head /* <Buffer> websocket header */
) {
    console.log('upgrade');

    const send =
        sendFactory(socket);
    const getMessages =
        getMessagesFactory(
            socket, 
            { 
                maxInMemoryStoreSize: 2147483648 // 2 GB 
            }
        );

    (async () => {
        for await (
            const {
                payload /* <Promise> <Uint8Array> */,
                opcode /* Integer <Number> from 0 to 15 */,
                rsv1 /* Integer <Number> from 0 to 1 */,
                rsv2 /* Integer <Number> from 0 to 1 */,
                rsv3 /* Integer <Number> from 0 to 1 */,
                mask /* Integer <Number> from 0 to 1 */
            } of getMessages()
            ) {

            if (
                payload.length
                &&
                mask === 0
            ) {
                // No masking from client, close the connection with a status code
                send(
                    prepareWebsocketFrame(
                        prepareCloseFramePayload({
                            code: 1002,
                            reason: 'Websocket payload from client was not masked.'
                        }),
                        {
                            opcode: 0x8 /* Close */
                        }
                    )
                );
            }

            switch (opcode) {
                case 0x1:
                    // Text
                    const decoder =
                        new TextDecoder("utf-8");

                    console.log(
                        `received message ${decoder.decode(payload)}`
                    );

                    break;

                case 0x2:
                    // Binary
                    console.log(
                        `received message with payload length ${payload.length}`
                    );

                    break;

                case 0x9:
                    // Ping, respond with Pong
                    send(
                        prepareWebsocketFrame(
                            payload,
                            {
                                opcode: 0xA /* Pong */
                            }
                        )
                    );

                    break;

                case 0xA:
                    // Pong
                    console.log("Pong");

                    break;

                case 0x8:
                    // Close
                    console.log("Connection closed.");

                    break;

                default:
                    console.log(
                        `received message with opcode ${opcode} payload length ${payload.length}`
                    );
            }
        }
    })();

    const headers =
        [] /* Optional <Array> of well-formed header strings */;

    sendHandshake(
        request,
        socket,
        headers
    );

    // Send Ping
    send(
        prepareWebsocketFrame(
            new Uint8Array(0),
            {
                opcode: 0x9 /* Ping */
            }
        )
    );

    const encoder =
        new TextEncoder('utf-8');

    // Send UTF-8 encoded message
    send(
        prepareWebsocketFrame(
            new Uint8Array(
                encoder.encode("Hello from the WebSocket server.")
            ),
            {
                isUtf8: true
            }
        )
    );
});

httpServer.listen(3000);

Here is example html (place in an .html file like index.html) that would connect to the example server:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ws-low-level example webpage</title>
</head>
<body>
    <script>
        let ws = new WebSocket("ws://localhost:3000/");
        ws.onopen = function() {
            ws.send("Hello from the WebSocket");
        }
    </script>
</body>
</html>

Imports

The library exports the following functions. They can be imported and used in your Node webserver (see the example above).

sendHandshake

function sendHandshake (
    request /* <http.IncomingMessage> */, 
    socket /* <stream.Duplex> */,
    headers /* <Array> of well-formatted http header strings */
) {
    // ...
}

Send the handshake to establish the WebSocket connection. Pass the request and socket from http.on('upgrade'), and optionally pass an array of header strings, for example to include a sec-websocket-extension or sec-websocket-protocol.

getMessagesFactory

function getMessagesFactory (
    socket /* <stream.Duplex> */,
    /* Optional options object */
    {
        /* Optional <Integer> of max in-memory message store size, in bytes. This is not enforced 
         * on small messages, which do not necessarily make use of the in-memory store class 
        */
        maxInMemoryStoreSize 
    } = {}
) {
    // ...
}

Returns an async generator getMessages. Pass the socket object from http.on('upgrade'). The factory should only be called once per endpoint/route.

sendFactory

function sendFactory (
    socket /* <stream.Duplex> */
) {
    //...
}

Returns a function send that accepts a frame. (Prepare a frame with the function prepareWebsocketFrame from ws-low-level, below). Pass the socket object from http.on('upgrade'). The factory can be called multiple times to make multiple send functions.

prepareWebsocketFrame

function prepareWebsocketFrame (
    payload /* <Uint8Array> */,
    /* Optional options object */
    {
        isUtf8 = false /* <Boolean> */,
        
        /* For advanced usage */
        opcode /* Integer <Number> between 0 and 15 */,
        fin = 1 /* Integer <Number> between 0 and 1 */,
        rsv1 = 0 /* Integer <Number> between 0 and 1 */,
        rsv2 = 0 /* Integer <Number> between 0 and 1 */,
        rsv3 = 0 /* Integer <Number> between 0 and 1 */
    } = {}
) {
    //...
}

Make a WebSocket frame to prepare it for sending. Provide the payload, which is by default encoded as a binary frame (opcode 2). Optionally provide an options object { isUtf8: true } to designate that the payload is encoded as UTF-8 text (opcode 1). Other opcodes can be optionally provided by providing an opcode in the options object. The fin bit (for fragmenting messages) and the rsv1, rsv2 and rsv3 bits (for extensions) can also be set.

prepareCloseFramePayload

 function prepareCloseFramePayload (
    {
        code /* Integer <Number> from 1000 to 65536 */,
        reason = '' /* optional UTF-8 <String> */,
    
        /* for advanced usage */
        extensionData = new Uint8Array(0) /* optional <Uint8Array> */
    } = {}
) {
    // ...
}

Prepare the payload of a close frame. Provide a code and optionally a reason. Optionally provide extension data.

Copyright

Copyright (c) 2024 Jon Lachlan.

License

GNU Public License v.3

https://www.gnu.org/licenses/gpl-3.0.en.html