@procedures/client 0.52.3 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +11 -0
  3. package/dist/adapters/http/server/cors.d.ts.map +1 -1
  4. package/dist/adapters/http/server/cors.js +2 -0
  5. package/dist/adapters/http/server/cors.js.map +1 -1
  6. package/dist/adapters/port/transport.d.ts +27 -0
  7. package/dist/adapters/port/transport.d.ts.map +1 -1
  8. package/dist/adapters/port/transport.js +30 -0
  9. package/dist/adapters/port/transport.js.map +1 -1
  10. package/dist/browser.d.ts +2 -2
  11. package/dist/browser.d.ts.map +1 -1
  12. package/dist/browser.js +1 -1
  13. package/dist/browser.js.map +1 -1
  14. package/dist/client/batch-executor.d.ts +3 -0
  15. package/dist/client/batch-executor.d.ts.map +1 -1
  16. package/dist/client/batch-executor.js +3 -0
  17. package/dist/client/batch-executor.js.map +1 -1
  18. package/dist/client/client.d.ts +2 -0
  19. package/dist/client/client.d.ts.map +1 -1
  20. package/dist/client/client.js +33 -5
  21. package/dist/client/client.js.map +1 -1
  22. package/dist/client/middleware/cache.d.ts.map +1 -1
  23. package/dist/client/middleware/cache.js +2 -0
  24. package/dist/client/middleware/cache.js.map +1 -1
  25. package/dist/client/middleware/rate-limit.d.ts.map +1 -1
  26. package/dist/client/middleware/rate-limit.js +3 -2
  27. package/dist/client/middleware/rate-limit.js.map +1 -1
  28. package/dist/procedures/interceptors.d.ts +10 -1
  29. package/dist/procedures/interceptors.d.ts.map +1 -1
  30. package/dist/procedures/interceptors.js +31 -8
  31. package/dist/procedures/interceptors.js.map +1 -1
  32. package/dist/server/host-policy.d.ts +8 -0
  33. package/dist/server/host-policy.d.ts.map +1 -1
  34. package/dist/server/host-policy.js +3 -0
  35. package/dist/server/host-policy.js.map +1 -1
  36. package/dist/server/limits.d.ts +1 -0
  37. package/dist/server/limits.d.ts.map +1 -1
  38. package/dist/server/limits.js +13 -0
  39. package/dist/server/limits.js.map +1 -1
  40. package/dist/server/procedure-server.d.ts +21 -0
  41. package/dist/server/procedure-server.d.ts.map +1 -1
  42. package/dist/server/procedure-server.js +35 -3
  43. package/dist/server/procedure-server.js.map +1 -1
  44. package/dist/tracing/otel.d.ts +55 -0
  45. package/dist/tracing/otel.d.ts.map +1 -0
  46. package/dist/tracing/otel.js +114 -0
  47. package/dist/tracing/otel.js.map +1 -0
  48. package/dist/tracing/traceparent.d.ts +40 -12
  49. package/dist/tracing/traceparent.d.ts.map +1 -1
  50. package/dist/tracing/traceparent.js +49 -14
  51. package/dist/tracing/traceparent.js.map +1 -1
  52. package/package.json +17 -6
  53. package/src/adapters/http/server/cors.ts +2 -0
  54. package/src/adapters/port/transport.ts +44 -0
  55. package/src/browser.ts +2 -2
  56. package/src/client/batch-executor.ts +3 -0
  57. package/src/client/client.ts +31 -5
  58. package/src/client/middleware/cache.ts +2 -0
  59. package/src/client/middleware/rate-limit.ts +4 -2
  60. package/src/procedures/interceptors.ts +40 -9
  61. package/src/server/host-policy.ts +11 -0
  62. package/src/server/limits.ts +13 -0
  63. package/src/server/procedure-server.ts +46 -4
  64. package/src/tracing/otel.ts +122 -0
  65. package/src/tracing/traceparent.ts +56 -15
  66. package/dist/.tsbuildinfo +0 -1
