jevstrudel.git / worker / src / index.ts
index.tsannotatedindex.tssource107 lines · 6.1 KB · raw
1// The jevstrudel Worker, at https://strudel.lmjtfy.fun. Its workers.dev
2// host, the site's address until 2026-10-05, answers every request with a
3// redirect there (moved.ts). The static site (`wrangler deploy --assets`)
4// has its big folders served by Cloudflare's asset layer before this runs;
5// pages and the files at the root come through the Worker first
6// (wrangler.json, run_worker_first), which is what lets the old host
7// redirect them, and fall through to the assets at the end. The paths that
8// are not files:
9//
10//   GET  /jev/auth/moved     a session arriving from the old address (moved.ts)
11//   POST /jev/v1/systemone   the Jev relay (relay.ts), everywhere
12//   POST /jev/votes          a "do you agree with Jev?" vote  } votes.ts,
13//   GET  /jev/votes/summary  the vote counts                  } everywhere
14//   …    /jev/auth/*         passkey accounts (auth.ts): register, sign
15//                            in and out, who is signed in; everywhere
16//   GET, POST /jev/me/radio  a signed-in listener's radio history (radio.ts)
17//   POST /jev/reactions      a listener's 🔥 or 😴             } listening.ts,
18//   GET  /jev/reactions      a song's tally, all listeners   } everywhere
19//   POST /jev/performances   a performance, for a replay link }
20//   GET  /jev/performances/<id>  one, to replay it            }
21//   GET  /jev/party/<room>   a listening party's WebSocket (party.ts), everywhere
22//   GET  /jev/parties[/<id>] live parties, and a listed one's room (party.ts)
23//   GET  /jev/lobby          the lobby's WebSocket: who is listening to what
24//                            (lobby.ts), everywhere
25//   GET  /jev/data[/*]       everything the site stores, for anyone to read
26//                            (data.ts): people too, never a secret; everywhere
27//   …    /jev/listeners/*    listeners' songs, covers, comments and pitches
28//                            (content.ts): public to read, signed in to
29//                            write, screened by Jev first; everywhere
30//   POST /jev/mcp            the hosted MCP, for everyone's own AI, with an
31//                            OAuth token (hosted-mcp.ts); its OAuth under
32//                            /jev/oauth/* and /.well-known/oauth-* (oauth.ts);
33//                            /jev/me/tabs, a signed-in tab joining its
34//                            user's hub, and /jev/me/apps, connected apps;
35//                            everywhere
36//   GET  /strudel/status     open MCP tabs; the page's probe  } dev only:
37//   GET  /strudel/ws         a tab's WebSocket to the hub     } they need
38//   POST /mcp                MCP over HTTP (mcp.ts)           } the HUB binding
39//
40// Its cron trigger (wrangler.json) runs the work Jev owes listeners'
41// content, screening and scoring, every minute (content-jobs.ts).
42import { auth, AUTH_PREFIX } from './auth';
43import { content, LISTENERS_PREFIX } from './content';
44import { data, DATA_PREFIX } from './data';
45import { sweep } from './content-jobs';
46import { d1Content } from './content-store';
47import type { Env } from './env';
48import { mcp } from './mcp';
49import { arrived, isOldHost, moved, MOVED_PATH } from './moved';
50import { oauth } from './oauth';
51import { radio, RADIO_PATH } from './radio';
52import { listening, PERFORMANCES_PATH, REACTIONS_PATH } from './listening';
53import { d1Listening } from './listening-store';
54import { lobby, LOBBY_PATH } from './lobby';
55import { parties, PARTIES_PATH, party, PARTY_PREFIX } from './party';
56import { relay, RELAY_PATH } from './relay';
57import { refuseCrossOrigin } from './session';
58import { SUMMARY_PATH, votes, VOTES_PATH } from './votes';
59import { d1Votes } from './votes-store';
60
61export { JevBudget } from './budget';
62export { Hub } from './hub';
63export { LobbyRoom } from './lobby';
64export { Party, PartyDirectory } from './party';
65export { TabHub } from './tab-hub';
66
67export default {
68  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
69    const { pathname, hostname } = new URL(request.url);
70    // the site's old address answers only with the way to the new (moved.ts)
71    if (isOldHost(hostname)) return moved(request, env);
72    // writes only from this site's own pages: never another site's, nor the
73    // sandbox listeners' songs play in (session.ts)
74    const crossOrigin = refuseCrossOrigin(request);
75    if (crossOrigin) return crossOrigin;
76    if (pathname === RELAY_PATH) return relay(request, env, ctx);
77    const hosted = await oauth(request, env, ctx);
78    if (hosted) return hosted;
79    if (pathname === VOTES_PATH || pathname === SUMMARY_PATH) {
80      return votes(request, env, d1Votes(env.DB));
81    }
82    if (pathname === MOVED_PATH) return arrived(request, env);
83    if (pathname.startsWith(AUTH_PREFIX)) return auth(request, env);
84    if (pathname === RADIO_PATH) return radio(request, env);
85    if (pathname === REACTIONS_PATH || pathname === PERFORMANCES_PATH || pathname.startsWith(`${PERFORMANCES_PATH}/`)) {
86      return listening(request, env, d1Listening(env.DB));
87    }
88    if (pathname.startsWith(PARTY_PREFIX)) return party(request, env);
89    if (pathname === PARTIES_PATH || pathname.startsWith(`${PARTIES_PATH}/`)) return parties(request, env);
90    if (pathname === LOBBY_PATH) return lobby(request, env);
91    if (pathname.startsWith(LISTENERS_PREFIX)) return content(request, env, ctx);
92    if (pathname === DATA_PREFIX || pathname.startsWith(`${DATA_PREFIX}/`)) return data(request, env);
93    if (pathname === '/mcp') return mcp(request, env);
94    if (env.HUB && pathname === '/strudel/ws') return env.HUB.get(env.HUB.idFromName('hub')).fetch(request);
95    if (env.HUB && pathname === '/strudel/status') {
96      const sessions = await env.HUB.get(env.HUB.idFromName('hub')).sessions();
97      return Response.json({ active_sessions: sessions });
98    }
99    // everything else is the static site: pages come through here
100    // (wrangler.json, run_worker_first) so the old address can redirect them
101    return env.ASSETS ? env.ASSETS.fetch(request) : new Response('not found', { status: 404 });
102  },
103
104  async scheduled(_controller: ScheduledController, env: Env, ctx: ExecutionContext): Promise<void> {
105    ctx.waitUntil(sweep(env, d1Content(env.DB)).then(() => undefined));
106  },
107} satisfies ExportedHandler<Env>;