Skip to content
Source
Sign inSign up
Open History to see the last commit

antigravityCallback.ts

113 lines3.8 KB

1// @effect-diagnostics nodeBuiltinImport:off - node:http sends the one-shot loopback callback with no proxy, redirect handling, or response logging.
2import * as NodeHttp from "node:http";
3​
4import { ProviderSetupError, type ProviderInstanceId } from "@t3tools/contracts";
5import * as Effect from "effect/Effect";
6​
7export interface AntigravityPendingCallback {
8 readonly redirectUri: string;
9 readonly state: string;
11​
12export const validateAntigravityCallbackUrl = Effect.fn("validateAntigravityCallbackUrl")(
13 function* (
14 instanceId: ProviderInstanceId,
15 pending: AntigravityPendingCallback,
16 callbackUrl: string,
17 ) {
18 const invalid = (detail: string) =>
19 new ProviderSetupError({ instanceId, operation: "complete", detail });
20 if (callbackUrl.length > 16_384) {
21 return yield* invalid("The sign-in response URL is too long.");
22 }
23 const callback = yield* Effect.try({
24 try: () => new URL(callbackUrl),
25 catch: () => invalid("Paste the complete redirect URL from the Google sign-in page."),
26 });
27 const expected = new URL(pending.redirectUri);
28 if (
29 callback.protocol !== "http:" ||
30 callback.hostname !== "127.0.0.1" ||
31 callback.origin !== expected.origin ||
32 callback.pathname !== expected.pathname ||
33 callback.username !== "" ||
34 callback.password !== "" ||
35 callback.hash !== ""
36 ) {
37 return yield* invalid("This redirect URL does not belong to the current sign-in.");
38 }
39 const states = callback.searchParams.getAll("state");
40 if (states.length !== 1 || states[0] !== pending.state) {
41 return yield* invalid("This redirect URL does not belong to the current sign-in.");
42 }
43 const codes = callback.searchParams.getAll("code");
44 const errors = callback.searchParams.getAll("error");
45 if (
46 !(
47 (codes.length === 1 && Boolean(codes[0]) && errors.length === 0) ||
48 (errors.length === 1 && Boolean(errors[0]) && codes.length === 0)
49 )
50 ) {
51 return yield* invalid("The redirect URL must contain one Google sign-in response.");
52 }
53 const issuers = callback.searchParams.getAll("iss");
54 if (
55 issuers.length > 1 ||
56 (issuers.length === 1 && issuers[0] !== "https://accounts.google.com")
57 ) {
58 return yield* invalid("The redirect URL is not a Google sign-in response.");
59 }
60 return callback;
61 },
62);
63​
64export const forwardAntigravityCallback = (
65 instanceId: ProviderInstanceId,
66 callback: URL,
67): Effect.Effect<void, ProviderSetupError> =>
68 Effect.callback<void, ProviderSetupError>((resume) => {
69 const failed = () =>
70 new ProviderSetupError({
71 instanceId,
72 operation: "complete",
73 detail: "Could not deliver the sign-in response. Start sign-in again.",
74 });
75 let response: NodeHttp.IncomingMessage | undefined;
76 const request = NodeHttp.request(
77 {
78 protocol: "http:",
79 hostname: callback.hostname,
80 port: callback.port,
81 path: `${callback.pathname}${callback.search}`,
82 method: "GET",
83 agent: false,
84 },
85 (incoming) => {
86 response = incoming;
87 incoming.once("error", () => resume(Effect.fail(failed())));
88 incoming.once("end", () => {
89 const status = incoming.statusCode ?? 0;
90 resume(status >= 200 && status < 300 ? Effect.void : Effect.fail(failed()));
91 });
92 incoming.resume();
93 },
94 );
95 request.once("error", () => resume(Effect.fail(failed())));
96 request.end();
97 return Effect.sync(() => {
98 request.destroy();
99 response?.destroy();
100 });
101 }).pipe(
102 Effect.timeoutOrElse({
103 duration: "10 seconds",
104 orElse: () =>
105 Effect.fail(
106 new ProviderSetupError({
107 instanceId,
108 operation: "complete",
109 detail: "The sign-in response timed out. Start sign-in again.",
110 }),
111 ),
112 }),
113 );