@@ -13,6 +13,8 @@ import type { Admission, AmbientCredentials, Credentials, Method, Principal, Pri
13
13
  import { methodName, methodToPath, pathToMethod } from "./method.js";
14
14
  import { isLocalCredentials } from "./request-auth.js";
15
15
  import { STREAM_METADATA_KEY } from "./stream-metadata.js";
16
+ import { context as otelContext } from "@opentelemetry/api";
17
+ import { callerContext } from "../tracing/otel.js";
16
18
  import type {
17
19
  AnyProcedure,
18
20
  ProcedurePath,
@@ -151,6 +153,20 @@ export interface ProcedureServerOptions extends ServerOptions {
151
153
  */
152
154
  token?: string | undefined;
153
155
 
156
+ /**
157
+ * Which callers can set the baggage of a call (roadmap item C10 of otel-ts). Each handler runs in
158
+ * the context of its caller: the remote span of the `traceparent` and `tracestate` of the
159
+ * metadata is its parent. The `baggage` of the metadata goes into the context only from a
160
+ * trusted caller. Without it, the handler runs with no baggage, so an end user cannot set the
161
+ * baggage of the services.
162
+ *
163
+ * A local request (`LOCAL_CREDENTIALS`) and a request with the token of the server are always
164
+ * trusted. This function can trust more requests, for example the requests of a principal of a
165
+ * service. It runs after the checks of the request, so it can read `request.principal`. An
166
+ * error of the function means "not trusted". The default trusts no other request.
167
+ */
168
+ trustBaggage?: ((request: ServerRequest<unknown>) => boolean) | undefined;
169
+
154
170
  /**
155
171
  * This function gives the principal of a request, or undefined to refuse it with
156
172
  * `UNAUTHORIZED`. It runs after the token check. The capability function can read
@@ -455,11 +471,17 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
455
471
  private readonly token: string | undefined;
456
472
  private readonly principalOf: PrincipalOf | undefined;
457
473
  private readonly authenticate: ProcedureServerOptions["authenticate"];
474
+ private readonly trustBaggage: ProcedureServerOptions["trustBaggage"];
475
+ /**
476
+ * The requests whose caller can set the baggage (C10): a local request, a request with the
477
+ * token of the server, or a request with the admission of such a request.
478
+ */
479
+ private readonly trustedCallers = new WeakSet<ServerRequest<unknown>>();
458
480
  /**
459
481
  * The admissions of this server (CLI-2b): the identity of each object of admitted credentials
460
482
  * that `admission()` gave. Only this server adds an entry, so a request cannot claim one.
461
483
  */
462
- private readonly admissions = new WeakMap<Credentials, { readonly principal: Principal | undefined; readonly narrowing: ServerRequest<unknown>["narrowing"] }>();
484
+ private readonly admissions = new WeakMap<Credentials, { readonly principal: Principal | undefined; readonly narrowing: ServerRequest<unknown>["narrowing"]; readonly trusted: boolean }>();
463
485
  /**
464
486
  * The admission of each request object. The late lookup of a lazy registry admits a request
465
487
  * before it loads packages, and the call of the same request uses that result again.
@@ -497,6 +519,7 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
497
519
  this.token = options.token;
498
520
  this.principalOf = options.principalOf;
499
521
  this.authenticate = options.authenticate;
522
+ this.trustBaggage = options.trustBaggage;
500
523
  const ambient = options.ambientCredentials ?? (this.principalOf ? "always" : this.authenticate ? "cookies" : undefined);
501
524
  this.ambientCredentials = ambient === false ? undefined : ambient;
502
525
  this.debugReport =
@@ -937,6 +960,7 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
937
960
  // again. Only this server makes such credentials, so a copy or a forgery has no entry.
938
961
  const admitted = request.credentials !== undefined ? this.admissions.get(request.credentials) : undefined;
939
962
  if (admitted) {
963
+ if (admitted.trusted) this.trustedCallers.add(request);
940
964
  if (admitted.principal) request.principal = admitted.principal;
941
965
  if (admitted.narrowing) {
942
966
  const order = this.capabilityOptions.clearanceOrder;
@@ -953,6 +977,8 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
953
977
  // A local face (this process, MCP over stdio, a message port) sets the local marker. The
954
978
  // marker key is a symbol, so a request from the network cannot carry it (CLI-1).
955
979
  const credentials = isLocalCredentials(request.credentials) ? undefined : request.credentials;
980
+ // A local caller is a part of this process: it can set the baggage (C10)
981
+ if (isLocalCredentials(request.credentials)) this.trustedCallers.add(request);
956
982
  let identity: RequestIdentity | undefined;
957
983
  if (this.principalOf && credentials !== undefined) {
958
984
  try {
@@ -966,6 +992,8 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
966
992
  const bearer = identity?.bearer ?? credentials.bearer;
967
993
  if (!tokenMatches(this.token, bearer)) return unauthorized("Unauthorized: this server requires its token");
968
994
  request.principal = { id: "token", via: "token" };
995
+ // A caller with the token of the server is a service of the operator: it can set the baggage (C10)
996
+ this.trustedCallers.add(request);
969
997
  }
970
998
  if (identity?.principal) request.principal = identity.principal;
971
999
  if (identity?.narrowing) {
@@ -1063,7 +1091,7 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
1063
1091
  return { ok: false, code: "UNAUTHORIZED", message: refusal.status.type === "error" ? refusal.status.message : "Unauthorized" };
1064
1092
  }
1065
1093
  const admitted: Credentials = Object.freeze({ ...credentials });
1066
- this.admissions.set(admitted, { principal: request.principal, narrowing: request.narrowing });
1094
+ this.admissions.set(admitted, { principal: request.principal, narrowing: request.narrowing, trusted: this.trustedCallers.has(request) });
1067
1095
  return { ok: true, credentials: admitted, ...(request.principal ? { principal: request.principal } : {}) };
1068
1096
  }
1069
1097
 
@@ -1144,6 +1172,17 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
1144
1172
  };
1145
1173
  }
1146
1174
 
1175
+ /** True when the caller of `request` can set the baggage of the call (C10). */
1176
+ private trustsCaller(request: ServerRequest<unknown>): boolean {
1177
+ if (this.trustedCallers.has(request)) return true;
1178
+ if (!this.trustBaggage) return false;
1179
+ try {
1180
+ return this.trustBaggage(request) === true;
1181
+ } catch {
1182
+ return false;
1183
+ }
1184
+ }
1185
+
1147
1186
  /** Run a procedure for a request with a signal. */
1148
1187
  private async run(procedure: AnyProcedure, request: ServerRequest<unknown>, signal: AbortSignal | undefined): Promise<ServerResponse<unknown>> {
1149
1188
  // The call uses the storages that it takes until its end: the end of a stream, for a stream
@@ -1160,7 +1199,10 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
1160
1199
  const message = `The server cannot narrow the capability of the request: ${error instanceof Error ? error.message : String(error)}`;
1161
1200
  return errorResponse(request.id, Object.assign(new Error(message), { code: "INTERNAL_ERROR" }), request);
1162
1201
  }
1163
- const output = await invokeProcedure(procedure, request.payload, {
1202
+ // The handler runs in the context of its caller (C10): its trace always, its baggage only
1203
+ // when the caller is trusted
1204
+ const caller = callerContext(request.metadata, this.trustsCaller(request));
1205
+ const output = await otelContext.with(caller, () => invokeProcedure(procedure, request.payload, {
1164
1206
  registry: this.procedureRegistry,
1165
1207
  metadata: request.metadata,
1166
1208
  signal,
@@ -1182,7 +1224,7 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
1182
1224
  ...(request.principal !== undefined ? { principal: request.principal } : {}),
1183
1225
  // The bus of the remote events is the bus of the procedures (EVENTS.md)
1184
1226
  ...(this.publisher ? { bus: this.publisher.bus } : {}),
1185
- });
1227
+ }));
1186
1228
  if (output.kind === "stream") {
1187
1229
  // The faces that collect a stream read this mark (roadmap R15)
1188
1230
  const unbounded = (procedure.metadata as { stream?: { unbounded?: boolean } } | undefined)?.stream?.unbounded === true;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The context of OpenTelemetry for the calls of a client and the invocations of a host (roadmap
3
+ * item C8 of otel-ts, decisions D5 and D6). This module uses `@opentelemetry/api` only. The API
4
+ * does nothing until a program registers an SDK, for example with `startNodeTelemetry()` of
5
+ * `@mark1russell7/otel-ts/node`.
6
+ *
7
+ * - With an SDK, each call gets a span of the API, and the registered propagator writes the
8
+ * context of the call into its metadata: `traceparent`, `tracestate` and `baggage`.
9
+ * - With no SDK, `startApiSpan` gives undefined, and the W3C code of `traceparent.ts` operates as
10
+ * before (D6).
11
+ */
12
+ import { context, createContextKey, isSpanContextValid, propagation, ROOT_CONTEXT, SpanStatusCode, trace } from "@opentelemetry/api";
13
+ import type { Context, Span, SpanKind, TextMapSetter } from "@opentelemetry/api";
14
+
15
+ /** The name of the tracer of the spans of this package */
16
+ export const TRACER_NAME = "@procedures/client";
17
+
18
+ /**
19
+ * The key of the mark `internal call`. It is the same key as the key of `withInternalCall` of
20
+ * `@mark1russell7/otel-ts/context`, because `createContextKey` gives `Symbol.for(description)`.
21
+ * The propagator of otel-ts sends the baggage only on a call with this mark. A test pins the
22
+ * description against otel-ts.
23
+ */
24
+ export const INTERNAL_CALL_KEY: symbol = createContextKey("@mark1russell7/otel-ts/context internal call");
25
+
26
+ /** This function gives `from` with the mark `internal call`. */
27
+ export function asInternalCall(from: Context): Context {
28
+ return from.setValue(INTERNAL_CALL_KEY, true);
29
+ }
30
+
31
+ /**
32
+ * This function starts a span of the API, or gives undefined when no SDK gives the span ids of
33
+ * its own. With no SDK, the API gives a span with no ids, or with the ids of its parent.
34
+ */
35
+ export function startApiSpan(name: string, kind: SpanKind, parent: Context): Span | undefined {
36
+ const parentSpan = trace.getSpanContext(parent);
37
+ const span = trace.getTracer(TRACER_NAME).startSpan(name, { kind }, parent);
38
+ const own = span.spanContext();
39
+ if (isSpanContextValid(own) && own.spanId !== parentSpan?.spanId) return span;
40
+ span.end();
41
+ return undefined;
42
+ }
43
+
44
+ /** This function ends `span`, with the status of `error` when the call failed. */
45
+ export function endApiSpan(span: Span, error?: unknown): void {
46
+ if (error !== undefined) {
47
+ span.recordException(error instanceof Error ? error : String(error));
48
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) });
49
+ const code = (error as { code?: unknown } | null)?.code;
50
+ if (typeof code === "string") span.setAttribute("procedure.error.code", code);
51
+ }
52
+ span.end();
53
+ }
54
+
55
+ const metadataSetter: TextMapSetter<Record<string, unknown>> = {
56
+ set: (carrier, key, value) => {
57
+ carrier[key] = value;
58
+ },
59
+ };
60
+
61
+ /** This function writes the context `from` into `metadata`, with the registered propagator. */
62
+ export function injectContext(from: Context, metadata: Record<string, unknown>): void {
63
+ propagation.inject(from, metadata, metadataSetter);
64
+ }
65
+
66
+ /**
67
+ * This function gives the parent context of an invocation of a host: the active context, with the
68
+ * remote span of the `traceparent` and `tracestate` of `metadata` when they are valid. The baggage
69
+ * of the metadata is not read: the host reads it only from a trusted caller (`@procedures/peer`).
70
+ */
71
+ export function remoteParent(metadata: Record<string, unknown>, from: Context = context.active()): Context {
72
+ const traceparent = metadata["traceparent"];
73
+ if (typeof traceparent !== "string") return from;
74
+ const carrier: Record<string, string> = { traceparent };
75
+ if (typeof metadata["tracestate"] === "string") carrier["tracestate"] = metadata["tracestate"];
76
+ const extracted = propagation.extract(from, carrier);
77
+ return isSpanContextValid(trace.getSpanContext(extracted) ?? { traceId: "", spanId: "", traceFlags: 0 }) ? extracted : from;
78
+ }
79
+
80
+ /** The trace context that the caller of a call gave in its metadata: `traceparent` and `tracestate`, strings only */
81
+ export type CallerTrace = { readonly traceparent?: string; readonly tracestate?: string };
82
+
83
+ /** This function gives the `traceparent` and `tracestate` of `metadata`, before the client writes its own. */
84
+ export function callerTraceOf(metadata: Readonly<Record<string, unknown>>): CallerTrace {
85
+ const { traceparent, tracestate } = metadata;
86
+ return {
87
+ ...(typeof traceparent === "string" ? { traceparent } : {}),
88
+ ...(typeof tracestate === "string" ? { tracestate } : {}),
89
+ };
90
+ }
91
+
92
+ /**
93
+ * This function gives the parent context of a try of a call of a client (roadmap item C19 of
94
+ * otel-ts): the active context when it has a valid span, else the remote span of `caller`, the
95
+ * trace context that the caller gave in the metadata of the call. For example, `mark` gives the
96
+ * environment variables `TRACEPARENT` and `TRACESTATE` there. Without this, the span of the call
97
+ * started a new trace, and the propagator wrote that trace over the trace context of the caller.
98
+ */
99
+ export function callParent(caller: CallerTrace, from: Context = context.active()): Context {
100
+ const active = trace.getSpanContext(from);
101
+ return active !== undefined && isSpanContextValid(active) ? from : remoteParent(caller, from);
102
+ }
103
+
104
+ /**
105
+ * This function gives the context of the caller of an invocation of a host (roadmap item C10 of
106
+ * otel-ts): the handler runs in it, so its logs, metrics, spans and nested calls carry the ids of
107
+ * the caller.
108
+ *
109
+ * - The remote span of the `traceparent` and `tracestate` of `metadata` is always the parent.
110
+ * - The `baggage` of `metadata` replaces the baggage of `from` only when the caller is trusted.
111
+ * - When the caller is not trusted, the context has no baggage: an end user cannot set the
112
+ * baggage of the services. The baggage of `from` can come from the headers of the same request,
113
+ * for example from the HTTP instrumentation of an SDK, so the function removes it too.
114
+ */
115
+ export function callerContext(metadata: Record<string, unknown>, trusted: boolean, from: Context = context.active()): Context {
116
+ const parent = remoteParent(metadata, from);
117
+ if (!trusted) return propagation.getBaggage(parent) === undefined ? parent : propagation.deleteBaggage(parent);
118
+ const baggage = metadata["baggage"];
119
+ if (typeof baggage !== "string") return parent;
120
+ const extracted = propagation.getBaggage(propagation.extract(ROOT_CONTEXT, { baggage }));
121
+ return extracted === undefined ? parent : propagation.setBaggage(parent, extracted);
122
+ }
@@ -4,16 +4,32 @@
4
4
  * the metadata of a call (and HTTP as the `traceparent` header). The tracing interceptor of a
5
5
  * server continues that trace, and gives it to the nested calls, so one trace goes from the
6
6
  * client to the last handler. Before, the client and the server made two traces.
7
+ *
8
+ * The checks of the ids are `isValidTraceId` and `isValidSpanId` of `@opentelemetry/api`
9
+ * (decision D7 of otel-ts). They refuse an id of only zeros.
7
10
  */
11
+ import { isValidSpanId, isValidTraceId } from "@opentelemetry/api";
8
12
 
9
- /** The metadata key of the W3C trace context of a call. */
13
+ /**
14
+ * The metadata key of the W3C trace context of a call.
15
+ *
16
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
17
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
18
+ */
10
19
  export const TRACEPARENT_METADATA_KEY = "traceparent";
11
20
 
21
+ /** The form of version 00. The ids in it have only lowercase digits, as W3C Trace Context requires. */
12
22
  const TRACEPARENT = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
13
- const ZERO_TRACE = "0".repeat(32);
14
- const ZERO_SPAN = "0".repeat(16);
15
23
 
16
- /** A trace context: the trace, the span of the caller, and the sampled flag. */
24
+ /** An uppercase hex digit. The checks of the API accept it, but W3C Trace Context does not. */
25
+ const UPPERCASE_HEX = /[A-F]/;
26
+
27
+ /**
28
+ * A trace context: the trace, the span of the caller, and the sampled flag.
29
+ *
30
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
31
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
32
+ */
17
33
  export interface TraceContext {
18
34
  readonly traceId: string;
19
35
  readonly spanId: string;
@@ -51,34 +67,59 @@ function randomHex(bytes: number): string {
51
67
  return hex;
52
68
  }
53
69
 
54
- /** This function makes a new trace id: 32 random hex digits. */
70
+ /**
71
+ * This function makes a new trace id: 32 random hex digits.
72
+ *
73
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
74
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
75
+ */
55
76
  export function newTraceId(): string {
56
77
  const id = randomHex(16);
57
- return id === ZERO_TRACE ? newTraceId() : id;
78
+ return isValidTraceId(id) ? id : newTraceId();
58
79
  }
59
80
 
60
- /** This function makes a new span id: 16 random hex digits. */
81
+ /**
82
+ * This function makes a new span id: 16 random hex digits.
83
+ *
84
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
85
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
86
+ */
61
87
  export function newSpanId(): string {
62
88
  const id = randomHex(8);
63
- return id === ZERO_SPAN ? newSpanId() : id;
89
+ return isValidSpanId(id) ? id : newSpanId();
64
90
  }
65
91
 
66
- /** True when the ids have the W3C form. */
92
+ /**
93
+ * This function gives true when the ids have the W3C form.
94
+ *
95
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
96
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
97
+ */
67
98
  export function isW3cIds(traceId: string, spanId: string): boolean {
68
- return /^[0-9a-f]{32}$/.test(traceId) && /^[0-9a-f]{16}$/.test(spanId) && traceId !== ZERO_TRACE && spanId !== ZERO_SPAN;
99
+ return isValidTraceId(traceId) && isValidSpanId(spanId) && !UPPERCASE_HEX.test(traceId) && !UPPERCASE_HEX.test(spanId);
69
100
  }
70
101
 
71
- /** The `traceparent` of a trace and a span. */
102
+ /**
103
+ * This function gives the `traceparent` of a trace and a span.
104
+ *
105
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
106
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
107
+ */
72
108
  export function formatTraceparent(traceId: string, spanId: string, sampled = true): string {
73
109
  return `00-${traceId}-${spanId}-${sampled ? "01" : "00"}`;
74
110
  }
75
111
 
76
- /** The trace context of a `traceparent` value, or undefined when it is not valid. */
112
+ /**
113
+ * This function gives the trace context of a `traceparent` value, or undefined when it is not valid.
114
+ *
115
+ * @deprecated With an SDK of OpenTelemetry, the registered propagator writes and reads the trace
116
+ * context (roadmap C8 of otel-ts). A later MINOR removes this export, when each app registers an SDK (D6).
117
+ */
77
118
  export function parseTraceparent(value: unknown): TraceContext | undefined {
78
119
  if (typeof value !== "string") return undefined;
79
120
  const match = TRACEPARENT.exec(value.trim().toLowerCase());
80
121
  if (!match) return undefined;
81
- const [, traceId, spanId, flags] = match;
82
- if (traceId === ZERO_TRACE || spanId === ZERO_SPAN) return undefined;
83
- return { traceId: traceId!, spanId: spanId!, sampled: (parseInt(flags!, 16) & 1) === 1 };
122
+ const [, traceId = "", spanId = "", flags = ""] = match;
123
+ if (!isValidTraceId(traceId) || !isValidSpanId(spanId)) return undefined;
124
+ return { traceId, spanId, sampled: (parseInt(flags, 16) & 1) === 1 };
84
125
  }