@introspection-sdk/introspection-proxy 0.7.2 → 0.8.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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @introspection-sdk/introspection-proxy@0.7.2 build /home/runner/work/introspection-js-sdk/introspection-js-sdk/packages/introspection-proxy
2
+ > @introspection-sdk/introspection-proxy@0.8.1 build /home/runner/work/introspection-js-sdk/introspection-js-sdk/packages/introspection-proxy
3
3
  > tsc
4
4
 
package/dist/index.d.ts CHANGED
@@ -1,13 +1,17 @@
1
1
  /**
2
2
  * `@introspection-sdk/introspection-proxy`
3
3
  *
4
- * Lightweight, dependency-minimal (undici only) helpers for routing
5
- * `fetch`-based clients through the Introspection egress credential-injection
6
- * proxy or a standard forward proxy. Has no OpenTelemetry or provider-SDK
7
- * dependencies, so it can be used standalone (e.g. alongside the
8
- * `introspection-pi` instrumentation wrapper) without pulling in the full
9
- * `introspection-node` surface.
4
+ * Lightweight, dependency-minimal helpers for routing `fetch`-based clients
5
+ * through the Introspection egress credential-injection proxy or a standard
6
+ * forward proxy. Depends only on undici and the zero-dependency
7
+ * `@opentelemetry/api` (proxied requests emit `introspection-proxy-call`
8
+ * spans that no-op unless the host process registers a tracer provider — no
9
+ * OTel SDK or provider-SDK dependencies), so it can be used standalone (e.g.
10
+ * alongside the `introspection-pi` instrumentation wrapper) without pulling
11
+ * in the full `introspection-node` surface.
10
12
  */
11
13
  export { getProxyDispatcher, createProxyFetch, installProxyFetch, resolveForwardProxyUrl, } from "./proxy.js";
12
14
  export type { ProxyFetchOptions } from "./proxy.js";
15
+ export { PROXY_CALL_SPAN_NAME, PROXY_TRACER_NAME } from "./tracing.js";
16
+ export type { ProxyMode } from "./tracing.js";
13
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACvE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,12 +1,15 @@
1
1
  /**
2
2
  * `@introspection-sdk/introspection-proxy`
3
3
  *
4
- * Lightweight, dependency-minimal (undici only) helpers for routing
5
- * `fetch`-based clients through the Introspection egress credential-injection
6
- * proxy or a standard forward proxy. Has no OpenTelemetry or provider-SDK
7
- * dependencies, so it can be used standalone (e.g. alongside the
8
- * `introspection-pi` instrumentation wrapper) without pulling in the full
9
- * `introspection-node` surface.
4
+ * Lightweight, dependency-minimal helpers for routing `fetch`-based clients
5
+ * through the Introspection egress credential-injection proxy or a standard
6
+ * forward proxy. Depends only on undici and the zero-dependency
7
+ * `@opentelemetry/api` (proxied requests emit `introspection-proxy-call`
8
+ * spans that no-op unless the host process registers a tracer provider — no
9
+ * OTel SDK or provider-SDK dependencies), so it can be used standalone (e.g.
10
+ * alongside the `introspection-pi` instrumentation wrapper) without pulling
11
+ * in the full `introspection-node` surface.
10
12
  */
11
13
  export { getProxyDispatcher, createProxyFetch, installProxyFetch, resolveForwardProxyUrl, } from "./proxy.js";
14
+ export { PROXY_CALL_SPAN_NAME, PROXY_TRACER_NAME } from "./tracing.js";
12
15
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
package/dist/proxy.d.ts CHANGED
@@ -3,6 +3,13 @@ export interface ProxyFetchOptions {
3
3
  egressProxyUrl?: string;
4
4
  egressProxyHosts?: string;
5
5
  forwardProxyUrl?: string;
6
+ /**
7
+ * Emit an `introspection-proxy-call` OTel span per proxied request
8
+ * (default true). Spans no-op unless the host process registered a tracer
9
+ * provider. Direct requests (no proxy, or `NO_PROXY` hosts) never get a
10
+ * span regardless of this flag.
11
+ */
12
+ tracing?: boolean;
6
13
  }
