@interfere/next 9.0.2 → 10.0.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 +33 -5
- package/dist/config.d.mts +24 -5
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +38 -28
- package/dist/config.mjs.map +1 -1
- package/dist/instrument-client.d.mts +14 -3
- package/dist/instrument-client.d.mts.map +1 -1
- package/dist/instrument-client.mjs +7 -9
- package/dist/instrument-client.mjs.map +1 -1
- package/dist/instrumentation-client.d.mts +1 -0
- package/dist/instrumentation-client.mjs +22 -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 +34 -0
- package/dist/instrumentation.edge.mjs.map +1 -0
- package/dist/instrumentation.mjs +165 -0
- package/dist/instrumentation.mjs.map +1 -0
- package/dist/internal/build/configure-build.d.mts +1 -2
- package/dist/internal/build/configure-build.d.mts.map +1 -1
- package/dist/internal/build/configure-build.mjs +10 -2
- package/dist/internal/build/configure-build.mjs.map +1 -1
- package/dist/internal/build/detect-bundler.d.mts +6 -0
- package/dist/internal/build/detect-bundler.d.mts.map +1 -0
- package/dist/internal/build/detect-bundler.mjs +9 -0
- package/dist/internal/build/detect-bundler.mjs.map +1 -0
- package/dist/internal/build/pipeline.d.mts +14 -1
- package/dist/internal/build/pipeline.d.mts.map +1 -1
- package/dist/internal/build/pipeline.mjs +26 -10
- 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 +13 -0
- package/dist/internal/build/release/destinations/index.mjs.map +1 -0
- 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 +13 -2
- 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 +4 -5
- package/dist/internal/build/release/index.mjs.map +1 -1
- 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 +20 -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 +68 -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 +112 -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 +22 -83
- 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 +13 -23
- 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 +49 -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 +134 -0
- package/dist/internal/build/source-maps/upload.mjs.map +1 -0
- package/dist/internal/build/value-injection-loader.mjs.map +1 -1
- package/dist/internal/env.d.mts +11 -2
- package/dist/internal/env.d.mts.map +1 -1
- package/dist/internal/env.mjs +12 -3
- 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 +10 -2
- 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 +32 -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 +35 -14
- 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 +11 -50
- package/dist/internal/route/handle-post.mjs.map +1 -1
- package/dist/internal/route/proxy.d.mts +21 -1
- package/dist/internal/route/proxy.d.mts.map +1 -1
- package/dist/internal/route/proxy.mjs +61 -16
- 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 +71 -37
- 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 +112 -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 +68 -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.mjs +2 -2
- package/dist/internal/server/remote-config.mjs.map +1 -1
- package/dist/internal/server/trace-meta.d.mts +34 -0
- package/dist/internal/server/trace-meta.d.mts.map +1 -0
- package/dist/internal/server/trace-meta.mjs +41 -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 +26 -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/setup-warnings.d.mts +17 -0
- package/dist/internal/setup-warnings.d.mts.map +1 -0
- package/dist/internal/setup-warnings.mjs +45 -0
- package/dist/internal/setup-warnings.mjs.map +1 -0
- package/dist/package.mjs +1 -1
- package/dist/provider.d.mts +23 -2
- package/dist/provider.d.mts.map +1 -0
- package/dist/provider.mjs +23 -1
- 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 +11 -9
- package/dist/route-handler.mjs.map +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.mjs +2 -2
- package/package.json +73 -20
- 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
|
@@ -1,22 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { log } from "../logger.mjs";
|
|
2
|
+
import { extractSubPath, formatProxyError, forwardToCollector, notConfiguredResponse, resolveAuthenticatedEnv } from "./proxy.mjs";
|
|
3
|
+
import { SW_SCRIPT } from "@interfere/react/sw";
|
|
4
4
|
//#region src/internal/route/handle-get.ts
|
|
5
|
-
|
|
5
|
+
const SW_HEADERS = {
|
|
6
|
+
"content-type": "application/javascript; charset=utf-8",
|
|
7
|
+
"service-worker-allowed": "/",
|
|
8
|
+
"cache-control": "public, max-age=3600"
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Generic GET proxy. SDK 10.x routes `GET /v2/config` (and any future
|
|
12
|
+
* GET-based endpoints) through here; SDK 9.x clients still hit
|
|
13
|
+
* `GET /v1/config` and get the same delegated response on the collector
|
|
14
|
+
* side.
|
|
15
|
+
*
|
|
16
|
+
* `/sw` is special-cased to serve the SDK's bundled service-worker
|
|
17
|
+
* script directly out of the customer's Next.js process. `SW_SCRIPT`
|
|
18
|
+
* is a string export emitted at @interfere/react build time
|
|
19
|
+
* (`scripts/build-sw.ts`); on Node runtime the customer resolves it
|
|
20
|
+
* via `node_modules`, on Edge runtime the customer's Next.js bundler
|
|
21
|
+
* inlines it into the Edge worker. Either way, no `fs.readFileSync`.
|
|
22
|
+
*/
|
|
23
|
+
async function handleGet(request) {
|
|
6
24
|
const subPath = extractSubPath(request);
|
|
7
|
-
if (subPath
|
|
25
|
+
if (subPath === "/sw") return new Response(SW_SCRIPT, {
|
|
8
26
|
status: 200,
|
|
9
|
-
headers:
|
|
10
|
-
"content-type": "application/javascript",
|
|
11
|
-
"cache-control": "public, max-age=3600"
|
|
12
|
-
}
|
|
27
|
+
headers: SW_HEADERS
|
|
13
28
|
});
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return forwardToCollector(request, env, subPath);
|
|
29
|
+
const env = resolveAuthenticatedEnv();
|
|
30
|
+
if (!env) return notConfiguredResponse();
|
|
31
|
+
try {
|
|
32
|
+
return await forwardToCollector(request, env, subPath);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
const detail = formatProxyError(error);
|
|
35
|
+
log.error(`Proxy ${request.method} ${subPath} failed`, detail.lines);
|
|
36
|
+
return Response.json({
|
|
37
|
+
code: "INTERFERE_PROXY_ERROR",
|
|
38
|
+
message: detail.message
|
|
39
|
+
}, { status: 502 });
|
|
18
40
|
}
|
|
19
|
-
return new Response("Not Found", { status: 404 });
|
|
20
41
|
}
|
|
21
42
|
//#endregion
|
|
22
43
|
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 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 (!env) {\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":";;;;AAWA,MAAM,aAAqC;CACzC,gBAAgB;CAIhB,0BAA0B;CAI1B,iBAAiB;CAClB;;;;;;;;;;;;;;AAeD,eAAsB,UAAU,SAAqC;CACnE,MAAM,UAAU,eAAe,QAAQ;CAEvC,IAAI,YAAY,OACd,OAAO,IAAI,SAAS,WAAW;EAAE,QAAQ;EAAK,SAAS;EAAY,CAAC;CAGtE,MAAM,MAAM,yBAAyB;CACrC,IAAI,CAAC,KACH,OAAO,uBAAuB;CAEhC,IAAI;EACF,OAAO,MAAM,mBAAmB,SAAS,KAAK,QAAQ;UAC/C,OAAO;EACd,MAAM,SAAS,iBAAiB,MAAM;EACtC,IAAI,MAAM,SAAS,QAAQ,OAAO,GAAG,QAAQ,UAAU,OAAO,MAAM;EACpE,OAAO,SAAS,KACd;GAAE,MAAM;GAAyB,SAAS,OAAO;GAAS,EAC1D,EAAE,QAAQ,KAAK,CAChB"}
|
|
@@ -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":";;AAoBA;;;;;;;;;;iBAAsB,UAAA,CAAW,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,QAAA"}
|
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
import { log } from "../logger.mjs";
|
|
2
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
3
|
//#region src/internal/route/handle-post.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
4
|
+
/**
|
|
5
|
+
* Generic POST proxy. SDK 10.x routes everything through here:
|
|
6
|
+
*
|
|
7
|
+
* - `/v2/sink` (OTLP traces / metrics / logs)
|
|
8
|
+
* - `/v2/replay/upload/:sessionId`
|
|
9
|
+
* - `/v1/session*` (session sync + identify)
|
|
10
|
+
*
|
|
11
|
+
* No envelope-aware special-casing — that endpoint (`/v1/ingest`) was
|
|
12
|
+
* 9.x-only and is not produced by 10.x. Customers on 9.x post directly
|
|
13
|
+
* to the collector through the same proxy without touching this file.
|
|
14
|
+
*/
|
|
18
15
|
async function handlePost(request) {
|
|
19
16
|
const env = resolveAuthenticatedEnv();
|
|
20
17
|
if (!env) return notConfiguredResponse();
|
|
21
18
|
const subPath = extractSubPath(request);
|
|
22
19
|
try {
|
|
23
|
-
if (subPath === API_PATHS.INGEST) return await handleIngest(request, env);
|
|
24
20
|
return await forwardToCollector(request, env, subPath);
|
|
25
21
|
} catch (error) {
|
|
26
22
|
const detail = formatProxyError(error);
|
|
@@ -31,40 +27,5 @@ async function handlePost(request) {
|
|
|
31
27
|
}, { status: 502 });
|
|
32
28
|
}
|
|
33
29
|
}
|
|
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
30
|
//#endregion
|
|
70
31
|
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 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 (!env) {\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":";;;;;;;;;;;;;;AAoBA,eAAsB,WAAW,SAAqC;CACpE,MAAM,MAAM,yBAAyB;CACrC,IAAI,CAAC,KACH,OAAO,uBAAuB;CAEhC,MAAM,UAAU,eAAe,QAAQ;CACvC,IAAI;EACF,OAAO,MAAM,mBAAmB,SAAS,KAAK,QAAQ;UAC/C,OAAO;EACd,MAAM,SAAS,iBAAiB,MAAM;EACtC,IAAI,MAAM,SAAS,QAAQ,OAAO,GAAG,QAAQ,UAAU,OAAO,MAAM;EACpE,OAAO,SAAS,KACd;GAAE,MAAM;GAAyB,SAAS,OAAO;GAAS,EAC1D,EAAE,QAAQ,KAAK,CAChB"}
|
|
@@ -1,6 +1,14 @@
|
|
|
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
13
|
apiKey: string;
|
|
6
14
|
apiUrl: string;
|
|
@@ -8,6 +16,18 @@ interface AuthenticatedEnv {
|
|
|
8
16
|
}
|
|
9
17
|
declare function resolveAuthenticatedEnv(): AuthenticatedEnv | null;
|
|
10
18
|
declare function extractSubPath(request: Request): string;
|
|
19
|
+
/**
|
|
20
|
+
* Preserve any query string verbatim when forwarding upstream.
|
|
21
|
+
*
|
|
22
|
+
* The browser SDK uses `?_pv=…` (and `?_pt=…` in direct-ingestion
|
|
23
|
+
* mode) as a `navigator.sendBeacon` fallback when `visibilitychange→
|
|
24
|
+
* hidden` aborts the keepalive fetch path. The collector's
|
|
25
|
+
* `otlpProducerGuard` and `surfaceAuth` accept those values verbatim
|
|
26
|
+
* — but only if the proxy actually forwards them. Passing
|
|
27
|
+
* `request.url`'s `search` through (`""` when absent, leading `?`
|
|
28
|
+
* when present) keeps the rewrite transparent.
|
|
29
|
+
*/
|
|
30
|
+
declare function extractSearch(request: Request): string;
|
|
11
31
|
declare function notConfiguredResponse(): Response;
|
|
12
32
|
declare function formatProxyError(error: unknown): {
|
|
13
33
|
message: string;
|
|
@@ -15,4 +35,4 @@ declare function formatProxyError(error: unknown): {
|
|
|
15
35
|
};
|
|
16
36
|
declare function forwardToCollector(request: Request, env: AuthenticatedEnv, subPath: string): Promise<Response>;
|
|
17
37
|
//#endregion
|
|
18
|
-
export { AuthenticatedEnv, extractSubPath, formatProxyError, forwardToCollector, notConfiguredResponse, resolveAuthenticatedEnv };
|
|
38
|
+
export { AuthenticatedEnv, extractSearch, extractSubPath, formatProxyError, forwardToCollector, forwardedHeaders, 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,OAAA;AAAA,UAWnC,gBAAA;EACf,MAAA;EACA,MAAA;EACA,OAAA,EAAS,YAAA;AAAA;AAAA,iBAGK,uBAAA,CAAA,GAA2B,gBAAA;AAAA,iBAQ3B,cAAA,CAAe,OAAA,EAAS,OAAA;;;;;;;AARxC;;;;;iBAiCgB,aAAA,CAAc,OAAA,EAAS,OAAA;AAAA,iBAMvB,qBAAA,CAAA,GAAyB,QAAA;AAAA,iBAgBzB,gBAAA,CAAiB,KAAA;EAC/B,OAAA;EACA,KAAA;AAAA;AAAA,iBAwBoB,kBAAA,CACpB,OAAA,EAAS,OAAA,EACT,GAAA,EAAK,gBAAA,EACL,OAAA,WACC,OAAA,CAAQ,QAAA"}
|
|
@@ -1,12 +1,34 @@
|
|
|
1
|
-
import { log } from "../logger.mjs";
|
|
2
1
|
import { readInterfereEnv } from "../env.mjs";
|
|
2
|
+
import { log } from "../logger.mjs";
|
|
3
3
|
import { omitUndefined } from "@interfere/helpers/omit-undefined";
|
|
4
|
+
import { resolveRoutePrefix } from "@interfere/constants/route-prefix";
|
|
4
5
|
import { extractCountryHeader } from "@interfere/types/sdk/geo";
|
|
5
6
|
//#region src/internal/route/proxy.ts
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const STRIPPED_AUTH_HEADERS = ["x-interfere-pub-token", "x-api-key"];
|
|
8
|
+
const STRIPPED_TRANSPORT_HEADERS = [
|
|
9
|
+
"host",
|
|
10
|
+
"content-length",
|
|
11
|
+
"connection",
|
|
12
|
+
"keep-alive",
|
|
13
|
+
"transfer-encoding",
|
|
14
|
+
"te",
|
|
15
|
+
"trailer",
|
|
16
|
+
"upgrade",
|
|
17
|
+
"proxy-authenticate",
|
|
18
|
+
"proxy-authorization"
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Clone the inbound request's headers, removing entries that should never
|
|
22
|
+
* cross the proxy boundary. Returns a mutable `Headers` so callers can
|
|
23
|
+
* `.set()`/`.delete()` upstream-specific values without having to merge
|
|
24
|
+
* record literals (which lose case-insensitivity and create the precedence
|
|
25
|
+
* footguns that masked the 2026-05-01 incident in code review).
|
|
26
|
+
*/
|
|
27
|
+
function forwardedHeaders(request) {
|
|
28
|
+
const headers = new Headers(request.headers);
|
|
29
|
+
for (const name of STRIPPED_AUTH_HEADERS) headers.delete(name);
|
|
30
|
+
for (const name of STRIPPED_TRANSPORT_HEADERS) headers.delete(name);
|
|
31
|
+
return headers;
|
|
10
32
|
}
|
|
11
33
|
function resolveAuthenticatedEnv() {
|
|
12
34
|
const env = readInterfereEnv();
|
|
@@ -18,10 +40,34 @@ function resolveAuthenticatedEnv() {
|
|
|
18
40
|
};
|
|
19
41
|
}
|
|
20
42
|
function extractSubPath(request) {
|
|
21
|
-
|
|
43
|
+
const prefix = resolveRoutePrefix();
|
|
44
|
+
const url = new URL(request.url);
|
|
45
|
+
const idx = url.pathname.indexOf(prefix);
|
|
46
|
+
if (idx === -1) return "/";
|
|
47
|
+
const remainder = url.pathname.slice(idx + prefix.length);
|
|
48
|
+
if (remainder === "") return "/";
|
|
49
|
+
return remainder.startsWith("/") ? remainder : `/${remainder}`;
|
|
22
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Preserve any query string verbatim when forwarding upstream.
|
|
53
|
+
*
|
|
54
|
+
* The browser SDK uses `?_pv=…` (and `?_pt=…` in direct-ingestion
|
|
55
|
+
* mode) as a `navigator.sendBeacon` fallback when `visibilitychange→
|
|
56
|
+
* hidden` aborts the keepalive fetch path. The collector's
|
|
57
|
+
* `otlpProducerGuard` and `surfaceAuth` accept those values verbatim
|
|
58
|
+
* — but only if the proxy actually forwards them. Passing
|
|
59
|
+
* `request.url`'s `search` through (`""` when absent, leading `?`
|
|
60
|
+
* when present) keeps the rewrite transparent.
|
|
61
|
+
*/
|
|
62
|
+
function extractSearch(request) {
|
|
63
|
+
return new URL(request.url).search;
|
|
64
|
+
}
|
|
65
|
+
let notConfiguredWarned = false;
|
|
23
66
|
function notConfiguredResponse() {
|
|
24
|
-
|
|
67
|
+
if (!notConfiguredWarned) {
|
|
68
|
+
notConfiguredWarned = true;
|
|
69
|
+
log.warn("Not configured", ["INTERFERE_API_KEY is not set. The proxy route will return 503."]);
|
|
70
|
+
}
|
|
25
71
|
return Response.json({
|
|
26
72
|
code: "INTERFERE_NOT_CONFIGURED",
|
|
27
73
|
message: "INTERFERE_API_KEY is required."
|
|
@@ -44,18 +90,17 @@ function formatProxyError(error) {
|
|
|
44
90
|
};
|
|
45
91
|
}
|
|
46
92
|
async function forwardToCollector(request, env, subPath) {
|
|
47
|
-
const url = `${env.apiUrl}${subPath}`;
|
|
48
|
-
const traceparent = request.headers.get("traceparent");
|
|
93
|
+
const url = `${env.apiUrl}${subPath}${extractSearch(request)}`;
|
|
49
94
|
const body = request.method !== "GET" && request.method !== "HEAD" ? await request.text() : void 0;
|
|
95
|
+
const headers = forwardedHeaders(request);
|
|
96
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
97
|
+
headers.set("x-api-key", env.apiKey);
|
|
98
|
+
const country = extractCountryHeader(request);
|
|
99
|
+
if (country !== null) headers.set("x-country-code", country);
|
|
50
100
|
const upstream = await fetch(url, {
|
|
51
101
|
...omitUndefined({ body }),
|
|
52
102
|
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
|
-
},
|
|
103
|
+
headers,
|
|
59
104
|
signal: AbortSignal.timeout(1e4)
|
|
60
105
|
});
|
|
61
106
|
if (!upstream.ok) {
|
|
@@ -86,4 +131,4 @@ async function forwardToCollector(request, env, subPath) {
|
|
|
86
131
|
});
|
|
87
132
|
}
|
|
88
133
|
//#endregion
|
|
89
|
-
export { extractSubPath, formatProxyError, forwardToCollector, notConfiguredResponse, resolveAuthenticatedEnv };
|
|
134
|
+
export { extractSearch, extractSubPath, formatProxyError, forwardToCollector, forwardedHeaders, 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// Auth headers we strip on principle:\n// - `x-interfere-pub-token` is browser-side SDK auth that the proxy replaces\n// with `x-api-key`.\n// - `x-api-key` is always supplied from server env; we don't trust whatever\n// the caller might have sent.\nconst STRIPPED_AUTH_HEADERS = [\"x-interfere-pub-token\", \"x-api-key\"] 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 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 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 `?_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 `surfaceAuth` 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\nlet notConfiguredWarned = false;\n\nexport function notConfiguredResponse(): Response {\n if (!notConfiguredWarned) {\n notConfiguredWarned = true;\n log.warn(\"Not configured\", [\n \"INTERFERE_API_KEY is not set. The proxy route will return 503.\",\n ]);\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}${extractSearch(request)}`;\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 headers.set(\"x-api-key\", env.apiKey);\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":"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"}
|
|
@@ -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,mBAAA;AAAA,iBAwBI,cAAA,CACd,KAAA,EAAO,KAAA;EAAU,MAAA;AAAA,GACjB,QAAA,WACA,OAAA,EAAS,qBAAA"}
|
|
@@ -1,53 +1,87 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isErrorCaptured, markErrorCaptured } from "./dedupe.mjs";
|
|
3
|
-
import { buildErrorEnvelope } from "./envelope.mjs";
|
|
4
|
-
import { normalizeRequest } from "./normalize-request.mjs";
|
|
1
|
+
import { isEnabledOnServer } from "../env.mjs";
|
|
5
2
|
import { isPluginEnabled } from "./remote-config.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { MECHANISM_TYPE } from "@interfere/types/sdk/errors";
|
|
3
|
+
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
4
|
+
import { MECHANISM_TYPE, isNonErrorException, toException } from "@interfere/types/sdk/errors";
|
|
9
5
|
//#region src/internal/server/capture.ts
|
|
6
|
+
const TRACER_NAME = "@interfere/next/server";
|
|
10
7
|
const ON_REQUEST_ERROR_MECHANISM = {
|
|
11
8
|
type: MECHANISM_TYPE.nextjs.onRequestError,
|
|
12
9
|
handled: false,
|
|
13
10
|
synthetic: false
|
|
14
11
|
};
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
headers: new Headers()
|
|
12
|
+
const DEFAULT_CAPTURE_ERROR_MECHANISM = {
|
|
13
|
+
type: MECHANISM_TYPE.nextjs.captureError,
|
|
14
|
+
handled: true
|
|
19
15
|
};
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
17
|
+
function buildAttrs(value, mechanism, context) {
|
|
18
|
+
const attrs = {
|
|
19
|
+
"interfere.exception.mechanism": mechanism.type,
|
|
20
|
+
"interfere.exception.handled": String(mechanism.handled)
|
|
21
|
+
};
|
|
22
|
+
const digest = context?.nextjs?.errorDigest;
|
|
23
|
+
if (digest) attrs["interfere.error.digest"] = digest;
|
|
24
|
+
if (context?.nextjs?.requestMethod) attrs["http.request.method"] = context.nextjs.requestMethod;
|
|
25
|
+
if (context?.nextjs?.requestPath) attrs["url.path"] = context.nextjs.requestPath;
|
|
26
|
+
if (isNonErrorException(value)) {
|
|
27
|
+
attrs["exception.type"] = value.type;
|
|
28
|
+
attrs["exception.message"] = value.value;
|
|
29
|
+
attrs["interfere.exception.kind"] = "non-error";
|
|
30
|
+
return attrs;
|
|
31
|
+
}
|
|
32
|
+
attrs["exception.type"] = value.name;
|
|
33
|
+
attrs["exception.message"] = value.message;
|
|
34
|
+
if (value.stack) attrs["exception.stacktrace"] = value.stack;
|
|
35
|
+
return attrs;
|
|
36
|
+
}
|
|
37
|
+
function recordException({ error, mechanism, context }) {
|
|
38
|
+
const value = toException(error);
|
|
39
|
+
const attrs = buildAttrs(value, mechanism, context);
|
|
40
|
+
const tracer = trace.getTracer(TRACER_NAME);
|
|
41
|
+
const active = trace.getActiveSpan();
|
|
42
|
+
if (active) {
|
|
43
|
+
active.addEvent("exception", attrs);
|
|
44
|
+
if (!mechanism.handled) active.setStatus({
|
|
45
|
+
code: SpanStatusCode.ERROR,
|
|
46
|
+
message: isNonErrorException(value) ? value.value : value.message
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const span = tracer.startSpan("interfere.captureError", { kind: SpanKind.INTERNAL });
|
|
51
|
+
span.addEvent("exception", attrs);
|
|
52
|
+
if (!mechanism.handled) span.setStatus({
|
|
53
|
+
code: SpanStatusCode.ERROR,
|
|
54
|
+
message: isNonErrorException(value) ? value.value : value.message
|
|
55
|
+
});
|
|
56
|
+
span.end();
|
|
57
|
+
}
|
|
58
|
+
function captureError(error, _request, context) {
|
|
59
|
+
if (!isEnabledOnServer()) return;
|
|
22
60
|
if (!isPluginEnabled("errors")) return;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
61
|
+
if (error instanceof Error) {
|
|
62
|
+
if (seen.has(error)) return;
|
|
63
|
+
seen.add(error);
|
|
64
|
+
}
|
|
65
|
+
recordException({
|
|
27
66
|
error,
|
|
28
|
-
|
|
29
|
-
context
|
|
30
|
-
runtime
|
|
67
|
+
mechanism: context?.mechanism ?? DEFAULT_CAPTURE_ERROR_MECHANISM,
|
|
68
|
+
context
|
|
31
69
|
});
|
|
32
|
-
try {
|
|
33
|
-
await sendEnvelope({
|
|
34
|
-
envelope,
|
|
35
|
-
runtime,
|
|
36
|
-
traceparent: context?.traceparent ?? normalizedRequest?.headers.get("traceparent") ?? void 0
|
|
37
|
-
});
|
|
38
|
-
} catch {}
|
|
39
70
|
}
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
71
|
+
function onRequestError(error, _request, context) {
|
|
72
|
+
if (seen.has(error)) return;
|
|
73
|
+
seen.add(error);
|
|
74
|
+
if (!isEnabledOnServer()) return;
|
|
75
|
+
if (!isPluginEnabled("errors")) return;
|
|
76
|
+
recordException({
|
|
77
|
+
error,
|
|
45
78
|
mechanism: ON_REQUEST_ERROR_MECHANISM,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
79
|
+
context: {
|
|
80
|
+
mechanism: ON_REQUEST_ERROR_MECHANISM,
|
|
81
|
+
nextjs: {
|
|
82
|
+
...context,
|
|
83
|
+
...error.digest ? { errorDigest: error.digest } : {}
|
|
84
|
+
}
|
|
51
85
|
}
|
|
52
86
|
});
|
|
53
87
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.mjs","names":[],"sources":["../../../src/internal/server/capture.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"capture.mjs","names":[],"sources":["../../../src/internal/server/capture.ts"],"sourcesContent":["import {\n isNonErrorException,\n MECHANISM_TYPE,\n type NonErrorException,\n toException,\n} from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { SpanKind, SpanStatusCode, trace } from \"@opentelemetry/api\";\n\nimport { isEnabledOnServer } from \"../env.js\";\nimport { isPluginEnabled } from \"./remote-config.js\";\nimport type { CaptureErrorContext, OnRequestErrorContext } from \"./types.js\";\n\nconst TRACER_NAME = \"@interfere/next/server\";\n\nconst ON_REQUEST_ERROR_MECHANISM: ErrorMechanism = {\n type: MECHANISM_TYPE.nextjs.onRequestError,\n handled: false,\n synthetic: false,\n};\n\nconst DEFAULT_CAPTURE_ERROR_MECHANISM: ErrorMechanism = {\n type: MECHANISM_TYPE.nextjs.captureError,\n handled: true,\n};\n\n// Per-process dedupe so a thrown route-handler error captured by both the\n// caller's `try { } catch { captureError(e) }` and Next's own\n// `onRequestError` hook only emits one exception event. WeakSet so GC'd\n// errors don't pin memory.\nconst seen = new WeakSet<Error>();\n\ninterface RecordExceptionInput {\n context?: CaptureErrorContext | undefined;\n error: unknown;\n mechanism: ErrorMechanism;\n}\n\nfunction buildAttrs(\n value: Error | NonErrorException,\n mechanism: ErrorMechanism,\n context: CaptureErrorContext | undefined\n): Record<string, string> {\n const attrs: Record<string, string> = {\n \"interfere.exception.mechanism\": mechanism.type,\n \"interfere.exception.handled\": String(mechanism.handled),\n };\n const digest = context?.nextjs?.errorDigest;\n if (digest) {\n attrs[\"interfere.error.digest\"] = digest;\n }\n if (context?.nextjs?.requestMethod) {\n attrs[\"http.request.method\"] = context.nextjs.requestMethod;\n }\n if (context?.nextjs?.requestPath) {\n attrs[\"url.path\"] = context.nextjs.requestPath;\n }\n if (isNonErrorException(value)) {\n attrs[\"exception.type\"] = value.type;\n attrs[\"exception.message\"] = value.value;\n attrs[\"interfere.exception.kind\"] = \"non-error\";\n return attrs;\n }\n attrs[\"exception.type\"] = value.name;\n attrs[\"exception.message\"] = value.message;\n if (value.stack) {\n attrs[\"exception.stacktrace\"] = value.stack;\n }\n return attrs;\n}\n\nfunction recordException({\n error,\n mechanism,\n context,\n}: RecordExceptionInput): void {\n const value = toException(error);\n const attrs = buildAttrs(value, mechanism, context);\n\n // Prefer the active span (route handler / Next request span). Fall\n // back to a fresh single-event span when no active span is available\n // — Next sometimes calls `onRequestError` from background contexts\n // (background revalidation, failed prerender) where the request span\n // is no longer current.\n const tracer = trace.getTracer(TRACER_NAME);\n const active = trace.getActiveSpan();\n if (active) {\n active.addEvent(\"exception\", attrs);\n if (!mechanism.handled) {\n active.setStatus({\n code: SpanStatusCode.ERROR,\n message: isNonErrorException(value) ? value.value : value.message,\n });\n }\n return;\n }\n\n const span = tracer.startSpan(\"interfere.captureError\", {\n kind: SpanKind.INTERNAL,\n });\n span.addEvent(\"exception\", attrs);\n if (!mechanism.handled) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: isNonErrorException(value) ? value.value : value.message,\n });\n }\n span.end();\n}\n\nexport function captureError(\n error: unknown,\n _request?: unknown,\n context?: CaptureErrorContext\n): void {\n if (!isEnabledOnServer()) {\n return;\n }\n\n if (!isPluginEnabled(\"errors\")) {\n return;\n }\n\n if (error instanceof Error) {\n if (seen.has(error)) {\n return;\n }\n seen.add(error);\n }\n\n recordException({\n error,\n mechanism: context?.mechanism ?? DEFAULT_CAPTURE_ERROR_MECHANISM,\n context,\n });\n}\n\nexport function onRequestError(\n error: Error & { digest?: string },\n _request: unknown,\n context: OnRequestErrorContext\n): void {\n if (seen.has(error)) {\n return;\n }\n seen.add(error);\n\n if (!isEnabledOnServer()) {\n return;\n }\n if (!isPluginEnabled(\"errors\")) {\n return;\n }\n\n recordException({\n error,\n mechanism: ON_REQUEST_ERROR_MECHANISM,\n context: {\n mechanism: ON_REQUEST_ERROR_MECHANISM,\n nextjs: {\n ...context,\n ...(error.digest ? { errorDigest: error.digest } : {}),\n },\n },\n });\n}\n"],"mappings":";;;;;AAcA,MAAM,cAAc;AAEpB,MAAM,6BAA6C;CACjD,MAAM,eAAe,OAAO;CAC5B,SAAS;CACT,WAAW;CACZ;AAED,MAAM,kCAAkD;CACtD,MAAM,eAAe,OAAO;CAC5B,SAAS;CACV;AAMD,MAAM,uBAAO,IAAI,SAAgB;AAQjC,SAAS,WACP,OACA,WACA,SACwB;CACxB,MAAM,QAAgC;EACpC,iCAAiC,UAAU;EAC3C,+BAA+B,OAAO,UAAU,QAAQ;EACzD;CACD,MAAM,SAAS,SAAS,QAAQ;CAChC,IAAI,QACF,MAAM,4BAA4B;CAEpC,IAAI,SAAS,QAAQ,eACnB,MAAM,yBAAyB,QAAQ,OAAO;CAEhD,IAAI,SAAS,QAAQ,aACnB,MAAM,cAAc,QAAQ,OAAO;CAErC,IAAI,oBAAoB,MAAM,EAAE;EAC9B,MAAM,oBAAoB,MAAM;EAChC,MAAM,uBAAuB,MAAM;EACnC,MAAM,8BAA8B;EACpC,OAAO;;CAET,MAAM,oBAAoB,MAAM;CAChC,MAAM,uBAAuB,MAAM;CACnC,IAAI,MAAM,OACR,MAAM,0BAA0B,MAAM;CAExC,OAAO;;AAGT,SAAS,gBAAgB,EACvB,OACA,WACA,WAC6B;CAC7B,MAAM,QAAQ,YAAY,MAAM;CAChC,MAAM,QAAQ,WAAW,OAAO,WAAW,QAAQ;CAOnD,MAAM,SAAS,MAAM,UAAU,YAAY;CAC3C,MAAM,SAAS,MAAM,eAAe;CACpC,IAAI,QAAQ;EACV,OAAO,SAAS,aAAa,MAAM;EACnC,IAAI,CAAC,UAAU,SACb,OAAO,UAAU;GACf,MAAM,eAAe;GACrB,SAAS,oBAAoB,MAAM,GAAG,MAAM,QAAQ,MAAM;GAC3D,CAAC;EAEJ;;CAGF,MAAM,OAAO,OAAO,UAAU,0BAA0B,EACtD,MAAM,SAAS,UAChB,CAAC;CACF,KAAK,SAAS,aAAa,MAAM;CACjC,IAAI,CAAC,UAAU,SACb,KAAK,UAAU;EACb,MAAM,eAAe;EACrB,SAAS,oBAAoB,MAAM,GAAG,MAAM,QAAQ,MAAM;EAC3D,CAAC;CAEJ,KAAK,KAAK;;AAGZ,SAAgB,aACd,OACA,UACA,SACM;CACN,IAAI,CAAC,mBAAmB,EACtB;CAGF,IAAI,CAAC,gBAAgB,SAAS,EAC5B;CAGF,IAAI,iBAAiB,OAAO;EAC1B,IAAI,KAAK,IAAI,MAAM,EACjB;EAEF,KAAK,IAAI,MAAM;;CAGjB,gBAAgB;EACd;EACA,WAAW,SAAS,aAAa;EACjC;EACD,CAAC;;AAGJ,SAAgB,eACd,OACA,UACA,SACM;CACN,IAAI,KAAK,IAAI,MAAM,EACjB;CAEF,KAAK,IAAI,MAAM;CAEf,IAAI,CAAC,mBAAmB,EACtB;CAEF,IAAI,CAAC,gBAAgB,SAAS,EAC5B;CAGF,gBAAgB;EACd;EACA,WAAW;EACX,SAAS;GACP,WAAW;GACX,QAAQ;IACN,GAAG;IACH,GAAI,MAAM,SAAS,EAAE,aAAa,MAAM,QAAQ,GAAG,EAAE;IACtD;GACF;EACF,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/internal/server/console-bridge.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Patches `console.{debug,log,info,warn,error}` to additionally emit OTel
|
|
4
|
+
* `LogRecord`s alongside the original output. Every log record carries
|
|
5
|
+
* the active span's context (so trace ↔ log correlation works in
|
|
6
|
+
* dashboards) and any `Error` arg gets unpacked into semconv exception
|
|
7
|
+
* attrs.
|
|
8
|
+
*
|
|
9
|
+
* Returns a disposer that restores the original `console.*` methods.
|
|
10
|
+
*
|
|
11
|
+
* Cycle protection: `emitting` guards against the bridge re-entering
|
|
12
|
+
* itself if a logger backend itself logs to console (which would
|
|
13
|
+
* recursively emit forever). Failures are bounded-rate logged via the
|
|
14
|
+
* original `console.error` so a broken backend doesn't drown the
|
|
15
|
+
* customer's logs in bridge-failure messages.
|
|
16
|
+
*/
|
|
17
|
+
declare function bridgeConsoleToOtel(): () => void;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { bridgeConsoleToOtel };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console-bridge.d.mts","names":[],"sources":["../../../src/internal/server/console-bridge.ts"],"mappings":";;AAgFA;;;;;;;;;;;;;;iBAAgB,mBAAA,CAAA"}
|