@interfere/next 4.0.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/internal/build/release/index.d.mts.map +1 -1
  2. package/dist/internal/build/release/index.mjs +3 -1
  3. package/dist/internal/build/release/index.mjs.map +1 -1
  4. package/dist/internal/env.d.mts +2 -1
  5. package/dist/internal/env.d.mts.map +1 -1
  6. package/dist/internal/env.mjs +5 -1
  7. package/dist/internal/env.mjs.map +1 -1
  8. package/dist/internal/route/handle-get.d.mts +1 -1
  9. package/dist/internal/route/handle-get.d.mts.map +1 -1
  10. package/dist/internal/route/handle-get.mjs +10 -2
  11. package/dist/internal/route/handle-get.mjs.map +1 -1
  12. package/dist/internal/route/handle-post.d.mts.map +1 -1
  13. package/dist/internal/route/handle-post.mjs +5 -63
  14. package/dist/internal/route/handle-post.mjs.map +1 -1
  15. package/dist/internal/route/proxy.d.mts +18 -0
  16. package/dist/internal/route/proxy.d.mts.map +1 -0
  17. package/dist/internal/route/proxy.mjs +82 -0
  18. package/dist/internal/route/proxy.mjs.map +1 -0
  19. package/dist/internal/server/capture.d.mts.map +1 -1
  20. package/dist/internal/server/capture.mjs +4 -0
  21. package/dist/internal/server/capture.mjs.map +1 -1
  22. package/dist/internal/server/envelope.d.mts.map +1 -1
  23. package/dist/internal/server/envelope.mjs +2 -0
  24. package/dist/internal/server/envelope.mjs.map +1 -1
  25. package/dist/internal/server/remote-config.d.mts +5 -0
  26. package/dist/internal/server/remote-config.d.mts.map +1 -0
  27. package/dist/internal/server/remote-config.mjs +29 -0
  28. package/dist/internal/server/remote-config.mjs.map +1 -0
  29. package/dist/internal/version.d.mts +4 -0
  30. package/dist/internal/version.d.mts.map +1 -0
  31. package/dist/internal/version.mjs +5 -0
  32. package/dist/internal/version.mjs.map +1 -0
  33. package/dist/package.mjs +5 -0
  34. package/dist/package.mjs.map +1 -0
  35. package/dist/route-handler.d.mts +1 -1
  36. package/dist/route-handler.d.mts.map +1 -1
  37. package/dist/route-handler.mjs +7 -2
  38. package/dist/route-handler.mjs.map +1 -1
  39. package/dist/server.d.mts +2 -1
  40. package/dist/server.mjs +2 -1
  41. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/internal/build/release/index.ts"],"mappings":";;;;;iBA2BgB,qBAAA,CACd,OAAA,UACA,MAAA,EAAQ,sBAAA,GACP,oBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/internal/build/release/index.ts"],"mappings":";;;;;iBA4BgB,qBAAA,CACd,OAAA,UACA,MAAA,EAAQ,sBAAA,GACP,oBAAA"}
@@ -1,5 +1,6 @@
1
1
  import { log } from "../../logger.mjs";
2
2
  import { runGitCommand } from "./git.mjs";
3
+ import { PRODUCER_VERSION } from "../../version.mjs";
3
4
  import { vercel_exports } from "./destinations/vercel.mjs";
4
5
  import { github_exports } from "./sources/github.mjs";
5
6
  //#region src/internal/build/release/index.ts
@@ -11,7 +12,8 @@ function resolveReleaseRequest(buildId, config) {
11
12
  return {
12
13
  source: sources[config.surface.sourceProvider].resolve(),
13
14
  destination: destinations[config.surface.destinationProvider].resolve(),
14
- buildId
15
+ buildId,
16
+ producerVersion: PRODUCER_VERSION
15
17
  };
16
18
  }
17
19
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/internal/build/release/index.ts"],"sourcesContent":["import type {\n DestinationProvider,\n ReleaseDestinationMetadata,\n ReleaseSourceMetadata,\n SourceProvider,\n} from \"@interfere/types/integrations\";\n\nimport * as vercel from \"./destinations/vercel.js\";\nimport * as github from \"./sources/github.js\";\nimport type { CreateReleaseRequest } from \"@interfere/types/releases/definition\";\nimport type { ReleasesConfigResponse } from \"@interfere/sdk/models/releases-config-response.js\";\nimport { log } from \"../../logger.js\";\n\nexport { runGitCommand } from \"./git.js\";\n\ninterface Resolver<T> {\n resolve(): T;\n}\n\nconst sources: Record<SourceProvider, Resolver<ReleaseSourceMetadata>> = {\n github,\n};\n\nconst destinations: Record<DestinationProvider, Resolver<ReleaseDestinationMetadata>> = {\n vercel,\n};\n\nexport function resolveReleaseRequest(\n buildId: string,\n config: ReleasesConfigResponse\n): CreateReleaseRequest {\n if (!config.surface.sourceProvider) {\n return log.fatal(\"Missing version control provider\", [\n \"This surface does not have a version control provider configured.\",\n `Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`,\n ]);\n }\n\n if (!config.surface.destinationProvider) {\n return log.fatal(\"Missing deployment target\", [\n \"This surface does not have a deployment target integration configured.\",\n `Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`,\n ]);\n }\n\n return {\n source: sources[config.surface.sourceProvider].resolve(),\n destination: destinations[config.surface.destinationProvider].resolve(),\n buildId,\n };\n}\n"],"mappings":";;;;;AAmBA,MAAM,UAAmE,EACvE,QAAA,gBACD;AAED,MAAM,eAAkF,EACtF,QAAA,gBACD;AAED,SAAgB,sBACd,SACA,QACsB;AACtB,KAAI,CAAC,OAAO,QAAQ,eAClB,QAAO,IAAI,MAAM,oCAAoC,CACnD,qEACA,4CAA4C,OAAO,IAAI,KAAK,YAAY,OAAO,QAAQ,OACxF,CAAC;AAGJ,KAAI,CAAC,OAAO,QAAQ,oBAClB,QAAO,IAAI,MAAM,6BAA6B,CAC5C,0EACA,4CAA4C,OAAO,IAAI,KAAK,YAAY,OAAO,QAAQ,OACxF,CAAC;AAGJ,QAAO;EACL,QAAQ,QAAQ,OAAO,QAAQ,gBAAgB,SAAS;EACxD,aAAa,aAAa,OAAO,QAAQ,qBAAqB,SAAS;EACvE;EACD"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/internal/build/release/index.ts"],"sourcesContent":["import type {\n DestinationProvider,\n ReleaseDestinationMetadata,\n ReleaseSourceMetadata,\n SourceProvider,\n} from \"@interfere/types/integrations\";\n\nimport * as vercel from \"./destinations/vercel.js\";\nimport * as github from \"./sources/github.js\";\nimport type { CreateReleaseRequest } from \"@interfere/types/releases/definition\";\nimport type { ReleasesConfigResponse } from \"@interfere/sdk/models/releases-config-response.js\";\nimport { log } from \"../../logger.js\";\nimport { PRODUCER_VERSION } from \"../../version.js\";\n\nexport { runGitCommand } from \"./git.js\";\n\ninterface Resolver<T> {\n resolve(): T;\n}\n\nconst sources: Record<SourceProvider, Resolver<ReleaseSourceMetadata>> = {\n github,\n};\n\nconst destinations: Record<DestinationProvider, Resolver<ReleaseDestinationMetadata>> = {\n vercel,\n};\n\nexport function resolveReleaseRequest(\n buildId: string,\n config: ReleasesConfigResponse\n): CreateReleaseRequest {\n if (!config.surface.sourceProvider) {\n return log.fatal(\"Missing version control provider\", [\n \"This surface does not have a version control provider configured.\",\n `Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`,\n ]);\n }\n\n if (!config.surface.destinationProvider) {\n return log.fatal(\"Missing deployment target\", [\n \"This surface does not have a deployment target integration configured.\",\n `Configure one at https://interfere.com/~/${config.org.slug}/surfaces/${config.surface.slug}`,\n ]);\n }\n\n return {\n source: sources[config.surface.sourceProvider].resolve(),\n destination: destinations[config.surface.destinationProvider].resolve(),\n buildId,\n producerVersion: PRODUCER_VERSION,\n };\n}\n"],"mappings":";;;;;;AAoBA,MAAM,UAAmE,EACvE,QAAA,gBACD;AAED,MAAM,eAAkF,EACtF,QAAA,gBACD;AAED,SAAgB,sBACd,SACA,QACsB;AACtB,KAAI,CAAC,OAAO,QAAQ,eAClB,QAAO,IAAI,MAAM,oCAAoC,CACnD,qEACA,4CAA4C,OAAO,IAAI,KAAK,YAAY,OAAO,QAAQ,OACxF,CAAC;AAGJ,KAAI,CAAC,OAAO,QAAQ,oBAClB,QAAO,IAAI,MAAM,6BAA6B,CAC5C,0EACA,4CAA4C,OAAO,IAAI,KAAK,YAAY,OAAO,QAAQ,OACxF,CAAC;AAGJ,QAAO;EACL,QAAQ,QAAQ,OAAO,QAAQ,gBAAgB,SAAS;EACxD,aAAa,aAAa,OAAO,QAAQ,qBAAqB,SAAS;EACvE;EACA,iBAAiB;EAClB"}
@@ -11,6 +11,7 @@ interface InterfereEnv {
11
11
  readonly destinationId: string | null;
12
12
  };
13
13
  }
14
+ declare function isEnabledInEnvironment(): boolean;
14
15
  declare function readInterfereEnv(): InterfereEnv;
15
16
  //#endregion