7
14
  export declare function resolveForwardProxyUrl(): string | undefined;
8
15
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAiBA,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,QAAQ,CAAC;AAEhB,MAAM,WAAW,iBAAiB;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAiBD,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAQ3D;AA0BD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,iBAAsB,GAC9B,UAAU,GAAG,SAAS,CAUxB;AAaD,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,iBAAsB,GAC9B,OAAO,KAAK,CAwDd;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,iBAAsB,GAAG,MAAM,IAAI,CAU7E"}
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAiBA,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,QAAQ,CAAC;AAOhB,MAAM,WAAW,iBAAiB;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAiBD,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAQ3D;AA0BD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,iBAAsB,GAC9B,UAAU,GAAG,SAAS,CAUxB;AAaD,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,iBAAsB,GAC9B,OAAO,KAAK,CAmFd;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,iBAAsB,GAAG,MAAM,IAAI,CAU7E"}
package/dist/proxy.js CHANGED
@@ -16,6 +16,7 @@
16
16
  import { Socket } from "node:net";
17
17
  import { connect as tlsConnect } from "node:tls";
18
18
  import { Agent, EnvHttpProxyAgent, fetch as undiciFetch, } from "undici";
19
+ import { matchesNoProxy, parseNoProxyEntries, tracedProxyCall, } from "./tracing.js";
19
20
  function resolveEgressUrl(options) {
20
21
  return options.egressProxyUrl ?? process.env.INTROSPECTION_EGRESS_URL;
21
22
  }
