diff --git a/api/discord/token.ts b/api/discord/token.ts
index f3a3f95c..0cfbf7d8 100644
--- a/api/discord/token.ts
+++ b/api/discord/token.ts
@@ -38,9 +38,6 @@ export default async function handler(req: any, res: any) {
client_secret: clientSecret,
grant_type: "authorization_code",
code,
- redirect_uri:
- process.env.DISCORD_ACTIVITY_REDIRECT_URI ||
- "https://aethex.dev/activity",
}).toString(),
},
);
diff --git a/client/components/ArmSwitcher.tsx b/client/components/ArmSwitcher.tsx
index 4bac7d6f..30506c8b 100644
--- a/client/components/ArmSwitcher.tsx
+++ b/client/components/ArmSwitcher.tsx
@@ -68,9 +68,22 @@ const ARMS: Arm[] = [
textColor: "text-purple-400",
href: "/staff",
},
+ {
+ id: "studio",
+ name: "AeThex | Studio",
+ label: "Studio",
+ color: "#00ffff",
+ bgColor: "bg-cyan-500/20",
+ textColor: "text-cyan-400",
+ href: "https://aethex.studio",
+ external: true,
+ },
];
+const STUDIO_SVG = `data:image/svg+xml,${encodeURIComponent('')}`;
+
const LOGO_URLS: Record = {
+ studio: STUDIO_SVG,
staff:
"https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2Fc0414efd7af54ef4b821a05d469150d0?format=webp&width=800",
labs: "https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2Fd93f7113d34347469e74421c3a3412e5?format=webp&width=800",
diff --git a/server/index.ts b/server/index.ts
index 03591f93..99d20d9e 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -41,6 +41,8 @@ import blogIndexHandler from "../api/blog/index";
import blogSlugHandler from "../api/blog/[slug]";
import aiChatHandler from "../api/ai/chat";
import aiTitleHandler from "../api/ai/title";
+import createCheckoutHandler from "../api/subscriptions/create-checkout";
+import manageSubscriptionHandler from "../api/subscriptions/manage";
// Developer API Keys handlers
import {
@@ -340,6 +342,11 @@ export function createServer() {
app.use((req, res, next) => {
// Allow embedding in iframes (Discord Activities need this)
res.setHeader("X-Frame-Options", "ALLOWALL");
+ // CSP with frame-ancestors for Discord Activity embedding
+ res.setHeader(
+ "Content-Security-Policy",
+ "default-src 'self' https: data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: blob: https:; font-src 'self' data: https:; connect-src 'self' https: wss:; frame-ancestors 'self' https://discord.com https://*.discord.com https://*.discordsays.com",
+ );
// Allow Discord to access the iframe
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
@@ -1815,9 +1822,6 @@ export function createServer() {
client_secret: clientSecret,
grant_type: "authorization_code",
code,
- redirect_uri:
- process.env.DISCORD_ACTIVITY_REDIRECT_URI ||
- "https://aethex.dev/activity",
}).toString(),
},
);
@@ -8177,5 +8181,16 @@ export function createServer() {
app.post("/api/ai/chat", aiChatHandler);
app.post("/api/ai/title", aiTitleHandler);
+ // Subscription API routes
+ app.post("/api/subscriptions/create-checkout", (req: express.Request, res: express.Response) => {
+ return createCheckoutHandler(req as any, res as any);
+ });
+ app.get("/api/subscriptions/manage", (req: express.Request, res: express.Response) => {
+ return manageSubscriptionHandler(req as any, res as any);
+ });
+ app.post("/api/subscriptions/manage", (req: express.Request, res: express.Response) => {
+ return manageSubscriptionHandler(req as any, res as any);
+ });
+
return app;
}
diff --git a/server/node-build.ts b/server/node-build.ts
index ca9a4d98..cae62dcd 100644
--- a/server/node-build.ts
+++ b/server/node-build.ts
@@ -335,6 +335,12 @@ app.get("*", async (req, res) => {
return res.status(404).json({ error: "API endpoint not found" });
}
+ // Don't serve the SPA shell for missing static-asset requests — they should 404
+ // cleanly rather than returning HTML (which causes "Unexpected token '<'" JS errors).
+ if (/\.(js|mjs|cjs|css|map|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|webp|avif)$/i.test(req.path)) {
+ return res.status(404).send("Not found");
+ }
+
try {
const template = getTemplate();
const meta = await resolveRouteMeta(req.path);