← Public packages
@kentcdodds/world-builder
Build a shared 3D voxel world with friends and your AI agents, live.
app/server.ts
247 lines · 8.9 KB · TypeScriptimport { packageContext } from 'kody:runtime'
import { JWKS } from '../src/lib/jwks.ts'
import { getCaller, humanPlayer } from '../src/lib/identity.ts'
import { signPowerSyncToken } from '../src/lib/token.ts'
import { POWERSYNC_URL } from '../src/lib/config.ts'
import {
WorldError,
applyBlockOps,
parseBlockOp,
requireWorldId,
updateCursor,
createRoom,
getRoom,
parseRoomRef,
renameRoom,
upsertPlayer,
type BlockOp,
} from '../src/lib/world-ops.ts'
import { DEFAULT_WORLD_ID, LIMITS, blockId } from '../src/shared/world-rules.ts'
import { renderShell } from './shell.ts'
const json = (body: unknown, status = 200, headers: Record<string, string> = {}) =>
Response.json(body, {
status,
headers: { 'cache-control': 'no-store', ...headers },
})
function relPath(pathname: string) {
const base = String(packageContext?.appBasePath ?? '')
const assets = String(packageContext?.assetBasePath ?? `${base}/_assets`).replace(/\/+$/, '')
if (base && pathname.startsWith(base)) pathname = pathname.slice(base.length)
return pathname.replace(/\/+$/, '') || '/'
}
async function requireHuman() {
const caller = await getCaller()
if (!caller) throw new WorldError('Sign in with Kody to build.', 401)
return humanPlayer(caller)
}
async function session(url: URL) {
const caller = await getCaller()
if (!caller) return json({ signedIn: false, worldId: DEFAULT_WORLD_ID, powersyncUrl: POWERSYNC_URL })
const player = humanPlayer(caller)
const worldId = requireWorldId(url.searchParams.get('world'))
let ready = true
try {
await upsertPlayer(player, worldId)
} catch (error) {
ready = false
console.warn('player upsert failed', error instanceof Error ? error.message : error)
}
return json({
signedIn: true,
ready,
worldId,
powersyncUrl: POWERSYNC_URL,
player: { id: player.id, displayName: player.displayName, color: player.color },
})
}
function roomJson(room: { slug: string; name: string; id: string; ownerId: string }, username: string | null) {
return { slug: room.slug, name: room.name, worldId: room.id, isOwner: !!username && room.ownerId === username }
}
async function rooms(request: Request, path: string, method: string) {
const caller = await getCaller()
if (path === '/api/rooms' && method === 'POST') {
if (!caller) throw new WorldError('Sign in with Kody to create a room.', 401)
return json({ room: roomJson(await createRoom(caller.username), caller.username) }, 201)
}
const slug = parseRoomRef(path.slice('/api/rooms/'.length))
if (!slug) throw new WorldError('That room link is not valid.', 404)
if (method === 'GET') {
try {
return json({ room: roomJson(await getRoom(slug), caller?.username ?? null) })
} catch (error) {
// Kody resolves credentials only for a signed-in person today, so a
// signed-out visitor can't read the world database yet.
if (!caller && !(error instanceof WorldError)) {
throw new WorldError('Watching without an account is not available yet. Sign in to watch and build.', 503)
}
throw error
}
}
if (method === 'PATCH') {
if (!caller) throw new WorldError('Sign in with Kody to rename the room.', 401)
const body = (await request.json().catch(() => null)) as { name?: unknown } | null
return json({ room: roomJson(await renameRoom(slug, caller.username, body?.name), caller.username) })
}
return json({ error: 'Method not allowed' }, 405)
}
/**
* PowerSync sync token. Signed-in people get `sub = u:<username>`; signed-out
* visitors get a random read-only spectator subject. A PowerSync token can only
* download (sync rules are read-only buckets); every write goes through
* `POST /api/upload`, which requires a Kody session, so a spectator token never
* grants write access.
*/
async function token() {
const caller = await getCaller()
const subject = caller ? humanPlayer(caller).id : `anon:${crypto.randomUUID()}`
try {
return json({ ...(await signPowerSyncToken(subject)), readOnly: !caller })
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
console.error('world-builder token signing failed', message)
// Signing errors describe configuration (approval, host), never key material.
throw new WorldError(`Could not sign a sync token: ${message.slice(0, 300)}`, 503)
}
}
type CrudOp = { op?: string; type?: string; id?: string; data?: Record<string, unknown> }
/** PowerSync uploadData target. Invalid ops are rejected (and revert on the client); a 429 asks the client to retry. */
async function upload(request: Request) {
const player = await requireHuman()
const len = Number(request.headers.get('content-length') ?? 0)
if (len > 256_000) throw new WorldError('Upload too large.', 413)
const body = (await request.json().catch(() => null)) as { ops?: Array<CrudOp> } | null
const ops = Array.isArray(body?.ops) ? body!.ops : []
if (ops.length > LIMITS.maxUploadOps) throw new WorldError('Too many changes in one upload.', 413)
const byWorld = new Map<string, Array<BlockOp>>()
let rejected = 0
let cursor: { worldId: string; pos: { x: number; y: number; z: number } | null } | null = null
for (const op of ops) {
if (op.type === 'blocks' && (op.op === 'PUT' || op.op === 'PATCH')) {
const d = op.data ?? {}
let worldId: string
try {
worldId = requireWorldId(d.world_id)
} catch {
rejected++
continue
}
const parsed = parseBlockOp(d, 'put')
if (!parsed || op.id !== blockId(worldId, parsed.x, parsed.y, parsed.z)) {
rejected++
continue
}
byWorld.set(worldId, [...(byWorld.get(worldId) ?? []), parsed])
} else if (op.type === 'blocks' && op.op === 'DELETE') {
const m = /^([0-9a-f-]{36}):(-?\d+):(-?\d+):(-?\d+)$/i.exec(String(op.id ?? ''))
const parsed = m ? parseBlockOp({ x: m[2], y: m[3], z: m[4] }, 'del') : null
if (!m || !parsed) {
rejected++
continue
}
const worldId = m[1]!.toLowerCase()
byWorld.set(worldId, [...(byWorld.get(worldId) ?? []), parsed])
} else if (op.type === 'players' && op.op === 'PATCH' && op.id === player.id) {
const d = op.data ?? {}
const has = d.cursor_x != null && d.cursor_y != null && d.cursor_z != null
let worldId = DEFAULT_WORLD_ID
try {
worldId = requireWorldId(d.world_id)
} catch {}
cursor = {
worldId,
pos: has ? { x: Number(d.cursor_x), y: Number(d.cursor_y), z: Number(d.cursor_z) } : null,
}
} else {
rejected++
}
}
let placed = 0
let removed = 0
for (const [worldId, worldOps] of byWorld) {
try {
const out = await applyBlockOps({ player, worldId, ops: worldOps })
placed += out.placed
removed += out.removed
} catch (error) {
// 429 should be retried by the client; other world errors drop the ops.
if (error instanceof WorldError && error.status !== 429) {
rejected += worldOps.length
continue
}
throw error
}
}
if (cursor) {
try {
await updateCursor(player, cursor.worldId, cursor.pos)
} catch (error) {
if (!(error instanceof WorldError && error.status === 429)) throw error
}
}
return json({ ok: true, placed, removed, rejected })
}
async function dispatch(request: Request) {
const url = new URL(request.url)
const path = relPath(url.pathname)
const method = request.method.toUpperCase()
if (path === '/health') return json({ ok: true, app: 'world-builder' })
if (path === '/.well-known/jwks.json' || path === '/jwks.json') {
return json(JWKS, 200, { 'cache-control': 'public, max-age=300' })
}
if (path === '/api/session' && method === 'GET') return session(url)
if (path === '/api/me' && method === 'GET') {
const caller = await getCaller()
return json(caller ? { signedIn: true, displayName: caller.displayName } : { signedIn: false })
}
if (path === '/api/token' && method === 'GET') return token()
if (path === '/api/upload') {
if (method !== 'POST') return json({ error: 'Method not allowed' }, 405)
return upload(request)
}
if (path === '/api/rooms' || path.startsWith('/api/rooms/')) return rooms(request, path, method)
if (path === '/manifest.webmanifest') {
const base = String(packageContext?.appBasePath ?? '')
const assets = String(packageContext?.assetBasePath ?? `${base}/_assets`)
return json(
{
name: 'Kody World Builder',
short_name: 'World Builder',
start_url: `${base}/`,
scope: `${base}/`,
display: 'standalone',
background_color: '#0f1420',
theme_color: '#0f1420',
icons: [{ src: `${assets}/icon.png`, sizes: '512x512', type: 'image/png' }],
},
200,
{ 'content-type': 'application/manifest+json' },
)
}
if (path.startsWith('/api/')) return json({ error: 'Not found' }, 404)
if (method === 'GET' || method === 'HEAD') return renderShell()
return new Response('Method Not Allowed', { status: 405 })
}
export default {
async fetch(request: Request) {
try {
return await dispatch(request)
} catch (error) {
if (error instanceof WorldError) return json({ error: error.message }, error.status)
console.error('world-builder request failed', error instanceof Error ? error.message : error)
return json({ error: 'Something went wrong. Try again.' }, 500)
}
},
}