nanocodex-vite 0.6.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.
- package/README.md +48 -0
- package/chatgpt-egress.mjs +298 -0
- package/chatgpt-subscription.mjs +451 -0
- package/cloudflare-plugin.mjs +80 -0
- package/cloudflare.d.mts +21 -0
- package/cloudflare.mjs +32 -0
- package/codex-auth-file.mjs +158 -0
- package/index.d.mts +36 -0
- package/index.mjs +6 -0
- package/oauth-relay-server.mjs +125 -0
- package/oauth-relay.d.mts +104 -0
- package/oauth-relay.mjs +313 -0
- package/package.json +65 -0
- package/plugin.mjs +525 -0
- package/scripts/build-js-package.sh +147 -0
- package/scripts/wasm-memory-views.mjs +39 -0
- package/tools.d.mts +8 -0
- package/tools.mjs +38 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
const MAX_AUTH_FILE_BYTES = 64 * 1024;
|
|
7
|
+
const DEFAULT_MINIMUM_TTL_MS = 5 * 60_000;
|
|
8
|
+
|
|
9
|
+
export function defaultCodexAuthFile(environment = process.env) {
|
|
10
|
+
const explicit = nonEmpty(environment.NANOCODEX_AUTH_FILE)
|
|
11
|
+
?? nonEmpty(environment.NANOCODEX_CODEX_AUTH_FILE);
|
|
12
|
+
if (explicit !== undefined) return resolve(explicit);
|
|
13
|
+
const codexHome = nonEmpty(environment.CODEX_HOME);
|
|
14
|
+
return resolve(codexHome === undefined ? join(homedir(), ".codex", "auth.json") : join(codexHome, "auth.json"));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function readCodexSubscription(path, options = {}) {
|
|
18
|
+
const authPath = resolve(path);
|
|
19
|
+
const minimumTtlMs = options.minimumTtlMs ?? DEFAULT_MINIMUM_TTL_MS;
|
|
20
|
+
if (!Number.isSafeInteger(minimumTtlMs) || minimumTtlMs < 0) {
|
|
21
|
+
throw new TypeError("minimumTtlMs must be a non-negative integer");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let file;
|
|
25
|
+
try {
|
|
26
|
+
file = await open(authPath, "r");
|
|
27
|
+
} catch (error) {
|
|
28
|
+
throw authError("missing", `cannot open Codex auth file: ${errorMessage(error)}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let encoded;
|
|
32
|
+
try {
|
|
33
|
+
const metadata = await file.stat();
|
|
34
|
+
if (!metadata.isFile()) throw new Error(`Codex auth path is not a file: ${authPath}`);
|
|
35
|
+
if (metadata.size > MAX_AUTH_FILE_BYTES) {
|
|
36
|
+
throw new Error(`Codex auth file exceeds ${MAX_AUTH_FILE_BYTES} bytes`);
|
|
37
|
+
}
|
|
38
|
+
if (process.platform !== "win32" && (metadata.mode & 0o077) !== 0) {
|
|
39
|
+
throw authError("insecure", "Codex auth file must not be accessible by group or other users");
|
|
40
|
+
}
|
|
41
|
+
encoded = await file.readFile();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error?.code === "NANOCODEX_AUTH_INSECURE") throw error;
|
|
44
|
+
throw authError("invalid", `cannot read Codex auth file: ${errorMessage(error)}`);
|
|
45
|
+
} finally {
|
|
46
|
+
await file.close();
|
|
47
|
+
}
|
|
48
|
+
if (encoded.byteLength > MAX_AUTH_FILE_BYTES) {
|
|
49
|
+
throw authError("invalid", `Codex auth file exceeds ${MAX_AUTH_FILE_BYTES} bytes`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let parsed;
|
|
53
|
+
try {
|
|
54
|
+
parsed = JSON.parse(encoded.toString("utf8"));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw authError("invalid", `cannot parse Codex auth file: ${errorMessage(error)}`);
|
|
57
|
+
}
|
|
58
|
+
if (!isRecord(parsed) || parsed.auth_mode !== "chatgpt" || !isRecord(parsed.tokens)) {
|
|
59
|
+
throw authError("missing", "Codex auth file does not contain a ChatGPT subscription login");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const accessToken = requiredString(parsed.tokens.access_token, "access token");
|
|
63
|
+
const idClaims = jwtPayload(optionalString(parsed.tokens.id_token));
|
|
64
|
+
const accessClaims = jwtPayload(accessToken);
|
|
65
|
+
const idAuth = nestedRecord(idClaims, "https://api.openai.com/auth");
|
|
66
|
+
const accessAuth = nestedRecord(accessClaims, "https://api.openai.com/auth");
|
|
67
|
+
const accountIds = [
|
|
68
|
+
optionalString(parsed.tokens.account_id),
|
|
69
|
+
optionalString(idAuth?.chatgpt_account_id),
|
|
70
|
+
optionalString(accessAuth?.chatgpt_account_id),
|
|
71
|
+
].filter((value) => value !== undefined);
|
|
72
|
+
const accountId = accountIds[0];
|
|
73
|
+
if (accountId === undefined) {
|
|
74
|
+
throw authError("invalid", "Codex auth file is missing the ChatGPT account ID");
|
|
75
|
+
}
|
|
76
|
+
if (accountIds.some((candidate) => candidate !== accountId)) {
|
|
77
|
+
throw authError("invalid", "Codex auth file contains conflicting ChatGPT account IDs");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const expiresAt = typeof accessClaims?.exp === "number" && Number.isFinite(accessClaims.exp)
|
|
81
|
+
? accessClaims.exp * 1_000
|
|
82
|
+
: undefined;
|
|
83
|
+
if (expiresAt === undefined || expiresAt <= Date.now() + minimumTtlMs) {
|
|
84
|
+
throw authError("expired", "Codex access token expires too soon; run `codex login` and retry");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const fedrampClaims = [
|
|
88
|
+
optionalBoolean(idAuth?.chatgpt_account_is_fedramp),
|
|
89
|
+
optionalBoolean(accessAuth?.chatgpt_account_is_fedramp),
|
|
90
|
+
].filter((value) => value !== undefined);
|
|
91
|
+
if (fedrampClaims.some((candidate) => candidate !== fedrampClaims[0])) {
|
|
92
|
+
throw authError("invalid", "Codex auth file contains conflicting FedRAMP claims");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return Object.freeze({
|
|
96
|
+
accessToken,
|
|
97
|
+
accountId,
|
|
98
|
+
fedramp: fedrampClaims[0] ?? false,
|
|
99
|
+
expiresAt,
|
|
100
|
+
revision: createHash("sha256")
|
|
101
|
+
.update(accessToken)
|
|
102
|
+
.update("\0")
|
|
103
|
+
.update(accountId)
|
|
104
|
+
.digest("base64url"),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function jwtPayload(token) {
|
|
109
|
+
if (token === undefined) return undefined;
|
|
110
|
+
const encoded = token.split(".")[1];
|
|
111
|
+
if (!encoded) return undefined;
|
|
112
|
+
try {
|
|
113
|
+
const value = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
114
|
+
return isRecord(value) ? value : undefined;
|
|
115
|
+
} catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function nestedRecord(value, key) {
|
|
121
|
+
const nested = value?.[key];
|
|
122
|
+
return isRecord(nested) ? nested : undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function requiredString(value, name) {
|
|
126
|
+
const normalized = optionalString(value);
|
|
127
|
+
if (normalized === undefined) {
|
|
128
|
+
throw authError("invalid", `Codex auth file is missing the ${name}`);
|
|
129
|
+
}
|
|
130
|
+
return normalized;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function optionalString(value) {
|
|
134
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function optionalBoolean(value) {
|
|
138
|
+
return typeof value === "boolean" ? value : undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function nonEmpty(value) {
|
|
142
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isRecord(value) {
|
|
146
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function authError(kind, message) {
|
|
150
|
+
return Object.assign(new Error(message), {
|
|
151
|
+
code: `NANOCODEX_AUTH_${kind.toUpperCase()}`,
|
|
152
|
+
kind,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function errorMessage(error) {
|
|
157
|
+
return error instanceof Error ? error.message : String(error);
|
|
158
|
+
}
|
package/index.d.mts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type NanocodexChatGptViteOptions = Readonly<{
|
|
2
|
+
/** Defaults to NANOCODEX_AUTH_FILE, NANOCODEX_CODEX_AUTH_FILE, CODEX_HOME/auth.json, then ~/.codex/auth.json. */
|
|
3
|
+
authFile?: string | URL | undefined;
|
|
4
|
+
/** Same-origin host-managed WebSocket path. Defaults to /api/responses. */
|
|
5
|
+
responsesPath?: `/${string}` | undefined;
|
|
6
|
+
}>;
|
|
7
|
+
|
|
8
|
+
export type NanocodexDevApplication = Readonly<{
|
|
9
|
+
/** Development response headers applied to every request handled by this mount. */
|
|
10
|
+
headers?: Readonly<Record<string, string>> | undefined;
|
|
11
|
+
/** Non-root URL path where the application is served during development. */
|
|
12
|
+
path: `/${string}`;
|
|
13
|
+
/** Application root. Its Vite config and index.html remain owned by the application. */
|
|
14
|
+
root: string | URL;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
export type NanocodexViteOptions = Readonly<{
|
|
18
|
+
/** Local ChatGPT subscription support is on by default; pass false to disable it. */
|
|
19
|
+
chatGpt?: NanocodexChatGptViteOptions | false | undefined;
|
|
20
|
+
/** Sibling Vite applications mounted into this development server. */
|
|
21
|
+
devApplications?: readonly NanocodexDevApplication[] | undefined;
|
|
22
|
+
/** Start the fixed local OAuth callback relay while serving. */
|
|
23
|
+
oauthRelay?: boolean | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
26
|
+
export type NanocodexVitePlugin = Readonly<{
|
|
27
|
+
name: "nanocodex";
|
|
28
|
+
enforce: "pre";
|
|
29
|
+
resolveId(source: string, importer?: string): string | null;
|
|
30
|
+
config(config: unknown, environment: Readonly<{ command: "build" | "serve" }>): unknown | Promise<unknown>;
|
|
31
|
+
configureServer(server: unknown): void | Promise<void>;
|
|
32
|
+
closeBundle(): void | Promise<void>;
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
/** Complete local integration for an ordinary Vite browser application. */
|
|
36
|
+
export function nanocodex(options?: NanocodexViteOptions): NanocodexVitePlugin;
|
package/index.mjs
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
LOCAL_OAUTH_RELAY_HMAC_KEY,
|
|
6
|
+
LOCAL_OAUTH_RELAY_HOST,
|
|
7
|
+
LOCAL_OAUTH_RELAY_ORIGIN,
|
|
8
|
+
LOCAL_OAUTH_RELAY_PORT,
|
|
9
|
+
localOAuthRelayCallbackRedirect,
|
|
10
|
+
localOAuthRelayChallengeProof,
|
|
11
|
+
} from "./oauth-relay.mjs";
|
|
12
|
+
|
|
13
|
+
export async function startLocalOAuthRelay() {
|
|
14
|
+
const server = createServer(async (request, response) => {
|
|
15
|
+
response.setHeader("cache-control", "no-store");
|
|
16
|
+
response.setHeader("referrer-policy", "no-referrer");
|
|
17
|
+
response.setHeader("x-content-type-options", "nosniff");
|
|
18
|
+
if (typeof request.url !== "string") return json(response, 404, { error: "not_found" });
|
|
19
|
+
|
|
20
|
+
let url;
|
|
21
|
+
try { url = new URL(request.url, LOCAL_OAUTH_RELAY_ORIGIN); } catch {
|
|
22
|
+
return json(response, 400, { error: "invalid_callback" });
|
|
23
|
+
}
|
|
24
|
+
if (request.method === "GET" && url.pathname === "/api/oauth-callback-relay") {
|
|
25
|
+
try {
|
|
26
|
+
return json(response, 200, {
|
|
27
|
+
service: "nanocodex-local-oauth-relay",
|
|
28
|
+
status: "ok",
|
|
29
|
+
version: 1,
|
|
30
|
+
proof: await localOAuthRelayChallengeProof(
|
|
31
|
+
url.searchParams.get("challenge"),
|
|
32
|
+
LOCAL_OAUTH_RELAY_HMAC_KEY,
|
|
33
|
+
),
|
|
34
|
+
});
|
|
35
|
+
} catch {
|
|
36
|
+
return json(response, 400, { error: "invalid_challenge" });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (request.method !== "GET") return json(response, 404, { error: "not_found" });
|
|
40
|
+
|
|
41
|
+
const destination = await localOAuthRelayCallbackRedirect(url, LOCAL_OAUTH_RELAY_HMAC_KEY);
|
|
42
|
+
if (!destination) return json(response, 400, { error: "invalid_callback" });
|
|
43
|
+
response.statusCode = 303;
|
|
44
|
+
response.setHeader("location", destination.href);
|
|
45
|
+
response.end();
|
|
46
|
+
});
|
|
47
|
+
server.on("clientError", (_error, socket) => {
|
|
48
|
+
socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
await listen(server);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error?.code === "EADDRINUSE" && await compatibleRelayIsRunning()) {
|
|
54
|
+
return followExistingRelay();
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
close: () => new Promise((resolve, reject) => {
|
|
60
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function listen(server) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
server.once("error", reject);
|
|
68
|
+
server.listen(LOCAL_OAUTH_RELAY_PORT, LOCAL_OAUTH_RELAY_HOST, () => {
|
|
69
|
+
server.off("error", reject);
|
|
70
|
+
resolve();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function followExistingRelay() {
|
|
76
|
+
let closed = false;
|
|
77
|
+
let acquiring = false;
|
|
78
|
+
let owned;
|
|
79
|
+
const timer = setInterval(async () => {
|
|
80
|
+
if (closed || acquiring || owned || await compatibleRelayIsRunning()) return;
|
|
81
|
+
acquiring = true;
|
|
82
|
+
try {
|
|
83
|
+
const relay = await startLocalOAuthRelay();
|
|
84
|
+
if (closed) await relay.close();
|
|
85
|
+
else owned = relay;
|
|
86
|
+
} catch {
|
|
87
|
+
// The fixed callback port changed hands while reacquiring; retry while Vite lives.
|
|
88
|
+
} finally {
|
|
89
|
+
acquiring = false;
|
|
90
|
+
}
|
|
91
|
+
}, 1_000);
|
|
92
|
+
timer.unref();
|
|
93
|
+
return {
|
|
94
|
+
async close() {
|
|
95
|
+
closed = true;
|
|
96
|
+
clearInterval(timer);
|
|
97
|
+
await owned?.close();
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function compatibleRelayIsRunning() {
|
|
103
|
+
const challenge = randomBytes(32).toString("base64url");
|
|
104
|
+
const expected = await localOAuthRelayChallengeProof(challenge, LOCAL_OAUTH_RELAY_HMAC_KEY);
|
|
105
|
+
try {
|
|
106
|
+
const response = await fetch(
|
|
107
|
+
`${LOCAL_OAUTH_RELAY_ORIGIN}/api/oauth-callback-relay?challenge=${challenge}`,
|
|
108
|
+
{ signal: AbortSignal.timeout(1_000) },
|
|
109
|
+
);
|
|
110
|
+
const body = await response.json();
|
|
111
|
+
return response.ok
|
|
112
|
+
&& body?.service === "nanocodex-local-oauth-relay"
|
|
113
|
+
&& body.status === "ok"
|
|
114
|
+
&& body.version === 1
|
|
115
|
+
&& body.proof === expected;
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function json(response, status, body) {
|
|
122
|
+
response.statusCode = status;
|
|
123
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
124
|
+
response.end(`${JSON.stringify(body)}\n`);
|
|
125
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export type LocalOAuthProvider = "github" | "google" | "gmail" | "gdrive" | "slack" | "x";
|
|
2
|
+
export type LocalOAuthFlow = "connect" | "managed";
|
|
3
|
+
export type LocalConnectorFlow = LocalOAuthFlow;
|
|
4
|
+
export type LocalConnectorAuthorization = Readonly<{
|
|
5
|
+
connector: LocalOAuthProvider;
|
|
6
|
+
redirectUri: string;
|
|
7
|
+
targetOrigin: string;
|
|
8
|
+
flow: LocalConnectorFlow;
|
|
9
|
+
}>;
|
|
10
|
+
export type LocalMcpAuthorization = Readonly<{
|
|
11
|
+
connectionId: string;
|
|
12
|
+
redirectUri: string;
|
|
13
|
+
targetOrigin: string;
|
|
14
|
+
flow: LocalConnectorFlow;
|
|
15
|
+
}>;
|
|
16
|
+
export type LocalOAuthRelayEnvelope = Readonly<{
|
|
17
|
+
v: 1;
|
|
18
|
+
p: LocalOAuthProvider;
|
|
19
|
+
o: string;
|
|
20
|
+
f: LocalOAuthFlow;
|
|
21
|
+
s: string;
|
|
22
|
+
i: number;
|
|
23
|
+
e: number;
|
|
24
|
+
n: string;
|
|
25
|
+
}>;
|
|
26
|
+
export type LocalMcpOAuthRelayEnvelope = Readonly<{
|
|
27
|
+
v: 1;
|
|
28
|
+
c: string;
|
|
29
|
+
o: string;
|
|
30
|
+
f: LocalOAuthFlow;
|
|
31
|
+
s: string;
|
|
32
|
+
i: number;
|
|
33
|
+
e: number;
|
|
34
|
+
n: string;
|
|
35
|
+
}>;
|
|
36
|
+
|
|
37
|
+
export const LOCAL_OAUTH_RELAY_HOST: "127.0.0.1";
|
|
38
|
+
export const LOCAL_OAUTH_RELAY_PORT: 47891;
|
|
39
|
+
export const LOCAL_OAUTH_RELAY_ORIGIN: "http://127.0.0.1:47891";
|
|
40
|
+
export const LOCAL_OAUTH_RELAY_HMAC_KEY: "nanocodex-local-oauth-relay-hmac-v1-only";
|
|
41
|
+
|
|
42
|
+
export function localOAuthRelayCallbackUrl(provider: string): string | undefined;
|
|
43
|
+
export function localMcpOAuthRelayCallbackUrl(connectionId: string): string | undefined;
|
|
44
|
+
export function isLocalNanocodexOrigin(value: string): boolean;
|
|
45
|
+
export function localConnectorAuthorization(
|
|
46
|
+
targetOrigin: string,
|
|
47
|
+
connector: string,
|
|
48
|
+
flow: LocalConnectorFlow,
|
|
49
|
+
): LocalConnectorAuthorization | undefined;
|
|
50
|
+
export function localMcpAuthorization(
|
|
51
|
+
targetOrigin: string,
|
|
52
|
+
connectionId: string,
|
|
53
|
+
flow: LocalConnectorFlow,
|
|
54
|
+
): LocalMcpAuthorization | undefined;
|
|
55
|
+
export function wrapLocalConnectorAuthorizationState(
|
|
56
|
+
authorizationUrl: URL,
|
|
57
|
+
local: LocalConnectorAuthorization,
|
|
58
|
+
relayKey: string,
|
|
59
|
+
): Promise<URL>;
|
|
60
|
+
export function wrapLocalMcpAuthorizationState(
|
|
61
|
+
authorizationUrl: URL,
|
|
62
|
+
local: LocalMcpAuthorization,
|
|
63
|
+
relayKey: string,
|
|
64
|
+
): Promise<URL>;
|
|
65
|
+
export function signLocalOAuthRelayState(
|
|
66
|
+
value: Readonly<{
|
|
67
|
+
provider: string;
|
|
68
|
+
connectionId?: undefined;
|
|
69
|
+
targetOrigin: string;
|
|
70
|
+
flow: string;
|
|
71
|
+
state: string;
|
|
72
|
+
}>,
|
|
73
|
+
secret: string,
|
|
74
|
+
options?: Readonly<{ now?: number; nonce?: string }>,
|
|
75
|
+
): Promise<string>;
|
|
76
|
+
export function signLocalOAuthRelayState(
|
|
77
|
+
value: Readonly<{
|
|
78
|
+
provider?: undefined;
|
|
79
|
+
connectionId: string;
|
|
80
|
+
targetOrigin: string;
|
|
81
|
+
flow: string;
|
|
82
|
+
state: string;
|
|
83
|
+
}>,
|
|
84
|
+
secret: string,
|
|
85
|
+
options?: Readonly<{ now?: number; nonce?: string }>,
|
|
86
|
+
): Promise<string>;
|
|
87
|
+
export function verifyLocalOAuthRelayState(
|
|
88
|
+
value: unknown,
|
|
89
|
+
expectedProvider: string,
|
|
90
|
+
secret: string,
|
|
91
|
+
options?: Readonly<{ now?: number }>,
|
|
92
|
+
): Promise<LocalOAuthRelayEnvelope | undefined>;
|
|
93
|
+
export function verifyLocalMcpOAuthRelayState(
|
|
94
|
+
value: unknown,
|
|
95
|
+
expectedConnectionId: string,
|
|
96
|
+
secret: string,
|
|
97
|
+
options?: Readonly<{ now?: number }>,
|
|
98
|
+
): Promise<LocalMcpOAuthRelayEnvelope | undefined>;
|
|
99
|
+
export function localOAuthRelayCallbackRedirect(
|
|
100
|
+
url: URL,
|
|
101
|
+
secret: string,
|
|
102
|
+
options?: Readonly<{ now?: number }>,
|
|
103
|
+
): Promise<URL | undefined>;
|
|
104
|
+
export function localOAuthRelayChallengeProof(challenge: string, secret: string): Promise<string>;
|