Files
tyx_AI_xhs/apps/api/src/app.ts
T

340 lines
10 KiB
TypeScript

import { randomBytes, randomUUID } from "node:crypto";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import {
BootstrapResponseSchema,
CorrelationIdSchema,
ErrorDetailsSchema,
ErrorEnvelopeSchema,
GenerationErrorCategorySchema,
ModelConfigSseEventSchema,
ModelRuntimeSseEventSchema,
SseEventSchema,
StableEngineeringErrorCodeSchema,
StateSseEventSchema,
createErrorEnvelope,
isCorrelationId,
type BootstrapResponse,
} from "@dada/shared-contracts";
import swagger from "@fastify/swagger";
import Fastify, { type FastifyReply } from "fastify";
import {
BrowserSupportRequestSchema,
BrowserSupportSuccessSchema,
BrowserUnsupportedReasonSchema,
browserSupportCookieMaxAgeSeconds,
browserSupportCookieName,
checkBrowserSupport,
createBrowserSupportCookie,
supportedBrowserSummary,
verifyBrowserSupportCookie,
type BrowserSupportRelease,
type BrowserUnsupportedReason,
} from "./browser-support.js";
import { EventHub } from "./event-hub.js";
import type { PublicAssetResolver } from "./local-data-root.js";
import { isAllowedNetworkRequest, type NetworkBoundaryOptions } from "./network-boundary.js";
const defaultBootstrap: BootstrapResponse = {
app_version: "0.0.0",
dependencies: [],
model_summary: {
config_set_version: null,
configured_default_model_id: null,
recommended_model_id: null,
runtime_availability_version: null,
},
public_features: [],
};
export interface CreateAppOptions {
bootstrap?: () => BootstrapResponse | Promise<BootstrapResponse>;
browserGate?: boolean;
browserSupportRelease?: BrowserSupportRelease;
browserSupportSecret?: Buffer;
eventHub?: EventHub;
networkBoundary?: NetworkBoundaryOptions;
publicAssets?: PublicAssetResolver;
}
const supportGateDirectory = resolve("apps/web/support-gate");
const supportGateHtml = readFileSync(resolve(supportGateDirectory, "index.html"), "utf8");
const supportGateCss = readFileSync(resolve(supportGateDirectory, "support-gate.css"), "utf8");
const supportGateJavaScript = readFileSync(resolve(supportGateDirectory, "support-gate.js"), "utf8");
const clientHints = "Sec-CH-UA, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform";
const contentSecurityPolicy = [
"default-src 'self'",
"script-src 'self'",
"style-src 'self'",
"img-src 'self' blob:",
"font-src 'self' blob:",
"connect-src 'self'",
"object-src 'none'",
"base-uri 'none'",
"frame-ancestors 'none'",
].join("; ");
function requestCorrelationId(headers: Record<string, string | string[] | undefined>) {
const header = headers["x-correlation-id"];
const candidate = Array.isArray(header) ? header[0] : header;
return isCorrelationId(candidate) ? candidate : randomUUID();
}
function headerValue(value: string | string[] | undefined) {
return Array.isArray(value) ? value[0] : value;
}
function isSupportGateRequest(method: string, path: string) {
if (method === "POST" && path === "/api/v1/support/check") return true;
if (method !== "GET" && method !== "HEAD") return false;
return (
path === "/" ||
path === "/app" ||
path.startsWith("/app/") ||
path === "/admin" ||
path.startsWith("/admin/") ||
path === "/support-gate.css" ||
path === "/support-gate.js" ||
path === "/RELEASE.json" ||
path === "/healthz"
);
}
function sendBrowserUnsupported(
reply: FastifyReply,
correlationId: string,
reason: BrowserUnsupportedReason,
release: BrowserSupportRelease | undefined,
) {
return reply.code(426).send(
createErrorEnvelope({
code: "BROWSER_UNSUPPORTED",
correlationId,
details: {
reason,
supported_browsers: supportedBrowserSummary(release),
},
}),
);
}
export async function createApp(options: CreateAppOptions = {}) {
const eventHub = options.eventHub ?? new EventHub();
const bootstrap = options.bootstrap ?? (() => defaultBootstrap);
const browserGate = options.browserGate ?? true;
const browserSupportSecret = options.browserSupportSecret ?? randomBytes(32);
const browserSupportRelease = options.browserSupportRelease;
const app = Fastify({
genReqId: (request) => requestCorrelationId(request.headers),
logger: false,
});
await app.register(swagger, {
openapi: {
info: {
title: "Dada P0-A",
version: "0.0.0",
},
openapi: "3.1.0",
},
refResolver: {
buildLocalReference: (schema, _baseUri, _fragment, index) =>
typeof schema.$id === "string" ? schema.$id : `schema-${index}`,
},
});
for (const schema of [
CorrelationIdSchema,
GenerationErrorCategorySchema,
StableEngineeringErrorCodeSchema,
ErrorDetailsSchema,
ErrorEnvelopeSchema,
BrowserUnsupportedReasonSchema,
BrowserSupportRequestSchema,
BrowserSupportSuccessSchema,
BootstrapResponseSchema,
StateSseEventSchema,
ModelConfigSseEventSchema,
ModelRuntimeSseEventSchema,
SseEventSchema,
]) {
app.addSchema(schema);
}
app.addHook("onRequest", async (request, reply) => {
reply.header("X-Correlation-Id", request.id);
reply.header("Accept-CH", clientHints);
reply.header("Cache-Control", "no-store");
reply.header("Content-Security-Policy", contentSecurityPolicy);
reply.header("X-Content-Type-Options", "nosniff");
const host = headerValue(request.headers.host);
const origin = headerValue(request.headers.origin);
if (!isAllowedNetworkRequest({ host, method: request.method, origin }, options.networkBoundary)) {
return sendBrowserUnsupported(reply, request.id, "identity_unavailable", browserSupportRelease);
}
const path = request.url.split("?", 1)[0] ?? "/";
if (!browserGate || isSupportGateRequest(request.method, path)) return;
const verified = verifyBrowserSupportCookie({
cookieHeader: headerValue(request.headers.cookie),
release: browserSupportRelease,
secChUa: headerValue(request.headers["sec-ch-ua"]),
secret: browserSupportSecret,
});
if (!verified.supported) {
return sendBrowserUnsupported(reply, request.id, verified.reason, browserSupportRelease);
}
});
for (const route of ["/", "/app", "/app/*", "/admin", "/admin/*"]) {
app.get(route, { schema: { hide: true } }, async (_request, reply) => {
reply.type("text/html; charset=utf-8");
return supportGateHtml;
});
}
app.get("/support-gate.css", { schema: { hide: true } }, async (_request, reply) => {
reply.type("text/css; charset=utf-8");
return supportGateCss;
});
app.get("/support-gate.js", { schema: { hide: true } }, async (_request, reply) => {
reply.type("text/javascript; charset=utf-8");
return supportGateJavaScript;
});
app.get("/RELEASE.json", { schema: { hide: true } }, async () => ({
app_version: browserSupportRelease?.appVersion ?? null,
browsers: supportedBrowserSummary(browserSupportRelease),
}));
app.get("/healthz", { schema: { hide: true } }, async () => ({
bind_scope: "loopback",
port: 43121,
status: "ready",
}));
app.get(
"/api/v1/assets/public/:resourceVersion/:assetId",
{ schema: { hide: true } },
async (request, reply) => {
const { assetId, resourceVersion } = request.params as { assetId?: string; resourceVersion?: string };
const resource = assetId && resourceVersion
? options.publicAssets?.read(resourceVersion, assetId)
: undefined;
if (!resource) return reply.code(404).send();
reply.type(resource.mimeType);
reply.header("Cache-Control", "public, max-age=31536000, immutable");
reply.header("Content-Disposition", "inline");
reply.header("ETag", `"sha256-${resource.sha256}"`);
return resource.bytes;
},
);
app.post(
"/api/v1/support/check",
{
attachValidation: true,
schema: {
body: BrowserSupportRequestSchema,
operationId: "checkBrowserSupport",
response: {
200: BrowserSupportSuccessSchema,
426: ErrorEnvelopeSchema,
},
tags: ["Browser support"],
},
},
async (request, reply) => {
const checked = checkBrowserSupport(
request.validationError ? undefined : request.body,
{
secChUa: headerValue(request.headers["sec-ch-ua"]),
secChUaFullVersionList: headerValue(request.headers["sec-ch-ua-full-version-list"]),
secChUaPlatform: headerValue(request.headers["sec-ch-ua-platform"]),
},
browserSupportRelease,
);
if (!checked.supported || !browserSupportRelease) {
return sendBrowserUnsupported(
reply,
request.id,
checked.supported ? "version_unsupported" : checked.reason,
browserSupportRelease,
);
}
const cookie = createBrowserSupportCookie(
browserSupportSecret,
browserSupportRelease,
checked.identity,
);
reply.header(
"Set-Cookie",
`${browserSupportCookieName}=${cookie}; Max-Age=${browserSupportCookieMaxAgeSeconds}; Path=/; HttpOnly; SameSite=Strict`,
);
return {
app_version: browserSupportRelease.appVersion,
browser: checked.identity,
status: "supported" as const,
supported_browsers: supportedBrowserSummary(browserSupportRelease),
};
},
);
app.get(
"/api/v1/bootstrap",
{
schema: {
operationId: "getBootstrap",
response: {
200: BootstrapResponseSchema,
426: ErrorEnvelopeSchema,
},
tags: ["Bootstrap"],
},
},
async () => bootstrap(),
);
app.get(
"/api/v1/events",
{
schema: {
operationId: "getEvents",
response: {
200: {
content: {
"text/event-stream": {
schema: SseEventSchema,
},
},
description: "Non-sensitive state change hints. REST remains authoritative.",
},
426: ErrorEnvelopeSchema,
},
tags: ["State events"],
},
},
(request, reply) => {
reply.hijack();
reply.raw.setHeader("Cache-Control", "no-store");
reply.raw.setHeader("Connection", "keep-alive");
reply.raw.setHeader("Content-Type", "text/event-stream; charset=utf-8");
reply.raw.setHeader("X-Correlation-Id", request.id);
reply.raw.writeHead(200);
reply.raw.write(": connected\n\n");
const unsubscribe = eventHub.connect(
(event) => {
reply.raw.write(`id: ${event.event_id}\n`);
reply.raw.write(`data: ${JSON.stringify(event)}\n\n`);
},
() => reply.raw.end(),
);
request.raw.once("close", unsubscribe);
},
);
return app;
}