receivedSpans

tcmts.cli.web.receivedSpans · #abwxrlk7 · term · runs anywhere

The spans a page sent, as /__tcm/traces takes them: at most 200, each checked field by field (ids as W3C's, a kind a page records, times near now, a definition the codebase has, a few plain attributes); a span that doesn't fit is left out, a field that doesn't is dropped. Undefined if it isn't a batch of spans at all.

import type { ReceivedSpan } from "#codebase/tcmts/cli/web";

export function receivedSpans(
  body: unknown,
  known: (hash: string) => boolean,
  now = Date.now(),
): ReceivedSpan[] | undefined {
  const list = (body as { spans?: unknown } | null)?.spans;
  if (!Array.isArray(list) || list.length > 200) return undefined;
  const id = (v: unknown, n: number) =>
    typeof v === "string" && v.length === n && /^[0-9a-f]+$/.test(v) && !/^0+$/.test(v)
      ? v
      : undefined;
  const hash = (v: unknown) =>
    typeof v === "string" && /^[a-z2-7]{52}$/.test(v) && known(v) ? v : undefined;
  const out: ReceivedSpan[] = [];
  for (const raw of list) {
    if (!raw || typeof raw !== "object") continue;
    const s = raw as Record<string, unknown>;
    const trace = id(s.trace, 32);
    const span = id(s.id, 16);
    const kind =
      s.kind === "client" || s.kind === "view" || s.kind === "navigate" || s.kind === "error"
        ? s.kind
        : undefined;
    const name = typeof s.name === "string" && s.name && s.name.length <= 200 ? s.name : undefined;
    const start =
      typeof s.start === "number" && Math.abs(s.start - now) < 86_400_000 ? s.start : undefined;
    const dur = typeof s.dur === "number" && s.dur >= 0 && s.dur <= 3_600_000 ? s.dur : undefined;
    if (!trace || !span || !kind || !name || start === undefined || dur === undefined) continue;
    const attrs: ReceivedSpan["attrs"] = {};
    if (s.attrs && typeof s.attrs === "object")
      for (const [k, v] of Object.entries(s.attrs).slice(0, 20)) {
        if (!/^[\w.]{1,64}$/.test(k)) continue;
        if (k === "tcm.from") {
          const from = hash(v);
          if (from) attrs[k] = from;
        } else if (typeof v === "string") attrs[k] = v.slice(0, 500);
        else if ((typeof v === "number" && Number.isFinite(v)) || typeof v === "boolean")
          attrs[k] = v;
      }
    const parent = id(s.parent, 16);
    const h = hash(s.hash);
    const link =
      typeof s.link === "string" && /^00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/.test(s.link)
        ? s.link
        : undefined;
    out.push({
      trace,
      id: span,
      ...(parent ? { parent } : {}),
      kind,
      name,
      ...(h ? { hash: h } : {}),
      start,
      dur,
      ...(typeof s.error === "string" ? { error: s.error.slice(0, 4000) } : {}),
      attrs,
      ...(link ? { link } : {}),
    });
  }
  return out;
}

uses

used by