330 lines · 10.8 KB · TypeScript
RawDownload
import * as Context from "effect/Context";import * as Crypto from "effect/Crypto";import * as Data from "effect/Data";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Scope from "effect/Scope";import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";import * as ElectronApp from "../electron/ElectronApp.ts";import * as ElectronWindow from "../electron/ElectronWindow.ts";import * as IpcChannels from "../ipc/channels.ts";import * as DesktopEnvironment from "./DesktopEnvironment.ts";import type * as Electron from "electron"; export const CLOUD_AUTH_CALLBACK_HOST = "auth";export const CLOUD_AUTH_CALLBACK_PATHNAME = "/callback";export const CLOUD_AUTH_CALLBACK_STATE_PARAM = "t3_state";export const CLOUD_AUTH_CALLBACK_SCHEME = "t3code";export const DEVELOPMENT_CLOUD_AUTH_CALLBACK_SCHEME = "t3code-dev"; const CLOUD_AUTH_REQUEST_TIMEOUT_MS = 5 * 60 * 1000; export class DesktopCloudAuthCallbackServerError extends Data.TaggedError(  "DesktopCloudAuthCallbackServerError",)<{  readonly cause: unknown;}> {  override get message() {    return "Failed to start the desktop cloud auth callback server.";  }} interface PendingCloudAuthRequest {  readonly state: string;  readonly redirectUrl: string;  readonly close: () => void;} export interface DesktopCloudAuthShape {  readonly createRequest: Effect.Effect<string, DesktopCloudAuthCallbackServerError>;  readonly configure: Effect.Effect<    void,    never,    ElectronApp.ElectronApp | ElectronWindow.ElectronWindow | Scope.Scope  >;} export class DesktopCloudAuth extends Context.Service<DesktopCloudAuth, DesktopCloudAuthShape>()(  "@t3tools/desktop/app/DesktopCloudAuth",) {} export function resolveCloudAuthCallbackScheme(input: { readonly isDevelopment: boolean }): string {  return input.isDevelopment ? DEVELOPMENT_CLOUD_AUTH_CALLBACK_SCHEME : CLOUD_AUTH_CALLBACK_SCHEME;} export function buildCloudAuthCallbackUrl(input: {  readonly scheme: string;  readonly state: string;}): string {  const url = new URL(    `${input.scheme}://${CLOUD_AUTH_CALLBACK_HOST}${CLOUD_AUTH_CALLBACK_PATHNAME}`,  );  url.searchParams.set(CLOUD_AUTH_CALLBACK_STATE_PARAM, input.state);  return url.toString();} export function parseCloudAuthCallbackUrl(input: {  readonly rawUrl: unknown;  readonly scheme: string;  readonly state: string;}): URL | null {  if (typeof input.rawUrl !== "string") {    return null;  }   try {    const url = new URL(input.rawUrl);    if (url.protocol !== `${input.scheme}:`) return null;    if (url.hostname !== CLOUD_AUTH_CALLBACK_HOST) return null;    if (url.pathname !== CLOUD_AUTH_CALLBACK_PATHNAME) return null;    if (url.searchParams.get(CLOUD_AUTH_CALLBACK_STATE_PARAM) !== input.state) return null;    return url;  } catch {    return null;  }} export function findCloudAuthCallbackUrl(input: {  readonly values: readonly unknown[];  readonly scheme: string;  readonly state: string;}): URL | null {  for (const value of input.values) {    const url = parseCloudAuthCallbackUrl({      rawUrl: value,      scheme: input.scheme,      state: input.state,    });    if (url) return url;  }  return null;} export function resolveProtocolClientLaunchArgs(input: {  readonly argv: readonly string[];}): readonly string[] {  return input.argv.slice(1);} function resolveConfiguredProtocolClient(): {  readonly path: string;  readonly args: readonly string[];} | null {  const path = process.env.T3CODE_DESKTOP_PROTOCOL_CLIENT_PATH?.trim();  if (!path) return null;   return {    path,    args: (process.env.T3CODE_DESKTOP_PROTOCOL_CLIENT_ARGS ?? "")      .split("\n")      .map((arg) => arg.trim())      .filter((arg) => arg.length > 0),  };} function isProtocolRegistrationManagedExternally(): boolean {  return process.env.T3CODE_DESKTOP_PROTOCOL_REGISTRATION_MANAGED?.trim() === "1";} function resolveProtocolCallbackForwardUrl(): URL | null {  const rawUrl = process.env.T3CODE_DESKTOP_PROTOCOL_CALLBACK_URL?.trim();  if (!rawUrl) return null;   try {    const url = new URL(rawUrl);    if (url.protocol !== "http:") return null;    if (url.hostname !== "127.0.0.1") return null;    if (url.pathname !== "/auth/callback") return null;    if (!url.port) return null;    return url;  } catch {    return null;  }} const closeCloudAuthRequest = (request: PendingCloudAuthRequest | null): null => {  request?.close();  return null;}; function createCloudAuthRequestTimeout(onExpire: () => void): ReturnType<typeof setTimeout> {  // @effect-diagnostics-next-line globalTimers:off - Auth request expiry is tied to an Electron callback server, not fiber scheduling.  return setTimeout(onExpire, CLOUD_AUTH_REQUEST_TIMEOUT_MS);} function ignoreCloudAuthCallback(_rawUrl: string) {} function startProtocolCallbackForwardServer(  callbackUrl: URL,  dispatch: (rawUrl: string) => void,): Effect.Effect<void, never, Scope.Scope> {  const port = Number.parseInt(callbackUrl.port, 10);  const routesLayer = HttpRouter.add(    "POST",    "/auth/callback",    Effect.gen(function* () {      const request = yield* HttpServerRequest.HttpServerRequest;      const rawUrl = yield* request.text;      yield* Effect.sync(() => {        dispatch(rawUrl);      });      return HttpServerResponse.empty({ status: 204 });    }),  );   return Effect.gen(function* () {    const NodeHttp = yield* Effect.promise(() => import("node:http"));    const serverLayer = NodeHttpServer.layer(NodeHttp.createServer, {      host: callbackUrl.hostname,      port,    });    yield* Layer.launch(HttpRouter.serve(routesLayer).pipe(Layer.provideMerge(serverLayer))).pipe(      Effect.forkScoped,    );  });} const make = Effect.gen(function* () {  const crypto = yield* Crypto.Crypto;  const environment = yield* DesktopEnvironment.DesktopEnvironment;  let pendingAuthRequest: PendingCloudAuthRequest | null = null;  let dispatchCloudAuthCallback: (rawUrl: string) => void = ignoreCloudAuthCallback;  const makeCloudAuthRequestState = Effect.gen(function* () {    const [left, right] = yield* Effect.all([crypto.randomUUIDv4, crypto.randomUUIDv4]);    return `${left}${right}`.replaceAll("-", "");  });   return DesktopCloudAuth.of({    createRequest: Effect.gen(function* () {      const scheme = resolveCloudAuthCallbackScheme({        isDevelopment: environment.isDevelopment,      });      const state = yield* makeCloudAuthRequestState.pipe(        Effect.mapError((cause) => new DesktopCloudAuthCallbackServerError({ cause })),      );       pendingAuthRequest = closeCloudAuthRequest(pendingAuthRequest);       const redirectUrl = buildCloudAuthCallbackUrl({ scheme, state });      const timeout = createCloudAuthRequestTimeout(() => {        pendingAuthRequest = closeCloudAuthRequest(pendingAuthRequest);      });      pendingAuthRequest = {        state,        redirectUrl,        close: () => clearTimeout(timeout),      };      return redirectUrl;    }),    configure: Effect.gen(function* () {      const electronApp = yield* ElectronApp.ElectronApp;      const electronWindow = yield* ElectronWindow.ElectronWindow;      const scope = yield* Scope.Scope;      const context = yield* Effect.context<ElectronWindow.ElectronWindow>();      const runPromise = Effect.runPromiseWith(context);      const scheme = resolveCloudAuthCallbackScheme({        isDevelopment: environment.isDevelopment,      });       yield* Scope.addFinalizer(        scope,        Effect.sync(() => {          pendingAuthRequest = closeCloudAuthRequest(pendingAuthRequest);        }),      );       if (isProtocolRegistrationManagedExternally()) {        // Development macOS launchers set the default URL handler before the stock Electron        // process starts so LaunchServices binds the scheme to the worktree-specific app bundle.      } else if (environment.isDevelopment) {        const configuredClient = resolveConfiguredProtocolClient();        if (configuredClient) {          yield* electronApp.setAsDefaultProtocolClient(            scheme,            configuredClient.path,            configuredClient.args,          );        } else {          yield* electronApp.setAsDefaultProtocolClient(            scheme,            process.execPath,            resolveProtocolClientLaunchArgs({ argv: process.argv }),          );        }      } else {        yield* electronApp.setAsDefaultProtocolClient(scheme);      }       dispatchCloudAuthCallback = (rawUrl: string) => {        const pending = pendingAuthRequest;        const callbackUrl = pending          ? parseCloudAuthCallbackUrl({ rawUrl, scheme, state: pending.state })          : null;        if (!callbackUrl) {          return;        }         pendingAuthRequest = closeCloudAuthRequest(pendingAuthRequest);        void runPromise(          Effect.gen(function* () {            yield* electronWindow.sendAll(              IpcChannels.CLOUD_AUTH_CALLBACK_CHANNEL,              callbackUrl.toString(),            );            const mainWindow = yield* electronWindow.currentMainOrFirst;            if (Option.isSome(mainWindow)) {              yield* electronWindow.reveal(mainWindow.value);            }          }),        );      };       const protocolCallbackForwardUrl = resolveProtocolCallbackForwardUrl();      if (environment.isDevelopment && protocolCallbackForwardUrl) {        yield* startProtocolCallbackForwardServer(          protocolCallbackForwardUrl,          dispatchCloudAuthCallback,        );      }       const hasInstanceLock = yield* electronApp.requestSingleInstanceLock;      if (!hasInstanceLock) {        return yield* electronApp.quit;      }       yield* electronApp.on<[Electron.Event, string]>("open-url", (event, rawUrl) => {        event.preventDefault?.();        dispatchCloudAuthCallback(rawUrl);      });       yield* electronApp.on<[Electron.Event, readonly string[]]>(        "second-instance",        (_event, argv) => {          const values = resolveProtocolClientLaunchArgs({ argv });          const pending = pendingAuthRequest;          const callbackUrl = pending            ? findCloudAuthCallbackUrl({ values, scheme, state: pending.state })            : null;          if (callbackUrl) {            dispatchCloudAuthCallback(callbackUrl.toString());            return;          }           void runPromise(            Effect.gen(function* () {              const mainWindow = yield* electronWindow.currentMainOrFirst;              if (Option.isSome(mainWindow)) {                yield* electronWindow.reveal(mainWindow.value);              }            }),          );        },      );    }).pipe(Effect.withSpan("desktop.cloudAuth.configure")),  });}); export const layer = Layer.effect(DesktopCloudAuth, make);