@@ -94,8 +95,13 @@ export function createProxyFetch(options = {}) {
94
95
  : undefined;
95
96
  if (!egress && !forward)
96
97
  return base;
98
+ const tracing = options.tracing ?? true;
99
+ // Snapshot NO_PROXY once, like EnvHttpProxyAgent does at construction. Used
100
+ // only to decide span emission — undici owns the actual routing.
101
+ const noProxyEntries = parseNoProxyEntries(process.env);
97
102
  return (input, init) => {
98
- const hostname = new URL(toUrlString(input)).hostname.toLowerCase();
103
+ const url = new URL(toUrlString(input));
104
+ const hostname = url.hostname.toLowerCase();
99
105
  const useEgress = !!egress && egressHosts.size > 0 && egressHosts.has(hostname);
100
106
  const dispatcher = useEgress ? egress : forward;
101
107
  if (!dispatcher)
@@ -124,7 +130,18 @@ export function createProxyFetch(options = {}) {
124
130
  if (useEgress && egressIsPlainHttp) {
125
131
  target = downgradeToHttp(target);
126
132
  }
127
- return undiciFetch(target, opts);
133
+ const execute = () => undiciFetch(target, opts);
134
+ // A forward-dispatched request to a NO_PROXY host goes direct inside
135
+ // EnvHttpProxyAgent — no proxy hop, so no proxy span (in-cluster clients
136
+ // trace those calls themselves as `introspection-api-call`).
137
+ const viaProxy = useEgress || !matchesNoProxy(hostname, url.port, noProxyEntries);
138
+ if (!tracing || !viaProxy)
139
+ return execute();
140
+ const method = (init?.method ??
141
+ (typeof Request !== "undefined" && input instanceof Request
142
+ ? input.method
143
+ : "GET")).toUpperCase();
144
+ return tracedProxyCall(useEgress ? "egress" : "forward", method, url, execute);
128
145
  };
129
146
  }
130
147
  export function installProxyFetch(options = {}) {
package/dist/proxy.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export declare const PROXY_CALL_SPAN_NAME = "introspection-proxy-call";
2
+ export declare const PROXY_TRACER_NAME = "@introspection-sdk/introspection-proxy";
3
+ export type ProxyMode = "egress" | "forward";
4
+ /**
5
+ * Parse `NO_PROXY`/`no_proxy` into normalized entries. Mirrors undici's
6
+ * `EnvHttpProxyAgent` semantics closely enough to decide span emission —
7
+ * undici still owns the actual routing decision.
8
+ */
9
+ export declare function parseNoProxyEntries(env: NodeJS.ProcessEnv): string[];
10
+ /** Whether `hostname[:port]` matches a `NO_PROXY` entry (i.e. goes direct). */
11
+ export declare function matchesNoProxy(hostname: string, port: string, entries: string[]): boolean;
12
+ /** Run `execute` inside an `introspection-proxy-call` CLIENT span. */
13
+ export declare function tracedProxyCall(mode: ProxyMode, method: string, url: URL, execute: () => Promise<Response>): Promise<Response>;
14
+ //# sourceMappingURL=tracing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,oBAAoB,6BAA6B,CAAC;AAC/D,eAAO,MAAM,iBAAiB,2CAA2C,CAAC;AAE1E,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE7C;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,EAAE,CAMpE;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAeT;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,GAC/B,OAAO,CAAC,QAAQ,CAAC,CAoCnB"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * OpenTelemetry span instrumentation for proxied requests.
3
+ *
4
+ * Emits one `introspection-proxy-call` CLIENT span per request that is
5
+ * actually routed through a proxy (egress credential-injection or forward
6
+ * CONNECT). Requests that go direct — no proxy configured, or the host is
7
+ * excluded via `NO_PROXY` — deliberately get no span: in-cluster callers
8
+ * (`NO_PROXY` covers the DP API) already trace those calls at the client
9
+ * layer, so proxy spans and client-layer spans stay mutually exclusive.
10
+ *
11
+ * Depends only on `@opentelemetry/api`: without a registered tracer provider
12
+ * every span here is a no-op, so the package stays safe to load in bare Node
13
+ * processes (e.g. the sandbox `NODE_OPTIONS` preload).
14
+ *
15
+ * Attributes are intentionally lean — transport shape only (method, host,
16
+ * port, query-stripped URL, proxy mode, status). Tenant/task identity is
17
+ * inferred server-side from the request's JWT, or stamped globally by the
18
+ * host process's span processors, never duplicated here. Query strings are
19
+ * stripped because they can carry capability tokens (presigned URLs,
20
+ * share ids).
21
+ */
22
+ import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
23
+ export const PROXY_CALL_SPAN_NAME = "introspection-proxy-call";
24
+ export const PROXY_TRACER_NAME = "@introspection-sdk/introspection-proxy";
25
+ /**
26
+ * Parse `NO_PROXY`/`no_proxy` into normalized entries. Mirrors undici's
27
+ * `EnvHttpProxyAgent` semantics closely enough to decide span emission —
28
+ * undici still owns the actual routing decision.
29
+ */
30
+ export function parseNoProxyEntries(env) {
31
+ const raw = env.NO_PROXY ?? env.no_proxy ?? "";
32
+ return raw
33
+ .split(",")
34
+ .map((entry) => entry.trim().toLowerCase())
35
+ .filter(Boolean);
36
+ }
37
+ /** Whether `hostname[:port]` matches a `NO_PROXY` entry (i.e. goes direct). */
38
+ export function matchesNoProxy(hostname, port, entries) {
39
+ for (const raw of entries) {
40
+ if (raw === "*")
41
+ return true;
42
+ let entry = raw;
43
+ let entryPort = "";
44
+ const colon = entry.lastIndexOf(":");
45
+ if (colon !== -1 && !entry.slice(colon).includes("]")) {
46
+ entryPort = entry.slice(colon + 1);
47
+ entry = entry.slice(0, colon);
48
+ }
49
+ if (entryPort && entryPort !== port)
50
+ continue;
51
+ if (entry.startsWith("."))
52
+ entry = entry.slice(1);
53
+ if (hostname === entry || hostname.endsWith(`.${entry}`))
54
+ return true;
55
+ }
56
+ return false;
57
+ }
58
+ /** Run `execute` inside an `introspection-proxy-call` CLIENT span. */
59
+ export function tracedProxyCall(mode, method, url, execute) {
60
+ const tracer = trace.getTracer(PROXY_TRACER_NAME);
61
+ const port = Number(url.port) || (url.protocol === "https:" ? 443 : 80);
62
+ return tracer.startActiveSpan(PROXY_CALL_SPAN_NAME, {
63
+ kind: SpanKind.CLIENT,
64
+ attributes: {
65
+ "http.request.method": method,
66
+ "server.address": url.hostname,
67
+ "server.port": port,
68
+ "url.full": url.origin + url.pathname,
69
+ "url.path": url.pathname,
70
+ "introspection.proxy.mode": mode,
71
+ },
72
+ }, async (span) => {
73
+ try {
74
+ const response = await execute();
75
+ span.setAttribute("http.response.status_code", response.status);
76
+ if (response.status >= 400) {
77
+ span.setStatus({ code: SpanStatusCode.ERROR });
78
+ }
79
+ return response;
80
+ }
81
+ catch (err) {
82
+ span.recordException(err instanceof Error ? err : String(err));
83
+ span.setStatus({
84
+ code: SpanStatusCode.ERROR,
85
+ message: err instanceof Error ? err.message : String(err),
86
+ });
87
+ throw err;
88
+ }
89
+ finally {
90
+ span.end();
91
+ }
92
+ });
93
+ }
94
+ //# sourceMappingURL=tracing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing.js","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@introspection-sdk/introspection-proxy",
3
- "version": "0.7.2",
3
+ "version": "0.8.1",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Outbound HTTP proxy helpers for routing fetch-based clients through the Introspection egress proxy or a standard forward proxy",
6
6
  "engines": {
@@ -16,6 +16,7 @@
16
16
  }
17
17
  },
18
18
  "dependencies": {
19
+ "@opentelemetry/api": "^1.9.0",
19
20
  "undici": "^8.7.0"
20
21
  },
21
22
  "devDependencies": {
package/src/index.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * `@introspection-sdk/introspection-proxy`
3
3
  *
4
- * Lightweight, dependency-minimal (undici only) helpers for routing
5
- * `fetch`-based clients through the Introspection egress credential-injection
6
- * proxy or a standard forward proxy. Has no OpenTelemetry or provider-SDK
7
- * dependencies, so it can be used standalone (e.g. alongside the
8
- * `introspection-pi` instrumentation wrapper) without pulling in the full
9
- * `introspection-node` surface.
4
+ * Lightweight, dependency-minimal helpers for routing `fetch`-based clients
5
+ * through the Introspection egress credential-injection proxy or a standard
6
+ * forward proxy. Depends only on undici and the zero-dependency
7
+ * `@opentelemetry/api` (proxied requests emit `introspection-proxy-call`
8
+ * spans that no-op unless the host process registers a tracer provider — no
9
+ * OTel SDK or provider-SDK dependencies), so it can be used standalone (e.g.
10
+ * alongside the `introspection-pi` instrumentation wrapper) without pulling
11
+ * in the full `introspection-node` surface.
10
12
  */
11
13
  export {
12
14
  getProxyDispatcher,
@@ -15,3 +17,5 @@ export {
15
17
  resolveForwardProxyUrl,
16
18
  } from "./proxy.js";
17
19
  export type { ProxyFetchOptions } from "./proxy.js";
20
+ export { PROXY_CALL_SPAN_NAME, PROXY_TRACER_NAME } from "./tracing.js";
21
+ export type { ProxyMode } from "./tracing.js";
package/src/proxy.ts CHANGED
@@ -22,11 +22,23 @@ import {
22
22
  fetch as undiciFetch,
23
23
  type Dispatcher,
24
24
  } from "undici";
25
+ import {
26
+ matchesNoProxy,
27
+ parseNoProxyEntries,
28
+ tracedProxyCall,
29
+ } from "./tracing.js";
25
30
 
26
31
  export interface ProxyFetchOptions {
27
32
  egressProxyUrl?: string;
28
33
  egressProxyHosts?: string;
29
34
  forwardProxyUrl?: string;
35
+ /**
36
+ * Emit an `introspection-proxy-call` OTel span per proxied request
37
+ * (default true). Spans no-op unless the host process registered a tracer
38
+ * provider. Direct requests (no proxy, or `NO_PROXY` hosts) never get a
39
+ * span regardless of this flag.
40
+ */
41
+ tracing?: boolean;
30
42
  }
31
43
 
32
44
  function resolveEgressUrl(options: ProxyFetchOptions): string | undefined {
@@ -127,8 +139,14 @@ export function createProxyFetch(
127
139
 
128
140
  if (!egress && !forward) return base;
129
141
 
142
+ const tracing = options.tracing ?? true;
143
+ // Snapshot NO_PROXY once, like EnvHttpProxyAgent does at construction. Used
144
+ // only to decide span emission — undici owns the actual routing.
145
+ const noProxyEntries = parseNoProxyEntries(process.env);
146
+
130
147
  return (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
131
- const hostname = new URL(toUrlString(input)).hostname.toLowerCase();
148
+ const url = new URL(toUrlString(input));
149
+ const hostname = url.hostname.toLowerCase();
132
150
 
133
151
  const useEgress =
134
152
  !!egress && egressHosts.size > 0 && egressHosts.has(hostname);
@@ -161,10 +179,31 @@ export function createProxyFetch(
161
179
  target = downgradeToHttp(target);
162
180
  }
163
181
 
164
- return undiciFetch(
165
- target as unknown as Parameters<typeof undiciFetch>[0],
166
- opts as unknown as Parameters<typeof undiciFetch>[1],
167
- ) as unknown as Promise<Response>;
182
+ const execute = () =>
183
+ undiciFetch(
184
+ target as unknown as Parameters<typeof undiciFetch>[0],
185
+ opts as unknown as Parameters<typeof undiciFetch>[1],
186
+ ) as unknown as Promise<Response>;
187
+
188
+ // A forward-dispatched request to a NO_PROXY host goes direct inside
189
+ // EnvHttpProxyAgent — no proxy hop, so no proxy span (in-cluster clients
190
+ // trace those calls themselves as `introspection-api-call`).
191
+ const viaProxy =
192
+ useEgress || !matchesNoProxy(hostname, url.port, noProxyEntries);
193
+ if (!tracing || !viaProxy) return execute();
194
+
195
+ const method = (
196
+ init?.method ??
197
+ (typeof Request !== "undefined" && input instanceof Request
198
+ ? input.method
199
+ : "GET")
200
+ ).toUpperCase();
201
+ return tracedProxyCall(
202
+ useEgress ? "egress" : "forward",
203
+ method,
204
+ url,
205
+ execute,
206
+ );
168
207
  };
169
208
  }
170
209
 
package/src/tracing.ts ADDED
@@ -0,0 +1,106 @@
1
+ /**
2
+ * OpenTelemetry span instrumentation for proxied requests.
3
+ *
4
+ * Emits one `introspection-proxy-call` CLIENT span per request that is
5
+ * actually routed through a proxy (egress credential-injection or forward
6
+ * CONNECT). Requests that go direct — no proxy configured, or the host is
7
+ * excluded via `NO_PROXY` — deliberately get no span: in-cluster callers
8
+ * (`NO_PROXY` covers the DP API) already trace those calls at the client
9
+ * layer, so proxy spans and client-layer spans stay mutually exclusive.
10
+ *
11
+ * Depends only on `@opentelemetry/api`: without a registered tracer provider
12
+ * every span here is a no-op, so the package stays safe to load in bare Node
13
+ * processes (e.g. the sandbox `NODE_OPTIONS` preload).
14
+ *
15
+ * Attributes are intentionally lean — transport shape only (method, host,
16
+ * port, query-stripped URL, proxy mode, status). Tenant/task identity is
17
+ * inferred server-side from the request's JWT, or stamped globally by the
18
+ * host process's span processors, never duplicated here. Query strings are
19
+ * stripped because they can carry capability tokens (presigned URLs,
20
+ * share ids).
21
+ */
22
+ import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
23
+
24
+ export const PROXY_CALL_SPAN_NAME = "introspection-proxy-call";
25
+ export const PROXY_TRACER_NAME = "@introspection-sdk/introspection-proxy";
26
+
27
+ export type ProxyMode = "egress" | "forward";
28
+
29
+ /**
30
+ * Parse `NO_PROXY`/`no_proxy` into normalized entries. Mirrors undici's
31
+ * `EnvHttpProxyAgent` semantics closely enough to decide span emission —
32
+ * undici still owns the actual routing decision.
33
+ */
34
+ export function parseNoProxyEntries(env: NodeJS.ProcessEnv): string[] {
35
+ const raw = env.NO_PROXY ?? env.no_proxy ?? "";
36
+ return raw
37
+ .split(",")
38
+ .map((entry) => entry.trim().toLowerCase())
39
+ .filter(Boolean);
40
+ }
41
+
42
+ /** Whether `hostname[:port]` matches a `NO_PROXY` entry (i.e. goes direct). */
43
+ export function matchesNoProxy(
44
+ hostname: string,
45
+ port: string,
46
+ entries: string[],
47
+ ): boolean {
48
+ for (const raw of entries) {
49
+ if (raw === "*") return true;
50
+ let entry = raw;
51
+ let entryPort = "";
52
+ const colon = entry.lastIndexOf(":");
53
+ if (colon !== -1 && !entry.slice(colon).includes("]")) {
54
+ entryPort = entry.slice(colon + 1);
55
+ entry = entry.slice(0, colon);
56
+ }
57
+ if (entryPort && entryPort !== port) continue;
58
+ if (entry.startsWith(".")) entry = entry.slice(1);
59
+ if (hostname === entry || hostname.endsWith(`.${entry}`)) return true;
60
+ }
61
+ return false;
62
+ }
63
+
64
+ /** Run `execute` inside an `introspection-proxy-call` CLIENT span. */
65
+ export function tracedProxyCall(
66
+ mode: ProxyMode,
67
+ method: string,
68
+ url: URL,
69
+ execute: () => Promise<Response>,
70
+ ): Promise<Response> {
71
+ const tracer = trace.getTracer(PROXY_TRACER_NAME);
72
+ const port = Number(url.port) || (url.protocol === "https:" ? 443 : 80);
73
+ return tracer.startActiveSpan(
74
+ PROXY_CALL_SPAN_NAME,
75
+ {
76
+ kind: SpanKind.CLIENT,
77
+ attributes: {
78
+ "http.request.method": method,
79
+ "server.address": url.hostname,
80
+ "server.port": port,
81
+ "url.full": url.origin + url.pathname,
82
+ "url.path": url.pathname,
83
+ "introspection.proxy.mode": mode,
84
+ },
85
+ },
86
+ async (span) => {
87
+ try {
88
+ const response = await execute();
89
+ span.setAttribute("http.response.status_code", response.status);
90
+ if (response.status >= 400) {
91
+ span.setStatus({ code: SpanStatusCode.ERROR });
92
+ }
93
+ return response;
94
+ } catch (err) {
95
+ span.recordException(err instanceof Error ? err : String(err));
96
+ span.setStatus({
97
+ code: SpanStatusCode.ERROR,
98
+ message: err instanceof Error ? err.message : String(err),
99
+ });
100
+ throw err;
101
+ } finally {
102
+ span.end();
103
+ }
104
+ },
105
+ );
106
+ }