Discord Gateway bridge for ClopinetteAI — forwards messages to Cloudflare Workers via HMAC-signed HTTP. Bun, Docker, ~180 lines. https://clopinette.app
  • TypeScript 100%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Marcelo 8ced609b30 fix: rewrite reconnect logic to prevent cascade loop
Root cause: Invalid Session (op 9) scheduled its own connect() AND the
subsequent WS close event scheduled another — two reconnect paths racing
endlessly. Each one killed the other's fresh connection, accumulating
stale heartbeat timers that spammed "not ACKed" forever.

Changes:
- Single reconnect path: op 7/9/zombie all close the WS, only the close
  handler calls scheduleReconnect() — no more dual-connect races
- scheduleReconnect() deduplicates (refuses if timer already pending)
- All timers (heartbeat jitter, interval, reconnect) tracked and cleared
  atomically in clearAllTimers() on every connect()
- heartbeatAcked reset to true on new connections (prevents instant
  zombie detection on fresh WS)
- send() guards readyState === OPEN (prevents InvalidStateError crash)
- Custom close code 4900 for self-initiated reconnects (close handler
  ignores these to avoid double-scheduling)
- Unhandled exception/rejection handlers prevent silent process death
2026-04-02 16:13:52 +01:00
docker-compose.yml Discord Gateway bridge for Clopinette 2026-04-01 14:36:00 +01:00
index.ts fix: rewrite reconnect logic to prevent cascade loop 2026-04-02 16:13:52 +01:00
README.md docs: add README with setup, security, and architecture 2026-04-01 16:55:06 +01:00

Discord Gateway Bridge

Lightweight bridge that connects to the Discord Gateway WebSocket and forwards messages to a ClopinetteAI worker via HTTP.

Discord requires a persistent WebSocket connection (Gateway) to receive regular messages — there is no webhook push like Telegram or WhatsApp. This bridge maintains that connection and forwards MESSAGE_CREATE events to the Clopinette worker.

How it works

Discord Gateway (WSS)
  |
  v
Bridge (Bun, ~180 lines)
  |  - Maintains WebSocket + heartbeat
  |  - Handles reconnect/resume
  |  - Filters: DMs forwarded directly,
  |    guilds only on @mention or reply
  |
  v (HTTP POST, HMAC-SHA256 signed)
Clopinette Worker (/webhook/discord-bridge)
  |
  v
Durable Object (per-user or per-guild)

Security

  • HMAC-SHA256 signature over timestamp + body — the shared secret never transits in the request
  • Anti-replay — worker rejects requests older than 5 minutes
  • Timing-safe comparison on the worker side
  • No public ports — the bridge only initiates outbound connections (Discord WSS + Worker HTTPS)
  • Health check returns only ok or disconnected, no sensitive data

Setup

1. Deploy the Clopinette worker

Follow the core setup to configure Discord secrets and register slash commands:

# In clopinette-ai-dev/
wrangler secret put DISCORD_TOKEN
wrangler secret put DISCORD_PUBLIC_KEY
wrangler secret put DISCORD_APPLICATION_ID
bun run deploy

# Register slash commands + generate bridge secret
curl -X POST https://your-worker/api/admin/setup-discord \
  -H "Authorization: Bearer YOUR_API_AUTH_KEY"
# → returns { bridgeSecret, interactionsUrl, bridgeUrl }

2. Deploy the bridge

Set these environment variables on your Docker host (Coolify, etc.):

Variable Description
DISCORD_TOKEN Bot token from Discord Developer Portal
BRIDGE_SECRET From the setup-discord response above
WORKER_URL Your worker URL, e.g. https://agent.clopinette.app
docker compose up -d

3. Discord Developer Portal

  • Bot tab → enable Message Content Intent
  • General Information → set Interactions Endpoint URL to https://your-worker/webhook/discord
  • OAuth2 → URL Generator → scopes bot + applications.commands, permissions: View Channels, Send Messages, Embed Links, Attach Files, Read Message History, Add Reactions, Use Slash Commands

Behavior

Context Trigger Response
DM Any message Forwarded to worker
Server @mention or reply to bot Forwarded to worker
Server Regular message Ignored
Any Bot messages Ignored

Gateway features

  • Heartbeat with ACK tracking (zombie connection detection)
  • Automatic reconnect with exponential backoff (max 30s)
  • Session resume on disconnect (no message loss)
  • Graceful shutdown on SIGTERM/SIGINT

Requirements

  • Bun runtime (via oven/bun:1-slim Docker image)
  • A running ClopinetteAI worker with Discord secrets configured

License

MIT