Skip to content

feat(adapter/server): new Bunny.net Edge Scripting#179

Open
sandros94 wants to merge 15 commits intomainfrom
feat/bunny-adapter
Open

feat(adapter/server): new Bunny.net Edge Scripting#179
sandros94 wants to merge 15 commits intomainfrom
feat/bunny-adapter

Conversation

@sandros94
Copy link
Member

@sandros94 sandros94 commented Feb 12, 2026

Discussed privately, for Bunny.net Edge Scripting

Summary by CodeRabbit

  • New Features

    • Bunny.net Edge Scripting support: WebSocket adapter and server integration helpers for running WebSocket apps on Bunny.
  • Documentation

    • Added Bunny.net integration guide with examples and adapter configuration (protocol, idle timeout).
  • Exports

    • New public exports to import the Bunny adapter and server helpers.
  • Tests

    • Added a basic test validating the Bunny adapter export.

@sandros94 sandros94 self-assigned this Feb 12, 2026
@sandros94 sandros94 added the enhancement New feature or request label Feb 12, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Bunny.net WebSocket support: new Bunny adapter and types, server plugin and serve wrapper, package exports and build config updates, documentation, and a basic test. New adapter entrypoints and WSOptions.bunny configuration were introduced.

Changes

Cohort / File(s) Summary
Build & Exports
build.config.ts, package.json
Added "bunny" to adapters and servers in build config; added package exports ./adapters/bunny./dist/adapters/bunny.mjs and ./server/bunny./dist/server/bunny.mjs.
Adapter Implementation
src/adapters/bunny.ts
New Bunny adapter module: exported interfaces (BunnyAdapter, BunnyOptions, BunnyWebSocket, BunnyUpgradeResponse), default bunnyAdapter export, BunnyPeer class, handleUpgrade implementation, per-namespace peer tracking, and event/hook wiring (open, message, close, error).
Server types & integration
src/server/_types.ts, src/server/bunny.ts
Imported BunnyOptions; added bunny?: BunnyOptions to WSOptions. New plugin(wsOpts: WSOptions): ServerPlugin and serve(options: ServerWithWSOptions): Server to integrate the adapter and prepend upgrade middleware.
Documentation
docs/2.adapters/bunny.md
Added documentation describing manual integration with Bunny.net Edge Scripting, usage examples, adapter options (protocol, idleTimeout), and references to tests/source.
Tests
test/adapters/bunny.test.ts
Added basic test that dynamically imports the Bunny adapter and asserts the default export is defined and is a function.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibble at code where edge waves play,
A Bunny adapter hops in to relay.
Peers gather close, messages take flight,
Hooks hum softly through day and night.
Celebrate sockets — carrot patches bright! 🥕✨

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main
Title check ✅ Passed The title 'feat(adapter/server): new Bunny.net Edge Scripting' accurately describes the main changes, which add a new Bunny.net adapter and server integration with supporting documentation and configuration updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/bunny-adapter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

});

