@interfere/next 11.0.0-canary.5 → 11.0.0-canary.7
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 +12 -37
- package/dist/config.d.mts +55 -8
- package/dist/config.mjs +1 -1
- package/dist/instrumentation.edge.mjs +1 -1
- package/dist/instrumentation.mjs +1 -1
- package/dist/internal/build/configure-build.d.mts +2 -1
- package/dist/internal/build/configure-build.mjs +1 -1
- package/dist/internal/build/pipeline.mjs +1 -1
- package/dist/internal/build/source-maps/discover-webpack.d.mts +1 -35
- package/dist/internal/build/source-maps/discover-webpack.mjs +1 -1
- package/dist/internal/build/source-maps/discover.d.mts +2 -15
- package/dist/internal/build/source-maps/discover.mjs +1 -1
- package/dist/internal/server/capture.d.mts +1 -1
- package/dist/internal/server/capture.mjs +1 -1
- package/dist/internal/server/server-flush.d.mts +5 -0
- package/dist/internal/server/server-flush.mjs +1 -0
- package/dist/package.mjs +1 -1
- package/package.json +10 -15
- package/dist/internal/route/handle-get.d.mts +0 -17
- package/dist/internal/route/handle-get.mjs +0 -1
- package/dist/internal/route/handle-post.d.mts +0 -15
- package/dist/internal/route/handle-post.mjs +0 -1
- package/dist/internal/route/proxy.d.mts +0 -20
- package/dist/internal/route/proxy.mjs +0 -1
- package/dist/route-handler.d.mts +0 -12
- package/dist/route-handler.mjs +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<a href="https://interfere.com">
|
|
3
3
|
<picture>
|
|
4
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://
|
|
5
|
-
<img src="https://
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://images.interfere.com/cdn-cgi/imagedelivery/ZQ3TDZdJHXvYNyL5BNulpQ/v2/header/logo-dark/format=auto">
|
|
5
|
+
<img src="https://images.interfere.com/cdn-cgi/imagedelivery/ZQ3TDZdJHXvYNyL5BNulpQ/v2/header/logo-light/format=auto" height="64" alt="Interfere">
|
|
6
6
|
</picture>
|
|
7
7
|
</a>
|
|
8
8
|
<h1 align="center">@interfere/next</h1>
|
|
@@ -19,11 +19,7 @@
|
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
21
|
<p align="center">
|
|
22
|
-
<a href="https://
|
|
23
|
-
·
|
|
24
|
-
<a href="https://support.interfere.com/roadmap">Feature Requests</a>
|
|
25
|
-
·
|
|
26
|
-
<a href="https://support.interfere.com/requests">Report a Bug</a>
|
|
22
|
+
<a href="https://interfere.com/docs">Documentation</a>
|
|
27
23
|
·
|
|
28
24
|
<a href="mailto:support@interfere.com">Get Help</a>
|
|
29
25
|
</p>
|
|
@@ -65,14 +61,7 @@ export default withInterfere(config);
|
|
|
65
61
|
export { onRequestError, register } from "@interfere/next/instrumentation";
|
|
66
62
|
```
|
|
67
63
|
|
|
68
|
-
**3.
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
// app/api/interfere/[[...path]]/route.ts
|
|
72
|
-
export * from "@interfere/next/route-handler";
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
**4. Add the provider**
|
|
64
|
+
**3. Add the provider**
|
|
76
65
|
|
|
77
66
|
```tsx
|
|
78
67
|
// app/layout.tsx
|
|
@@ -99,38 +88,24 @@ export default function RootLayout({
|
|
|
99
88
|
| --- | --- | --- |
|
|
100
89
|
| `INTERFERE_PUBLIC_KEY` | Yes | Surface publishable key in the `interfere_pub_us_*` or `interfere_pub_eu_*` format. Public by design; used for ingestion. |
|
|
101
90
|
| `INTERFERE_API_KEY` | Yes for release metadata | Interfere secret key in the `interfere_secret_us_*` or `interfere_secret_eu_*` format. Server-side build credential; never expose in browser code. |
|
|
102
|
-
| `
|
|
91
|
+
| `INTERFERE_API_URL` | No | Override the ingest endpoint in `direct` mode (e.g. a regional endpoint). Defaults to Interfere's endpoint. |
|
|
103
92
|
|
|
104
93
|
### Migrating From 0.x
|
|
105
94
|
|
|
106
95
|
Upgrade all `@interfere/*` SDK packages together. Keep the env var names, but replace old `int_pub_*` / `interfere_pk_*` values with the dashboard's regional publishable key, and replace old `ak_*` / `interfere_ak_*` build keys with the regional Interfere secret key.
|
|
107
96
|
|
|
108
|
-
##
|
|
109
|
-
|
|
110
|
-
By default the SDK proxies all telemetry through `/api/interfere`. You can move this to any path by setting `NEXT_PUBLIC_INTERFERE_ROUTE_PREFIX` and mounting the route handler at the matching location.
|
|
97
|
+
## Ingest Endpoint
|
|
111
98
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
**1. Set the environment variable**
|
|
115
|
-
|
|
116
|
-
```env
|
|
117
|
-
NEXT_PUBLIC_INTERFERE_ROUTE_PREFIX=/__telemetry
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
**2. Mount the route handler at the matching path**
|
|
99
|
+
By default the SDK posts telemetry to Interfere's endpoint. Ingest requests never carry cookies. Set `apiHost` to a subdomain of your own site that resolves to Interfere via a DNS record — this survives ad-blockers with no server-side component:
|
|
121
100
|
|
|
122
101
|
```ts
|
|
123
|
-
//
|
|
124
|
-
export
|
|
102
|
+
// next.config.ts
|
|
103
|
+
export default withInterfere({
|
|
104
|
+
interfere: { apiHost: "https://in.yourapp.com" },
|
|
105
|
+
});
|
|
125
106
|
```
|
|
126
107
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
| Input | Resolved |
|
|
130
|
-
| --- | --- |
|
|
131
|
-
| `/__telemetry` | `/__telemetry` |
|
|
132
|
-
| `__telemetry/` | `/__telemetry` |
|
|
133
|
-
| *(empty)* or `/` | `/api/interfere` (default) |
|
|
108
|
+
Add a CNAME record pointing your subdomain at the target shown in your dashboard; Interfere provisions the TLS certificate automatically. To target a regional endpoint instead, set `apiHost` (or the `INTERFERE_API_URL` env) to it.
|
|
134
109
|
|
|
135
110
|
## Identity Management
|
|
136
111
|
|
package/dist/config.d.mts
CHANGED
|
@@ -3,14 +3,52 @@ import { NextConfig } from "next";
|
|
|
3
3
|
|
|
4
4
|
//#region src/config.d.ts
|
|
5
5
|
interface NextConfigWithInterfere extends NextConfig {
|
|
6
|
-
/**
|
|
7
|
-
* Reserved for future SDK config. Setting `buildId` here is rejected
|
|
8
|
-
* loudly — it caused server/client slug drift in 9.x. Override the
|
|
9
|
-
* commit SHA via the `INTERFERE_SOURCE_ID` env var (set on both build
|
|
10
|
-
* and runtime envs) instead.
|
|
11
|
-
*/
|
|
12
6
|
interfere?: {
|
|
7
|
+
/**
|
|
8
|
+
* Reserved. Setting `buildId` here is rejected loudly — it caused
|
|
9
|
+
* server/client slug drift in 9.x. Override the commit SHA via the
|
|
10
|
+
* `INTERFERE_SOURCE_ID` env var (set on both build and runtime envs)
|
|
11
|
+
* instead.
|
|
12
|
+
*/
|
|
13
13
|
readonly buildId?: never;
|
|
14
|
+
/**
|
|
15
|
+
* The endpoint the browser SDK posts telemetry to. Defaults to
|
|
16
|
+
* Interfere's endpoint. Set it to a subdomain of your own site that
|
|
17
|
+
* resolves to Interfere via a DNS record (e.g.
|
|
18
|
+
* `"https://in.yourapp.com"`) to survive ad-blockers, or to a
|
|
19
|
+
* regional endpoint. Must be a full URL.
|
|
20
|
+
*/
|
|
21
|
+
readonly apiHost?: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Interfere-specific build settings, passed as the first argument to
|
|
26
|
+
* `withInterfere(settings, nextConfig)`. Kept separate from the Next.js
|
|
27
|
+
* config so SDK options never collide with Next's own keys.
|
|
28
|
+
*/
|
|
29
|
+
interface NextPluginConfig {
|
|
30
|
+
readonly sourceMaps?: {
|
|
31
|
+
/**
|
|
32
|
+
* Delete source maps from the build output after they've been uploaded
|
|
33
|
+
* to Interfere.
|
|
34
|
+
*
|
|
35
|
+
* Defaults to `true`: maps are uploaded so Interfere can symbolicate
|
|
36
|
+
* stack traces, then removed from the build so your original source is
|
|
37
|
+
* never shipped to production.
|
|
38
|
+
*
|
|
39
|
+
* Set to `false` to keep the maps on disk after upload. Do this when another
|
|
40
|
+
* tool shares the same Next.js build output and runs its own source-map
|
|
41
|
+
* upload from `.next` — Interfere's default purge would delete the maps
|
|
42
|
+
* that tool needs.
|
|
43
|
+
*
|
|
44
|
+
* @default true
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* // Keep maps so another source-map uploader can consume them too:
|
|
48
|
+
* export default withInterfere({ sourceMaps: { purge: false } }, nextConfig);
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
readonly purge?: boolean;
|
|
14
52
|
};
|
|
15
53
|
}
|
|
16
54
|
interface ResolvedBuildConfig {
|
|
@@ -33,6 +71,14 @@ interface ResolvedBuildConfig {
|
|
|
33
71
|
*/
|
|
34
72
|
readonly environment: string | null;
|
|
35
73
|
readonly publicKey: string | null;
|
|
74
|
+
/**
|
|
75
|
+
* When `true` (default), the build pipeline deletes source maps from the
|
|
76
|
+
* build output after uploading them, so they never ship to production.
|
|
77
|
+
* Resolved from `NextPluginConfig.sourceMaps.purge`; set it `false` to
|
|
78
|
+
* leave maps on disk for another uploader that shares the same `.next`
|
|
79
|
+
* output.
|
|
80
|
+
*/
|
|
81
|
+
readonly purgeSourceMaps: boolean;
|
|
36
82
|
/**
|
|
37
83
|
* Deterministically derived from the leading 16 hex of the commit SHA, so
|
|
38
84
|
* the build step and the collector arrive at the same
|
|
@@ -42,10 +88,11 @@ interface ResolvedBuildConfig {
|
|
|
42
88
|
*/
|
|
43
89
|
readonly releaseSlug: ReleaseSlug | null;
|
|
44
90
|
}
|
|
45
|
-
declare function withInterfere(
|
|
91
|
+
declare function withInterfere(upstream?: NextConfigWithInterfere): NextConfig;
|
|
92
|
+
declare function withInterfere(settings: NextPluginConfig, config: NextConfigWithInterfere): NextConfig;
|
|
46
93
|
interface ProductionCompileContext {
|
|
47
94
|
readonly distDir: string;
|
|
48
95
|
readonly projectDir: string;
|
|
49
96
|
}
|
|
50
97
|
//#endregion
|
|
51
|
-
export { NextConfigWithInterfere, ProductionCompileContext, ResolvedBuildConfig, withInterfere };
|
|
98
|
+
export { NextConfigWithInterfere, NextPluginConfig, ProductionCompileContext, ResolvedBuildConfig, withInterfere };
|
package/dist/config.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{configureBuild}from"./internal/build/configure-build.mjs";import{readInterfereEnv}from"./internal/env.mjs";import{FatalError,log}from"./internal/logger.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{readNextMajorVersion,warnIfServerInstrumentationMissing}from"./internal/setup-warnings.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{classifyReleasePipelineError,diagnoseRelease,isInterfereApiKey}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{PHASE_PRODUCTION_BUILD}from"next/constants.js";const CLIENT_TRACE_METADATA_KEYS=[`traceparent`,`baggage`],DIAGNOSTIC_CONTEXT={publicKeyEnvVar:`INTERFERE_PUBLIC_KEY`};function reportReleaseDiagnostic(code,input){let diagnostic=diagnoseRelease(code,DIAGNOSTIC_CONTEXT,input),lines=[...diagnostic.details];if(diagnostic.severity===`fatal`){log.fatal(diagnostic.title,lines);return}log.warn(diagnostic.title,lines)}function withInterfere(
|
|
1
|
+
import{configureBuild}from"./internal/build/configure-build.mjs";import{readInterfereEnv}from"./internal/env.mjs";import{FatalError,log}from"./internal/logger.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{readNextMajorVersion,warnIfServerInstrumentationMissing}from"./internal/setup-warnings.mjs";import{API_URL}from"@interfere/constants/api";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{classifyReleasePipelineError,diagnoseRelease,isInterfereApiKey}from"@interfere/types/sdk/build-diagnostics";import{resolveDeployEnvironment}from"@interfere/types/sdk/deploy-environment";import{PHASE_PRODUCTION_BUILD}from"next/constants.js";const CLIENT_TRACE_METADATA_KEYS=[`traceparent`,`baggage`],DIAGNOSTIC_CONTEXT={publicKeyEnvVar:`INTERFERE_PUBLIC_KEY`};function reportReleaseDiagnostic(code,input){let diagnostic=diagnoseRelease(code,DIAGNOSTIC_CONTEXT,input),lines=[...diagnostic.details];if(diagnostic.severity===`fatal`){log.fatal(diagnostic.title,lines);return}log.warn(diagnostic.title,lines)}function withInterfere(settingsOrConfig={},maybeConfig){let[settings,nextConfig]=maybeConfig===void 0?[{},settingsOrConfig]:[settingsOrConfig,maybeConfig],{interfere,env:userEnv,webpack,turbopack,compiler,productionBrowserSourceMaps,...rest}=nextConfig;if(interfere&&`buildId`in interfere&&interfere.buildId!==void 0)return log.fatal(`Removed: interfere.buildId`,["`interfere.buildId` was removed in SDK 10. It only worked at build time, so server-side spans drifted from the bundle's slug.","Override the commit SHA via the `INTERFERE_SOURCE_ID` env var on both build and runtime envs instead."]);let projectDir=process.cwd(),config=resolveBuildConfig(settings),apiHost=resolveBrowserApiHost(interfere?.apiHost,config.apiUrl),forceEnable=!!process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE,build=configureBuild({existingWebpack:webpack,existingTurbopack:turbopack,projectDir,values:{__INTERFERE_RELEASE_SLUG__:forceEnable?LOCAL_DEV_RELEASE_SLUG:config.releaseSlug,...config.publicKey&&{__INTERFERE_PUBLIC_KEY__:config.publicKey,...apiHost&&{__INTERFERE_API_URL__:apiHost}},...config.environment&&{__INTERFERE_ENVIRONMENT__:config.environment},...forceEnable&&{__INTERFERE_FORCE_ENABLE__:!0}}});if(config.publicKey!==null&&!build.clientInstrumentationApplies)return log.fatal(`Missing instrumentation client`,[`INTERFERE_PUBLIC_KEY is set but no instrumentation-client file was found.`,`Create an instrumentation-client.ts file in your project root or src/ directory.`]);config.publicKey!==null&&warnIfServerInstrumentationMissing(projectDir);let nextMajor=readNextMajorVersion(projectDir),experimental=interfereExperimentalConfig({existing:rest.experimental,instrumentationHook:nextMajor===null||nextMajor<=14,traceMetadata:config.publicKey!==null||forceEnable}),existingHook=compiler?.runAfterProductionCompile,browserSourceMaps=config.apiKey===null?productionBrowserSourceMaps:!0;return config.apiKey!==null&&productionBrowserSourceMaps===!1&&log.warn(`Forcing productionBrowserSourceMaps`,[`withInterfere() requires production source maps to symbolicate errors.`,"Your `productionBrowserSourceMaps: false` was overridden to `true`."]),{...rest,...userEnv?{env:userEnv}:{},compiler:{...omitUndefined(compiler??{}),async runAfterProductionCompile(context){existingHook&&await existingHook(context);try{await runBuildReleasePipeline(context,config)}catch(error){if(error instanceof FatalError)throw error;let message=error instanceof Error?error.message:String(error);log.warn(`Skipping release pipeline`,[`Interfere encountered an unexpected error.`,`Build will continue, but this release's data will be ingested, but ignored as finding when generating insights.`,`Error: ${message}`])}}},...omitUndefined({productionBrowserSourceMaps:browserSourceMaps,turbopack:build.turbopack,webpack:build.webpack}),...experimental?{experimental}:{}}}function interfereExperimentalConfig({existing,instrumentationHook,traceMetadata}){let merged={...existing,...instrumentationHook?{instrumentationHook:!0}:{},...traceMetadata?{clientTraceMetadata:[...new Set([...existing?.clientTraceMetadata??[],...CLIENT_TRACE_METADATA_KEYS])]}:{}};return Object.keys(merged).length>0?merged:void 0}function resolveBrowserApiHost(apiHost,apiUrl){let host=apiHost??apiUrl;if(host!==API_URL)return normalizeHost(host)}function normalizeHost(host){try{return new URL(host).origin}catch{return log.fatal(`Invalid apiHost`,[`interfere.apiHost must be a full URL, e.g. "https://in.yourapp.com".`,`Received: ${host}`])}}function resolveBuildConfig(settings){let{apiKey,apiUrl,publicKey}=readInterfereEnv(),{commitSha,slug}=resolveReleaseSlug(),environment=process.env.NEXT_PHASE===PHASE_PRODUCTION_BUILD?resolveDeployEnvironment(process.env):null;return{apiKey:apiKey??null,apiUrl,buildId:commitSha,environment,publicKey:publicKey??null,purgeSourceMaps:settings.sourceMaps?.purge??!0,releaseSlug:slug}}async function runBuildReleasePipeline(context,config){let{apiKey,buildId,releaseSlug}=config;if(!apiKey)return log.fatal(`API key not set`,[`withInterfere() requires an API key to publish release metadata during production builds.`,`Set the INTERFERE_API_KEY environment variable, or remove withInterfere() from your Next.js config.`,`See: https://interfere.com/docs/guides/next-js`]);if(!isInterfereApiKey(apiKey))return reportReleaseDiagnostic(`API_KEY_WRONG_PREFIX`,{apiKey});if(!(buildId&&releaseSlug))return log.fatal(`Commit SHA missing`,[`Could not resolve a commit SHA from environment variables or git.`,"Set `INTERFERE_SOURCE_ID`, ensure your CI exposes its git SHA env var (e.g. `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`), or run inside a git checkout."]);let{runBuildPipeline}=await import(`./internal/build/pipeline.mjs`);try{let result=await runBuildPipeline(context,{...config,apiKey,buildId,releaseSlug});if(!result.ready){log.warn(`Skipping`,[`No source maps found`]);return}log.info(`Completed`,[`https://interfere.com/~/${result.config?.org.slug}/surfaces/${result.config?.surface.slug}`,`Release: ${result.release?.destination.slug??`Unknown`}`,`Build: ${result.buildId}`,`Artifacts: ${result.fileCount} source maps`])}catch(error){let message=error instanceof Error?error.message:String(error);reportReleaseDiagnostic(classifyReleasePipelineError(error),{errorMessage:message});return}}export{withInterfere};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readInterfereEnv}from"./internal/env.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{FetchTraceExporter}from"./internal/server/edge-exporter.mjs";import{withPublicKey}from"./internal/url.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{trace}from"@opentelemetry/api";import{resourceFromAttributes}from"@opentelemetry/resources";import{BasicTracerProvider,SimpleSpanProcessor}from"@opentelemetry/sdk-trace-base";let registered=!1;function register(opts={}){if(registered)return Promise.resolve();let env=readInterfereEnv();if(!env.publicKey)return Promise.resolve();let releaseSlug=process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE?LOCAL_DEV_RELEASE_SLUG:null;releaseSlug||console.warn(`[interfere] edge runtime has no build-baked release.slug; the collector will reject edge spans until one is wired (production follow-up).`);let sinkUrl=withPublicKey(`${env.apiUrl}/v2/sink`,env.publicKey),provider=new BasicTracerProvider({resource:resourceFromAttributes({"service.name":opts.serviceName??`interfere-sdk-next-edge`,"service.namespace":`interfere`,"deployment.environment.name":env.nodeEnvironment,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}}),spanProcessors:[new SimpleSpanProcessor(new FetchTraceExporter(sinkUrl,{"x-interfere-producer-version":PRODUCER_VERSION}))]});return trace.setGlobalTracerProvider(provider),registered=!0,Promise.resolve()}function buildInterfereOtelKit(_opts={}){return null}export{buildInterfereOtelKit,onRequestError,register};
|
|
1
|
+
import{readInterfereEnv}from"./internal/env.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{FetchTraceExporter}from"./internal/server/edge-exporter.mjs";import{setServerFlush}from"./internal/server/server-flush.mjs";import{withPublicKey}from"./internal/url.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{LOCAL_DEV_RELEASE_SLUG}from"@interfere/types/releases/slug";import{trace}from"@opentelemetry/api";import{resourceFromAttributes}from"@opentelemetry/resources";import{BasicTracerProvider,SimpleSpanProcessor}from"@opentelemetry/sdk-trace-base";let registered=!1;function register(opts={}){if(registered)return Promise.resolve();let env=readInterfereEnv();if(!env.publicKey)return Promise.resolve();let releaseSlug=process.env.NEXT_PUBLIC_INTERFERE_FORCE_ENABLE?LOCAL_DEV_RELEASE_SLUG:null;releaseSlug||console.warn(`[interfere] edge runtime has no build-baked release.slug; the collector will reject edge spans until one is wired (production follow-up).`);let sinkUrl=withPublicKey(`${env.apiUrl}/v2/sink`,env.publicKey),provider=new BasicTracerProvider({resource:resourceFromAttributes({"service.name":opts.serviceName??`interfere-sdk-next-edge`,"service.namespace":`interfere`,"deployment.environment.name":env.nodeEnvironment,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}}),spanProcessors:[new SimpleSpanProcessor(new FetchTraceExporter(sinkUrl,{"x-interfere-producer-version":PRODUCER_VERSION}))]});return trace.setGlobalTracerProvider(provider),setServerFlush(()=>provider.forceFlush()),registered=!0,Promise.resolve()}function buildInterfereOtelKit(_opts={}){return null}export{buildInterfereOtelKit,onRequestError,register};
|
package/dist/instrumentation.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readInterfereEnv}from"./internal/env.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{withPublicKey}from"./internal/url.mjs";import{fetchAndCacheRemoteConfig}from"./internal/server/remote-config.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{bridgeConsoleToOtel}from"./internal/server/console-bridge.mjs";import{installGlobalHandlers}from"./internal/server/global-handlers.mjs";import{PrerenderSafeIdGenerator}from"./internal/server/id-generator.mjs";import{metrics,propagation}from"@opentelemetry/api";import{resourceFromAttributes}from"@opentelemetry/resources";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{BaggageSpanProcessor}from"@opentelemetry/baggage-span-processor";import{AsyncLocalStorageContextManager}from"@opentelemetry/context-async-hooks";import{OTLPLogExporter}from"@opentelemetry/exporter-logs-otlp-http";import{AggregationTemporalityPreference,OTLPMetricExporter}from"@opentelemetry/exporter-metrics-otlp-http";import{OTLPTraceExporter}from"@opentelemetry/exporter-trace-otlp-http";import{registerInstrumentations}from"@opentelemetry/instrumentation";import{UndiciInstrumentation}from"@opentelemetry/instrumentation-undici";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{MeterProvider,PeriodicExportingMetricReader}from"@opentelemetry/sdk-metrics";import{NodeTracerProvider}from"@opentelemetry/sdk-trace-node";let registered=!1,tracerProvider=null,loggerProvider=null,meterProvider=null;async function flush(){await Promise.all([tracerProvider?.forceFlush(),loggerProvider?.forceFlush(),meterProvider?.forceFlush()])}function matchesAny(url,patterns){for(let pattern of patterns)if(pattern instanceof RegExp?pattern.test(url):url.includes(pattern))return!0;return!1}function buildInterfereOtelKit(opts={}){let env=readInterfereEnv();if(!env.publicKey)return null;let{slug:releaseSlug}=resolveReleaseSlug();releaseSlug||console.warn("[interfere] No commit SHA available; server spans will ship without `release.slug`. Set `INTERFERE_SOURCE_ID` (or any of `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`) on the runtime env.");let sinkUrl=withPublicKey(`${env.apiUrl}/v2/sink`,env.publicKey),ignoreUrls=[sinkUrl,...opts.ignoreUrls??[]],propagateContextUrls=opts.propagateContextUrls??[],exporterHeaders={"x-interfere-producer-version":PRODUCER_VERSION};return{resourceAttributes:{"service.name":opts.serviceName??`interfere-sdk-next-server`,"service.namespace":`interfere`,"deployment.environment.name":env.nodeEnvironment,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}},spanProcessors:[new BaggageSpanProcessor(()=>!0),new BatchSpanProcessor(new OTLPTraceExporter({url:sinkUrl,headers:exporterHeaders})),...opts._internalAdditionalSpanProcessors??[]],logRecordProcessors:[new BatchLogRecordProcessor(new OTLPLogExporter({url:sinkUrl,headers:exporterHeaders})),...opts._internalAdditionalLogRecordProcessors??[]],metricReaders:[new PeriodicExportingMetricReader({exporter:new OTLPMetricExporter({url:sinkUrl,headers:exporterHeaders,temporalityPreference:AggregationTemporalityPreference.DELTA}),exportIntervalMillis:3e4}),...opts._internalAdditionalMetricReaders??[]],propagators:[new W3CBaggagePropagator],instrumentations:[new UndiciInstrumentation({ignoreRequestHook:req=>matchesAny(`${req.origin}${req.path}`,ignoreUrls),requestHook:(span,req)=>{matchesAny(`${req.origin}${req.path}`,propagateContextUrls)&&span.setAttribute(`interfere.propagated`,!0)}})],enableConsoleBridge:()=>bridgeConsoleToOtel(),fetchRemoteConfig:()=>fetchAndCacheRemoteConfig()}}async function register(opts={}){if(registered)return;let kit=buildInterfereOtelKit(opts);if(!kit)return;let resource=resourceFromAttributes(kit.resourceAttributes);tracerProvider=new NodeTracerProvider({resource,spanProcessors:kit.spanProcessors,idGenerator:new PrerenderSafeIdGenerator}),propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,...kit.propagators]})),tracerProvider.register({contextManager:new AsyncLocalStorageContextManager().enable()}),loggerProvider=new LoggerProvider({resource,processors:kit.logRecordProcessors});let{logs:logsApi}=await import(`@opentelemetry/api-logs`);logsApi.setGlobalLoggerProvider(loggerProvider),meterProvider=new MeterProvider({resource,readers:kit.metricReaders}),metrics.setGlobalMeterProvider(meterProvider),registerInstrumentations({tracerProvider,meterProvider,instrumentations:kit.instrumentations}),opts.consoleBridge!==!1&&kit.enableConsoleBridge(),installGlobalHandlers({captureUncaughtException:opts.captureUncaughtException??!0,captureUnhandledRejection:opts.captureUnhandledRejection??!0,exitOnUncaughtException:process.env.NODE_ENV===`production`,flush}),registered=!0,await kit.fetchRemoteConfig()}export{PrerenderSafeIdGenerator,buildInterfereOtelKit,flush,onRequestError,register};
|
|
1
|
+
import{readInterfereEnv}from"./internal/env.mjs";import{resolveReleaseSlug}from"./internal/release-slug.mjs";import{PRODUCER_VERSION}from"./internal/version.mjs";import{setServerFlush}from"./internal/server/server-flush.mjs";import{withPublicKey}from"./internal/url.mjs";import{fetchAndCacheRemoteConfig}from"./internal/server/remote-config.mjs";import{onRequestError}from"./internal/server/capture.mjs";import{bridgeConsoleToOtel}from"./internal/server/console-bridge.mjs";import{installGlobalHandlers}from"./internal/server/global-handlers.mjs";import{PrerenderSafeIdGenerator}from"./internal/server/id-generator.mjs";import{metrics,propagation}from"@opentelemetry/api";import{resourceFromAttributes}from"@opentelemetry/resources";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{BaggageSpanProcessor}from"@opentelemetry/baggage-span-processor";import{AsyncLocalStorageContextManager}from"@opentelemetry/context-async-hooks";import{OTLPLogExporter}from"@opentelemetry/exporter-logs-otlp-http";import{AggregationTemporalityPreference,OTLPMetricExporter}from"@opentelemetry/exporter-metrics-otlp-http";import{OTLPTraceExporter}from"@opentelemetry/exporter-trace-otlp-http";import{registerInstrumentations}from"@opentelemetry/instrumentation";import{UndiciInstrumentation}from"@opentelemetry/instrumentation-undici";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{MeterProvider,PeriodicExportingMetricReader}from"@opentelemetry/sdk-metrics";import{NodeTracerProvider}from"@opentelemetry/sdk-trace-node";let registered=!1,tracerProvider=null,loggerProvider=null,meterProvider=null;async function flush(){await Promise.all([tracerProvider?.forceFlush(),loggerProvider?.forceFlush(),meterProvider?.forceFlush()])}function matchesAny(url,patterns){for(let pattern of patterns)if(pattern instanceof RegExp?pattern.test(url):url.includes(pattern))return!0;return!1}function buildInterfereOtelKit(opts={}){let env=readInterfereEnv();if(!env.publicKey)return null;let{slug:releaseSlug}=resolveReleaseSlug();releaseSlug||console.warn("[interfere] No commit SHA available; server spans will ship without `release.slug`. Set `INTERFERE_SOURCE_ID` (or any of `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`) on the runtime env.");let sinkUrl=withPublicKey(`${env.apiUrl}/v2/sink`,env.publicKey),ignoreUrls=[sinkUrl,...opts.ignoreUrls??[]],propagateContextUrls=opts.propagateContextUrls??[],exporterHeaders={"x-interfere-producer-version":PRODUCER_VERSION};return{resourceAttributes:{"service.name":opts.serviceName??`interfere-sdk-next-server`,"service.namespace":`interfere`,"deployment.environment.name":env.nodeEnvironment,"telemetry.sdk.language":`nodejs`,"interfere.sdk.name":`@interfere/next`,"interfere.sdk.version":PRODUCER_VERSION,...releaseSlug?{"release.slug":releaseSlug}:{}},spanProcessors:[new BaggageSpanProcessor(()=>!0),new BatchSpanProcessor(new OTLPTraceExporter({url:sinkUrl,headers:exporterHeaders})),...opts._internalAdditionalSpanProcessors??[]],logRecordProcessors:[new BatchLogRecordProcessor(new OTLPLogExporter({url:sinkUrl,headers:exporterHeaders})),...opts._internalAdditionalLogRecordProcessors??[]],metricReaders:[new PeriodicExportingMetricReader({exporter:new OTLPMetricExporter({url:sinkUrl,headers:exporterHeaders,temporalityPreference:AggregationTemporalityPreference.DELTA}),exportIntervalMillis:3e4}),...opts._internalAdditionalMetricReaders??[]],propagators:[new W3CBaggagePropagator],instrumentations:[new UndiciInstrumentation({ignoreRequestHook:req=>matchesAny(`${req.origin}${req.path}`,ignoreUrls),requestHook:(span,req)=>{matchesAny(`${req.origin}${req.path}`,propagateContextUrls)&&span.setAttribute(`interfere.propagated`,!0)}})],enableConsoleBridge:()=>bridgeConsoleToOtel(),fetchRemoteConfig:()=>fetchAndCacheRemoteConfig()}}async function register(opts={}){if(registered)return;let kit=buildInterfereOtelKit(opts);if(!kit)return;let resource=resourceFromAttributes(kit.resourceAttributes);tracerProvider=new NodeTracerProvider({resource,spanProcessors:kit.spanProcessors,idGenerator:new PrerenderSafeIdGenerator}),propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,...kit.propagators]})),tracerProvider.register({contextManager:new AsyncLocalStorageContextManager().enable()}),loggerProvider=new LoggerProvider({resource,processors:kit.logRecordProcessors});let{logs:logsApi}=await import(`@opentelemetry/api-logs`);logsApi.setGlobalLoggerProvider(loggerProvider),meterProvider=new MeterProvider({resource,readers:kit.metricReaders}),metrics.setGlobalMeterProvider(meterProvider),registerInstrumentations({tracerProvider,meterProvider,instrumentations:kit.instrumentations}),opts.consoleBridge!==!1&&kit.enableConsoleBridge(),installGlobalHandlers({captureUncaughtException:opts.captureUncaughtException??!0,captureUnhandledRejection:opts.captureUnhandledRejection??!0,exitOnUncaughtException:process.env.NODE_ENV===`production`,flush}),setServerFlush(flush),registered=!0,await kit.fetchRemoteConfig()}export{PrerenderSafeIdGenerator,buildInterfereOtelKit,flush,onRequestError,register};
|
|
@@ -2,9 +2,9 @@ import { NextConfig } from "next";
|
|
|
2
2
|
|
|
3
3
|
//#region src/internal/build/configure-build.d.ts
|
|
4
4
|
interface InterfereInjectedValues {
|
|
5
|
+
readonly __INTERFERE_API_URL__?: string;
|
|
5
6
|
readonly __INTERFERE_ENVIRONMENT__?: string;
|
|
6
7
|
readonly __INTERFERE_FORCE_ENABLE__?: boolean;
|
|
7
|
-
readonly __INTERFERE_PROXY_MODE__?: boolean;
|
|
8
8
|
readonly __INTERFERE_PUBLIC_KEY__?: string;
|
|
9
9
|
readonly __INTERFERE_RELEASE_SLUG__: string | null;
|
|
10
10
|
}
|
|
@@ -15,6 +15,7 @@ interface ConfigureBuildInput {
|
|
|
15
15
|
readonly values: InterfereInjectedValues;
|
|
16
16
|
}
|
|
17
17
|
interface ConfigureBuildOutput {
|
|
18
|
+
readonly clientInstrumentationApplies: boolean;
|
|
18
19
|
readonly turbopack: NextConfig["turbopack"] | undefined;
|
|
19
20
|
readonly webpack: NextConfig["webpack"] | undefined;
|
|
20
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync}from"node:fs";import{basename,dirname,join,normalize,resolve}from"node:path";import{fileURLToPath}from"node:url";const INSTRUMENTATION_CLIENT_FILES=[`instrumentation-client.ts`,`instrumentation-client.tsx`,`instrumentation-client.js`,`instrumentation-client.jsx`,`src/instrumentation-client.ts`,`src/instrumentation-client.tsx`,`src/instrumentation-client.js`,`src/instrumentation-client.jsx`],INSTRUMENTATION_SERVER_FILES=[`instrumentation.ts`,`instrumentation.tsx`,`instrumentation.js`,`instrumentation.jsx`,`src/instrumentation.ts`,`src/instrumentation.tsx`,`src/instrumentation.js`,`src/instrumentation.jsx`],LOADER_FILE_CANDIDATES=[`value-injection-loader.ts`,`value-injection-loader.js`,`value-injection-loader.mjs`,`value-injection-loader.cjs`];function configureBuild({existingWebpack,existingTurbopack,projectDir,values}){let clientPath=resolveFirstExisting(projectDir,INSTRUMENTATION_CLIENT_FILES),targets=[clientPath,resolveFirstExisting(projectDir,INSTRUMENTATION_SERVER_FILES)].filter(path=>path!==null);return{webpack:buildWebpackHook(existingWebpack,
|
|
1
|
+
import{existsSync}from"node:fs";import{basename,dirname,join,normalize,resolve}from"node:path";import{fileURLToPath}from"node:url";const INSTRUMENTATION_CLIENT_FILES=[`instrumentation-client.ts`,`instrumentation-client.tsx`,`instrumentation-client.js`,`instrumentation-client.jsx`,`src/instrumentation-client.ts`,`src/instrumentation-client.tsx`,`src/instrumentation-client.js`,`src/instrumentation-client.jsx`],INSTRUMENTATION_SERVER_FILES=[`instrumentation.ts`,`instrumentation.tsx`,`instrumentation.js`,`instrumentation.jsx`,`src/instrumentation.ts`,`src/instrumentation.tsx`,`src/instrumentation.js`,`src/instrumentation.jsx`],LOADER_FILE_CANDIDATES=[`value-injection-loader.ts`,`value-injection-loader.js`,`value-injection-loader.mjs`,`value-injection-loader.cjs`];function configureBuild({existingWebpack,existingTurbopack,projectDir,values}){let clientPath=resolveFirstExisting(projectDir,INSTRUMENTATION_CLIENT_FILES),targets=[clientPath,resolveFirstExisting(projectDir,INSTRUMENTATION_SERVER_FILES)].filter(path=>path!==null),clientInstrumentationApplies=!!(clientPath&&hasInjectedMetadata(values));return{clientInstrumentationApplies,webpack:buildWebpackHook(existingWebpack,targets,values,clientInstrumentationApplies),turbopack:buildTurbopackConfig(existingTurbopack,targets,values,clientInstrumentationApplies)}}function hasInjectedMetadata(metadata){return metadata.__INTERFERE_RELEASE_SLUG__!==null||metadata.__INTERFERE_PUBLIC_KEY__!==void 0}function buildWebpackHook(existingWebpack,targets,values,injectionApplies){if(!injectionApplies)return existingWebpack;let normalizedTargets=new Set(targets.map(path=>normalize(path))),injectionLoaderPath=resolveLoaderPath();return(webpackConfig,options)=>{let outputConfig=existingWebpack?existingWebpack(webpackConfig,options):webpackConfig,mutableConfig=outputConfig;return mutableConfig.module??={},mutableConfig.module.rules??=[],mutableConfig.module.rules.push({test:resource=>!!resource&&normalizedTargets.has(normalize(resource)),use:[{loader:injectionLoaderPath,options:{values}}]}),outputConfig}}function buildTurbopackConfig(existingTurbopack,targets,values,injectionApplies){let baseConfig=existingTurbopack??{},debugIds=baseConfig.debugIds??!0;if(!injectionApplies)return existingTurbopack===void 0&&debugIds===baseConfig.debugIds?existingTurbopack:{...baseConfig,debugIds};let rules={...baseConfig.rules},loaderPath=resolveLoaderPath();for(let target of targets){let ruleKey=`**/${basename(target)}`;rules[ruleKey]=appendTurbopackLoader(rules[ruleKey],{loader:loaderPath,options:{serializedValues:JSON.stringify(values)}})}return{...baseConfig,debugIds,rules}}function appendTurbopackLoader(existing,loader){let rule=existing??{},loaders=Array.isArray(rule.loaders)?rule.loaders:[];return{...rule,loaders:[...loaders,loader]}}function resolveFirstExisting(projectDir,candidates){for(let candidate of candidates){let filePath=resolve(projectDir,candidate);if(existsSync(filePath))return filePath}return null}function resolveLoaderPath(){let directory=resolve(dirname(fileURLToPath(import.meta.url)));for(let candidate of LOADER_FILE_CANDIDATES){let filePath=join(directory,candidate);if(existsSync(filePath))return filePath}return join(directory,`value-injection-loader.js`)}export{configureBuild};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{detectActiveBundler}from"./detect-bundler.mjs";import{resolveReleaseRequest}from"./release/index.mjs";import{discoverSourceMaps}from"./source-maps/discover.mjs";import{uploadSourceMaps}from"./source-maps/upload.mjs";import{cleanupSourceMaps}from"./source-maps/index.mjs";import{releaseSlugSchema}from"@interfere/types/releases/slug";import{HTTPClient,Interfere}from"@interfere/sdk";async function timed(fn){let start=performance.now();return[await fn(),Math.round(performance.now()-start)]}async function runBuildPipeline(context,metadata){let{apiUrl,apiKey}=metadata,httpClient=new HTTPClient;httpClient.addHook(`beforeRequest`,request=>{let nextRequest=new Request(request);return nextRequest.headers.set(`Authorization`,`Bearer ${apiKey}`),nextRequest});let sdk=new Interfere({serverURL:apiUrl,httpClient}),security={apiKey},start=performance.now(),bundler=detectActiveBundler(),[{discovered,config},discoverMs]=await timed(async()=>{let[discovered,config]=await Promise.all([discoverSourceMaps({bundler,projectDir:context.projectDir,distDir:context.distDir}),sdk.releases.getConfig(security)]);return{discovered,config}});if(discovered.files.length===0)return{ready:!1,reason:`no_source_maps`,fileCount:0};let releaseRequest=resolveReleaseRequest(metadata.buildId,metadata.environment,metadata.releaseSlug,config),[release,createReleaseMs]=await timed(()=>sdk.releases.create(security,releaseRequest)),releaseSlug=releaseSlugSchema.parse(release.destination.slug),buildId=release.build.hash??metadata.buildId,[{totalBytes},uploadMs]=await timed(()=>uploadSourceMaps({apiUrl,bundler,discovered,httpClient,releaseSlug})),[,preflightMs]=await timed(()=>sdk.releases.preflight(security,{releaseSlug})),[,cleanupMs]=await timed(()=>cleanupSourceMaps(discovered.files)),timing={discover:discoverMs,createRelease:createReleaseMs,upload:uploadMs,preflight:preflightMs,cleanup:cleanupMs,total:Math.round(performance.now()-start),fileCount:discovered.files.length,totalBytes};return{ready:!0,fileCount:discovered.files.length,release:{...release,destination:{...release.destination,slug:releaseSlug}},config,buildId,timing}}export{runBuildPipeline};
|
|
1
|
+
import{detectActiveBundler}from"./detect-bundler.mjs";import{resolveReleaseRequest}from"./release/index.mjs";import{discoverSourceMaps}from"./source-maps/discover.mjs";import{uploadSourceMaps}from"./source-maps/upload.mjs";import{cleanupSourceMaps}from"./source-maps/index.mjs";import{releaseSlugSchema}from"@interfere/types/releases/slug";import{HTTPClient,Interfere}from"@interfere/sdk";async function timed(fn){let start=performance.now();return[await fn(),Math.round(performance.now()-start)]}async function runBuildPipeline(context,metadata){let{apiUrl,apiKey}=metadata,httpClient=new HTTPClient;httpClient.addHook(`beforeRequest`,request=>{let nextRequest=new Request(request);return nextRequest.headers.set(`Authorization`,`Bearer ${apiKey}`),nextRequest});let sdk=new Interfere({serverURL:apiUrl,httpClient}),security={apiKey},start=performance.now(),bundler=detectActiveBundler(),[{discovered,config},discoverMs]=await timed(async()=>{let[discovered,config]=await Promise.all([discoverSourceMaps({bundler,projectDir:context.projectDir,distDir:context.distDir}),sdk.releases.getConfig(security)]);return{discovered,config}});if(discovered.files.length===0)return{ready:!1,reason:`no_source_maps`,fileCount:0};let releaseRequest=resolveReleaseRequest(metadata.buildId,metadata.environment,metadata.releaseSlug,config),[release,createReleaseMs]=await timed(()=>sdk.releases.create(security,releaseRequest)),releaseSlug=releaseSlugSchema.parse(release.destination.slug),buildId=release.build.hash??metadata.buildId,[{totalBytes},uploadMs]=await timed(()=>uploadSourceMaps({apiUrl,bundler,discovered,httpClient,releaseSlug})),[,preflightMs]=await timed(()=>sdk.releases.preflight(security,{releaseSlug})),[,cleanupMs]=await timed(async()=>{metadata.purgeSourceMaps&&await cleanupSourceMaps(discovered.files)}),timing={discover:discoverMs,createRelease:createReleaseMs,upload:uploadMs,preflight:preflightMs,cleanup:cleanupMs,total:Math.round(performance.now()-start),fileCount:discovered.files.length,totalBytes};return{ready:!0,fileCount:discovered.files.length,release:{...release,destination:{...release.destination,slug:releaseSlug}},config,buildId,timing}}export{runBuildPipeline};
|
|
@@ -1,41 +1,7 @@
|
|
|
1
1
|
import { SourceMapFile } from "./discover.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/internal/build/source-maps/discover-webpack.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Inserts a `//# debugId=…` comment into the chunk. Idempotent: if a
|
|
6
|
-
* spec-compliant comment is already present (e.g. webpack 5.104+ with
|
|
7
|
-
* `SourceMapDevToolPlugin({ debugIds: true })`, turbopack with
|
|
8
|
-
* `experimental.turbopack.debugIds`, or any other tool) it's
|
|
9
|
-
* overwritten with the supplied debugId — callers that read the
|
|
10
|
-
* existing id via `readDebugIdFromJs` first get an idempotent no-op.
|
|
11
|
-
*
|
|
12
|
-
* Otherwise the comment is appended just before the existing
|
|
13
|
-
* `//# sourceMappingURL=…` line so the browser still resolves the map.
|
|
14
|
-
*/
|
|
15
|
-
declare function injectDebugIdIntoJs(content: string, debugId: string): string;
|
|
16
|
-
/**
|
|
17
|
-
* Adds `debugId` to the source-map JSON. Idempotent: writes both
|
|
18
|
-
* `debugId` (TC39 spec field) and `debug_id` (legacy variant some
|
|
19
|
-
* older Sentry tooling emits) so manifests stay readable across the
|
|
20
|
-
* ecosystem. Re-running with the same id produces identical output.
|
|
21
|
-
*/
|
|
22
4
|
declare function injectDebugIdIntoMap(content: string, debugId: string): string;
|
|
23
|
-
/**
|
|
24
|
-
* Webpack-mode discovery: walks the dist tree, parses each `.js`
|
|
25
|
-
* chunk's `//# sourceMappingURL=…` comment to find its paired `.map`,
|
|
26
|
-
* and ensures both carry the same `debugId`.
|
|
27
|
-
*
|
|
28
|
-
* Idempotent w.r.t. debug-id state on disk:
|
|
29
|
-
* - If the chunk already carries a `//# debugId=…` comment (webpack
|
|
30
|
-
* 5.104+ with `SourceMapDevToolPlugin({ debugIds: true })`, or a
|
|
31
|
-
* third-party tool that already injected one) we reuse it.
|
|
32
|
-
* - Otherwise we generate a fresh UUID and inject it into both files.
|
|
33
|
-
*
|
|
34
|
-
* The "reuse if present" branch matches the strategy in Sentry's
|
|
35
|
-
* webpack plugin (`addDebugIdToBundleSource` in
|
|
36
|
-
* `bundler-plugin-core/src/debug-id-upload.ts`) and means our
|
|
37
|
-
* discovery works transparently when bundlers natively emit debugIds.
|
|
38
|
-
*/
|
|
39
5
|
declare function discoverWebpack(opts: {
|
|
40
6
|
projectDir: string;
|
|
41
7
|
distDir: string;
|
|
@@ -44,4 +10,4 @@ declare function discoverWebpack(opts: {
|
|
|
44
10
|
sourceFileCount: number;
|
|
45
11
|
}>;
|
|
46
12
|
//#endregion
|
|
47
|
-
export { discoverWebpack,
|
|
13
|
+
export { discoverWebpack, injectDebugIdIntoMap };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash,randomUUID}from"node:crypto";import{readFile,writeFile}from"node:fs/promises";const SOURCEMAPPING_RE=/\/\/[#@]\s*sourceMappingURL=(\S+)\s
|
|
1
|
+
import{resolveDistDir,toPublicPath,walkDistTrees}from"./paths.mjs";import{relative}from"node:path";import{createHash,randomUUID}from"node:crypto";import{readFile,writeFile}from"node:fs/promises";import{injectDebugIdIntoJs,readDebugIdFromJs}from"@interfere/types/data/source-maps";const SOURCEMAPPING_RE=/\/\/[#@]\s*sourceMappingURL=(\S+)\s*$/;function injectDebugIdIntoMap(content,debugId){let json=JSON.parse(content);return json.debugId=debugId,JSON.stringify(json)}async function discoverWebpack(opts){let absDistDir=resolveDistDir(opts.projectDir,opts.distDir),relDistDir=relative(opts.projectDir,absDistDir),jsPaths=await walkDistTrees(absDistDir,`.js`),pairs=new Map;for(let jsAbs of jsPaths){let content=await readFile(jsAbs,`utf8`),match=SOURCEMAPPING_RE.exec(content);if(!match?.[1])continue;let mapAbs=`${jsAbs.slice(0,jsAbs.lastIndexOf(`/`)+1)}${decodeURIComponent(match[1])}`,jsPublic=toPublicPath(relative(opts.projectDir,jsAbs),relDistDir),mapPublic=toPublicPath(relative(opts.projectDir,mapAbs),relDistDir);pairs.set(mapPublic,{mapAbs,jsAbs,jsPublic,jsContent:content,existingDebugId:readDebugIdFromJs(content)})}return{files:(await Promise.all(Array.from(pairs.entries(),async([mapPublic,pair])=>{let mapContent=await readFile(pair.mapAbs,`utf8`).catch(()=>null);if(mapContent===null)return null;let debugId=pair.existingDebugId??randomUUID();if(!pair.existingDebugId){let injectedJs=injectDebugIdIntoJs(pair.jsContent,debugId);await writeFile(pair.jsAbs,injectedJs,`utf8`)}let finalMap=JSON.parse(mapContent).debugId===debugId?mapContent:injectDebugIdIntoMap(mapContent,debugId);return finalMap!==mapContent&&await writeFile(pair.mapAbs,finalMap,`utf8`),{absolute:pair.mapAbs,path:mapPublic,content:finalMap,hash:createHash(`sha256`).update(finalMap).digest(`hex`),debugId,chunkUrl:pair.jsPublic}}))).filter(f=>f!==null),sourceFileCount:pairs.size}}export{discoverWebpack,injectDebugIdIntoMap};
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { NextBuildBundler } from "../detect-bundler.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { injectDebugIdIntoMap } from "./discover-webpack.mjs";
|
|
3
|
+
import { injectDebugIdIntoJs } from "@interfere/types/data/source-maps";
|
|
3
4
|
|
|
4
5
|
//#region src/internal/build/source-maps/discover.d.ts
|
|
5
6
|
interface SourceMapFile {
|
|
6
7
|
readonly absolute: string;
|
|
7
|
-
/**
|
|
8
|
-
* Public URL the runtime will load this chunk from. ALWAYS populated
|
|
9
|
-
* for both bundlers — turbopack discovery walks JS chunks (in addition
|
|
10
|
-
* to .map files) so it can record this. Lifted onto the manifest as
|
|
11
|
-
* `chunkUrl`; server-side suffix-matching pairs `frame.fileName` to
|
|
12
|
-
* this field by content-hashed filename.
|
|
13
|
-
*/
|
|
14
8
|
readonly chunkUrl: string;
|
|
15
9
|
readonly content: string;
|
|
16
10
|
readonly debugId: string;
|
|
@@ -21,13 +15,6 @@ interface DiscoveryResult {
|
|
|
21
15
|
readonly files: SourceMapFile[];
|
|
22
16
|
readonly sourceFileCount: number;
|
|
23
17
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Single entry point for source-map discovery. The pipeline calls this
|
|
26
|
-
* with the active bundler from `detectActiveBundler()`; new bundlers (e.g.
|
|
27
|
-
* rspack) drop in here as additional cases without touching the pipeline.
|
|
28
|
-
* Other manifest bundlers (for Vite, CLI, etc.) live outside the Next
|
|
29
|
-
* build pipeline and should use their own discovery implementation.
|
|
30
|
-
*/
|
|
31
18
|
declare function discoverSourceMaps(opts: {
|
|
32
19
|
bundler: NextBuildBundler;
|
|
33
20
|
projectDir: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{normalizeDistDir}from"./paths.mjs";import{discoverTurbopack}from"./discover-turbopack.mjs";import{discoverWebpack,
|
|
1
|
+
import{normalizeDistDir}from"./paths.mjs";import{discoverTurbopack}from"./discover-turbopack.mjs";import{discoverWebpack,injectDebugIdIntoMap}from"./discover-webpack.mjs";import{injectDebugIdIntoJs}from"@interfere/types/data/source-maps";async function discoverSourceMaps(opts){let distDir=normalizeDistDir(opts.distDir);switch(opts.bundler){case`webpack`:return discoverWebpack({projectDir:opts.projectDir,distDir});case`turbopack`:return discoverTurbopack({projectDir:opts.projectDir,distDir})}}export{discoverSourceMaps,injectDebugIdIntoJs,injectDebugIdIntoMap};
|
|
@@ -9,6 +9,6 @@ interface OnRequestErrorRequest {
|
|
|
9
9
|
}
|
|
10
10
|
declare function onRequestError(error: Error & {
|
|
11
11
|
digest?: string;
|
|
12
|
-
}, request: OnRequestErrorRequest | undefined, context: OnRequestErrorContext): void
|
|
12
|
+
}, request: OnRequestErrorRequest | undefined, context: OnRequestErrorContext): Promise<void>;
|
|
13
13
|
//#endregion
|
|
14
14
|
export { captureError, onRequestError };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isEnabledOnServer}from"../env.mjs";import{isPluginEnabled}from"./remote-config.mjs";import{readServerAction}from"./server-action-marker.mjs";import{SpanKind,SpanStatusCode,trace}from"@opentelemetry/api";import{MECHANISM_TYPE,isNonErrorException,toException}from"@interfere/types/sdk/errors";const ON_REQUEST_ERROR_MECHANISM={type:MECHANISM_TYPE.nextjs.onRequestError,handled:!1,synthetic:!1},SERVER_ACTION_MECHANISM={type:MECHANISM_TYPE.nextjs.serverAction,handled:!1,synthetic:!1},DEFAULT_CAPTURE_ERROR_MECHANISM={type:MECHANISM_TYPE.nextjs.captureError,handled:!0},seen=new WeakSet;function buildAttrs(value,mechanism,context){let attrs={"interfere.exception.mechanism":mechanism.type,"interfere.exception.handled":mechanism.handled},digest=context?.nextjs?.errorDigest;if(digest&&(attrs[`interfere.error.digest`]=digest),context?.nextjs?.requestMethod&&(attrs[`http.request.method`]=context.nextjs.requestMethod),context?.nextjs?.requestPath&&(attrs[`url.path`]=context.nextjs.requestPath),context?.nextjs?.routePath&&(attrs[`http.route`]=context.nextjs.routePath),context?.nextjs?.routeType===`route`&&!mechanism.handled&&(attrs[`http.response.status_code`]=500),context?.serverAction&&(attrs[`interfere.server_action.name`]=context.serverAction.name,context.serverAction.argsShape&&(attrs[`interfere.server_action.args_shape`]=context.serverAction.argsShape)),context?.requestHeaders)for(let[name,headerValue]of Object.entries(context.requestHeaders))attrs[`http.request.header.${name}`]=headerValue;return isNonErrorException(value)?(attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`,attrs):(attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,attrs[`interfere.exception.kind`]=`error`,value.stack&&(attrs[`exception.stacktrace`]=value.stack),attrs)}function recordException({error,mechanism,context}){let value=toException(error),attrs=buildAttrs(value,mechanism,context),tracer=trace.getTracer(`@interfere/next/server`),active=trace.getActiveSpan();if(active?.isRecording()){active.addEvent(`exception`,attrs),mechanism.handled||active.setStatus({code:SpanStatusCode.ERROR,message:isNonErrorException(value)?value.value:value.message});return}let span=tracer.startSpan(`interfere.captureError`,{kind:SpanKind.INTERNAL});span.addEvent(`exception`,attrs),mechanism.handled||span.setStatus({code:SpanStatusCode.ERROR,message:isNonErrorException(value)?value.value:value.message}),span.end()}function captureError(error,_request,context){if(isEnabledOnServer()&&isPluginEnabled(`errors`)){if(error instanceof Error){if(seen.has(error))return;seen.add(error)}recordException({error,mechanism:context?.mechanism??DEFAULT_CAPTURE_ERROR_MECHANISM,context})}}const CAPTURED_REQUEST_HEADERS=[`accept`,`accept-language`,`content-length`,`content-type`,`host`,`referer`,`user-agent`,`x-forwarded-for`,`x-forwarded-host`,`x-forwarded-proto`,`x-vercel-id`];function pickAllowedHeaders(headers){if(!headers)return;let picked={};for(let name of CAPTURED_REQUEST_HEADERS){let value=headers[name];typeof value==`string`&&(picked[name]=value)}return Object.keys(picked).length>0?picked:void 0}function onRequestError(error,request,context){if(seen.has(error)||(seen.add(error),!isEnabledOnServer())||!isPluginEnabled(`errors`))return;let action=readServerAction(error),mechanism=action?SERVER_ACTION_MECHANISM:
|
|
1
|
+
import{isEnabledOnServer}from"../env.mjs";import{flushServerTelemetry}from"./server-flush.mjs";import{isPluginEnabled}from"./remote-config.mjs";import{readServerAction}from"./server-action-marker.mjs";import{SpanKind,SpanStatusCode,trace}from"@opentelemetry/api";import{MECHANISM_TYPE,isNonErrorException,toException}from"@interfere/types/sdk/errors";const ON_REQUEST_ERROR_MECHANISM={type:MECHANISM_TYPE.nextjs.onRequestError,handled:!1,synthetic:!1},SERVER_ACTION_MECHANISM={type:MECHANISM_TYPE.nextjs.serverAction,handled:!1,synthetic:!1},ROUTE_HANDLER_MECHANISM={type:MECHANISM_TYPE.nextjs.routeHandler,handled:!1,synthetic:!1},DEFAULT_CAPTURE_ERROR_MECHANISM={type:MECHANISM_TYPE.nextjs.captureError,handled:!0},CONTROL_FLOW_DIGEST_PREFIXES=[`NEXT_REDIRECT`,`NEXT_NOT_FOUND`,`NEXT_HTTP_ERROR_FALLBACK`];function isNextControlFlowError(error){if(!(error instanceof Error))return!1;let digest=error.digest;return typeof digest==`string`&&CONTROL_FLOW_DIGEST_PREFIXES.some(prefix=>digest.startsWith(prefix))}const seen=new WeakSet;function buildAttrs(value,mechanism,context){let attrs={"interfere.exception.mechanism":mechanism.type,"interfere.exception.handled":mechanism.handled},digest=context?.nextjs?.errorDigest;if(digest&&(attrs[`interfere.error.digest`]=digest),context?.nextjs?.requestMethod&&(attrs[`http.request.method`]=context.nextjs.requestMethod),context?.nextjs?.requestPath&&(attrs[`url.path`]=context.nextjs.requestPath),context?.nextjs?.routePath&&(attrs[`http.route`]=context.nextjs.routePath),context?.nextjs?.routeType===`route`&&!mechanism.handled&&(attrs[`http.response.status_code`]=500),context?.serverAction&&(attrs[`interfere.server_action.name`]=context.serverAction.name,context.serverAction.argsShape&&(attrs[`interfere.server_action.args_shape`]=context.serverAction.argsShape)),context?.requestHeaders)for(let[name,headerValue]of Object.entries(context.requestHeaders))attrs[`http.request.header.${name}`]=headerValue;return isNonErrorException(value)?(attrs[`exception.type`]=value.type,attrs[`exception.message`]=value.value,attrs[`interfere.exception.kind`]=`non-error`,attrs):(attrs[`exception.type`]=value.name,attrs[`exception.message`]=value.message,attrs[`interfere.exception.kind`]=`error`,value.stack&&(attrs[`exception.stacktrace`]=value.stack),attrs)}function recordException({error,mechanism,context}){let value=toException(error),attrs=buildAttrs(value,mechanism,context),tracer=trace.getTracer(`@interfere/next/server`),active=trace.getActiveSpan();if(active?.isRecording()){active.addEvent(`exception`,attrs),mechanism.handled||active.setStatus({code:SpanStatusCode.ERROR,message:isNonErrorException(value)?value.value:value.message});return}let span=tracer.startSpan(`interfere.captureError`,{kind:SpanKind.INTERNAL});span.addEvent(`exception`,attrs),mechanism.handled||span.setStatus({code:SpanStatusCode.ERROR,message:isNonErrorException(value)?value.value:value.message}),span.end()}function captureError(error,_request,context){if(isEnabledOnServer()&&isPluginEnabled(`errors`)&&!isNextControlFlowError(error)){if(error instanceof Error){if(seen.has(error))return;seen.add(error)}recordException({error,mechanism:context?.mechanism??DEFAULT_CAPTURE_ERROR_MECHANISM,context})}}const CAPTURED_REQUEST_HEADERS=[`accept`,`accept-language`,`content-length`,`content-type`,`host`,`referer`,`user-agent`,`x-forwarded-for`,`x-forwarded-host`,`x-forwarded-proto`,`x-vercel-id`];function pickAllowedHeaders(headers){if(!headers)return;let picked={};for(let name of CAPTURED_REQUEST_HEADERS){let value=headers[name];typeof value==`string`&&(picked[name]=value)}return Object.keys(picked).length>0?picked:void 0}async function onRequestError(error,request,context){if(isNextControlFlowError(error)||seen.has(error)||(seen.add(error),!isEnabledOnServer())||!isPluginEnabled(`errors`))return;let action=readServerAction(error),mechanism=ON_REQUEST_ERROR_MECHANISM;action?mechanism=SERVER_ACTION_MECHANISM:context.routeType===`route`&&(mechanism=ROUTE_HANDLER_MECHANISM);let requestHeaders=pickAllowedHeaders(request?.headers);recordException({error,mechanism,context:{mechanism,nextjs:{...context,...request?.method?{requestMethod:request.method}:{},...request?.path?{requestPath:request.path}:{},...error.digest?{errorDigest:error.digest}:{}},...requestHeaders?{requestHeaders}:{},...action?{serverAction:action}:{}}});try{await flushServerTelemetry()}catch{return}}export{captureError,onRequestError};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let flushFn=null;function setServerFlush(fn){flushFn=fn}function flushServerTelemetry(){return flushFn?flushFn():Promise.resolve()}export{flushServerTelemetry,setServerFlush};
|
package/dist/package.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var name=`@interfere/next`,version=`11.0.0-canary.
|
|
1
|
+
var name=`@interfere/next`,version=`11.0.0-canary.7`;export{name,version};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interfere/next",
|
|
3
|
-
"version": "11.0.0-canary.
|
|
3
|
+
"version": "11.0.0-canary.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Build software that never breaks.",
|
|
6
6
|
"keywords": [
|
|
@@ -77,11 +77,6 @@
|
|
|
77
77
|
"types": "./dist/provider.d.mts",
|
|
78
78
|
"default": "./dist/provider.mjs"
|
|
79
79
|
},
|
|
80
|
-
"./route-handler": {
|
|
81
|
-
"monorepo": "./src/route-handler.ts",
|
|
82
|
-
"types": "./dist/route-handler.d.mts",
|
|
83
|
-
"default": "./dist/route-handler.mjs"
|
|
84
|
-
},
|
|
85
80
|
"./server": {
|
|
86
81
|
"monorepo": "./src/server.ts",
|
|
87
82
|
"types": "./dist/server.d.mts",
|
|
@@ -98,11 +93,11 @@
|
|
|
98
93
|
"typecheck": "tsgo --noEmit"
|
|
99
94
|
},
|
|
100
95
|
"dependencies": {
|
|
101
|
-
"@interfere/constants": "^9.0.2-canary.
|
|
102
|
-
"@interfere/helpers": "^9.0.
|
|
103
|
-
"@interfere/react": "^11.0.0-canary.
|
|
104
|
-
"@interfere/sdk": "^11.0.0-canary.
|
|
105
|
-
"@interfere/types": "^
|
|
96
|
+
"@interfere/constants": "^9.0.2-canary.3",
|
|
97
|
+
"@interfere/helpers": "^9.0.1-canary.2",
|
|
98
|
+
"@interfere/react": "^11.0.0-canary.7",
|
|
99
|
+
"@interfere/sdk": "^11.0.0-canary.7",
|
|
100
|
+
"@interfere/types": "^10.0.0-canary.6",
|
|
106
101
|
"@opentelemetry/api": "^1.9.1",
|
|
107
102
|
"@opentelemetry/api-logs": "^0.219.0",
|
|
108
103
|
"@opentelemetry/baggage-span-processor": "^0.5.0",
|
|
@@ -138,12 +133,12 @@
|
|
|
138
133
|
}
|
|
139
134
|
},
|
|
140
135
|
"devDependencies": {
|
|
141
|
-
"@interfere/test-utils": "^9.0.
|
|
142
|
-
"@interfere/typescript-config": "^9.0.
|
|
143
|
-
"@types/node": "^26.
|
|
136
|
+
"@interfere/test-utils": "^9.0.1-canary.1",
|
|
137
|
+
"@interfere/typescript-config": "^9.0.1-canary.1",
|
|
138
|
+
"@types/node": "^26.1.0",
|
|
144
139
|
"@types/react": "19.2.17",
|
|
145
140
|
"@types/react-dom": "19.2.3",
|
|
146
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
141
|
+
"@typescript/native-preview": "7.0.0-dev.20260701.1",
|
|
147
142
|
"@vitest/browser": "4.1.9",
|
|
148
143
|
"@vitest/browser-playwright": "4.1.9",
|
|
149
144
|
"@vitest/coverage-v8": "4.1.9",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
//#region src/internal/route/handle-get.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Generic GET proxy. SDK 10.x routes `GET /v2/config` (and any future
|
|
4
|
-
* GET-based endpoints) through here; SDK 9.x clients still hit
|
|
5
|
-
* `GET /v1/config` and get the same delegated response on the collector
|
|
6
|
-
* side.
|
|
7
|
-
*
|
|
8
|
-
* `/sw` is special-cased to serve the SDK's bundled service-worker
|
|
9
|
-
* script directly out of the customer's Next.js process. `SW_SCRIPT`
|
|
10
|
-
* is a string export emitted at @interfere/react build time
|
|
11
|
-
* (`scripts/build-sw.ts`); on Node runtime the customer resolves it
|
|
12
|
-
* via `node_modules`, on Edge runtime the customer's Next.js bundler
|
|
13
|
-
* inlines it into the Edge worker. Either way, no `fs.readFileSync`.
|
|
14
|
-
*/
|
|
15
|
-
declare function handleGet(request: Request): Promise<Response>;
|
|
16
|
-
//#endregion
|
|
17
|
-
export { handleGet };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{log}from"../logger.mjs";import{extractSubPath,formatProxyError,forwardToCollector,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv}from"./proxy.mjs";import{SW_SCRIPT}from"@interfere/react/sw";const SW_HEADERS={"content-type":`application/javascript; charset=utf-8`,"service-worker-allowed":`/`,"cache-control":`public, max-age=3600`};async function handleGet(request){let subPath=extractSubPath(request);if(subPath===`/sw`)return new Response(SW_SCRIPT,{status:200,headers:SW_HEADERS});let env=resolveAuthenticatedEnv();if(!hasPublicKeyCredential(request,env.publicKey))return notConfiguredResponse();try{return await forwardToCollector(request,env,subPath)}catch(error){let detail=formatProxyError(error);return log.error(`Proxy ${request.method} ${subPath} failed`,detail.lines),Response.json({code:`INTERFERE_PROXY_ERROR`,message:detail.message},{status:502})}}export{handleGet};
|
|
@@ -1,15 +0,0 @@
|
|
|
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
|
-
*/
|
|
13
|
-
declare function handlePost(request: Request): Promise<Response>;
|
|
14
|
-
//#endregion
|
|
15
|
-
export { handlePost };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{log}from"../logger.mjs";import{extractSubPath,formatProxyError,forwardToCollector,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv}from"./proxy.mjs";async function handlePost(request){let env=resolveAuthenticatedEnv();if(!hasPublicKeyCredential(request,env.publicKey))return notConfiguredResponse();let subPath=extractSubPath(request);try{return await forwardToCollector(request,env,subPath)}catch(error){let detail=formatProxyError(error);return log.error(`Proxy ${request.method} ${subPath} failed`,detail.lines),Response.json({code:`INTERFERE_PROXY_ERROR`,message:detail.message},{status:502})}}export{handlePost};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { InterfereEnv } from "../env.mjs";
|
|
2
|
-
|
|
3
|
-
//#region src/internal/route/proxy.d.ts
|
|
4
|
-
interface AuthenticatedEnv {
|
|
5
|
-
apiUrl: string;
|
|
6
|
-
publicKey: string | null;
|
|
7
|
-
release: InterfereEnv["release"];
|
|
8
|
-
}
|
|
9
|
-
declare function resolveAuthenticatedEnv(): AuthenticatedEnv;
|
|
10
|
-
/** True when the proxy can attach a publishable key from env or the request. */
|
|
11
|
-
declare function hasPublicKeyCredential(request: Request, envPublicKey: string | null): boolean;
|
|
12
|
-
declare function notConfiguredResponse(): Response;
|
|
13
|
-
declare function extractSubPath(request: Request): string;
|
|
14
|
-
declare function formatProxyError(error: unknown): {
|
|
15
|
-
message: string;
|
|
16
|
-
lines: string[];
|
|
17
|
-
};
|
|
18
|
-
declare function forwardToCollector(request: Request, env: AuthenticatedEnv, subPath: string): Promise<Response>;
|
|
19
|
-
//#endregion
|
|
20
|
-
export { AuthenticatedEnv, extractSubPath, formatProxyError, forwardToCollector, hasPublicKeyCredential, notConfiguredResponse, resolveAuthenticatedEnv };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{readInterfereEnv}from"../env.mjs";import{log}from"../logger.mjs";import{omitUndefined}from"@interfere/helpers/omit-undefined";import{resolveRoutePrefix}from"@interfere/constants/route-prefix";import{extractCountryHeader}from"@interfere/types/sdk/geo";const STRIPPED_AUTH_HEADERS=[`authorization`,`x-api-key`,`x-interfere-api-key`],STRIPPED_TRANSPORT_HEADERS=[`host`,`content-length`,`connection`,`keep-alive`,`transfer-encoding`,`te`,`trailer`,`upgrade`,`proxy-authenticate`,`proxy-authorization`];function forwardedHeaders(request){let headers=new Headers(request.headers);for(let name of STRIPPED_AUTH_HEADERS)headers.delete(name);for(let name of STRIPPED_TRANSPORT_HEADERS)headers.delete(name);return headers}function resolveAuthenticatedEnv(){let env=readInterfereEnv();return{apiUrl:env.apiUrl,publicKey:env.publicKey,release:env.release}}function hasPublicKeyCredential(request,envPublicKey){return envPublicKey?!0:new URL(request.url).searchParams.has(`pk`)}function notConfiguredResponse(){return log.warn(`Not configured`,[`INTERFERE_PUBLIC_KEY is not set and the request has no ?pk= query param. The proxy route will return 503.`]),Response.json({code:`INTERFERE_NOT_CONFIGURED`,message:`INTERFERE_PUBLIC_KEY is required.`},{status:503})}function extractSubPath(request){let prefix=resolveRoutePrefix(),url=new URL(request.url),idx=url.pathname.indexOf(prefix);if(idx===-1)return`/`;let remainder=url.pathname.slice(idx+prefix.length);return remainder===``?`/`:remainder.startsWith(`/`)?remainder:`/${remainder}`}function formatProxyError(error){if(!(error instanceof Error))return{message:String(error),lines:[String(error)]};let lines=[`${error.name}: ${error.message}`];if(`cause`in error&&error.cause){let cause=error.cause instanceof Error?error.cause.message:String(error.cause);lines.push(`cause: ${cause}`)}return`code`in error&&typeof error.code==`string`&&lines.push(`code: ${error.code}`),{message:`${error.name}: ${error.message}`,lines}}function buildUpstreamUrl(apiUrl,subPath,request,publicKey){let incoming=new URL(request.url),upstream=new URL(`${apiUrl}${subPath}`);for(let[key,value]of incoming.searchParams)upstream.searchParams.append(key,value);return publicKey&&!upstream.searchParams.has(`pk`)&&upstream.searchParams.set(`pk`,publicKey),upstream.toString()}async function forwardToCollector(request,env,subPath){let url=buildUpstreamUrl(env.apiUrl,subPath,request,env.publicKey),body=request.method!==`GET`&&request.method!==`HEAD`?new Uint8Array(await request.arrayBuffer()):void 0,headers=forwardedHeaders(request);headers.has(`content-type`)||headers.set(`content-type`,`application/json`);let country=extractCountryHeader(request);country!==null&&headers.set(`x-country-code`,country);let upstream=await fetch(url,{...omitUndefined({body}),method:request.method,headers,signal:AbortSignal.timeout(1e4)});if(!upstream.ok){let text=await upstream.text().catch(()=>``);return upstream.status>=500?(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status})):upstream.status>=400?(log.warn(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_WARNING`,message:text},{status:upstream.status})):(log.error(`Upstream ${upstream.status} for ${request.method} ${subPath}`,[text]),Response.json({code:`INTERFERE_UPSTREAM_ERROR`,message:text},{status:upstream.status}))}return new Response(upstream.body,{status:upstream.status,headers:{"content-type":upstream.headers.get(`content-type`)??`application/json`}})}export{extractSubPath,formatProxyError,forwardToCollector,hasPublicKeyCredential,notConfiguredResponse,resolveAuthenticatedEnv};
|
package/dist/route-handler.d.mts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
//#region src/route-handler.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Customers consume this module via `export * from "@interfere/next/route-handler"`
|
|
4
|
-
* so any HTTP method we add later (PATCH, DELETE, …) is picked up
|
|
5
|
-
* without a customer file change. Do NOT add non-method exports here —
|
|
6
|
-
* `route-handler.lint.test.ts` will fail the build if you do.
|
|
7
|
-
*/
|
|
8
|
-
declare function GET(request: Request): Promise<Response>;
|
|
9
|
-
declare function POST(request: Request): Promise<Response>;
|
|
10
|
-
declare function OPTIONS(): Response;
|
|
11
|
-
//#endregion
|
|
12
|
-
export { GET, OPTIONS, POST };
|
package/dist/route-handler.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{isEnabledOnServer}from"./internal/env.mjs";import{handleGet}from"./internal/route/handle-get.mjs";import{handlePost}from"./internal/route/handle-post.mjs";const DISABLED=()=>new Response(null,{status:204});async function GET(request){return isEnabledOnServer()?await handleGet(request):DISABLED()}function POST(request){return isEnabledOnServer()?handlePost(request):Promise.resolve(DISABLED())}const CORS_HEADERS={"Access-Control-Allow-Origin":`*`,"Access-Control-Allow-Methods":`GET, POST, OPTIONS`,"Access-Control-Allow-Headers":`Content-Type`,"Access-Control-Max-Age":`86400`};function OPTIONS(){return new Response(null,{status:200,headers:CORS_HEADERS})}export{GET,OPTIONS,POST};
|