16
- export { InterfereEnv, readInterfereEnv };
17
+ export { InterfereEnv, isEnabledInEnvironment, readInterfereEnv };
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.mts","names":[],"sources":["../../src/internal/env.ts"],"mappings":";;;UAKiB,YAAA;EAAA,SACN,MAAA;EAAA,SACA,MAAA;EAAA,SACA,WAAA;EAAA,SACA,eAAA,EAAiB,OAAA,CAAQ,GAAA;EAAA,SACzB,OAAA;IAAA,SACE,QAAA;IAAA,SACA,aAAA;EAAA;AAAA;AAAA,iBAIG,gBAAA,CAAA,GAAoB,YAAA"}
1
+ {"version":3,"file":"env.d.mts","names":[],"sources":["../../src/internal/env.ts"],"mappings":";;;UAKiB,YAAA;EAAA,SACN,MAAA;EAAA,SACA,MAAA;EAAA,SACA,WAAA;EAAA,SACA,eAAA,EAAiB,OAAA,CAAQ,GAAA;EAAA,SACzB,OAAA;IAAA,SACE,QAAA;IAAA,SACA,aAAA;EAAA;AAAA;AAAA,iBAIG,sBAAA,CAAA;AAAA,iBAOA,gBAAA,CAAA,GAAoB,YAAA"}
@@ -2,6 +2,10 @@ import { API_URL } from "@interfere/constants/api";
2
2
  import { parseEnvValue } from "@interfere/types/sdk/env";
3
3
  import { normalizeEnv } from "@interfere/types/sdk/runtime";
4
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
+ }
5
9
  function readInterfereEnv() {
6
10
  const nodeEnvironment = normalizeEnv(process.env.NODE_ENV) ?? "production";
7
11
  return {
@@ -16,4 +20,4 @@ function readInterfereEnv() {
16
20
  };
17
21
  }
18
22
  //#endregion
19
- export { readInterfereEnv };
23
+ export { isEnabledInEnvironment, 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: Exclude<Env, null>;\n readonly release: {\n readonly sourceId: string | null;\n readonly destinationId: string | null;\n };\n}\n\nexport function readInterfereEnv(): InterfereEnv {\n const nodeEnvironment = normalizeEnv(process.env.NODE_ENV) ?? \"production\";\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 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":";;;;AAgBA,SAAgB,mBAAiC;CAC/C,MAAM,kBAAkB,aAAa,QAAQ,IAAI,SAAS,IAAI;AAE9D,QAAO;EACL,QAAQ,cAAc,QAAQ,IAAI,kBAAkB;EACpD,QAAQ,cAAc,QAAQ,IAAI,kBAAkB,IAAI;EACxD,aAAa,cAAc,QAAQ,IAAI,aAAa;EACpD;EACA,SAAS;GACP,UAAU,cAAc,QAAQ,IAAI,+BAA+B;GACnE,eAAe,cACb,QAAQ,IAAI,iCACb;GACF;EACF"}
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: Exclude<Env, null>;\n readonly release: {\n readonly sourceId: string | null;\n readonly destinationId: string | null;\n };\n}\n\nexport function isEnabledInEnvironment(): 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) ?? \"production\";\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 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":";;;;AAgBA,SAAgB,yBAAkC;AAChD,KAAI,QAAQ,IAAI,aAAa,aAC3B,QAAO;AAET,QAAO,CAAC,CAAC,QAAQ,IAAI;;AAGvB,SAAgB,mBAAiC;CAC/C,MAAM,kBAAkB,aAAa,QAAQ,IAAI,SAAS,IAAI;AAE9D,QAAO;EACL,QAAQ,cAAc,QAAQ,IAAI,kBAAkB;EACpD,QAAQ,cAAc,QAAQ,IAAI,kBAAkB,IAAI;EACxD,aAAa,cAAc,QAAQ,IAAI,aAAa;EACpD;EACA,SAAS;GACP,UAAU,cAAc,QAAQ,IAAI,+BAA+B;GACnE,eAAe,cACb,QAAQ,IAAI,iCACb;GACF;EACF"}
@@ -1,4 +1,4 @@
1
1
  //#region src/internal/route/handle-get.d.ts
2
- declare function handleGet(request: Request): Response;
2
+ declare function handleGet(request: Request): Response | Promise<Response>;
3
3
  //#endregion
4
4
  export { handleGet };
@@ -1 +1 @@
1
- {"version":3,"file":"handle-get.d.mts","names":[],"sources":["../../../src/internal/route/handle-get.ts"],"mappings":";iBAEgB,SAAA,CAAU,OAAA,EAAS,OAAA,GAAU,QAAA"}
1
+ {"version":3,"file":"handle-get.d.mts","names":[],"sources":["../../../src/internal/route/handle-get.ts"],"mappings":";iBAUgB,SAAA,CAAU,OAAA,EAAS,OAAA,GAAU,QAAA,GAAW,OAAA,CAAQ,QAAA"}
@@ -1,8 +1,10 @@
1
+ import { extractSubPath, forwardToCollector, notConfiguredResponse, resolveAuthenticatedEnv } from "./proxy.mjs";
1
2
  import { SW_SCRIPT } from "./sw-script.mjs";
3
+ import { API_PATHS } from "@interfere/constants/api";
2
4
  //#region src/internal/route/handle-get.ts
