import Mime from "@effect/platform-node/Mime";import { AuthOrchestrationOperateScope, AuthOrchestrationReadScope, EnvironmentHttpApi,} from "@t3tools/contracts";import { isDevProxiedPath } from "@t3tools/shared/devProxy";import { decodeOtlpTraceRecords } from "@t3tools/shared/observability";import * as Data from "effect/Data";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as Schema from "effect/Schema";import * as Stream from "effect/Stream";import { cast } from "effect/Function";import { HttpBody, HttpClient, HttpClientResponse, HttpMiddleware, HttpRouter, HttpServerResponse, HttpServerRequest, HttpServerRespondable,} from "effect/unstable/http";import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder";import { OtlpTracer } from "effect/unstable/observability"; import * as ServerConfig from "./config.ts";import { ASSET_ROUTE_PREFIX, resolveAsset } from "./assets/AssetAccess.ts";import { statMediaFile, streamMediaFile, type OpenMediaFile } from "./assets/MediaFile.ts";import { ATTACHMENT_UPLOAD_ROUTE_PREFIX, storeAttachmentUpload, validateAttachmentUploadToken,} from "./assets/AttachmentUpload.ts";import * as BrowserTraceCollector from "./observability/BrowserTraceCollector.ts";import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";import { traceRelayRequest } from "./cloud/traceRelayRequest.ts";import { annotateEnvironmentRequest, failEnvironmentScopeRequired, failEnvironmentAuthInvalid, failEnvironmentInternal,} from "./auth/http.ts";import * as ServerEnvironment from "./environment/ServerEnvironment.ts";import { browserApiCorsAllowedHeaders, browserApiCorsAllowedMethods } from "./httpCors.ts"; const OTLP_TRACES_PROXY_PATH = "/api/observability/v1/traces";const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "::1", "localhost"]);const DESKTOP_RENDERER_ORIGINS = ["t3code://app", "t3code-dev://app"];const SVG_CONTENT_SECURITY_POLICY = "default-src 'none'; style-src 'unsafe-inline'; sandbox";// HTML previews are agent output, not the app. The sandbox gives the document an// opaque origin: scripts run, but same-origin cookies, storage, and API calls are// out of reach. Relative sibling assets still load through their signed URLs.const HTML_CONTENT_SECURITY_POLICY = "sandbox allow-scripts allow-forms allow-popups allow-modals"; // Types a browser may render as a document if a proxy strips the disposition// header. Downloads of these fall back to octet-stream.const DOWNLOAD_MIME_TYPE_PATTERN = /^[\w!#$&^.+-]+\/[\w!#$&^.+-]+$/;const isSafeDownloadMimeType = (mimeType: string): boolean => DOWNLOAD_MIME_TYPE_PATTERN.test(mimeType) && !/(?:^text\/html$|\/xml(?:$|-)|\+xml$)/i.test(mimeType.trim().toLowerCase());const isSafeInlineVideoMimeType = (mimeType: string): boolean => DOWNLOAD_MIME_TYPE_PATTERN.test(mimeType) && mimeType.toLowerCase().startsWith("video/");const isSafeInlineDocumentMimeType = (mimeType: string): boolean => mimeType.toLowerCase() === "application/pdf" || mimeType.toLowerCase() === "text/html"; /** RFC 6266 disposition with an ASCII fallback name plus a UTF-8 `filename*`. */export function downloadContentDisposition(fileName?: string): string { if (fileName === undefined) { return "attachment"; } // toWellFormed: encodeURIComponent throws URIError on unpaired surrogates. const sanitized = fileName.toWellFormed().replace(/[\p{Cc}"\\]/gu, "_"); const asciiFallback = sanitized.replace(/[^\u0020-\u007e]/g, "_"); const needsExtended = asciiFallback !== sanitized; const extendedName = encodeURIComponent(sanitized).replace( /['()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`, ); return `attachment; filename="${asciiFallback}"${ needsExtended ? `; filename*=UTF-8''${extendedName}` : "" }`;} export function assetResponseHeaders( filePath: string, options?: { readonly download?: boolean; readonly fileName?: string; readonly mimeType?: string; },): Record<string, string> { const lowerPath = filePath.toLowerCase(); const inlineMimeType = options?.mimeType?.split(";", 1)[0]?.trim(); return { "Cache-Control": "private, max-age=3600", "X-Content-Type-Options": "nosniff", ...(options?.download ? { "Content-Disposition": downloadContentDisposition(options.fileName), "Content-Security-Policy": "default-src 'none'; sandbox", "Content-Type": options.mimeType !== undefined && isSafeDownloadMimeType(options.mimeType) ? options.mimeType : "application/octet-stream", } : inlineMimeType !== undefined && isSafeInlineVideoMimeType(inlineMimeType) ? { "Content-Type": inlineMimeType } : inlineMimeType !== undefined && isSafeInlineDocumentMimeType(inlineMimeType) ? { "Content-Type": inlineMimeType.toLowerCase() === "text/html" ? "text/html; charset=utf-8" : "application/pdf", ...(inlineMimeType.toLowerCase() === "text/html" ? { "Content-Security-Policy": HTML_CONTENT_SECURITY_POLICY } : {}), } : lowerPath.endsWith(".html") || lowerPath.endsWith(".htm") ? { "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": HTML_CONTENT_SECURITY_POLICY, } : {}), ...(!options?.download && lowerPath.endsWith(".svg") ? { "Content-Security-Policy": SVG_CONTENT_SECURITY_POLICY } : {}), };} /** A single byte range for native video readers; unsupported range syntax uses the full file. */function assetByteRange(header: string, size: bigint) { const match = /^bytes=(\d*)-(\d*)$/i.exec(header.trim()); if (!match || (!match[1] && !match[2])) return null; const first = match[1] ? BigInt(match[1]) : null; const last = match[2] ? BigInt(match[2]) : null; if (first !== null && last !== null && last < first) return null; if (size === 0n || (first !== null && first >= size) || (first === null && last === 0n)) { return { _tag: "Unsatisfiable" as const }; } const start = first ?? (last! >= size ? 0n : size - last!); const end = first === null || last === null || last >= size ? size - 1n : last; if (!Number.isSafeInteger(Number(start)) || !Number.isSafeInteger(Number(end))) { return { _tag: "Unsatisfiable" as const }; } return { _tag: "Range" as const, offset: start, bytesToRead: end - start + 1n, contentRange: `bytes ${start}-${end}/${size}`, };} export const assetFileResponse = Effect.fn("assetFileResponse")(function* ( asset: { readonly path: string; readonly download?: boolean; readonly fileName?: string; readonly mimeType?: string; readonly file?: OpenMediaFile; }, rangeHeader?: string, ifRangeHeader?: string, method: "GET" | "HEAD" = "GET",) { const headers = assetResponseHeaders(asset.path, asset); const mediaFile = asset.file; const mediaInfo = mediaFile ? yield* statMediaFile(asset.path, mediaFile) : undefined; const isVideo = headers["Content-Type"]?.toLowerCase().startsWith("video/") === true; if (mediaFile && isVideo) { // Host videos can change in place. Do not invite conditional range requests // with validators that cannot establish byte-for-byte identity. headers["Cache-Control"] = "private, no-store"; } let status = 200; let offset = 0n; let bytesToRead: bigint | undefined; if (isVideo) { headers["Accept-Ranges"] = "bytes"; // If-Range requires a matching validator. A full response is safe when we cannot validate it. if (method === "GET" && rangeHeader && ifRangeHeader === undefined) { const fs = yield* FileSystem.FileSystem; const info = mediaInfo ?? (yield* fs.stat(asset.path)); const range = assetByteRange(rangeHeader, info.size); if (range?._tag === "Unsatisfiable") { return HttpServerResponse.empty({ status: 416, headers: { ...headers, "Content-Range": `bytes */${info.size}` }, }); } if (range?._tag === "Range") { status = 206; offset = range.offset; bytesToRead = range.bytesToRead; headers["Content-Range"] = range.contentRange; } } } if (mediaFile && mediaInfo) { const size = bytesToRead ?? mediaInfo.size; headers["Content-Type"] ??= Mime.getType(asset.path) ?? "application/octet-stream"; headers["Content-Length"] = String(size); if (!isVideo) { headers["Last-Modified"] = mediaInfo.mtime.toUTCString(); headers.ETag = `W/"${mediaInfo.size.toString(16)}-${mediaInfo.mtimeMs.toString(16)}"`; } if (method === "HEAD" || size === 0n) { return HttpServerResponse.empty({ status, headers }); } const body = streamMediaFile(mediaFile, offset, size); if (!body) { return HttpServerResponse.text("File is too large to preview.", { status: 413 }); } return HttpServerResponse.stream(body, { status, headers, }); } return yield* HttpServerResponse.file(asset.path, { status, offset, bytesToRead, headers });}); export const httpCompressionLayer = HttpRouter.middleware(HttpMiddleware.compression(), { global: true,}); export const browserApiCorsLayer = Layer.unwrap( Effect.gen(function* () { const config = yield* ServerConfig.ServerConfig; const devOrigin = config.devUrl?.origin; // Dev uses credentialed requests from Vite or the Electron custom origin, so both must be // explicit. Packaged desktop omits credentials and uses Effect's default wildcard origin. // // T3CODE_DEV_ALLOWED_ORIGINS covers dev servers reached from a second // origin — a tailnet name, a LAN IP, a phone. Browser dev normally proxies // through Vite and is same-origin (no preflight at all), so this is a // safety net for the desktop renderer and any direct-to-backend caller. return HttpRouter.cors({ ...(devOrigin ? { allowedOrigins: [devOrigin, ...DESKTOP_RENDERER_ORIGINS, ...config.devAllowedOrigins], credentials: true, } : {}), allowedMethods: browserApiCorsAllowedMethods, allowedHeaders: browserApiCorsAllowedHeaders, maxAge: 600, }); }),); export function isLoopbackHostname(hostname: string): boolean { const normalizedHostname = hostname .trim() .toLowerCase() .replace(/^\[(.*)\]$/, "$1"); return LOOPBACK_HOSTNAMES.has(normalizedHostname);} export function resolveDevRedirectUrl(devUrl: URL, requestUrl: URL): string { const redirectUrl = new URL(devUrl.toString()); redirectUrl.pathname = requestUrl.pathname; redirectUrl.search = requestUrl.search; redirectUrl.hash = requestUrl.hash; return redirectUrl.toString();} const authenticateRawRouteWithScope = ( scope: typeof AuthOrchestrationReadScope | typeof AuthOrchestrationOperateScope,) => Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; const serverAuth = yield* EnvironmentAuth.EnvironmentAuth; const session = yield* serverAuth.authenticateHttpRequest(request).pipe( Effect.catchIf(EnvironmentAuth.isServerAuthCredentialError, (error) => failEnvironmentAuthInvalid( EnvironmentAuth.serverAuthCredentialReason(error), EnvironmentAuth.serverAuthDpopFailureReason(error), ), ), Effect.catchIf(EnvironmentAuth.isServerAuthInternalError, (error) => failEnvironmentInternal("internal_error", error), ), ); if (!session.scopes.includes(scope)) { return yield* failEnvironmentScopeRequired(scope); } }); export const serverEnvironmentHttpApiLayer = HttpApiBuilder.group( EnvironmentHttpApi, "metadata", Effect.fnUntraced(function* (handlers) { const serverEnvironment = yield* ServerEnvironment.ServerEnvironment; return handlers.handle( "descriptor", Effect.fn("environment.metadata.descriptor")(function* (args) { yield* annotateEnvironmentRequest(args.endpoint.name); return yield* serverEnvironment.getDescriptor; }, traceRelayRequest), ); }),); class DecodeOtlpTraceRecordsError extends Data.TaggedError("DecodeOtlpTraceRecordsError")<{ readonly cause: unknown; readonly bodyJson: OtlpTracer.TraceData;}> {} export const otlpTracesProxyRouteLayer = HttpRouter.add( "POST", OTLP_TRACES_PROXY_PATH, Effect.gen(function* () { yield* authenticateRawRouteWithScope(AuthOrchestrationOperateScope); const request = yield* HttpServerRequest.HttpServerRequest; const config = yield* ServerConfig.ServerConfig; const otlpTracesUrl = config.otlpTracesUrl; const browserTraceCollector = yield* BrowserTraceCollector.BrowserTraceCollector; const httpClient = yield* HttpClient.HttpClient; const bodyJson = cast<unknown, OtlpTracer.TraceData>(yield* request.json); yield* Effect.try({ try: () => decodeOtlpTraceRecords(bodyJson), catch: (cause) => new DecodeOtlpTraceRecordsError({ cause, bodyJson }), }).pipe( Effect.flatMap((records) => browserTraceCollector.record(records)), Effect.catch((cause) => Effect.logWarning("Failed to decode browser OTLP traces", { cause, bodyJson, }), ), ); if (otlpTracesUrl === undefined) { return HttpServerResponse.empty({ status: 204 }); } return yield* httpClient .post(otlpTracesUrl, { body: HttpBody.jsonUnsafe(bodyJson), }) .pipe( Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.as(HttpServerResponse.empty({ status: 204 })), Effect.tapError((cause) => Effect.logWarning("Failed to export browser OTLP traces", { cause, otlpTracesUrl, }), ), Effect.orElseSucceed(() => HttpServerResponse.text("Trace export failed.", { status: 502 }), ), ); }).pipe( Effect.catchTags({ EnvironmentAuthInvalidError: HttpServerRespondable.toResponse, EnvironmentInternalError: HttpServerRespondable.toResponse, EnvironmentScopeRequiredError: HttpServerRespondable.toResponse, }), ),); export const assetRouteLayer = HttpRouter.add( "GET", `${ASSET_ROUTE_PREFIX}/*`, Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; const url = HttpServerRequest.toURL(request); if (Option.isNone(url)) { return HttpServerResponse.text("Bad Request", { status: 400 }); } const suffix = url.value.pathname.slice(`${ASSET_ROUTE_PREFIX}/`.length); const separatorIndex = suffix.indexOf("/"); if (separatorIndex <= 0) { return HttpServerResponse.text("Not Found", { status: 404 }); } const asset = yield* resolveAsset( suffix.slice(0, separatorIndex), suffix.slice(separatorIndex + 1), ); if (!asset) { return HttpServerResponse.text("Not Found", { status: 404 }); } return yield* assetFileResponse( asset, request.method === "GET" ? request.headers.range : undefined, request.headers["if-range"], request.method === "HEAD" ? "HEAD" : "GET", ).pipe( Effect.orElseSucceed(() => HttpServerResponse.text("Internal Server Error", { status: 500 })), ); }),); export const attachmentUploadRouteLayer = HttpRouter.add( "POST", `${ATTACHMENT_UPLOAD_ROUTE_PREFIX}/*`, Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; const url = HttpServerRequest.toURL(request); if (Option.isNone(url)) { return HttpServerResponse.text("Bad Request", { status: 400 }); } const token = url.value.pathname.slice(`${ATTACHMENT_UPLOAD_ROUTE_PREFIX}/`.length); if (!token) { return HttpServerResponse.text("Not Found", { status: 404 }); } const claims = yield* validateAttachmentUploadToken(token); if (!claims) { return HttpServerResponse.text("Not Found", { status: 404 }); } const contentLengthHeader = request.headers["content-length"]; if ( contentLengthHeader !== undefined && (!Number.isInteger(Number(contentLengthHeader)) || Number(contentLengthHeader) !== claims.sizeBytes) ) { return HttpServerResponse.text("Content-Length must match the upload size.", { status: 400, }); } // Keep the request stream in the route scope until the response is sent. const bodyPull = yield* Stream.toPull(request.stream); const stored = yield* storeAttachmentUpload(claims, Stream.fromPull(Effect.succeed(bodyPull))); return stored.ok ? HttpServerResponse.empty({ status: 204 }) : HttpServerResponse.text(stored.detail, { status: stored.status }); }),); const decodeBuildManifest = Schema.decodeUnknownEffect( Schema.fromJsonString( Schema.Record( Schema.String, Schema.Struct({ file: Schema.String, css: Schema.optional(Schema.Array(Schema.String)), assets: Schema.optional(Schema.Array(Schema.String)), }), ), ),); const loadImmutableBuildAssets = Effect.gen(function* () { const config = yield* ServerConfig.ServerConfig; const staticDir = config.staticDir ?? (config.devUrl ? yield* ServerConfig.resolveStaticDir() : undefined); if (!staticDir) return new Set<string>(); const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; return yield* fileSystem.readFileString(path.join(staticDir, ".vite", "manifest.json")).pipe( Effect.flatMap(decodeBuildManifest), Effect.map( (manifest) => new Set( Object.values(manifest).flatMap((entry) => [ entry.file, ...(entry.css ?? []), ...(entry.assets ?? []), ]), ), ), Effect.orElseSucceed(() => new Set<string>()), );}); const openStaticFile = Effect.fn("openStaticFile")(function* (filePath: string) { const fileSystem = yield* FileSystem.FileSystem; // Reject directories and special files before opening. Response metadata comes from the handle. const pathInfo = yield* fileSystem.stat(filePath).pipe(Effect.orElseSucceed(() => null)); if (pathInfo?.type !== "File") return null; const file = yield* fileSystem.open(filePath, { flag: "r" }); const info = yield* file.stat; return info.type === "File" ? { file, info } : null;}); const streamStaticFile = (file: FileSystem.File, size: bigint) => Stream.unfold( 0n, Effect.fnUntraced(function* (offset: bigint) { if (offset >= size) return; const remaining = size - offset; const bytes = yield* file.readAlloc(remaining < 65_536n ? remaining : 65_536n); if (Option.isNone(bytes)) return; return [bytes.value, offset + BigInt(bytes.value.byteLength)] as const; }), ); const handleStaticAndDevRequest = Effect.fn("handleStaticAndDevRequest")( function* (immutableBuildAssets: ReadonlySet<string>) {Lines 1–500 of 634. Load more
import Mime from "@effect/platform-node/Mime"; import { AuthOrchestrationOperateScope, AuthOrchestrationReadScope, EnvironmentHttpApi, } from "@t3tools/contracts"; import { isDevProxiedPath } from "@t3tools/shared/devProxy"; import { decodeOtlpTraceRecords } from "@t3tools/shared/observability"; import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; import { cast } from "effect/Function"; import { HttpBody, HttpClient, HttpClientResponse, HttpMiddleware, HttpRouter, HttpServerResponse, HttpServerRequest, HttpServerRespondable, } from "effect/unstable/http"; import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder"; import { OtlpTracer } from "effect/unstable/observability"; import * as ServerConfig from "./config.ts"; import { ASSET_ROUTE_PREFIX, resolveAsset } from "./assets/AssetAccess.ts"; import { statMediaFile, streamMediaFile, type OpenMediaFile } from "./assets/MediaFile.ts"; import { ATTACHMENT_UPLOAD_ROUTE_PREFIX, storeAttachmentUpload, validateAttachmentUploadToken, } from "./assets/AttachmentUpload.ts"; import * as BrowserTraceCollector from "./observability/BrowserTraceCollector.ts"; import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts"; import { traceRelayRequest } from "./cloud/traceRelayRequest.ts"; import { annotateEnvironmentRequest, failEnvironmentScopeRequired, failEnvironmentAuthInvalid, failEnvironmentInternal, } from "./auth/http.ts"; import * as ServerEnvironment from "./environment/ServerEnvironment.ts"; import { browserApiCorsAllowedHeaders, browserApiCorsAllowedMethods } from "./httpCors.ts"; const OTLP_TRACES_PROXY_PATH = "/api/observability/v1/traces"; const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "::1", "localhost"]); const DESKTOP_RENDERER_ORIGINS = ["t3code://app", "t3code-dev://app"]; const SVG_CONTENT_SECURITY_POLICY = "default-src 'none'; style-src 'unsafe-inline'; sandbox"; // HTML previews are agent output, not the app. The sandbox gives the document an // opaque origin: scripts run, but same-origin cookies, storage, and API calls are // out of reach. Relative sibling assets still load through their signed URLs. const HTML_CONTENT_SECURITY_POLICY = "sandbox allow-scripts allow-forms allow-popups allow-modals"; // Types a browser may render as a document if a proxy strips the disposition // header. Downloads of these fall back to octet-stream. const DOWNLOAD_MIME_TYPE_PATTERN = /^[\w!#$&^.+-]+\/[\w!#$&^.+-]+$/; const isSafeDownloadMimeType = (mimeType: string): boolean => DOWNLOAD_MIME_TYPE_PATTERN.test(mimeType) && !/(?:^text\/html$|\/xml(?:$|-)|\+xml$)/i.test(mimeType.trim().toLowerCase()); const isSafeInlineVideoMimeType = (mimeType: string): boolean => DOWNLOAD_MIME_TYPE_PATTERN.test(mimeType) && mimeType.toLowerCase().startsWith("video/"); const isSafeInlineDocumentMimeType = (mimeType: string): boolean => mimeType.toLowerCase() === "application/pdf" || mimeType.toLowerCase() === "text/html"; /** RFC 6266 disposition with an ASCII fallback name plus a UTF-8 `filename*`. */ export function downloadContentDisposition(fileName?: string): string { if (fileName === undefined) { return "attachment"; } // toWellFormed: encodeURIComponent throws URIError on unpaired surrogates. const sanitized = fileName.toWellFormed().replace(/[\p{Cc}"\\]/gu, "_"); const asciiFallback = sanitized.replace(/[^\u0020-\u007e]/g, "_"); const needsExtended = asciiFallback !== sanitized; const extendedName = encodeURIComponent(sanitized).replace( /['()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`, ); return `attachment; filename="${asciiFallback}"${ needsExtended ? `; filename*=UTF-8''${extendedName}` : "" }`; } export function assetResponseHeaders( filePath: string, options?: { readonly download?: boolean; readonly fileName?: string; readonly mimeType?: string; }, ): Record<string, string> { const lowerPath = filePath.toLowerCase(); const inlineMimeType = options?.mimeType?.split(";", 1)[0]?.trim(); return { "Cache-Control": "private, max-age=3600", "X-Content-Type-Options": "nosniff", ...(options?.download ? { "Content-Disposition": downloadContentDisposition(options.fileName), "Content-Security-Policy": "default-src 'none'; sandbox", "Content-Type": options.mimeType !== undefined && isSafeDownloadMimeType(options.mimeType) ? options.mimeType : "application/octet-stream", } : inlineMimeType !== undefined && isSafeInlineVideoMimeType(inlineMimeType) ? { "Content-Type": inlineMimeType } : inlineMimeType !== undefined && isSafeInlineDocumentMimeType(inlineMimeType) ? { "Content-Type": inlineMimeType.toLowerCase() === "text/html" ? "text/html; charset=utf-8" : "application/pdf", ...(inlineMimeType.toLowerCase() === "text/html" ? { "Content-Security-Policy": HTML_CONTENT_SECURITY_POLICY } : {}), } : lowerPath.endsWith(".html") || lowerPath.endsWith(".htm") ? { "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": HTML_CONTENT_SECURITY_POLICY, } : {}), ...(!options?.download && lowerPath.endsWith(".svg") ? { "Content-Security-Policy": SVG_CONTENT_SECURITY_POLICY } : {}), }; } /** A single byte range for native video readers; unsupported range syntax uses the full file. */ function assetByteRange(header: string, size: bigint) { const match = /^bytes=(\d*)-(\d*)$/i.exec(header.trim()); if (!match || (!match[1] && !match[2])) return null; const first = match[1] ? BigInt(match[1]) : null; const last = match[2] ? BigInt(match[2]) : null; if (first !== null && last !== null && last < first) return null; if (size === 0n || (first !== null && first >= size) || (first === null && last === 0n)) { return { _tag: "Unsatisfiable" as const }; } const start = first ?? (last! >= size ? 0n : size - last!); const end = first === null || last === null || last >= size ? size - 1n : last; if (!Number.isSafeInteger(Number(start)) || !Number.isSafeInteger(Number(end))) { return { _tag: "Unsatisfiable" as const }; } return { _tag: "Range" as const, offset: start, bytesToRead: end - start + 1n, contentRange: `bytes ${start}-${end}/${size}`, }; } export const assetFileResponse = Effect.fn("assetFileResponse")(function* ( asset: { readonly path: string; readonly download?: boolean; readonly fileName?: string; readonly mimeType?: string; readonly file?: OpenMediaFile; }, rangeHeader?: string, ifRangeHeader?: string, method: "GET" | "HEAD" = "GET", ) { const headers = assetResponseHeaders(asset.path, asset); const mediaFile = asset.file; const mediaInfo = mediaFile ? yield* statMediaFile(asset.path, mediaFile) : undefined; const isVideo = headers["Content-Type"]?.toLowerCase().startsWith("video/") === true; if (mediaFile && isVideo) { // Host videos can change in place. Do not invite conditional range requests // with validators that cannot establish byte-for-byte identity. headers["Cache-Control"] = "private, no-store"; } let status = 200; let offset = 0n; let bytesToRead: bigint | undefined; if (isVideo) { headers["Accept-Ranges"] = "bytes"; // If-Range requires a matching validator. A full response is safe when we cannot validate it. if (method === "GET" && rangeHeader && ifRangeHeader === undefined) { const fs = yield* FileSystem.FileSystem; const info = mediaInfo ?? (yield* fs.stat(asset.path)); const range = assetByteRange(rangeHeader, info.size); if (range?._tag === "Unsatisfiable") { return HttpServerResponse.empty({ status: 416, headers: { ...headers, "Content-Range": `bytes */${info.size}` }, }); } if (range?._tag === "Range") { status = 206; offset = range.offset; bytesToRead = range.bytesToRead; headers["Content-Range"] = range.contentRange; } } } if (mediaFile && mediaInfo) { const size = bytesToRead ?? mediaInfo.size; headers["Content-Type"] ??= Mime.getType(asset.path) ?? "application/octet-stream"; headers["Content-Length"] = String(size); if (!isVideo) { headers["Last-Modified"] = mediaInfo.mtime.toUTCString(); headers.ETag = `W/"${mediaInfo.size.toString(16)}-${mediaInfo.mtimeMs.toString(16)}"`; } if (method === "HEAD" || size === 0n) { return HttpServerResponse.empty({ status, headers }); } const body = streamMediaFile(mediaFile, offset, size); if (!body) { return HttpServerResponse.text("File is too large to preview.", { status: 413 }); } return HttpServerResponse.stream(body, { status, headers, }); } return yield* HttpServerResponse.file(asset.path, { status, offset, bytesToRead, headers }); }); export const httpCompressionLayer = HttpRouter.middleware(HttpMiddleware.compression(), { global: true, }); export const browserApiCorsLayer = Layer.unwrap( Effect.gen(function* () { const config = yield* ServerConfig.ServerConfig; const devOrigin = config.devUrl?.origin; // Dev uses credentialed requests from Vite or the Electron custom origin, so both must be // explicit. Packaged desktop omits credentials and uses Effect's default wildcard origin. // // T3CODE_DEV_ALLOWED_ORIGINS covers dev servers reached from a second // origin — a tailnet name, a LAN IP, a phone. Browser dev normally proxies // through Vite and is same-origin (no preflight at all), so this is a // safety net for the desktop renderer and any direct-to-backend caller. return HttpRouter.cors({ ...(devOrigin ? { allowedOrigins: [devOrigin, ...DESKTOP_RENDERER_ORIGINS, ...config.devAllowedOrigins], credentials: true, } : {}), allowedMethods: browserApiCorsAllowedMethods, allowedHeaders: browserApiCorsAllowedHeaders, maxAge: 600, }); }), ); export function isLoopbackHostname(hostname: string): boolean { const normalizedHostname = hostname .trim() .toLowerCase() .replace(/^\[(.*)\]$/, "$1"); return LOOPBACK_HOSTNAMES.has(normalizedHostname); } export function resolveDevRedirectUrl(devUrl: URL, requestUrl: URL): string { const redirectUrl = new URL(devUrl.toString()); redirectUrl.pathname = requestUrl.pathname; redirectUrl.search = requestUrl.search; redirectUrl.hash = requestUrl.hash; return redirectUrl.toString(); } const authenticateRawRouteWithScope = ( scope: typeof AuthOrchestrationReadScope | typeof AuthOrchestrationOperateScope, ) => Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; const serverAuth = yield* EnvironmentAuth.EnvironmentAuth; const session = yield* serverAuth.authenticateHttpRequest(request).pipe( Effect.catchIf(EnvironmentAuth.isServerAuthCredentialError, (error) => failEnvironmentAuthInvalid( EnvironmentAuth.serverAuthCredentialReason(error), EnvironmentAuth.serverAuthDpopFailureReason(error), ), ), Effect.catchIf(EnvironmentAuth.isServerAuthInternalError, (error) => failEnvironmentInternal("internal_error", error), ), ); if (!session.scopes.includes(scope)) { return yield* failEnvironmentScopeRequired(scope); } }); export const serverEnvironmentHttpApiLayer = HttpApiBuilder.group( EnvironmentHttpApi, "metadata", Effect.fnUntraced(function* (handlers) { const serverEnvironment = yield* ServerEnvironment.ServerEnvironment; return handlers.handle( "descriptor", Effect.fn("environment.metadata.descriptor")(function* (args) { yield* annotateEnvironmentRequest(args.endpoint.name); return yield* serverEnvironment.getDescriptor; }, traceRelayRequest), ); }), ); class DecodeOtlpTraceRecordsError extends Data.TaggedError("DecodeOtlpTraceRecordsError")<{ readonly cause: unknown; readonly bodyJson: OtlpTracer.TraceData; }> {} export const otlpTracesProxyRouteLayer = HttpRouter.add( "POST", OTLP_TRACES_PROXY_PATH, Effect.gen(function* () { yield* authenticateRawRouteWithScope(AuthOrchestrationOperateScope); const request = yield* HttpServerRequest.HttpServerRequest; const config = yield* ServerConfig.ServerConfig; const otlpTracesUrl = config.otlpTracesUrl; const browserTraceCollector = yield* BrowserTraceCollector.BrowserTraceCollector; const httpClient = yield* HttpClient.HttpClient; const bodyJson = cast<unknown, OtlpTracer.TraceData>(yield* request.json); yield* Effect.try({ try: () => decodeOtlpTraceRecords(bodyJson), catch: (cause) => new DecodeOtlpTraceRecordsError({ cause, bodyJson }), }).pipe( Effect.flatMap((records) => browserTraceCollector.record(records)), Effect.catch((cause) => Effect.logWarning("Failed to decode browser OTLP traces", { cause, bodyJson, }), ), ); if (otlpTracesUrl === undefined) { return HttpServerResponse.empty({ status: 204 }); } return yield* httpClient .post(otlpTracesUrl, { body: HttpBody.jsonUnsafe(bodyJson), }) .pipe( Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.as(HttpServerResponse.empty({ status: 204 })), Effect.tapError((cause) => Effect.logWarning("Failed to export browser OTLP traces", { cause, otlpTracesUrl, }), ), Effect.orElseSucceed(() => HttpServerResponse.text("Trace export failed.", { status: 502 }), ), ); }).pipe( Effect.catchTags({ EnvironmentAuthInvalidError: HttpServerRespondable.toResponse, EnvironmentInternalError: HttpServerRespondable.toResponse, EnvironmentScopeRequiredError: HttpServerRespondable.toResponse, }), ), ); export const assetRouteLayer = HttpRouter.add( "GET", `${ASSET_ROUTE_PREFIX}/*`, Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; const url = HttpServerRequest.toURL(request); if (Option.isNone(url)) { return HttpServerResponse.text("Bad Request", { status: 400 }); } const suffix = url.value.pathname.slice(`${ASSET_ROUTE_PREFIX}/`.length); const separatorIndex = suffix.indexOf("/"); if (separatorIndex <= 0) { return HttpServerResponse.text("Not Found", { status: 404 }); } const asset = yield* resolveAsset( suffix.slice(0, separatorIndex), suffix.slice(separatorIndex + 1), ); if (!asset) { return HttpServerResponse.text("Not Found", { status: 404 }); } return yield* assetFileResponse( asset, request.method === "GET" ? request.headers.range : undefined, request.headers["if-range"], request.method === "HEAD" ? "HEAD" : "GET", ).pipe( Effect.orElseSucceed(() => HttpServerResponse.text("Internal Server Error", { status: 500 })), ); }), ); export const attachmentUploadRouteLayer = HttpRouter.add( "POST", `${ATTACHMENT_UPLOAD_ROUTE_PREFIX}/*`, Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest; const url = HttpServerRequest.toURL(request); if (Option.isNone(url)) { return HttpServerResponse.text("Bad Request", { status: 400 }); } const token = url.value.pathname.slice(`${ATTACHMENT_UPLOAD_ROUTE_PREFIX}/`.length); if (!token) { return HttpServerResponse.text("Not Found", { status: 404 }); } const claims = yield* validateAttachmentUploadToken(token); if (!claims) { return HttpServerResponse.text("Not Found", { status: 404 }); } const contentLengthHeader = request.headers["content-length"]; if ( contentLengthHeader !== undefined && (!Number.isInteger(Number(contentLengthHeader)) || Number(contentLengthHeader) !== claims.sizeBytes) ) { return HttpServerResponse.text("Content-Length must match the upload size.", { status: 400, }); } // Keep the request stream in the route scope until the response is sent. const bodyPull = yield* Stream.toPull(request.stream); const stored = yield* storeAttachmentUpload(claims, Stream.fromPull(Effect.succeed(bodyPull))); return stored.ok ? HttpServerResponse.empty({ status: 204 }) : HttpServerResponse.text(stored.detail, { status: stored.status }); }), ); const decodeBuildManifest = Schema.decodeUnknownEffect( Schema.fromJsonString( Schema.Record( Schema.String, Schema.Struct({ file: Schema.String, css: Schema.optional(Schema.Array(Schema.String)), assets: Schema.optional(Schema.Array(Schema.String)), }), ), ), ); const loadImmutableBuildAssets = Effect.gen(function* () { const config = yield* ServerConfig.ServerConfig; const staticDir = config.staticDir ?? (config.devUrl ? yield* ServerConfig.resolveStaticDir() : undefined); if (!staticDir) return new Set<string>(); const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; return yield* fileSystem.readFileString(path.join(staticDir, ".vite", "manifest.json")).pipe( Effect.flatMap(decodeBuildManifest), Effect.map( (manifest) => new Set( Object.values(manifest).flatMap((entry) => [ entry.file, ...(entry.css ?? []), ...(entry.assets ?? []), ]), ), ), Effect.orElseSucceed(() => new Set<string>()), ); }); const openStaticFile = Effect.fn("openStaticFile")(function* (filePath: string) { const fileSystem = yield* FileSystem.FileSystem; // Reject directories and special files before opening. Response metadata comes from the handle. const pathInfo = yield* fileSystem.stat(filePath).pipe(Effect.orElseSucceed(() => null)); if (pathInfo?.type !== "File") return null; const file = yield* fileSystem.open(filePath, { flag: "r" }); const info = yield* file.stat; return info.type === "File" ? { file, info } : null; }); const streamStaticFile = (file: FileSystem.File, size: bigint) => Stream.unfold( 0n, Effect.fnUntraced(function* (offset: bigint) { if (offset >= size) return; const remaining = size - offset; const bytes = yield* file.readAlloc(remaining < 65_536n ? remaining : 65_536n); if (Option.isNone(bytes)) return; return [bytes.value, offset + BigInt(bytes.value.byteLength)] as const; }), ); const handleStaticAndDevRequest = Effect.fn("handleStaticAndDevRequest")( function* (immutableBuildAssets: ReadonlySet<string>) {