Compare commits
No commits in common. "main" and "probablycorey/fix-proxy-404s" have entirely different histories.
main
...
probablyco
29
CLAUDE.md
29
CLAUDE.md
|
|
@ -2,14 +2,24 @@
|
|||
|
||||
This is a [toes](/Users/corey/code/toes) app. See the [toes CLAUDE.md](/Users/corey/code/toes/CLAUDE.md) for the framework docs.
|
||||
|
||||
Wraps the Tronbyt Go server (self-hosted Tidbyt replacement) as a toes-managed subprocess. Bun proxies all HTTP and WebSocket traffic to the Go binary.
|
||||
Wraps the Tronbyt Go server (self-hosted Tidbyt replacement) as a toes-managed subprocess. Bun proxies all HTTP and WebSocket traffic to the Go binary over a unix socket.
|
||||
|
||||
## Key Files
|
||||
## How It Works
|
||||
|
||||
- `src/server.ts` — entry point, creates Bun server, calls spawn
|
||||
- `src/proxy.ts` — HTTP + WebSocket proxy from Bun to Go binary on port 8000
|
||||
- `src/binary.ts` — Go binary lifecycle: download, spawn, health checks, shutdown
|
||||
- `bin/` — Go binary (gitignored, auto-downloaded per-platform)
|
||||
```
|
||||
Tidbyt device → tronbyt.toes.local → toes → Bun (PORT) → Go binary (unix socket)
|
||||
```
|
||||
|
||||
- `src/server/index.tsx` — spawns Go binary, proxies HTTP + WebSocket, health checks
|
||||
- `bin/` — pre-built Go binary (gitignored, per-platform)
|
||||
- No UI of its own — Go server serves its own web dashboard
|
||||
|
||||
## Setup
|
||||
|
||||
1. Download the binary for your platform from https://github.com/tronbyt/server/releases
|
||||
2. Place it in `bin/` (e.g. `bin/tronbyt-server-darwin-arm64`)
|
||||
3. `chmod +x bin/tronbyt-server-*`
|
||||
4. On macOS: System Settings → Privacy & Security → Allow Anyway
|
||||
|
||||
## Env Vars
|
||||
|
||||
|
|
@ -19,3 +29,10 @@ Tronbyt-specific vars (set via toes env config):
|
|||
- `PRODUCTION` — `true` enables firmware downloads and system apps (default)
|
||||
- `SINGLE_USER_AUTO_LOGIN` — `true` for home network (default)
|
||||
- `SYSTEM_APPS_AUTO_REFRESH` — `true` to keep community apps updated (default)
|
||||
|
||||
## Device Config
|
||||
|
||||
Set the Tidbyt Image URL to:
|
||||
```
|
||||
http://tronbyt.toes.local/<device-id>/next
|
||||
```
|
||||
|
|
|
|||
54
README.md
54
README.md
|
|
@ -2,11 +2,7 @@
|
|||
|
||||
Run a [Tronbyt](https://github.com/tronbyt/server) server as a toes app. Tronbyt is a self-hosted replacement for Tidbyt's cloud — it renders Starlark apps into WebP frames and pushes them to Tidbyt LED displays over your local network.
|
||||
|
||||
This app spawns the Tronbyt Go binary as a subprocess and proxies all traffic (HTTP + WebSocket) through Bun. The Go server handles everything — web dashboard, device connections, app rendering. Bun sits in front and makes it a good toes citizen.
|
||||
|
||||
```
|
||||
Tidbyt device → tronbyt.toes.local → toes (port 80) → Bun (PORT) → Go binary (port 8000)
|
||||
```
|
||||
This app spawns the pre-built Tronbyt Go binary as a subprocess and proxies all traffic (HTTP + WebSocket) to it over a unix socket. The Go server handles everything — web dashboard, device connections, app rendering. Bun just sits in front and makes it a good toes citizen.
|
||||
|
||||
## Install
|
||||
|
||||
|
|
@ -17,13 +13,13 @@ git remote add toes http://git.toes.local/tronbyt
|
|||
git push toes main
|
||||
```
|
||||
|
||||
Pushing to the git remote deploys the app, runs `bun install`, and starts it automatically.
|
||||
Pushing to the git tool deploys the app, runs `bun install` (which downloads the binary), and starts it automatically.
|
||||
|
||||
### 2. The binary
|
||||
|
||||
The Go binary auto-downloads from GitHub releases on first start if it's not already present. It's gitignored since it's platform-specific and ~50MB.
|
||||
The Tronbyt Go binary is downloaded automatically during `bun install` via the postinstall script. It assumes you're running on a Raspberry Pi (linux-arm64) — which is what toes is designed for.
|
||||
|
||||
To download manually:
|
||||
The binary is gitignored since it's platform-specific and ~50MB. If you need to re-download it or the postinstall didn't run, you can grab it manually:
|
||||
|
||||
```sh
|
||||
curl -L -o bin/tronbyt-server-linux-arm64 \
|
||||
|
|
@ -31,16 +27,14 @@ curl -L -o bin/tronbyt-server-linux-arm64 \
|
|||
chmod +x bin/tronbyt-server-linux-arm64
|
||||
```
|
||||
|
||||
If the binary already exists in `bin/`, the postinstall skips the download.
|
||||
|
||||
### 3. First boot
|
||||
|
||||
On first start, the Go server clones the [community Starlark apps repo](https://github.com/tronbyt/apps) into `DATA_DIR/system-apps/`. This can take a while on a Pi. With `PRODUCTION=true` (the default), it also downloads firmware for OTA device updates.
|
||||
On first start, the server clones the [community Starlark apps repo](https://github.com/tronbyt/apps) (~15 seconds). With `PRODUCTION=false` (the default), firmware downloads are skipped.
|
||||
|
||||
All data (SQLite DB, cloned apps, firmware) is stored in the app's `DATA_DIR`, which persists across restarts and deploys.
|
||||
|
||||
### 4. Create an account
|
||||
|
||||
Visit `http://tronbyt.toes.local/auth/register` to create your user account, then log in and click "New Tronbyt" to add a device.
|
||||
|
||||
## Configure your Tidbyt
|
||||
|
||||
1. Flash your Tidbyt with [Tronbyt firmware](https://github.com/tronbyt/server/releases) (see firmware flashing docs)
|
||||
|
|
@ -58,37 +52,15 @@ Set these through `toes env tronbyt` to override defaults:
|
|||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `PRODUCTION` | `true` | Enables firmware downloads for OTA updates |
|
||||
| `PRODUCTION` | `false` | Set `true` to enable firmware downloads for OTA updates |
|
||||
| `SINGLE_USER_AUTO_LOGIN` | `true` | Auto-login without password (good for home network) |
|
||||
| `SYSTEM_APPS_AUTO_REFRESH` | `true` | Auto-refresh community apps repo every 12h |
|
||||
|
||||
## How the binary is managed
|
||||
|
||||
- On first start, `src/binary.ts` downloads the platform-appropriate binary (`tronbyt-server-{darwin|linux}-{arm64|amd64}`) into `bin/`
|
||||
- The binary runs as a child process listening on TCP port 8000, with `stdout`/`stderr` inherited (logs show up in toes)
|
||||
- `src/proxy.ts` forwards all HTTP and WebSocket traffic from the toes-assigned PORT to the Go binary on port 8000
|
||||
- `bun install` runs `scripts/postinstall.sh`, which downloads `tronbyt-server-linux-arm64` into `bin/` if it doesn't already exist
|
||||
- At runtime, the app looks for a binary in `bin/` matching the current platform (`tronbyt-server-{darwin|linux}-{arm64|amd64}`)
|
||||
- If the binary isn't found, the app logs an error with the download URL and the expected filename
|
||||
- The binary runs as a child process with `stdout`/`stderr` inherited (logs show up in toes)
|
||||
- On shutdown (SIGTERM from toes), the app forwards SIGTERM to the Go process
|
||||
- Health checks poll `http://127.0.0.1:8000/health` — the `/ok` endpoint reports "starting" until the Go server is ready
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Add App page is empty (no community apps)
|
||||
|
||||
The Go server clones `https://github.com/tronbyt/apps.git` into `DATA_DIR/system-apps/` on first boot. If that clone fails (network issue, permissions), you get 0 apps and the 12h auto-refresh won't fix a broken git state. Fix it manually:
|
||||
|
||||
```bash
|
||||
cd /home/toes/data/toes/tronbyt/system-apps/
|
||||
git fetch origin '+refs/heads/*:refs/remotes/origin/*'
|
||||
git checkout -b main origin/main # or: git reset --hard origin/main
|
||||
chown -R toes:toes . # must be owned by the toes user
|
||||
```
|
||||
|
||||
Then restart tronbyt: `POST http://toes.local/api/apps/tronbyt/restart`, then `POST http://toes.local/api/apps/tronbyt/start`.
|
||||
|
||||
### Go binary not starting
|
||||
|
||||
Check logs: `sudo journalctl -u toes | grep tronbyt`. Verify port 8000 is bound: `ss -tlnp | grep 8000`. The binary can take 30+ seconds to start when loading ~1000 community apps.
|
||||
|
||||
### File permission errors
|
||||
|
||||
Everything under `DATA_DIR` must be owned by the `toes` user. Fix with `sudo chown -R toes:toes /home/toes/data/toes/tronbyt/`.
|
||||
- The unix socket is created in `DATA_DIR/tronbyt.sock` and cleaned up on startup to handle stale sockets from crashes
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
"scripts": {
|
||||
"toes": "bun run --watch src/server.ts",
|
||||
"start": "bun toes",
|
||||
"dev": "bun run --hot src/server.ts"
|
||||
"dev": "bun run --hot src/server.ts",
|
||||
"postinstall": "bash scripts/postinstall.sh"
|
||||
},
|
||||
"toes": {
|
||||
"icon": "🖥️"
|
||||
|
|
@ -18,4 +19,4 @@
|
|||
"typescript": "^5.9.2"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
scripts/postinstall.sh
Executable file
17
scripts/postinstall.sh
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
BIN_DIR="$(dirname "$0")/../bin"
|
||||
BINARY="$BIN_DIR/tronbyt-server-linux-arm64"
|
||||
URL="https://github.com/tronbyt/server/releases/latest/download/tronbyt-server-linux-arm64"
|
||||
|
||||
if [ -f "$BINARY" ]; then
|
||||
echo "tronbyt binary already exists, skipping download"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Downloading tronbyt server binary..."
|
||||
mkdir -p "$BIN_DIR"
|
||||
curl -L -o "$BINARY" "$URL"
|
||||
chmod +x "$BINARY"
|
||||
echo "Done"
|
||||
103
src/binary.ts
103
src/binary.ts
|
|
@ -1,7 +1,6 @@
|
|||
import { join } from 'path'
|
||||
import { chmodSync, mkdirSync } from 'fs'
|
||||
import { accessSync, chmodSync, constants, mkdirSync } from 'fs'
|
||||
import type { Subprocess } from 'bun'
|
||||
import { DATA_DIR, PRODUCTION, SINGLE_USER_AUTO_LOGIN, SYSTEM_APPS_AUTO_REFRESH } from './env'
|
||||
|
||||
const BIN_DIR = join(import.meta.dir, '..', 'bin')
|
||||
const GO_PORT = 8000
|
||||
|
|
@ -12,48 +11,6 @@ let proc: Subprocess | undefined
|
|||
export const isHealthy = () => healthy
|
||||
export const isRunning = () => !!proc
|
||||
|
||||
export async function spawn() {
|
||||
const binPath = join(BIN_DIR, getBinaryName())
|
||||
|
||||
if (!(await Bun.file(binPath).exists())) {
|
||||
if (!(await downloadBinary(binPath))) return
|
||||
}
|
||||
|
||||
console.log('Starting tronbyt server...')
|
||||
|
||||
proc = Bun.spawn([binPath], {
|
||||
env: {
|
||||
...process.env,
|
||||
DATA_DIR,
|
||||
DB_DSN: join(DATA_DIR, 'tronbyt.db'),
|
||||
PRODUCTION,
|
||||
SINGLE_USER_AUTO_LOGIN,
|
||||
SYSTEM_APPS_AUTO_REFRESH,
|
||||
},
|
||||
stdout: 'inherit',
|
||||
stderr: 'inherit',
|
||||
})
|
||||
|
||||
proc.exited.then((code) => {
|
||||
console.log(`Tronbyt server exited with code ${code}`)
|
||||
healthy = false
|
||||
proc = undefined
|
||||
})
|
||||
|
||||
if (await waitForHealthy()) {
|
||||
healthy = true
|
||||
console.log('Tronbyt server is healthy')
|
||||
} else {
|
||||
console.error('Tronbyt server failed to become healthy')
|
||||
}
|
||||
}
|
||||
|
||||
export function shutdown() {
|
||||
if (!proc) return
|
||||
console.log('Shutting down tronbyt server...')
|
||||
proc.kill('SIGTERM')
|
||||
}
|
||||
|
||||
function getBinaryName(): string {
|
||||
const platform = process.platform === 'darwin' ? 'darwin' : 'linux'
|
||||
const arch = process.arch === 'x64' ? 'amd64' : 'arm64'
|
||||
|
|
@ -96,3 +53,61 @@ async function downloadBinary(binPath: string): Promise<boolean> {
|
|||
}
|
||||
}
|
||||
|
||||
function validate(dataDir: string): string | undefined {
|
||||
if (!dataDir) return 'DATA_DIR env var is not set — toes should provide this automatically'
|
||||
|
||||
const binPath = join(BIN_DIR, getBinaryName())
|
||||
try {
|
||||
accessSync(binPath, constants.X_OK)
|
||||
} catch {
|
||||
return `Binary not found or not executable: ${binPath}`
|
||||
}
|
||||
}
|
||||
|
||||
export async function spawn(dataDir: string) {
|
||||
const binPath = join(BIN_DIR, getBinaryName())
|
||||
|
||||
if (!(await Bun.file(binPath).exists())) {
|
||||
if (!(await downloadBinary(binPath))) return
|
||||
}
|
||||
|
||||
const error = validate(dataDir)
|
||||
if (error) {
|
||||
console.error(`Setup error: ${error}`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Starting tronbyt server...')
|
||||
|
||||
proc = Bun.spawn([binPath], {
|
||||
env: {
|
||||
...process.env,
|
||||
DATA_DIR: dataDir,
|
||||
DB_DSN: join(dataDir, 'tronbyt.db'),
|
||||
PRODUCTION: process.env.PRODUCTION ?? 'true',
|
||||
SINGLE_USER_AUTO_LOGIN: process.env.SINGLE_USER_AUTO_LOGIN ?? 'true',
|
||||
SYSTEM_APPS_AUTO_REFRESH: process.env.SYSTEM_APPS_AUTO_REFRESH ?? 'true',
|
||||
},
|
||||
stdout: 'inherit',
|
||||
stderr: 'inherit',
|
||||
})
|
||||
|
||||
proc.exited.then((code) => {
|
||||
console.log(`Tronbyt server exited with code ${code}`)
|
||||
healthy = false
|
||||
proc = undefined
|
||||
})
|
||||
|
||||
if (await waitForHealthy()) {
|
||||
healthy = true
|
||||
console.log('Tronbyt server is healthy')
|
||||
} else {
|
||||
console.error('Tronbyt server failed to become healthy')
|
||||
}
|
||||
}
|
||||
|
||||
export function shutdown() {
|
||||
if (!proc) return
|
||||
console.log('Shutting down tronbyt server...')
|
||||
proc.kill('SIGTERM')
|
||||
}
|
||||
|
|
|
|||
11
src/env.ts
11
src/env.ts
|
|
@ -1,11 +0,0 @@
|
|||
function required(name: string): string {
|
||||
const value = process.env[name]
|
||||
if (!value) throw new Error(`Missing required env var: ${name}`)
|
||||
return value
|
||||
}
|
||||
|
||||
export const DATA_DIR = required('DATA_DIR')
|
||||
export const PORT = Number(process.env.PORT) || 3000
|
||||
export const PRODUCTION = process.env.PRODUCTION ?? 'true'
|
||||
export const SINGLE_USER_AUTO_LOGIN = process.env.SINGLE_USER_AUTO_LOGIN ?? 'true'
|
||||
export const SYSTEM_APPS_AUTO_REFRESH = process.env.SYSTEM_APPS_AUTO_REFRESH ?? 'true'
|
||||
132
src/proxy.ts
132
src/proxy.ts
|
|
@ -1,5 +1,4 @@
|
|||
import type { ServerWebSocket } from 'bun'
|
||||
import { isHealthy, isRunning } from './binary'
|
||||
|
||||
export interface WsData {
|
||||
path: string
|
||||
|
|
@ -9,86 +8,61 @@ export interface WsData {
|
|||
const GO_PORT = 8000
|
||||
const GO_BASE = `http://127.0.0.1:${GO_PORT}`
|
||||
|
||||
const WS_CONNECT_TIMEOUT = 5000
|
||||
const upstreams = new Map<ServerWebSocket<WsData>, WebSocket>()
|
||||
|
||||
export function healthCheck(): Promise<Response> {
|
||||
if (!isHealthy()) return Promise.resolve(new Response('starting', { status: isRunning() ? 200 : 503 }))
|
||||
return fetch(`${GO_BASE}/health`)
|
||||
.then((r) => (r.ok ? new Response('ok') : new Response('unhealthy', { status: 503 })))
|
||||
.catch(() => new Response('unhealthy', { status: 503 }))
|
||||
}
|
||||
export function createProxy(isHealthy: () => boolean, isRunning: () => boolean) {
|
||||
async function proxyFetch(req: Request): Promise<Response> {
|
||||
const url = new URL(req.url)
|
||||
|
||||
export async function proxyFetch(req: Request): Promise<Response> {
|
||||
const url = new URL(req.url)
|
||||
const hasBody = req.method !== 'GET' && req.method !== 'HEAD'
|
||||
const body = hasBody ? await req.arrayBuffer() : undefined
|
||||
|
||||
return fetch(`${GO_BASE}${url.pathname}${url.search}`, {
|
||||
method: req.method,
|
||||
headers: req.headers,
|
||||
body,
|
||||
redirect: 'manual',
|
||||
}).then((r) => {
|
||||
// Bun auto-decompresses gzip but leaves content-encoding header.
|
||||
// Strip it so the next proxy layer doesn't try to decompress again.
|
||||
const headers = new Headers(r.headers)
|
||||
headers.delete('content-encoding')
|
||||
headers.delete('content-length')
|
||||
return new Response(r.body, { status: r.status, headers })
|
||||
}).catch((e) => {
|
||||
console.error('Proxy error:', e)
|
||||
return new Response('Tronbyt server is not responding', { status: 502 })
|
||||
})
|
||||
}
|
||||
|
||||
interface UpstreamState {
|
||||
socket: WebSocket
|
||||
pending: (string | ArrayBuffer | Uint8Array)[]
|
||||
}
|
||||
|
||||
const upstreams = new Map<ServerWebSocket<WsData>, UpstreamState>()
|
||||
|
||||
function cleanup(ws: ServerWebSocket<WsData>) {
|
||||
const state = upstreams.get(ws)
|
||||
if (!state) return
|
||||
upstreams.delete(ws)
|
||||
if (state.socket.readyState <= WebSocket.OPEN) state.socket.close()
|
||||
}
|
||||
|
||||
export const websocket = {
|
||||
open(ws: ServerWebSocket<WsData>) {
|
||||
const socket = new WebSocket(`ws://127.0.0.1:${GO_PORT}${ws.data.path}`, ws.data.protocols)
|
||||
socket.binaryType = 'arraybuffer'
|
||||
const state: UpstreamState = { socket, pending: [] }
|
||||
upstreams.set(ws, state)
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
console.error('WebSocket upstream connection timed out')
|
||||
cleanup(ws)
|
||||
ws.close()
|
||||
}, WS_CONNECT_TIMEOUT)
|
||||
|
||||
socket.addEventListener('open', () => {
|
||||
clearTimeout(timeout)
|
||||
for (const msg of state.pending) socket.send(msg)
|
||||
state.pending.length = 0
|
||||
})
|
||||
socket.addEventListener('message', (e) => ws.send(e.data as string | ArrayBuffer))
|
||||
socket.addEventListener('close', () => { cleanup(ws); ws.close() })
|
||||
socket.addEventListener('error', () => { clearTimeout(timeout); cleanup(ws); ws.close() })
|
||||
},
|
||||
|
||||
message(ws: ServerWebSocket<WsData>, msg: string | ArrayBuffer | Uint8Array) {
|
||||
const state = upstreams.get(ws)
|
||||
if (!state) return
|
||||
if (state.socket.readyState === WebSocket.OPEN) {
|
||||
state.socket.send(msg)
|
||||
} else {
|
||||
state.pending.push(msg)
|
||||
if (url.pathname === '/ok') {
|
||||
if (!isHealthy()) return new Response('starting', { status: isRunning() ? 200 : 503 })
|
||||
return fetch(`${GO_BASE}/health`)
|
||||
.then((r) => (r.ok ? new Response('ok') : new Response('unhealthy', { status: 503 })))
|
||||
.catch(() => new Response('unhealthy', { status: 503 }))
|
||||
}
|
||||
},
|
||||
|
||||
close(ws: ServerWebSocket<WsData>) {
|
||||
cleanup(ws)
|
||||
},
|
||||
const hasBody = req.method !== 'GET' && req.method !== 'HEAD'
|
||||
const body = hasBody ? await req.arrayBuffer() : undefined
|
||||
|
||||
return fetch(`${GO_BASE}${url.pathname}${url.search}`, {
|
||||
method: req.method,
|
||||
headers: req.headers,
|
||||
body,
|
||||
redirect: 'manual',
|
||||
}).then((r) => {
|
||||
// Bun auto-decompresses gzip but leaves content-encoding header.
|
||||
// Strip it so the next proxy layer doesn't try to decompress again.
|
||||
const headers = new Headers(r.headers)
|
||||
headers.delete('content-encoding')
|
||||
headers.delete('content-length')
|
||||
return new Response(r.body, { status: r.status, headers })
|
||||
}).catch((e) => {
|
||||
console.error('Proxy error:', e)
|
||||
return new Response('Tronbyt server is not responding', { status: 502 })
|
||||
})
|
||||
}
|
||||
|
||||
const websocket = {
|
||||
open(ws: ServerWebSocket<WsData>) {
|
||||
const upstream = new WebSocket(`ws://127.0.0.1:${GO_PORT}${ws.data.path}`, ws.data.protocols)
|
||||
upstream.binaryType = 'arraybuffer'
|
||||
upstreams.set(ws, upstream)
|
||||
|
||||
upstream.addEventListener('message', (e) => ws.send(e.data as string | ArrayBuffer))
|
||||
upstream.addEventListener('close', () => { upstreams.delete(ws); ws.close() })
|
||||
upstream.addEventListener('error', () => { upstreams.delete(ws); ws.close() })
|
||||
},
|
||||
|
||||
message(ws: ServerWebSocket<WsData>, msg: string | ArrayBuffer | Uint8Array) {
|
||||
const upstream = upstreams.get(ws)
|
||||
if (upstream?.readyState === WebSocket.OPEN) upstream.send(msg)
|
||||
},
|
||||
|
||||
close(ws: ServerWebSocket<WsData>) {
|
||||
upstreams.get(ws)?.close()
|
||||
upstreams.delete(ws)
|
||||
},
|
||||
}
|
||||
|
||||
return { proxyFetch, websocket }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,25 @@
|
|||
import { healthCheck, proxyFetch, websocket, type WsData } from './proxy'
|
||||
import { shutdown, spawn } from './binary'
|
||||
import { PORT } from './env'
|
||||
import { createProxy, type WsData } from './proxy'
|
||||
import { isHealthy, isRunning, shutdown, spawn } from './binary'
|
||||
|
||||
const server = Bun.serve<WsData>({
|
||||
const DATA_DIR = process.env.DATA_DIR!
|
||||
const PORT = Number(process.env.PORT) || 3000
|
||||
|
||||
const { proxyFetch, websocket } = createProxy(isHealthy, isRunning)
|
||||
|
||||
const server = Bun.serve({
|
||||
port: PORT,
|
||||
hostname: '::',
|
||||
idleTimeout: 255,
|
||||
|
||||
fetch(req, server) {
|
||||
const url = new URL(req.url)
|
||||
|
||||
if (url.pathname === '/ok') {
|
||||
return healthCheck()
|
||||
}
|
||||
|
||||
if (req.headers.get('upgrade')?.toLowerCase() === 'websocket') {
|
||||
const url = new URL(req.url)
|
||||
const protocolHeader = req.headers.get('sec-websocket-protocol')
|
||||
const protocols = protocolHeader ? protocolHeader.split(',').map((p) => p.trim()) : []
|
||||
const headers: Record<string, string> = {}
|
||||
if (protocolHeader) headers['sec-websocket-protocol'] = protocolHeader
|
||||
|
||||
const upgradeSuccessful = server.upgrade(req, { data: { path: url.pathname + url.search, protocols }, headers })
|
||||
if (upgradeSuccessful) return
|
||||
if (server.upgrade(req, { data: { path: url.pathname + url.search, protocols }, headers })) return
|
||||
return new Response('WebSocket upgrade failed', { status: 500 })
|
||||
}
|
||||
|
||||
|
|
@ -36,4 +34,4 @@ console.log(`Listening on port ${server.port}`)
|
|||
process.on('SIGTERM', shutdown)
|
||||
process.on('SIGINT', shutdown)
|
||||
|
||||
spawn()
|
||||
spawn(DATA_DIR)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user