3
5
  function handleGet(request) {
4
- if (!new URL(request.url).pathname.endsWith("/sw")) return new Response("Not Found", { status: 404 });
5
- return new Response(SW_SCRIPT, {
6
+ const subPath = extractSubPath(request);
7
+ if (subPath.endsWith("/sw")) return new Response(SW_SCRIPT, {
6
8
  status: 200,
7
9
  headers: {
8
10
  "content-type": "application/javascript",
@@ -10,6 +12,12 @@ function handleGet(request) {
10
12
  "cache-control": "public, max-age=3600"
11
13
  }
12
14
  });
15
+ if (subPath === API_PATHS.CONFIG) {
16
+ const env = resolveAuthenticatedEnv();
17
+ if (!env) return notConfiguredResponse();
18
+ return forwardToCollector(request, env, subPath);
19
+ }
20
+ return new Response("Not Found", { status: 404 });
13
21
  }
14
22
  //#endregion
15
23
  export { handleGet };
@@ -1 +1 @@
1
- {"version":3,"file":"handle-get.mjs","names":[],"sources":["../../../src/internal/route/handle-get.ts"],"sourcesContent":["import { SW_SCRIPT } from \"./sw-script.js\";\n\nexport function handleGet(request: Request): Response {\n const pathname = new URL(request.url).pathname;\n if (!pathname.endsWith(\"/sw\")) {\n return new Response(\"Not Found\", { status: 404 });\n }\n\n return new Response(SW_SCRIPT, {\n status: 200,\n headers: {\n \"content-type\": \"application/javascript\",\n \"service-worker-allowed\": \"/\",\n \"cache-control\": \"public, max-age=3600\",\n },\n });\n}\n"],"mappings":";;AAEA,SAAgB,UAAU,SAA4B;AAEpD,KAAI,CADa,IAAI,IAAI,QAAQ,IAAI,CAAC,SACxB,SAAS,MAAM,CAC3B,QAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,KAAK,CAAC;AAGnD,QAAO,IAAI,SAAS,WAAW;EAC7B,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,0BAA0B;GAC1B,iBAAiB;GAClB;EACF,CAAC"}
1
+ {"version":3,"file":"handle-get.mjs","names":[],"sources":["../../../src/internal/route/handle-get.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\n\nimport {\n extractSubPath,\n forwardToCollector,\n notConfiguredResponse,\n resolveAuthenticatedEnv,\n} from \"./proxy.js\";\nimport { SW_SCRIPT } from \"./sw-script.js\";\n\nexport function handleGet(request: Request): Response | Promise<Response> {\n const subPath = extractSubPath(request);\n\n if (subPath.endsWith(\"/sw\")) {\n return new Response(SW_SCRIPT, {\n status: 200,\n headers: {\n \"content-type\": \"application/javascript\",\n \"service-worker-allowed\": \"/\",\n \"cache-control\": \"public, max-age=3600\",\n },\n });\n }\n\n if (subPath === API_PATHS.CONFIG) {\n const env = resolveAuthenticatedEnv();\n if (!env) {\n return notConfiguredResponse();\n }\n return forwardToCollector(request, env, subPath);\n }\n\n return new Response(\"Not Found\", { status: 404 });\n}\n"],"mappings":";;;;AAUA,SAAgB,UAAU,SAAgD;CACxE,MAAM,UAAU,eAAe,QAAQ;AAEvC,KAAI,QAAQ,SAAS,MAAM,CACzB,QAAO,IAAI,SAAS,WAAW;EAC7B,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,0BAA0B;GAC1B,iBAAiB;GAClB;EACF,CAAC;AAGJ,KAAI,YAAY,UAAU,QAAQ;EAChC,MAAM,MAAM,yBAAyB;AACrC,MAAI,CAAC,IACH,QAAO,uBAAuB;AAEhC,SAAO,mBAAmB,SAAS,KAAK,QAAQ;;AAGlD,QAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,KAAK,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"handle-post.d.mts","names":[],"sources":["../../../src/internal/route/handle-post.ts"],"mappings":";iBA2EsB,UAAA,CAAW,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,QAAA"}
1
+ {"version":3,"file":"handle-post.d.mts","names":[],"sources":["../../../src/internal/route/handle-post.ts"],"mappings":";iBAoCsB,UAAA,CAAW,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,QAAA"}
@@ -1,5 +1,5 @@
1
1
  import { log } from "../logger.mjs";
2
- import { readInterfereEnv } from "../env.mjs";
2
+ import { extractSubPath, formatProxyError, forwardToCollector, notConfiguredResponse, resolveAuthenticatedEnv } from "./proxy.mjs";
3
3
  import { API_PATHS } from "@interfere/constants/api";
4
4
  //#region src/internal/route/handle-post.ts
5
5
  function parseEnvelopes(value) {
@@ -14,44 +14,13 @@ function injectReleaseMetadata(envelopes, metadata) {
14
14
  releaseId: metadata.destinationId ?? envelope.releaseId
15
15
  }));
16
16
  }
17
- function formatProxyError(error) {
18
- if (!(error instanceof Error)) return {
19
- message: String(error),
20
- lines: [String(error)]
21
- };
22
- const lines = [`${error.name}: ${error.message}`];
23
- if ("cause" in error && error.cause) {
24
- const cause = error.cause instanceof Error ? error.cause.message : String(error.cause);
25
- lines.push(`cause: ${cause}`);
26
- }
27
- if ("code" in error && typeof error.code === "string") lines.push(`code: ${error.code}`);
28
- return {
29
- message: `${error.name}: ${error.message}`,
30
- lines
31
- };
32
- }
33
- const PROXY_PATH_PATTERN = /\/api\/interfere(\/.*)/;
34
- function extractSubPath(request) {
35
- return new URL(request.url).pathname.match(PROXY_PATH_PATTERN)?.[1] ?? "/";
36
- }
37
17
  async function handlePost(request) {
38
- const env = readInterfereEnv();
39
- if (env.apiKey === null) {
40
- log.warn("Not configured", ["INTERFERE_API_KEY is not set. The proxy route will return 503."]);
41
- return Response.json({
42
- code: "INTERFERE_NOT_CONFIGURED",
43
- message: "INTERFERE_API_KEY is required."
44
- }, { status: 503 });
45
- }
46
- const authed = {
47
- apiKey: env.apiKey,
48
- apiUrl: env.apiUrl,
49
- release: env.release
50
- };
18
+ const env = resolveAuthenticatedEnv();
19
+ if (!env) return notConfiguredResponse();
51
20
  const subPath = extractSubPath(request);
52
21
  try {
53
- if (subPath === API_PATHS.INGEST) return await handleIngest(request, authed);
54
- return await forwardToCollector(request, authed, subPath);
22
+ if (subPath === API_PATHS.INGEST) return await handleIngest(request, env);
23
+ return await forwardToCollector(request, env, subPath);
55
24
  } catch (error) {
56
25
  const detail = formatProxyError(error);
57
26
  log.error(`Proxy ${request.method} ${subPath} failed`, detail.lines);
@@ -92,32 +61,5 @@ async function handleIngest(request, env) {
92
61
  headers: { "content-type": upstream.headers.get("content-type") ?? "application/json" }
93
62
  });
94
63
  }
95
- async function forwardToCollector(request, env, subPath) {
96
- const url = `${env.apiUrl}${subPath}`;
97
- const traceparent = request.headers.get("traceparent");
98
- const body = await request.text();
99
- const upstream = await fetch(url, {
100
- method: request.method,
101
- headers: {
102
- "content-type": request.headers.get("content-type") ?? "application/json",
103
- "x-api-key": env.apiKey,
104
- ...traceparent ? { traceparent } : {}
105
- },
106
- body,
107
- signal: AbortSignal.timeout(1e4)
108
- });
109
- if (!upstream.ok) {
110
- const body = await upstream.text().catch(() => "");
111
- log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [body]);
112
- return Response.json({
113
- code: "INTERFERE_UPSTREAM_ERROR",
114
- message: body
115
- }, { status: upstream.status });
116
- }
117
- return new Response(upstream.body, {
118
- status: upstream.status,
119
- headers: { "content-type": upstream.headers.get("content-type") ?? "application/json" }
120
- });
121
- }
122
64
  //#endregion
123
65
  export { handlePost };
@@ -1 +1 @@
1
- {"version":3,"file":"handle-post.mjs","names":[],"sources":["../../../src/internal/route/handle-post.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\nimport type { Envelope } from \"@interfere/types/sdk/envelope\";\n\nimport { type InterfereEnv, readInterfereEnv } from \"../env.js\";\nimport { log } from \"../logger.js\";\n\nfunction parseEnvelopes(value: unknown): Envelope[] | null {\n if (!Array.isArray(value)) {\n return null;\n }\n\n return value as Envelope[];\n}\n\nfunction injectReleaseMetadata(\n envelopes: Envelope[],\n metadata: { sourceId: string | null; destinationId: string | null }\n): Envelope[] {\n if (metadata.sourceId === null && metadata.destinationId === null) {\n return envelopes;\n }\n\n return envelopes.map((envelope) => ({\n ...envelope,\n buildId: metadata.sourceId ?? envelope.buildId,\n releaseId: metadata.destinationId ?? envelope.releaseId,\n }));\n}\n\nfunction formatProxyError(error: unknown): {\n message: string;\n lines: string[];\n} {\n if (!(error instanceof Error)) {\n return {\n message: String(error),\n lines: [String(error)],\n };\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 {\n message: `${error.name}: ${error.message}`,\n lines,\n };\n}\n\nconst PROXY_PATH_PATTERN = /\\/api\\/interfere(\\/.*)/;\n\nfunction 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\ninterface AuthenticatedEnv {\n apiKey: string;\n apiUrl: string;\n release: InterfereEnv[\"release\"];\n}\n\nexport async function handlePost(request: Request): Promise<Response> {\n const env = readInterfereEnv();\n\n if (env.apiKey === null) {\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\n const authed: AuthenticatedEnv = {\n apiKey: env.apiKey,\n apiUrl: env.apiUrl,\n release: env.release,\n };\n\n const subPath = extractSubPath(request);\n\n try {\n if (subPath === API_PATHS.INGEST) {\n return await handleIngest(request, authed);\n }\n\n return await forwardToCollector(request, authed, subPath);\n } catch (error) {\n const detail = formatProxyError(error);\n log.error(`Proxy ${request.method} ${subPath} failed`, detail.lines);\n return Response.json(\n {\n code: \"INTERFERE_PROXY_ERROR\",\n message: detail.message,\n },\n { status: 502 }\n );\n }\n}\n\nasync function handleIngest(\n request: Request,\n env: AuthenticatedEnv\n): Promise<Response> {\n let payload: unknown;\n try {\n payload = await request.json();\n } catch {\n return Response.json(\n {\n code: \"INTERFERE_INVALID_JSON\",\n message: \"Request body must be valid JSON.\",\n },\n { status: 400 }\n );\n }\n\n const envelopes = parseEnvelopes(payload);\n if (envelopes === null) {\n return Response.json(\n {\n code: \"INTERFERE_INVALID_ENVELOPES\",\n message: \"Request body must be an array of envelopes.\",\n },\n { status: 400 }\n );\n }\n\n const traceparent = request.headers.get(\"traceparent\");\n const upstream = await fetch(`${env.apiUrl}${API_PATHS.INGEST}`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n \"x-api-key\": env.apiKey,\n ...(traceparent ? { traceparent } : {}),\n },\n body: JSON.stringify(injectReleaseMetadata(envelopes, env.release)),\n signal: AbortSignal.timeout(10_000),\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\nasync 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 body = await request.text();\n\n const upstream = await fetch(url, {\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 },\n body,\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!upstream.ok) {\n const body = await upstream.text().catch(() => \"\");\n log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [\n body,\n ]);\n return Response.json(\n { code: \"INTERFERE_UPSTREAM_ERROR\", message: body },\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,SAAS,eAAe,OAAmC;AACzD,KAAI,CAAC,MAAM,QAAQ,MAAM,CACvB,QAAO;AAGT,QAAO;;AAGT,SAAS,sBACP,WACA,UACY;AACZ,KAAI,SAAS,aAAa,QAAQ,SAAS,kBAAkB,KAC3D,QAAO;AAGT,QAAO,UAAU,KAAK,cAAc;EAClC,GAAG;EACH,SAAS,SAAS,YAAY,SAAS;EACvC,WAAW,SAAS,iBAAiB,SAAS;EAC/C,EAAE;;AAGL,SAAS,iBAAiB,OAGxB;AACA,KAAI,EAAE,iBAAiB,OACrB,QAAO;EACL,SAAS,OAAO,MAAM;EACtB,OAAO,CAAC,OAAO,MAAM,CAAC;EACvB;CAGH,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;EACL,SAAS,GAAG,MAAM,KAAK,IAAI,MAAM;EACjC;EACD;;AAGH,MAAM,qBAAqB;AAE3B,SAAS,eAAe,SAA0B;AAGhD,QAFY,IAAI,IAAI,QAAQ,IAAI,CACd,SAAS,MAAM,mBAAmB,GACrC,MAAM;;AASvB,eAAsB,WAAW,SAAqC;CACpE,MAAM,MAAM,kBAAkB;AAE9B,KAAI,IAAI,WAAW,MAAM;AACvB,MAAI,KAAK,kBAAkB,CACzB,iEACD,CAAC;AAEF,SAAO,SAAS,KACd;GACE,MAAM;GACN,SAAS;GACV,EACD,EAAE,QAAQ,KAAK,CAChB;;CAGH,MAAM,SAA2B;EAC/B,QAAQ,IAAI;EACZ,QAAQ,IAAI;EACZ,SAAS,IAAI;EACd;CAED,MAAM,UAAU,eAAe,QAAQ;AAEvC,KAAI;AACF,MAAI,YAAY,UAAU,OACxB,QAAO,MAAM,aAAa,SAAS,OAAO;AAG5C,SAAO,MAAM,mBAAmB,SAAS,QAAQ,QAAQ;UAClD,OAAO;EACd,MAAM,SAAS,iBAAiB,MAAM;AACtC,MAAI,MAAM,SAAS,QAAQ,OAAO,GAAG,QAAQ,UAAU,OAAO,MAAM;AACpE,SAAO,SAAS,KACd;GACE,MAAM;GACN,SAAS,OAAO;GACjB,EACD,EAAE,QAAQ,KAAK,CAChB;;;AAIL,eAAe,aACb,SACA,KACmB;CACnB,IAAI;AACJ,KAAI;AACF,YAAU,MAAM,QAAQ,MAAM;SACxB;AACN,SAAO,SAAS,KACd;GACE,MAAM;GACN,SAAS;GACV,EACD,EAAE,QAAQ,KAAK,CAChB;;CAGH,MAAM,YAAY,eAAe,QAAQ;AACzC,KAAI,cAAc,KAChB,QAAO,SAAS,KACd;EACE,MAAM;EACN,SAAS;EACV,EACD,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,cAAc,QAAQ,QAAQ,IAAI,cAAc;CACtD,MAAM,WAAW,MAAM,MAAM,GAAG,IAAI,SAAS,UAAU,UAAU;EAC/D,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,aAAa,IAAI;GACjB,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;GACvC;EACD,MAAM,KAAK,UAAU,sBAAsB,WAAW,IAAI,QAAQ,CAAC;EACnE,QAAQ,YAAY,QAAQ,IAAO;EACpC,CAAC;AAEF,QAAO,IAAI,SAAS,SAAS,MAAM;EACjC,QAAQ,SAAS;EACjB,SAAS,EACP,gBACE,SAAS,QAAQ,IAAI,eAAe,IAAI,oBAC3C;EACF,CAAC;;AAGJ,eAAe,mBACb,SACA,KACA,SACmB;CACnB,MAAM,MAAM,GAAG,IAAI,SAAS;CAC5B,MAAM,cAAc,QAAQ,QAAQ,IAAI,cAAc;CACtD,MAAM,OAAO,MAAM,QAAQ,MAAM;CAEjC,MAAM,WAAW,MAAM,MAAM,KAAK;EAChC,QAAQ,QAAQ;EAChB,SAAS;GACP,gBAAgB,QAAQ,QAAQ,IAAI,eAAe,IAAI;GACvD,aAAa,IAAI;GACjB,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;GACvC;EACD;EACA,QAAQ,YAAY,QAAQ,IAAO;EACpC,CAAC;AAEF,KAAI,CAAC,SAAS,IAAI;EAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAClD,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":"handle-post.mjs","names":[],"sources":["../../../src/internal/route/handle-post.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\nimport type { Envelope } from \"@interfere/types/sdk/envelope\";\n\nimport { log } from \"../logger.js\";\nimport {\n type AuthenticatedEnv,\n extractSubPath,\n formatProxyError,\n forwardToCollector,\n notConfiguredResponse,\n resolveAuthenticatedEnv,\n} from \"./proxy.js\";\n\nfunction parseEnvelopes(value: unknown): Envelope[] | null {\n if (!Array.isArray(value)) {\n return null;\n }\n\n return value as Envelope[];\n}\n\nfunction injectReleaseMetadata(\n envelopes: Envelope[],\n metadata: { sourceId: string | null; destinationId: string | null }\n): Envelope[] {\n if (metadata.sourceId === null && metadata.destinationId === null) {\n return envelopes;\n }\n\n return envelopes.map((envelope) => ({\n ...envelope,\n buildId: metadata.sourceId ?? envelope.buildId,\n releaseId: metadata.destinationId ?? envelope.releaseId,\n }));\n}\n\nexport async function handlePost(request: Request): Promise<Response> {\n const env = resolveAuthenticatedEnv();\n if (!env) {\n return notConfiguredResponse();\n }\n\n const subPath = extractSubPath(request);\n\n try {\n if (subPath === API_PATHS.INGEST) {\n return await handleIngest(request, env);\n }\n\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\nasync function handleIngest(\n request: Request,\n env: AuthenticatedEnv\n): Promise<Response> {\n let payload: unknown;\n try {\n payload = await request.json();\n } catch {\n return Response.json(\n {\n code: \"INTERFERE_INVALID_JSON\",\n message: \"Request body must be valid JSON.\",\n },\n { status: 400 }\n );\n }\n\n const envelopes = parseEnvelopes(payload);\n if (envelopes === null) {\n return Response.json(\n {\n code: \"INTERFERE_INVALID_ENVELOPES\",\n message: \"Request body must be an array of envelopes.\",\n },\n { status: 400 }\n );\n }\n\n const traceparent = request.headers.get(\"traceparent\");\n const upstream = await fetch(`${env.apiUrl}${API_PATHS.INGEST}`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n \"x-api-key\": env.apiKey,\n ...(traceparent ? { traceparent } : {}),\n },\n body: JSON.stringify(injectReleaseMetadata(envelopes, env.release)),\n signal: AbortSignal.timeout(10_000),\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":";;;;AAaA,SAAS,eAAe,OAAmC;AACzD,KAAI,CAAC,MAAM,QAAQ,MAAM,CACvB,QAAO;AAGT,QAAO;;AAGT,SAAS,sBACP,WACA,UACY;AACZ,KAAI,SAAS,aAAa,QAAQ,SAAS,kBAAkB,KAC3D,QAAO;AAGT,QAAO,UAAU,KAAK,cAAc;EAClC,GAAG;EACH,SAAS,SAAS,YAAY,SAAS;EACvC,WAAW,SAAS,iBAAiB,SAAS;EAC/C,EAAE;;AAGL,eAAsB,WAAW,SAAqC;CACpE,MAAM,MAAM,yBAAyB;AACrC,KAAI,CAAC,IACH,QAAO,uBAAuB;CAGhC,MAAM,UAAU,eAAe,QAAQ;AAEvC,KAAI;AACF,MAAI,YAAY,UAAU,OACxB,QAAO,MAAM,aAAa,SAAS,IAAI;AAGzC,SAAO,MAAM,mBAAmB,SAAS,KAAK,QAAQ;UAC/C,OAAO;EACd,MAAM,SAAS,iBAAiB,MAAM;AACtC,MAAI,MAAM,SAAS,QAAQ,OAAO,GAAG,QAAQ,UAAU,OAAO,MAAM;AACpE,SAAO,SAAS,KACd;GAAE,MAAM;GAAyB,SAAS,OAAO;GAAS,EAC1D,EAAE,QAAQ,KAAK,CAChB;;;AAIL,eAAe,aACb,SACA,KACmB;CACnB,IAAI;AACJ,KAAI;AACF,YAAU,MAAM,QAAQ,MAAM;SACxB;AACN,SAAO,SAAS,KACd;GACE,MAAM;GACN,SAAS;GACV,EACD,EAAE,QAAQ,KAAK,CAChB;;CAGH,MAAM,YAAY,eAAe,QAAQ;AACzC,KAAI,cAAc,KAChB,QAAO,SAAS,KACd;EACE,MAAM;EACN,SAAS;EACV,EACD,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,cAAc,QAAQ,QAAQ,IAAI,cAAc;CACtD,MAAM,WAAW,MAAM,MAAM,GAAG,IAAI,SAAS,UAAU,UAAU;EAC/D,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,aAAa,IAAI;GACjB,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;GACvC;EACD,MAAM,KAAK,UAAU,sBAAsB,WAAW,IAAI,QAAQ,CAAC;EACnE,QAAQ,YAAY,QAAQ,IAAO;EACpC,CAAC;AAEF,QAAO,IAAI,SAAS,SAAS,MAAM;EACjC,QAAQ,SAAS;EACjB,SAAS,EACP,gBACE,SAAS,QAAQ,IAAI,eAAe,IAAI,oBAC3C;EACF,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { InterfereEnv } from "../env.mjs";
2
+
3
+ //#region src/internal/route/proxy.d.ts
4
+ interface AuthenticatedEnv {
5
+ apiKey: string;
6
+ apiUrl: string;
7
+ release: InterfereEnv["release"];
8
+ }
9
+ declare function resolveAuthenticatedEnv(): AuthenticatedEnv | null;
10
+ declare function extractSubPath(request: Request): string;
11
+ declare function notConfiguredResponse(): Response;
12
+ declare function formatProxyError(error: unknown): {
13
+ message: string;
14
+ lines: string[];
15
+ };
16
+ declare function forwardToCollector(request: Request, env: AuthenticatedEnv, subPath: string): Promise<Response>;
17
+ //#endregion
18
+ export { AuthenticatedEnv, extractSubPath, formatProxyError, forwardToCollector, notConfiguredResponse, resolveAuthenticatedEnv };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.mts","names":[],"sources":["../../../src/internal/route/proxy.ts"],"mappings":";;;UAKiB,gBAAA;EACf,MAAA;EACA,MAAA;EACA,OAAA,EAAS,YAAA;AAAA;AAAA,iBAGK,uBAAA,CAAA,GAA2B,gBAAA;AAAA,iBAQ3B,cAAA,CAAe,OAAA,EAAS,OAAA;AAAA,iBAMxB,qBAAA,CAAA,GAAyB,QAAA;AAAA,iBAazB,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"}
@@ -0,0 +1,82 @@
1
+ import { log } from "../logger.mjs";
2
+ import { readInterfereEnv } from "../env.mjs";
3
+ //#region src/internal/route/proxy.ts
4
+ const PROXY_PATH_PATTERN = /\/api\/interfere(\/.*)/;
5
+ function resolveAuthenticatedEnv() {
6
+ const env = readInterfereEnv();
7
+ if (env.apiKey === null) return null;
8
+ return {
9
+ apiKey: env.apiKey,
10
+ apiUrl: env.apiUrl,
11
+ release: env.release
12
+ };
13
+ }
14
+ function extractSubPath(request) {
15
+ return new URL(request.url).pathname.match(PROXY_PATH_PATTERN)?.[1] ?? "/";
16
+ }
17
+ function notConfiguredResponse() {
18
+ log.warn("Not configured", ["INTERFERE_API_KEY is not set. The proxy route will return 503."]);
19
+ return Response.json({
20
+ code: "INTERFERE_NOT_CONFIGURED",
21
+ message: "INTERFERE_API_KEY is required."
22
+ }, { status: 503 });
23
+ }
24
+ function formatProxyError(error) {
25
+ if (!(error instanceof Error)) return {
26
+ message: String(error),
27
+ lines: [String(error)]
28
+ };
29
+ const lines = [`${error.name}: ${error.message}`];
30
+ if ("cause" in error && error.cause) {
31
+ const cause = error.cause instanceof Error ? error.cause.message : String(error.cause);
32
+ lines.push(`cause: ${cause}`);
33
+ }
34
+ if ("code" in error && typeof error.code === "string") lines.push(`code: ${error.code}`);
35
+ return {
36
+ message: `${error.name}: ${error.message}`,
37
+ lines
38
+ };
39
+ }
40
+ async function forwardToCollector(request, env, subPath) {
41
+ const url = `${env.apiUrl}${subPath}`;
42
+ const traceparent = request.headers.get("traceparent");
43
+ const body = request.method !== "GET" && request.method !== "HEAD" ? await request.text() : void 0;
44
+ const upstream = await fetch(url, {
45
+ method: request.method,
46
+ headers: {
47
+ "content-type": request.headers.get("content-type") ?? "application/json",
48
+ "x-api-key": env.apiKey,
49
+ ...traceparent ? { traceparent } : {}
50
+ },
51
+ ...body === void 0 ? {} : { body },
52
+ signal: AbortSignal.timeout(1e4)
53
+ });
54
+ if (!upstream.ok) {
55
+ const text = await upstream.text().catch(() => "");
56
+ if (upstream.status >= 500) {
57
+ log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [text]);
58
+ return Response.json({
59
+ code: "INTERFERE_UPSTREAM_ERROR",
60
+ message: text
61
+ }, { status: upstream.status });
62
+ }
63
+ if (upstream.status >= 400) {
64
+ log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [text]);
65
+ return Response.json({
66
+ code: "INTERFERE_UPSTREAM_WARNING",
67
+ message: text
68
+ }, { status: upstream.status });
69
+ }
70
+ log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`, [text]);
71
+ return Response.json({
72
+ code: "INTERFERE_UPSTREAM_ERROR",
73
+ message: text
74
+ }, { status: upstream.status });
75
+ }
76
+ return new Response(upstream.body, {
77
+ status: upstream.status,
78
+ headers: { "content-type": upstream.headers.get("content-type") ?? "application/json" }
79
+ });
80
+ }
81
+ //#endregion
82
+ export { extractSubPath, formatProxyError, forwardToCollector, notConfiguredResponse, resolveAuthenticatedEnv };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.mjs","names":[],"sources":["../../../src/internal/route/proxy.ts"],"sourcesContent":["import { type InterfereEnv, readInterfereEnv } from \"../env.js\";\nimport { log } from \"../logger.js\";\n\nconst PROXY_PATH_PATTERN = /\\/api\\/interfere(\\/.*)/;\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 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 },\n ...(body === undefined ? {} : { body }),\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":";;;AAGA,MAAM,qBAAqB;AAQ3B,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,QAFY,IAAI,IAAI,QAAQ,IAAI,CACd,SAAS,MAAM,mBAAmB,GACrC,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,QAAQ,QAAQ;EAChB,SAAS;GACP,gBAAgB,QAAQ,QAAQ,IAAI,eAAe,IAAI;GACvD,aAAa,IAAI;GACjB,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;GACvC;EACD,GAAI,SAAS,KAAA,IAAY,EAAE,GAAG,EAAE,MAAM;EACtC,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 +1 @@
1
- {"version":3,"file":"capture.d.mts","names":[],"sources":["../../../src/internal/server/capture.ts"],"mappings":";;;iBAqBsB,YAAA,CACpB,KAAA,WACA,OAAA,YACA,OAAA,GAAU,mBAAA,GACT,OAAA;AAAA,iBA4BmB,cAAA,CACpB,KAAA,EAAO,KAAA;EAAU,MAAA;AAAA,GACjB,OAAA,WACA,OAAA,EAAS,qBAAA,GACR,OAAA"}
1
+ {"version":3,"file":"capture.d.mts","names":[],"sources":["../../../src/internal/server/capture.ts"],"mappings":";;;iBAuBsB,YAAA,CACpB,KAAA,WACA,OAAA,YACA,OAAA,GAAU,mBAAA,GACT,OAAA;AAAA,iBAoCmB,cAAA,CACpB,KAAA,EAAO,KAAA;EAAU,MAAA;AAAA,GACjB,OAAA,WACA,OAAA,EAAS,qBAAA,GACR,OAAA"}
@@ -1,6 +1,8 @@
1
+ import { isEnabledInEnvironment } from "../env.mjs";
1
2
  import { isErrorCaptured, markErrorCaptured } from "./dedupe.mjs";
2
3
  import { buildErrorEnvelope } from "./envelope.mjs";
3
4
  import { normalizeRequest } from "./normalize-request.mjs";
5
+ import { isPluginEnabled } from "./remote-config.mjs";
4
6
  import { resolveServerCaptureRuntime } from "./runtime.mjs";
5
7
  import { sendEnvelope } from "./transport.mjs";
6
8
  //#region src/internal/server/capture.ts
@@ -15,6 +17,8 @@ const DEFAULT_REQUEST = {
15
17
  headers: new Headers()
16
18
  };
17
19
  async function captureError(error, request, context) {
20
+ if (!isEnabledInEnvironment()) return;
21
+ if (!isPluginEnabled("errors")) return;
18
22
  const runtime = resolveServerCaptureRuntime();
19
23
  if (runtime.apiKey === null) return;
20
24
  const normalizedRequest = normalizeRequest(request);
@@ -1 +1 @@
1
- {"version":3,"file":"capture.mjs","names":[],"sources":["../../../src/internal/server/capture.ts"],"sourcesContent":["import type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { isErrorCaptured, markErrorCaptured } from \"./dedupe.js\";\nimport { buildErrorEnvelope } from \"./envelope.js\";\nimport { normalizeRequest, TRACEPARENT_HEADER } from \"./normalize-request.js\";\nimport { resolveServerCaptureRuntime } from \"./runtime.js\";\nimport { sendEnvelope } from \"./transport.js\";\nimport type { CaptureErrorContext, OnRequestErrorContext } from \"./types.js\";\n\nconst ON_REQUEST_ERROR_MECHANISM: ErrorMechanism = {\n type: \"onRequestError\",\n handled: false,\n synthetic: false,\n};\n\nconst DEFAULT_REQUEST = {\n method: \"GET\",\n path: \"/\",\n headers: new Headers(),\n};\n\nexport async function captureError(\n error: unknown,\n request?: unknown,\n context?: CaptureErrorContext\n): Promise<void> {\n const runtime = resolveServerCaptureRuntime();\n if (runtime.apiKey === null) {\n return;\n }\n\n const normalizedRequest = normalizeRequest(request);\n const envelope = buildErrorEnvelope({\n error,\n request: normalizedRequest,\n context,\n runtime,\n });\n\n try {\n await sendEnvelope({\n envelope,\n runtime,\n traceparent:\n context?.traceparent ??\n normalizedRequest?.headers.get(TRACEPARENT_HEADER) ??\n undefined,\n });\n } catch {\n /* best-effort */\n }\n}\n\nexport async function onRequestError(\n error: Error & { digest?: string },\n request: unknown,\n context: OnRequestErrorContext\n): Promise<void> {\n if (isErrorCaptured(error)) {\n return;\n }\n markErrorCaptured(error);\n\n const normalizedRequest = normalizeRequest(request) ?? DEFAULT_REQUEST;\n\n await captureError(error, normalizedRequest, {\n mechanism: ON_REQUEST_ERROR_MECHANISM,\n nextjs: {\n ...context,\n requestMethod: normalizedRequest.method,\n requestPath: normalizedRequest.path,\n ...(error.digest ? { errorDigest: error.digest } : {}),\n },\n });\n}\n"],"mappings":";;;;;;AASA,MAAM,6BAA6C;CACjD,MAAM;CACN,SAAS;CACT,WAAW;CACZ;AAED,MAAM,kBAAkB;CACtB,QAAQ;CACR,MAAM;CACN,SAAS,IAAI,SAAS;CACvB;AAED,eAAsB,aACpB,OACA,SACA,SACe;CACf,MAAM,UAAU,6BAA6B;AAC7C,KAAI,QAAQ,WAAW,KACrB;CAGF,MAAM,oBAAoB,iBAAiB,QAAQ;CACnD,MAAM,WAAW,mBAAmB;EAClC;EACA,SAAS;EACT;EACA;EACD,CAAC;AAEF,KAAI;AACF,QAAM,aAAa;GACjB;GACA;GACA,aACE,SAAS,eACT,mBAAmB,QAAQ,IAAA,cAAuB,IAClD,KAAA;GACH,CAAC;SACI;;AAKV,eAAsB,eACpB,OACA,SACA,SACe;AACf,KAAI,gBAAgB,MAAM,CACxB;AAEF,mBAAkB,MAAM;CAExB,MAAM,oBAAoB,iBAAiB,QAAQ,IAAI;AAEvD,OAAM,aAAa,OAAO,mBAAmB;EAC3C,WAAW;EACX,QAAQ;GACN,GAAG;GACH,eAAe,kBAAkB;GACjC,aAAa,kBAAkB;GAC/B,GAAI,MAAM,SAAS,EAAE,aAAa,MAAM,QAAQ,GAAG,EAAE;GACtD;EACF,CAAC"}
1
+ {"version":3,"file":"capture.mjs","names":[],"sources":["../../../src/internal/server/capture.ts"],"sourcesContent":["import type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { isEnabledInEnvironment } from \"../env.js\";\nimport { isErrorCaptured, markErrorCaptured } from \"./dedupe.js\";\nimport { buildErrorEnvelope } from \"./envelope.js\";\nimport { normalizeRequest, TRACEPARENT_HEADER } from \"./normalize-request.js\";\nimport { isPluginEnabled } from \"./remote-config.js\";\nimport { resolveServerCaptureRuntime } from \"./runtime.js\";\nimport { sendEnvelope } from \"./transport.js\";\nimport type { CaptureErrorContext, OnRequestErrorContext } from \"./types.js\";\n\nconst ON_REQUEST_ERROR_MECHANISM: ErrorMechanism = {\n type: \"onRequestError\",\n handled: false,\n synthetic: false,\n};\n\nconst DEFAULT_REQUEST = {\n method: \"GET\",\n path: \"/\",\n headers: new Headers(),\n};\n\nexport async function captureError(\n error: unknown,\n request?: unknown,\n context?: CaptureErrorContext\n): Promise<void> {\n if (!isEnabledInEnvironment()) {\n return;\n }\n\n if (!isPluginEnabled(\"errors\")) {\n return;\n }\n\n const runtime = resolveServerCaptureRuntime();\n if (runtime.apiKey === null) {\n return;\n }\n\n const normalizedRequest = normalizeRequest(request);\n const envelope = buildErrorEnvelope({\n error,\n request: normalizedRequest,\n context,\n runtime,\n });\n\n try {\n await sendEnvelope({\n envelope,\n runtime,\n traceparent:\n context?.traceparent ??\n normalizedRequest?.headers.get(TRACEPARENT_HEADER) ??\n undefined,\n });\n } catch {\n /* best-effort */\n }\n}\n\nexport async function onRequestError(\n error: Error & { digest?: string },\n request: unknown,\n context: OnRequestErrorContext\n): Promise<void> {\n if (isErrorCaptured(error)) {\n return;\n }\n markErrorCaptured(error);\n\n const normalizedRequest = normalizeRequest(request) ?? DEFAULT_REQUEST;\n\n await captureError(error, normalizedRequest, {\n mechanism: ON_REQUEST_ERROR_MECHANISM,\n nextjs: {\n ...context,\n requestMethod: normalizedRequest.method,\n requestPath: normalizedRequest.path,\n ...(error.digest ? { errorDigest: error.digest } : {}),\n },\n });\n}\n"],"mappings":";;;;;;;;AAWA,MAAM,6BAA6C;CACjD,MAAM;CACN,SAAS;CACT,WAAW;CACZ;AAED,MAAM,kBAAkB;CACtB,QAAQ;CACR,MAAM;CACN,SAAS,IAAI,SAAS;CACvB;AAED,eAAsB,aACpB,OACA,SACA,SACe;AACf,KAAI,CAAC,wBAAwB,CAC3B;AAGF,KAAI,CAAC,gBAAgB,SAAS,CAC5B;CAGF,MAAM,UAAU,6BAA6B;AAC7C,KAAI,QAAQ,WAAW,KACrB;CAGF,MAAM,oBAAoB,iBAAiB,QAAQ;CACnD,MAAM,WAAW,mBAAmB;EAClC;EACA,SAAS;EACT;EACA;EACD,CAAC;AAEF,KAAI;AACF,QAAM,aAAa;GACjB;GACA;GACA,aACE,SAAS,eACT,mBAAmB,QAAQ,IAAA,cAAuB,IAClD,KAAA;GACH,CAAC;SACI;;AAKV,eAAsB,eACpB,OACA,SACA,SACe;AACf,KAAI,gBAAgB,MAAM,CACxB;AAEF,mBAAkB,MAAM;CAExB,MAAM,oBAAoB,iBAAiB,QAAQ,IAAI;AAEvD,OAAM,aAAa,OAAO,mBAAmB;EAC3C,WAAW;EACX,QAAQ;GACN,GAAG;GACH,eAAe,kBAAkB;GACjC,aAAa,kBAAkB;GAC/B,GAAI,MAAM,SAAS,EAAE,aAAa,MAAM,QAAQ,GAAG,EAAE;GACtD;EACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"envelope.d.mts","names":[],"sources":["../../../src/internal/server/envelope.ts"],"mappings":";;;;;UAmBU,wBAAA;EAAA,SACC,OAAA,GAAU,mBAAA;EAAA,SACV,KAAA;EAAA,SACA,OAAA,EAAS,iBAAA;EAAA,SACT,OAAA,EAAS,oBAAA;AAAA;AAAA,iBAGJ,kBAAA,CACd,MAAA,EAAQ,wBAAA,GACP,QAAA"}
1
+ {"version":3,"file":"envelope.d.mts","names":[],"sources":["../../../src/internal/server/envelope.ts"],"mappings":";;;;;UAoBU,wBAAA;EAAA,SACC,OAAA,GAAU,mBAAA;EAAA,SACV,KAAA;EAAA,SACA,OAAA,EAAS,iBAAA;EAAA,SACT,OAAA,EAAS,oBAAA;AAAA;AAAA,iBAGJ,kBAAA,CACd,MAAA,EAAQ,wBAAA,GACP,QAAA"}
@@ -1,3 +1,4 @@
1
+ import { PRODUCER_VERSION } from "../version.mjs";
1
2
  import { sessionIdSchema } from "@interfere/types/data/session";
2
3
  import { toError, toExceptions } from "@interfere/types/sdk/errors";
3
4
  import { v7 } from "uuid";
@@ -23,6 +24,7 @@ function buildErrorEnvelope(params) {
23
24
  environment: runtime.environment,
24
25
  buildId: runtime.buildId,
25
26
  releaseId: runtime.releaseId,
27
+ producerVersion: PRODUCER_VERSION,
26
28
  sessionId: session.id,
27
29
  ...session.source ? { sessionSource: session.source } : {},
28
30
  ...nextjsContext ? { context: nextjsContext } : {}
@@ -1 +1 @@
1
- {"version":3,"file":"envelope.mjs","names":["uuidv7"],"sources":["../../../src/internal/server/envelope.ts"],"sourcesContent":["import type { SessionId } from \"@interfere/types/data/session\";\nimport { sessionIdSchema } from \"@interfere/types/data/session\";\nimport type { Envelope, SessionSource } from \"@interfere/types/sdk/envelope\";\nimport { toError, toExceptions } from \"@interfere/types/sdk/errors\";\nimport type { NextjsContext } from \"@interfere/types/sdk/plugins/context/next\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { v7 as uuidv7 } from \"uuid\";\n\nimport type { ServerCaptureRuntime } from \"./runtime.js\";\nimport type { CaptureErrorContext, NormalizedRequest } from \"./types.js\";\n\nconst SESSION_HEADER = \"x-interfere-session\";\n\nconst DEFAULT_ERROR_MECHANISM: ErrorMechanism = {\n type: \"captureError\",\n handled: true,\n};\n\ninterface BuildErrorEnvelopeParams {\n readonly context?: CaptureErrorContext;\n readonly error: unknown;\n readonly request: NormalizedRequest | null;\n readonly runtime: ServerCaptureRuntime;\n}\n\nexport function buildErrorEnvelope(\n params: BuildErrorEnvelopeParams\n): Envelope<\"error\"> {\n const { error, request, context, runtime } = params;\n const session = resolveSession(request?.headers ?? new Headers());\n\n let nextjsContext: NextjsContext | undefined;\n if (context?.nextjs) {\n nextjsContext = toNextjsContext(context.nextjs);\n } else if (request) {\n nextjsContext = toNextjsRequestContext(request);\n }\n\n return {\n uuid: uuidv7(),\n v: 0,\n type: \"error\",\n payload: {\n exceptions: toExceptions(\n toError(error),\n context?.mechanism ?? DEFAULT_ERROR_MECHANISM\n ),\n },\n clientTs: Date.now(),\n runtime: runtime.runtime,\n environment: runtime.environment,\n buildId: runtime.buildId,\n releaseId: runtime.releaseId,\n sessionId: session.id,\n ...(session.source ? { sessionSource: session.source } : {}),\n ...(nextjsContext ? { context: nextjsContext } : {}),\n };\n}\n\nfunction toNextjsRequestContext(request: NormalizedRequest): NextjsContext {\n return {\n runtime: \"nextjs\",\n requestMethod: request.method,\n requestPath: request.path,\n };\n}\n\nfunction toNextjsContext(\n context: Omit<NextjsContext, \"runtime\">\n): NextjsContext {\n return {\n runtime: \"nextjs\",\n ...context,\n };\n}\n\nfunction resolveSession(headers: Headers): {\n readonly id: SessionId | null;\n readonly source?: SessionSource;\n} {\n const sessionId = headers.get(SESSION_HEADER);\n if (sessionId === null) {\n return { id: null };\n }\n\n const parsed = sessionIdSchema.safeParse(sessionId);\n if (!parsed.success) {\n return { id: null };\n }\n\n return {\n id: parsed.data,\n source: \"header\",\n };\n}\n"],"mappings":";;;;AAYA,MAAM,iBAAiB;AAEvB,MAAM,0BAA0C;CAC9C,MAAM;CACN,SAAS;CACV;AASD,SAAgB,mBACd,QACmB;CACnB,MAAM,EAAE,OAAO,SAAS,SAAS,YAAY;CAC7C,MAAM,UAAU,eAAe,SAAS,WAAW,IAAI,SAAS,CAAC;CAEjE,IAAI;AACJ,KAAI,SAAS,OACX,iBAAgB,gBAAgB,QAAQ,OAAO;UACtC,QACT,iBAAgB,uBAAuB,QAAQ;AAGjD,QAAO;EACL,MAAMA,IAAQ;EACd,GAAG;EACH,MAAM;EACN,SAAS,EACP,YAAY,aACV,QAAQ,MAAM,EACd,SAAS,aAAa,wBACvB,EACF;EACD,UAAU,KAAK,KAAK;EACpB,SAAS,QAAQ;EACjB,aAAa,QAAQ;EACrB,SAAS,QAAQ;EACjB,WAAW,QAAQ;EACnB,WAAW,QAAQ;EACnB,GAAI,QAAQ,SAAS,EAAE,eAAe,QAAQ,QAAQ,GAAG,EAAE;EAC3D,GAAI,gBAAgB,EAAE,SAAS,eAAe,GAAG,EAAE;EACpD;;AAGH,SAAS,uBAAuB,SAA2C;AACzE,QAAO;EACL,SAAS;EACT,eAAe,QAAQ;EACvB,aAAa,QAAQ;EACtB;;AAGH,SAAS,gBACP,SACe;AACf,QAAO;EACL,SAAS;EACT,GAAG;EACJ;;AAGH,SAAS,eAAe,SAGtB;CACA,MAAM,YAAY,QAAQ,IAAI,eAAe;AAC7C,KAAI,cAAc,KAChB,QAAO,EAAE,IAAI,MAAM;CAGrB,MAAM,SAAS,gBAAgB,UAAU,UAAU;AACnD,KAAI,CAAC,OAAO,QACV,QAAO,EAAE,IAAI,MAAM;AAGrB,QAAO;EACL,IAAI,OAAO;EACX,QAAQ;EACT"}
1
+ {"version":3,"file":"envelope.mjs","names":["uuidv7"],"sources":["../../../src/internal/server/envelope.ts"],"sourcesContent":["import type { SessionId } from \"@interfere/types/data/session\";\nimport { sessionIdSchema } from \"@interfere/types/data/session\";\nimport type { Envelope, SessionSource } from \"@interfere/types/sdk/envelope\";\nimport { toError, toExceptions } from \"@interfere/types/sdk/errors\";\nimport type { NextjsContext } from \"@interfere/types/sdk/plugins/context/next\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { v7 as uuidv7 } from \"uuid\";\n\nimport { PRODUCER_VERSION } from \"../version.js\";\nimport type { ServerCaptureRuntime } from \"./runtime.js\";\nimport type { CaptureErrorContext, NormalizedRequest } from \"./types.js\";\n\nconst SESSION_HEADER = \"x-interfere-session\";\n\nconst DEFAULT_ERROR_MECHANISM: ErrorMechanism = {\n type: \"captureError\",\n handled: true,\n};\n\ninterface BuildErrorEnvelopeParams {\n readonly context?: CaptureErrorContext;\n readonly error: unknown;\n readonly request: NormalizedRequest | null;\n readonly runtime: ServerCaptureRuntime;\n}\n\nexport function buildErrorEnvelope(\n params: BuildErrorEnvelopeParams\n): Envelope<\"error\"> {\n const { error, request, context, runtime } = params;\n const session = resolveSession(request?.headers ?? new Headers());\n\n let nextjsContext: NextjsContext | undefined;\n if (context?.nextjs) {\n nextjsContext = toNextjsContext(context.nextjs);\n } else if (request) {\n nextjsContext = toNextjsRequestContext(request);\n }\n\n return {\n uuid: uuidv7(),\n v: 0,\n type: \"error\",\n payload: {\n exceptions: toExceptions(\n toError(error),\n context?.mechanism ?? DEFAULT_ERROR_MECHANISM\n ),\n },\n clientTs: Date.now(),\n runtime: runtime.runtime,\n environment: runtime.environment,\n buildId: runtime.buildId,\n releaseId: runtime.releaseId,\n producerVersion: PRODUCER_VERSION,\n sessionId: session.id,\n ...(session.source ? { sessionSource: session.source } : {}),\n ...(nextjsContext ? { context: nextjsContext } : {}),\n };\n}\n\nfunction toNextjsRequestContext(request: NormalizedRequest): NextjsContext {\n return {\n runtime: \"nextjs\",\n requestMethod: request.method,\n requestPath: request.path,\n };\n}\n\nfunction toNextjsContext(\n context: Omit<NextjsContext, \"runtime\">\n): NextjsContext {\n return {\n runtime: \"nextjs\",\n ...context,\n };\n}\n\nfunction resolveSession(headers: Headers): {\n readonly id: SessionId | null;\n readonly source?: SessionSource;\n} {\n const sessionId = headers.get(SESSION_HEADER);\n if (sessionId === null) {\n return { id: null };\n }\n\n const parsed = sessionIdSchema.safeParse(sessionId);\n if (!parsed.success) {\n return { id: null };\n }\n\n return {\n id: parsed.data,\n source: \"header\",\n };\n}\n"],"mappings":";;;;;AAaA,MAAM,iBAAiB;AAEvB,MAAM,0BAA0C;CAC9C,MAAM;CACN,SAAS;CACV;AASD,SAAgB,mBACd,QACmB;CACnB,MAAM,EAAE,OAAO,SAAS,SAAS,YAAY;CAC7C,MAAM,UAAU,eAAe,SAAS,WAAW,IAAI,SAAS,CAAC;CAEjE,IAAI;AACJ,KAAI,SAAS,OACX,iBAAgB,gBAAgB,QAAQ,OAAO;UACtC,QACT,iBAAgB,uBAAuB,QAAQ;AAGjD,QAAO;EACL,MAAMA,IAAQ;EACd,GAAG;EACH,MAAM;EACN,SAAS,EACP,YAAY,aACV,QAAQ,MAAM,EACd,SAAS,aAAa,wBACvB,EACF;EACD,UAAU,KAAK,KAAK;EACpB,SAAS,QAAQ;EACjB,aAAa,QAAQ;EACrB,SAAS,QAAQ;EACjB,WAAW,QAAQ;EACnB,iBAAiB;EACjB,WAAW,QAAQ;EACnB,GAAI,QAAQ,SAAS,EAAE,eAAe,QAAQ,QAAQ,GAAG,EAAE;EAC3D,GAAI,gBAAgB,EAAE,SAAS,eAAe,GAAG,EAAE;EACpD;;AAGH,SAAS,uBAAuB,SAA2C;AACzE,QAAO;EACL,SAAS;EACT,eAAe,QAAQ;EACvB,aAAa,QAAQ;EACtB;;AAGH,SAAS,gBACP,SACe;AACf,QAAO;EACL,SAAS;EACT,GAAG;EACJ;;AAGH,SAAS,eAAe,SAGtB;CACA,MAAM,YAAY,QAAQ,IAAI,eAAe;AAC7C,KAAI,cAAc,KAChB,QAAO,EAAE,IAAI,MAAM;CAGrB,MAAM,SAAS,gBAAgB,UAAU,UAAU;AACnD,KAAI,CAAC,OAAO,QACV,QAAO,EAAE,IAAI,MAAM;AAGrB,QAAO;EACL,IAAI,OAAO;EACX,QAAQ;EACT"}
@@ -0,0 +1,5 @@
1
+ //#region src/internal/server/remote-config.d.ts
2
+ declare function fetchAndCacheRemoteConfig(): Promise<void>;
3
+ declare function isPluginEnabled(plugin: string): boolean;
4
+ //#endregion
5
+ export { fetchAndCacheRemoteConfig, isPluginEnabled };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-config.d.mts","names":[],"sources":["../../../src/internal/server/remote-config.ts"],"mappings":";iBAUsB,yBAAA,CAAA,GAA6B,OAAA;AAAA,iBAkCnC,eAAA,CAAgB,MAAA"}
@@ -0,0 +1,29 @@
1
+ import { isEnabledInEnvironment, readInterfereEnv } from "../env.mjs";
2
+ import { API_PATHS } from "@interfere/constants/api";
3
+ //#region src/internal/server/remote-config.ts
4
+ let cachedConfig = null;
5
+ async function fetchAndCacheRemoteConfig() {
6
+ if (!isEnabledInEnvironment()) return;
7
+ const env = readInterfereEnv();
8
+ if (env.apiKey === null) return;
9
+ try {
10
+ const url = `${env.apiUrl}${API_PATHS.CONFIG}`;
11
+ const response = await fetch(url, {
12
+ method: "GET",
13
+ headers: {
14
+ "content-type": "application/json",
15
+ "x-api-key": env.apiKey
16
+ },
17
+ signal: AbortSignal.timeout(1e4)
18
+ });
19
+ if (!response.ok) return;
20
+ const config = await response.json();
21
+ if (config?.plugins) cachedConfig = config.plugins;
22
+ } catch {}
23
+ }
24
+ function isPluginEnabled(plugin) {
25
+ if (!cachedConfig) return true;
26
+ return cachedConfig[plugin] !== false;
27
+ }
28
+ //#endregion
29
+ export { fetchAndCacheRemoteConfig, isPluginEnabled };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-config.mjs","names":[],"sources":["../../../src/internal/server/remote-config.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\nimport type {\n RemoteConfig,\n RemotePluginConfig,\n} from \"@interfere/types/sdk/remote-config\";\n\nimport { isEnabledInEnvironment, readInterfereEnv } from \"../env.js\";\n\nlet cachedConfig: RemotePluginConfig | null = null;\n\nexport async function fetchAndCacheRemoteConfig(): Promise<void> {\n if (!isEnabledInEnvironment()) {\n return;\n }\n\n const env = readInterfereEnv();\n if (env.apiKey === null) {\n return;\n }\n\n try {\n const url = `${env.apiUrl}${API_PATHS.CONFIG}`;\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n \"content-type\": \"application/json\",\n \"x-api-key\": env.apiKey,\n },\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!response.ok) {\n return;\n }\n\n const config = (await response.json()) as RemoteConfig;\n if (config?.plugins) {\n cachedConfig = config.plugins;\n }\n } catch {\n // Fail silently — all plugins remain enabled\n }\n}\n\nexport function isPluginEnabled(plugin: string): boolean {\n if (!cachedConfig) {\n return true;\n }\n return cachedConfig[plugin as keyof RemotePluginConfig] !== false;\n}\n"],"mappings":";;;AAQA,IAAI,eAA0C;AAE9C,eAAsB,4BAA2C;AAC/D,KAAI,CAAC,wBAAwB,CAC3B;CAGF,MAAM,MAAM,kBAAkB;AAC9B,KAAI,IAAI,WAAW,KACjB;AAGF,KAAI;EACF,MAAM,MAAM,GAAG,IAAI,SAAS,UAAU;EACtC,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,aAAa,IAAI;IAClB;GACD,QAAQ,YAAY,QAAQ,IAAO;GACpC,CAAC;AAEF,MAAI,CAAC,SAAS,GACZ;EAGF,MAAM,SAAU,MAAM,SAAS,MAAM;AACrC,MAAI,QAAQ,QACV,gBAAe,OAAO;SAElB;;AAKV,SAAgB,gBAAgB,QAAyB;AACvD,KAAI,CAAC,aACH,QAAO;AAET,QAAO,aAAa,YAAwC"}
@@ -0,0 +1,4 @@
1
+ //#region src/internal/version.d.ts
2
+ declare const PRODUCER_VERSION: string;
3
+ //#endregion
4
+ export { PRODUCER_VERSION };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.mts","names":[],"sources":["../../src/internal/version.ts"],"mappings":";cAEa,gBAAA"}
@@ -0,0 +1,5 @@
1
+ import { name, version } from "../package.mjs";
2
+ //#region src/internal/version.ts
3
+ const PRODUCER_VERSION = `${name}@${version}`;
4
+ //#endregion
5
+ export { PRODUCER_VERSION };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.mjs","names":["pkg.name","pkg.version"],"sources":["../../src/internal/version.ts"],"sourcesContent":["import pkg from \"../../package.json\" with { type: \"json\" };\n\nexport const PRODUCER_VERSION = `${pkg.name}@${pkg.version}`;\n"],"mappings":";;AAEA,MAAa,mBAAmB,GAAGA,KAAS,GAAGC"}
@@ -0,0 +1,5 @@
1
+ //#region package.json
2
+ var name = "@interfere/next";
3
+ var version = "5.0.1";
4
+ //#endregion
5
+ export { name, version };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
@@ -1,5 +1,5 @@
1
1
  //#region src/route-handler.d.ts
2
- declare function GET(request: Request): Response;
2
+ declare function GET(request: Request): Promise<Response>;
3
3
  declare function POST(request: Request): Promise<Response>;
4
4
  declare function PUT(request: Request): Promise<Response>;
5
5
  declare function OPTIONS(): Response;
@@ -1 +1 @@
1
- {"version":3,"file":"route-handler.d.mts","names":[],"sources":["../src/route-handler.ts"],"mappings":";iBAGgB,GAAA,CAAI,OAAA,EAAS,OAAA,GAAU,QAAA;AAAA,iBAIvB,IAAA,CAAK,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,QAAA;AAAA,iBAIhC,GAAA,CAAI,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,QAAA;AAAA,iBAW/B,OAAA,CAAA,GAAW,QAAA"}
1
+ {"version":3,"file":"route-handler.d.mts","names":[],"sources":["../src/route-handler.ts"],"mappings":";iBAMsB,GAAA,CAAI,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,QAAA;AAAA,iBAOrC,IAAA,CAAK,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,QAAA;AAAA,iBAOhC,GAAA,CAAI,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,QAAA;AAAA,iBAc/B,OAAA,CAAA,GAAW,QAAA"}
@@ -1,13 +1,18 @@
1
+ import { isEnabledInEnvironment } from "./internal/env.mjs";
1
2
  import { handleGet } from "./internal/route/handle-get.mjs";
2
3
  import { handlePost } from "./internal/route/handle-post.mjs";
3
4
  //#region src/route-handler.ts
4
- function GET(request) {
5
- return handleGet(request);
5
+ const DISABLED = () => new Response(null, { status: 204 });
6
+ async function GET(request) {
7
+ if (!isEnabledInEnvironment()) return DISABLED();
8
+ return await handleGet(request);
6
9
  }
7
10
  function POST(request) {
11
+ if (!isEnabledInEnvironment()) return Promise.resolve(DISABLED());
8
12
  return handlePost(request);
9
13
  }
10
14
  function PUT(request) {
15
+ if (!isEnabledInEnvironment()) return Promise.resolve(DISABLED());
11
16
  return handlePost(request);
12
17
  }
13
18
  const CORS_HEADERS = {
@@ -1 +1 @@
1
- {"version":3,"file":"route-handler.mjs","names":[],"sources":["../src/route-handler.ts"],"sourcesContent":["import { handleGet } from \"./internal/route/handle-get.js\";\nimport { handlePost } from \"./internal/route/handle-post.js\";\n\nexport function GET(request: Request): Response {\n return handleGet(request);\n}\n\nexport function POST(request: Request): Promise<Response> {\n return handlePost(request);\n}\n\nexport function PUT(request: Request): Promise<Response> {\n return handlePost(request);\n}\n\nconst CORS_HEADERS = {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"GET, POST, PUT, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Access-Control-Max-Age\": \"86400\",\n};\n\nexport function OPTIONS(): Response {\n return new Response(null, { status: 200, headers: CORS_HEADERS });\n}\n"],"mappings":";;;AAGA,SAAgB,IAAI,SAA4B;AAC9C,QAAO,UAAU,QAAQ;;AAG3B,SAAgB,KAAK,SAAqC;AACxD,QAAO,WAAW,QAAQ;;AAG5B,SAAgB,IAAI,SAAqC;AACvD,QAAO,WAAW,QAAQ;;AAG5B,MAAM,eAAe;CACnB,+BAA+B;CAC/B,gCAAgC;CAChC,gCAAgC;CAChC,0BAA0B;CAC3B;AAED,SAAgB,UAAoB;AAClC,QAAO,IAAI,SAAS,MAAM;EAAE,QAAQ;EAAK,SAAS;EAAc,CAAC"}
1
+ {"version":3,"file":"route-handler.mjs","names":[],"sources":["../src/route-handler.ts"],"sourcesContent":["import { isEnabledInEnvironment } from \"./internal/env.js\";\nimport { handleGet } from \"./internal/route/handle-get.js\";\nimport { handlePost } from \"./internal/route/handle-post.js\";\n\nconst DISABLED = () => new Response(null, { status: 204 });\n\nexport async function GET(request: Request): Promise<Response> {\n if (!isEnabledInEnvironment()) {\n return DISABLED();\n }\n return await handleGet(request);\n}\n\nexport function POST(request: Request): Promise<Response> {\n if (!isEnabledInEnvironment()) {\n return Promise.resolve(DISABLED());\n }\n return handlePost(request);\n}\n\nexport function PUT(request: Request): Promise<Response> {\n if (!isEnabledInEnvironment()) {\n return Promise.resolve(DISABLED());\n }\n return handlePost(request);\n}\n\nconst CORS_HEADERS = {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"GET, POST, PUT, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Access-Control-Max-Age\": \"86400\",\n};\n\nexport function OPTIONS(): Response {\n return new Response(null, { status: 200, headers: CORS_HEADERS });\n}\n"],"mappings":";;;;AAIA,MAAM,iBAAiB,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,CAAC;AAE1D,eAAsB,IAAI,SAAqC;AAC7D,KAAI,CAAC,wBAAwB,CAC3B,QAAO,UAAU;AAEnB,QAAO,MAAM,UAAU,QAAQ;;AAGjC,SAAgB,KAAK,SAAqC;AACxD,KAAI,CAAC,wBAAwB,CAC3B,QAAO,QAAQ,QAAQ,UAAU,CAAC;AAEpC,QAAO,WAAW,QAAQ;;AAG5B,SAAgB,IAAI,SAAqC;AACvD,KAAI,CAAC,wBAAwB,CAC3B,QAAO,QAAQ,QAAQ,UAAU,CAAC;AAEpC,QAAO,WAAW,QAAQ;;AAG5B,MAAM,eAAe;CACnB,+BAA+B;CAC/B,gCAAgC;CAChC,gCAAgC;CAChC,0BAA0B;CAC3B;AAED,SAAgB,UAAoB;AAClC,QAAO,IAAI,SAAS,MAAM;EAAE,QAAQ;EAAK,SAAS;EAAc,CAAC"}
package/dist/server.d.mts CHANGED
@@ -1,3 +1,4 @@
1
1
  import { CaptureErrorContext, OnRequestErrorContext } from "./internal/server/types.mjs";
2
2
  import { captureError, onRequestError } from "./internal/server/capture.mjs";
3
- export { type CaptureErrorContext, type OnRequestErrorContext, captureError, onRequestError };
3
+ import { fetchAndCacheRemoteConfig } from "./internal/server/remote-config.mjs";
4
+ export { type CaptureErrorContext, type OnRequestErrorContext, captureError, onRequestError, fetchAndCacheRemoteConfig as register };
package/dist/server.mjs CHANGED
@@ -1,2 +1,3 @@
1
+ import { fetchAndCacheRemoteConfig } from "./internal/server/remote-config.mjs";
1
2
  import { captureError, onRequestError } from "./internal/server/capture.mjs";
2
- export { captureError, onRequestError };
3
+ export { captureError, onRequestError, fetchAndCacheRemoteConfig as register };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interfere/next",
3
- "version": "4.0.0",
3
+ "version": "5.0.1",
4
4
  "license": "MIT",
5
5
  "description": "Build software that never breaks.",
6
6
  "keywords": [
@@ -64,10 +64,10 @@
64
64
  "test": "bun run test:unit && bun run test:browser"
65
65
  },
66
66
  "dependencies": {
67
- "@interfere/constants": "^4.0.0",
68
- "@interfere/react": "^4.0.0",
69
- "@interfere/sdk": "^4.0.0",
70
- "@interfere/types": "^4.0.0",
67
+ "@interfere/constants": "^5.0.0",
68
+ "@interfere/react": "^5.0.1",
69
+ "@interfere/sdk": "^5.0.0",
70
+ "@interfere/types": "^5.0.0",
71
71
  "chalk": "^5.6.2",
72
72
  "uuid": "^13.0.0"
73
73
  },
@@ -78,8 +78,8 @@
78
78
  "react-dom": ">=19"
79
79
  },
80
80
  "devDependencies": {
81
- "@interfere/typescript-config": "^4.0.0",
82
- "@interfere/vitest-config": "^4.0.0",
81
+ "@interfere/typescript-config": "^5.0.0",
82
+ "@interfere/vitest-config": "^5.0.0",
83
83
  "@testing-library/react": "^16.3.2",
84
84
  "@types/node": "^24.12.0",
85
85
  "@types/react": "19.2.14",