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 🙏

© 2026 – Pkg Stats / Ryan Hefner

discord-gateway-cloudflare-do

v0.1.2

Published

Cloudflare Durable Objects Discord Gateway for Chat SDK — persistent WebSocket with session resume, heartbeat via alarms, and automatic reconnection

Downloads

275

Readme

discord-gateway-cloudflare-do

CI npm version npm downloads License: MIT

Persistent Discord Gateway WebSocket via Durable Objects. Forwards messages and reactions to your Worker as HTTP POSTs — no discord.js, no Node.js, no cron.

npm install discord-gateway-cloudflare-do

Quick Start

import { DiscordGatewayDO, getGatewayStub } from "discord-gateway-cloudflare-do";

export { DiscordGatewayDO };

interface Env {
  DISCORD_GATEWAY: DurableObjectNamespace<DiscordGatewayDO>;
  DISCORD_BOT_TOKEN: string;
  DISCORD_GATEWAY_SECRET: string;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const url = new URL(request.url);

    // Receive forwarded Gateway events
    if (url.pathname === "/webhook" && request.method === "POST") {
      const token = request.headers.get("x-discord-gateway-token");
      if (token !== env.DISCORD_GATEWAY_SECRET) {
        return new Response("Unauthorized", { status: 401 });
      }
      const event = (await request.json()) as {
        type: string;
        timestamp: number;
        data: any;
      };
      console.log(event.type, event.data);
      return new Response("OK");
    }

    // Connect the Gateway (one-time — persists across deploys and evictions)
    if (url.pathname === "/connect" && request.method === "POST") {
      const gateway = getGatewayStub({ namespace: env.DISCORD_GATEWAY });
      return Response.json(
        await gateway.connect({
          botToken: env.DISCORD_BOT_TOKEN,
          webhookUrl: `${url.origin}/webhook`,
          webhookSecret: env.DISCORD_GATEWAY_SECRET,
        }),
      );
    }

    return new Response("Not found", { status: 404 });
  },
};
// wrangler.jsonc
{
  "name": "my-bot",
  "main": "src/index.ts",
  "compatibility_date": "2025-04-01",
  "durable_objects": {
    "bindings": [
      { "name": "DISCORD_GATEWAY", "class_name": "DiscordGatewayDO" }
    ]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["DiscordGatewayDO"] }
  ]
}

Deploy, then connect once:

npx wrangler deploy
curl -X POST https://my-bot.example.com/connect

That's it. The connection survives Worker redeployments and DO evictions.

Discord Prerequisites

Use Discord's official docs to configure your app and bot before calling /connect:

If intents are misconfigured, Discord will close with 4014 and the DO will stop reconnecting until config is fixed and /connect is called again.

How It Works

                     ┌──────────────────────┐
                     │ Discord Gateway API  │
                     └──────────┬───────────┘
                           WebSocket
                     ┌──────────┴───────────┐
                     │ DiscordGatewayDO     │
                     │  • heartbeat (alarm) │
                     │  • session resume    │
                     │  • auto-reconnect    │
                     └──────────┬───────────┘
                          HTTP POST
                  x-discord-gateway-token: <webhookSecret>
                  { type, timestamp, data }
                     ┌──────────┴───────────┐
                     │ Your Worker          │
                     └──────────────────────┘

The DO maintains a WebSocket to Discord and forwards events as HTTP POSTs to your webhook URL with:

  • Header: x-discord-gateway-token: <webhookSecret> (or bot token for backward compatibility)
  • Body: { type: "GATEWAY_MESSAGE_CREATE", timestamp: 1234567890, data: { ... } }

Only three event types are forwarded:

| Discord Event | Forwarded As | |---|---| | MESSAGE_CREATE | GATEWAY_MESSAGE_CREATE | | MESSAGE_REACTION_ADD | GATEWAY_MESSAGE_REACTION_ADD | | MESSAGE_REACTION_REMOVE | GATEWAY_MESSAGE_REACTION_REMOVE |

API

getGatewayStub(options)

Returns a typed DO stub.

const gateway = getGatewayStub({
  namespace: env.DISCORD_GATEWAY, // required — DO namespace binding
  name: "default",                // optional — instance name (for multi-agent)
  locationHint: "enam",           // optional — DO location hint
});

gateway.connect(credentials)

Stores credentials and opens a WebSocket to the Discord Gateway.

await gateway.connect({
  botToken: "MTk...",
  webhookUrl: "https://my-bot.example.com/webhook",
  webhookSecret: "your-random-webhook-secret",
});
// → { status: "connecting" }

Returns { status: "connecting" } on success, { error: string } on failure. The webhook URL must be HTTPS.

webhookSecret is optional but strongly recommended. If omitted, the DO falls back to using botToken in the forwarding header for backward compatibility.

gateway.disconnect()

Closes the WebSocket and clears all stored credentials and state.

await gateway.disconnect();
// → { status: "disconnected" }

gateway.status()

await gateway.status();
// → { status: "connected", sessionId: "...", connectedAt: "...", sequence: 42, reconnectAttempts: 0 }

Status is "connected", "connecting", or "disconnected".

Multi-Agent

One DO instance per bot — use the name parameter:

const gateway = getGatewayStub({
  namespace: env.DISCORD_GATEWAY,
  name: agentId, // "agent-1", "agent-2", etc.
});

await gateway.connect({
  botToken: agentBotToken,
  webhookUrl: `https://my-app.example.com/webhook?agent=${agentId}`,
});

Chat SDK

Drop-in replacement for the Chat SDK's startGatewayListener() — same forwarding format, so handleWebhook() accepts events without changes.

npm install chat @chat-adapter/discord chat-state-cloudflare-do discord-gateway-cloudflare-do

Both HTTP Interactions (slash commands) and forwarded Gateway events (messages, reactions) land on the same webhook endpoint. The Chat SDK auto-detects which type based on the x-discord-gateway-token header.

For current Chat SDK compatibility, leave webhookSecret unset (or set it equal to botToken) so the forwarded header matches what the adapter expects.

See examples/chat-sdk/ for a complete working example.

Resilience

| Scenario | Behavior | |---|---| | WebSocket close | Exponential backoff (1s → 5min cap) with jitter | | Missed heartbeat | Detected after 2× interval; triggers reconnect | | Op 7 (Reconnect) | Immediate reconnect with 1s minimum delay | | Op 9 (Invalid Session) | 1–5s random delay, then re-identify or resume | | DO eviction | Next alarm detects lost WebSocket; reconnects | | Reconnect storm | Rate limited (5 per 60s); falls back to backoff |

Session resume (op 6) replays missed events whenever a session ID and sequence number are available.

Examples

Complete examples in examples/:

| Example | Description | |---|---| | standalone/ | Plain Worker, no framework — handle events directly | | chat-sdk/ | Chat SDK + @chat-adapter/discord + chat-state-cloudflare-do |

Development

npm install          # Install dependencies
npm run build        # Build with tsup
npm run typecheck    # TypeScript check
npm test             # Run tests (29 tests)

Zero dependencies — only imports from cloudflare:workers (provided by the runtime). 14KB bundled.

License

MIT