astro-bun-websocket

1.0.1 • Public • Published

astro-bun-websocket 🔌

This Astro integration provides an adapter for Astro that allows you to host SSG and SSR sites on Bun, with realtime features using WebSocket.

Why astro-bun-websocket?

The astro-bun-websocket integration allows you to handle WebSocket connections in your Astro project and deploy it on Bun, with realtime features. You no longer need to maintain a separate WebSocket server and complicated build processes to add realtime features. You can handle WebSocket requests directly in your API Routes, middleware, and even in the frontmatter of your Astro pages!

As a fork of the @NuroDev/astro-bun adapter, it provides the same configuration options, and remains backwards compatible with its features and behavior.

Installation

Manual Install

First, install the astro-bun-websocket package using your package manager. If you're using npm or aren't sure, run this in the terminal:

npm install astro-bun-websocket

Then, add this integration to your astro.config.* file using the adapter property:

    // astro.config.mjs
    import { defineConfig } from "astro/config"
-    import bun from "@nurodev/astro-bun"
+    import bunWebSocket from "astro-bun-websocket"

    export default defineConfig({
        // ...
-        adapter: bun()
+        adapter: bunWebSocket(),
        // ...
    });

Usage

The integration adds an upgradeWebSocket() method to the context.locals object. This method returns an object with response and socket fields.

// src/pages/api/socket.ts
export const GET: APIRoute = ctx => {
    if (ctx.request.headers.get("upgrade") === "websocket") {
        const { response, socket } = ctx.locals.upgradeWebSocket()
        socket.onmessage = event => {
            if (event.data === "ping") {
                socket.send("pong")
            }
        }
        return response
    }
    return new Response("Upgrade required", { status: 426 })
}

The response field provides a Response object that you can return to accept the WebSocket upgrade request. The socket field is a WebSocket object, which allows you to send messages to the browser and receive messages from it. This API of the WebSocket is fully compatible with the browser's WebSocket API, so you can program the server WebSocket in the same way you would program the browser WebSocket.

Note that this integration does not require any special client-side WebSocket library, you can use the native WebSocket API in the browser. Here is an example of the client-side code that connects to the above server WebSocket:

---
// src/pages/index.astro
---
<script>
    const socket = new WebSocket(`${location.origin}/api/socket`)
    socket onopen = event => {
        socket.send("ping")
    }
    socket.onmessage = event => {
        console.log(event.data)
    }
</script>

To simplify the detection of whether a request can be upgraded to a WebSocket connection, the adapter also adds locals.isUpgradeRequest to the context. This value is true if the request can be upgraded to a WebSocket connection, and false otherwise.

- if (ctx.request.headers.get("upgrade") === "websocket") { ... }
+ if (ctx.locals.isUpgradeRequest) { ... }

Authorizing and rejecting requests

If the endpoint returns any response other than the one provided by upgradeWebSocket(), the upgrade request will be rejected, and a WebSocket connection will not be established. This enables you to easily implement authorization logic or other checks before accepting the upgrade request.

For example, the following code shows how you would check for the presence of a cookie:

// src/pages/api/socket.ts
export const GET: APIRoute = ctx => {
    const cookie = ctx.cookies.get("xyz")
    if (!cookie) {
        return new Response("Unauthorized", { status: 401 })
    }
    if (!ctx.locals.isUpgradeRequest) {
        return new Response("Upgrade Required", { status: 426 })
    }
    const { response, socket } = ctx.locals.upgradeWebSocket()
    handleWebSocket(socket)
    return response
}

When a WebSocket connection fails to establish, browsers do not provide specific information in the error event about the cause. A WebSocket connection can close "cleanly" if it was successfully established and then intentionally closed. Alternatively, it can close non-cleanly if the server becomes unreachable due to network issues or if the connection could not be established in the first place. See the MDN documentation for more information: CloseEvent: wasClean property - Web APIs | MDN.

Troubleshooting

Error: "The request must be an upgrade request to upgrade the connection to a WebSocket."

This error occurs when a connection upgrade attempt is made using locals.upgradeWebSocket() on a request that does not have the necessary headers.

On the server, verify that the request is an upgrade request by checking locals.isUpgradeRequest before calling locals.upgradeWebSocket().

It is important to know that in the browser, the fetch API cannot be used to send an upgrade request. Make sure that you are instantiating the built-in WebSocket class. See examples on MDN: WebSocket - Web APIs | MDN.

For additional help, check out the Discussions tab on the GitHub repo.

Contributing

This package is maintained by lilnasy independently from Astro.

The code for this package is commited to the repository as a series of patches applied on top of the NuroDev/astro-bun repository, which is where the code for the @NuroDev/astro-bun adapter is maintained. Additionally, the NuroDev/astro-bun repository is added as a git submodule to make it easier to update the patches. The package.json file contains scripts to automatically manage the upstream repository and the patches.

To introduce a change, make sure you're in packages/bun-websocket directory:

../gratelets/ $ cd packages/bun-websocket

Then, run the load_patches script using pnpm to clone the upstream repository and apply the patches:

../gratelets/packages/bun-websocket/ $ pnpm run load_patches

Now, you can browse around the code by going into the NuroDev/astro-bun submodule:

../gratelets/packages/bun-websocket/ $ cd NuroDev/astro-bun
../bun-websocket/NuroDev/astro-bun/ $ code .

Note that dependencies would need to separately be installed in the NuroDev/astro-bun submodule.

../bun-websocket/NuroDev/astro-bun/ $ bun install

After you've made the changes you want, you can commit them as normal. However, instead of pushing the changes, you would update the patches by running create_patches script using pnpm:

../bun-websocket/NuroDev/astro-bun/ $ pnpm run create_patches

This will add and update patches present in packages/bun-websocket with the changes you've made.

Now, you can commit these patch files to the gratelets repository, and push.

../bun-websocket/NuroDev/astro-bun/ $ cd ../..
../gratelets/packages/bun-websocket/ $ git commit -m "fix bug"
../gratelets/packages/bun-websocket/ $ git push

Changelog

See CHANGELOG.md for a history of changes to this integration.

Package Sidebar

Install

npm i astro-bun-websocket

Weekly Downloads

124

Version

1.0.1

License

MIT

Unpacked Size

40.3 kB

Total Files

14

Last publish

Collaborators

  • arshx