158 lines · 5.0 KB · TypeScript
RawDownload
// @effect-diagnostics-next-line nodeBuiltinImport:off - NodeHttpServer.layer takes `NodeHttp.createServer` as argimport * as NodeHttp from "node:http"; import { NodeHttpServer, NodeRuntime, NodeServices } from "@effect/platform-node";import * as Config from "effect/Config";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Path from "effect/Path";import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; interface MockUpdateServerConfig {  readonly port: number;  readonly rootRealPath: string;} const resolveMockUpdateServerConfig = Effect.gen(function* () {  const fileSystem = yield* FileSystem.FileSystem;  const path = yield* Path.Path;  const config = yield* Config.all({    port: Config.port("T3CODE_DESKTOP_MOCK_UPDATE_SERVER_PORT").pipe(Config.withDefault(3000)),    root: Config.string("T3CODE_DESKTOP_MOCK_UPDATE_SERVER_ROOT").pipe(      Config.withDefault("../release-mock"),    ),  });   const resolvedRoot = path.resolve(import.meta.dirname, config.root);   return {    port: config.port,    rootRealPath: yield* fileSystem.realPath(resolvedRoot),  } satisfies MockUpdateServerConfig;}); const isOutsideRoot = (rootRealPath: string, filePath: string) =>  Effect.gen(function* () {    const path = yield* Path.Path;    const relativePath = path.relative(rootRealPath, filePath);    return (      relativePath === ".." || relativePath.startsWith("../") || relativePath.startsWith("..\\")    );  }); const isWithinRoot = (rootRealPath: string, filePath: string) =>  Effect.gen(function* () {    const fileSystem = yield* FileSystem.FileSystem;    const resolvedFilePath = yield* fileSystem.realPath(filePath).pipe(      Effect.match({        onFailure: () => undefined,        onSuccess: (resolvedPath) => resolvedPath,      }),    );     return (      resolvedFilePath !== undefined && !(yield* isOutsideRoot(rootRealPath, resolvedFilePath))    );  }); const resolveRequestedFilePath = (rootRealPath: string, requestUrl: string | undefined) =>  Effect.gen(function* () {    const path = yield* Path.Path;    const rawPath = (requestUrl ?? "/").split("?", 1)[0] ?? "/";    const decodedPath = yield* Effect.try({      try: () => decodeURIComponent(rawPath),      catch: () => null,    }).pipe(      Effect.match({        onFailure: () => undefined,        onSuccess: (value) => value,      }),    );     if (!decodedPath) {      return undefined;    }     if (decodedPath.includes("\0")) {      return undefined;    }     const filePath = path.resolve(      rootRealPath,      `.${decodedPath.startsWith("/") ? decodedPath : `/${decodedPath}`}`,    );     return (yield* isOutsideRoot(rootRealPath, filePath)) ? undefined : filePath;  }); const isServableFile = (rootRealPath: string, filePath: string) =>  Effect.gen(function* () {    const fileSystem = yield* FileSystem.FileSystem;    const stat = yield* fileSystem.stat(filePath).pipe(      Effect.match({        onFailure: () => undefined,        onSuccess: (info) => info,      }),    );     if (stat?.type !== "File") {      return false;    }     return yield* isWithinRoot(rootRealPath, filePath);  }); export const makeMockUpdateRouteLayer = (rootRealPath: string) => {  return HttpRouter.add(    "*",    "*",    Effect.gen(function* () {      const request = yield* HttpServerRequest.HttpServerRequest;      const requestPath = (request.url ?? "/").split("?", 1)[0] ?? "/";      yield* Effect.logInfo(`Request received for path: ${requestPath}`);       const filePath = yield* resolveRequestedFilePath(rootRealPath, request.url);      if (!filePath) {        yield* Effect.logWarning(`Attempted to access file outside of root: ${request.url ?? "/"}`);        return HttpServerResponse.text("Not Found", { status: 404 });      }       if (!(yield* isServableFile(rootRealPath, filePath))) {        yield* Effect.logWarning(`Attempted to access invalid file: ${filePath}`);        return HttpServerResponse.text("Not Found", { status: 404 });      }       yield* Effect.logInfo(`Serving file: ${filePath}`);      return yield* HttpServerResponse.file(filePath, { status: 200 });    }).pipe(      Effect.catchCause((cause) =>        Effect.gen(function* () {          yield* Effect.logError(`Unhandled mock update request failure: ${cause}`);          return HttpServerResponse.text("Internal Server Error", { status: 500 });        }),      ),    ),  );}; const makeMockUpdateServerLayer = (config: MockUpdateServerConfig) =>  HttpRouter.serve(makeMockUpdateRouteLayer(config.rootRealPath)).pipe(    Layer.provideMerge(      NodeHttpServer.layer(NodeHttp.createServer, {        host: "localhost",        port: config.port,      }),    ),    Layer.provideMerge(NodeServices.layer),  ); if (import.meta.main) {  resolveMockUpdateServerConfig.pipe(    Effect.map(makeMockUpdateServerLayer),    Layer.unwrap,    Layer.launch,    Effect.provide(NodeServices.layer),    NodeRuntime.runMain,  );}