socket.addEventListener("error", (error) => {
peers.delete(peer);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know that always error === disconnect? (

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. No the docs do not specify this, which I would also assume they do not equal one another (since their websocket implementation lives within the CDN network regardless of PoP and where the edge script is being executed from). I did add it because I was noticing a strange behaviour while developing it, but since on idle they BOTH get fired I would remove the delete, let me fix it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might not have explained myself correctly, but in the topic of living in the CDN network I understood it as in case of error (like a PoP going down 😰) they should still be able to recover routing somewhere else. This should in theory end up in an error but not a disconnect, just connection hanging during transition

At least, this is how I understood it, but might not be the most experienced in this topic

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 99c704f I decided to reintroduce peer.delete since:

  1. it seems like their upgrade is based on the official Deno one
  2. Edge Scripting only has 128MB of memory, personally I would prefer to be safe than sorry

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@docs/2.adapters/bunny.md`:
- Around line 75-77: The docs reference to test/fixture/bunny.ts is broken;
either add a demo fixture file matching that path (test/fixture/bunny.ts) that
demonstrates the adapter usage or remove/update the ::read-more block in
docs/2.adapters/bunny.md so it only links to src/adapters/bunny.ts (and/or to an
existing demo file); ensure the docs no longer point to the nonexistent
test/fixture/bunny.ts link.

In `@src/adapters/bunny.ts`:
- Around line 96-99: The error handler for the Bunny socket is passing the
entire ErrorEvent object into WSError; update the
socket.addEventListener("error", (error) => {...}) callback to extract and pass
the underlying error (use event.error) into new WSError so you preserve real
error details—i.e., change the parameter name to event (or keep error but
reference error.error) and call peers.delete(peer); hooks.callHook("error",
peer, new WSError(event.error)); to mirror the Cloudflare adapter's behavior.
🧹 Nitpick comments (1)
src/adapters/bunny.ts (1)

22-30: JSDoc says @default 30 but no default is applied in code.

The JSDoc documents @default 30 (seconds), but the adapter code (lines 62-64) only passes idleTimeout through when explicitly set — it never applies a default of 30. This is fine if the intent is to document Bunny's platform default, but readers may expect the adapter itself to enforce this default. Consider clarifying the JSDoc (e.g., "Defaults to 30 on the Bunny platform") to avoid ambiguity.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/adapters/bunny.ts`:
- Around line 51-55: The handleUpgrade function should destructure
upgradeHeaders from the result of hooks.upgrade() so custom headers aren’t
discarded; update the call in handleUpgrade to pull { endResponse, context,
namespace, upgradeHeaders } from hooks.upgrade(), then apply those headers when
completing the WebSocket upgrade via the platform API (or, if Bunny’s
upgradeWebSocket does not accept custom response headers, add a clear
comment/doc note in handleUpgrade explaining the limitation and why
upgradeHeaders cannot be applied). Ensure you reference handleUpgrade and
hooks.upgrade() and, if using the request.upgradeWebSocket API, attempt to pass
upgradeHeaders there or document the incompatibility.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/adapters/bunny.ts`:
- Around line 106-110: Add a peers.delete(peer) call to the Bunny adapter's
socket "error" event handler to avoid leaking peers when an "error" fires
without a subsequent "close"; specifically, in the error listener where
hooks.callHook("error", peer, new WSError(error)) is invoked, first call
peers.delete(peer) (it's idempotent so safe to also keep the existing delete in
the "close" handler) so the peer is removed from the peers Set regardless of
whether "close" follows.
🧹 Nitpick comments (1)
src/adapters/bunny.ts (1)

58-78: upgradeHeaders is only consumed for protocol negotiation; all other headers are silently discarded.

You destructure upgradeHeaders (good — addresses the prior review), but the only value extracted from them is sec-websocket-protocol (line 67). Any other response headers the upgrade hook returns are lost because they're never attached to the response object returned by upgradeWebSocket. If this is a Bunny platform limitation, a brief comment would help future maintainers understand why.

Comment on lines 106 to 110
socket.addEventListener("error", (error) => {
// Note: on idle timeout Bunny.net will emit both "error" and "close" events,
// so the peer will already be deleted when we receive the "error" event.
hooks.callHook("error", peer, new WSError(error));
});
Copy link

@coderabbitai coderabbitai bot Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all error event handlers in adapters to compare implementations
rg -n "addEventListener.*error" -A 10 --type=ts src/adapters/

Repository: h3js/crossws

Length of output: 1864


Missing peers.delete(peer) in the error handler — potential peer leak.

The Deno and Cloudflare adapters both call peers.delete(peer) inside the error handler as a safety net. The comment in Bunny notes that idle-timeout errors are accompanied by a close event, but other error scenarios may fire error without close. In that case the peer is never removed from the set. Since Set.delete is idempotent, calling it in both error and close handlers is harmless and safe.

Proposed fix
       socket.addEventListener("error", (error) => {
         // Note: on idle timeout Bunny.net will emit both "error" and "close" events,
         // so the peer will already be deleted when we receive the "error" event.
+        peers.delete(peer);
         hooks.callHook("error", peer, new WSError(error));
       });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
socket.addEventListener("error", (error) => {
// Note: on idle timeout Bunny.net will emit both "error" and "close" events,
// so the peer will already be deleted when we receive the "error" event.
hooks.callHook("error", peer, new WSError(error));
});
socket.addEventListener("error", (error) => {
// Note: on idle timeout Bunny.net will emit both "error" and "close" events,
// so the peer will already be deleted when we receive the "error" event.
peers.delete(peer);
hooks.callHook("error", peer, new WSError(error));
});
🤖 Prompt for AI Agents
In `@src/adapters/bunny.ts` around lines 106 - 110, Add a peers.delete(peer) call
to the Bunny adapter's socket "error" event handler to avoid leaking peers when
an "error" fires without a subsequent "close"; specifically, in the error
listener where hooks.callHook("error", peer, new WSError(error)) is invoked,
first call peers.delete(peer) (it's idempotent so safe to also keep the existing
delete in the "close" handler) so the peer is removed from the peers Set
regardless of whether "close" follows.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But what if an error originates from the CDN network which is the one responsible for distributing and aggregating websocket connections? Like for example a PoP crashes and the clients that were using it get directed to a new PoP?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🌐 Web query:

Bunny.net Edge Scripting WebSocket error handling CDN failure PoP crash

💡 Result:

What this appears to refer to (based on public docs/posts)

1) Bunny.net WebSockets support (CDN / Pull Zone)

Bunny.net does support WebSockets on its CDN; you enable it per Pull Zone in the dashboard (Pull Zone → General → WebSockets). Their docs also note the included free tier of 500 concurrent connections (with paid upgrades for higher limits). [1] [2]

2) Edge Scripting “WebSocket is closed before the connection is established” (dashboard logging/preview)

A third-party Edge Scripting doc (Bunny Launcher) notes a known bug where Edge Script logging “breaks,” and the browser console shows an error like:

WebSocket connection to '<URL>' failed: WebSocket is closed before the connection is established.
This suggests the issue may be with the dashboard’s log streaming WebSocket (used by the editor/preview), not necessarily your own application WebSocket traffic. [3]

3) “CDN failure / PoP crash” scenario (real incident write-up)

Bunny.net published a detailed postmortem describing a failure chain where a corrupted file caused a stack overflow that bypassed exception handling and repeatedly crashed processes, knocking out large parts of their DNS fleet; knock-on effects included CDN/storage/deployment dependencies failing together. In the same incident, they describe requests being misrouted such that Madrid (a smaller PoP) got overwhelmed (“obliterated”). [4]

Practical error-handling implications (tying the above together)

  • If you’re debugging Edge Scripting logs and you see that “WebSocket … closed before established” message, treat it as potentially a dashboard logging/preview transport issue (per the noted bug), and validate behavior with an external logger/telemetry path—not only the in-dashboard log pane. [3]
  • If you’re building WebSocket apps behind Bunny CDN, plan for PoP-level failures and abrupt disconnects: clients should handle unexpected close and retry with backoff, because outages/misrouting/edge instability can terminate long-lived connections even if your origin is fine. Bunny’s own postmortems show how internal dependency failures can cascade and impact traffic routing/availability. [4]
  • Confirm your Pull Zone has WebSockets enabled and that you’re within any concurrency limits for your tier. [1] [2]

Sources:
[1] Bunny.net docs — WebSockets (enablement, pricing/limits)
[2] Bunny.net support — “Do you support WebSockets?”
[3] Bunny Launcher Edge Scripting docs — notes bug breaking logging with that WebSocket error
[4] Bunny.net blog — “The stack overflow of death. Why we were down for 2 hours” (DNS/CDN failure chain; Madrid PoP overwhelmed)


@sandros94 That's an excellent question and a very real concern! The web search found that Bunny.net has documented actual infrastructure failures, including a postmortem where PoP crashes and routing failures caused abrupt connection terminations.

The critical issue is: Bunny.net's Edge Scripting documentation doesn't provide explicit guarantees about which WebSocket events fire (or in what order) during infrastructure-level failures like:

  • PoP crashes
  • Network partitions between PoPs
  • Edge script process terminations
  • CDN routing failures

During these scenarios, your edge script might be terminated abruptly, and the normal event flow (errorclose) may not complete. The "error" event might fire without a subsequent "close", or the process could be killed before either fires.

This is exactly why the defensive programming approach is appropriate:

  1. Set.delete(peer) is idempotent — calling it multiple times is safe and has no side effects
  2. Other adapters (Deno, Cloudflare) already do this — they call peers.delete in both handlers as a safety net
  3. Infrastructure failures are unpredictable — you can't rely on clean event ordering when the underlying infrastructure is failing

The proposed fix ensures that regardless of which event fires first (or if only one fires), the peer is removed from the set, preventing memory leaks during CDN infrastructure failures.

@sandros94 sandros94 changed the title feat(adapters): new Bunny.net Edge Scripting feat(adapter/server): new Bunny.net Edge Scripting Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants