All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 43s
The player now holds a queue instead of a single item, so albums play through and single tracks share the same code path. Adds a "stream" item kind for expiring, remotely-hosted sources: Bandcamp signs its mp3 URLs for ~24h, so an item carries the page it came from and re-resolves itself on error or on a stale restored session, falling back to the iframe embed if that fails too. Gated behind GERBEUR_BANDCAMP_PLAYER (default "embed"), injected into a meta tag the same way as site-name/site-emoji. Also: player header gains artwork and a subtitle, volume persists across queue advances and reloads, and cross-origin streams get a plain seek bar rather than a waveform that can never decode. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SbtjNsT5wvuABnfqhegZEJ
131 lines
3.2 KiB
TypeScript
131 lines
3.2 KiB
TypeScript
import { Application } from "@oak/oak";
|
|
import { oakCors } from "@tajpouria/cors";
|
|
|
|
import attachmentsRouter from "./routes/attachments.ts";
|
|
import dumpsRouter from "./routes/dumps.ts";
|
|
import filesRouter from "./routes/files.ts";
|
|
import thumbnailsRouter from "./routes/thumbnails.ts";
|
|
import usersRouter from "./routes/users.ts";
|
|
import avatarsRouter from "./routes/avatars.ts";
|
|
import wsRouter from "./routes/ws.ts";
|
|
import previewRouter from "./routes/preview.ts";
|
|
import bandcampRouter from "./routes/bandcamp.ts";
|
|
import playlistsRouter from "./routes/playlists.ts";
|
|
import commentsRouter from "./routes/comments.ts";
|
|
import chatRouter from "./routes/chat.ts";
|
|
import followsRouter from "./routes/follows.ts";
|
|
import notificationsRouter from "./routes/notifications.ts";
|
|
import invitesRouter from "./routes/invites.ts";
|
|
import searchRouter from "./routes/search.ts";
|
|
import categoriesRouter from "./routes/categories.ts";
|
|
|
|
import { ALLOWED_ORIGINS, LISTEN_HOST, PORT, PUBLIC_URL } from "./config.ts";
|
|
import { errorMiddleware } from "./middleware/error.ts";
|
|
import { ogMiddleware } from "./middleware/og.ts";
|
|
import { routeStaticFilesFrom } from "./lib/static.ts";
|
|
|
|
const app = new Application();
|
|
|
|
const cors = oakCors({ origin: ALLOWED_ORIGINS });
|
|
|
|
app.use(errorMiddleware);
|
|
app.use(ogMiddleware);
|
|
// Only invoke oakCors when the origin is present and allowed.
|
|
// The library calls next() without await when origin is falsy, which
|
|
// corrupts the response in Oak's async middleware chain.
|
|
app.use(async (ctx, next) => {
|
|
const origin = ctx.request.headers.get("origin");
|
|
if (origin && ALLOWED_ORIGINS.includes(origin)) {
|
|
return await cors(ctx, next);
|
|
}
|
|
await next();
|
|
});
|
|
app.use(
|
|
dumpsRouter.routes(),
|
|
dumpsRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
filesRouter.routes(),
|
|
filesRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
thumbnailsRouter.routes(),
|
|
thumbnailsRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
attachmentsRouter.routes(),
|
|
attachmentsRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
usersRouter.routes(),
|
|
usersRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
avatarsRouter.routes(),
|
|
avatarsRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
wsRouter.routes(),
|
|
wsRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
previewRouter.routes(),
|
|
previewRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
bandcampRouter.routes(),
|
|
bandcampRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
playlistsRouter.routes(),
|
|
playlistsRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
commentsRouter.routes(),
|
|
commentsRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
chatRouter.routes(),
|
|
chatRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
followsRouter.routes(),
|
|
followsRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
notificationsRouter.routes(),
|
|
notificationsRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
invitesRouter.routes(),
|
|
invitesRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
searchRouter.routes(),
|
|
searchRouter.allowedMethods(),
|
|
);
|
|
app.use(
|
|
categoriesRouter.routes(),
|
|
categoriesRouter.allowedMethods(),
|
|
);
|
|
app.use(routeStaticFilesFrom([
|
|
`${Deno.cwd()}/dist`,
|
|
`${Deno.cwd()}/public`,
|
|
]));
|
|
|
|
app.addEventListener(
|
|
"listen",
|
|
() => console.log(`Server listening on ${PUBLIC_URL}`),
|
|
);
|
|
|
|
app.addEventListener(
|
|
"error",
|
|
(e) => console.log(`Uncaught error: ${e.message}`),
|
|
);
|
|
|
|
if (import.meta.main) {
|
|
await app.listen({ hostname: LISTEN_HOST, port: PORT });
|
|
}
|
|
|
|
export { app };
|