@interfere/next 9.0.2 → 10.0.1-canary.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 +39 -6
- package/dist/config.d.mts +25 -5
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +1 -100
- package/dist/config.mjs.map +1 -1
- package/dist/instrument-client.d.mts +11 -3
- package/dist/instrument-client.d.mts.map +1 -1
- package/dist/instrument-client.mjs +1 -11
- package/dist/instrument-client.mjs.map +1 -1
- package/dist/instrumentation-client.d.mts +1 -0
- package/dist/instrumentation-client.mjs +1 -0
- package/dist/instrumentation-client.mjs.map +1 -0
- package/dist/instrumentation.d.mts +134 -0
- package/dist/instrumentation.d.mts.map +1 -0
- package/dist/instrumentation.edge.d.mts +35 -0
- package/dist/instrumentation.edge.d.mts.map +1 -0
- package/dist/instrumentation.edge.mjs +1 -0
- package/dist/instrumentation.edge.mjs.map +1 -0
- package/dist/instrumentation.mjs +1 -0
- package/dist/instrumentation.mjs.map +1 -0
- package/dist/internal/build/configure-build.d.mts +3 -2
- package/dist/internal/build/configure-build.d.mts.map +1 -1
- package/dist/internal/build/configure-build.mjs +1 -87
- package/dist/internal/build/configure-build.mjs.map +1 -1
- package/dist/internal/build/detect-bundler.d.mts +7 -0
- package/dist/internal/build/detect-bundler.d.mts.map +1 -0
- package/dist/internal/build/detect-bundler.mjs +1 -0
- package/dist/internal/build/detect-bundler.mjs.map +1 -0
- package/dist/internal/build/pipeline.d.mts +13 -2
- package/dist/internal/build/pipeline.d.mts.map +1 -1
- package/dist/internal/build/pipeline.mjs +1 -66
- package/dist/internal/build/pipeline.mjs.map +1 -1
- package/dist/internal/build/release/destinations/index.d.mts +14 -0
- package/dist/internal/build/release/destinations/index.d.mts.map +1 -0
- package/dist/internal/build/release/destinations/index.mjs +1 -0
- package/dist/internal/build/release/destinations/index.mjs.map +1 -0
- package/dist/internal/build/release/destinations/vercel.d.mts.map +1 -1
- package/dist/internal/build/release/destinations/vercel.mjs +1 -23
- package/dist/internal/build/release/destinations/vercel.mjs.map +1 -1
- package/dist/internal/build/release/git.d.mts +13 -0
- package/dist/internal/build/release/git.d.mts.map +1 -1
- package/dist/internal/build/release/git.mjs +1 -21
- package/dist/internal/build/release/git.mjs.map +1 -1
- package/dist/internal/build/release/index.d.mts +2 -1
- package/dist/internal/build/release/index.d.mts.map +1 -1
- package/dist/internal/build/release/index.mjs +1 -19
- package/dist/internal/build/release/index.mjs.map +1 -1
- package/dist/internal/build/release/sources/github.d.mts.map +1 -1
- package/dist/internal/build/release/sources/github.mjs +1 -13
- package/dist/internal/build/release/sources/github.mjs.map +1 -1
- package/dist/internal/build/release/sources/index.d.mts +21 -0
- package/dist/internal/build/release/sources/index.d.mts.map +1 -0
- package/dist/internal/build/release/sources/index.mjs +1 -0
- package/dist/internal/build/release/sources/index.mjs.map +1 -0
- package/dist/internal/build/source-maps/discover-turbopack.d.mts +32 -0
- package/dist/internal/build/source-maps/discover-turbopack.d.mts.map +1 -0
- package/dist/internal/build/source-maps/discover-turbopack.mjs +1 -0
- package/dist/internal/build/source-maps/discover-turbopack.mjs.map +1 -0
- package/dist/internal/build/source-maps/discover-webpack.d.mts +53 -0
- package/dist/internal/build/source-maps/discover-webpack.d.mts.map +1 -0
- package/dist/internal/build/source-maps/discover-webpack.mjs +1 -0
- package/dist/internal/build/source-maps/discover-webpack.mjs.map +1 -0
- package/dist/internal/build/source-maps/discover.d.mts +28 -10
- package/dist/internal/build/source-maps/discover.d.mts.map +1 -1
- package/dist/internal/build/source-maps/discover.mjs +1 -87
- package/dist/internal/build/source-maps/discover.mjs.map +1 -1
- package/dist/internal/build/source-maps/index.d.mts +2 -24
- package/dist/internal/build/source-maps/index.d.mts.map +1 -1
- package/dist/internal/build/source-maps/index.mjs +1 -28
- package/dist/internal/build/source-maps/index.mjs.map +1 -1
- package/dist/internal/build/source-maps/paths.d.mts +28 -0
- package/dist/internal/build/source-maps/paths.d.mts.map +1 -0
- package/dist/internal/build/source-maps/paths.mjs +1 -0
- package/dist/internal/build/source-maps/paths.mjs.map +1 -0
- package/dist/internal/build/source-maps/upload.d.mts +46 -0
- package/dist/internal/build/source-maps/upload.d.mts.map +1 -0
- package/dist/internal/build/source-maps/upload.mjs +1 -0
- package/dist/internal/build/source-maps/upload.mjs.map +1 -0
- package/dist/internal/build/value-injection-loader.d.mts.map +1 -1
- package/dist/internal/build/value-injection-loader.mjs +2 -24
- package/dist/internal/build/value-injection-loader.mjs.map +1 -1
- package/dist/internal/env.d.mts +13 -3
- package/dist/internal/env.d.mts.map +1 -1
- package/dist/internal/env.mjs +1 -23
- package/dist/internal/env.mjs.map +1 -1
- package/dist/internal/logger.d.mts +9 -1
- package/dist/internal/logger.d.mts.map +1 -1
- package/dist/internal/logger.mjs +1 -60
- package/dist/internal/logger.mjs.map +1 -1
- package/dist/internal/release-slug.d.mts +25 -0
- package/dist/internal/release-slug.d.mts.map +1 -0
- package/dist/internal/release-slug.mjs +1 -0
- package/dist/internal/release-slug.mjs.map +1 -0
- package/dist/internal/route/handle-get.d.mts +14 -1
- package/dist/internal/route/handle-get.d.mts.map +1 -1
- package/dist/internal/route/handle-get.mjs +1 -22
- package/dist/internal/route/handle-get.mjs.map +1 -1
- package/dist/internal/route/handle-post.d.mts +11 -0
- package/dist/internal/route/handle-post.d.mts.map +1 -1
- package/dist/internal/route/handle-post.mjs +1 -70
- package/dist/internal/route/handle-post.mjs.map +1 -1
- package/dist/internal/route/proxy.d.mts +26 -4
- package/dist/internal/route/proxy.d.mts.map +1 -1
- package/dist/internal/route/proxy.mjs +1 -89
- package/dist/internal/route/proxy.mjs.map +1 -1
- package/dist/internal/server/capture.d.mts +2 -2
- package/dist/internal/server/capture.d.mts.map +1 -1
- package/dist/internal/server/capture.mjs +1 -55
- package/dist/internal/server/capture.mjs.map +1 -1
- package/dist/internal/server/console-bridge.d.mts +19 -0
- package/dist/internal/server/console-bridge.d.mts.map +1 -0
- package/dist/internal/server/console-bridge.mjs +1 -0
- package/dist/internal/server/console-bridge.mjs.map +1 -0
- package/dist/internal/server/id-generator.d.mts +38 -0
- package/dist/internal/server/id-generator.d.mts.map +1 -0
- package/dist/internal/server/id-generator.mjs +1 -0
- package/dist/internal/server/id-generator.mjs.map +1 -0
- package/dist/internal/server/instrumentation-options.d.mts +86 -0
- package/dist/internal/server/instrumentation-options.d.mts.map +1 -0
- package/dist/internal/server/instrumentation-options.mjs +1 -0
- package/dist/internal/server/remote-config.d.mts.map +1 -1
- package/dist/internal/server/remote-config.mjs +1 -29
- package/dist/internal/server/remote-config.mjs.map +1 -1
- package/dist/internal/server/trace-meta.d.mts +32 -0
- package/dist/internal/server/trace-meta.d.mts.map +1 -0
- package/dist/internal/server/trace-meta.mjs +1 -0
- package/dist/internal/server/trace-meta.mjs.map +1 -0
- package/dist/internal/server/traceparent.d.mts +16 -0
- package/dist/internal/server/traceparent.d.mts.map +1 -0
- package/dist/internal/server/traceparent.mjs +1 -0
- package/dist/internal/server/traceparent.mjs.map +1 -0
- package/dist/internal/server/types.d.mts +1 -7
- package/dist/internal/server/types.d.mts.map +1 -1
- package/dist/internal/server/types.mjs +1 -1
- package/dist/internal/setup-warnings.d.mts +17 -0
- package/dist/internal/setup-warnings.d.mts.map +1 -0
- package/dist/internal/setup-warnings.mjs +1 -0
- package/dist/internal/setup-warnings.mjs.map +1 -0
- package/dist/internal/url.d.mts +4 -0
- package/dist/internal/url.d.mts.map +1 -0
- package/dist/internal/url.mjs +1 -0
- package/dist/internal/url.mjs.map +1 -0
- package/dist/internal/version.mjs +1 -5
- package/dist/internal/version.mjs.map +1 -1
- package/dist/package.mjs +1 -5
- package/dist/provider.d.mts +23 -2
- package/dist/provider.d.mts.map +1 -0
- package/dist/provider.mjs +1 -3
- package/dist/provider.mjs.map +1 -0
- package/dist/route-handler.d.mts +7 -2
- package/dist/route-handler.d.mts.map +1 -1
- package/dist/route-handler.mjs +1 -31
- package/dist/route-handler.mjs.map +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.mjs +1 -3
- package/package.json +78 -26
- package/dist/internal/route/sw-script.d.mts +0 -4
- package/dist/internal/route/sw-script.d.mts.map +0 -1
- package/dist/internal/route/sw-script.mjs +0 -38
- package/dist/internal/route/sw-script.mjs.map +0 -1
- package/dist/internal/server/dedupe.d.mts +0 -5
- package/dist/internal/server/dedupe.d.mts.map +0 -1
- package/dist/internal/server/dedupe.mjs +0 -11
- package/dist/internal/server/dedupe.mjs.map +0 -1
- package/dist/internal/server/envelope.d.mts +0 -14
- package/dist/internal/server/envelope.d.mts.map +0 -1
- package/dist/internal/server/envelope.mjs +0 -59
- package/dist/internal/server/envelope.mjs.map +0 -1
- package/dist/internal/server/normalize-request.d.mts +0 -7
- package/dist/internal/server/normalize-request.d.mts.map +0 -1
- package/dist/internal/server/normalize-request.mjs +0 -50
- package/dist/internal/server/normalize-request.mjs.map +0 -1
- package/dist/internal/server/runtime.d.mts +0 -14
- package/dist/internal/server/runtime.d.mts.map +0 -1
- package/dist/internal/server/runtime.mjs +0 -18
- package/dist/internal/server/runtime.mjs.map +0 -1
- package/dist/internal/server/transport.d.mts +0 -12
- package/dist/internal/server/transport.d.mts.map +0 -1
- package/dist/internal/server/transport.mjs +0 -26
- package/dist/internal/server/transport.mjs.map +0 -1
package/dist/internal/env.mjs
CHANGED
|
@@ -1,23 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { API_URL } from "@interfere/constants/api";
|
|
3
|
-
import { normalizeEnv } from "@interfere/types/sdk/runtime";
|
|
4
|
-
//#region src/internal/env.ts
|
|
5
|
-
function isEnabledInEnvironment() {
|
|
6
|
-
if (process.env["NODE_ENV"] === "production") return true;
|
|
7
|
-
return !!process.env["NEXT_PUBLIC_INTERFERE_FORCE_ENABLE"];
|
|
8
|
-
}
|
|
9
|
-
function readInterfereEnv() {
|
|
10
|
-
const nodeEnvironment = normalizeEnv(process.env["NODE_ENV"]) ?? "production";
|
|
11
|
-
return {
|
|
12
|
-
apiKey: parseEnvValue(process.env["INTERFERE_API_KEY"]),
|
|
13
|
-
apiUrl: parseEnvValue(process.env["INTERFERE_API_URL"]) ?? API_URL,
|
|
14
|
-
nextRuntime: parseEnvValue(process.env["NEXT_RUNTIME"]),
|
|
15
|
-
nodeEnvironment,
|
|
16
|
-
release: {
|
|
17
|
-
sourceId: parseEnvValue(process.env["NEXT_PUBLIC_INTERFERE_BUILD_ID"]),
|
|
18
|
-
destinationId: parseEnvValue(process.env["NEXT_PUBLIC_INTERFERE_RELEASE_ID"])
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
//#endregion
|
|
23
|
-
export { isEnabledInEnvironment, readInterfereEnv };
|
|
1
|
+
import{API_URL}from"@interfere/constants/api";import{parseEnvValue}from"@interfere/types/sdk/env";import{normalizeEnv}from"@interfere/types/sdk/runtime";function isEnabledOnServer(){return process.env.NODE_ENV===`production`?!0:!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE}function readInterfereEnv(){let nodeEnvironment=normalizeEnv(process.env.NODE_ENV);return{apiKey:parseEnvValue(process.env.INTERFERE_API_KEY),apiUrl:parseEnvValue(process.env.INTERFERE_API_URL)??API_URL,nextRuntime:parseEnvValue(process.env.NEXT_RUNTIME),nodeEnvironment,publicKey:parseEnvValue(process.env.INTERFERE_PUBLIC_KEY),release:{sourceId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID),destinationId:parseEnvValue(process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID)}}}export{isEnabledOnServer,readInterfereEnv};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.mjs","names":[],"sources":["../../src/internal/env.ts"],"sourcesContent":["import { API_URL } from \"@interfere/constants/api\";\nimport { parseEnvValue } from \"@interfere/types/sdk/env\";\nimport type { Env } from \"@interfere/types/sdk/runtime\";\nimport { normalizeEnv } from \"@interfere/types/sdk/runtime\";\n\nexport interface InterfereEnv {\n readonly apiKey: string | null;\n readonly apiUrl: string;\n readonly nextRuntime: string | null;\n readonly nodeEnvironment:
|
|
1
|
+
{"version":3,"file":"env.mjs","names":[],"sources":["../../src/internal/env.ts"],"sourcesContent":["import { API_URL } from \"@interfere/constants/api\";\nimport { parseEnvValue } from \"@interfere/types/sdk/env\";\nimport type { Env } from \"@interfere/types/sdk/runtime\";\nimport { normalizeEnv } from \"@interfere/types/sdk/runtime\";\n\nexport interface InterfereEnv {\n readonly apiKey: string | null;\n readonly apiUrl: string;\n readonly nextRuntime: string | null;\n readonly nodeEnvironment: Env;\n readonly publicKey: string | null;\n readonly release: {\n readonly sourceId: string | null;\n readonly destinationId: string | null;\n };\n}\n\n/**\n * Server-side gate for the proxy route handler, the `captureError` /\n * `onRequestError` helpers, and the remote-config fetcher. Distinct\n * from the browser-side `isEnabledByEnvironment` (in\n * `@interfere/react/internal/kernel`) because the server has different\n * env conventions: `NEXT_PUBLIC_INTERFERE_FORCE_ENABLE` is the dev\n * opt-in, and there's no \"unknown runtime, default enabled\" case the\n * way the browser SDK has for plain-browser / Vite hosts.\n */\nexport function isEnabledOnServer(): boolean {\n if (process.env[\"NODE_ENV\"] === \"production\") {\n return true;\n }\n return !!process.env[\"NEXT_PUBLIC_INTERFERE_FORCE_ENABLE\"];\n}\n\nexport function readInterfereEnv(): InterfereEnv {\n const nodeEnvironment = normalizeEnv(process.env[\"NODE_ENV\"]);\n\n return {\n apiKey: parseEnvValue(process.env[\"INTERFERE_API_KEY\"]),\n apiUrl: parseEnvValue(process.env[\"INTERFERE_API_URL\"]) ?? API_URL,\n nextRuntime: parseEnvValue(process.env[\"NEXT_RUNTIME\"]),\n nodeEnvironment,\n publicKey: parseEnvValue(process.env[\"INTERFERE_PUBLIC_KEY\"]),\n release: {\n sourceId: parseEnvValue(process.env[\"NEXT_PUBLIC_INTERFERE_BUILD_ID\"]),\n destinationId: parseEnvValue(\n process.env[\"NEXT_PUBLIC_INTERFERE_RELEASE_ID\"]\n ),\n },\n };\n}\n"],"mappings":"yJA0BA,SAAgB,mBAA6B,CAI3C,OAHI,QAAQ,IAAI,WAAgB,aACvB,GAEF,CAAC,CAAC,QAAQ,IAAI,kCACvB,CAEA,SAAgB,kBAAiC,CAC/C,IAAM,gBAAkB,aAAa,QAAQ,IAAI,QAAW,EAE5D,MAAO,CACL,OAAQ,cAAc,QAAQ,IAAI,iBAAoB,EACtD,OAAQ,cAAc,QAAQ,IAAI,iBAAoB,GAAK,QAC3D,YAAa,cAAc,QAAQ,IAAI,YAAe,EACtD,gBACA,UAAW,cAAc,QAAQ,IAAI,oBAAuB,EAC5D,QAAS,CACP,SAAU,cAAc,QAAQ,IAAI,8BAAiC,EACrE,cAAe,cACb,QAAQ,IAAI,gCACd,CACF,CACF,CACF"}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
//#region src/internal/logger.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by `log.fatal` in test environments where `process.exit` is not
|
|
4
|
+
* appropriate. Tagged so callers' catch blocks can re-throw fatal errors and
|
|
5
|
+
* keep test-env behavior aligned with the production hard-exit.
|
|
6
|
+
*/
|
|
7
|
+
declare class FatalError extends Error {
|
|
8
|
+
readonly name = "FatalError";
|
|
9
|
+
}
|
|
2
10
|
declare const log: {
|
|
3
11
|
info: (title: string, lines: string[]) => void;
|
|
4
12
|
warn: (title: string, lines: string[]) => void;
|
|
@@ -6,4 +14,4 @@ declare const log: {
|
|
|
6
14
|
fatal(title: string, lines: string[]): never;
|
|
7
15
|
};
|
|
8
16
|
//#endregion
|
|
9
|
-
export { log };
|
|
17
|
+
export { FatalError, log };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.mts","names":[],"sources":["../../src/internal/logger.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"logger.d.mts","names":[],"sources":["../../src/internal/logger.ts"],"mappings":";;AAuDA;;;;cAAa,UAAA,SAAmB,KAAK;EAAA,SACjB,IAAI;AAAA;AAAA,cA8BX,GAAA;wBACS,KAAA;wBACA,KAAA;yBACC,KAAA;uBACF,KAAA;AAAA"}
|
package/dist/internal/logger.mjs
CHANGED
|
@@ -1,60 +1 @@
|
|
|
1
|
-
import chalk from
|
|
2
|
-
//#region src/internal/logger.ts
|
|
3
|
-
const styles = {
|
|
4
|
-
info: {
|
|
5
|
-
prefix: `${chalk.whiteBright.bold("❖")}`,
|
|
6
|
-
text: chalk.cyan.bold,
|
|
7
|
-
content: chalk.white,
|
|
8
|
-
prependLevel: false
|
|
9
|
-
},
|
|
10
|
-
warn: {
|
|
11
|
-
prefix: `${chalk.yellow.bold("⚠")} `,
|
|
12
|
-
text: chalk.yellow.bold,
|
|
13
|
-
content: chalk.yellowBright,
|
|
14
|
-
prependLevel: false
|
|
15
|
-
},
|
|
16
|
-
error: {
|
|
17
|
-
prefix: `${chalk.red.bold("⨯")} `,
|
|
18
|
-
text: chalk.red.bold,
|
|
19
|
-
content: chalk.redBright,
|
|
20
|
-
prependLevel: true
|
|
21
|
-
},
|
|
22
|
-
fatal: {
|
|
23
|
-
prefix: `${chalk.red.bold("⨯")} `,
|
|
24
|
-
text: chalk.red.bold,
|
|
25
|
-
content: chalk.redBright,
|
|
26
|
-
prependLevel: true
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
const consoleMethods = {
|
|
30
|
-
info: "log",
|
|
31
|
-
warn: "warn",
|
|
32
|
-
error: "error",
|
|
33
|
-
fatal: "error"
|
|
34
|
-
};
|
|
35
|
-
function isTestEnv() {
|
|
36
|
-
return Boolean(process.env["VITEST"] || process.env["VITEST_WORKER_ID"]);
|
|
37
|
-
}
|
|
38
|
-
function emit(level, title, lines) {
|
|
39
|
-
if (isTestEnv()) return;
|
|
40
|
-
const style = styles[level];
|
|
41
|
-
const method = consoleMethods[level];
|
|
42
|
-
const fn = globalThis.console[method];
|
|
43
|
-
if (typeof fn !== "function") return;
|
|
44
|
-
const invoke = (...args) => Reflect.apply(fn, globalThis.console, args);
|
|
45
|
-
const prependLevel = style.prependLevel ? `[${style.text(level.toUpperCase())}] ` : "";
|
|
46
|
-
invoke(`${prependLevel} ${chalk.white("Interfere →")} ${style.text(title)}`);
|
|
47
|
-
for (const [i, line] of lines.entries()) invoke(`${prependLevel} ${i === lines.length - 1 ? "└" : "├"} ${style.content(line)}`);
|
|
48
|
-
}
|
|
49
|
-
const log = {
|
|
50
|
-
info: (title, lines) => emit("info", title, lines),
|
|
51
|
-
warn: (title, lines) => emit("warn", title, lines),
|
|
52
|
-
error: (title, lines) => emit("error", title, lines),
|
|
53
|
-
fatal(title, lines) {
|
|
54
|
-
emit("fatal", title, lines);
|
|
55
|
-
if (isTestEnv()) throw new Error(title);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
//#endregion
|
|
60
|
-
export { log };
|
|
1
|
+
import chalk from"chalk";const styles={info:{prefix:`${chalk.whiteBright.bold(`❖`)}`,text:chalk.cyan.bold,content:chalk.white,prependLevel:!1},warn:{prefix:`${chalk.yellow.bold(`⚠`)} `,text:chalk.yellow.bold,content:chalk.yellowBright,prependLevel:!1},error:{prefix:`${chalk.red.bold(`⨯`)} `,text:chalk.red.bold,content:chalk.redBright,prependLevel:!0},fatal:{prefix:`${chalk.red.bold(`⨯`)} `,text:chalk.red.bold,content:chalk.redBright,prependLevel:!0}},consoleMethods={info:`log`,warn:`warn`,error:`error`,fatal:`error`};function isTestEnv(){return!!(process.env.VITEST||process.env.VITEST_WORKER_ID)}var FatalError=class extends Error{name=`FatalError`};function emit(level,title,lines){if(isTestEnv())return;let style=styles[level],method=consoleMethods[level],fn=globalThis.console[method];if(typeof fn!=`function`)return;let invoke=(...args)=>Reflect.apply(fn,globalThis.console,args),prependLevel=style.prependLevel?`[${style.text(level.toUpperCase())}] `:``;invoke(`${prependLevel} ${chalk.white(`Interfere →`)} ${style.text(title)}`);for(let[i,line]of lines.entries())invoke(`${prependLevel} ${i===lines.length-1?`└`:`├`} ${style.content(line)}`)}const log={info:(title,lines)=>emit(`info`,title,lines),warn:(title,lines)=>emit(`warn`,title,lines),error:(title,lines)=>emit(`error`,title,lines),fatal(title,lines){if(emit(`fatal`,title,lines),isTestEnv())throw new FatalError(title);process.exit(1)}};export{FatalError,log};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.mjs","names":[],"sources":["../../src/internal/logger.ts"],"sourcesContent":["import chalk from \"chalk\";\n\ntype LogLevel = \"info\" | \"warn\" | \"error\" | \"fatal\";\n\nconst styles = {\n info: {\n prefix: `${chalk.whiteBright.bold(\"❖\")}`,\n text: chalk.cyan.bold,\n content: chalk.white,\n prependLevel: false,\n },\n warn: {\n prefix: `${chalk.yellow.bold(\"⚠\")} `,\n text: chalk.yellow.bold,\n content: chalk.yellowBright,\n prependLevel: false,\n },\n error: {\n prefix: `${chalk.red.bold(\"⨯\")} `,\n text: chalk.red.bold,\n content: chalk.redBright,\n prependLevel: true,\n },\n fatal: {\n prefix: `${chalk.red.bold(\"⨯\")} `,\n text: chalk.red.bold,\n content: chalk.redBright,\n prependLevel: true,\n },\n} satisfies Record<\n LogLevel,\n {\n prefix: string;\n text: typeof chalk.bold;\n content: typeof chalk;\n prependLevel: boolean;\n }\n>;\n\nconst consoleMethods = {\n info: \"log\",\n warn: \"warn\",\n error: \"error\",\n fatal: \"error\",\n} satisfies Record<LogLevel, string>;\n\nfunction isTestEnv() {\n return Boolean(process.env[\"VITEST\"] || process.env[\"VITEST_WORKER_ID\"]);\n}\n\nfunction emit(level: LogLevel, title: string, lines: string[]) {\n if (isTestEnv()) {\n return;\n }\n\n const style = styles[level];\n const method = consoleMethods[level] as keyof Console;\n const fn = globalThis.console[method];\n if (typeof fn !== \"function\") {\n return;\n }\n\n const invoke = (...args: unknown[]) =>\n Reflect.apply(fn, globalThis.console, args);\n\n const prependLevel = style.prependLevel\n ? `[${style.text(level.toUpperCase())}] `\n : \"\";\n\n invoke(`${prependLevel} ${chalk.white(\"Interfere →\")} ${style.text(title)}`);\n\n for (const [i, line] of lines.entries()) {\n const connector = i === lines.length - 1 ? \"└\" : \"├\";\n invoke(`${prependLevel} ${connector} ${style.content(line)}`);\n }\n}\n\nexport const log = {\n info: (title: string, lines: string[]) => emit(\"info\", title, lines),\n warn: (title: string, lines: string[]) => emit(\"warn\", title, lines),\n error: (title: string, lines: string[]) => emit(\"error\", title, lines),\n fatal(title: string, lines: string[]): never {\n emit(\"fatal\", title, lines);\n\n if (isTestEnv()) {\n throw new
|
|
1
|
+
{"version":3,"file":"logger.mjs","names":[],"sources":["../../src/internal/logger.ts"],"sourcesContent":["import chalk from \"chalk\";\n\ntype LogLevel = \"info\" | \"warn\" | \"error\" | \"fatal\";\n\nconst styles = {\n info: {\n prefix: `${chalk.whiteBright.bold(\"❖\")}`,\n text: chalk.cyan.bold,\n content: chalk.white,\n prependLevel: false,\n },\n warn: {\n prefix: `${chalk.yellow.bold(\"⚠\")} `,\n text: chalk.yellow.bold,\n content: chalk.yellowBright,\n prependLevel: false,\n },\n error: {\n prefix: `${chalk.red.bold(\"⨯\")} `,\n text: chalk.red.bold,\n content: chalk.redBright,\n prependLevel: true,\n },\n fatal: {\n prefix: `${chalk.red.bold(\"⨯\")} `,\n text: chalk.red.bold,\n content: chalk.redBright,\n prependLevel: true,\n },\n} satisfies Record<\n LogLevel,\n {\n prefix: string;\n text: typeof chalk.bold;\n content: typeof chalk;\n prependLevel: boolean;\n }\n>;\n\nconst consoleMethods = {\n info: \"log\",\n warn: \"warn\",\n error: \"error\",\n fatal: \"error\",\n} satisfies Record<LogLevel, string>;\n\nfunction isTestEnv() {\n return Boolean(process.env[\"VITEST\"] || process.env[\"VITEST_WORKER_ID\"]);\n}\n\n/**\n * Thrown by `log.fatal` in test environments where `process.exit` is not\n * appropriate. Tagged so callers' catch blocks can re-throw fatal errors and\n * keep test-env behavior aligned with the production hard-exit.\n */\nexport class FatalError extends Error {\n override readonly name = \"FatalError\";\n}\n\nfunction emit(level: LogLevel, title: string, lines: string[]) {\n if (isTestEnv()) {\n return;\n }\n\n const style = styles[level];\n const method = consoleMethods[level] as keyof Console;\n const fn = globalThis.console[method];\n if (typeof fn !== \"function\") {\n return;\n }\n\n const invoke = (...args: unknown[]) =>\n Reflect.apply(fn, globalThis.console, args);\n\n const prependLevel = style.prependLevel\n ? `[${style.text(level.toUpperCase())}] `\n : \"\";\n\n invoke(`${prependLevel} ${chalk.white(\"Interfere →\")} ${style.text(title)}`);\n\n for (const [i, line] of lines.entries()) {\n const connector = i === lines.length - 1 ? \"└\" : \"├\";\n invoke(`${prependLevel} ${connector} ${style.content(line)}`);\n }\n}\n\nexport const log = {\n info: (title: string, lines: string[]) => emit(\"info\", title, lines),\n warn: (title: string, lines: string[]) => emit(\"warn\", title, lines),\n error: (title: string, lines: string[]) => emit(\"error\", title, lines),\n fatal(title: string, lines: string[]): never {\n emit(\"fatal\", title, lines);\n\n if (isTestEnv()) {\n throw new FatalError(title);\n }\n\n process.exit(1);\n },\n};\n"],"mappings":"yBAIA,MAAM,OAAS,CACb,KAAM,CACJ,OAAQ,GAAG,MAAM,YAAY,KAAK,GAAG,IACrC,KAAM,MAAM,KAAK,KACjB,QAAS,MAAM,MACf,aAAc,EAChB,EACA,KAAM,CACJ,OAAQ,GAAG,MAAM,OAAO,KAAK,GAAG,EAAE,GAClC,KAAM,MAAM,OAAO,KACnB,QAAS,MAAM,aACf,aAAc,EAChB,EACA,MAAO,CACL,OAAQ,GAAG,MAAM,IAAI,KAAK,GAAG,EAAE,GAC/B,KAAM,MAAM,IAAI,KAChB,QAAS,MAAM,UACf,aAAc,EAChB,EACA,MAAO,CACL,OAAQ,GAAG,MAAM,IAAI,KAAK,GAAG,EAAE,GAC/B,KAAM,MAAM,IAAI,KAChB,QAAS,MAAM,UACf,aAAc,EAChB,CACF,EAUM,eAAiB,CACrB,KAAM,MACN,KAAM,OACN,MAAO,QACP,MAAO,OACT,EAEA,SAAS,WAAY,CACnB,MAAO,GAAQ,QAAQ,IAAI,QAAa,QAAQ,IAAI,iBACtD,CAOA,IAAa,WAAb,cAAgC,KAAM,CACpC,KAAyB,YAC3B,EAEA,SAAS,KAAK,MAAiB,MAAe,MAAiB,CAC7D,GAAI,UAAU,EACZ,OAGF,IAAM,MAAQ,OAAO,OACf,OAAS,eAAe,OACxB,GAAK,WAAW,QAAQ,QAC9B,GAAI,OAAO,IAAO,WAChB,OAGF,IAAM,QAAU,GAAG,OACjB,QAAQ,MAAM,GAAI,WAAW,QAAS,IAAI,EAEtC,aAAe,MAAM,aACvB,IAAI,MAAM,KAAK,MAAM,YAAY,CAAC,EAAE,IACpC,GAEJ,OAAO,GAAG,aAAa,GAAG,MAAM,MAAM,aAAa,EAAE,GAAG,MAAM,KAAK,KAAK,GAAG,EAE3E,IAAK,GAAM,CAAC,EAAG,QAAS,MAAM,QAAQ,EAEpC,OAAO,GAAG,aAAa,GADL,IAAM,MAAM,OAAS,EAAI,IAAM,IACb,GAAG,MAAM,QAAQ,IAAI,GAAG,CAEhE,CAEA,MAAa,IAAM,CACjB,MAAO,MAAe,QAAoB,KAAK,OAAQ,MAAO,KAAK,EACnE,MAAO,MAAe,QAAoB,KAAK,OAAQ,MAAO,KAAK,EACnE,OAAQ,MAAe,QAAoB,KAAK,QAAS,MAAO,KAAK,EACrE,MAAM,MAAe,MAAwB,CAG3C,GAFA,KAAK,QAAS,MAAO,KAAK,EAEtB,UAAU,EACZ,MAAM,IAAI,WAAW,KAAK,EAG5B,QAAQ,KAAK,CAAC,CAChB,CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ReleaseSlug } from "@interfere/types/releases/slug";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/release-slug.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Walks the same env keys at build time (`withInterfere`) and at runtime
|
|
6
|
+
* (server-side `register()`), then falls back to `git rev-parse HEAD`. Both
|
|
7
|
+
* call sites resolving the same SHA → both derive the same `release.slug`,
|
|
8
|
+
* so server and client spans agree by construction.
|
|
9
|
+
*
|
|
10
|
+
* Override path for non-CI builds: set `INTERFERE_SOURCE_ID` (or any other
|
|
11
|
+
* key in `releaseSourceIdEnvKeys`) on both the build env and the runtime
|
|
12
|
+
* env. The `interfere.buildId` next.config knob was removed in 10.0 because
|
|
13
|
+
* it only worked at build time and caused server/client slug drift.
|
|
14
|
+
*
|
|
15
|
+
* `runGitCommand` (`node:child_process`) keeps this module on the Node side
|
|
16
|
+
* of `@interfere/next`'s dual entry — the edge entrypoint
|
|
17
|
+
* (`instrumentation.edge.ts`) intentionally doesn't import this file.
|
|
18
|
+
*/
|
|
19
|
+
declare function resolveCommitSha(): string | null;
|
|
20
|
+
declare function resolveReleaseSlug(): {
|
|
21
|
+
commitSha: string | null;
|
|
22
|
+
slug: ReleaseSlug | null;
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { resolveCommitSha, resolveReleaseSlug };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-slug.d.mts","names":[],"sources":["../../src/internal/release-slug.ts"],"mappings":";;;;;AAwBA;;;;AAAgC;AAOhC;;;;;;;;iBAPgB,gBAAA,CAAA;AAAA,iBAOA,kBAAA,CAAA;EACd,SAAA;EACA,IAAA,EAAM,WAAW;AAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{runGitCommand}from"./build/release/git.mjs";import{deriveReleaseSlug}from"@interfere/types/releases/slug";import{readFirstEnvValue}from"@interfere/types/sdk/env";import{releaseSourceIdEnvKeys}from"@interfere/types/integrations";function resolveCommitSha(){return readFirstEnvValue(process.env,releaseSourceIdEnvKeys)??runGitCommand(`git rev-parse HEAD`)}function resolveReleaseSlug(){let commitSha=resolveCommitSha();return{commitSha,slug:commitSha?deriveReleaseSlug(commitSha):null}}export{resolveCommitSha,resolveReleaseSlug};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-slug.mjs","names":[],"sources":["../../src/internal/release-slug.ts"],"sourcesContent":["import { releaseSourceIdEnvKeys } from \"@interfere/types/integrations\";\nimport {\n deriveReleaseSlug,\n type ReleaseSlug,\n} from \"@interfere/types/releases/slug\";\nimport { readFirstEnvValue } from \"@interfere/types/sdk/env\";\n\nimport { runGitCommand } from \"./build/release/git.js\";\n\n/**\n * Walks the same env keys at build time (`withInterfere`) and at runtime\n * (server-side `register()`), then falls back to `git rev-parse HEAD`. Both\n * call sites resolving the same SHA → both derive the same `release.slug`,\n * so server and client spans agree by construction.\n *\n * Override path for non-CI builds: set `INTERFERE_SOURCE_ID` (or any other\n * key in `releaseSourceIdEnvKeys`) on both the build env and the runtime\n * env. The `interfere.buildId` next.config knob was removed in 10.0 because\n * it only worked at build time and caused server/client slug drift.\n *\n * `runGitCommand` (`node:child_process`) keeps this module on the Node side\n * of `@interfere/next`'s dual entry — the edge entrypoint\n * (`instrumentation.edge.ts`) intentionally doesn't import this file.\n */\nexport function resolveCommitSha(): string | null {\n return (\n readFirstEnvValue(process.env, releaseSourceIdEnvKeys) ??\n runGitCommand(\"git rev-parse HEAD\")\n );\n}\n\nexport function resolveReleaseSlug(): {\n commitSha: string | null;\n slug: ReleaseSlug | null;\n} {\n const commitSha = resolveCommitSha();\n return {\n commitSha,\n slug: commitSha ? deriveReleaseSlug(commitSha) : null,\n };\n}\n"],"mappings":"2OAwBA,SAAgB,kBAAkC,CAChD,OACE,kBAAkB,QAAQ,IAAK,sBAAsB,GACrD,cAAc,oBAAoB,CAEtC,CAEA,SAAgB,oBAGd,CACA,IAAM,UAAY,iBAAiB,EACnC,MAAO,CACL,UACA,KAAM,UAAY,kBAAkB,SAAS,EAAI,IACnD,CACF"}
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
//#region src/internal/route/handle-get.d.ts
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Generic GET proxy. SDK 10.x routes `GET /v2/config` (and any future
|
|
4
|
+
* GET-based endpoints) through here; SDK 9.x clients still hit
|
|
5
|
+
* `GET /v1/config` and get the same delegated response on the collector
|
|
6
|
+
* side.
|
|
7
|
+
*
|
|
8
|
+
* `/sw` is special-cased to serve the SDK's bundled service-worker
|
|
9
|
+
* script directly out of the customer's Next.js process. `SW_SCRIPT`
|
|
10
|
+
* is a string export emitted at @interfere/react build time
|
|
11
|
+
* (`scripts/build-sw.ts`); on Node runtime the customer resolves it
|
|
12
|
+
* via `node_modules`, on Edge runtime the customer's Next.js bundler
|
|
13
|
+
* inlines it into the Edge worker. Either way, no `fs.readFileSync`.
|
|
14
|
+
*/
|
|
15
|
+
declare function handleGet(request: Request): Promise<Response>;
|
|
3
16
|
//#endregion
|
|
4
17
|
export { handleGet };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-get.d.mts","names":[],"sources":["../../../src/internal/route/handle-get.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"handle-get.d.mts","names":[],"sources":["../../../src/internal/route/handle-get.ts"],"mappings":";;AAqCA;;;;;;;;;;;;iBAAsB,SAAA,CAAU,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,QAAA"}
|
|
@@ -1,22 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { SW_SCRIPT } from "./sw-script.mjs";
|
|
3
|
-
import { API_PATHS } from "@interfere/constants/api";
|
|
4
|
-
//#region src/internal/route/handle-get.ts
|
|
5
|
-
function handleGet(request) {
|
|
6
|
-
const subPath = extractSubPath(request);
|
|
7
|
-
if (subPath.endsWith("/sw")) return new Response(SW_SCRIPT, {
|
|
8
|
-
status: 200,
|
|
9
|
-
headers: {
|
|
10
|
-
"content-type": "application/javascript",
|
|
11
|
-
"cache-control": "public, max-age=3600"
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
if (subPath === API_PATHS.CONFIG) {
|
|
15
|
-
const env = resolveAuthenticatedEnv();
|
|
16
|
-
if (!env) return notConfiguredResponse();
|
|
17
|
-
return forwardToCollector(request, env, subPath);
|
|
18
|
-
}
|
|
19
|
-
return new Response("Not Found", { status: 404 });
|
|
20
|
-
}
|
|
21
|
-
//#endregion
|
|
22
|
-
export { handleGet };
|
|
1
|
+
import{log}from"../logger.mjs";import{extractSubPath,formatProxyError,forwardToCollector,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv}from"./proxy.mjs";import{SW_SCRIPT}from"@interfere/react/sw";const SW_HEADERS={"content-type":`application/javascript; charset=utf-8`,"service-worker-allowed":`/`,"cache-control":`public, max-age=3600`};async function handleGet(request){let subPath=extractSubPath(request);if(subPath===`/sw`)return new Response(SW_SCRIPT,{status:200,headers:SW_HEADERS});let env=resolveAuthenticatedEnv();if(!hasPublicKeyCredential(request,env.publicKey))return notConfiguredResponse();try{return await forwardToCollector(request,env,subPath)}catch(error){let detail=formatProxyError(error);return log.error(`Proxy ${request.method} ${subPath} failed`,detail.lines),Response.json({code:`INTERFERE_PROXY_ERROR`,message:detail.message},{status:502})}}export{handleGet};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-get.mjs","names":[],"sources":["../../../src/internal/route/handle-get.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"handle-get.mjs","names":[],"sources":["../../../src/internal/route/handle-get.ts"],"sourcesContent":["import { SW_SCRIPT } from \"@interfere/react/sw\";\n\nimport { log } from \"../logger.js\";\nimport {\n extractSubPath,\n formatProxyError,\n forwardToCollector,\n hasPublicKeyCredential,\n notConfiguredResponse,\n resolveAuthenticatedEnv,\n} from \"./proxy.js\";\n\nconst SW_HEADERS: Record<string, string> = {\n \"content-type\": \"application/javascript; charset=utf-8\",\n // Allow the SW to control the whole origin, not just `/api/interfere/`.\n // The SDK still scopes its `register()` call narrowly; this header just\n // unlocks the option without re-issuing a new SW URL.\n \"service-worker-allowed\": \"/\",\n // Customers' edges (Vercel, Cloudflare) cache aggressively by default;\n // a 1-hour TTL balances \"pick up SW updates within a deploy cycle\"\n // against \"don't refetch on every navigation\".\n \"cache-control\": \"public, max-age=3600\",\n};\n\n/**\n * Generic GET proxy. SDK 10.x routes `GET /v2/config` (and any future\n * GET-based endpoints) through here; SDK 9.x clients still hit\n * `GET /v1/config` and get the same delegated response on the collector\n * side.\n *\n * `/sw` is special-cased to serve the SDK's bundled service-worker\n * script directly out of the customer's Next.js process. `SW_SCRIPT`\n * is a string export emitted at @interfere/react build time\n * (`scripts/build-sw.ts`); on Node runtime the customer resolves it\n * via `node_modules`, on Edge runtime the customer's Next.js bundler\n * inlines it into the Edge worker. Either way, no `fs.readFileSync`.\n */\nexport async function handleGet(request: Request): Promise<Response> {\n const subPath = extractSubPath(request);\n\n if (subPath === \"/sw\") {\n return new Response(SW_SCRIPT, { status: 200, headers: SW_HEADERS });\n }\n\n const env = resolveAuthenticatedEnv();\n if (!hasPublicKeyCredential(request, env.publicKey)) {\n return notConfiguredResponse();\n }\n try {\n return await forwardToCollector(request, env, subPath);\n } catch (error) {\n const detail = formatProxyError(error);\n log.error(`Proxy ${request.method} ${subPath} failed`, detail.lines);\n return Response.json(\n { code: \"INTERFERE_PROXY_ERROR\", message: detail.message },\n { status: 502 }\n );\n }\n}\n"],"mappings":"2NAYA,MAAM,WAAqC,CACzC,eAAgB,wCAIhB,yBAA0B,IAI1B,gBAAiB,sBACnB,EAeA,eAAsB,UAAU,QAAqC,CACnE,IAAM,QAAU,eAAe,OAAO,EAEtC,GAAI,UAAY,MACd,OAAO,IAAI,SAAS,UAAW,CAAE,OAAQ,IAAK,QAAS,UAAW,CAAC,EAGrE,IAAM,IAAM,wBAAwB,EACpC,GAAI,CAAC,uBAAuB,QAAS,IAAI,SAAS,EAChD,OAAO,sBAAsB,EAE/B,GAAI,CACF,OAAO,MAAM,mBAAmB,QAAS,IAAK,OAAO,CACvD,OAAS,MAAO,CACd,IAAM,OAAS,iBAAiB,KAAK,EAErC,OADA,IAAI,MAAM,SAAS,QAAQ,OAAO,GAAG,QAAQ,SAAU,OAAO,KAAK,EAC5D,SAAS,KACd,CAAE,KAAM,wBAAyB,QAAS,OAAO,OAAQ,EACzD,CAAE,OAAQ,GAAI,CAChB,CACF,CACF"}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
//#region src/internal/route/handle-post.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Generic POST proxy. SDK 10.x routes everything through here:
|
|
4
|
+
*
|
|
5
|
+
* - `/v2/sink` (OTLP traces / metrics / logs)
|
|
6
|
+
* - `/v2/replay/upload/:sessionId`
|
|
7
|
+
* - `/v1/session*` (session sync + identify)
|
|
8
|
+
*
|
|
9
|
+
* No envelope-aware special-casing — that endpoint (`/v1/ingest`) was
|
|
10
|
+
* 9.x-only and is not produced by 10.x. Customers on 9.x post directly
|
|
11
|
+
* to the collector through the same proxy without touching this file.
|
|
12
|
+
*/
|
|
2
13
|
declare function handlePost(request: Request): Promise<Response>;
|
|
3
14
|
//#endregion
|
|
4
15
|
export { handlePost };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-post.d.mts","names":[],"sources":["../../../src/internal/route/handle-post.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"handle-post.d.mts","names":[],"sources":["../../../src/internal/route/handle-post.ts"],"mappings":";;AAqBA;;;;;;;;;;iBAAsB,UAAA,CAAW,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,QAAA"}
|
|
@@ -1,70 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { extractSubPath, formatProxyError, forwardToCollector, notConfiguredResponse, resolveAuthenticatedEnv } from "./proxy.mjs";
|
|
3
|
-
import { API_PATHS } from "@interfere/constants/api";
|
|
4
|
-
import { extractCountryHeader } from "@interfere/types/sdk/geo";
|
|
5
|
-
//#region src/internal/route/handle-post.ts
|
|
6
|
-
function parseEnvelopes(value) {
|
|
7
|
-
if (!Array.isArray(value)) return null;
|
|
8
|
-
return value;
|
|
9
|
-
}
|
|
10
|
-
function injectReleaseMetadata(envelopes, metadata) {
|
|
11
|
-
if (metadata.sourceId === null && metadata.destinationId === null) return envelopes;
|
|
12
|
-
return envelopes.map((envelope) => ({
|
|
13
|
-
...envelope,
|
|
14
|
-
buildId: metadata.sourceId ?? envelope.buildId,
|
|
15
|
-
releaseId: metadata.destinationId ?? envelope.releaseId
|
|
16
|
-
}));
|
|
17
|
-
}
|
|
18
|
-
async function handlePost(request) {
|
|
19
|
-
const env = resolveAuthenticatedEnv();
|
|
20
|
-
if (!env) return notConfiguredResponse();
|
|
21
|
-
const subPath = extractSubPath(request);
|
|
22
|
-
try {
|
|
23
|
-
if (subPath === API_PATHS.INGEST) return await handleIngest(request, env);
|
|
24
|
-
return await forwardToCollector(request, env, subPath);
|
|
25
|
-
} catch (error) {
|
|
26
|
-
const detail = formatProxyError(error);
|
|
27
|
-
log.error(`Proxy ${request.method} ${subPath} failed`, detail.lines);
|
|
28
|
-
return Response.json({
|
|
29
|
-
code: "INTERFERE_PROXY_ERROR",
|
|
30
|
-
message: detail.message
|
|
31
|
-
}, { status: 502 });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
async function handleIngest(request, env) {
|
|
35
|
-
let payload;
|
|
36
|
-
try {
|
|
37
|
-
payload = await request.json();
|
|
38
|
-
} catch {
|
|
39
|
-
return Response.json({
|
|
40
|
-
code: "INTERFERE_INVALID_JSON",
|
|
41
|
-
message: "Request body must be valid JSON."
|
|
42
|
-
}, { status: 400 });
|
|
43
|
-
}
|
|
44
|
-
const envelopes = parseEnvelopes(payload);
|
|
45
|
-
if (envelopes === null) return Response.json({
|
|
46
|
-
code: "INTERFERE_INVALID_ENVELOPES",
|
|
47
|
-
message: "Request body must be an array of envelopes."
|
|
48
|
-
}, { status: 400 });
|
|
49
|
-
const traceparent = request.headers.get("traceparent");
|
|
50
|
-
const upstream = await fetch(`${env.apiUrl}${API_PATHS.INGEST}`, {
|
|
51
|
-
method: "POST",
|
|
52
|
-
headers: {
|
|
53
|
-
"content-type": "application/json",
|
|
54
|
-
"x-api-key": env.apiKey,
|
|
55
|
-
...traceparent ? { traceparent } : {},
|
|
56
|
-
...(() => {
|
|
57
|
-
const country = extractCountryHeader(request);
|
|
58
|
-
return country ? { "x-country-code": country } : {};
|
|
59
|
-
})()
|
|
60
|
-
},
|
|
61
|
-
body: JSON.stringify(injectReleaseMetadata(envelopes, env.release)),
|
|
62
|
-
signal: AbortSignal.timeout(1e4)
|
|
63
|
-
});
|
|
64
|
-
return new Response(upstream.body, {
|
|
65
|
-
status: upstream.status,
|
|
66
|
-
headers: { "content-type": upstream.headers.get("content-type") ?? "application/json" }
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
//#endregion
|
|
70
|
-
export { handlePost };
|
|
1
|
+
import{log}from"../logger.mjs";import{extractSubPath,formatProxyError,forwardToCollector,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv}from"./proxy.mjs";async function handlePost(request){let env=resolveAuthenticatedEnv();if(!hasPublicKeyCredential(request,env.publicKey))return notConfiguredResponse();let subPath=extractSubPath(request);try{return await forwardToCollector(request,env,subPath)}catch(error){let detail=formatProxyError(error);return log.error(`Proxy ${request.method} ${subPath} failed`,detail.lines),Response.json({code:`INTERFERE_PROXY_ERROR`,message:detail.message},{status:502})}}export{handlePost};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-post.mjs","names":[],"sources":["../../../src/internal/route/handle-post.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"handle-post.mjs","names":[],"sources":["../../../src/internal/route/handle-post.ts"],"sourcesContent":["import { log } from \"../logger.js\";\nimport {\n extractSubPath,\n formatProxyError,\n forwardToCollector,\n hasPublicKeyCredential,\n notConfiguredResponse,\n resolveAuthenticatedEnv,\n} from \"./proxy.js\";\n\n/**\n * Generic POST proxy. SDK 10.x routes everything through here:\n *\n * - `/v2/sink` (OTLP traces / metrics / logs)\n * - `/v2/replay/upload/:sessionId`\n * - `/v1/session*` (session sync + identify)\n *\n * No envelope-aware special-casing — that endpoint (`/v1/ingest`) was\n * 9.x-only and is not produced by 10.x. Customers on 9.x post directly\n * to the collector through the same proxy without touching this file.\n */\nexport async function handlePost(request: Request): Promise<Response> {\n const env = resolveAuthenticatedEnv();\n if (!hasPublicKeyCredential(request, env.publicKey)) {\n return notConfiguredResponse();\n }\n const subPath = extractSubPath(request);\n try {\n return await forwardToCollector(request, env, subPath);\n } catch (error) {\n const detail = formatProxyError(error);\n log.error(`Proxy ${request.method} ${subPath} failed`, detail.lines);\n return Response.json(\n { code: \"INTERFERE_PROXY_ERROR\", message: detail.message },\n { status: 502 }\n );\n }\n}\n"],"mappings":"gLAqBA,eAAsB,WAAW,QAAqC,CACpE,IAAM,IAAM,wBAAwB,EACpC,GAAI,CAAC,uBAAuB,QAAS,IAAI,SAAS,EAChD,OAAO,sBAAsB,EAE/B,IAAM,QAAU,eAAe,OAAO,EACtC,GAAI,CACF,OAAO,MAAM,mBAAmB,QAAS,IAAK,OAAO,CACvD,OAAS,MAAO,CACd,IAAM,OAAS,iBAAiB,KAAK,EAErC,OADA,IAAI,MAAM,SAAS,QAAQ,OAAO,GAAG,QAAQ,SAAU,OAAO,KAAK,EAC5D,SAAS,KACd,CAAE,KAAM,wBAAyB,QAAS,OAAO,OAAQ,EACzD,CAAE,OAAQ,GAAI,CAChB,CACF,CACF"}
|
|
@@ -1,18 +1,40 @@
|
|
|
1
1
|
import { InterfereEnv } from "../env.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/internal/route/proxy.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Clone the inbound request's headers, removing entries that should never
|
|
6
|
+
* cross the proxy boundary. Returns a mutable `Headers` so callers can
|
|
7
|
+
* `.set()`/`.delete()` upstream-specific values without having to merge
|
|
8
|
+
* record literals (which lose case-insensitivity and create the precedence
|
|
9
|
+
* footguns that masked the 2026-05-01 incident in code review).
|
|
10
|
+
*/
|
|
11
|
+
declare function forwardedHeaders(request: Request): Headers;
|
|
4
12
|
interface AuthenticatedEnv {
|
|
5
|
-
apiKey: string;
|
|
6
13
|
apiUrl: string;
|
|
14
|
+
publicKey: string | null;
|
|
7
15
|
release: InterfereEnv["release"];
|
|
8
16
|
}
|
|
9
|
-
declare function resolveAuthenticatedEnv(): AuthenticatedEnv
|
|
10
|
-
|
|
17
|
+
declare function resolveAuthenticatedEnv(): AuthenticatedEnv;
|
|
18
|
+
/** True when the proxy can attach a publishable key from env or the request. */
|
|
19
|
+
declare function hasPublicKeyCredential(request: Request, envPublicKey: string | null): boolean;
|
|
11
20
|
declare function notConfiguredResponse(): Response;
|
|
21
|
+
declare function extractSubPath(request: Request): string;
|
|
22
|
+
/**
|
|
23
|
+
* Preserve any query string verbatim when forwarding upstream.
|
|
24
|
+
*
|
|
25
|
+
* The browser SDK uses `?_pv=…` and `?pk=…` (and `?_pt=…` in direct-ingestion
|
|
26
|
+
* mode) as a `navigator.sendBeacon` fallback when `visibilitychange→
|
|
27
|
+
* hidden` aborts the keepalive fetch path. The collector's
|
|
28
|
+
* `otlpProducerGuard` and `surfacePkAuth` accept those values verbatim
|
|
29
|
+
* — but only if the proxy actually forwards them. Passing
|
|
30
|
+
* `request.url`'s `search` through (`""` when absent, leading `?`
|
|
31
|
+
* when present) keeps the rewrite transparent.
|
|
32
|
+
*/
|
|
33
|
+
declare function extractSearch(request: Request): string;
|
|
12
34
|
declare function formatProxyError(error: unknown): {
|
|
13
35
|
message: string;
|
|
14
36
|
lines: string[];
|
|
15
37
|
};
|
|
16
38
|
declare function forwardToCollector(request: Request, env: AuthenticatedEnv, subPath: string): Promise<Response>;
|
|
17
39
|
//#endregion
|
|
18
|
-
export { AuthenticatedEnv, extractSubPath, formatProxyError, forwardToCollector, notConfiguredResponse, resolveAuthenticatedEnv };
|
|
40
|
+
export { AuthenticatedEnv, extractSearch, extractSubPath, formatProxyError, forwardToCollector, forwardedHeaders, hasPublicKeyCredential, notConfiguredResponse, resolveAuthenticatedEnv };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.mts","names":[],"sources":["../../../src/internal/route/proxy.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"proxy.d.mts","names":[],"sources":["../../../src/internal/route/proxy.ts"],"mappings":";;;;;AAuDA;;;;;iBAAgB,gBAAA,CAAiB,OAAA,EAAS,OAAA,GAAU,OAAO;AAAA,UAW1C,gBAAA;EACf,MAAA;EACA,SAAA;EACA,OAAA,EAAS,YAAY;AAAA;AAAA,iBAGP,uBAAA,CAAA,GAA2B,gBAAgB;;iBAU3C,sBAAA,CACd,OAAA,EAAS,OAAO,EAChB,YAAA;AAAA,iBAQc,qBAAA,CAAA,GAAyB,QAAQ;AAAA,iBAajC,cAAA,CAAe,OAAgB,EAAP,OAAO;;;AApCxB;AAGvB;;;;AAA2D;AAU3D;;;iBAgDgB,aAAA,CAAc,OAAgB,EAAP,OAAO;AAAA,iBAI9B,gBAAA,CAAiB,KAAA;EAC/B,OAAA;EACA,KAAA;AAAA;AAAA,iBAqDoB,kBAAA,CACpB,OAAA,EAAS,OAAA,EACT,GAAA,EAAK,gBAAA,EACL,OAAA,WACC,OAAA,CAAQ,QAAA"}
|
|
@@ -1,89 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { readInterfereEnv } from "../env.mjs";
|
|
3
|
-
import { omitUndefined } from "@interfere/helpers/omit-undefined";
|
|
4
|
-
import { extractCountryHeader } from "@interfere/types/sdk/geo";
|
|
5
|
-
//#region src/internal/route/proxy.ts
|
|
6
|
-
const PROXY_PATH_PATTERN = /\/api\/interfere(\/.*)/;
|
|
7
|
-
function geoHeaders(request) {
|
|
8
|
-
const country = extractCountryHeader(request);
|
|
9
|
-
return country ? { "x-country-code": country } : {};
|
|
10
|
-
}
|
|
11
|
-
function resolveAuthenticatedEnv() {
|
|
12
|
-
const env = readInterfereEnv();
|
|
13
|
-
if (env.apiKey === null) return null;
|
|
14
|
-
return {
|
|
15
|
-
apiKey: env.apiKey,
|
|
16
|
-
apiUrl: env.apiUrl,
|
|
17
|
-
release: env.release
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
function extractSubPath(request) {
|
|
21
|
-
return new URL(request.url).pathname.match(PROXY_PATH_PATTERN)?.[1] ?? "/";
|
|
22
|
-
}
|
|
23
|
-
function notConfiguredResponse() {
|
|
24
|
-
log.warn("Not configured", ["INTERFERE_API_KEY is not set. The proxy route will return 503."]);
|
|
25
|
-
return Response.json({
|
|
26
|
-
code: "INTERFERE_NOT_CONFIGURED",
|
|
27
|
-
message: "INTERFERE_API_KEY is required."
|
|
28
|
-
}, { status: 503 });
|
|
29
|
-
}
|
|
30
|
-
function formatProxyError(error) {
|
|
31
|
-
if (!(error instanceof Error)) return {
|
|
32
|
-
message: String(error),
|
|
33
|
-
lines: [String(error)]
|
|
34
|
-
};
|
|
35
|
-
const lines = [`${error.name}: ${error.message}`];
|
|
36
|
-
if ("cause" in error && error.cause) {
|
|
37
|
-
const cause = error.cause instanceof Error ? error.cause.message : String(error.cause);
|
|
38
|
-
lines.push(`cause: ${cause}`);
|
|
39
|
-
}
|
|
40
|
-
if ("code" in error && typeof error.code === "string") lines.push(`code: ${error.code}`);
|
|
41
|
-
return {
|
|
42
|
-
message: `${error.name}: ${error.message}`,
|
|
43
|
-
lines
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
async function forwardToCollector(request, env, subPath) {
|
|
47
|
-
const url = `${env.apiUrl}${subPath}`;
|
|
48
|
-
const traceparent = request.headers.get("traceparent");
|
|
49
|
-
const body = request.method !== "GET" && request.method !== "HEAD" ? await request.text() : void 0;
|
|
50
|
-
const upstream = await fetch(url, {
|
|
51
|
-
...omitUndefined({ body }),
|
|
52
|
-
method: request.method,
|
|
53
|
-
headers: {
|
|
54
|
-
"content-type": request.headers.get("content-type") ?? "application/json",
|
|
55
|
-
"x-api-key": env.apiKey,
|
|
56
|
-
...traceparent ? { traceparent } : {},
|
|
57
|
-
...geoHeaders(request)
|
|
58
|
-
},
|
|
59
|
-
signal: AbortSignal.timeout(1e4)
|
|
60
|
-
});
|
|
61
|
-
if (!upstream.ok) {
|
|
62
|
-
const text = await upstream.text().catch(() => "");
|
|
63
|
-
if (upstream.status >= 500) {
|
|
64
|
-
log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [text]);
|
|
65
|
-
return Response.json({
|
|
66
|
-
code: "INTERFERE_UPSTREAM_ERROR",
|
|
67
|
-
message: text
|
|
68
|
-
}, { status: upstream.status });
|
|
69
|
-
}
|
|
70
|
-
if (upstream.status >= 400) {
|
|
71
|
-
log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [text]);
|
|
72
|
-
return Response.json({
|
|
73
|
-
code: "INTERFERE_UPSTREAM_WARNING",
|
|
74
|
-
message: text
|
|
75
|
-
}, { status: upstream.status });
|
|
76
|
-
}
|
|
77
|
-
log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [text]);
|
|
78
|
-
return Response.json({
|
|
79
|
-
code: "INTERFERE_UPSTREAM_ERROR",
|
|
80
|
-
message: text
|
|
81
|
-
}, { status: upstream.status });
|
|
82
|
-
}
|
|
83
|
-
return new Response(upstream.body, {
|
|
84
|
-
status: upstream.status,
|
|
85
|
-
headers: { "content-type": upstream.headers.get("content-type") ?? "application/json" }
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
//#endregion
|
|
89
|
-
export { extractSubPath, formatProxyError, forwardToCollector, notConfiguredResponse, resolveAuthenticatedEnv };
|
|
1
|
+
import{readInterfereEnv}from"../env.mjs";import{log}from"../logger.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{resolveRoutePrefix}from"@interfere/constants/route-prefix";import{extractCountryHeader}from"@interfere/types/sdk/geo";const STRIPPED_AUTH_HEADERS=[`authorization`,`x-api-key`,`x-interfere-api-key`],STRIPPED_TRANSPORT_HEADERS=[`host`,`content-length`,`connection`,`keep-alive`,`transfer-encoding`,`te`,`trailer`,`upgrade`,`proxy-authenticate`,`proxy-authorization`];function forwardedHeaders(request){let headers=new Headers(request.headers);for(let name of STRIPPED_AUTH_HEADERS)headers.delete(name);for(let name of STRIPPED_TRANSPORT_HEADERS)headers.delete(name);return headers}function resolveAuthenticatedEnv(){let env=readInterfereEnv();return{apiUrl:env.apiUrl,publicKey:env.publicKey,release:env.release}}function hasPublicKeyCredential(request,envPublicKey){return envPublicKey?!0:new URL(request.url).searchParams.has(`pk`)}function notConfiguredResponse(){return log.warn(`Not configured`,[`INTERFERE_PUBLIC_KEY is not set and the request has no ?pk= query param. The proxy route will return 503.`]),Response.json({code:`INTERFERE_NOT_CONFIGURED`,message:`INTERFERE_PUBLIC_KEY is required.`},{status:503})}function extractSubPath(request){let prefix=resolveRoutePrefix(),url=new URL(request.url),idx=url.pathname.indexOf(prefix);if(idx===-1)return`/`;let remainder=url.pathname.slice(idx+prefix.length);return remainder===``?`/`:remainder.startsWith(`/`)?remainder:`/${remainder}`}function extractSearch(request){return new URL(request.url).search}function formatProxyError(error){if(!(error instanceof Error))return{message:String(error),lines:[String(error)]};let lines=[`${error.name}: ${error.message}`];if(`cause`in error&&error.cause){let cause=error.cause instanceof Error?error.cause.message:String(error.cause);lines.push(`cause: ${cause}`)}return`code`in error&&typeof error.code==`string`&&lines.push(`code: ${error.code}`),{message:`${error.name}: ${error.message}`,lines}}function buildUpstreamUrl(apiUrl,subPath,request,publicKey){let incoming=new URL(request.url),upstream=new URL(`${apiUrl}${subPath}`);for(let[key,value]of incoming.searchParams)upstream.searchParams.append(key,value);return publicKey&&!upstream.searchParams.has(`pk`)&&upstream.searchParams.set(`pk`,publicKey),upstream.toString()}async function forwardToCollector(request,env,subPath){let url=buildUpstreamUrl(env.apiUrl,subPath,request,env.publicKey),body=request.method!==`GET`&&request.method!==`HEAD`?await request.text():void 0,headers=forwardedHeaders(request);headers.has(`content-type`)||headers.set(`content-type`,`application/json`);let country=extractCountryHeader(request);country!==null&&headers.set(`x-country-code`,country);let upstream=await fetch(url,{...omitUndefined({body}),method:request.method,headers,signal:AbortSignal.timeout(1e4)});if(!upstream.ok){let text=await upstream.text().catch(()=>``);return upstream.status>=500?(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status})):upstream.status>=400?(log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_WARNING`,message:text},{status:upstream.status})):(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status}))}return new Response(upstream.body,{status:upstream.status,headers:{"content-type":upstream.headers.get(`content-type`)??`application/json`}})}export{extractSearch,extractSubPath,formatProxyError,forwardToCollector,forwardedHeaders,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.mjs","names":[],"sources":["../../../src/internal/route/proxy.ts"],"sourcesContent":["import { omitUndefined } from \"@interfere/helpers/omit-undefined\";\nimport { extractCountryHeader } from \"@interfere/types/sdk/geo\";\n\nimport { type InterfereEnv, readInterfereEnv } from \"../env.js\";\nimport { log } from \"../logger.js\";\n\nconst PROXY_PATH_PATTERN = /\\/api\\/interfere(\\/.*)/;\n\nfunction geoHeaders(request: Request): Record<string, string> {\n const country = extractCountryHeader(request);\n return country ? { \"x-country-code\": country } : {};\n}\n\nexport interface AuthenticatedEnv {\n apiKey: string;\n apiUrl: string;\n release: InterfereEnv[\"release\"];\n}\n\nexport function resolveAuthenticatedEnv(): AuthenticatedEnv | null {\n const env = readInterfereEnv();\n if (env.apiKey === null) {\n return null;\n }\n return { apiKey: env.apiKey, apiUrl: env.apiUrl, release: env.release };\n}\n\nexport function extractSubPath(request: Request): string {\n const url = new URL(request.url);\n const match = url.pathname.match(PROXY_PATH_PATTERN);\n return match?.[1] ?? \"/\";\n}\n\nexport function notConfiguredResponse(): Response {\n log.warn(\"Not configured\", [\n \"INTERFERE_API_KEY is not set. The proxy route will return 503.\",\n ]);\n return Response.json(\n {\n code: \"INTERFERE_NOT_CONFIGURED\",\n message: \"INTERFERE_API_KEY is required.\",\n },\n { status: 503 }\n );\n}\n\nexport function formatProxyError(error: unknown): {\n message: string;\n lines: string[];\n} {\n if (!(error instanceof Error)) {\n return { message: String(error), lines: [String(error)] };\n }\n\n const lines = [`${error.name}: ${error.message}`];\n\n if (\"cause\" in error && error.cause) {\n const cause =\n error.cause instanceof Error ? error.cause.message : String(error.cause);\n lines.push(`cause: ${cause}`);\n }\n\n if (\n \"code\" in error &&\n typeof (error as NodeJS.ErrnoException).code === \"string\"\n ) {\n lines.push(`code: ${(error as NodeJS.ErrnoException).code}`);\n }\n\n return { message: `${error.name}: ${error.message}`, lines };\n}\n\nexport async function forwardToCollector(\n request: Request,\n env: AuthenticatedEnv,\n subPath: string\n): Promise<Response> {\n const url = `${env.apiUrl}${subPath}`;\n const traceparent = request.headers.get(\"traceparent\");\n const hasBody = request.method !== \"GET\" && request.method !== \"HEAD\";\n const body = hasBody ? await request.text() : undefined;\n\n const upstream = await fetch(url, {\n ...omitUndefined({ body }),\n method: request.method,\n headers: {\n \"content-type\": request.headers.get(\"content-type\") ?? \"application/json\",\n \"x-api-key\": env.apiKey,\n ...(traceparent ? { traceparent } : {}),\n ...geoHeaders(request),\n },\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!upstream.ok) {\n const text = await upstream.text().catch(() => \"\");\n\n if (upstream.status >= 500) {\n log.error(\n `Upstream ${upstream.status} for ${request.method} ${subPath}`,\n [text]\n );\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_ERROR\", message: text },\n { status: upstream.status }\n );\n }\n if (upstream.status >= 400) {\n log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [\n text,\n ]);\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_WARNING\", message: text },\n { status: upstream.status }\n );\n }\n\n log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [\n text,\n ]);\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_ERROR\", message: text },\n { status: upstream.status }\n );\n }\n\n return new Response(upstream.body, {\n status: upstream.status,\n headers: {\n \"content-type\":\n upstream.headers.get(\"content-type\") ?? \"application/json\",\n },\n });\n}\n"],"mappings":";;;;;AAMA,MAAM,qBAAqB;AAE3B,SAAS,WAAW,SAA0C;CAC5D,MAAM,UAAU,qBAAqB,QAAQ;AAC7C,QAAO,UAAU,EAAE,kBAAkB,SAAS,GAAG,EAAE;;AASrD,SAAgB,0BAAmD;CACjE,MAAM,MAAM,kBAAkB;AAC9B,KAAI,IAAI,WAAW,KACjB,QAAO;AAET,QAAO;EAAE,QAAQ,IAAI;EAAQ,QAAQ,IAAI;EAAQ,SAAS,IAAI;EAAS;;AAGzE,SAAgB,eAAe,SAA0B;AAGvD,QADc,IADE,IAAI,QAAQ,IACX,CAAC,SAAS,MAAM,mBACrB,GAAG,MAAM;;AAGvB,SAAgB,wBAAkC;AAChD,KAAI,KAAK,kBAAkB,CACzB,iEACD,CAAC;AACF,QAAO,SAAS,KACd;EACE,MAAM;EACN,SAAS;EACV,EACD,EAAE,QAAQ,KAAK,CAChB;;AAGH,SAAgB,iBAAiB,OAG/B;AACA,KAAI,EAAE,iBAAiB,OACrB,QAAO;EAAE,SAAS,OAAO,MAAM;EAAE,OAAO,CAAC,OAAO,MAAM,CAAC;EAAE;CAG3D,MAAM,QAAQ,CAAC,GAAG,MAAM,KAAK,IAAI,MAAM,UAAU;AAEjD,KAAI,WAAW,SAAS,MAAM,OAAO;EACnC,MAAM,QACJ,MAAM,iBAAiB,QAAQ,MAAM,MAAM,UAAU,OAAO,MAAM,MAAM;AAC1E,QAAM,KAAK,UAAU,QAAQ;;AAG/B,KACE,UAAU,SACV,OAAQ,MAAgC,SAAS,SAEjD,OAAM,KAAK,SAAU,MAAgC,OAAO;AAG9D,QAAO;EAAE,SAAS,GAAG,MAAM,KAAK,IAAI,MAAM;EAAW;EAAO;;AAG9D,eAAsB,mBACpB,SACA,KACA,SACmB;CACnB,MAAM,MAAM,GAAG,IAAI,SAAS;CAC5B,MAAM,cAAc,QAAQ,QAAQ,IAAI,cAAc;CAEtD,MAAM,OADU,QAAQ,WAAW,SAAS,QAAQ,WAAW,SACxC,MAAM,QAAQ,MAAM,GAAG,KAAA;CAE9C,MAAM,WAAW,MAAM,MAAM,KAAK;EAChC,GAAG,cAAc,EAAE,MAAM,CAAC;EAC1B,QAAQ,QAAQ;EAChB,SAAS;GACP,gBAAgB,QAAQ,QAAQ,IAAI,eAAe,IAAI;GACvD,aAAa,IAAI;GACjB,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;GACtC,GAAG,WAAW,QAAQ;GACvB;EACD,QAAQ,YAAY,QAAQ,IAAO;EACpC,CAAC;AAEF,KAAI,CAAC,SAAS,IAAI;EAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAElD,MAAI,SAAS,UAAU,KAAK;AAC1B,OAAI,MACF,YAAY,SAAS,OAAO,OAAO,QAAQ,OAAO,GAAG,WACrD,CAAC,KAAK,CACP;AACD,UAAO,SAAS,KACd;IAAE,MAAM;IAA4B,SAAS;IAAM,EACnD,EAAE,QAAQ,SAAS,QAAQ,CAC5B;;AAEH,MAAI,SAAS,UAAU,KAAK;AAC1B,OAAI,KAAK,YAAY,SAAS,OAAO,OAAO,QAAQ,OAAO,GAAG,WAAW,CACvE,KACD,CAAC;AACF,UAAO,SAAS,KACd;IAAE,MAAM;IAA8B,SAAS;IAAM,EACrD,EAAE,QAAQ,SAAS,QAAQ,CAC5B;;AAGH,MAAI,MAAM,YAAY,SAAS,OAAO,OAAO,QAAQ,OAAO,GAAG,WAAW,CACxE,KACD,CAAC;AACF,SAAO,SAAS,KACd;GAAE,MAAM;GAA4B,SAAS;GAAM,EACnD,EAAE,QAAQ,SAAS,QAAQ,CAC5B;;AAGH,QAAO,IAAI,SAAS,SAAS,MAAM;EACjC,QAAQ,SAAS;EACjB,SAAS,EACP,gBACE,SAAS,QAAQ,IAAI,eAAe,IAAI,oBAC3C;EACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"proxy.mjs","names":[],"sources":["../../../src/internal/route/proxy.ts"],"sourcesContent":["import { resolveRoutePrefix } from \"@interfere/constants/route-prefix\";\nimport { omitUndefined } from \"@interfere/helpers/omit-undefined\";\nimport { extractCountryHeader } from \"@interfere/types/sdk/geo\";\n\nimport { type InterfereEnv, readInterfereEnv } from \"../env.js\";\nimport { log } from \"../logger.js\";\n\n// Never forward customer API keys from browser-originated ingestion requests.\nconst STRIPPED_AUTH_HEADERS = [\n \"authorization\",\n \"x-api-key\",\n \"x-interfere-api-key\",\n] as const;\n\n// Headers tied to the *inbound* HTTP transaction. These must not leak onto\n// the *outbound* `fetch` because the runtime computes them fresh for the new\n// body / connection, and forwarding them stale either silently corrupts the\n// request or hangs it until a timeout fires.\n//\n// Specifically `content-length`: `handleIngest` parses the incoming JSON,\n// injects build/release metadata, then re-`JSON.stringify`s the result. The\n// new body almost always has a different byte length to the original, so\n// forwarding the inbound `content-length` makes undici stall writing the\n// body until our 10s `AbortSignal.timeout` fires with\n// `Request body length does not match content-length header`. This was the\n// 2026-05-01 prod symptom (Vercel proxy 502s on `POST /v1/ingest` from\n// dashboard + homepage).\n//\n// `host` would route the upstream fetch back at the proxy hostname. fetch\n// sets it correctly from the URL automatically.\n//\n// The remainder are RFC 7230 §6.1 hop-by-hop headers — meaningful only on\n// the connection that received them. There's no canonical stdlib list for\n// any of this; every proxy in the Node ecosystem hand-rolls the same set\n// (`http-proxy`, Cloudflare Workers docs, Hono, undici's ProxyAgent, …).\nconst STRIPPED_TRANSPORT_HEADERS = [\n \"host\",\n \"content-length\",\n \"connection\",\n \"keep-alive\",\n \"transfer-encoding\",\n \"te\",\n \"trailer\",\n \"upgrade\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n] as const;\n\n/**\n * Clone the inbound request's headers, removing entries that should never\n * cross the proxy boundary. Returns a mutable `Headers` so callers can\n * `.set()`/`.delete()` upstream-specific values without having to merge\n * record literals (which lose case-insensitivity and create the precedence\n * footguns that masked the 2026-05-01 incident in code review).\n */\nexport function forwardedHeaders(request: Request): Headers {\n const headers = new Headers(request.headers);\n for (const name of STRIPPED_AUTH_HEADERS) {\n headers.delete(name);\n }\n for (const name of STRIPPED_TRANSPORT_HEADERS) {\n headers.delete(name);\n }\n return headers;\n}\n\nexport interface AuthenticatedEnv {\n apiUrl: string;\n publicKey: string | null;\n release: InterfereEnv[\"release\"];\n}\n\nexport function resolveAuthenticatedEnv(): AuthenticatedEnv {\n const env = readInterfereEnv();\n return {\n apiUrl: env.apiUrl,\n publicKey: env.publicKey,\n release: env.release,\n };\n}\n\n/** True when the proxy can attach a publishable key from env or the request. */\nexport function hasPublicKeyCredential(\n request: Request,\n envPublicKey: string | null\n): boolean {\n if (envPublicKey) {\n return true;\n }\n return new URL(request.url).searchParams.has(\"pk\");\n}\n\nexport function notConfiguredResponse(): Response {\n log.warn(\"Not configured\", [\n \"INTERFERE_PUBLIC_KEY is not set and the request has no ?pk= query param. The proxy route will return 503.\",\n ]);\n return Response.json(\n {\n code: \"INTERFERE_NOT_CONFIGURED\",\n message: \"INTERFERE_PUBLIC_KEY is required.\",\n },\n { status: 503 }\n );\n}\n\nexport function extractSubPath(request: Request): string {\n const prefix = resolveRoutePrefix();\n const url = new URL(request.url);\n const idx = url.pathname.indexOf(prefix);\n if (idx === -1) {\n return \"/\";\n }\n const remainder = url.pathname.slice(idx + prefix.length);\n if (remainder === \"\") {\n return \"/\";\n }\n return remainder.startsWith(\"/\") ? remainder : `/${remainder}`;\n}\n\n/**\n * Preserve any query string verbatim when forwarding upstream.\n *\n * The browser SDK uses `?_pv=…` and `?pk=…` (and `?_pt=…` in direct-ingestion\n * mode) as a `navigator.sendBeacon` fallback when `visibilitychange→\n * hidden` aborts the keepalive fetch path. The collector's\n * `otlpProducerGuard` and `surfacePkAuth` accept those values verbatim\n * — but only if the proxy actually forwards them. Passing\n * `request.url`'s `search` through (`\"\"` when absent, leading `?`\n * when present) keeps the rewrite transparent.\n */\nexport function extractSearch(request: Request): string {\n return new URL(request.url).search;\n}\n\nexport function formatProxyError(error: unknown): {\n message: string;\n lines: string[];\n} {\n if (!(error instanceof Error)) {\n return { message: String(error), lines: [String(error)] };\n }\n\n const lines = [`${error.name}: ${error.message}`];\n\n if (\"cause\" in error && error.cause) {\n const cause =\n error.cause instanceof Error ? error.cause.message : String(error.cause);\n lines.push(`cause: ${cause}`);\n }\n\n if (\n \"code\" in error &&\n typeof (error as NodeJS.ErrnoException).code === \"string\"\n ) {\n lines.push(`code: ${(error as NodeJS.ErrnoException).code}`);\n }\n\n return { message: `${error.name}: ${error.message}`, lines };\n}\n\n/**\n * Append `?pk=` to the outbound URL from the server env. Proxy mode\n * means the browser never sees `INTERFERE_PUBLIC_KEY` (it's not\n * `NEXT_PUBLIC_`-exposed), so the proxy is the only place the\n * credential can attach to the request. Preserves any existing query\n * params (e.g. `_pv=` debug flag). If the inbound request already\n * carries `?pk=`, that value wins — a misconfigured server-env pk\n * shouldn't clobber an explicit one from the SDK.\n */\nfunction buildUpstreamUrl(\n apiUrl: string,\n subPath: string,\n request: Request,\n publicKey: string | null\n): string {\n const incoming = new URL(request.url);\n const upstream = new URL(`${apiUrl}${subPath}`);\n\n for (const [key, value] of incoming.searchParams) {\n upstream.searchParams.append(key, value);\n }\n\n if (publicKey && !upstream.searchParams.has(\"pk\")) {\n upstream.searchParams.set(\"pk\", publicKey);\n }\n\n return upstream.toString();\n}\n\nexport async function forwardToCollector(\n request: Request,\n env: AuthenticatedEnv,\n subPath: string\n): Promise<Response> {\n const url = buildUpstreamUrl(env.apiUrl, subPath, request, env.publicKey);\n const hasBody = request.method !== \"GET\" && request.method !== \"HEAD\";\n const body = hasBody ? await request.text() : undefined;\n\n const headers = forwardedHeaders(request);\n // Default to JSON only when the inbound request didn't pin a content-type\n // — non-`/v1/ingest` paths relay the body bytes verbatim, so honour\n // whatever the caller said it was sending.\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n const country = extractCountryHeader(request);\n if (country !== null) {\n headers.set(\"x-country-code\", country);\n }\n\n const upstream = await fetch(url, {\n ...omitUndefined({ body }),\n method: request.method,\n headers,\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!upstream.ok) {\n const text = await upstream.text().catch(() => \"\");\n\n if (upstream.status >= 500) {\n log.error(\n `Upstream ${upstream.status} for ${request.method} ${subPath}`,\n [text]\n );\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_ERROR\", message: text },\n { status: upstream.status }\n );\n }\n if (upstream.status >= 400) {\n log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [\n text,\n ]);\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_WARNING\", message: text },\n { status: upstream.status }\n );\n }\n\n log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [\n text,\n ]);\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_ERROR\", message: text },\n { status: upstream.status }\n );\n }\n\n return new Response(upstream.body, {\n status: upstream.status,\n headers: {\n \"content-type\":\n upstream.headers.get(\"content-type\") ?? \"application/json\",\n },\n });\n}\n"],"mappings":"kQAQA,MAAM,sBAAwB,CAC5B,gBACA,YACA,qBACF,EAuBM,2BAA6B,CACjC,OACA,iBACA,aACA,aACA,oBACA,KACA,UACA,UACA,qBACA,qBACF,EASA,SAAgB,iBAAiB,QAA2B,CAC1D,IAAM,QAAU,IAAI,QAAQ,QAAQ,OAAO,EAC3C,IAAK,IAAM,QAAQ,sBACjB,QAAQ,OAAO,IAAI,EAErB,IAAK,IAAM,QAAQ,2BACjB,QAAQ,OAAO,IAAI,EAErB,OAAO,OACT,CAQA,SAAgB,yBAA4C,CAC1D,IAAM,IAAM,iBAAiB,EAC7B,MAAO,CACL,OAAQ,IAAI,OACZ,UAAW,IAAI,UACf,QAAS,IAAI,OACf,CACF,CAGA,SAAgB,uBACd,QACA,aACS,CAIT,OAHI,aACK,GAEF,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,IAAI,CACnD,CAEA,SAAgB,uBAAkC,CAIhD,OAHA,IAAI,KAAK,iBAAkB,CACzB,2GACF,CAAC,EACM,SAAS,KACd,CACE,KAAM,2BACN,QAAS,mCACX,EACA,CAAE,OAAQ,GAAI,CAChB,CACF,CAEA,SAAgB,eAAe,QAA0B,CACvD,IAAM,OAAS,mBAAmB,EAC5B,IAAM,IAAI,IAAI,QAAQ,GAAG,EACzB,IAAM,IAAI,SAAS,QAAQ,MAAM,EACvC,GAAI,MAAQ,GACV,MAAO,IAET,IAAM,UAAY,IAAI,SAAS,MAAM,IAAM,OAAO,MAAM,EAIxD,OAHI,YAAc,GACT,IAEF,UAAU,WAAW,GAAG,EAAI,UAAY,IAAI,WACrD,CAaA,SAAgB,cAAc,QAA0B,CACtD,OAAO,IAAI,IAAI,QAAQ,GAAG,EAAE,MAC9B,CAEA,SAAgB,iBAAiB,MAG/B,CACA,GAAI,EAAE,iBAAiB,OACrB,MAAO,CAAE,QAAS,OAAO,KAAK,EAAG,MAAO,CAAC,OAAO,KAAK,CAAC,CAAE,EAG1D,IAAM,MAAQ,CAAC,GAAG,MAAM,KAAK,IAAI,MAAM,SAAS,EAEhD,GAAI,UAAW,OAAS,MAAM,MAAO,CACnC,IAAM,MACJ,MAAM,iBAAiB,MAAQ,MAAM,MAAM,QAAU,OAAO,MAAM,KAAK,EACzE,MAAM,KAAK,UAAU,OAAO,CAC9B,CASA,MANE,SAAU,OACV,OAAQ,MAAgC,MAAS,UAEjD,MAAM,KAAK,SAAU,MAAgC,MAAM,EAGtD,CAAE,QAAS,GAAG,MAAM,KAAK,IAAI,MAAM,UAAW,KAAM,CAC7D,CAWA,SAAS,iBACP,OACA,QACA,QACA,UACQ,CACR,IAAM,SAAW,IAAI,IAAI,QAAQ,GAAG,EAC9B,SAAW,IAAI,IAAI,GAAG,SAAS,SAAS,EAE9C,IAAK,GAAM,CAAC,IAAK,SAAU,SAAS,aAClC,SAAS,aAAa,OAAO,IAAK,KAAK,EAOzC,OAJI,WAAa,CAAC,SAAS,aAAa,IAAI,IAAI,GAC9C,SAAS,aAAa,IAAI,KAAM,SAAS,EAGpC,SAAS,SAAS,CAC3B,CAEA,eAAsB,mBACpB,QACA,IACA,QACmB,CACnB,IAAM,IAAM,iBAAiB,IAAI,OAAQ,QAAS,QAAS,IAAI,SAAS,EAElE,KADU,QAAQ,SAAW,OAAS,QAAQ,SAAW,OACxC,MAAM,QAAQ,KAAK,EAAI,IAAA,GAExC,QAAU,iBAAiB,OAAO,EAInC,QAAQ,IAAI,cAAc,GAC7B,QAAQ,IAAI,eAAgB,kBAAkB,EAEhD,IAAM,QAAU,qBAAqB,OAAO,EACxC,UAAY,MACd,QAAQ,IAAI,iBAAkB,OAAO,EAGvC,IAAM,SAAW,MAAM,MAAM,IAAK,CAChC,GAAG,cAAc,CAAE,IAAK,CAAC,EACzB,OAAQ,QAAQ,OAChB,QACA,OAAQ,YAAY,QAAQ,GAAM,CACpC,CAAC,EAED,GAAI,CAAC,SAAS,GAAI,CAChB,IAAM,KAAO,MAAM,SAAS,KAAK,EAAE,UAAY,EAAE,EAyBjD,OAvBI,SAAS,QAAU,KACrB,IAAI,MACF,YAAY,SAAS,OAAO,OAAO,QAAQ,OAAO,GAAG,UACrD,CAAC,IAAI,CACP,EACO,SAAS,KACd,CAAE,KAAM,2BAA4B,QAAS,IAAK,EAClD,CAAE,OAAQ,SAAS,MAAO,CAC5B,GAEE,SAAS,QAAU,KACrB,IAAI,KAAK,YAAY,SAAS,OAAO,OAAO,QAAQ,OAAO,GAAG,UAAW,CACvE,IACF,CAAC,EACM,SAAS,KACd,CAAE,KAAM,6BAA8B,QAAS,IAAK,EACpD,CAAE,OAAQ,SAAS,MAAO,CAC5B,IAGF,IAAI,MAAM,YAAY,SAAS,OAAO,OAAO,QAAQ,OAAO,GAAG,UAAW,CACxE,IACF,CAAC,EACM,SAAS,KACd,CAAE,KAAM,2BAA4B,QAAS,IAAK,EAClD,CAAE,OAAQ,SAAS,MAAO,CAC5B,EACF,CAEA,OAAO,IAAI,SAAS,SAAS,KAAM,CACjC,OAAQ,SAAS,OACjB,QAAS,CACP,eACE,SAAS,QAAQ,IAAI,cAAc,GAAK,kBAC5C,CACF,CAAC,CACH"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { CaptureErrorContext, OnRequestErrorContext } from "./types.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/internal/server/capture.d.ts
|
|
4
|
-
declare function captureError(error: unknown,
|
|
4
|
+
declare function captureError(error: unknown, _request?: unknown, context?: CaptureErrorContext): void;
|
|
5
5
|
declare function onRequestError(error: Error & {
|
|
6
6
|
digest?: string;
|
|
7
|
-
},
|
|
7
|
+
}, _request: unknown, context: OnRequestErrorContext): void;
|
|
8
8
|
//#endregion
|
|
9
9
|
export { captureError, onRequestError };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.d.mts","names":[],"sources":["../../../src/internal/server/capture.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"capture.d.mts","names":[],"sources":["../../../src/internal/server/capture.ts"],"mappings":";;;iBA+GgB,YAAA,CACd,KAAA,WACA,QAAA,YACA,OAAA,GAAU,mBAAmB;AAAA,iBAwBf,cAAA,CACd,KAAA,EAAO,KAAA;EAAU,MAAA;AAAA,GACjB,QAAA,WACA,OAAA,EAAS,qBAAqB"}
|