@nimbase/cli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/chunk-2BYB6W4B.js +44 -0
  2. package/dist/chunk-2BYB6W4B.js.map +1 -0
  3. package/dist/chunk-6MCLE65K.js +46 -0
  4. package/dist/chunk-6MCLE65K.js.map +1 -0
  5. package/dist/chunk-IJCEQQWX.js +150 -0
  6. package/dist/chunk-IJCEQQWX.js.map +1 -0
  7. package/dist/chunk-KKZVWJDK.js +528 -0
  8. package/dist/chunk-KKZVWJDK.js.map +1 -0
  9. package/dist/commands/auth/login.js +8 -32
  10. package/dist/commands/auth/login.js.map +1 -1
  11. package/dist/commands/auth/logout.js +14 -15
  12. package/dist/commands/auth/logout.js.map +1 -1
  13. package/dist/commands/auth/signup.js +18 -0
  14. package/dist/commands/auth/signup.js.map +1 -0
  15. package/dist/commands/auth/status.js +1 -1
  16. package/dist/commands/connector/connect.js +93 -0
  17. package/dist/commands/connector/connect.js.map +1 -0
  18. package/dist/commands/connector/list.js +42 -0
  19. package/dist/commands/connector/list.js.map +1 -0
  20. package/dist/commands/connector/revoke.js +50 -0
  21. package/dist/commands/connector/revoke.js.map +1 -0
  22. package/dist/commands/connector/status.js +46 -0
  23. package/dist/commands/connector/status.js.map +1 -0
  24. package/dist/commands/connector/sync.js +57 -0
  25. package/dist/commands/connector/sync.js.map +1 -0
  26. package/dist/commands/workspace/create.js +2 -4
  27. package/dist/commands/workspace/create.js.map +1 -1
  28. package/dist/commands/workspace/current.js +2 -4
  29. package/dist/commands/workspace/current.js.map +1 -1
  30. package/dist/commands/workspace/list.js +2 -4
  31. package/dist/commands/workspace/list.js.map +1 -1
  32. package/dist/commands/workspace/select.js +2 -4
  33. package/dist/commands/workspace/select.js.map +1 -1
  34. package/package.json +13 -3
  35. package/dist/chunk-YQIGSGD4.js +0 -293
  36. package/dist/chunk-YQIGSGD4.js.map +0 -1
  37. package/dist/chunk-Z6XCOT3K.js +0 -35
  38. package/dist/chunk-Z6XCOT3K.js.map +0 -1
