@canaryai/cli 0.2.0 → 0.2.1
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/dist/{chunk-2T64Z2NI.js → chunk-7R4YFGP6.js} +53 -2
- package/dist/chunk-7R4YFGP6.js.map +1 -0
- package/dist/chunk-DXJNFJ3A.js +64 -0
- package/dist/chunk-DXJNFJ3A.js.map +1 -0
- package/dist/{chunk-V7U52ISX.js → chunk-HOYYXZPV.js} +136 -131
- package/dist/chunk-HOYYXZPV.js.map +1 -0
- package/dist/{chunk-ROTCL5WO.js → chunk-TO66FC4R.js} +688 -479
- package/dist/chunk-TO66FC4R.js.map +1 -0
- package/dist/{feature-flag-ESPSOSKG.js → feature-flag-ZDLDYRSF.js} +15 -92
- package/dist/feature-flag-ZDLDYRSF.js.map +1 -0
- package/dist/index.js +17 -65
- package/dist/index.js.map +1 -1
- package/dist/{knobs-HKONHY55.js → knobs-3MKMOXIV.js} +19 -104
- package/dist/knobs-3MKMOXIV.js.map +1 -0
- package/dist/{local-browser-MKKPBTYI.js → local-browser-GG5GUXDS.js} +2 -2
- package/dist/{mcp-4F4HI7L2.js → mcp-AD67OLQM.js} +3 -3
- package/dist/{psql-6IFVXM3A.js → psql-IVAPNYZV.js} +2 -2
- package/dist/{redis-HZC32IEO.js → redis-LWY7L6AS.js} +2 -2
- package/dist/{release-WOD3DAX4.js → release-KQFCTAXA.js} +5 -35
- package/dist/release-KQFCTAXA.js.map +1 -0
- package/dist/runner/preload.js +7 -323
- package/dist/runner/preload.js.map +1 -1
- package/dist/test.js +5 -340
- package/dist/test.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2T64Z2NI.js.map +0 -1
- package/dist/chunk-ROTCL5WO.js.map +0 -1
- package/dist/chunk-V7U52ISX.js.map +0 -1
- package/dist/feature-flag-ESPSOSKG.js.map +0 -1
- package/dist/knobs-HKONHY55.js.map +0 -1
- package/dist/release-WOD3DAX4.js.map +0 -1
- /package/dist/{local-browser-MKKPBTYI.js.map → local-browser-GG5GUXDS.js.map} +0 -0
- /package/dist/{mcp-4F4HI7L2.js.map → mcp-AD67OLQM.js.map} +0 -0
- /package/dist/{psql-6IFVXM3A.js.map → psql-IVAPNYZV.js.map} +0 -0
- /package/dist/{redis-HZC32IEO.js.map → redis-LWY7L6AS.js.map} +0 -0
|
@@ -2,8 +2,24 @@
|
|
|
2
2
|
import fs from "fs/promises";
|
|
3
3
|
import os from "os";
|
|
4
4
|
import path from "path";
|
|
5
|
+
import process from "process";
|
|
5
6
|
var AUTH_DIR = path.join(os.homedir(), ".config", "canary-cli");
|
|
6
7
|
var AUTH_FILE = path.join(AUTH_DIR, "auth.json");
|
|
8
|
+
function getArgValue(argv, key) {
|
|
9
|
+
const index = argv.indexOf(key);
|
|
10
|
+
if (index === -1 || index >= argv.length - 1) return void 0;
|
|
11
|
+
return argv[index + 1];
|
|
12
|
+
}
|
|
13
|
+
function hasFlag(argv, ...flags) {
|
|
14
|
+
return flags.some((flag) => argv.includes(flag));
|
|
15
|
+
}
|
|
16
|
+
function envToProfile(env) {
|
|
17
|
+
if (env === "prod" || env === "production") return "production";
|
|
18
|
+
if (env === "dev") return "dev";
|
|
19
|
+
if (env === "local") return "local";
|
|
20
|
+
return env;
|
|
21
|
+
}
|
|
22
|
+
var VALID_ENVS = ["prod", "dev", "local"];
|
|
7
23
|
var ENV_URLS = {
|
|
8
24
|
prod: {
|
|
9
25
|
api: "https://api.trycanary.ai",
|
|
@@ -85,13 +101,48 @@ async function saveAuth(auth, profile) {
|
|
|
85
101
|
await fs.writeFile(AUTH_FILE, JSON.stringify(existing, null, 2), { encoding: "utf8", mode: 384 });
|
|
86
102
|
return AUTH_FILE;
|
|
87
103
|
}
|
|
104
|
+
async function resolveConfig(argv) {
|
|
105
|
+
const env = getArgValue(argv, "--env");
|
|
106
|
+
const profile = env ? envToProfile(env) : void 0;
|
|
107
|
+
if (env && !ENV_URLS[env]) {
|
|
108
|
+
console.error(`Unknown environment: ${env}`);
|
|
109
|
+
console.error(`Valid environments: ${VALID_ENVS.join(", ")}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
const storedApiUrl = await readStoredApiUrl(profile);
|
|
113
|
+
const apiUrl = getArgValue(argv, "--api-url") ?? (env ? ENV_URLS[env].api : void 0) ?? process.env.CANARY_API_URL ?? storedApiUrl ?? "https://api.trycanary.ai";
|
|
114
|
+
const explicitToken = getArgValue(argv, "--token");
|
|
115
|
+
let token;
|
|
116
|
+
if (explicitToken) {
|
|
117
|
+
token = explicitToken;
|
|
118
|
+
} else if (profile) {
|
|
119
|
+
token = await readStoredToken(profile) ?? process.env.CANARY_API_TOKEN ?? null;
|
|
120
|
+
if (!token) {
|
|
121
|
+
console.error(`Error: No token found for "${env}" profile.`);
|
|
122
|
+
console.error(`Run: canary login --env ${env}`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
token = process.env.CANARY_API_TOKEN ?? await readStoredToken() ?? null;
|
|
127
|
+
if (!token) {
|
|
128
|
+
console.error("Error: No API token found.");
|
|
129
|
+
console.error("Run: canary login");
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { apiUrl, token };
|
|
134
|
+
}
|
|
88
135
|
|
|
89
136
|
export {
|
|
137
|
+
getArgValue,
|
|
138
|
+
hasFlag,
|
|
139
|
+
envToProfile,
|
|
90
140
|
ENV_URLS,
|
|
91
141
|
readStoredAuth,
|
|
92
142
|
readStoredToken,
|
|
93
143
|
readStoredApiUrl,
|
|
94
144
|
readAllStoredTokens,
|
|
95
|
-
saveAuth
|
|
145
|
+
saveAuth,
|
|
146
|
+
resolveConfig
|
|
96
147
|
};
|
|
97
|
-
//# sourceMappingURL=chunk-
|
|
148
|
+
//# sourceMappingURL=chunk-7R4YFGP6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\ntype StoredAuth = {\n token?: string;\n apiUrl?: string;\n orgId?: string;\n orgName?: string;\n};\n\n/** Multi-profile auth file format */\ntype MultiProfileAuth = {\n profiles: Record<string, StoredAuth>;\n default: string;\n};\n\n/** Legacy flat format (single profile) */\ntype LegacyAuth = StoredAuth;\n\nconst AUTH_DIR = path.join(os.homedir(), \".config\", \"canary-cli\");\nconst AUTH_FILE = path.join(AUTH_DIR, \"auth.json\");\n\n/* ── Shared argv helpers ─────────────────────────────────────────────── */\n\nexport function getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1 || index >= argv.length - 1) return undefined;\n return argv[index + 1];\n}\n\nexport function hasFlag(argv: string[], ...flags: string[]): boolean {\n return flags.some((flag) => argv.includes(flag));\n}\n\n/* ── Environment / profile mapping ───────────────────────────────────── */\n\n/** Map --env values to profile names for multi-profile auth storage */\nexport function envToProfile(env: string): string {\n if (env === \"prod\" || env === \"production\") return \"production\";\n if (env === \"dev\") return \"dev\";\n if (env === \"local\") return \"local\";\n return env;\n}\n\nconst VALID_ENVS = [\"prod\", \"dev\", \"local\"];\n\n/** Environment URL mappings shared across CLI commands */\nexport const ENV_URLS: Record<string, { api: string; app: string }> = {\n prod: {\n api: \"https://api.trycanary.ai\",\n app: \"https://app.trycanary.ai\",\n },\n production: {\n api: \"https://api.trycanary.ai\",\n app: \"https://app.trycanary.ai\",\n },\n dev: {\n api: \"https://api.dev.trycanary.ai\",\n app: \"https://app.dev.trycanary.ai\",\n },\n local: {\n api: \"http://localhost:3000\",\n app: \"http://localhost:5173\",\n },\n};\n\nfunction isMultiProfile(data: unknown): data is MultiProfileAuth {\n return typeof data === \"object\" && data !== null && \"profiles\" in data;\n}\n\n/** Read the raw auth file and normalize to multi-profile format */\nasync function readAuthFile(): Promise<MultiProfileAuth | null> {\n try {\n const content = await fs.readFile(AUTH_FILE, \"utf8\");\n const data = JSON.parse(content) as unknown;\n\n if (isMultiProfile(data)) {\n return data;\n }\n\n // Legacy flat format — treat as the \"default\" profile\n const legacy = data as LegacyAuth;\n return {\n profiles: { default: legacy },\n default: \"default\",\n };\n } catch {\n return null;\n }\n}\n\nexport async function readStoredAuth(profile?: string): Promise<StoredAuth | null> {\n const auth = await readAuthFile();\n if (!auth) return null;\n\n const key = profile ?? auth.default;\n return auth.profiles[key] ?? null;\n}\n\nexport async function readStoredToken(profile?: string): Promise<string | null> {\n const auth = await readStoredAuth(profile);\n return auth?.token ?? null;\n}\n\nexport async function readStoredApiUrl(profile?: string): Promise<string | null> {\n const auth = await readStoredAuth(profile);\n return auth?.apiUrl ?? null;\n}\n\n/** Return all stored tokens across every profile */\nexport async function readAllStoredTokens(): Promise<string[]> {\n const auth = await readAuthFile();\n if (!auth) return [];\n return Object.values(auth.profiles)\n .map((p) => p.token)\n .filter((t): t is string => !!t);\n}\n\nexport async function saveAuth(auth: StoredAuth, profile?: string): Promise<string> {\n await fs.mkdir(AUTH_DIR, { recursive: true, mode: 0o700 });\n\n // Read existing file (may be legacy or multi-profile)\n let existing: MultiProfileAuth;\n try {\n const content = await fs.readFile(AUTH_FILE, \"utf8\");\n const data = JSON.parse(content) as unknown;\n\n if (isMultiProfile(data)) {\n existing = data;\n } else {\n // Migrate legacy flat format into profiles.default\n existing = {\n profiles: { default: data as LegacyAuth },\n default: \"default\",\n };\n }\n } catch {\n existing = { profiles: {}, default: \"default\" };\n }\n\n const key = profile ?? \"default\";\n existing.profiles[key] = auth;\n\n // Set default to this profile if no default exists yet\n if (!existing.profiles[existing.default]) {\n existing.default = key;\n }\n\n await fs.writeFile(AUTH_FILE, JSON.stringify(existing, null, 2), { encoding: \"utf8\", mode: 0o600 });\n return AUTH_FILE;\n}\n\n/* ── Shared config resolution ────────────────────────────────────────── */\n\nexport type ResolvedConfig = { apiUrl: string; token: string };\n\n/**\n * Resolve API URL and auth token from argv flags, env vars, and stored profiles.\n * Used by knobs, feature-flag, release, and debug-session commands.\n */\nexport async function resolveConfig(argv: string[]): Promise<ResolvedConfig> {\n const env = getArgValue(argv, \"--env\");\n const profile = env ? envToProfile(env) : undefined;\n\n if (env && !ENV_URLS[env]) {\n console.error(`Unknown environment: ${env}`);\n console.error(`Valid environments: ${VALID_ENVS.join(\", \")}`);\n process.exit(1);\n }\n\n const storedApiUrl = await readStoredApiUrl(profile);\n const apiUrl =\n getArgValue(argv, \"--api-url\") ??\n (env ? ENV_URLS[env].api : undefined) ??\n process.env.CANARY_API_URL ??\n storedApiUrl ??\n \"https://api.trycanary.ai\";\n\n // Explicit --token flag always wins\n const explicitToken = getArgValue(argv, \"--token\");\n\n let token: string | null;\n if (explicitToken) {\n token = explicitToken;\n } else if (profile) {\n // --env was specified: profile token takes priority, env var is fallback\n token = (await readStoredToken(profile)) ?? process.env.CANARY_API_TOKEN ?? null;\n if (!token) {\n console.error(`Error: No token found for \"${env}\" profile.`);\n console.error(`Run: canary login --env ${env}`);\n process.exit(1);\n }\n } else {\n // No --env: env var first, then default profile\n token = process.env.CANARY_API_TOKEN ?? (await readStoredToken()) ?? null;\n if (!token) {\n console.error(\"Error: No API token found.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n }\n\n return { apiUrl, token };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getArgValue
|
|
3
|
+
} from "./chunk-7R4YFGP6.js";
|
|
4
|
+
|
|
5
|
+
// src/cli-helpers.ts
|
|
6
|
+
import process from "process";
|
|
7
|
+
function toLifecycleLabel(stage) {
|
|
8
|
+
switch (stage) {
|
|
9
|
+
case "deprecated":
|
|
10
|
+
return "deprecated";
|
|
11
|
+
case "ready_for_cleanup":
|
|
12
|
+
return "ready_for_cleanup";
|
|
13
|
+
default:
|
|
14
|
+
return "active";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function parseLifecycleStage(argv) {
|
|
18
|
+
const stage = getArgValue(argv, "--stage");
|
|
19
|
+
if (!stage || !["active", "deprecated", "ready_for_cleanup"].includes(stage)) {
|
|
20
|
+
console.error("Error: --stage is required and must be one of: active, deprecated, ready_for_cleanup");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
return stage;
|
|
24
|
+
}
|
|
25
|
+
async function apiRequest(apiUrl, token, method, path, body) {
|
|
26
|
+
const res = await fetch(`${apiUrl}${path}`, {
|
|
27
|
+
method,
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `Bearer ${token}`,
|
|
30
|
+
"Content-Type": "application/json"
|
|
31
|
+
},
|
|
32
|
+
...body ? { body: JSON.stringify(body) } : {}
|
|
33
|
+
});
|
|
34
|
+
if (res.status === 401) {
|
|
35
|
+
console.error("Error: Unauthorized. Your session may have expired.");
|
|
36
|
+
console.error("Run: canary login");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
return await res.json();
|
|
40
|
+
}
|
|
41
|
+
async function fetchList(apiUrl, token, path, listKey) {
|
|
42
|
+
const res = await fetch(`${apiUrl}${path}`, {
|
|
43
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
44
|
+
});
|
|
45
|
+
if (res.status === 401) {
|
|
46
|
+
console.error("Error: Unauthorized. Your session may have expired.");
|
|
47
|
+
console.error("Run: canary login");
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const json = await res.json();
|
|
51
|
+
if (!json.ok) {
|
|
52
|
+
console.error(`Error: ${json.error}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
return json[listKey] ?? [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
toLifecycleLabel,
|
|
60
|
+
parseLifecycleStage,
|
|
61
|
+
apiRequest,
|
|
62
|
+
fetchList
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=chunk-DXJNFJ3A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli-helpers.ts"],"sourcesContent":["/**\n * Shared CLI helpers for superadmin management commands (knobs, feature-flags).\n */\n\nimport process from \"node:process\";\nimport { getArgValue } from \"./auth.js\";\n\nexport type LifecycleStage = \"active\" | \"deprecated\" | \"ready_for_cleanup\";\n\nexport function toLifecycleLabel(stage: LifecycleStage): string {\n switch (stage) {\n case \"deprecated\":\n return \"deprecated\";\n case \"ready_for_cleanup\":\n return \"ready_for_cleanup\";\n default:\n return \"active\";\n }\n}\n\nexport function parseLifecycleStage(argv: string[]): LifecycleStage {\n const stage = getArgValue(argv, \"--stage\");\n if (!stage || ![\"active\", \"deprecated\", \"ready_for_cleanup\"].includes(stage)) {\n console.error(\"Error: --stage is required and must be one of: active, deprecated, ready_for_cleanup\");\n process.exit(1);\n }\n return stage as LifecycleStage;\n}\n\nexport async function apiRequest<T extends { ok: boolean; error?: string }>(\n apiUrl: string,\n token: string,\n method: string,\n path: string,\n body?: Record<string, unknown>\n): Promise<T> {\n const res = await fetch(`${apiUrl}${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n ...(body ? { body: JSON.stringify(body) } : {}),\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n return (await res.json()) as T;\n}\n\nexport async function fetchList<T>(\n apiUrl: string,\n token: string,\n path: string,\n listKey: string\n): Promise<T[]> {\n const res = await fetch(`${apiUrl}${path}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n const json = (await res.json()) as Record<string, unknown>;\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n return (json[listKey] as T[]) ?? [];\n}\n"],"mappings":";;;;;AAIA,OAAO,aAAa;AAKb,SAAS,iBAAiB,OAA+B;AAC9D,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEO,SAAS,oBAAoB,MAAgC;AAClE,QAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,MAAI,CAAC,SAAS,CAAC,CAAC,UAAU,cAAc,mBAAmB,EAAE,SAAS,KAAK,GAAG;AAC5E,YAAQ,MAAM,sFAAsF;AACpG,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAEA,eAAsB,WACpB,QACA,OACA,QACA,MACA,MACY;AACZ,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,IAAI,IAAI;AAAA,IAC1C;AAAA,IACA,SAAS;AAAA,MACP,eAAe,UAAU,KAAK;AAAA,MAC9B,gBAAgB;AAAA,IAClB;AAAA,IACA,GAAI,OAAO,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,CAAC;AAAA,EAC/C,CAAC;AAED,MAAI,IAAI,WAAW,KAAK;AACtB,YAAQ,MAAM,qDAAqD;AACnE,YAAQ,MAAM,mBAAmB;AACjC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAQ,MAAM,IAAI,KAAK;AACzB;AAEA,eAAsB,UACpB,QACA,OACA,MACA,SACc;AACd,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,IAAI,IAAI;AAAA,IAC1C,SAAS,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,EAC9C,CAAC;AAED,MAAI,IAAI,WAAW,KAAK;AACtB,YAAQ,MAAM,qDAAqD;AACnE,YAAQ,MAAM,mBAAmB;AACjC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,MAAI,CAAC,KAAK,IAAI;AACZ,YAAQ,MAAM,UAAU,KAAK,KAAK,EAAE;AACpC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAQ,KAAK,OAAO,KAAa,CAAC;AACpC;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readStoredToken
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-7R4YFGP6.js";
|
|
4
4
|
|
|
5
5
|
// src/local-run.ts
|
|
6
6
|
import process from "process";
|
|
@@ -99,11 +99,7 @@ async function runTunnel(argv) {
|
|
|
99
99
|
let reconnectAttempts = 0;
|
|
100
100
|
const connect = async () => {
|
|
101
101
|
try {
|
|
102
|
-
const data = await createTunnel({
|
|
103
|
-
apiUrl,
|
|
104
|
-
token,
|
|
105
|
-
port
|
|
106
|
-
});
|
|
102
|
+
const data = await createTunnel({ apiUrl, token, port });
|
|
107
103
|
console.log(`Tunnel connected: ${data.publicUrl ?? data.tunnelId}`);
|
|
108
104
|
if (data.publicUrl) {
|
|
109
105
|
console.log(`Public URL: ${data.publicUrl}`);
|
|
@@ -174,13 +170,128 @@ async function createTunnel(input) {
|
|
|
174
170
|
}
|
|
175
171
|
return { tunnelId: data.tunnelId, publicUrl: data.publicUrl, token: data.token };
|
|
176
172
|
}
|
|
173
|
+
function handleHttpRequest(ctx, request) {
|
|
174
|
+
const targetUrl = `http://localhost:${ctx.port}${request.path.startsWith("/") ? request.path : `/${request.path}`}`;
|
|
175
|
+
const body = request.bodyBase64 ? Buffer.from(request.bodyBase64, "base64") : void 0;
|
|
176
|
+
const headers = { ...request.headers };
|
|
177
|
+
delete headers.host;
|
|
178
|
+
delete headers["content-length"];
|
|
179
|
+
fetch(targetUrl, { method: request.method, headers, body: body ?? void 0 }).then(async (res) => {
|
|
180
|
+
const resBody = await res.arrayBuffer();
|
|
181
|
+
const resHeaders = Object.fromEntries(res.headers.entries());
|
|
182
|
+
delete resHeaders["set-cookie"];
|
|
183
|
+
const getSetCookie = res.headers.getSetCookie;
|
|
184
|
+
const setCookieValues = typeof getSetCookie === "function" ? getSetCookie.call(res.headers) : [];
|
|
185
|
+
const fallbackSetCookie = res.headers.get("set-cookie");
|
|
186
|
+
if (setCookieValues.length === 0 && fallbackSetCookie) {
|
|
187
|
+
setCookieValues.push(fallbackSetCookie);
|
|
188
|
+
}
|
|
189
|
+
if (setCookieValues.length > 0) {
|
|
190
|
+
resHeaders["set-cookie"] = setCookieValues;
|
|
191
|
+
}
|
|
192
|
+
const responsePayload = {
|
|
193
|
+
type: "http_response",
|
|
194
|
+
id: request.id,
|
|
195
|
+
status: res.status,
|
|
196
|
+
headers: resHeaders,
|
|
197
|
+
bodyBase64: resBody.byteLength ? Buffer.from(resBody).toString("base64") : null
|
|
198
|
+
};
|
|
199
|
+
ctx.ws.send(JSON.stringify(responsePayload));
|
|
200
|
+
}).catch((error) => {
|
|
201
|
+
const responsePayload = {
|
|
202
|
+
type: "http_response",
|
|
203
|
+
id: request.id,
|
|
204
|
+
status: 502,
|
|
205
|
+
headers: { "content-type": "text/plain" },
|
|
206
|
+
bodyBase64: Buffer.from(`Tunnel error: ${String(error)}`).toString("base64")
|
|
207
|
+
};
|
|
208
|
+
ctx.ws.send(JSON.stringify(responsePayload));
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function handleWsOpen(ctx, request) {
|
|
212
|
+
const targetUrl = `ws://localhost:${ctx.port}${request.path.startsWith("/") ? request.path : `/${request.path}`}`;
|
|
213
|
+
const protocolsHeader = request.headers["sec-websocket-protocol"] ?? request.headers["Sec-WebSocket-Protocol"];
|
|
214
|
+
const protocols = protocolsHeader ? protocolsHeader.split(",").map((v) => v.trim()).filter(Boolean) : void 0;
|
|
215
|
+
const localWs = new WebSocket(targetUrl, protocols);
|
|
216
|
+
ctx.wsConnections.set(request.id, localWs);
|
|
217
|
+
localWs.onopen = () => {
|
|
218
|
+
ctx.ws.send(JSON.stringify({ type: "ws_ready", id: request.id }));
|
|
219
|
+
const queued = ctx.wsQueues.get(request.id);
|
|
220
|
+
if (queued) {
|
|
221
|
+
for (const message of queued) {
|
|
222
|
+
ctx.ws.send(JSON.stringify(message));
|
|
223
|
+
}
|
|
224
|
+
ctx.wsQueues.delete(request.id);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
localWs.onmessage = (event) => {
|
|
228
|
+
const data = typeof event.data === "string" ? Buffer.from(event.data) : Buffer.from(event.data);
|
|
229
|
+
const response = {
|
|
230
|
+
type: "ws_message",
|
|
231
|
+
id: request.id,
|
|
232
|
+
dataBase64: data.toString("base64"),
|
|
233
|
+
isBinary: typeof event.data !== "string"
|
|
234
|
+
};
|
|
235
|
+
ctx.ws.send(JSON.stringify(response));
|
|
236
|
+
};
|
|
237
|
+
localWs.onclose = (event) => {
|
|
238
|
+
ctx.wsConnections.delete(request.id);
|
|
239
|
+
const response = {
|
|
240
|
+
type: "ws_close",
|
|
241
|
+
id: request.id,
|
|
242
|
+
code: event.code,
|
|
243
|
+
reason: event.reason
|
|
244
|
+
};
|
|
245
|
+
ctx.ws.send(JSON.stringify(response));
|
|
246
|
+
};
|
|
247
|
+
localWs.onerror = () => {
|
|
248
|
+
ctx.wsConnections.delete(request.id);
|
|
249
|
+
const response = {
|
|
250
|
+
type: "ws_close",
|
|
251
|
+
id: request.id,
|
|
252
|
+
code: 1011,
|
|
253
|
+
reason: "local_ws_error"
|
|
254
|
+
};
|
|
255
|
+
ctx.ws.send(JSON.stringify(response));
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function handleWsMessage(ctx, message) {
|
|
259
|
+
const localWs = ctx.wsConnections.get(message.id);
|
|
260
|
+
const data = Buffer.from(message.dataBase64, "base64");
|
|
261
|
+
if (!localWs || localWs.readyState !== WebSocket.OPEN) {
|
|
262
|
+
const queued = ctx.wsQueues.get(message.id) ?? [];
|
|
263
|
+
queued.push(message);
|
|
264
|
+
ctx.wsQueues.set(message.id, queued);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (message.isBinary) {
|
|
268
|
+
localWs.send(data);
|
|
269
|
+
} else {
|
|
270
|
+
localWs.send(data.toString());
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function handleWsClose(ctx, message) {
|
|
274
|
+
const localWs = ctx.wsConnections.get(message.id);
|
|
275
|
+
if (!localWs) {
|
|
276
|
+
const queued = ctx.wsQueues.get(message.id) ?? [];
|
|
277
|
+
queued.push(message);
|
|
278
|
+
ctx.wsQueues.set(message.id, queued);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
localWs.close(message.code ?? 1e3, message.reason ?? "");
|
|
282
|
+
ctx.wsConnections.delete(message.id);
|
|
283
|
+
}
|
|
177
284
|
function connectTunnel(input) {
|
|
178
285
|
const wsUrl = toWebSocketUrl(
|
|
179
286
|
`${input.apiUrl}/local-tests/tunnels/${input.tunnelId}/connect?token=${input.token}`
|
|
180
287
|
);
|
|
181
288
|
const ws = new WebSocket(wsUrl);
|
|
182
|
-
const
|
|
183
|
-
|
|
289
|
+
const ctx = {
|
|
290
|
+
ws,
|
|
291
|
+
port: input.port,
|
|
292
|
+
wsConnections: /* @__PURE__ */ new Map(),
|
|
293
|
+
wsQueues: /* @__PURE__ */ new Map()
|
|
294
|
+
};
|
|
184
295
|
ws.onopen = () => {
|
|
185
296
|
input.onReady?.();
|
|
186
297
|
};
|
|
@@ -194,128 +305,22 @@ function connectTunnel(input) {
|
|
|
194
305
|
try {
|
|
195
306
|
const raw = typeof event.data === "string" ? event.data : Buffer.from(event.data).toString();
|
|
196
307
|
const payload = JSON.parse(raw);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const getSetCookie = res.headers.getSetCookie;
|
|
214
|
-
const setCookieValues = typeof getSetCookie === "function" ? getSetCookie.call(res.headers) : [];
|
|
215
|
-
const fallbackSetCookie = res.headers.get("set-cookie");
|
|
216
|
-
if (setCookieValues.length === 0 && fallbackSetCookie) {
|
|
217
|
-
setCookieValues.push(fallbackSetCookie);
|
|
218
|
-
}
|
|
219
|
-
if (setCookieValues.length > 0) {
|
|
220
|
-
resHeaders["set-cookie"] = setCookieValues;
|
|
221
|
-
}
|
|
222
|
-
const responsePayload = {
|
|
223
|
-
type: "http_response",
|
|
224
|
-
id: request.id,
|
|
225
|
-
status: res.status,
|
|
226
|
-
headers: resHeaders,
|
|
227
|
-
bodyBase64: resBody.byteLength ? Buffer.from(resBody).toString("base64") : null
|
|
228
|
-
};
|
|
229
|
-
ws.send(JSON.stringify(responsePayload));
|
|
230
|
-
} catch (error) {
|
|
231
|
-
const responsePayload = {
|
|
232
|
-
type: "http_response",
|
|
233
|
-
id: request.id,
|
|
234
|
-
status: 502,
|
|
235
|
-
headers: { "content-type": "text/plain" },
|
|
236
|
-
bodyBase64: Buffer.from(`Tunnel error: ${String(error)}`).toString("base64")
|
|
237
|
-
};
|
|
238
|
-
ws.send(JSON.stringify(responsePayload));
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
if (payload.type === "ws_open") {
|
|
242
|
-
const request = payload;
|
|
243
|
-
const targetUrl = `ws://localhost:${input.port}${request.path.startsWith("/") ? request.path : `/${request.path}`}`;
|
|
244
|
-
const protocolsHeader = request.headers["sec-websocket-protocol"] ?? request.headers["Sec-WebSocket-Protocol"];
|
|
245
|
-
const protocols = protocolsHeader ? protocolsHeader.split(",").map((value) => value.trim()).filter(Boolean) : void 0;
|
|
246
|
-
const localWs = new WebSocket(targetUrl, protocols);
|
|
247
|
-
wsConnections.set(request.id, localWs);
|
|
248
|
-
localWs.onopen = () => {
|
|
249
|
-
ws.send(JSON.stringify({ type: "ws_ready", id: request.id }));
|
|
250
|
-
const queued = wsQueues.get(request.id);
|
|
251
|
-
if (queued) {
|
|
252
|
-
for (const message of queued) {
|
|
253
|
-
ws.send(JSON.stringify(message));
|
|
254
|
-
}
|
|
255
|
-
wsQueues.delete(request.id);
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
localWs.onmessage = (event2) => {
|
|
259
|
-
const data = typeof event2.data === "string" ? Buffer.from(event2.data) : Buffer.from(event2.data);
|
|
260
|
-
const response = {
|
|
261
|
-
type: "ws_message",
|
|
262
|
-
id: request.id,
|
|
263
|
-
dataBase64: data.toString("base64"),
|
|
264
|
-
isBinary: typeof event2.data !== "string"
|
|
265
|
-
};
|
|
266
|
-
ws.send(JSON.stringify(response));
|
|
267
|
-
};
|
|
268
|
-
localWs.onclose = (event2) => {
|
|
269
|
-
wsConnections.delete(request.id);
|
|
270
|
-
const response = {
|
|
271
|
-
type: "ws_close",
|
|
272
|
-
id: request.id,
|
|
273
|
-
code: event2.code,
|
|
274
|
-
reason: event2.reason
|
|
275
|
-
};
|
|
276
|
-
ws.send(JSON.stringify(response));
|
|
277
|
-
};
|
|
278
|
-
localWs.onerror = () => {
|
|
279
|
-
wsConnections.delete(request.id);
|
|
280
|
-
const response = {
|
|
281
|
-
type: "ws_close",
|
|
282
|
-
id: request.id,
|
|
283
|
-
code: 1011,
|
|
284
|
-
reason: "local_ws_error"
|
|
285
|
-
};
|
|
286
|
-
ws.send(JSON.stringify(response));
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
if (payload.type === "ws_message") {
|
|
290
|
-
const message = payload;
|
|
291
|
-
const localWs = wsConnections.get(message.id);
|
|
292
|
-
const data = Buffer.from(message.dataBase64, "base64");
|
|
293
|
-
if (!localWs || localWs.readyState !== WebSocket.OPEN) {
|
|
294
|
-
const queued = wsQueues.get(message.id) ?? [];
|
|
295
|
-
queued.push(message);
|
|
296
|
-
wsQueues.set(message.id, queued);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
if (message.isBinary) {
|
|
300
|
-
localWs.send(data);
|
|
301
|
-
} else {
|
|
302
|
-
localWs.send(data.toString());
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
if (payload.type === "ws_close") {
|
|
306
|
-
const message = payload;
|
|
307
|
-
const localWs = wsConnections.get(message.id);
|
|
308
|
-
if (!localWs) {
|
|
309
|
-
const queued = wsQueues.get(message.id) ?? [];
|
|
310
|
-
queued.push(message);
|
|
311
|
-
wsQueues.set(message.id, queued);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
localWs.close(message.code ?? 1e3, message.reason ?? "");
|
|
315
|
-
wsConnections.delete(message.id);
|
|
316
|
-
}
|
|
317
|
-
if (payload.type === "health_ping") {
|
|
318
|
-
ws.send(JSON.stringify({ type: "health_pong" }));
|
|
308
|
+
switch (payload.type) {
|
|
309
|
+
case "http_request":
|
|
310
|
+
handleHttpRequest(ctx, payload);
|
|
311
|
+
break;
|
|
312
|
+
case "ws_open":
|
|
313
|
+
handleWsOpen(ctx, payload);
|
|
314
|
+
break;
|
|
315
|
+
case "ws_message":
|
|
316
|
+
handleWsMessage(ctx, payload);
|
|
317
|
+
break;
|
|
318
|
+
case "ws_close":
|
|
319
|
+
handleWsClose(ctx, payload);
|
|
320
|
+
break;
|
|
321
|
+
case "health_ping":
|
|
322
|
+
ws.send(JSON.stringify({ type: "health_pong" }));
|
|
323
|
+
break;
|
|
319
324
|
}
|
|
320
325
|
} catch (error) {
|
|
321
326
|
console.error("Tunnel message error", error);
|
|
@@ -331,4 +336,4 @@ export {
|
|
|
331
336
|
createTunnel,
|
|
332
337
|
connectTunnel
|
|
333
338
|
};
|
|
334
|
-
//# sourceMappingURL=chunk-
|
|
339
|
+
//# sourceMappingURL=chunk-HOYYXZPV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/local-run.ts","../src/tunnel.ts"],"sourcesContent":["import process from \"node:process\";\nimport { readStoredToken } from \"./auth\";\n\ntype LocalRunOptions = {\n apiUrl: string;\n token?: string;\n title?: string;\n featureSpec?: string;\n startUrl?: string;\n tunnelUrl?: string;\n};\n\ntype LocalRunResponse = {\n ok: boolean;\n runId?: string;\n watchUrl?: string;\n error?: string;\n};\n\nfunction getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1) return undefined;\n return argv[index + 1];\n}\n\nexport async function runLocalTest(argv: string[]) {\n const apiUrl = getArgValue(argv, \"--api-url\") ?? process.env.CANARY_API_URL ?? \"https://api.trycanary.ai\";\n const token =\n getArgValue(argv, \"--token\") ??\n process.env.CANARY_API_TOKEN ??\n (await readStoredToken());\n const title = getArgValue(argv, \"--title\");\n const featureSpec = getArgValue(argv, \"--feature\");\n const startUrl = getArgValue(argv, \"--start-url\");\n const tunnelUrl = getArgValue(argv, \"--tunnel-url\");\n\n if (!tunnelUrl && !startUrl) {\n console.error(\"Missing --tunnel-url or --start-url\");\n process.exit(1);\n }\n\n if (!token) {\n console.error(\"Missing token. Run `canary login` first or set CANARY_API_TOKEN.\");\n process.exit(1);\n }\n\n const result = await createLocalRun({\n apiUrl,\n token,\n title,\n featureSpec,\n startUrl,\n tunnelUrl,\n });\n\n console.log(`Local test queued: ${result.runId}`);\n if (result.watchUrl) {\n console.log(`Watch: ${result.watchUrl}`);\n }\n}\n\nexport async function createLocalRun(input: {\n apiUrl: string;\n token: string;\n title?: string;\n featureSpec?: string;\n startUrl?: string;\n tunnelUrl?: string;\n}): Promise<{ runId: string; watchUrl?: string }> {\n const body = {\n title: input.title ?? null,\n featureSpec: input.featureSpec ?? null,\n startUrl: input.startUrl ?? null,\n tunnelPublicUrl: input.tunnelUrl ?? null,\n };\n\n const response = await fetch(`${input.apiUrl}/local-tests/runs`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${input.token}`,\n },\n body: JSON.stringify(body),\n });\n\n const json = (await response.json()) as LocalRunResponse;\n if (!response.ok || !json.ok || !json.runId) {\n throw new Error(json.error ?? response.statusText);\n }\n\n return { runId: json.runId, watchUrl: json.watchUrl };\n}\n","import { createHash } from \"node:crypto\";\nimport os from \"node:os\";\nimport process from \"node:process\";\nimport { readStoredToken } from \"./auth\";\n\nexport type CreateTunnelResponse = {\n ok: boolean;\n tunnelId?: string;\n publicUrl?: string;\n token?: string;\n error?: string;\n};\n\ntype ProxyRequest = {\n type: \"http_request\";\n id: string;\n method: string;\n path: string;\n headers: Record<string, string>;\n bodyBase64?: string | null;\n};\n\ntype ProxyResponse = {\n type: \"http_response\";\n id: string;\n status: number;\n headers?: Record<string, string | string[]>;\n bodyBase64?: string | null;\n};\n\ntype WsOpen = {\n type: \"ws_open\";\n id: string;\n path: string;\n headers: Record<string, string>;\n};\n\ntype WsReady = {\n type: \"ws_ready\";\n id: string;\n};\n\ntype WsMessage = {\n type: \"ws_message\";\n id: string;\n dataBase64: string;\n isBinary: boolean;\n};\n\ntype WsClose = {\n type: \"ws_close\";\n id: string;\n code?: number;\n reason?: string;\n};\n\nfunction getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1) return undefined;\n return argv[index + 1];\n}\n\nfunction toWebSocketUrl(apiUrl: string): string {\n const url = new URL(apiUrl);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return url.toString();\n}\n\nfunction createFingerprint(): string {\n const raw = `${os.hostname()}-${os.userInfo().username}-${process.version}`;\n return createHash(\"sha256\").update(raw).digest(\"hex\").slice(0, 16);\n}\n\nexport async function runTunnel(argv: string[]) {\n const apiUrl = getArgValue(argv, \"--api-url\") ?? process.env.CANARY_API_URL ?? \"https://api.trycanary.ai\";\n const token =\n getArgValue(argv, \"--token\") ??\n process.env.CANARY_API_TOKEN ??\n (await readStoredToken());\n const portRaw = getArgValue(argv, \"--port\") ?? process.env.CANARY_LOCAL_PORT;\n\n if (!portRaw) {\n console.error(\"Missing --port\");\n process.exit(1);\n }\n\n const port = Number(portRaw);\n if (Number.isNaN(port) || port <= 0) {\n console.error(\"Invalid --port value\");\n process.exit(1);\n }\n\n if (!token) {\n console.error(\"Missing token. Run `canary login` first or set CANARY_API_TOKEN.\");\n process.exit(1);\n }\n\n const maxReconnectAttempts = 10;\n const baseReconnectDelayMs = 1000;\n let reconnectAttempts = 0;\n\n const connect = async (): Promise<void> => {\n try {\n const data = await createTunnel({ apiUrl, token, port });\n\n console.log(`Tunnel connected: ${data.publicUrl ?? data.tunnelId}`);\n if (data.publicUrl) {\n console.log(`Public URL: ${data.publicUrl}`);\n console.log(\"\");\n console.log(\"To use this tunnel for sandbox agent callbacks, add to apps/api/.env:\");\n console.log(` SANDBOX_AGENT_API_URL=${data.publicUrl}`);\n console.log(\"\");\n }\n\n const ws = connectTunnel({\n apiUrl,\n tunnelId: data.tunnelId,\n token: data.token,\n port,\n onReady: () => {\n reconnectAttempts = 0;\n },\n });\n\n return new Promise<void>((resolve, reject) => {\n ws.onclose = (event) => {\n console.log(`Tunnel closed (code: ${event.code})`);\n\n if (reconnectAttempts < maxReconnectAttempts) {\n const delay = Math.min(baseReconnectDelayMs * Math.pow(2, reconnectAttempts), 30000);\n reconnectAttempts++;\n console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${maxReconnectAttempts})...`);\n setTimeout(() => {\n connect().then(resolve).catch(reject);\n }, delay);\n } else {\n console.error(\"Max reconnection attempts reached. Exiting.\");\n process.exit(1);\n }\n };\n\n ws.onerror = (event) => {\n console.error(\"Tunnel error:\", event);\n };\n });\n } catch (error) {\n if (reconnectAttempts < maxReconnectAttempts) {\n const delay = Math.min(baseReconnectDelayMs * Math.pow(2, reconnectAttempts), 30000);\n reconnectAttempts++;\n console.error(`Failed to create tunnel: ${error}`);\n console.log(`Retrying in ${delay}ms (attempt ${reconnectAttempts}/${maxReconnectAttempts})...`);\n await new Promise((resolve) => setTimeout(resolve, delay));\n return connect();\n } else {\n console.error(\"Max reconnection attempts reached. Exiting.\");\n process.exit(1);\n }\n }\n };\n\n await connect();\n}\n\nexport async function createTunnel(input: {\n apiUrl: string;\n token: string;\n port: number;\n}): Promise<{ tunnelId: string; publicUrl?: string; token: string }> {\n const response = await fetch(`${input.apiUrl}/local-tests/tunnels`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${input.token}`,\n },\n body: JSON.stringify({\n requestedPort: input.port,\n clientFingerprint: createFingerprint(),\n }),\n });\n\n const data = (await response.json()) as CreateTunnelResponse;\n if (!response.ok || !data.ok || !data.tunnelId || !data.token) {\n throw new Error(data.error ?? response.statusText);\n }\n\n return { tunnelId: data.tunnelId, publicUrl: data.publicUrl, token: data.token };\n}\n\n// === Tunnel Message Handlers ===\n\ntype TunnelContext = {\n ws: WebSocket;\n port: number;\n wsConnections: Map<string, WebSocket>;\n wsQueues: Map<string, Array<WsMessage | WsClose>>;\n};\n\nfunction handleHttpRequest(ctx: TunnelContext, request: ProxyRequest): void {\n const targetUrl = `http://localhost:${ctx.port}${\n request.path.startsWith(\"/\") ? request.path : `/${request.path}`\n }`;\n const body = request.bodyBase64 ? Buffer.from(request.bodyBase64, \"base64\") : undefined;\n const headers = { ...request.headers };\n delete headers.host;\n delete headers[\"content-length\"];\n\n fetch(targetUrl, { method: request.method, headers, body: body ?? undefined })\n .then(async (res) => {\n const resBody = await res.arrayBuffer();\n const resHeaders: Record<string, string | string[]> = Object.fromEntries(res.headers.entries());\n delete resHeaders[\"set-cookie\"];\n\n const getSetCookie = (res.headers as Headers & { getSetCookie?: () => string[] }).getSetCookie;\n const setCookieValues = typeof getSetCookie === \"function\" ? getSetCookie.call(res.headers) : [];\n const fallbackSetCookie = res.headers.get(\"set-cookie\");\n if (setCookieValues.length === 0 && fallbackSetCookie) {\n setCookieValues.push(fallbackSetCookie);\n }\n if (setCookieValues.length > 0) {\n resHeaders[\"set-cookie\"] = setCookieValues;\n }\n\n const responsePayload: ProxyResponse = {\n type: \"http_response\",\n id: request.id,\n status: res.status,\n headers: resHeaders,\n bodyBase64: resBody.byteLength ? Buffer.from(resBody).toString(\"base64\") : null,\n };\n ctx.ws.send(JSON.stringify(responsePayload));\n })\n .catch((error) => {\n const responsePayload: ProxyResponse = {\n type: \"http_response\",\n id: request.id,\n status: 502,\n headers: { \"content-type\": \"text/plain\" },\n bodyBase64: Buffer.from(`Tunnel error: ${String(error)}`).toString(\"base64\"),\n };\n ctx.ws.send(JSON.stringify(responsePayload));\n });\n}\n\nfunction handleWsOpen(ctx: TunnelContext, request: WsOpen): void {\n const targetUrl = `ws://localhost:${ctx.port}${\n request.path.startsWith(\"/\") ? request.path : `/${request.path}`\n }`;\n const protocolsHeader =\n request.headers[\"sec-websocket-protocol\"] ?? request.headers[\"Sec-WebSocket-Protocol\"];\n const protocols = protocolsHeader\n ? protocolsHeader.split(\",\").map((v) => v.trim()).filter(Boolean)\n : undefined;\n const localWs = new WebSocket(targetUrl, protocols);\n ctx.wsConnections.set(request.id, localWs);\n\n localWs.onopen = () => {\n ctx.ws.send(JSON.stringify({ type: \"ws_ready\", id: request.id } satisfies WsReady));\n const queued = ctx.wsQueues.get(request.id);\n if (queued) {\n for (const message of queued) {\n ctx.ws.send(JSON.stringify(message));\n }\n ctx.wsQueues.delete(request.id);\n }\n };\n\n localWs.onmessage = (event) => {\n const data =\n typeof event.data === \"string\"\n ? Buffer.from(event.data)\n : Buffer.from(event.data as ArrayBuffer);\n const response: WsMessage = {\n type: \"ws_message\",\n id: request.id,\n dataBase64: data.toString(\"base64\"),\n isBinary: typeof event.data !== \"string\",\n };\n ctx.ws.send(JSON.stringify(response));\n };\n\n localWs.onclose = (event) => {\n ctx.wsConnections.delete(request.id);\n const response: WsClose = {\n type: \"ws_close\",\n id: request.id,\n code: event.code,\n reason: event.reason,\n };\n ctx.ws.send(JSON.stringify(response));\n };\n\n localWs.onerror = () => {\n ctx.wsConnections.delete(request.id);\n const response: WsClose = {\n type: \"ws_close\",\n id: request.id,\n code: 1011,\n reason: \"local_ws_error\",\n };\n ctx.ws.send(JSON.stringify(response));\n };\n}\n\nfunction handleWsMessage(ctx: TunnelContext, message: WsMessage): void {\n const localWs = ctx.wsConnections.get(message.id);\n const data = Buffer.from(message.dataBase64, \"base64\");\n if (!localWs || localWs.readyState !== WebSocket.OPEN) {\n const queued = ctx.wsQueues.get(message.id) ?? [];\n queued.push(message);\n ctx.wsQueues.set(message.id, queued);\n return;\n }\n if (message.isBinary) {\n localWs.send(data);\n } else {\n localWs.send(data.toString());\n }\n}\n\nfunction handleWsClose(ctx: TunnelContext, message: WsClose): void {\n const localWs = ctx.wsConnections.get(message.id);\n if (!localWs) {\n const queued = ctx.wsQueues.get(message.id) ?? [];\n queued.push(message);\n ctx.wsQueues.set(message.id, queued);\n return;\n }\n localWs.close(message.code ?? 1000, message.reason ?? \"\");\n ctx.wsConnections.delete(message.id);\n}\n\n// === Connect ===\n\nexport function connectTunnel(input: {\n apiUrl: string;\n tunnelId: string;\n token: string;\n port: number;\n onReady?: () => void;\n}): WebSocket {\n const wsUrl = toWebSocketUrl(\n `${input.apiUrl}/local-tests/tunnels/${input.tunnelId}/connect?token=${input.token}`\n );\n const ws = new WebSocket(wsUrl);\n const ctx: TunnelContext = {\n ws,\n port: input.port,\n wsConnections: new Map(),\n wsQueues: new Map(),\n };\n\n ws.onopen = () => {\n input.onReady?.();\n };\n\n ws.onerror = (event) => {\n console.error(\"Tunnel error\", event);\n };\n\n ws.onclose = () => {\n console.log(\"Tunnel closed\");\n };\n\n ws.onmessage = async (event) => {\n try {\n const raw =\n typeof event.data === \"string\"\n ? event.data\n : Buffer.from(event.data as ArrayBuffer).toString();\n const payload = JSON.parse(raw) as { type: string };\n\n switch (payload.type) {\n case \"http_request\":\n handleHttpRequest(ctx, payload as ProxyRequest);\n break;\n case \"ws_open\":\n handleWsOpen(ctx, payload as WsOpen);\n break;\n case \"ws_message\":\n handleWsMessage(ctx, payload as WsMessage);\n break;\n case \"ws_close\":\n handleWsClose(ctx, payload as WsClose);\n break;\n case \"health_ping\":\n ws.send(JSON.stringify({ type: \"health_pong\" }));\n break;\n }\n } catch (error) {\n console.error(\"Tunnel message error\", error);\n }\n };\n\n return ws;\n}\n"],"mappings":";;;;;AAAA,OAAO,aAAa;AAmBpB,SAAS,YAAY,MAAgB,KAAiC;AACpE,QAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,MAAI,UAAU,GAAI,QAAO;AACzB,SAAO,KAAK,QAAQ,CAAC;AACvB;AAEA,eAAsB,aAAa,MAAgB;AACjD,QAAM,SAAS,YAAY,MAAM,WAAW,KAAK,QAAQ,IAAI,kBAAkB;AAC/E,QAAM,QACJ,YAAY,MAAM,SAAS,KAC3B,QAAQ,IAAI,oBACX,MAAM,gBAAgB;AACzB,QAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,QAAM,cAAc,YAAY,MAAM,WAAW;AACjD,QAAM,WAAW,YAAY,MAAM,aAAa;AAChD,QAAM,YAAY,YAAY,MAAM,cAAc;AAElD,MAAI,CAAC,aAAa,CAAC,UAAU;AAC3B,YAAQ,MAAM,qCAAqC;AACnD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,OAAO;AACV,YAAQ,MAAM,kEAAkE;AAChF,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,SAAS,MAAM,eAAe;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,UAAQ,IAAI,sBAAsB,OAAO,KAAK,EAAE;AAChD,MAAI,OAAO,UAAU;AACnB,YAAQ,IAAI,UAAU,OAAO,QAAQ,EAAE;AAAA,EACzC;AACF;AAEA,eAAsB,eAAe,OAOa;AAChD,QAAM,OAAO;AAAA,IACX,OAAO,MAAM,SAAS;AAAA,IACtB,aAAa,MAAM,eAAe;AAAA,IAClC,UAAU,MAAM,YAAY;AAAA,IAC5B,iBAAiB,MAAM,aAAa;AAAA,EACtC;AAEA,QAAM,WAAW,MAAM,MAAM,GAAG,MAAM,MAAM,qBAAqB;AAAA,IAC/D,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,UAAU,MAAM,KAAK;AAAA,IACtC;AAAA,IACA,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AAED,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,MAAI,CAAC,SAAS,MAAM,CAAC,KAAK,MAAM,CAAC,KAAK,OAAO;AAC3C,UAAM,IAAI,MAAM,KAAK,SAAS,SAAS,UAAU;AAAA,EACnD;AAEA,SAAO,EAAE,OAAO,KAAK,OAAO,UAAU,KAAK,SAAS;AACtD;;;AC3FA,SAAS,kBAAkB;AAC3B,OAAO,QAAQ;AACf,OAAOA,cAAa;AAsDpB,SAASC,aAAY,MAAgB,KAAiC;AACpE,QAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,MAAI,UAAU,GAAI,QAAO;AACzB,SAAO,KAAK,QAAQ,CAAC;AACvB;AAEA,SAAS,eAAe,QAAwB;AAC9C,QAAM,MAAM,IAAI,IAAI,MAAM;AAC1B,MAAI,WAAW,IAAI,aAAa,WAAW,SAAS;AACpD,SAAO,IAAI,SAAS;AACtB;AAEA,SAAS,oBAA4B;AACnC,QAAM,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,GAAG,SAAS,EAAE,QAAQ,IAAIC,SAAQ,OAAO;AACzE,SAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACnE;AAEA,eAAsB,UAAU,MAAgB;AAC9C,QAAM,SAASD,aAAY,MAAM,WAAW,KAAKC,SAAQ,IAAI,kBAAkB;AAC/E,QAAM,QACJD,aAAY,MAAM,SAAS,KAC3BC,SAAQ,IAAI,oBACX,MAAM,gBAAgB;AACzB,QAAM,UAAUD,aAAY,MAAM,QAAQ,KAAKC,SAAQ,IAAI;AAE3D,MAAI,CAAC,SAAS;AACZ,YAAQ,MAAM,gBAAgB;AAC9B,IAAAA,SAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAO,OAAO,OAAO;AAC3B,MAAI,OAAO,MAAM,IAAI,KAAK,QAAQ,GAAG;AACnC,YAAQ,MAAM,sBAAsB;AACpC,IAAAA,SAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,OAAO;AACV,YAAQ,MAAM,kEAAkE;AAChF,IAAAA,SAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,uBAAuB;AAC7B,QAAM,uBAAuB;AAC7B,MAAI,oBAAoB;AAExB,QAAM,UAAU,YAA2B;AACzC,QAAI;AACF,YAAM,OAAO,MAAM,aAAa,EAAE,QAAQ,OAAO,KAAK,CAAC;AAEvD,cAAQ,IAAI,qBAAqB,KAAK,aAAa,KAAK,QAAQ,EAAE;AAClE,UAAI,KAAK,WAAW;AAClB,gBAAQ,IAAI,eAAe,KAAK,SAAS,EAAE;AAC3C,gBAAQ,IAAI,EAAE;AACd,gBAAQ,IAAI,uEAAuE;AACnF,gBAAQ,IAAI,2BAA2B,KAAK,SAAS,EAAE;AACvD,gBAAQ,IAAI,EAAE;AAAA,MAChB;AAEA,YAAM,KAAK,cAAc;AAAA,QACvB;AAAA,QACA,UAAU,KAAK;AAAA,QACf,OAAO,KAAK;AAAA,QACZ;AAAA,QACA,SAAS,MAAM;AACb,8BAAoB;AAAA,QACtB;AAAA,MACF,CAAC;AAED,aAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,WAAG,UAAU,CAAC,UAAU;AACtB,kBAAQ,IAAI,wBAAwB,MAAM,IAAI,GAAG;AAEjD,cAAI,oBAAoB,sBAAsB;AAC5C,kBAAM,QAAQ,KAAK,IAAI,uBAAuB,KAAK,IAAI,GAAG,iBAAiB,GAAG,GAAK;AACnF;AACA,oBAAQ,IAAI,mBAAmB,KAAK,eAAe,iBAAiB,IAAI,oBAAoB,MAAM;AAClG,uBAAW,MAAM;AACf,sBAAQ,EAAE,KAAK,OAAO,EAAE,MAAM,MAAM;AAAA,YACtC,GAAG,KAAK;AAAA,UACV,OAAO;AACL,oBAAQ,MAAM,6CAA6C;AAC3D,YAAAA,SAAQ,KAAK,CAAC;AAAA,UAChB;AAAA,QACF;AAEA,WAAG,UAAU,CAAC,UAAU;AACtB,kBAAQ,MAAM,iBAAiB,KAAK;AAAA,QACtC;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,oBAAoB,sBAAsB;AAC5C,cAAM,QAAQ,KAAK,IAAI,uBAAuB,KAAK,IAAI,GAAG,iBAAiB,GAAG,GAAK;AACnF;AACA,gBAAQ,MAAM,4BAA4B,KAAK,EAAE;AACjD,gBAAQ,IAAI,eAAe,KAAK,eAAe,iBAAiB,IAAI,oBAAoB,MAAM;AAC9F,cAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,CAAC;AACzD,eAAO,QAAQ;AAAA,MACjB,OAAO;AACL,gBAAQ,MAAM,6CAA6C;AAC3D,QAAAA,SAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ;AAChB;AAEA,eAAsB,aAAa,OAIkC;AACnE,QAAM,WAAW,MAAM,MAAM,GAAG,MAAM,MAAM,wBAAwB;AAAA,IAClE,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,UAAU,MAAM,KAAK;AAAA,IACtC;AAAA,IACA,MAAM,KAAK,UAAU;AAAA,MACnB,eAAe,MAAM;AAAA,MACrB,mBAAmB,kBAAkB;AAAA,IACvC,CAAC;AAAA,EACH,CAAC;AAED,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,MAAI,CAAC,SAAS,MAAM,CAAC,KAAK,MAAM,CAAC,KAAK,YAAY,CAAC,KAAK,OAAO;AAC7D,UAAM,IAAI,MAAM,KAAK,SAAS,SAAS,UAAU;AAAA,EACnD;AAEA,SAAO,EAAE,UAAU,KAAK,UAAU,WAAW,KAAK,WAAW,OAAO,KAAK,MAAM;AACjF;AAWA,SAAS,kBAAkB,KAAoB,SAA6B;AAC1E,QAAM,YAAY,oBAAoB,IAAI,IAAI,GAC5C,QAAQ,KAAK,WAAW,GAAG,IAAI,QAAQ,OAAO,IAAI,QAAQ,IAAI,EAChE;AACA,QAAM,OAAO,QAAQ,aAAa,OAAO,KAAK,QAAQ,YAAY,QAAQ,IAAI;AAC9E,QAAM,UAAU,EAAE,GAAG,QAAQ,QAAQ;AACrC,SAAO,QAAQ;AACf,SAAO,QAAQ,gBAAgB;AAE/B,QAAM,WAAW,EAAE,QAAQ,QAAQ,QAAQ,SAAS,MAAM,QAAQ,OAAU,CAAC,EAC1E,KAAK,OAAO,QAAQ;AACnB,UAAM,UAAU,MAAM,IAAI,YAAY;AACtC,UAAM,aAAgD,OAAO,YAAY,IAAI,QAAQ,QAAQ,CAAC;AAC9F,WAAO,WAAW,YAAY;AAE9B,UAAM,eAAgB,IAAI,QAAwD;AAClF,UAAM,kBAAkB,OAAO,iBAAiB,aAAa,aAAa,KAAK,IAAI,OAAO,IAAI,CAAC;AAC/F,UAAM,oBAAoB,IAAI,QAAQ,IAAI,YAAY;AACtD,QAAI,gBAAgB,WAAW,KAAK,mBAAmB;AACrD,sBAAgB,KAAK,iBAAiB;AAAA,IACxC;AACA,QAAI,gBAAgB,SAAS,GAAG;AAC9B,iBAAW,YAAY,IAAI;AAAA,IAC7B;AAEA,UAAM,kBAAiC;AAAA,MACrC,MAAM;AAAA,MACN,IAAI,QAAQ;AAAA,MACZ,QAAQ,IAAI;AAAA,MACZ,SAAS;AAAA,MACT,YAAY,QAAQ,aAAa,OAAO,KAAK,OAAO,EAAE,SAAS,QAAQ,IAAI;AAAA,IAC7E;AACA,QAAI,GAAG,KAAK,KAAK,UAAU,eAAe,CAAC;AAAA,EAC7C,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,UAAM,kBAAiC;AAAA,MACrC,MAAM;AAAA,MACN,IAAI,QAAQ;AAAA,MACZ,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,aAAa;AAAA,MACxC,YAAY,OAAO,KAAK,iBAAiB,OAAO,KAAK,CAAC,EAAE,EAAE,SAAS,QAAQ;AAAA,IAC7E;AACA,QAAI,GAAG,KAAK,KAAK,UAAU,eAAe,CAAC;AAAA,EAC7C,CAAC;AACL;AAEA,SAAS,aAAa,KAAoB,SAAuB;AAC/D,QAAM,YAAY,kBAAkB,IAAI,IAAI,GAC1C,QAAQ,KAAK,WAAW,GAAG,IAAI,QAAQ,OAAO,IAAI,QAAQ,IAAI,EAChE;AACA,QAAM,kBACJ,QAAQ,QAAQ,wBAAwB,KAAK,QAAQ,QAAQ,wBAAwB;AACvF,QAAM,YAAY,kBACd,gBAAgB,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,IAC9D;AACJ,QAAM,UAAU,IAAI,UAAU,WAAW,SAAS;AAClD,MAAI,cAAc,IAAI,QAAQ,IAAI,OAAO;AAEzC,UAAQ,SAAS,MAAM;AACrB,QAAI,GAAG,KAAK,KAAK,UAAU,EAAE,MAAM,YAAY,IAAI,QAAQ,GAAG,CAAmB,CAAC;AAClF,UAAM,SAAS,IAAI,SAAS,IAAI,QAAQ,EAAE;AAC1C,QAAI,QAAQ;AACV,iBAAW,WAAW,QAAQ;AAC5B,YAAI,GAAG,KAAK,KAAK,UAAU,OAAO,CAAC;AAAA,MACrC;AACA,UAAI,SAAS,OAAO,QAAQ,EAAE;AAAA,IAChC;AAAA,EACF;AAEA,UAAQ,YAAY,CAAC,UAAU;AAC7B,UAAM,OACJ,OAAO,MAAM,SAAS,WAClB,OAAO,KAAK,MAAM,IAAI,IACtB,OAAO,KAAK,MAAM,IAAmB;AAC3C,UAAM,WAAsB;AAAA,MAC1B,MAAM;AAAA,MACN,IAAI,QAAQ;AAAA,MACZ,YAAY,KAAK,SAAS,QAAQ;AAAA,MAClC,UAAU,OAAO,MAAM,SAAS;AAAA,IAClC;AACA,QAAI,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;AAAA,EACtC;AAEA,UAAQ,UAAU,CAAC,UAAU;AAC3B,QAAI,cAAc,OAAO,QAAQ,EAAE;AACnC,UAAM,WAAoB;AAAA,MACxB,MAAM;AAAA,MACN,IAAI,QAAQ;AAAA,MACZ,MAAM,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,IAChB;AACA,QAAI,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;AAAA,EACtC;AAEA,UAAQ,UAAU,MAAM;AACtB,QAAI,cAAc,OAAO,QAAQ,EAAE;AACnC,UAAM,WAAoB;AAAA,MACxB,MAAM;AAAA,MACN,IAAI,QAAQ;AAAA,MACZ,MAAM;AAAA,MACN,QAAQ;AAAA,IACV;AACA,QAAI,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;AAAA,EACtC;AACF;AAEA,SAAS,gBAAgB,KAAoB,SAA0B;AACrE,QAAM,UAAU,IAAI,cAAc,IAAI,QAAQ,EAAE;AAChD,QAAM,OAAO,OAAO,KAAK,QAAQ,YAAY,QAAQ;AACrD,MAAI,CAAC,WAAW,QAAQ,eAAe,UAAU,MAAM;AACrD,UAAM,SAAS,IAAI,SAAS,IAAI,QAAQ,EAAE,KAAK,CAAC;AAChD,WAAO,KAAK,OAAO;AACnB,QAAI,SAAS,IAAI,QAAQ,IAAI,MAAM;AACnC;AAAA,EACF;AACA,MAAI,QAAQ,UAAU;AACpB,YAAQ,KAAK,IAAI;AAAA,EACnB,OAAO;AACL,YAAQ,KAAK,KAAK,SAAS,CAAC;AAAA,EAC9B;AACF;AAEA,SAAS,cAAc,KAAoB,SAAwB;AACjE,QAAM,UAAU,IAAI,cAAc,IAAI,QAAQ,EAAE;AAChD,MAAI,CAAC,SAAS;AACZ,UAAM,SAAS,IAAI,SAAS,IAAI,QAAQ,EAAE,KAAK,CAAC;AAChD,WAAO,KAAK,OAAO;AACnB,QAAI,SAAS,IAAI,QAAQ,IAAI,MAAM;AACnC;AAAA,EACF;AACA,UAAQ,MAAM,QAAQ,QAAQ,KAAM,QAAQ,UAAU,EAAE;AACxD,MAAI,cAAc,OAAO,QAAQ,EAAE;AACrC;AAIO,SAAS,cAAc,OAMhB;AACZ,QAAM,QAAQ;AAAA,IACZ,GAAG,MAAM,MAAM,wBAAwB,MAAM,QAAQ,kBAAkB,MAAM,KAAK;AAAA,EACpF;AACA,QAAM,KAAK,IAAI,UAAU,KAAK;AAC9B,QAAM,MAAqB;AAAA,IACzB;AAAA,IACA,MAAM,MAAM;AAAA,IACZ,eAAe,oBAAI,IAAI;AAAA,IACvB,UAAU,oBAAI,IAAI;AAAA,EACpB;AAEA,KAAG,SAAS,MAAM;AAChB,UAAM,UAAU;AAAA,EAClB;AAEA,KAAG,UAAU,CAAC,UAAU;AACtB,YAAQ,MAAM,gBAAgB,KAAK;AAAA,EACrC;AAEA,KAAG,UAAU,MAAM;AACjB,YAAQ,IAAI,eAAe;AAAA,EAC7B;AAEA,KAAG,YAAY,OAAO,UAAU;AAC9B,QAAI;AACF,YAAM,MACJ,OAAO,MAAM,SAAS,WAClB,MAAM,OACN,OAAO,KAAK,MAAM,IAAmB,EAAE,SAAS;AACtD,YAAM,UAAU,KAAK,MAAM,GAAG;AAE9B,cAAQ,QAAQ,MAAM;AAAA,QACpB,KAAK;AACH,4BAAkB,KAAK,OAAuB;AAC9C;AAAA,QACF,KAAK;AACH,uBAAa,KAAK,OAAiB;AACnC;AAAA,QACF,KAAK;AACH,0BAAgB,KAAK,OAAoB;AACzC;AAAA,QACF,KAAK;AACH,wBAAc,KAAK,OAAkB;AACrC;AAAA,QACF,KAAK;AACH,aAAG,KAAK,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC,CAAC;AAC/C;AAAA,MACJ;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,wBAAwB,KAAK;AAAA,IAC7C;AAAA,EACF;AAEA,SAAO;AACT;","names":["process","getArgValue","process"]}
|