@@ -0,0 +1,44 @@
1
+ import {
2
+ authPageUrl,
3
+ openInBrowser,
4
+ startBrowserAuthCallback
5
+ } from "./chunk-IJCEQQWX.js";
6
+ import {
7
+ NimbaseCommand,
8
+ describeError,
9
+ env,
10
+ writeSession
11
+ } from "./chunk-KKZVWJDK.js";
12
+
13
+ // src/browser-auth-command.ts
14
+ var BrowserAuthCommand = class extends NimbaseCommand {
15
+ async authenticateInBrowser(page) {
16
+ const callback = await startBrowserAuthCallback(
17
+ new URL(env.NIMBASE_CLI_APP_URL).origin
18
+ ).catch((error) => this.error(describeError(error, this.apiUrl)));
19
+ const url = authPageUrl(env.NIMBASE_CLI_APP_URL, page, {
20
+ callbackPort: callback.port,
21
+ state: callback.state
22
+ });
23
+ this.log(`Opening Nimbase ${page} in your browser: ${url}`);
24
+ try {
25
+ await openInBrowser(url).catch(() => {
26
+ this.warn(`Could not open a browser. Visit ${url}`);
27
+ });
28
+ const oneTimeToken = await callback.token;
29
+ const token = await this.api.exchangeOneTimeToken(oneTimeToken);
30
+ const user = await this.api.getUser(token);
31
+ await writeSession(this.apiUrl, token);
32
+ this.log(`Signed in as ${user.email}.`);
33
+ } catch (error) {
34
+ this.error(describeError(error, this.apiUrl));
35
+ } finally {
36
+ await callback.close().catch(() => void 0);
37
+ }
38
+ }
39
+ };
40
+
41
+ export {
42
+ BrowserAuthCommand
43
+ };
44
+ //# sourceMappingURL=chunk-2BYB6W4B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/browser-auth-command.ts"],"sourcesContent":["import type { AuthPage } from \"./browser.js\";\nimport { NimbaseCommand } from \"./base-command.js\";\nimport {\n authPageUrl,\n openInBrowser,\n startBrowserAuthCallback,\n} from \"./browser.js\";\nimport { writeSession } from \"./credentials.js\";\nimport { env } from \"./env.js\";\nimport { describeError } from \"./error-message.js\";\n\nexport abstract class BrowserAuthCommand extends NimbaseCommand {\n protected async authenticateInBrowser(page: AuthPage): Promise<void> {\n const callback = await startBrowserAuthCallback(\n new URL(env.NIMBASE_CLI_APP_URL).origin,\n ).catch((error: unknown) => this.error(describeError(error, this.apiUrl)));\n const url = authPageUrl(env.NIMBASE_CLI_APP_URL, page, {\n callbackPort: callback.port,\n state: callback.state,\n });\n\n this.log(`Opening Nimbase ${page} in your browser: ${url}`);\n try {\n await openInBrowser(url).catch(() => {\n this.warn(`Could not open a browser. Visit ${url}`);\n });\n const oneTimeToken = await callback.token;\n const token = await this.api.exchangeOneTimeToken(oneTimeToken);\n const user = await this.api.getUser(token);\n await writeSession(this.apiUrl, token);\n this.log(`Signed in as ${user.email}.`);\n } catch (error) {\n this.error(describeError(error, this.apiUrl));\n } finally {\n await callback.close().catch(() => undefined);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAWO,IAAe,qBAAf,cAA0C,eAAe;AAAA,EAC9D,MAAgB,sBAAsB,MAA+B;AACnE,UAAM,WAAW,MAAM;AAAA,MACrB,IAAI,IAAI,IAAI,mBAAmB,EAAE;AAAA,IACnC,EAAE,MAAM,CAAC,UAAmB,KAAK,MAAM,cAAc,OAAO,KAAK,MAAM,CAAC,CAAC;AACzE,UAAM,MAAM,YAAY,IAAI,qBAAqB,MAAM;AAAA,MACrD,cAAc,SAAS;AAAA,MACvB,OAAO,SAAS;AAAA,IAClB,CAAC;AAED,SAAK,IAAI,mBAAmB,IAAI,qBAAqB,GAAG,EAAE;AAC1D,QAAI;AACF,YAAM,cAAc,GAAG,EAAE,MAAM,MAAM;AACnC,aAAK,KAAK,mCAAmC,GAAG,EAAE;AAAA,MACpD,CAAC;AACD,YAAM,eAAe,MAAM,SAAS;AACpC,YAAM,QAAQ,MAAM,KAAK,IAAI,qBAAqB,YAAY;AAC9D,YAAM,OAAO,MAAM,KAAK,IAAI,QAAQ,KAAK;AACzC,YAAM,aAAa,KAAK,QAAQ,KAAK;AACrC,WAAK,IAAI,gBAAgB,KAAK,KAAK,GAAG;AAAA,IACxC,SAAS,OAAO;AACd,WAAK,MAAM,cAAc,OAAO,KAAK,MAAM,CAAC;AAAA,IAC9C,UAAE;AACA,YAAM,SAAS,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,IAC9C;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,46 @@
1
+ // src/connectors.ts
2
+ function resolveConnector(connections, selector) {
3
+ const normalized = selector.trim().toLowerCase();
4
+ const matches = connections.filter(
5
+ (connection2) => connection2.id === selector || connection2.provider.toLowerCase() === normalized || connection2.displayName.toLowerCase() === normalized
6
+ );
7
+ const connection = matches[0];
8
+ if (!connection) return { kind: "not_found" };
9
+ if (matches.length > 1) return { kind: "ambiguous" };
10
+ return { kind: "found", connection };
11
+ }
12
+ function changedProviderConnection(before, after, provider) {
13
+ const timestamps = new Map(
14
+ before.map((connection) => [connection.id, connection.updatedAt.getTime()])
15
+ );
16
+ return after.find((connection) => {
17
+ if (connection.provider !== provider || connection.status !== "active") {
18
+ return false;
19
+ }
20
+ const previous = timestamps.get(connection.id);
21
+ return previous === void 0 || connection.updatedAt.getTime() > previous;
22
+ }) ?? null;
23
+ }
24
+ async function waitForConnectorConnection(load, before, provider, options) {
25
+ const now = options.now ?? Date.now;
26
+ const sleep = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
27
+ const deadline = now() + options.timeoutMs;
28
+ while (now() <= deadline) {
29
+ const connection = changedProviderConnection(
30
+ before,
31
+ await load(),
32
+ provider
33
+ );
34
+ if (connection) return connection;
35
+ const remaining = deadline - now();
36
+ if (remaining <= 0) return null;
37
+ await sleep(Math.min(options.pollIntervalMs ?? 1e3, remaining));
38
+ }
39
+ return null;
40
+ }
41
+
42
+ export {
43
+ resolveConnector,
44
+ waitForConnectorConnection
45
+ };
46
+ //# sourceMappingURL=chunk-6MCLE65K.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connectors.ts"],"sourcesContent":["import type { ConnectorConnection } from \"@nimbase/sdk\";\n\nexport function resolveConnector(\n connections: readonly ConnectorConnection[],\n selector: string,\n):\n | { readonly kind: \"found\"; readonly connection: ConnectorConnection }\n | { readonly kind: \"not_found\" }\n | { readonly kind: \"ambiguous\" } {\n const normalized = selector.trim().toLowerCase();\n const matches = connections.filter(\n (connection) =>\n connection.id === selector ||\n connection.provider.toLowerCase() === normalized ||\n connection.displayName.toLowerCase() === normalized,\n );\n const connection = matches[0];\n if (!connection) return { kind: \"not_found\" };\n if (matches.length > 1) return { kind: \"ambiguous\" };\n return { kind: \"found\", connection };\n}\n\nexport function changedProviderConnection(\n before: readonly ConnectorConnection[],\n after: readonly ConnectorConnection[],\n provider: string,\n): ConnectorConnection | null {\n const timestamps = new Map(\n before.map((connection) => [connection.id, connection.updatedAt.getTime()]),\n );\n return (\n after.find((connection) => {\n if (connection.provider !== provider || connection.status !== \"active\") {\n return false;\n }\n const previous = timestamps.get(connection.id);\n return (\n previous === undefined || connection.updatedAt.getTime() > previous\n );\n }) ?? null\n );\n}\n\nexport async function waitForConnectorConnection(\n load: () => Promise<readonly ConnectorConnection[]>,\n before: readonly ConnectorConnection[],\n provider: string,\n options: {\n readonly timeoutMs: number;\n readonly pollIntervalMs?: number;\n readonly now?: () => number;\n readonly sleep?: (milliseconds: number) => Promise<void>;\n },\n): Promise<ConnectorConnection | null> {\n const now = options.now ?? Date.now;\n const sleep =\n options.sleep ??\n ((milliseconds: number) =>\n new Promise<void>((resolve) => setTimeout(resolve, milliseconds)));\n const deadline = now() + options.timeoutMs;\n while (now() <= deadline) {\n const connection = changedProviderConnection(\n before,\n await load(),\n provider,\n );\n if (connection) return connection;\n const remaining = deadline - now();\n if (remaining <= 0) return null;\n await sleep(Math.min(options.pollIntervalMs ?? 1_000, remaining));\n }\n return null;\n}\n"],"mappings":";AAEO,SAAS,iBACd,aACA,UAIiC;AACjC,QAAM,aAAa,SAAS,KAAK,EAAE,YAAY;AAC/C,QAAM,UAAU,YAAY;AAAA,IAC1B,CAACA,gBACCA,YAAW,OAAO,YAClBA,YAAW,SAAS,YAAY,MAAM,cACtCA,YAAW,YAAY,YAAY,MAAM;AAAA,EAC7C;AACA,QAAM,aAAa,QAAQ,CAAC;AAC5B,MAAI,CAAC,WAAY,QAAO,EAAE,MAAM,YAAY;AAC5C,MAAI,QAAQ,SAAS,EAAG,QAAO,EAAE,MAAM,YAAY;AACnD,SAAO,EAAE,MAAM,SAAS,WAAW;AACrC;AAEO,SAAS,0BACd,QACA,OACA,UAC4B;AAC5B,QAAM,aAAa,IAAI;AAAA,IACrB,OAAO,IAAI,CAAC,eAAe,CAAC,WAAW,IAAI,WAAW,UAAU,QAAQ,CAAC,CAAC;AAAA,EAC5E;AACA,SACE,MAAM,KAAK,CAAC,eAAe;AACzB,QAAI,WAAW,aAAa,YAAY,WAAW,WAAW,UAAU;AACtE,aAAO;AAAA,IACT;AACA,UAAM,WAAW,WAAW,IAAI,WAAW,EAAE;AAC7C,WACE,aAAa,UAAa,WAAW,UAAU,QAAQ,IAAI;AAAA,EAE/D,CAAC,KAAK;AAEV;AAEA,eAAsB,2BACpB,MACA,QACA,UACA,SAMqC;AACrC,QAAM,MAAM,QAAQ,OAAO,KAAK;AAChC,QAAM,QACJ,QAAQ,UACP,CAAC,iBACA,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,YAAY,CAAC;AACpE,QAAM,WAAW,IAAI,IAAI,QAAQ;AACjC,SAAO,IAAI,KAAK,UAAU;AACxB,UAAM,aAAa;AAAA,MACjB;AAAA,MACA,MAAM,KAAK;AAAA,MACX;AAAA,IACF;AACA,QAAI,WAAY,QAAO;AACvB,UAAM,YAAY,WAAW,IAAI;AACjC,QAAI,aAAa,EAAG,QAAO;AAC3B,UAAM,MAAM,KAAK,IAAI,QAAQ,kBAAkB,KAAO,SAAS,CAAC;AAAA,EAClE;AACA,SAAO;AACT;","names":["connection"]}
@@ -0,0 +1,150 @@
1
+ // src/browser.ts
2
+ import { spawn } from "child_process";
3
+ import { randomBytes, timingSafeEqual } from "crypto";
4
+ import { createServer } from "http";
5
+ function authPageUrl(appUrl, page, callback) {
6
+ const url = new URL(appUrl);
7
+ url.pathname = `/${page}`;
8
+ url.search = "";
9
+ url.hash = "";
10
+ if (callback) {
11
+ url.searchParams.set("callback_port", callback.callbackPort.toString());
12
+ url.searchParams.set("state", callback.state);
13
+ }
14
+ return url.toString();
15
+ }
16
+ async function startBrowserAuthCallback(allowedOrigin, options = {}) {
17
+ const origin = new URL(allowedOrigin).origin;
18
+ const state = options.state ?? randomBytes(32).toString("base64url");
19
+ const timeoutMs = options.timeoutMs ?? 5 * 60 * 1e3;
20
+ let completed = false;
21
+ let resolveToken = () => void 0;
22
+ let rejectToken = () => void 0;
23
+ const token = new Promise((resolve, reject) => {
24
+ resolveToken = resolve;
25
+ rejectToken = reject;
26
+ });
27
+ const server = createServer((request, response) => {
28
+ void handleCallbackRequest(request, response, {
29
+ origin,
30
+ state,
31
+ completed: () => completed,
32
+ complete: (oneTimeToken) => {
33
+ completed = true;
34
+ resolveToken(oneTimeToken);
35
+ }
36
+ }).catch(() => {
37
+ if (!response.headersSent) response.writeHead(400);
38
+ response.end();
39
+ });
40
+ });
41
+ await new Promise((resolve, reject) => {
42
+ const handleError = (error) => reject(error);
43
+ server.once("error", handleError);
44
+ server.listen(0, "127.0.0.1", () => {
45
+ server.off("error", handleError);
46
+ resolve();
47
+ });
48
+ });
49
+ server.on("error", (error) => rejectToken(error));
50
+ const address = server.address();
51
+ const timer = setTimeout(() => {
52
+ rejectToken(new Error("Browser authentication timed out. Try again."));
53
+ void closeServer(server);
54
+ }, timeoutMs);
55
+ void token.finally(() => clearTimeout(timer)).catch(() => void 0);
56
+ return {
57
+ port: address.port,
58
+ state,
59
+ token,
60
+ close: () => closeServer(server)
61
+ };
62
+ }
63
+ function browserLaunchCommand(url, platform = process.platform) {
64
+ if (platform === "darwin") return { command: "open", args: [url] };
65
+ if (platform === "win32") {
66
+ return {
67
+ command: "cmd",
68
+ args: ["/d", "/s", "/c", `start "" "${url}"`]
69
+ };
70
+ }
71
+ return { command: "xdg-open", args: [url] };
72
+ }
73
+ async function openInBrowser(url) {
74
+ const launch = browserLaunchCommand(url);
75
+ await new Promise((resolve, reject) => {
76
+ const child = spawn(launch.command, launch.args, {
77
+ detached: true,
78
+ stdio: "ignore"
79
+ });
80
+ child.once("error", reject);
81
+ child.once("spawn", () => {
82
+ child.unref();
83
+ resolve();
84
+ });
85
+ });
86
+ }
87
+ async function handleCallbackRequest(request, response, options) {
88
+ if (request.headers.origin !== options.origin) {
89
+ response.writeHead(403).end();
90
+ return;
91
+ }
92
+ response.setHeader("Access-Control-Allow-Origin", options.origin);
93
+ response.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
94
+ response.setHeader("Access-Control-Allow-Headers", "Content-Type");
95
+ response.setHeader("Vary", "Origin");
96
+ if (request.headers["access-control-request-private-network"] === "true") {
97
+ response.setHeader("Access-Control-Allow-Private-Network", "true");
98
+ }
99
+ if (request.method === "OPTIONS") {
100
+ response.writeHead(204).end();
101
+ return;
102
+ }
103
+ if (request.method !== "POST" || request.url !== "/callback") {
104
+ response.writeHead(404).end();
105
+ return;
106
+ }
107
+ if (options.completed()) {
108
+ response.writeHead(409).end();
109
+ return;
110
+ }
111
+ const body = await readRequestBody(request);
112
+ const parsed = JSON.parse(body);
113
+ if (!isCallbackPayload(parsed) || !statesMatch(parsed.state, options.state)) {
114
+ response.writeHead(403).end();
115
+ return;
116
+ }
117
+ response.writeHead(204).end();
118
+ options.complete(parsed.token);
119
+ }
120
+ async function readRequestBody(request) {
121
+ let body = "";
122
+ for await (const chunk of request) {
123
+ body += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
124
+ if (Buffer.byteLength(body) > 8 * 1024) {
125
+ throw new Error("Callback payload is too large.");
126
+ }
127
+ }
128
+ return body;
129
+ }
130
+ function isCallbackPayload(value) {
131
+ return typeof value === "object" && value !== null && "state" in value && typeof value.state === "string" && "token" in value && typeof value.token === "string" && value.token.length > 0 && value.token.length <= 512;
132
+ }
133
+ function statesMatch(received, expected) {
134
+ const receivedBytes = Buffer.from(received);
135
+ const expectedBytes = Buffer.from(expected);
136
+ return receivedBytes.length === expectedBytes.length && timingSafeEqual(receivedBytes, expectedBytes);
137
+ }
138
+ async function closeServer(server) {
139
+ if (!server.listening) return;
140
+ await new Promise((resolve, reject) => {
141
+ server.close((error) => error ? reject(error) : resolve());
142
+ });
143
+ }
144
+
145
+ export {
146
+ authPageUrl,
147
+ startBrowserAuthCallback,
148
+ openInBrowser
149
+ };
150
+ //# sourceMappingURL=chunk-IJCEQQWX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/browser.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { createServer } from \"node:http\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\n\nexport type AuthPage = \"login\" | \"signup\";\n\ninterface AuthCallbackParameters {\n callbackPort: number;\n state: string;\n}\n\nexport interface BrowserAuthCallback {\n readonly port: number;\n readonly state: string;\n readonly token: Promise<string>;\n close(): Promise<void>;\n}\n\nexport function authPageUrl(\n appUrl: string,\n page: AuthPage,\n callback?: AuthCallbackParameters,\n): string {\n const url = new URL(appUrl);\n url.pathname = `/${page}`;\n url.search = \"\";\n url.hash = \"\";\n if (callback) {\n url.searchParams.set(\"callback_port\", callback.callbackPort.toString());\n url.searchParams.set(\"state\", callback.state);\n }\n return url.toString();\n}\n\nexport async function startBrowserAuthCallback(\n allowedOrigin: string,\n options: { state?: string; timeoutMs?: number } = {},\n): Promise<BrowserAuthCallback> {\n const origin = new URL(allowedOrigin).origin;\n const state = options.state ?? randomBytes(32).toString(\"base64url\");\n const timeoutMs = options.timeoutMs ?? 5 * 60 * 1_000;\n let completed = false;\n let resolveToken: (token: string) => void = () => undefined;\n let rejectToken: (error: Error) => void = () => undefined;\n const token = new Promise<string>((resolve, reject) => {\n resolveToken = resolve;\n rejectToken = reject;\n });\n\n const server = createServer((request, response) => {\n void handleCallbackRequest(request, response, {\n origin,\n state,\n completed: () => completed,\n complete: (oneTimeToken) => {\n completed = true;\n resolveToken(oneTimeToken);\n },\n }).catch(() => {\n if (!response.headersSent) response.writeHead(400);\n response.end();\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n const handleError = (error: Error) => reject(error);\n server.once(\"error\", handleError);\n server.listen(0, \"127.0.0.1\", () => {\n server.off(\"error\", handleError);\n resolve();\n });\n });\n\n server.on(\"error\", (error) => rejectToken(error));\n const address = server.address() as AddressInfo;\n const timer = setTimeout(() => {\n rejectToken(new Error(\"Browser authentication timed out. Try again.\"));\n void closeServer(server);\n }, timeoutMs);\n void token.finally(() => clearTimeout(timer)).catch(() => undefined);\n\n return {\n port: address.port,\n state,\n token,\n close: () => closeServer(server),\n };\n}\n\nexport function browserLaunchCommand(\n url: string,\n platform: NodeJS.Platform = process.platform,\n): { command: string; args: string[] } {\n if (platform === \"darwin\") return { command: \"open\", args: [url] };\n if (platform === \"win32\") {\n return {\n command: \"cmd\",\n args: [\"/d\", \"/s\", \"/c\", `start \"\" \"${url}\"`],\n };\n }\n return { command: \"xdg-open\", args: [url] };\n}\n\nexport async function openInBrowser(url: string): Promise<void> {\n const launch = browserLaunchCommand(url);\n\n await new Promise<void>((resolve, reject) => {\n const child = spawn(launch.command, launch.args, {\n detached: true,\n stdio: \"ignore\",\n });\n child.once(\"error\", reject);\n child.once(\"spawn\", () => {\n child.unref();\n resolve();\n });\n });\n}\n\ninterface CallbackRequestOptions {\n origin: string;\n state: string;\n completed(): boolean;\n complete(token: string): void;\n}\n\nasync function handleCallbackRequest(\n request: IncomingMessage,\n response: ServerResponse,\n options: CallbackRequestOptions,\n): Promise<void> {\n if (request.headers.origin !== options.origin) {\n response.writeHead(403).end();\n return;\n }\n\n response.setHeader(\"Access-Control-Allow-Origin\", options.origin);\n response.setHeader(\"Access-Control-Allow-Methods\", \"POST, OPTIONS\");\n response.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n response.setHeader(\"Vary\", \"Origin\");\n if (request.headers[\"access-control-request-private-network\"] === \"true\") {\n response.setHeader(\"Access-Control-Allow-Private-Network\", \"true\");\n }\n\n if (request.method === \"OPTIONS\") {\n response.writeHead(204).end();\n return;\n }\n if (request.method !== \"POST\" || request.url !== \"/callback\") {\n response.writeHead(404).end();\n return;\n }\n if (options.completed()) {\n response.writeHead(409).end();\n return;\n }\n\n const body = await readRequestBody(request);\n const parsed: unknown = JSON.parse(body);\n if (!isCallbackPayload(parsed) || !statesMatch(parsed.state, options.state)) {\n response.writeHead(403).end();\n return;\n }\n\n response.writeHead(204).end();\n options.complete(parsed.token);\n}\n\nasync function readRequestBody(request: IncomingMessage): Promise<string> {\n let body = \"\";\n for await (const chunk of request) {\n body += Buffer.isBuffer(chunk) ? chunk.toString(\"utf8\") : String(chunk);\n if (Buffer.byteLength(body) > 8 * 1_024) {\n throw new Error(\"Callback payload is too large.\");\n }\n }\n return body;\n}\n\nfunction isCallbackPayload(\n value: unknown,\n): value is { state: string; token: string } {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"state\" in value &&\n typeof value.state === \"string\" &&\n \"token\" in value &&\n typeof value.token === \"string\" &&\n value.token.length > 0 &&\n value.token.length <= 512\n );\n}\n\nfunction statesMatch(received: string, expected: string): boolean {\n const receivedBytes = Buffer.from(received);\n const expectedBytes = Buffer.from(expected);\n return (\n receivedBytes.length === expectedBytes.length &&\n timingSafeEqual(receivedBytes, expectedBytes)\n );\n}\n\nasync function closeServer(\n server: ReturnType<typeof createServer>,\n): Promise<void> {\n if (!server.listening) return;\n await new Promise<void>((resolve, reject) => {\n server.close((error) => (error ? reject(error) : resolve()));\n });\n}\n"],"mappings":";AAAA,SAAS,aAAa;AACtB,SAAS,aAAa,uBAAuB;AAC7C,SAAS,oBAAoB;AAkBtB,SAAS,YACd,QACA,MACA,UACQ;AACR,QAAM,MAAM,IAAI,IAAI,MAAM;AAC1B,MAAI,WAAW,IAAI,IAAI;AACvB,MAAI,SAAS;AACb,MAAI,OAAO;AACX,MAAI,UAAU;AACZ,QAAI,aAAa,IAAI,iBAAiB,SAAS,aAAa,SAAS,CAAC;AACtE,QAAI,aAAa,IAAI,SAAS,SAAS,KAAK;AAAA,EAC9C;AACA,SAAO,IAAI,SAAS;AACtB;AAEA,eAAsB,yBACpB,eACA,UAAkD,CAAC,GACrB;AAC9B,QAAM,SAAS,IAAI,IAAI,aAAa,EAAE;AACtC,QAAM,QAAQ,QAAQ,SAAS,YAAY,EAAE,EAAE,SAAS,WAAW;AACnE,QAAM,YAAY,QAAQ,aAAa,IAAI,KAAK;AAChD,MAAI,YAAY;AAChB,MAAI,eAAwC,MAAM;AAClD,MAAI,cAAsC,MAAM;AAChD,QAAM,QAAQ,IAAI,QAAgB,CAAC,SAAS,WAAW;AACrD,mBAAe;AACf,kBAAc;AAAA,EAChB,CAAC;AAED,QAAM,SAAS,aAAa,CAAC,SAAS,aAAa;AACjD,SAAK,sBAAsB,SAAS,UAAU;AAAA,MAC5C;AAAA,MACA;AAAA,MACA,WAAW,MAAM;AAAA,MACjB,UAAU,CAAC,iBAAiB;AAC1B,oBAAY;AACZ,qBAAa,YAAY;AAAA,MAC3B;AAAA,IACF,CAAC,EAAE,MAAM,MAAM;AACb,UAAI,CAAC,SAAS,YAAa,UAAS,UAAU,GAAG;AACjD,eAAS,IAAI;AAAA,IACf,CAAC;AAAA,EACH,CAAC;AAED,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAM,cAAc,CAAC,UAAiB,OAAO,KAAK;AAClD,WAAO,KAAK,SAAS,WAAW;AAChC,WAAO,OAAO,GAAG,aAAa,MAAM;AAClC,aAAO,IAAI,SAAS,WAAW;AAC/B,cAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AAED,SAAO,GAAG,SAAS,CAAC,UAAU,YAAY,KAAK,CAAC;AAChD,QAAM,UAAU,OAAO,QAAQ;AAC/B,QAAM,QAAQ,WAAW,MAAM;AAC7B,gBAAY,IAAI,MAAM,8CAA8C,CAAC;AACrE,SAAK,YAAY,MAAM;AAAA,EACzB,GAAG,SAAS;AACZ,OAAK,MAAM,QAAQ,MAAM,aAAa,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AAEnE,SAAO;AAAA,IACL,MAAM,QAAQ;AAAA,IACd;AAAA,IACA;AAAA,IACA,OAAO,MAAM,YAAY,MAAM;AAAA,EACjC;AACF;AAEO,SAAS,qBACd,KACA,WAA4B,QAAQ,UACC;AACrC,MAAI,aAAa,SAAU,QAAO,EAAE,SAAS,QAAQ,MAAM,CAAC,GAAG,EAAE;AACjE,MAAI,aAAa,SAAS;AACxB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,MAAM,CAAC,MAAM,MAAM,MAAM,aAAa,GAAG,GAAG;AAAA,IAC9C;AAAA,EACF;AACA,SAAO,EAAE,SAAS,YAAY,MAAM,CAAC,GAAG,EAAE;AAC5C;AAEA,eAAsB,cAAc,KAA4B;AAC9D,QAAM,SAAS,qBAAqB,GAAG;AAEvC,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAM,QAAQ,MAAM,OAAO,SAAS,OAAO,MAAM;AAAA,MAC/C,UAAU;AAAA,MACV,OAAO;AAAA,IACT,CAAC;AACD,UAAM,KAAK,SAAS,MAAM;AAC1B,UAAM,KAAK,SAAS,MAAM;AACxB,YAAM,MAAM;AACZ,cAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;AASA,eAAe,sBACb,SACA,UACA,SACe;AACf,MAAI,QAAQ,QAAQ,WAAW,QAAQ,QAAQ;AAC7C,aAAS,UAAU,GAAG,EAAE,IAAI;AAC5B;AAAA,EACF;AAEA,WAAS,UAAU,+BAA+B,QAAQ,MAAM;AAChE,WAAS,UAAU,gCAAgC,eAAe;AAClE,WAAS,UAAU,gCAAgC,cAAc;AACjE,WAAS,UAAU,QAAQ,QAAQ;AACnC,MAAI,QAAQ,QAAQ,wCAAwC,MAAM,QAAQ;AACxE,aAAS,UAAU,wCAAwC,MAAM;AAAA,EACnE;AAEA,MAAI,QAAQ,WAAW,WAAW;AAChC,aAAS,UAAU,GAAG,EAAE,IAAI;AAC5B;AAAA,EACF;AACA,MAAI,QAAQ,WAAW,UAAU,QAAQ,QAAQ,aAAa;AAC5D,aAAS,UAAU,GAAG,EAAE,IAAI;AAC5B;AAAA,EACF;AACA,MAAI,QAAQ,UAAU,GAAG;AACvB,aAAS,UAAU,GAAG,EAAE,IAAI;AAC5B;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,gBAAgB,OAAO;AAC1C,QAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,MAAI,CAAC,kBAAkB,MAAM,KAAK,CAAC,YAAY,OAAO,OAAO,QAAQ,KAAK,GAAG;AAC3E,aAAS,UAAU,GAAG,EAAE,IAAI;AAC5B;AAAA,EACF;AAEA,WAAS,UAAU,GAAG,EAAE,IAAI;AAC5B,UAAQ,SAAS,OAAO,KAAK;AAC/B;AAEA,eAAe,gBAAgB,SAA2C;AACxE,MAAI,OAAO;AACX,mBAAiB,SAAS,SAAS;AACjC,YAAQ,OAAO,SAAS,KAAK,IAAI,MAAM,SAAS,MAAM,IAAI,OAAO,KAAK;AACtE,QAAI,OAAO,WAAW,IAAI,IAAI,IAAI,MAAO;AACvC,YAAM,IAAI,MAAM,gCAAgC;AAAA,IAClD;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBACP,OAC2C;AAC3C,SACE,OAAO,UAAU,YACjB,UAAU,QACV,WAAW,SACX,OAAO,MAAM,UAAU,YACvB,WAAW,SACX,OAAO,MAAM,UAAU,YACvB,MAAM,MAAM,SAAS,KACrB,MAAM,MAAM,UAAU;AAE1B;AAEA,SAAS,YAAY,UAAkB,UAA2B;AAChE,QAAM,gBAAgB,OAAO,KAAK,QAAQ;AAC1C,QAAM,gBAAgB,OAAO,KAAK,QAAQ;AAC1C,SACE,cAAc,WAAW,cAAc,UACvC,gBAAgB,eAAe,aAAa;AAEhD;AAEA,eAAe,YACb,QACe;AACf,MAAI,CAAC,OAAO,UAAW;AACvB,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAO,MAAM,CAAC,UAAW,QAAQ,OAAO,KAAK,IAAI,QAAQ,CAAE;AAAA,EAC7D,CAAC;AACH;","names":[]}