@glasstrace/sdk 0.1.0 → 0.2.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.
- package/dist/index.cjs +253 -99
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -5
- package/dist/index.d.ts +37 -5
- package/dist/index.js +252 -99
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/env-detection.ts","../src/session.ts","../src/fetch-classifier.ts","../src/anon-key.ts","../src/init-client.ts","../src/span-processor.ts","../src/enriching-exporter.ts","../src/discovery-endpoint.ts","../src/otel-config.ts","../src/register.ts","../src/source-map-uploader.ts","../src/config-wrapper.ts","../src/import-graph.ts"],"sourcesContent":["// @glasstrace/sdk\nexport { SdkError } from \"./errors.js\";\nexport {\n readEnvVars,\n resolveConfig,\n isProductionDisabled,\n isAnonymousMode,\n} from \"./env-detection.js\";\nexport type { ResolvedConfig } from \"./env-detection.js\";\nexport {\n deriveSessionId,\n getOrigin,\n getDateString,\n SessionManager,\n} from \"./session.js\";\nexport { classifyFetchTarget } from \"./fetch-classifier.js\";\nexport type { FetchTarget } from \"./fetch-classifier.js\";\nexport { getOrCreateAnonKey, readAnonKey } from \"./anon-key.js\";\n\n// Init client + config cache\nexport {\n loadCachedConfig,\n saveCachedConfig,\n sendInitRequest,\n performInit,\n getActiveConfig,\n} from \"./init-client.js\";\n\n// Custom span processor (deprecated — now a pass-through)\nexport { GlasstraceSpanProcessor } from \"./span-processor.js\";\n\n// Enriching exporter — performs all glasstrace.* enrichment at export time\nexport { GlasstraceExporter } from \"./enriching-exporter.js\";\nexport type { GlasstraceExporterOptions } from \"./enriching-exporter.js\";\n\n// Discovery endpoint\nexport { createDiscoveryHandler } from \"./discovery-endpoint.js\";\n\n// registerGlasstrace orchestrator\nexport {\n registerGlasstrace,\n getDiscoveryHandler,\n} from \"./register.js\";\n\n// Config wrapper + source map uploader\nexport { withGlasstraceConfig } from \"./config-wrapper.js\";\nexport {\n collectSourceMaps,\n computeBuildHash,\n uploadSourceMaps,\n} from \"./source-map-uploader.js\";\nexport type { SourceMapEntry } from \"./source-map-uploader.js\";\n\n// Import graph builder\nexport {\n discoverTestFiles,\n extractImports,\n buildImportGraph,\n} from \"./import-graph.js\";\n","import type { SdkDiagnosticCode } from \"@glasstrace/protocol\";\n\n/**\n * Internal SDK error class with a typed diagnostic code.\n * Caught at the boundary and converted to a log message + diagnostic entry.\n * Never thrown to the developer.\n */\nexport class SdkError extends Error {\n readonly code: SdkDiagnosticCode;\n\n constructor(code: SdkDiagnosticCode, message: string, cause?: Error) {\n super(message, { cause });\n this.name = \"SdkError\";\n this.code = code;\n }\n}\n","import type { GlasstraceEnvVars, GlasstraceOptions } from \"@glasstrace/protocol\";\n\n/**\n * Resolved configuration after merging explicit options with environment variables.\n */\nexport interface ResolvedConfig {\n apiKey: string | undefined;\n endpoint: string;\n forceEnable: boolean;\n verbose: boolean;\n environment: string | undefined;\n coverageMapEnabled: boolean;\n nodeEnv: string | undefined;\n vercelEnv: string | undefined;\n}\n\nconst DEFAULT_ENDPOINT = \"https://api.glasstrace.dev\";\n\n/**\n * Reads all recognized Glasstrace environment variables from process.env.\n * Returns undefined for any variable not set. Never throws.\n */\nexport function readEnvVars(): GlasstraceEnvVars {\n return {\n GLASSTRACE_API_KEY: process.env.GLASSTRACE_API_KEY,\n GLASSTRACE_FORCE_ENABLE: process.env.GLASSTRACE_FORCE_ENABLE,\n GLASSTRACE_ENV: process.env.GLASSTRACE_ENV,\n GLASSTRACE_COVERAGE_MAP: process.env.GLASSTRACE_COVERAGE_MAP,\n NODE_ENV: process.env.NODE_ENV,\n VERCEL_ENV: process.env.VERCEL_ENV,\n };\n}\n\n/**\n * Merges explicit GlasstraceOptions with environment variables.\n * Explicit options take precedence over environment variables.\n */\nexport function resolveConfig(options?: GlasstraceOptions): ResolvedConfig {\n const env = readEnvVars();\n\n return {\n apiKey: options?.apiKey ?? env.GLASSTRACE_API_KEY,\n endpoint: options?.endpoint ?? DEFAULT_ENDPOINT,\n forceEnable: options?.forceEnable ?? env.GLASSTRACE_FORCE_ENABLE === \"true\",\n verbose: options?.verbose ?? false,\n environment: env.GLASSTRACE_ENV,\n coverageMapEnabled: env.GLASSTRACE_COVERAGE_MAP === \"true\",\n nodeEnv: env.NODE_ENV,\n vercelEnv: env.VERCEL_ENV,\n };\n}\n\n/**\n * Returns true when the SDK should be inactive (production detected without force-enable).\n * Logic order:\n * 1. forceEnable === true → return false (override)\n * 2. NODE_ENV === 'production' → return true\n * 3. VERCEL_ENV === 'production' → return true\n * 4. Otherwise → return false\n */\nexport function isProductionDisabled(config: ResolvedConfig): boolean {\n if (config.forceEnable) {\n return false;\n }\n if (config.nodeEnv === \"production\") {\n return true;\n }\n if (config.vercelEnv === \"production\") {\n return true;\n }\n return false;\n}\n\n/**\n * Returns true when no API key is configured (anonymous mode).\n * Treats undefined, empty string, whitespace-only, and gt_anon_* keys as anonymous.\n */\nexport function isAnonymousMode(config: ResolvedConfig): boolean {\n if (config.apiKey === undefined) {\n return true;\n }\n if (config.apiKey.trim() === \"\") {\n return true;\n }\n if (config.apiKey.startsWith(\"gt_anon_\")) {\n return true;\n }\n return false;\n}\n","import { createHash } from \"node:crypto\";\nimport { SessionIdSchema } from \"@glasstrace/protocol\";\nimport type { SessionId } from \"@glasstrace/protocol\";\n\nconst FOUR_HOURS_MS = 4 * 60 * 60 * 1000;\n\n/**\n * Derives a deterministic session ID from the given inputs using SHA-256.\n * The hash is truncated to 16 hex characters and parsed through SessionIdSchema.\n *\n * @param apiKey - The project's API key (or anonymous placeholder).\n * @param origin - The origin string identifying the deployment environment.\n * @param date - UTC date as YYYY-MM-DD.\n * @param windowIndex - Zero-based index of the 4-hour activity window within the day.\n * @returns A 16-character hex SessionId.\n */\nexport function deriveSessionId(\n apiKey: string,\n origin: string,\n date: string,\n windowIndex: number,\n): SessionId {\n const input = JSON.stringify([apiKey, origin, date, windowIndex]);\n const hash = createHash(\"sha256\").update(input).digest(\"hex\").slice(0, 16);\n return SessionIdSchema.parse(hash);\n}\n\n/**\n * Returns the origin string for the current process.\n * If GLASSTRACE_ENV is set, returns that value.\n * Otherwise returns `localhost:{PORT}` (PORT defaults to 3000).\n *\n * @returns The origin string used as a session derivation input.\n */\nexport function getOrigin(): string {\n if (process.env.GLASSTRACE_ENV) {\n return process.env.GLASSTRACE_ENV;\n }\n const port = process.env.PORT ?? \"3000\";\n return `localhost:${port}`;\n}\n\n/**\n * Returns the current UTC date as a YYYY-MM-DD string.\n *\n * @returns The UTC date formatted as \"YYYY-MM-DD\".\n */\nexport function getDateString(): string {\n const now = new Date();\n const year = now.getUTCFullYear();\n const month = String(now.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(now.getUTCDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Tracks the current session state with 4-hour window tracking.\n * Instantiated once by the orchestrator.\n */\nexport class SessionManager {\n private windowIndex: number = 0;\n private lastActivityTimestamp: number = 0;\n private lastDate: string = \"\";\n private lastApiKey: string = \"\";\n private currentSessionId: SessionId | null = null;\n\n /**\n * Returns the current session ID, deriving a new one if:\n * - More than 4 hours have elapsed since last activity\n * - The UTC date has changed (resets window index to 0)\n * - The API key has changed (e.g., deferred anonymous key swap)\n * - This is the first call\n *\n * @param apiKey - The project's API key used in session derivation.\n * @returns The current or newly derived SessionId.\n */\n getSessionId(apiKey: string): SessionId {\n const now = Date.now();\n const currentDate = getDateString();\n const origin = getOrigin();\n\n const elapsed = now - this.lastActivityTimestamp;\n const dateChanged = currentDate !== this.lastDate;\n const apiKeyChanged = apiKey !== this.lastApiKey;\n\n if (dateChanged) {\n // New UTC day: reset window index\n this.windowIndex = 0;\n this.lastDate = currentDate;\n this.lastApiKey = apiKey;\n this.currentSessionId = deriveSessionId(apiKey, origin, currentDate, this.windowIndex);\n } else if (apiKeyChanged) {\n // API key changed (e.g., anonymous key resolved): re-derive with same window\n this.lastApiKey = apiKey;\n this.currentSessionId = deriveSessionId(apiKey, origin, currentDate, this.windowIndex);\n } else if (this.currentSessionId === null || elapsed > FOUR_HOURS_MS) {\n // First call or gap exceeding 4 hours: increment window\n if (this.currentSessionId !== null) {\n this.windowIndex++;\n }\n this.lastApiKey = apiKey;\n this.currentSessionId = deriveSessionId(apiKey, origin, currentDate, this.windowIndex);\n this.lastDate = currentDate;\n }\n\n this.lastActivityTimestamp = now;\n return this.currentSessionId;\n }\n}\n","/**\n * The set of recognized fetch target categories.\n */\nexport type FetchTarget = \"supabase\" | \"stripe\" | \"internal\" | \"unknown\";\n\n/**\n * Classifies an outbound fetch target URL into a known category.\n * Classification is case-insensitive and based on the URL hostname.\n * Uses dot-boundary matching to avoid false positives (e.g. evilstripe.com).\n *\n * Returns one of: 'supabase', 'stripe', 'internal', or 'unknown'.\n */\nexport function classifyFetchTarget(url: string): FetchTarget {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return \"unknown\";\n }\n\n const hostname = parsed.hostname.toLowerCase();\n\n if (\n hostname === \"supabase.co\" ||\n hostname.endsWith(\".supabase.co\") ||\n hostname === \"supabase.in\" ||\n hostname.endsWith(\".supabase.in\")\n ) {\n return \"supabase\";\n }\n\n if (hostname === \"stripe.com\" || hostname.endsWith(\".stripe.com\")) {\n return \"stripe\";\n }\n\n const port = process.env.PORT ?? \"3000\";\n const internalOrigin = `localhost:${port}`;\n const parsedPort = parsed.port || (parsed.protocol === \"https:\" ? \"443\" : \"80\");\n const urlOrigin = `${hostname}:${parsedPort}`;\n\n if (urlOrigin === internalOrigin) {\n return \"internal\";\n }\n\n return \"unknown\";\n}\n","import { readFile, writeFile, mkdir, chmod } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { AnonApiKeySchema, createAnonApiKey } from \"@glasstrace/protocol\";\nimport type { AnonApiKey } from \"@glasstrace/protocol\";\n\nconst GLASSTRACE_DIR = \".glasstrace\";\nconst ANON_KEY_FILE = \"anon_key\";\n\n/**\n * In-memory cache for ephemeral keys when filesystem persistence fails.\n * Keyed by resolved project root to support multiple roots in tests.\n */\nconst ephemeralKeyCache = new Map<string, AnonApiKey>();\n\n/**\n * Reads an existing anonymous key from the filesystem.\n * Returns the key if valid, or null if:\n * - The file does not exist\n * - The file content is invalid\n * - An I/O error occurs\n */\nexport async function readAnonKey(projectRoot?: string): Promise<AnonApiKey | null> {\n const root = projectRoot ?? process.cwd();\n const keyPath = join(root, GLASSTRACE_DIR, ANON_KEY_FILE);\n\n try {\n const content = await readFile(keyPath, \"utf-8\");\n const result = AnonApiKeySchema.safeParse(content);\n if (result.success) {\n return result.data;\n }\n } catch {\n // Fall through to check ephemeral cache\n }\n\n // Check in-memory cache (used when filesystem persistence failed)\n const cached = ephemeralKeyCache.get(root);\n if (cached !== undefined) {\n return cached;\n }\n\n return null;\n}\n\n/**\n * Gets an existing anonymous key from the filesystem, or creates a new one.\n *\n * - If file exists and contains a valid key, returns it\n * - If file does not exist or content is invalid, generates a new key via createAnonApiKey()\n * - Writes the new key to `.glasstrace/anon_key`, creating the directory if needed\n * - On file write failure: logs a warning, caches an ephemeral in-memory key so\n * repeated calls in the same process return the same key\n */\nexport async function getOrCreateAnonKey(projectRoot?: string): Promise<AnonApiKey> {\n const root = projectRoot ?? process.cwd();\n const dirPath = join(root, GLASSTRACE_DIR);\n const keyPath = join(dirPath, ANON_KEY_FILE);\n\n // Try reading existing key from filesystem\n const existingKey = await readAnonKey(root);\n if (existingKey !== null) {\n return existingKey;\n }\n\n // Check in-memory cache (used when filesystem is unavailable)\n const cached = ephemeralKeyCache.get(root);\n if (cached !== undefined) {\n return cached;\n }\n\n // Generate a new key\n const newKey = createAnonApiKey();\n\n // Persist to filesystem\n try {\n await mkdir(dirPath, { recursive: true, mode: 0o700 });\n await writeFile(keyPath, newKey, \"utf-8\");\n await chmod(keyPath, 0o600);\n } catch (err) {\n // Cache in memory so repeated calls get the same ephemeral key\n ephemeralKeyCache.set(root, newKey);\n console.warn(\n `[glasstrace] Failed to persist anonymous key to ${keyPath}: ${err instanceof Error ? err.message : String(err)}. Using ephemeral key.`,\n );\n }\n\n return newKey;\n}\n","import { readFileSync } from \"node:fs\";\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport {\n SdkInitResponseSchema,\n SdkCachedConfigSchema,\n DEFAULT_CAPTURE_CONFIG,\n} from \"@glasstrace/protocol\";\nimport type {\n SdkInitResponse,\n CaptureConfig,\n AnonApiKey,\n ImportGraphPayload,\n SdkHealthReport,\n SdkDiagnosticCode,\n} from \"@glasstrace/protocol\";\nimport type { ResolvedConfig } from \"./env-detection.js\";\n\nconst GLASSTRACE_DIR = \".glasstrace\";\nconst CONFIG_FILE = \"config\";\nconst TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;\nconst INIT_TIMEOUT_MS = 10_000;\n\n/** In-memory config from the latest successful init response. */\nlet currentConfig: SdkInitResponse | null = null;\n\n/** Whether the next init call should be skipped (rate-limit backoff). */\nlet rateLimitBackoff = false;\n\n/**\n * Reads and validates a cached config file from `.glasstrace/config`.\n * Returns the parsed `SdkInitResponse` or `null` on any failure.\n */\nexport function loadCachedConfig(projectRoot?: string): SdkInitResponse | null {\n const root = projectRoot ?? process.cwd();\n const configPath = join(root, GLASSTRACE_DIR, CONFIG_FILE);\n\n try {\n // Use synchronous read for startup performance (this is called during init)\n const content = readFileSync(configPath, \"utf-8\");\n const parsed = JSON.parse(content);\n const cached = SdkCachedConfigSchema.parse(parsed);\n\n // Warn if cache is stale\n const age = Date.now() - cached.cachedAt;\n if (age > TWENTY_FOUR_HOURS_MS) {\n console.warn(\n `[glasstrace] Cached config is ${Math.round(age / 3600000)}h old. Will refresh on next init.`,\n );\n }\n\n // Parse the response through the schema\n const result = SdkInitResponseSchema.safeParse(cached.response);\n if (result.success) {\n return result.data;\n }\n\n console.warn(\"[glasstrace] Cached config failed validation. Using defaults.\");\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Persists the init response to `.glasstrace/config`.\n * On failure, logs a warning and continues.\n */\nexport async function saveCachedConfig(\n response: SdkInitResponse,\n projectRoot?: string,\n): Promise<void> {\n const root = projectRoot ?? process.cwd();\n const dirPath = join(root, GLASSTRACE_DIR);\n const configPath = join(dirPath, CONFIG_FILE);\n\n try {\n await mkdir(dirPath, { recursive: true });\n const cached = {\n response,\n cachedAt: Date.now(),\n };\n await writeFile(configPath, JSON.stringify(cached), \"utf-8\");\n } catch (err) {\n console.warn(\n `[glasstrace] Failed to cache config to ${configPath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\n/**\n * Sends a POST request to `/v1/sdk/init`.\n * Validates the response against `SdkInitResponseSchema`.\n */\nexport async function sendInitRequest(\n config: ResolvedConfig,\n anonKey: AnonApiKey | null,\n sdkVersion: string,\n importGraph?: ImportGraphPayload,\n healthReport?: SdkHealthReport,\n diagnostics?: Array<{ code: SdkDiagnosticCode; message: string; timestamp: number }>,\n signal?: AbortSignal,\n): Promise<SdkInitResponse> {\n // Determine the API key for auth\n const effectiveKey = config.apiKey ?? anonKey;\n if (!effectiveKey) {\n throw new Error(\"No API key available for init request\");\n }\n\n // Build the request payload\n const payload: Record<string, unknown> = {\n apiKey: effectiveKey,\n sdkVersion,\n };\n\n // Straggler linking: if dev key is set AND anonKey is provided\n if (config.apiKey && anonKey) {\n payload.anonKey = anonKey;\n }\n\n if (config.environment) {\n payload.environment = config.environment;\n }\n if (importGraph) {\n payload.importGraph = importGraph;\n }\n if (healthReport) {\n payload.healthReport = healthReport;\n }\n if (diagnostics) {\n payload.diagnostics = diagnostics;\n }\n\n const url = `${config.endpoint}/v1/sdk/init`;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${effectiveKey}`,\n },\n body: JSON.stringify(payload),\n signal,\n });\n\n if (!response.ok) {\n const error = new Error(`Init request failed with status ${response.status}`);\n (error as unknown as Record<string, unknown>).status = response.status;\n throw error;\n }\n\n const body = await response.json();\n return SdkInitResponseSchema.parse(body);\n}\n\n/**\n * Orchestrates the full init flow: send request, update config, cache result.\n * This function MUST NOT throw.\n */\nexport async function performInit(\n config: ResolvedConfig,\n anonKey: AnonApiKey | null,\n sdkVersion: string,\n): Promise<void> {\n // Skip if in rate-limit backoff\n if (rateLimitBackoff) {\n rateLimitBackoff = false; // Reset for next call\n return;\n }\n\n try {\n const effectiveKey = config.apiKey ?? anonKey;\n if (!effectiveKey) {\n console.warn(\"[glasstrace] No API key available for init request.\");\n return;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), INIT_TIMEOUT_MS);\n\n try {\n // Delegate to sendInitRequest to avoid duplicating fetch logic\n const result = await sendInitRequest(\n config,\n anonKey,\n sdkVersion,\n undefined,\n undefined,\n undefined,\n controller.signal,\n );\n\n clearTimeout(timeoutId);\n\n // Update in-memory config\n currentConfig = result;\n\n // Persist to disk\n await saveCachedConfig(result);\n } catch (err) {\n clearTimeout(timeoutId);\n\n if (err instanceof DOMException && err.name === \"AbortError\") {\n console.warn(\"[glasstrace] ingestion_unreachable: Init request timed out.\");\n return;\n }\n\n // Check for HTTP status errors attached by sendInitRequest\n const status = (err as Record<string, unknown>).status;\n if (status === 401) {\n console.warn(\n \"[glasstrace] ingestion_auth_failed: Check your GLASSTRACE_API_KEY.\",\n );\n return;\n }\n\n if (status === 429) {\n console.warn(\"[glasstrace] ingestion_rate_limited: Backing off.\");\n rateLimitBackoff = true;\n return;\n }\n\n if (typeof status === \"number\" && status >= 400) {\n console.warn(\n `[glasstrace] Init request failed with status ${status}. Using cached config.`,\n );\n return;\n }\n\n // Schema validation failure from sendInitRequest.parse\n if (err instanceof Error && err.name === \"ZodError\") {\n console.warn(\n \"[glasstrace] Init response failed validation (schema version mismatch?). Using cached config.\",\n );\n return;\n }\n\n // Network error or other fetch failure\n console.warn(\n `[glasstrace] ingestion_unreachable: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n } catch (err) {\n // Outermost catch -- should never reach here, but safety net\n console.warn(\n `[glasstrace] Unexpected init error: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\n/**\n * Returns the current capture config from the three-tier fallback chain:\n * 1. In-memory config from latest init response\n * 2. File cache\n * 3. DEFAULT_CAPTURE_CONFIG\n */\nexport function getActiveConfig(): CaptureConfig {\n // Tier 1: in-memory\n if (currentConfig) {\n return currentConfig.config;\n }\n\n // Tier 2: file cache\n const cached = loadCachedConfig();\n if (cached) {\n return cached.config;\n }\n\n // Tier 3: defaults\n return { ...DEFAULT_CAPTURE_CONFIG };\n}\n\n/**\n * Resets the in-memory config store. For testing only.\n */\nexport function _resetConfigForTesting(): void {\n currentConfig = null;\n rateLimitBackoff = false;\n}\n\n/**\n * Sets the in-memory config directly. Used by performInit and the orchestrator.\n */\nexport function _setCurrentConfig(config: SdkInitResponse): void {\n currentConfig = config;\n}\n\n/**\n * Returns whether rate-limit backoff is active. For testing only.\n */\nexport function _isRateLimitBackoff(): boolean {\n return rateLimitBackoff;\n}\n","import type { Context } from \"@opentelemetry/api\";\nimport type { SpanProcessor, ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\nimport type { Span } from \"@opentelemetry/sdk-trace-base\";\nimport type { CaptureConfig } from \"@glasstrace/protocol\";\nimport type { SessionManager } from \"./session.js\";\n\n/**\n * Lightweight SpanProcessor that delegates to a wrapped processor.\n *\n * All glasstrace.* attribute enrichment has been moved to {@link GlasstraceExporter}\n * (see enriching-exporter.ts), which enriches spans at export time. This resolves:\n * - Cold-start spans are buffered in the exporter, not dropped\n * - Vercel's CompositeSpanProcessor skips onEnding(); the exporter doesn't need it\n * - Session ID is computed at export time with the resolved API key\n *\n * This class is retained for backward compatibility. New code should use\n * GlasstraceExporter directly.\n *\n * @deprecated Use GlasstraceExporter for span enrichment. This processor is now a pass-through.\n */\nexport class GlasstraceSpanProcessor implements SpanProcessor {\n private readonly wrappedProcessor: SpanProcessor;\n\n /* eslint-disable @typescript-eslint/no-unused-vars -- backward compat signature */\n constructor(\n wrappedProcessor: SpanProcessor,\n _sessionManager?: SessionManager,\n _apiKey?: string | (() => string),\n _getConfig?: () => CaptureConfig,\n _environment?: string,\n ) {\n /* eslint-enable @typescript-eslint/no-unused-vars */\n this.wrappedProcessor = wrappedProcessor;\n }\n\n onStart(span: Span, parentContext: Context): void {\n this.wrappedProcessor.onStart(span, parentContext);\n }\n\n onEnd(readableSpan: ReadableSpan): void {\n this.wrappedProcessor.onEnd(readableSpan);\n }\n\n async shutdown(): Promise<void> {\n return this.wrappedProcessor.shutdown();\n }\n\n async forceFlush(): Promise<void> {\n return this.wrappedProcessor.forceFlush();\n }\n}\n","import { SpanKind } from \"@opentelemetry/api\";\nimport type { ReadableSpan, SpanExporter } from \"@opentelemetry/sdk-trace-base\";\nimport type { ExportResult } from \"@opentelemetry/core\";\nimport { GLASSTRACE_ATTRIBUTE_NAMES } from \"@glasstrace/protocol\";\nimport type { CaptureConfig } from \"@glasstrace/protocol\";\nimport type { SessionManager } from \"./session.js\";\nimport { classifyFetchTarget } from \"./fetch-classifier.js\";\n\nconst ATTR = GLASSTRACE_ATTRIBUTE_NAMES;\n\n/**\n * Sentinel value indicating the API key has not yet been resolved.\n */\nexport const API_KEY_PENDING = \"pending\" as const;\n\n/**\n * Maximum number of spans to buffer while waiting for key resolution.\n * Prevents unbounded memory growth if the key never resolves.\n */\nconst MAX_PENDING_SPANS = 1024;\n\n/**\n * Options for constructing a {@link GlasstraceExporter}.\n */\nexport interface GlasstraceExporterOptions {\n getApiKey: () => string;\n sessionManager: SessionManager;\n getConfig: () => CaptureConfig;\n environment: string | undefined;\n endpointUrl: string;\n createDelegate: ((url: string, headers: Record<string, string>) => SpanExporter) | null;\n}\n\ninterface PendingBatch {\n spans: ReadableSpan[];\n resultCallback: (result: ExportResult) => void;\n}\n\n/**\n * A SpanExporter that enriches spans with glasstrace.* attributes at export\n * time, then delegates to a real OTLP exporter.\n *\n * This design resolves three issues:\n * - Spans emitted before the API key resolves are buffered (not dropped)\n * and flushed once the key is available.\n * - Enrichment happens in the exporter (not onEnding), so it works\n * on Vercel where CompositeSpanProcessor does not forward onEnding().\n * - Session ID is computed at export time using the resolved API key,\n * not the \"pending\" placeholder.\n */\nexport class GlasstraceExporter implements SpanExporter {\n private readonly getApiKey: () => string;\n private readonly sessionManager: SessionManager;\n private readonly getConfig: () => CaptureConfig;\n private readonly environment: string | undefined;\n private readonly endpointUrl: string;\n private readonly createDelegateFn: ((url: string, headers: Record<string, string>) => SpanExporter) | null;\n\n private delegate: SpanExporter | null = null;\n private pendingBatches: PendingBatch[] = [];\n private pendingSpanCount = 0;\n private overflowLogged = false;\n\n constructor(options: GlasstraceExporterOptions) {\n this.getApiKey = options.getApiKey;\n this.sessionManager = options.sessionManager;\n this.getConfig = options.getConfig;\n this.environment = options.environment;\n this.endpointUrl = options.endpointUrl;\n this.createDelegateFn = options.createDelegate;\n }\n\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {\n const enrichedSpans = spans.map((span) => this.enrichSpan(span));\n\n const currentKey = this.getApiKey();\n if (currentKey === API_KEY_PENDING) {\n // Buffer spans until key resolves\n this.bufferSpans(enrichedSpans, resultCallback);\n return;\n }\n\n // Key is available — ensure delegate exists and export\n const exporter = this.ensureDelegate();\n if (exporter) {\n exporter.export(enrichedSpans, resultCallback);\n } else {\n // No delegate factory — report success so the pipeline does not stall\n resultCallback({ code: 0 });\n }\n }\n\n /**\n * Called when the API key transitions from \"pending\" to a resolved value.\n * Creates the delegate exporter and flushes all buffered spans.\n */\n notifyKeyResolved(): void {\n this.flushPending();\n }\n\n async shutdown(): Promise<void> {\n const currentKey = this.getApiKey();\n if (currentKey !== API_KEY_PENDING && this.pendingBatches.length > 0) {\n this.flushPending();\n } else if (this.pendingBatches.length > 0) {\n console.warn(\n `[glasstrace] Shutdown with ${this.pendingSpanCount} buffered spans — API key never resolved, spans lost.`,\n );\n // Complete pending callbacks so pipeline doesn't hang\n for (const batch of this.pendingBatches) {\n batch.resultCallback({ code: 0 });\n }\n this.pendingBatches = [];\n this.pendingSpanCount = 0;\n }\n\n if (this.delegate) {\n return this.delegate.shutdown();\n }\n }\n\n forceFlush(): Promise<void> {\n if (this.delegate?.forceFlush) {\n return this.delegate.forceFlush();\n }\n return Promise.resolve();\n }\n\n /**\n * Enriches a ReadableSpan with all glasstrace.* attributes.\n * Returns a new ReadableSpan wrapper; the original span is not mutated.\n * Each attribute derivation is wrapped in its own try-catch for partial\n * enrichment resilience.\n */\n private enrichSpan(span: ReadableSpan): ReadableSpan {\n const attrs = span.attributes ?? {};\n const name = span.name ?? \"\";\n const extra: Record<string, string | number> = {};\n\n // glasstrace.trace.type\n try {\n extra[ATTR.TRACE_TYPE] = \"server\";\n } catch {\n // omitted\n }\n\n // glasstrace.session.id — computed at export time with the current API key\n try {\n const sessionId = this.sessionManager.getSessionId(this.getApiKey());\n extra[ATTR.SESSION_ID] = sessionId;\n } catch {\n // Session ID omitted for this span\n }\n\n // glasstrace.environment\n try {\n const env = this.environment ?? process.env.GLASSTRACE_ENV;\n if (env) {\n extra[ATTR.ENVIRONMENT] = env;\n }\n } catch {\n // omitted\n }\n\n // glasstrace.correlation.id\n try {\n const existingCid = attrs[\"glasstrace.correlation.id\"];\n if (typeof existingCid === \"string\") {\n extra[ATTR.CORRELATION_ID] = existingCid;\n }\n } catch {\n // omitted\n }\n\n // glasstrace.route\n try {\n const route =\n (attrs[\"http.route\"] as string | undefined) ?? name;\n if (route) {\n extra[ATTR.ROUTE] = route;\n }\n } catch {\n // omitted\n }\n\n // glasstrace.http.method\n try {\n const method =\n (attrs[\"http.method\"] as string | undefined) ??\n (attrs[\"http.request.method\"] as string | undefined);\n if (method) {\n extra[ATTR.HTTP_METHOD] = method;\n }\n } catch {\n // omitted\n }\n\n // glasstrace.http.status_code\n try {\n const statusCode =\n (attrs[\"http.status_code\"] as number | undefined) ??\n (attrs[\"http.response.status_code\"] as number | undefined);\n if (statusCode !== undefined) {\n extra[ATTR.HTTP_STATUS_CODE] = statusCode;\n }\n } catch {\n // omitted\n }\n\n // glasstrace.http.duration_ms\n try {\n if (span.startTime && span.endTime) {\n const [startSec, startNano] = span.startTime;\n const [endSec, endNano] = span.endTime;\n const durationMs =\n (endSec - startSec) * 1000 + (endNano - startNano) / 1_000_000;\n if (durationMs >= 0) {\n extra[ATTR.HTTP_DURATION_MS] = durationMs;\n }\n }\n } catch {\n // omitted\n }\n\n // glasstrace.error.message\n try {\n const errorMessage = attrs[\"exception.message\"] as string | undefined;\n if (errorMessage) {\n extra[ATTR.ERROR_MESSAGE] = errorMessage;\n }\n } catch {\n // omitted\n }\n\n // glasstrace.error.code + glasstrace.error.category\n try {\n const errorType = attrs[\"exception.type\"] as string | undefined;\n if (errorType) {\n extra[ATTR.ERROR_CODE] = errorType;\n extra[ATTR.ERROR_CATEGORY] = deriveErrorCategory(errorType);\n }\n } catch {\n // omitted\n }\n\n // glasstrace.error.field\n try {\n const errorField = attrs[\"error.field\"] as string | undefined;\n if (errorField) {\n extra[ATTR.ERROR_FIELD] = errorField;\n }\n } catch {\n // omitted\n }\n\n // glasstrace.orm.*\n try {\n // Support both OTel >=1.9 (instrumentationScope) and <1.9 (instrumentationLibrary)\n const spanAny = span as unknown as Record<string, { name?: string } | undefined>;\n const instrumentationName =\n (spanAny.instrumentationScope?.name ?? spanAny.instrumentationLibrary?.name) ?? \"\";\n const ormProvider = deriveOrmProvider(instrumentationName);\n if (ormProvider) {\n extra[ATTR.ORM_PROVIDER] = ormProvider;\n\n const model =\n (attrs[\"db.sql.table\"] as string | undefined) ??\n (attrs[\"db.prisma.model\"] as string | undefined);\n if (model) {\n extra[ATTR.ORM_MODEL] = model;\n }\n\n const operation = attrs[\"db.operation\"] as string | undefined;\n if (operation) {\n extra[ATTR.ORM_OPERATION] = operation;\n }\n }\n } catch {\n // omitted\n }\n\n // glasstrace.fetch.target\n try {\n const url =\n (attrs[\"http.url\"] as string | undefined) ??\n (attrs[\"url.full\"] as string | undefined);\n if (url && span.kind === SpanKind.CLIENT) {\n extra[ATTR.FETCH_TARGET] = classifyFetchTarget(url);\n }\n } catch {\n // omitted\n }\n\n return createEnrichedSpan(span, extra);\n }\n\n /**\n * Lazily creates the delegate OTLP exporter once the API key is resolved.\n */\n private ensureDelegate(): SpanExporter | null {\n if (!this.createDelegateFn) return null;\n if (this.delegate) return this.delegate;\n\n const currentKey = this.getApiKey();\n if (currentKey === API_KEY_PENDING) return null;\n\n this.delegate = this.createDelegateFn(this.endpointUrl, {\n Authorization: `Bearer ${currentKey}`,\n });\n return this.delegate;\n }\n\n /**\n * Buffers enriched spans while the API key is pending.\n * Evicts oldest batches if the buffer exceeds MAX_PENDING_SPANS.\n */\n private bufferSpans(\n spans: ReadableSpan[],\n resultCallback: (result: ExportResult) => void,\n ): void {\n this.pendingBatches.push({ spans, resultCallback });\n this.pendingSpanCount += spans.length;\n\n // Evict oldest batches if over limit\n while (this.pendingSpanCount > MAX_PENDING_SPANS && this.pendingBatches.length > 1) {\n const evicted = this.pendingBatches.shift()!;\n this.pendingSpanCount -= evicted.spans.length;\n // Complete callback so pipeline doesn't hang\n evicted.resultCallback({ code: 0 });\n\n if (!this.overflowLogged) {\n this.overflowLogged = true;\n console.warn(\n \"[glasstrace] Pending span buffer overflow — oldest spans evicted. \" +\n \"This usually means the API key is taking too long to resolve.\",\n );\n }\n }\n }\n\n /**\n * Flushes all buffered spans through the delegate exporter.\n * Called when the API key resolves.\n */\n private flushPending(): void {\n if (this.pendingBatches.length === 0) return;\n\n const exporter = this.ensureDelegate();\n if (!exporter) {\n // No delegate factory — complete callbacks and discard\n for (const batch of this.pendingBatches) {\n batch.resultCallback({ code: 0 });\n }\n this.pendingBatches = [];\n this.pendingSpanCount = 0;\n return;\n }\n\n const batches = this.pendingBatches;\n this.pendingBatches = [];\n this.pendingSpanCount = 0;\n\n for (const batch of batches) {\n exporter.export(batch.spans, batch.resultCallback);\n }\n }\n}\n\n/**\n * Creates a ReadableSpan wrapper that inherits all properties from the\n * original span but overrides `attributes` to include additional entries.\n * The original span is not mutated.\n */\nfunction createEnrichedSpan(\n span: ReadableSpan,\n extra: Record<string, string | number>,\n): ReadableSpan {\n const enrichedAttributes = { ...span.attributes, ...extra };\n return Object.create(span, {\n attributes: {\n value: enrichedAttributes,\n enumerable: true,\n },\n }) as ReadableSpan;\n}\n\n/**\n * Derives ORM provider from the instrumentation library name.\n */\nexport function deriveOrmProvider(instrumentationName: string): string | null {\n const lower = instrumentationName.toLowerCase();\n if (lower.includes(\"prisma\")) {\n return \"prisma\";\n }\n if (lower.includes(\"drizzle\")) {\n return \"drizzle\";\n }\n return null;\n}\n\n/**\n * Derives error category from error type string.\n */\nexport function deriveErrorCategory(errorType: string): string {\n const lower = errorType.toLowerCase();\n if (lower.includes(\"validation\") || lower.includes(\"zod\")) {\n return \"validation\";\n }\n if (\n lower.includes(\"network\") ||\n lower.includes(\"econnrefused\") ||\n lower.includes(\"fetch\") ||\n lower.includes(\"timeout\")\n ) {\n return \"network\";\n }\n if (lower.includes(\"auth\") || lower.includes(\"unauthorized\") || lower.includes(\"forbidden\")) {\n return \"auth\";\n }\n if (lower.includes(\"notfound\") || lower.includes(\"not_found\")) {\n return \"not-found\";\n }\n return \"internal\";\n}\n","import type { AnonApiKey, SessionId } from \"@glasstrace/protocol\";\n\n/**\n * Checks whether the given Origin header is allowed for CORS access.\n *\n * Allowed origins:\n * - `chrome-extension://*` — any Chrome extension\n * - `moz-extension://*` — any Firefox extension\n * - `safari-web-extension://*` — any Safari extension\n * - Absent origin (same-origin / non-browser request)\n *\n * Replaced wildcard `*` to prevent arbitrary websites from\n * reading the anonymous API key from localhost.\n */\nfunction isAllowedOrigin(origin: string | null): boolean {\n if (origin === null) return true;\n if (origin.startsWith(\"chrome-extension://\")) return true;\n if (origin.startsWith(\"moz-extension://\")) return true;\n if (origin.startsWith(\"safari-web-extension://\")) return true;\n return false;\n}\n\n/**\n * Builds CORS headers for a given request origin.\n * Returns headers with `Access-Control-Allow-Origin` set to the origin\n * if allowed, otherwise omits that header entirely.\n */\nfunction buildCorsHeaders(origin: string | null): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Vary: \"Origin\",\n };\n\n if (origin && isAllowedOrigin(origin)) {\n headers[\"Access-Control-Allow-Origin\"] = origin;\n }\n\n return headers;\n}\n\n/**\n * Creates a request handler for the `/__glasstrace/config` discovery endpoint.\n *\n * The returned handler checks if the request URL path is `/__glasstrace/config`.\n * If not, returns `null` (pass-through). If it matches, returns a `DiscoveryResponse`\n * with the anonymous key and current session ID.\n *\n * The triple guard (anonymous + dev + active) is enforced by the caller,\n * not by this module. If the handler is registered, it serves.\n */\nexport function createDiscoveryHandler(\n getAnonKey: () => Promise<AnonApiKey | null>,\n getSessionId: () => SessionId,\n): (request: Request) => Promise<Response | null> {\n return async (request: Request): Promise<Response | null> => {\n // Check path match\n let url: URL;\n try {\n url = new URL(request.url);\n } catch {\n return null;\n }\n\n if (url.pathname !== \"/__glasstrace/config\") {\n return null;\n }\n\n // Restrict CORS to known extension origins instead of wildcard\n const origin = request.headers.get(\"Origin\");\n const corsHeaders = buildCorsHeaders(origin);\n\n // Handle CORS preflight\n if (request.method === \"OPTIONS\") {\n return new Response(null, {\n status: 204,\n headers: {\n ...corsHeaders,\n \"Access-Control-Allow-Methods\": \"GET, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n },\n });\n }\n\n // Only allow GET requests\n if (request.method !== \"GET\") {\n return new Response(\n JSON.stringify({ error: \"method_not_allowed\" }),\n {\n status: 405,\n headers: corsHeaders,\n },\n );\n }\n\n try {\n // Get the anonymous key\n const anonKey = await getAnonKey();\n\n if (anonKey === null) {\n return new Response(\n JSON.stringify({ error: \"not_ready\" }),\n {\n status: 503,\n headers: corsHeaders,\n },\n );\n }\n\n // Get the current session ID\n const sessionId = getSessionId();\n\n return new Response(\n JSON.stringify({ key: anonKey, sessionId }),\n {\n status: 200,\n headers: corsHeaders,\n },\n );\n } catch {\n return new Response(\n JSON.stringify({ error: \"internal_error\" }),\n {\n status: 500,\n headers: corsHeaders,\n },\n );\n }\n };\n}\n\n// Exported for testing\nexport { isAllowedOrigin, buildCorsHeaders };\n","import type { ResolvedConfig } from \"./env-detection.js\";\nimport type { SessionManager } from \"./session.js\";\nimport type { SpanExporter } from \"@opentelemetry/sdk-trace-base\";\nimport { GlasstraceExporter, API_KEY_PENDING } from \"./enriching-exporter.js\";\nimport { getActiveConfig } from \"./init-client.js\";\n\n/** Module-level resolved API key, updated when the anon key resolves. */\nlet _resolvedApiKey: string = API_KEY_PENDING;\n\n/** Module-level reference to the active exporter for key-resolution notification. */\nlet _activeExporter: GlasstraceExporter | null = null;\n\n/**\n * Sets the resolved API key for OTel export authentication.\n * Called once the anonymous key or dev key is available.\n * @param key - The resolved API key (anonymous or developer).\n */\nexport function setResolvedApiKey(key: string): void {\n _resolvedApiKey = key;\n}\n\n/**\n * Returns the current resolved API key.\n * Returns the {@link API_KEY_PENDING} sentinel if the key has not yet resolved.\n */\nexport function getResolvedApiKey(): string {\n return _resolvedApiKey;\n}\n\n/**\n * Notifies the active exporter that the API key has transitioned from\n * \"pending\" to a resolved value. This triggers flushing of any buffered spans.\n */\nexport function notifyApiKeyResolved(): void {\n _activeExporter?.notifyKeyResolved();\n}\n\n/**\n * Resets OTel configuration state to initial values. For testing only.\n */\nexport function resetOtelConfigForTesting(): void {\n _resolvedApiKey = API_KEY_PENDING;\n _activeExporter = null;\n}\n\n/**\n * Dynamically imports an optional peer dependency at runtime.\n *\n * Uses `Function()` constructor to build the `import()` expression so that\n * bundlers (webpack, esbuild, turbopack) do not attempt to resolve the module\n * at compile time. This is intentional -- the SDK's peer dependencies are\n * optional, and static analysis would cause missing-module build errors for\n * users who have not installed them.\n *\n * @param moduleId - The npm package name to import (e.g. \"@vercel/otel\").\n * @returns The module namespace object, or `null` if the module is not installed.\n */\nasync function tryImport(moduleId: string): Promise<Record<string, unknown> | null> {\n try {\n return await (Function(\"id\", \"return import(id)\")(moduleId) as Promise<Record<string, unknown>>);\n } catch {\n return null;\n }\n}\n\n/**\n * Configures OpenTelemetry with the GlasstraceExporter.\n * The exporter handles all span enrichment (glasstrace.* attributes) at\n * export time, solving buffering, no-onEnding,\n * and session-ID-uses-resolved-key concerns.\n *\n * Attempts to use `@vercel/otel` first, falls back to bare OTel SDK.\n *\n * @param config - The resolved SDK configuration (endpoint, environment, etc.).\n * @param sessionManager - Provides session IDs for span enrichment.\n */\nexport async function configureOtel(\n config: ResolvedConfig,\n sessionManager: SessionManager,\n): Promise<void> {\n // Build OTLP exporter configuration\n const exporterUrl = `${config.endpoint}/v1/traces`;\n\n // Build the exporter factory from the optional OTLP peer dependency\n let createOtlpExporter: ((url: string, headers: Record<string, string>) => SpanExporter) | null = null;\n const otlpModule = await tryImport(\"@opentelemetry/exporter-trace-otlp-http\");\n if (otlpModule && typeof otlpModule.OTLPTraceExporter === \"function\") {\n const OTLPTraceExporter = otlpModule.OTLPTraceExporter as new (opts: {\n url: string;\n headers: Record<string, string>;\n }) => SpanExporter;\n createOtlpExporter = (url: string, headers: Record<string, string>) =>\n new OTLPTraceExporter({ url, headers });\n }\n\n // Create the GlasstraceExporter that enriches + buffers + delegates\n const glasstraceExporter = new GlasstraceExporter({\n getApiKey: getResolvedApiKey,\n sessionManager,\n getConfig: () => getActiveConfig(),\n environment: config.environment,\n endpointUrl: exporterUrl,\n createDelegate: createOtlpExporter,\n });\n _activeExporter = glasstraceExporter;\n\n // Try @vercel/otel first\n const vercelOtel = await tryImport(\"@vercel/otel\");\n if (vercelOtel && typeof vercelOtel.registerOTel === \"function\") {\n if (!createOtlpExporter) {\n console.warn(\n \"[glasstrace] @opentelemetry/exporter-trace-otlp-http not found for @vercel/otel path. Trace export disabled.\",\n );\n }\n\n const otelConfig: Record<string, unknown> = {\n serviceName: \"glasstrace-sdk\",\n traceExporter: glasstraceExporter,\n };\n\n // Try @prisma/instrumentation\n const prismaModule = await tryImport(\"@prisma/instrumentation\");\n if (prismaModule) {\n const PrismaInstrumentation = prismaModule.PrismaInstrumentation as\n (new () => unknown) | undefined;\n if (PrismaInstrumentation) {\n otelConfig.instrumentations = [new PrismaInstrumentation()];\n }\n }\n\n (vercelOtel.registerOTel as (opts: Record<string, unknown>) => void)(otelConfig);\n return;\n }\n\n // Fallback: bare OTel SDK with BasicTracerProvider\n try {\n const otelSdk = await import(\"@opentelemetry/sdk-trace-base\");\n const otelApi = await import(\"@opentelemetry/api\");\n\n if (!createOtlpExporter) {\n // No OTLP exporter available -- rebuild GlasstraceExporter with a\n // ConsoleSpanExporter delegate so spans still get glasstrace.* enrichment.\n const consoleExporter = new otelSdk.ConsoleSpanExporter();\n const consoleGlasstraceExporter = new GlasstraceExporter({\n getApiKey: getResolvedApiKey,\n sessionManager,\n getConfig: () => getActiveConfig(),\n environment: config.environment,\n endpointUrl: exporterUrl,\n createDelegate: () => consoleExporter,\n });\n _activeExporter = consoleGlasstraceExporter;\n\n console.warn(\n \"[glasstrace] @opentelemetry/exporter-trace-otlp-http not found. Using ConsoleSpanExporter.\",\n );\n\n const processor = new otelSdk.SimpleSpanProcessor(consoleGlasstraceExporter);\n const provider = new otelSdk.BasicTracerProvider({\n spanProcessors: [processor],\n });\n otelApi.trace.setGlobalTracerProvider(provider);\n return;\n }\n\n const processor = new otelSdk.SimpleSpanProcessor(glasstraceExporter);\n const provider = new otelSdk.BasicTracerProvider({\n spanProcessors: [processor],\n });\n\n // Warn if another OTel provider is already registered to avoid\n // silently overwriting existing tracing (e.g., Datadog, New Relic).\n const existingProvider = otelApi.trace.getTracerProvider();\n if (existingProvider && existingProvider.constructor.name !== \"ProxyTracerProvider\") {\n console.warn(\n \"[glasstrace] An existing OpenTelemetry TracerProvider was detected and will be replaced. \" +\n \"If you use another tracing tool, configure Glasstrace as an additional exporter instead.\",\n );\n }\n\n otelApi.trace.setGlobalTracerProvider(provider);\n } catch {\n console.warn(\n \"[glasstrace] Neither @vercel/otel nor @opentelemetry/sdk-trace-base available. Tracing disabled.\",\n );\n }\n}\n","declare const __SDK_VERSION__: string;\n\nimport type { GlasstraceOptions, AnonApiKey } from \"@glasstrace/protocol\";\nimport { resolveConfig, isProductionDisabled, isAnonymousMode } from \"./env-detection.js\";\nimport type { ResolvedConfig } from \"./env-detection.js\";\nimport { SessionManager } from \"./session.js\";\nimport { getOrCreateAnonKey, readAnonKey } from \"./anon-key.js\";\nimport { loadCachedConfig, performInit, _setCurrentConfig } from \"./init-client.js\";\nimport { createDiscoveryHandler } from \"./discovery-endpoint.js\";\nimport { configureOtel, setResolvedApiKey, getResolvedApiKey, notifyApiKeyResolved, resetOtelConfigForTesting } from \"./otel-config.js\";\n\n/** Module-level state tracking for the registered discovery handler. */\nlet discoveryHandler: ((request: Request) => Promise<Response | null>) | null = null;\n\n/** Module-level flag to prevent double registration. */\nlet isRegistered = false;\n\n/** Generation counter to invalidate stale background promises after reset. */\nlet registrationGeneration = 0;\n\n/**\n * The primary SDK entry point called by developers in their `instrumentation.ts`.\n * Orchestrates OTel setup, span processor, init client, anon key, and discovery endpoint.\n *\n * This function is synchronous and MUST NOT throw. The developer's server is never blocked.\n * Background work (key resolution, init call) happens via fire-and-forget promises.\n *\n * @param options - Optional SDK configuration. Environment variables are used as fallbacks.\n *\n * @example\n * ```ts\n * // instrumentation.ts\n * import { registerGlasstrace } from \"@glasstrace/sdk\";\n * registerGlasstrace(); // uses env vars\n * ```\n */\nexport function registerGlasstrace(options?: GlasstraceOptions): void {\n try {\n // Prevent double registration\n if (isRegistered) {\n return;\n }\n\n // Step 1: Resolve config\n const config = resolveConfig(options);\n if (config.verbose) {\n console.info(\"[glasstrace] Step 1: Config resolved.\");\n }\n\n // Step 2: Production check\n if (isProductionDisabled(config)) {\n console.warn(\n \"[glasstrace] Disabled in production. Set GLASSTRACE_FORCE_ENABLE=true to override.\",\n );\n return;\n }\n if (config.verbose) {\n console.info(\"[glasstrace] Step 2: Not production-disabled.\");\n }\n\n // Step 3: Determine auth mode\n const anonymous = isAnonymousMode(config);\n let effectiveKey: string | undefined = config.apiKey;\n\n if (effectiveKey) {\n setResolvedApiKey(effectiveKey);\n }\n\n if (config.verbose) {\n console.info(\n `[glasstrace] Step 3: Auth mode = ${anonymous ? \"anonymous\" : \"dev-key\"}.`,\n );\n }\n\n // Step 4: Load cached config and apply to in-memory store\n const cachedInitResponse = loadCachedConfig();\n if (cachedInitResponse) {\n _setCurrentConfig(cachedInitResponse);\n }\n if (config.verbose) {\n console.info(\n `[glasstrace] Step 4: Cached config ${cachedInitResponse ? \"loaded and applied\" : \"not found\"}.`,\n );\n }\n\n // Step 5: Create SessionManager\n const sessionManager = new SessionManager();\n if (config.verbose) {\n console.info(\"[glasstrace] Step 5: SessionManager created.\");\n }\n\n isRegistered = true;\n const currentGeneration = registrationGeneration;\n\n // Step 6: Configure OTel IMMEDIATELY in all modes.\n // OTel is registered before the anon key resolves so that\n // spans are captured from cold start. GlasstraceExporter buffers spans\n // while the key is \"pending\" and flushes them once notifyApiKeyResolved()\n // is called after anonymous key resolution.\n // This is fire-and-forget -- OTel failure must not block init.\n void configureOtel(config, sessionManager).then(\n () => {\n if (config.verbose) {\n console.info(\"[glasstrace] Step 6: OTel configured.\");\n }\n },\n (err: unknown) => {\n console.warn(\n `[glasstrace] Failed to configure OTel: ${err instanceof Error ? err.message : String(err)}`,\n );\n },\n );\n\n // Step 7 + 8 + anonymous key resolution -- all in background\n if (anonymous) {\n // Step 8: Register discovery endpoint IMMEDIATELY with async key resolution\n if (isDiscoveryEnabled(config)) {\n let resolvedAnonKey: AnonApiKey | null = null;\n const anonKeyPromise = getOrCreateAnonKey();\n\n // Use getResolvedApiKey() for session ID instead of\n // capturing the mutable effectiveKey closure variable.\n discoveryHandler = createDiscoveryHandler(\n async () => resolvedAnonKey,\n () => sessionManager.getSessionId(getResolvedApiKey()),\n );\n\n if (config.verbose) {\n console.info(\"[glasstrace] Step 8: Discovery endpoint registered (key pending).\");\n }\n\n // Background: resolve key, update API key, then init\n void (async () => {\n try {\n if (currentGeneration !== registrationGeneration) return;\n\n const anonKey = await anonKeyPromise;\n resolvedAnonKey = anonKey;\n setResolvedApiKey(anonKey);\n notifyApiKeyResolved();\n effectiveKey = anonKey;\n\n if (currentGeneration !== registrationGeneration) return;\n\n // Update the discovery handler to serve the resolved key\n // Use getResolvedApiKey() for canonical key state\n discoveryHandler = createDiscoveryHandler(\n () => Promise.resolve(anonKey),\n () => sessionManager.getSessionId(getResolvedApiKey()),\n );\n\n if (config.verbose) {\n console.info(\"[glasstrace] Step 7: Background init firing.\");\n }\n\n await performInit(config, anonKey, __SDK_VERSION__);\n } catch (err) {\n console.warn(\n `[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n })();\n } else {\n // Anonymous + non-dev: no discovery endpoint, just background init\n void (async () => {\n try {\n if (currentGeneration !== registrationGeneration) return;\n\n const anonKey = await getOrCreateAnonKey();\n setResolvedApiKey(anonKey);\n notifyApiKeyResolved();\n effectiveKey = anonKey;\n\n if (currentGeneration !== registrationGeneration) return;\n\n if (config.verbose) {\n console.info(\"[glasstrace] Step 7: Background init firing.\");\n }\n\n await performInit(config, anonKey, __SDK_VERSION__);\n } catch (err) {\n console.warn(\n `[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n })();\n }\n } else {\n // Dev key mode: read straggler anon key, then fire init\n void (async () => {\n try {\n if (currentGeneration !== registrationGeneration) return;\n\n let anonKeyForInit: AnonApiKey | null = null;\n try {\n anonKeyForInit = await readAnonKey();\n } catch {\n // Expected when no prior anon key file exists on disk (first run).\n }\n\n if (currentGeneration !== registrationGeneration) return;\n\n if (config.verbose) {\n console.info(\"[glasstrace] Step 7: Background init firing.\");\n }\n await performInit(config, anonKeyForInit, __SDK_VERSION__);\n } catch (err) {\n console.warn(\n `[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n })();\n }\n\n // Step 9: Import graph (coverageMapEnabled) -- placeholder\n if (config.coverageMapEnabled && config.verbose) {\n console.info(\"[glasstrace] Step 9: Import graph building skipped.\");\n }\n } catch (err) {\n console.warn(\n `[glasstrace] Registration failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\n/**\n * Returns the registered discovery handler, or null if not registered.\n */\nexport function getDiscoveryHandler(): ((request: Request) => Promise<Response | null>) | null {\n return discoveryHandler;\n}\n\n/**\n * Returns `true` if the discovery endpoint should be enabled for this environment.\n *\n * Tightened from \"not production\" to explicit development conditions:\n * 1. `GLASSTRACE_DISCOVERY_ENABLED=true` -- explicit override (highest priority)\n * 2. `GLASSTRACE_DISCOVERY_ENABLED=false` -- explicit disable (highest priority)\n * 3. `NODE_ENV` is `\"development\"` or unset, AND `VERCEL_ENV` is not `\"production\"`\n *\n * Environments like `staging`, `test`, and `ci` do not expose the discovery\n * endpoint by default, since it serves the anonymous API key over CORS.\n *\n * @param config - The resolved SDK configuration.\n * @returns Whether the discovery endpoint should be registered.\n */\nfunction isDiscoveryEnabled(config: ResolvedConfig): boolean {\n // Explicit flag takes precedence\n if (process.env.GLASSTRACE_DISCOVERY_ENABLED === \"true\") return true;\n if (process.env.GLASSTRACE_DISCOVERY_ENABLED === \"false\") return false;\n\n // Block production environments\n if (config.nodeEnv === \"production\") return false;\n if (config.vercelEnv === \"production\") return false;\n\n // Only allow in development or when NODE_ENV is unset\n if (config.nodeEnv === \"development\" || config.nodeEnv === undefined) return true;\n\n return false;\n}\n\n/**\n * Resets registration state. For testing only.\n */\nexport function _resetRegistrationForTesting(): void {\n isRegistered = false;\n discoveryHandler = null;\n registrationGeneration++;\n resetOtelConfigForTesting();\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport { execSync } from \"node:child_process\";\nimport {\n SourceMapUploadResponseSchema,\n type SourceMapUploadResponse,\n} from \"@glasstrace/protocol\";\n\nexport interface SourceMapEntry {\n filePath: string;\n content: string;\n}\n\n/**\n * Recursively finds all .map files in the given build directory.\n * Returns relative paths and file contents.\n */\nexport async function collectSourceMaps(\n buildDir: string,\n): Promise<SourceMapEntry[]> {\n const results: SourceMapEntry[] = [];\n\n try {\n await walkDir(buildDir, buildDir, results);\n } catch {\n // Directory doesn't exist or is unreadable — return empty\n return [];\n }\n\n return results;\n}\n\nasync function walkDir(\n baseDir: string,\n currentDir: string,\n results: SourceMapEntry[],\n): Promise<void> {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(currentDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n await walkDir(baseDir, fullPath, results);\n } else if (entry.isFile() && entry.name.endsWith(\".map\")) {\n try {\n const content = await fs.readFile(fullPath, \"utf-8\");\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, \"/\");\n // Strip the trailing .map extension so the key matches the compiled\n // JS path that the runtime uses for stack-frame lookups (e.g.\n // \"static/chunks/main.js\" instead of \"static/chunks/main.js.map\").\n const compiledPath = relativePath.replace(/\\.map$/, \"\");\n results.push({ filePath: compiledPath, content });\n } catch {\n // Skip unreadable files\n }\n }\n }\n}\n\n/**\n * Computes a build hash for source map uploads.\n *\n * First tries `git rev-parse HEAD` to get the git commit SHA.\n * On failure, falls back to a deterministic content hash:\n * sort source map file paths alphabetically, concatenate each as\n * `{relativePath}\\n{fileLength}\\n{fileContent}`, then SHA-256 the result.\n */\nexport async function computeBuildHash(\n maps?: SourceMapEntry[],\n): Promise<string> {\n // Try git first\n try {\n const sha = execSync(\"git rev-parse HEAD\", { encoding: \"utf-8\" }).trim();\n if (sha) {\n return sha;\n }\n } catch {\n // Git not available, fall through to content hash\n }\n\n // Fallback: content-based hash\n const sortedMaps = [...(maps ?? [])].sort((a, b) =>\n a.filePath.localeCompare(b.filePath),\n );\n\n const hashInput = sortedMaps\n .map((m) => `${m.filePath}\\n${m.content.length}\\n${m.content}`)\n .join(\"\");\n\n const hash = crypto.createHash(\"sha256\").update(hashInput).digest(\"hex\");\n return hash;\n}\n\n/**\n * Uploads source maps to the ingestion API.\n *\n * POSTs to `{endpoint}/v1/source-maps` with the API key, build hash,\n * and file entries. Validates the response against SourceMapUploadResponseSchema.\n */\nexport async function uploadSourceMaps(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[],\n): Promise<SourceMapUploadResponse> {\n const body = {\n apiKey,\n buildHash,\n files: maps.map((m) => ({\n filePath: m.filePath,\n sourceMap: m.content,\n })),\n };\n\n const baseUrl = endpoint.replace(/\\/+$/, \"\");\n const response = await fetch(`${baseUrl}/v1/source-maps`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new Error(\n `Source map upload failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return SourceMapUploadResponseSchema.parse(json);\n}\n","import { collectSourceMaps, computeBuildHash, uploadSourceMaps } from \"./source-map-uploader.js\";\n\ntype NextConfig = Record<string, unknown>;\ntype WebpackConfigFn = (config: Record<string, unknown>, context: Record<string, unknown>) => Record<string, unknown>;\n\n/** Subset of the webpack context object passed by Next.js to the webpack config function. */\ninterface WebpackContext {\n isServer: boolean;\n dev: boolean;\n [key: string]: unknown;\n}\n\n/** Minimal webpack compiler shape for the afterEmit hook. */\ninterface WebpackCompiler {\n hooks?: {\n afterEmit?: {\n tapPromise?: (name: string, fn: (compilation: unknown) => Promise<void>) => void;\n };\n };\n}\n\n/**\n * Wraps the developer's Next.js config to enable source map generation\n * and upload .map files to the ingestion API at build time.\n *\n * The build NEVER fails because of Glasstrace — all errors are caught\n * and logged as warnings.\n *\n * @param nextConfig - The developer's existing Next.js configuration object.\n * @returns A new config object with source map generation and upload enabled.\n */\nexport function withGlasstraceConfig(nextConfig: NextConfig): NextConfig {\n // Handle null/undefined gracefully\n const config: NextConfig = nextConfig != null ? { ...nextConfig } : {};\n\n // Enable server-side source map generation for Glasstrace uploads.\n // Intentionally does NOT set productionBrowserSourceMaps — that exposes\n // full source code publicly via browser DevTools. Users who want public\n // browser source maps can set it explicitly in their Next.js config.\n\n // Enable server-side source maps (Next.js experimental feature)\n const existingExperimental = (config.experimental as Record<string, unknown>) ?? {};\n config.experimental = { ...existingExperimental, serverSourceMaps: true };\n\n // Capture distDir for source map collection (default: .next)\n const distDir = typeof config.distDir === \"string\" ? config.distDir : \".next\";\n\n // Capture existing webpack config if any\n const existingWebpack = config.webpack as WebpackConfigFn | undefined;\n\n // Register webpack config modifier\n config.webpack = (\n webpackConfig: Record<string, unknown>,\n context: Record<string, unknown>,\n ): Record<string, unknown> => {\n // Call existing webpack config first\n let result = webpackConfig;\n if (typeof existingWebpack === \"function\") {\n result = existingWebpack(webpackConfig, context);\n }\n\n const webpackContext = context as WebpackContext;\n\n // Only run source map upload on client-side production builds (not server, not dev)\n if (!webpackContext.isServer && webpackContext.dev === false) {\n // Register a plugin to collect and upload source maps after compilation\n const plugins = (result.plugins as Array<Record<string, unknown>>) ?? [];\n plugins.push({\n apply(compiler: Record<string, unknown>) {\n const typedCompiler = compiler as WebpackCompiler;\n if (typedCompiler.hooks?.afterEmit?.tapPromise) {\n typedCompiler.hooks.afterEmit.tapPromise(\n \"GlasstraceSourceMapUpload\",\n async () => {\n await handleSourceMapUpload(distDir);\n },\n );\n }\n },\n });\n result.plugins = plugins;\n }\n\n return result;\n };\n\n return config;\n}\n\n/**\n * Collects source map files from the build output directory and uploads\n * them to the Glasstrace ingestion API. Never throws — all errors are\n * caught and logged as warnings so the build is never blocked.\n *\n * Exported for testing only; not part of the public API.\n *\n * @param distDir - The Next.js build output directory (e.g. \".next\").\n */\nexport async function handleSourceMapUpload(distDir: string): Promise<void> {\n try {\n const apiKey = process.env.GLASSTRACE_API_KEY;\n const endpoint =\n process.env.GLASSTRACE_ENDPOINT ?? \"https://api.glasstrace.dev\";\n\n // Anonymous mode: skip upload\n if (!apiKey || apiKey.trim() === \"\") {\n console.info(\n \"[glasstrace] Source map upload skipped (no API key). Stack traces will show compiled locations.\",\n );\n return;\n }\n\n const maps = await collectSourceMaps(distDir);\n\n if (maps.length === 0) {\n console.info(\"[glasstrace] No source map files found. Skipping upload.\");\n return;\n }\n\n const buildHash = await computeBuildHash(maps);\n\n await uploadSourceMaps(apiKey, endpoint, buildHash, maps);\n console.info(\n `[glasstrace] Uploaded ${String(maps.length)} source map(s) for build ${buildHash}.`,\n );\n } catch (error: unknown) {\n // Build must NEVER fail because of Glasstrace\n const message =\n error instanceof Error ? error.message : \"Unknown error\";\n console.warn(\n `[glasstrace] Source map upload failed: ${message}. Build continues normally.`,\n );\n }\n}\n","import * as fs from \"node:fs/promises\";\nimport * as fsSync from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport { createBuildHash, type ImportGraphPayload } from \"@glasstrace/protocol\";\n\n/** Maximum number of test files to process to prevent runaway in large projects */\nconst MAX_TEST_FILES = 5000;\n\n/** Directories to exclude from test file discovery */\nconst EXCLUDED_DIRS = new Set([\"node_modules\", \".next\", \".git\", \"dist\", \".turbo\"]);\n\n/** Conventional test file patterns */\nconst DEFAULT_TEST_PATTERNS = [\n /\\.test\\.tsx?$/,\n /\\.spec\\.tsx?$/,\n];\n\n/**\n * Converts a glob pattern (e.g. \"e2e/**\\/*.ts\") to an anchored RegExp.\n * Uses a placeholder to avoid `*` replacement corrupting the `**\\/` output.\n *\n * @param glob - A file glob pattern such as \"src/**\\/*.test.ts\".\n * @returns A RegExp that matches paths against the glob from start to end.\n */\nfunction globToRegExp(glob: string): RegExp {\n const DOUBLE_STAR_PLACEHOLDER = \"\\0DSTAR\\0\";\n const regexStr = glob\n .replace(/\\*\\*\\//g, DOUBLE_STAR_PLACEHOLDER) // protect **/ first\n .replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\") // escape all regex metacharacters (except *)\n .replace(/\\*/g, \"[^/]+\")\n .replace(new RegExp(DOUBLE_STAR_PLACEHOLDER.replace(/\\0/g, \"\\\\0\"), \"g\"), \"(?:.+/)?\");\n return new RegExp(\"^\" + regexStr + \"$\");\n}\n\n/**\n * Attempts to read include patterns from vitest.config.*, vite.config.*,\n * or jest.config.* files. Returns additional RegExp patterns extracted\n * from the config, or an empty array if no config is found or parsing fails.\n * This is best-effort — it reads the config as text and extracts patterns\n * via regex, without evaluating the JS.\n *\n * For Vitest/Vite configs, looks for `test.include` arrays.\n * For Jest configs, looks for `testMatch` arrays.\n * Does not support `testRegex` (string-based Jest pattern) — that is\n * left as future work.\n */\nfunction loadCustomTestPatterns(projectRoot: string): RegExp[] {\n const configNames = [\n \"vitest.config.ts\",\n \"vitest.config.js\",\n \"vitest.config.mts\",\n \"vitest.config.mjs\",\n \"vite.config.ts\",\n \"vite.config.js\",\n \"vite.config.mts\",\n \"vite.config.mjs\",\n \"jest.config.ts\",\n \"jest.config.js\",\n \"jest.config.mts\",\n \"jest.config.mjs\",\n ];\n\n for (const name of configNames) {\n const configPath = path.join(projectRoot, name);\n let content: string;\n try {\n content = fsSync.readFileSync(configPath, \"utf-8\");\n } catch {\n // Config file does not exist at this path — try next candidate\n continue;\n }\n\n try {\n const isJest = name.startsWith(\"jest.\");\n let includeMatch: RegExpExecArray | null = null;\n\n if (isJest) {\n // Jest: look for testMatch: [...]\n includeMatch = /testMatch\\s*:\\s*\\[([^\\]]*)\\]/s.exec(content);\n } else {\n // Vitest/Vite: look for `test` block's `include` to avoid\n // matching `coverage.include` or other unrelated arrays.\n // Strategy: find `test` property, then look for `include` within\n // the next ~500 chars (heuristic to stay within the test block).\n const testBlockMatch = /\\btest\\s*[:{]\\s*/s.exec(content);\n if (testBlockMatch) {\n const afterTest = content.slice(testBlockMatch.index, testBlockMatch.index + 500);\n includeMatch = /include\\s*:\\s*\\[([^\\]]*)\\]/s.exec(afterTest);\n }\n }\n\n if (!includeMatch) {\n continue;\n }\n\n const arrayContent = includeMatch[1];\n const stringRegex = /['\"]([^'\"]+)['\"]/g;\n const patterns: RegExp[] = [];\n let match: RegExpExecArray | null;\n match = stringRegex.exec(arrayContent);\n while (match !== null) {\n patterns.push(globToRegExp(match[1]));\n match = stringRegex.exec(arrayContent);\n }\n\n if (patterns.length > 0) {\n return patterns;\n }\n } catch {\n // Regex-based config parsing failed — fall through to next config file\n continue;\n }\n }\n\n return [];\n}\n\n/**\n * Discovers test files by scanning the project directory for conventional\n * test file patterns. Also reads vitest/jest config files for custom include\n * patterns and merges them with the defaults. Excludes node_modules/ and .next/.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns Relative POSIX paths from projectRoot, capped at {@link MAX_TEST_FILES}.\n */\nexport async function discoverTestFiles(\n projectRoot: string,\n): Promise<string[]> {\n const customPatterns = loadCustomTestPatterns(projectRoot);\n const testPatterns = [...DEFAULT_TEST_PATTERNS, ...customPatterns];\n const results: string[] = [];\n\n try {\n await walkForTests(projectRoot, projectRoot, results, testPatterns);\n } catch {\n // Project root directory does not exist or is unreadable — return empty\n return [];\n }\n\n return results.slice(0, MAX_TEST_FILES);\n}\n\n/** Recursively walks directories, collecting test file paths into `results`. */\nasync function walkForTests(\n baseDir: string,\n currentDir: string,\n results: string[],\n testPatterns: RegExp[],\n): Promise<void> {\n if (results.length >= MAX_TEST_FILES) {\n return;\n }\n\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(currentDir, { withFileTypes: true });\n } catch {\n // Directory is unreadable (permissions, broken symlink) — skip subtree\n return;\n }\n\n for (const entry of entries) {\n if (results.length >= MAX_TEST_FILES) {\n return;\n }\n\n const fullPath = path.join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n if (EXCLUDED_DIRS.has(entry.name)) {\n continue;\n }\n await walkForTests(baseDir, fullPath, results, testPatterns);\n } else if (entry.isFile()) {\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, \"/\");\n\n // Check if it matches test patterns or is in __tests__\n const isTestFile =\n testPatterns.some((p) => p.test(entry.name) || p.test(relativePath)) ||\n relativePath.includes(\"__tests__\");\n\n if (isTestFile && (entry.name.endsWith(\".ts\") || entry.name.endsWith(\".tsx\"))) {\n results.push(relativePath);\n }\n }\n }\n}\n\n/**\n * Extracts import paths from file content using regex.\n * Handles ES module imports, CommonJS requires, and dynamic imports.\n *\n * @param fileContent - The full text content of a TypeScript/JavaScript file.\n * @returns An array of import path strings as written in the source (e.g. \"./foo\", \"react\").\n */\nexport function extractImports(fileContent: string): string[] {\n const seen = new Set<string>();\n const imports: string[] = [];\n\n /** Adds a path to the result if not already present. */\n const addUnique = (importPath: string): void => {\n if (!seen.has(importPath)) {\n seen.add(importPath);\n imports.push(importPath);\n }\n };\n\n // ES module: import ... from 'path' or import 'path'\n const esImportRegex = /import\\s+(?:(?:[\\w*{}\\s,]+)\\s+from\\s+)?['\"]([^'\"]+)['\"]/g;\n let match: RegExpExecArray | null;\n\n match = esImportRegex.exec(fileContent);\n while (match !== null) {\n addUnique(match[1]);\n match = esImportRegex.exec(fileContent);\n }\n\n // CommonJS: require('path')\n const requireRegex = /require\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n match = requireRegex.exec(fileContent);\n while (match !== null) {\n addUnique(match[1]);\n match = requireRegex.exec(fileContent);\n }\n\n // Dynamic import: import('path')\n const dynamicImportRegex = /import\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n match = dynamicImportRegex.exec(fileContent);\n while (match !== null) {\n addUnique(match[1]);\n match = dynamicImportRegex.exec(fileContent);\n }\n\n return imports;\n}\n\n/**\n * Builds an import graph mapping test file paths to their imported module paths.\n *\n * Discovers test files, reads each, extracts imports, and builds a graph.\n * Computes a deterministic buildHash from the serialized graph content.\n * Individual file read failures are silently skipped.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns An {@link ImportGraphPayload} containing the graph and a deterministic buildHash.\n */\nexport async function buildImportGraph(\n projectRoot: string,\n): Promise<ImportGraphPayload> {\n const testFiles = await discoverTestFiles(projectRoot);\n const graph: Record<string, string[]> = {};\n\n for (const testFile of testFiles) {\n const fullPath = path.join(projectRoot, testFile);\n try {\n const content = await fs.readFile(fullPath, \"utf-8\");\n const imports = extractImports(content);\n graph[testFile] = imports;\n } catch {\n // File is unreadable (permissions, deleted between discovery and read) — skip\n continue;\n }\n }\n\n // Compute deterministic build hash from graph content\n const sortedKeys = Object.keys(graph).sort();\n const serialized = sortedKeys\n .map((key) => `${key}:${JSON.stringify(graph[key])}`)\n .join(\"\\n\");\n const hashHex = crypto\n .createHash(\"sha256\")\n .update(serialized)\n .digest(\"hex\");\n const buildHash = createBuildHash(hashHex);\n\n return { buildHash, graph };\n}\n"],"mappings":"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","names":["import_protocol","import_promises","import_node_path","import_protocol","GLASSTRACE_DIR","import_protocol","processor","provider","import_protocol","fs","path","crypto","import_protocol"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/env-detection.ts","../src/session.ts","../src/fetch-classifier.ts","../src/anon-key.ts","../src/init-client.ts","../src/span-processor.ts","../src/enriching-exporter.ts","../src/discovery-endpoint.ts","../src/otel-config.ts","../src/console-capture.ts","../src/capture-error.ts","../src/register.ts","../src/source-map-uploader.ts","../src/config-wrapper.ts","../src/import-graph.ts"],"sourcesContent":["// @glasstrace/sdk\nexport { SdkError } from \"./errors.js\";\nexport {\n readEnvVars,\n resolveConfig,\n isProductionDisabled,\n isAnonymousMode,\n} from \"./env-detection.js\";\nexport type { ResolvedConfig } from \"./env-detection.js\";\nexport {\n deriveSessionId,\n getOrigin,\n getDateString,\n SessionManager,\n} from \"./session.js\";\nexport { classifyFetchTarget } from \"./fetch-classifier.js\";\nexport type { FetchTarget } from \"./fetch-classifier.js\";\nexport { getOrCreateAnonKey, readAnonKey } from \"./anon-key.js\";\n\n// Init client + config cache\nexport {\n loadCachedConfig,\n saveCachedConfig,\n sendInitRequest,\n performInit,\n getActiveConfig,\n} from \"./init-client.js\";\n\n// Custom span processor (deprecated — now a pass-through)\nexport { GlasstraceSpanProcessor } from \"./span-processor.js\";\n\n// Enriching exporter — performs all glasstrace.* enrichment at export time\nexport { GlasstraceExporter } from \"./enriching-exporter.js\";\nexport type { GlasstraceExporterOptions } from \"./enriching-exporter.js\";\n\n// Discovery endpoint\nexport { createDiscoveryHandler } from \"./discovery-endpoint.js\";\n\n// registerGlasstrace orchestrator\nexport {\n registerGlasstrace,\n getDiscoveryHandler,\n} from \"./register.js\";\n\n// Config wrapper + source map uploader\nexport { withGlasstraceConfig } from \"./config-wrapper.js\";\nexport {\n collectSourceMaps,\n computeBuildHash,\n uploadSourceMaps,\n} from \"./source-map-uploader.js\";\nexport type { SourceMapEntry } from \"./source-map-uploader.js\";\n\n// Manual error capture\nexport { captureError } from \"./capture-error.js\";\n\n// Import graph builder\nexport {\n discoverTestFiles,\n extractImports,\n buildImportGraph,\n} from \"./import-graph.js\";\n","import type { SdkDiagnosticCode } from \"@glasstrace/protocol\";\n\n/**\n * Internal SDK error class with a typed diagnostic code.\n * Caught at the boundary and converted to a log message + diagnostic entry.\n * Never thrown to the developer.\n */\nexport class SdkError extends Error {\n readonly code: SdkDiagnosticCode;\n\n constructor(code: SdkDiagnosticCode, message: string, cause?: Error) {\n super(message, { cause });\n this.name = \"SdkError\";\n this.code = code;\n }\n}\n","import type { GlasstraceEnvVars, GlasstraceOptions } from \"@glasstrace/protocol\";\n\n/**\n * Resolved configuration after merging explicit options with environment variables.\n */\nexport interface ResolvedConfig {\n apiKey: string | undefined;\n endpoint: string;\n forceEnable: boolean;\n verbose: boolean;\n environment: string | undefined;\n coverageMapEnabled: boolean;\n nodeEnv: string | undefined;\n vercelEnv: string | undefined;\n}\n\nconst DEFAULT_ENDPOINT = \"https://api.glasstrace.dev\";\n\n/**\n * Reads all recognized Glasstrace environment variables from process.env.\n * Returns undefined for any variable not set. Never throws.\n */\nexport function readEnvVars(): GlasstraceEnvVars {\n return {\n GLASSTRACE_API_KEY: process.env.GLASSTRACE_API_KEY,\n GLASSTRACE_FORCE_ENABLE: process.env.GLASSTRACE_FORCE_ENABLE,\n GLASSTRACE_ENV: process.env.GLASSTRACE_ENV,\n GLASSTRACE_COVERAGE_MAP: process.env.GLASSTRACE_COVERAGE_MAP,\n NODE_ENV: process.env.NODE_ENV,\n VERCEL_ENV: process.env.VERCEL_ENV,\n };\n}\n\n/**\n * Merges explicit GlasstraceOptions with environment variables.\n * Explicit options take precedence over environment variables.\n */\nexport function resolveConfig(options?: GlasstraceOptions): ResolvedConfig {\n const env = readEnvVars();\n\n return {\n apiKey: options?.apiKey ?? env.GLASSTRACE_API_KEY,\n endpoint: options?.endpoint ?? DEFAULT_ENDPOINT,\n forceEnable: options?.forceEnable ?? env.GLASSTRACE_FORCE_ENABLE === \"true\",\n verbose: options?.verbose ?? false,\n environment: env.GLASSTRACE_ENV,\n coverageMapEnabled: env.GLASSTRACE_COVERAGE_MAP === \"true\",\n nodeEnv: env.NODE_ENV,\n vercelEnv: env.VERCEL_ENV,\n };\n}\n\n/**\n * Returns true when the SDK should be inactive (production detected without force-enable).\n * Logic order:\n * 1. forceEnable === true → return false (override)\n * 2. NODE_ENV === 'production' → return true\n * 3. VERCEL_ENV === 'production' → return true\n * 4. Otherwise → return false\n */\nexport function isProductionDisabled(config: ResolvedConfig): boolean {\n if (config.forceEnable) {\n return false;\n }\n if (config.nodeEnv === \"production\") {\n return true;\n }\n if (config.vercelEnv === \"production\") {\n return true;\n }\n return false;\n}\n\n/**\n * Returns true when no API key is configured (anonymous mode).\n * Treats undefined, empty string, whitespace-only, and gt_anon_* keys as anonymous.\n */\nexport function isAnonymousMode(config: ResolvedConfig): boolean {\n if (config.apiKey === undefined) {\n return true;\n }\n if (config.apiKey.trim() === \"\") {\n return true;\n }\n if (config.apiKey.startsWith(\"gt_anon_\")) {\n return true;\n }\n return false;\n}\n","import { createHash } from \"node:crypto\";\nimport { SessionIdSchema } from \"@glasstrace/protocol\";\nimport type { SessionId } from \"@glasstrace/protocol\";\n\nconst FOUR_HOURS_MS = 4 * 60 * 60 * 1000;\n\n/** Cached at module load to avoid reading process.env on every span. */\nlet cachedGlasstraceEnv: string | undefined = process.env.GLASSTRACE_ENV;\nlet cachedPort: string = process.env.PORT ?? \"3000\";\n\n/**\n * Re-reads cached environment variables. For testing only.\n */\nexport function _resetEnvCacheForTesting(): void {\n cachedGlasstraceEnv = process.env.GLASSTRACE_ENV;\n cachedPort = process.env.PORT ?? \"3000\";\n}\n\n/**\n * Derives a deterministic session ID from the given inputs using SHA-256.\n * The hash is truncated to 16 hex characters and parsed through SessionIdSchema.\n *\n * @param apiKey - The project's API key (or anonymous placeholder).\n * @param origin - The origin string identifying the deployment environment.\n * @param date - UTC date as YYYY-MM-DD.\n * @param windowIndex - Zero-based index of the 4-hour activity window within the day.\n * @returns A 16-character hex SessionId.\n */\nexport function deriveSessionId(\n apiKey: string,\n origin: string,\n date: string,\n windowIndex: number,\n): SessionId {\n const input = JSON.stringify([apiKey, origin, date, windowIndex]);\n const hash = createHash(\"sha256\").update(input).digest(\"hex\").slice(0, 16);\n return SessionIdSchema.parse(hash);\n}\n\n/**\n * Returns the origin string for the current process.\n * If GLASSTRACE_ENV is set, returns that value.\n * Otherwise returns `localhost:{PORT}` (PORT defaults to 3000).\n *\n * @returns The origin string used as a session derivation input.\n */\nexport function getOrigin(): string {\n if (cachedGlasstraceEnv) {\n return cachedGlasstraceEnv;\n }\n return `localhost:${cachedPort}`;\n}\n\n/**\n * Returns the current UTC date as a YYYY-MM-DD string.\n *\n * @returns The UTC date formatted as \"YYYY-MM-DD\".\n */\nexport function getDateString(): string {\n const now = new Date();\n const year = now.getUTCFullYear();\n const month = String(now.getUTCMonth() + 1).padStart(2, \"0\");\n const day = String(now.getUTCDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Tracks the current session state with 4-hour window tracking.\n * Instantiated once by the orchestrator.\n */\nexport class SessionManager {\n private windowIndex: number = 0;\n private lastActivityTimestamp: number = 0;\n private lastDate: string = \"\";\n private lastApiKey: string = \"\";\n private currentSessionId: SessionId | null = null;\n\n /**\n * Returns the current session ID, deriving a new one if:\n * - More than 4 hours have elapsed since last activity\n * - The UTC date has changed (resets window index to 0)\n * - The API key has changed (e.g., deferred anonymous key swap)\n * - This is the first call\n *\n * @param apiKey - The project's API key used in session derivation.\n * @returns The current or newly derived SessionId.\n */\n getSessionId(apiKey: string): SessionId {\n const now = Date.now();\n const currentDate = getDateString();\n const origin = getOrigin();\n\n const elapsed = now - this.lastActivityTimestamp;\n const dateChanged = currentDate !== this.lastDate;\n const apiKeyChanged = apiKey !== this.lastApiKey;\n\n if (dateChanged) {\n // New UTC day: reset window index\n this.windowIndex = 0;\n this.lastDate = currentDate;\n this.lastApiKey = apiKey;\n this.currentSessionId = deriveSessionId(apiKey, origin, currentDate, this.windowIndex);\n } else if (apiKeyChanged) {\n // API key changed (e.g., anonymous key resolved): re-derive with same window\n this.lastApiKey = apiKey;\n this.currentSessionId = deriveSessionId(apiKey, origin, currentDate, this.windowIndex);\n } else if (this.currentSessionId === null || elapsed > FOUR_HOURS_MS) {\n // First call or gap exceeding 4 hours: increment window\n if (this.currentSessionId !== null) {\n this.windowIndex++;\n }\n this.lastApiKey = apiKey;\n this.currentSessionId = deriveSessionId(apiKey, origin, currentDate, this.windowIndex);\n this.lastDate = currentDate;\n }\n\n this.lastActivityTimestamp = now;\n return this.currentSessionId;\n }\n}\n","/**\n * The set of recognized fetch target categories.\n */\nexport type FetchTarget = \"supabase\" | \"stripe\" | \"internal\" | \"unknown\";\n\n/** Cached at module load to avoid reading process.env on every span. */\nlet cachedPort: string = process.env.PORT ?? \"3000\";\n\n/**\n * Re-reads cached environment variables. For testing only.\n */\nexport function _resetEnvCacheForTesting(): void {\n cachedPort = process.env.PORT ?? \"3000\";\n}\n\n/**\n * Classifies an outbound fetch target URL into a known category.\n * Classification is case-insensitive and based on the URL hostname.\n * Uses dot-boundary matching to avoid false positives (e.g. evilstripe.com).\n *\n * Returns one of: 'supabase', 'stripe', 'internal', or 'unknown'.\n */\nexport function classifyFetchTarget(url: string): FetchTarget {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return \"unknown\";\n }\n\n const hostname = parsed.hostname.toLowerCase();\n\n if (\n hostname === \"supabase.co\" ||\n hostname.endsWith(\".supabase.co\") ||\n hostname === \"supabase.in\" ||\n hostname.endsWith(\".supabase.in\")\n ) {\n return \"supabase\";\n }\n\n if (hostname === \"stripe.com\" || hostname.endsWith(\".stripe.com\")) {\n return \"stripe\";\n }\n\n const internalOrigin = `localhost:${cachedPort}`;\n const parsedPort = parsed.port || (parsed.protocol === \"https:\" ? \"443\" : \"80\");\n const urlOrigin = `${hostname}:${parsedPort}`;\n\n if (urlOrigin === internalOrigin) {\n return \"internal\";\n }\n\n return \"unknown\";\n}\n","import { readFile, writeFile, mkdir, chmod } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { AnonApiKeySchema, createAnonApiKey } from \"@glasstrace/protocol\";\nimport type { AnonApiKey } from \"@glasstrace/protocol\";\n\nconst GLASSTRACE_DIR = \".glasstrace\";\nconst ANON_KEY_FILE = \"anon_key\";\n\n/**\n * In-memory cache for ephemeral keys when filesystem persistence fails.\n * Keyed by resolved project root to support multiple roots in tests.\n */\nconst ephemeralKeyCache = new Map<string, AnonApiKey>();\n\n/**\n * Reads an existing anonymous key from the filesystem.\n * Returns the key if valid, or null if:\n * - The file does not exist\n * - The file content is invalid\n * - An I/O error occurs\n */\nexport async function readAnonKey(projectRoot?: string): Promise<AnonApiKey | null> {\n const root = projectRoot ?? process.cwd();\n const keyPath = join(root, GLASSTRACE_DIR, ANON_KEY_FILE);\n\n try {\n const content = await readFile(keyPath, \"utf-8\");\n const result = AnonApiKeySchema.safeParse(content);\n if (result.success) {\n return result.data;\n }\n } catch {\n // Fall through to check ephemeral cache\n }\n\n // Check in-memory cache (used when filesystem persistence failed)\n const cached = ephemeralKeyCache.get(root);\n if (cached !== undefined) {\n return cached;\n }\n\n return null;\n}\n\n/**\n * Gets an existing anonymous key from the filesystem, or creates a new one.\n *\n * - If file exists and contains a valid key, returns it\n * - If file does not exist or content is invalid, generates a new key via createAnonApiKey()\n * - Writes the new key to `.glasstrace/anon_key`, creating the directory if needed\n * - On file write failure: logs a warning, caches an ephemeral in-memory key so\n * repeated calls in the same process return the same key\n */\nexport async function getOrCreateAnonKey(projectRoot?: string): Promise<AnonApiKey> {\n const root = projectRoot ?? process.cwd();\n const dirPath = join(root, GLASSTRACE_DIR);\n const keyPath = join(dirPath, ANON_KEY_FILE);\n\n // Try reading existing key from filesystem\n const existingKey = await readAnonKey(root);\n if (existingKey !== null) {\n return existingKey;\n }\n\n // Check in-memory cache (used when filesystem is unavailable)\n const cached = ephemeralKeyCache.get(root);\n if (cached !== undefined) {\n return cached;\n }\n\n // Generate a new key\n const newKey = createAnonApiKey();\n\n // Persist to filesystem\n try {\n await mkdir(dirPath, { recursive: true, mode: 0o700 });\n await writeFile(keyPath, newKey, \"utf-8\");\n await chmod(keyPath, 0o600);\n } catch (err) {\n // Cache in memory so repeated calls get the same ephemeral key\n ephemeralKeyCache.set(root, newKey);\n console.warn(\n `[glasstrace] Failed to persist anonymous key to ${keyPath}: ${err instanceof Error ? err.message : String(err)}. Using ephemeral key.`,\n );\n }\n\n return newKey;\n}\n","import { readFileSync } from \"node:fs\";\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport {\n SdkInitResponseSchema,\n SdkCachedConfigSchema,\n DEFAULT_CAPTURE_CONFIG,\n} from \"@glasstrace/protocol\";\nimport type {\n SdkInitResponse,\n CaptureConfig,\n AnonApiKey,\n ImportGraphPayload,\n SdkHealthReport,\n SdkDiagnosticCode,\n} from \"@glasstrace/protocol\";\nimport type { ResolvedConfig } from \"./env-detection.js\";\n\nconst GLASSTRACE_DIR = \".glasstrace\";\nconst CONFIG_FILE = \"config\";\nconst TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000;\nconst INIT_TIMEOUT_MS = 10_000;\n\n/** In-memory config from the latest successful init response. */\nlet currentConfig: SdkInitResponse | null = null;\n\n/** Whether the next init call should be skipped (rate-limit backoff). */\nlet rateLimitBackoff = false;\n\n/**\n * Reads and validates a cached config file from `.glasstrace/config`.\n * Returns the parsed `SdkInitResponse` or `null` on any failure.\n */\nexport function loadCachedConfig(projectRoot?: string): SdkInitResponse | null {\n const root = projectRoot ?? process.cwd();\n const configPath = join(root, GLASSTRACE_DIR, CONFIG_FILE);\n\n try {\n // Use synchronous read for startup performance (this is called during init)\n const content = readFileSync(configPath, \"utf-8\");\n const parsed = JSON.parse(content);\n const cached = SdkCachedConfigSchema.parse(parsed);\n\n // Warn if cache is stale\n const age = Date.now() - cached.cachedAt;\n if (age > TWENTY_FOUR_HOURS_MS) {\n console.warn(\n `[glasstrace] Cached config is ${Math.round(age / 3600000)}h old. Will refresh on next init.`,\n );\n }\n\n // Parse the response through the schema\n const result = SdkInitResponseSchema.safeParse(cached.response);\n if (result.success) {\n return result.data;\n }\n\n console.warn(\"[glasstrace] Cached config failed validation. Using defaults.\");\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Persists the init response to `.glasstrace/config`.\n * On failure, logs a warning and continues.\n */\nexport async function saveCachedConfig(\n response: SdkInitResponse,\n projectRoot?: string,\n): Promise<void> {\n const root = projectRoot ?? process.cwd();\n const dirPath = join(root, GLASSTRACE_DIR);\n const configPath = join(dirPath, CONFIG_FILE);\n\n try {\n await mkdir(dirPath, { recursive: true });\n const cached = {\n response,\n cachedAt: Date.now(),\n };\n await writeFile(configPath, JSON.stringify(cached), \"utf-8\");\n } catch (err) {\n console.warn(\n `[glasstrace] Failed to cache config to ${configPath}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\n/**\n * Sends a POST request to `/v1/sdk/init`.\n * Validates the response against `SdkInitResponseSchema`.\n */\nexport async function sendInitRequest(\n config: ResolvedConfig,\n anonKey: AnonApiKey | null,\n sdkVersion: string,\n importGraph?: ImportGraphPayload,\n healthReport?: SdkHealthReport,\n diagnostics?: Array<{ code: SdkDiagnosticCode; message: string; timestamp: number }>,\n signal?: AbortSignal,\n): Promise<SdkInitResponse> {\n // Determine the API key for auth\n const effectiveKey = config.apiKey ?? anonKey;\n if (!effectiveKey) {\n throw new Error(\"No API key available for init request\");\n }\n\n // Build the request payload\n const payload: Record<string, unknown> = {\n apiKey: effectiveKey,\n sdkVersion,\n };\n\n // Straggler linking: if dev key is set AND anonKey is provided\n if (config.apiKey && anonKey) {\n payload.anonKey = anonKey;\n }\n\n if (config.environment) {\n payload.environment = config.environment;\n }\n if (importGraph) {\n payload.importGraph = importGraph;\n }\n if (healthReport) {\n payload.healthReport = healthReport;\n }\n if (diagnostics) {\n payload.diagnostics = diagnostics;\n }\n\n const url = `${config.endpoint}/v1/sdk/init`;\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${effectiveKey}`,\n },\n body: JSON.stringify(payload),\n signal,\n });\n\n if (!response.ok) {\n // Consume the response body to release the connection back to the pool.\n // Without this, the underlying TCP socket stays allocated until GC, which\n // causes connection pool exhaustion under sustained error conditions.\n // Wrapped in try-catch so a stream error doesn't mask the HTTP status error.\n try { await response.text(); } catch { /* body drain is best-effort */ }\n const error = new Error(`Init request failed with status ${response.status}`);\n (error as unknown as Record<string, unknown>).status = response.status;\n throw error;\n }\n\n const body = await response.json();\n return SdkInitResponseSchema.parse(body);\n}\n\n/**\n * Orchestrates the full init flow: send request, update config, cache result.\n * This function MUST NOT throw.\n */\nexport async function performInit(\n config: ResolvedConfig,\n anonKey: AnonApiKey | null,\n sdkVersion: string,\n): Promise<void> {\n // Skip if in rate-limit backoff\n if (rateLimitBackoff) {\n rateLimitBackoff = false; // Reset for next call\n return;\n }\n\n try {\n const effectiveKey = config.apiKey ?? anonKey;\n if (!effectiveKey) {\n console.warn(\"[glasstrace] No API key available for init request.\");\n return;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), INIT_TIMEOUT_MS);\n\n try {\n // Delegate to sendInitRequest to avoid duplicating fetch logic\n const result = await sendInitRequest(\n config,\n anonKey,\n sdkVersion,\n undefined,\n undefined,\n undefined,\n controller.signal,\n );\n\n clearTimeout(timeoutId);\n\n // Update in-memory config\n currentConfig = result;\n\n // Persist to disk\n await saveCachedConfig(result);\n } catch (err) {\n clearTimeout(timeoutId);\n\n if (err instanceof DOMException && err.name === \"AbortError\") {\n console.warn(\"[glasstrace] ingestion_unreachable: Init request timed out.\");\n return;\n }\n\n // Check for HTTP status errors attached by sendInitRequest\n const status = (err as Record<string, unknown>).status;\n if (status === 401) {\n console.warn(\n \"[glasstrace] ingestion_auth_failed: Check your GLASSTRACE_API_KEY.\",\n );\n return;\n }\n\n if (status === 429) {\n console.warn(\"[glasstrace] ingestion_rate_limited: Backing off.\");\n rateLimitBackoff = true;\n return;\n }\n\n if (typeof status === \"number\" && status >= 400) {\n console.warn(\n `[glasstrace] Init request failed with status ${status}. Using cached config.`,\n );\n return;\n }\n\n // Schema validation failure from sendInitRequest.parse\n if (err instanceof Error && err.name === \"ZodError\") {\n console.warn(\n \"[glasstrace] Init response failed validation (schema version mismatch?). Using cached config.\",\n );\n return;\n }\n\n // Network error or other fetch failure\n console.warn(\n `[glasstrace] ingestion_unreachable: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n } catch (err) {\n // Outermost catch -- should never reach here, but safety net\n console.warn(\n `[glasstrace] Unexpected init error: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\n/**\n * Returns the current capture config from the three-tier fallback chain:\n * 1. In-memory config from latest init response\n * 2. File cache\n * 3. DEFAULT_CAPTURE_CONFIG\n */\nexport function getActiveConfig(): CaptureConfig {\n // Tier 1: in-memory\n if (currentConfig) {\n return currentConfig.config;\n }\n\n // Tier 2: file cache\n const cached = loadCachedConfig();\n if (cached) {\n return cached.config;\n }\n\n // Tier 3: defaults\n return { ...DEFAULT_CAPTURE_CONFIG };\n}\n\n/**\n * Resets the in-memory config store. For testing only.\n */\nexport function _resetConfigForTesting(): void {\n currentConfig = null;\n rateLimitBackoff = false;\n}\n\n/**\n * Sets the in-memory config directly. Used by performInit and the orchestrator.\n */\nexport function _setCurrentConfig(config: SdkInitResponse): void {\n currentConfig = config;\n}\n\n/**\n * Returns whether rate-limit backoff is active. For testing only.\n */\nexport function _isRateLimitBackoff(): boolean {\n return rateLimitBackoff;\n}\n","import type { Context } from \"@opentelemetry/api\";\nimport type { SpanProcessor, ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\nimport type { Span } from \"@opentelemetry/sdk-trace-base\";\nimport type { CaptureConfig } from \"@glasstrace/protocol\";\nimport type { SessionManager } from \"./session.js\";\n\n/**\n * Lightweight SpanProcessor that delegates to a wrapped processor.\n *\n * All glasstrace.* attribute enrichment has been moved to {@link GlasstraceExporter}\n * (see enriching-exporter.ts), which enriches spans at export time. This resolves:\n * - Cold-start spans are buffered in the exporter, not dropped\n * - Vercel's CompositeSpanProcessor skips onEnding(); the exporter doesn't need it\n * - Session ID is computed at export time with the resolved API key\n *\n * This class is retained for backward compatibility. New code should use\n * GlasstraceExporter directly.\n *\n * @deprecated Use GlasstraceExporter for span enrichment. This processor is now a pass-through.\n */\nexport class GlasstraceSpanProcessor implements SpanProcessor {\n private readonly wrappedProcessor: SpanProcessor;\n\n /* eslint-disable @typescript-eslint/no-unused-vars -- backward compat signature */\n constructor(\n wrappedProcessor: SpanProcessor,\n _sessionManager?: SessionManager,\n _apiKey?: string | (() => string),\n _getConfig?: () => CaptureConfig,\n _environment?: string,\n ) {\n /* eslint-enable @typescript-eslint/no-unused-vars */\n this.wrappedProcessor = wrappedProcessor;\n }\n\n onStart(span: Span, parentContext: Context): void {\n this.wrappedProcessor.onStart(span, parentContext);\n }\n\n onEnd(readableSpan: ReadableSpan): void {\n this.wrappedProcessor.onEnd(readableSpan);\n }\n\n async shutdown(): Promise<void> {\n return this.wrappedProcessor.shutdown();\n }\n\n async forceFlush(): Promise<void> {\n return this.wrappedProcessor.forceFlush();\n }\n}\n","import { SpanKind } from \"@opentelemetry/api\";\nimport type { ReadableSpan, SpanExporter } from \"@opentelemetry/sdk-trace-base\";\nimport type { ExportResult } from \"@opentelemetry/core\";\nimport { GLASSTRACE_ATTRIBUTE_NAMES } from \"@glasstrace/protocol\";\nimport type { CaptureConfig } from \"@glasstrace/protocol\";\nimport type { SessionManager } from \"./session.js\";\nimport { classifyFetchTarget } from \"./fetch-classifier.js\";\n\nconst ATTR = GLASSTRACE_ATTRIBUTE_NAMES;\n\n/**\n * Sentinel value indicating the API key has not yet been resolved.\n */\nexport const API_KEY_PENDING = \"pending\" as const;\n\n/**\n * Maximum number of spans to buffer while waiting for key resolution.\n * Prevents unbounded memory growth if the key never resolves.\n */\nconst MAX_PENDING_SPANS = 1024;\n\n\n/**\n * Options for constructing a {@link GlasstraceExporter}.\n */\nexport interface GlasstraceExporterOptions {\n getApiKey: () => string;\n sessionManager: SessionManager;\n getConfig: () => CaptureConfig;\n environment: string | undefined;\n endpointUrl: string;\n createDelegate: ((url: string, headers: Record<string, string>) => SpanExporter) | null;\n}\n\ninterface PendingBatch {\n spans: ReadableSpan[];\n resultCallback: (result: ExportResult) => void;\n}\n\n/**\n * A SpanExporter that enriches spans with glasstrace.* attributes at export\n * time, then delegates to a real OTLP exporter.\n *\n * This design resolves three issues:\n * - Spans emitted before the API key resolves are buffered (not dropped)\n * and flushed once the key is available.\n * - Enrichment happens in the exporter (not onEnding), so it works\n * on Vercel where CompositeSpanProcessor does not forward onEnding().\n * - Session ID is computed at export time using the resolved API key,\n * not the \"pending\" placeholder.\n */\nexport class GlasstraceExporter implements SpanExporter {\n private readonly getApiKey: () => string;\n private readonly sessionManager: SessionManager;\n private readonly getConfig: () => CaptureConfig;\n private readonly environment: string | undefined;\n private readonly endpointUrl: string;\n private readonly createDelegateFn: ((url: string, headers: Record<string, string>) => SpanExporter) | null;\n\n private delegate: SpanExporter | null = null;\n private delegateKey: string | null = null;\n private pendingBatches: PendingBatch[] = [];\n private pendingSpanCount = 0;\n private overflowLogged = false;\n\n constructor(options: GlasstraceExporterOptions) {\n this.getApiKey = options.getApiKey;\n this.sessionManager = options.sessionManager;\n this.getConfig = options.getConfig;\n this.environment = options.environment;\n this.endpointUrl = options.endpointUrl;\n this.createDelegateFn = options.createDelegate;\n }\n\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {\n const currentKey = this.getApiKey();\n if (currentKey === API_KEY_PENDING) {\n // Buffer raw (unenriched) spans — enrichment deferred to flush time\n // so session IDs are computed with the resolved key, not \"pending\".\n this.bufferSpans(spans, resultCallback);\n return;\n }\n\n // Key is available — enrich and export\n const enrichedSpans = spans.map((span) => this.enrichSpan(span));\n const exporter = this.ensureDelegate();\n if (exporter) {\n exporter.export(enrichedSpans, resultCallback);\n } else {\n // No delegate factory — report success so the pipeline does not stall\n resultCallback({ code: 0 });\n }\n }\n\n /**\n * Called when the API key transitions from \"pending\" to a resolved value.\n * Creates the delegate exporter and flushes all buffered spans.\n */\n notifyKeyResolved(): void {\n this.flushPending();\n }\n\n async shutdown(): Promise<void> {\n const currentKey = this.getApiKey();\n if (currentKey !== API_KEY_PENDING && this.pendingBatches.length > 0) {\n this.flushPending();\n } else if (this.pendingBatches.length > 0) {\n console.warn(\n `[glasstrace] Shutdown with ${this.pendingSpanCount} buffered spans — API key never resolved, spans lost.`,\n );\n // Complete pending callbacks so pipeline doesn't hang\n for (const batch of this.pendingBatches) {\n batch.resultCallback({ code: 0 });\n }\n this.pendingBatches = [];\n this.pendingSpanCount = 0;\n }\n\n if (this.delegate) {\n return this.delegate.shutdown();\n }\n }\n\n forceFlush(): Promise<void> {\n if (this.delegate?.forceFlush) {\n return this.delegate.forceFlush();\n }\n return Promise.resolve();\n }\n\n /**\n * Enriches a ReadableSpan with all glasstrace.* attributes.\n * Returns a new ReadableSpan wrapper; the original span is not mutated.\n *\n * External function calls (getSessionId, deriveErrorCategory,\n * deriveOrmProvider, classifyFetchTarget) are individually guarded so a\n * failure in one does not prevent the remaining attributes from being set.\n * On total failure, returns the original span unchanged.\n */\n private enrichSpan(span: ReadableSpan): ReadableSpan {\n try {\n const attrs = span.attributes ?? {};\n const name = span.name ?? \"\";\n const extra: Record<string, string | number> = {};\n\n // glasstrace.trace.type\n extra[ATTR.TRACE_TYPE] = \"server\";\n\n // glasstrace.session.id — calls external code (crypto, schema validation)\n try {\n const sessionId = this.sessionManager.getSessionId(this.getApiKey());\n extra[ATTR.SESSION_ID] = sessionId;\n } catch { /* session ID omitted */ }\n\n // glasstrace.environment\n const env = this.environment ?? process.env.GLASSTRACE_ENV;\n if (env) {\n extra[ATTR.ENVIRONMENT] = env;\n }\n\n // glasstrace.correlation.id\n const existingCid = attrs[\"glasstrace.correlation.id\"];\n if (typeof existingCid === \"string\") {\n extra[ATTR.CORRELATION_ID] = existingCid;\n }\n\n // glasstrace.route\n const route =\n (attrs[\"http.route\"] as string | undefined) ?? name;\n if (route) {\n extra[ATTR.ROUTE] = route;\n }\n\n // glasstrace.http.method\n const method =\n (attrs[\"http.method\"] as string | undefined) ??\n (attrs[\"http.request.method\"] as string | undefined);\n if (method) {\n extra[ATTR.HTTP_METHOD] = method;\n }\n\n // glasstrace.http.status_code\n const statusCode =\n (attrs[\"http.status_code\"] as number | undefined) ??\n (attrs[\"http.response.status_code\"] as number | undefined);\n if (statusCode !== undefined) {\n extra[ATTR.HTTP_STATUS_CODE] = statusCode;\n }\n\n // glasstrace.http.duration_ms\n if (span.startTime && span.endTime) {\n const [startSec, startNano] = span.startTime;\n const [endSec, endNano] = span.endTime;\n const durationMs =\n (endSec - startSec) * 1000 + (endNano - startNano) / 1_000_000;\n if (durationMs >= 0) {\n extra[ATTR.HTTP_DURATION_MS] = durationMs;\n }\n }\n\n // glasstrace.error.message\n const errorMessage = attrs[\"exception.message\"] as string | undefined;\n if (errorMessage) {\n extra[ATTR.ERROR_MESSAGE] = errorMessage;\n }\n\n // glasstrace.error.code + glasstrace.error.category — calls deriveErrorCategory\n try {\n const errorType = attrs[\"exception.type\"] as string | undefined;\n if (errorType) {\n extra[ATTR.ERROR_CODE] = errorType;\n extra[ATTR.ERROR_CATEGORY] = deriveErrorCategory(errorType);\n }\n } catch { /* error category omitted */ }\n\n // glasstrace.error.field\n const errorField = attrs[\"error.field\"] as string | undefined;\n if (errorField) {\n extra[ATTR.ERROR_FIELD] = errorField;\n }\n\n // glasstrace.orm.* — calls deriveOrmProvider\n try {\n const spanAny = span as unknown as Record<string, { name?: string } | undefined>;\n const instrumentationName =\n (spanAny.instrumentationScope?.name ?? spanAny.instrumentationLibrary?.name) ?? \"\";\n const ormProvider = deriveOrmProvider(instrumentationName);\n if (ormProvider) {\n extra[ATTR.ORM_PROVIDER] = ormProvider;\n\n const model =\n (attrs[\"db.sql.table\"] as string | undefined) ??\n (attrs[\"db.prisma.model\"] as string | undefined);\n if (model) {\n extra[ATTR.ORM_MODEL] = model;\n }\n\n const operation = attrs[\"db.operation\"] as string | undefined;\n if (operation) {\n extra[ATTR.ORM_OPERATION] = operation;\n }\n }\n } catch { /* ORM attributes omitted */ }\n\n // glasstrace.fetch.target — calls classifyFetchTarget\n try {\n const url =\n (attrs[\"http.url\"] as string | undefined) ??\n (attrs[\"url.full\"] as string | undefined);\n if (url && span.kind === SpanKind.CLIENT) {\n extra[ATTR.FETCH_TARGET] = classifyFetchTarget(url);\n }\n } catch { /* fetch target omitted */ }\n\n return createEnrichedSpan(span, extra);\n } catch {\n // Return original span unchanged so the export pipeline is never blocked\n return span;\n }\n }\n\n /**\n * Lazily creates the delegate OTLP exporter once the API key is resolved.\n * Recreates the delegate if the key has changed (e.g., after key rotation)\n * so the Authorization header stays current.\n */\n private ensureDelegate(): SpanExporter | null {\n if (!this.createDelegateFn) return null;\n\n const currentKey = this.getApiKey();\n if (currentKey === API_KEY_PENDING) return null;\n\n // Recreate delegate if the key has changed since last creation\n if (this.delegate && this.delegateKey === currentKey) {\n return this.delegate;\n }\n\n // Shut down old delegate if key rotated. Catch errors to prevent\n // unhandled rejections from crashing the process during rotation.\n if (this.delegate) {\n void this.delegate.shutdown?.().catch(() => {});\n }\n\n this.delegate = this.createDelegateFn(this.endpointUrl, {\n Authorization: `Bearer ${currentKey}`,\n });\n this.delegateKey = currentKey;\n return this.delegate;\n }\n\n /**\n * Buffers raw (unenriched) spans while the API key is pending.\n * Evicts oldest batches if the buffer exceeds MAX_PENDING_SPANS.\n * Re-checks the key after buffering to close the race window where\n * the key resolves between the caller's check and this buffer call.\n */\n private bufferSpans(\n spans: ReadableSpan[],\n resultCallback: (result: ExportResult) => void,\n ): void {\n this.pendingBatches.push({ spans, resultCallback });\n this.pendingSpanCount += spans.length;\n\n // Evict oldest batches if over limit\n while (this.pendingSpanCount > MAX_PENDING_SPANS && this.pendingBatches.length > 1) {\n const evicted = this.pendingBatches.shift()!;\n this.pendingSpanCount -= evicted.spans.length;\n // Complete callback so pipeline doesn't hang\n evicted.resultCallback({ code: 0 });\n\n if (!this.overflowLogged) {\n this.overflowLogged = true;\n console.warn(\n \"[glasstrace] Pending span buffer overflow — oldest spans evicted. \" +\n \"This usually means the API key is taking too long to resolve.\",\n );\n }\n }\n\n // Re-check: if the key resolved between the caller's check and now,\n // flush immediately to avoid spans stuck in the buffer.\n if (this.getApiKey() !== API_KEY_PENDING) {\n this.flushPending();\n }\n }\n\n /**\n * Flushes all buffered spans through the delegate exporter.\n * Enriches spans at flush time (not buffer time) so that session IDs\n * are computed with the resolved API key instead of the \"pending\" sentinel.\n */\n private flushPending(): void {\n if (this.pendingBatches.length === 0) return;\n\n const exporter = this.ensureDelegate();\n if (!exporter) {\n // No delegate factory — complete callbacks and discard\n for (const batch of this.pendingBatches) {\n batch.resultCallback({ code: 0 });\n }\n this.pendingBatches = [];\n this.pendingSpanCount = 0;\n return;\n }\n\n const batches = this.pendingBatches;\n this.pendingBatches = [];\n this.pendingSpanCount = 0;\n\n for (const batch of batches) {\n // Enrich at flush time with the now-resolved key\n const enriched = batch.spans.map((span) => this.enrichSpan(span));\n exporter.export(enriched, batch.resultCallback);\n }\n }\n\n}\n\n/**\n * Creates a ReadableSpan wrapper that inherits all properties from the\n * original span but overrides `attributes` to include additional entries.\n * The original span is not mutated.\n */\nfunction createEnrichedSpan(\n span: ReadableSpan,\n extra: Record<string, string | number>,\n): ReadableSpan {\n const enrichedAttributes = { ...span.attributes, ...extra };\n return Object.create(span, {\n attributes: {\n value: enrichedAttributes,\n enumerable: true,\n },\n }) as ReadableSpan;\n}\n\n/**\n * Derives ORM provider from the instrumentation library name.\n */\nexport function deriveOrmProvider(instrumentationName: string): string | null {\n const lower = instrumentationName.toLowerCase();\n if (lower.includes(\"prisma\")) {\n return \"prisma\";\n }\n if (lower.includes(\"drizzle\")) {\n return \"drizzle\";\n }\n return null;\n}\n\n/**\n * Derives error category from error type string.\n */\nexport function deriveErrorCategory(errorType: string): string {\n const lower = errorType.toLowerCase();\n if (lower.includes(\"validation\") || lower.includes(\"zod\")) {\n return \"validation\";\n }\n if (\n lower.includes(\"network\") ||\n lower.includes(\"econnrefused\") ||\n lower.includes(\"fetch\") ||\n lower.includes(\"timeout\")\n ) {\n return \"network\";\n }\n if (lower.includes(\"auth\") || lower.includes(\"unauthorized\") || lower.includes(\"forbidden\")) {\n return \"auth\";\n }\n if (lower.includes(\"notfound\") || lower.includes(\"not_found\")) {\n return \"not-found\";\n }\n return \"internal\";\n}\n","import type { AnonApiKey, SessionId } from \"@glasstrace/protocol\";\n\n/**\n * Checks whether the given Origin header is allowed for CORS access.\n *\n * Allowed origins:\n * - `chrome-extension://*` — any Chrome extension\n * - `moz-extension://*` — any Firefox extension\n * - `safari-web-extension://*` — any Safari extension\n * - Absent origin (same-origin / non-browser request)\n *\n * Replaced wildcard `*` to prevent arbitrary websites from\n * reading the anonymous API key from localhost.\n */\nfunction isAllowedOrigin(origin: string | null): boolean {\n if (origin === null) return true;\n if (origin.startsWith(\"chrome-extension://\")) return true;\n if (origin.startsWith(\"moz-extension://\")) return true;\n if (origin.startsWith(\"safari-web-extension://\")) return true;\n return false;\n}\n\n/**\n * Builds CORS headers for a given request origin.\n * Returns headers with `Access-Control-Allow-Origin` set to the origin\n * if allowed, otherwise omits that header entirely.\n */\nfunction buildCorsHeaders(origin: string | null): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Vary: \"Origin\",\n };\n\n if (origin && isAllowedOrigin(origin)) {\n headers[\"Access-Control-Allow-Origin\"] = origin;\n }\n\n return headers;\n}\n\n/**\n * Creates a request handler for the `/__glasstrace/config` discovery endpoint.\n *\n * The returned handler checks if the request URL path is `/__glasstrace/config`.\n * If not, returns `null` (pass-through). If it matches, returns a `DiscoveryResponse`\n * with the anonymous key and current session ID.\n *\n * The triple guard (anonymous + dev + active) is enforced by the caller,\n * not by this module. If the handler is registered, it serves.\n */\nexport function createDiscoveryHandler(\n getAnonKey: () => Promise<AnonApiKey | null>,\n getSessionId: () => SessionId,\n): (request: Request) => Promise<Response | null> {\n return async (request: Request): Promise<Response | null> => {\n // Check path match\n let url: URL;\n try {\n url = new URL(request.url);\n } catch {\n return null;\n }\n\n if (url.pathname !== \"/__glasstrace/config\") {\n return null;\n }\n\n // Restrict CORS to known extension origins instead of wildcard\n const origin = request.headers.get(\"Origin\");\n const corsHeaders = buildCorsHeaders(origin);\n\n // Handle CORS preflight\n if (request.method === \"OPTIONS\") {\n return new Response(null, {\n status: 204,\n headers: {\n ...corsHeaders,\n \"Access-Control-Allow-Methods\": \"GET, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n },\n });\n }\n\n // Only allow GET requests\n if (request.method !== \"GET\") {\n return new Response(\n JSON.stringify({ error: \"method_not_allowed\" }),\n {\n status: 405,\n headers: corsHeaders,\n },\n );\n }\n\n try {\n // Get the anonymous key\n const anonKey = await getAnonKey();\n\n if (anonKey === null) {\n return new Response(\n JSON.stringify({ error: \"not_ready\" }),\n {\n status: 503,\n headers: corsHeaders,\n },\n );\n }\n\n // Get the current session ID\n const sessionId = getSessionId();\n\n return new Response(\n JSON.stringify({ key: anonKey, sessionId }),\n {\n status: 200,\n headers: corsHeaders,\n },\n );\n } catch {\n return new Response(\n JSON.stringify({ error: \"internal_error\" }),\n {\n status: 500,\n headers: corsHeaders,\n },\n );\n }\n };\n}\n\n// Exported for testing\nexport { isAllowedOrigin, buildCorsHeaders };\n","import type { ResolvedConfig } from \"./env-detection.js\";\nimport type { SessionManager } from \"./session.js\";\nimport type { SpanExporter } from \"@opentelemetry/sdk-trace-base\";\nimport { GlasstraceExporter, API_KEY_PENDING } from \"./enriching-exporter.js\";\nimport { getActiveConfig } from \"./init-client.js\";\n\n/** Module-level resolved API key, updated when the anon key resolves. */\nlet _resolvedApiKey: string = API_KEY_PENDING;\n\n/** Module-level reference to the active exporter for key-resolution notification. */\nlet _activeExporter: GlasstraceExporter | null = null;\n\n/** Registered shutdown handler, tracked so it can be removed on reset. */\nlet _shutdownHandler: ((signal: NodeJS.Signals) => void) | null = null;\n\n/**\n * Sets the resolved API key for OTel export authentication.\n * Called once the anonymous key or dev key is available.\n * @param key - The resolved API key (anonymous or developer).\n */\nexport function setResolvedApiKey(key: string): void {\n _resolvedApiKey = key;\n}\n\n/**\n * Returns the current resolved API key.\n * Returns the {@link API_KEY_PENDING} sentinel if the key has not yet resolved.\n */\nexport function getResolvedApiKey(): string {\n return _resolvedApiKey;\n}\n\n/**\n * Notifies the active exporter that the API key has transitioned from\n * \"pending\" to a resolved value. This triggers flushing of any buffered spans.\n */\nexport function notifyApiKeyResolved(): void {\n _activeExporter?.notifyKeyResolved();\n}\n\n/**\n * Resets OTel configuration state to initial values. For testing only.\n */\nexport function resetOtelConfigForTesting(): void {\n _resolvedApiKey = API_KEY_PENDING;\n _activeExporter = null;\n if (_shutdownHandler && typeof process !== \"undefined\") {\n process.removeListener(\"SIGTERM\", _shutdownHandler);\n process.removeListener(\"SIGINT\", _shutdownHandler);\n _shutdownHandler = null;\n }\n}\n\n/**\n * Dynamically imports an optional peer dependency at runtime.\n *\n * Uses `Function()` constructor to build the `import()` expression so that\n * bundlers (webpack, esbuild, turbopack) do not attempt to resolve the module\n * at compile time. This is intentional -- the SDK's peer dependencies are\n * optional, and static analysis would cause missing-module build errors for\n * users who have not installed them.\n *\n * **CSP note:** The `Function()` constructor is semantically equivalent to\n * `eval()` and will trigger Content Security Policy violations in environments\n * that disallow `unsafe-eval`. If your CSP blocks this, install the OTel peer\n * dependencies explicitly so they resolve via normal `import` statements, or\n * use the `@vercel/otel` path which does not rely on `tryImport` for its own\n * module.\n *\n * @param moduleId - The npm package name to import (e.g. \"@vercel/otel\").\n * @returns The module namespace object, or `null` if the module is not installed.\n */\nasync function tryImport(moduleId: string): Promise<Record<string, unknown> | null> {\n try {\n return await (Function(\"id\", \"return import(id)\")(moduleId) as Promise<Record<string, unknown>>);\n } catch {\n return null;\n }\n}\n\n/**\n * Registers process shutdown hooks that flush and shut down the OTel provider.\n * Uses `process.once()` to avoid stacking handlers on repeated calls.\n * Guarded for non-Node environments where `process` may not exist.\n */\nfunction registerShutdownHooks(provider: { shutdown: () => Promise<void> }): void {\n if (typeof process === \"undefined\" || typeof process.once !== \"function\") {\n return;\n }\n\n // Remove any previously registered handler before adding a new one\n if (_shutdownHandler) {\n process.removeListener(\"SIGTERM\", _shutdownHandler);\n process.removeListener(\"SIGINT\", _shutdownHandler);\n }\n\n let shutdownCalled = false;\n\n const shutdown = (signal: string) => {\n if (shutdownCalled) return;\n shutdownCalled = true;\n\n void provider.shutdown()\n .catch((err: unknown) => {\n console.warn(\n `[glasstrace] Error during OTel shutdown: ${err instanceof Error ? err.message : String(err)}`,\n );\n })\n .finally(() => {\n // Re-raise the signal so Node's default termination behavior proceeds.\n // Remove our listeners first to avoid re-entering this handler.\n process.removeListener(\"SIGTERM\", _shutdownHandler!);\n process.removeListener(\"SIGINT\", _shutdownHandler!);\n process.kill(process.pid, signal);\n });\n };\n\n const handler = (signal: NodeJS.Signals) => shutdown(signal);\n _shutdownHandler = handler;\n process.once(\"SIGTERM\", handler);\n process.once(\"SIGINT\", handler);\n}\n\n/**\n * Configures OpenTelemetry with the GlasstraceExporter.\n * The exporter handles all span enrichment (glasstrace.* attributes) at\n * export time, solving buffering, no-onEnding,\n * and session-ID-uses-resolved-key concerns.\n *\n * Attempts to use `@vercel/otel` first, falls back to bare OTel SDK.\n *\n * @param config - The resolved SDK configuration (endpoint, environment, etc.).\n * @param sessionManager - Provides session IDs for span enrichment.\n */\nexport async function configureOtel(\n config: ResolvedConfig,\n sessionManager: SessionManager,\n): Promise<void> {\n // Build OTLP exporter configuration\n const exporterUrl = `${config.endpoint}/v1/traces`;\n\n // Build the exporter factory from the optional OTLP peer dependency\n let createOtlpExporter: ((url: string, headers: Record<string, string>) => SpanExporter) | null = null;\n const otlpModule = await tryImport(\"@opentelemetry/exporter-trace-otlp-http\");\n if (otlpModule && typeof otlpModule.OTLPTraceExporter === \"function\") {\n const OTLPTraceExporter = otlpModule.OTLPTraceExporter as new (opts: {\n url: string;\n headers: Record<string, string>;\n }) => SpanExporter;\n createOtlpExporter = (url: string, headers: Record<string, string>) =>\n new OTLPTraceExporter({ url, headers });\n }\n\n // Create the GlasstraceExporter that enriches + buffers + delegates\n const glasstraceExporter = new GlasstraceExporter({\n getApiKey: getResolvedApiKey,\n sessionManager,\n getConfig: () => getActiveConfig(),\n environment: config.environment,\n endpointUrl: exporterUrl,\n createDelegate: createOtlpExporter,\n });\n _activeExporter = glasstraceExporter;\n\n // Try @vercel/otel first\n const vercelOtel = await tryImport(\"@vercel/otel\");\n if (vercelOtel && typeof vercelOtel.registerOTel === \"function\") {\n if (!createOtlpExporter) {\n console.warn(\n \"[glasstrace] @opentelemetry/exporter-trace-otlp-http not found for @vercel/otel path. Trace export disabled.\",\n );\n }\n\n const otelConfig: Record<string, unknown> = {\n serviceName: \"glasstrace-sdk\",\n traceExporter: glasstraceExporter,\n };\n\n // Try @prisma/instrumentation\n const prismaModule = await tryImport(\"@prisma/instrumentation\");\n if (prismaModule) {\n const PrismaInstrumentation = prismaModule.PrismaInstrumentation as\n (new () => unknown) | undefined;\n if (PrismaInstrumentation) {\n otelConfig.instrumentations = [new PrismaInstrumentation()];\n }\n }\n\n (vercelOtel.registerOTel as (opts: Record<string, unknown>) => void)(otelConfig);\n return;\n }\n\n // Fallback: bare OTel SDK with BasicTracerProvider\n try {\n const otelSdk = await import(\"@opentelemetry/sdk-trace-base\");\n const otelApi = await import(\"@opentelemetry/api\");\n\n // Check for an existing OTel provider before registering.\n // If another tool (Datadog, Sentry, New Relic) already registered a provider,\n // skip Glasstrace registration to avoid silently breaking their tracing.\n // OTel wraps the global provider in a ProxyTracerProvider, so we probe for a\n // real provider by requesting a tracer and checking if it's a \"ProxyTracer\"\n // (the no-op default) or a real Tracer from a registered provider.\n const existingProvider = otelApi.trace.getTracerProvider();\n const probeTracer = existingProvider.getTracer(\"glasstrace-probe\");\n if (probeTracer.constructor.name !== \"ProxyTracer\") {\n console.warn(\n \"[glasstrace] An existing OpenTelemetry TracerProvider is already registered. \" +\n \"Glasstrace will not overwrite it. To use Glasstrace alongside another \" +\n \"tracing tool, add GlasstraceExporter as an additional span processor \" +\n \"on your existing provider.\",\n );\n _activeExporter = null;\n return;\n }\n\n if (!createOtlpExporter) {\n // No OTLP exporter available -- rebuild GlasstraceExporter with a\n // ConsoleSpanExporter delegate so spans still get glasstrace.* enrichment.\n const consoleExporter = new otelSdk.ConsoleSpanExporter();\n const consoleGlasstraceExporter = new GlasstraceExporter({\n getApiKey: getResolvedApiKey,\n sessionManager,\n getConfig: () => getActiveConfig(),\n environment: config.environment,\n endpointUrl: exporterUrl,\n createDelegate: () => consoleExporter,\n });\n _activeExporter = consoleGlasstraceExporter;\n\n console.warn(\n \"[glasstrace] @opentelemetry/exporter-trace-otlp-http not found. Using ConsoleSpanExporter.\",\n );\n\n // ConsoleSpanExporter uses SimpleSpanProcessor — synchronous export is\n // acceptable for debug-only console output.\n const processor = new otelSdk.SimpleSpanProcessor(consoleGlasstraceExporter);\n const provider = new otelSdk.BasicTracerProvider({\n spanProcessors: [processor],\n });\n otelApi.trace.setGlobalTracerProvider(provider);\n registerShutdownHooks(provider);\n return;\n }\n\n // Use BatchSpanProcessor for production OTLP exports to avoid blocking\n // the event loop on every span.end() call.\n const processor = new otelSdk.BatchSpanProcessor(glasstraceExporter);\n const provider = new otelSdk.BasicTracerProvider({\n spanProcessors: [processor],\n });\n\n otelApi.trace.setGlobalTracerProvider(provider);\n registerShutdownHooks(provider);\n } catch {\n console.warn(\n \"[glasstrace] Neither @vercel/otel nor @opentelemetry/sdk-trace-base available. Tracing disabled.\",\n );\n }\n}\n","/**\n * Console error/warn capture module.\n *\n * When enabled, monkey-patches `console.error` and `console.warn` to record\n * their output as OTel span events on the currently active span. SDK-internal\n * log messages (prefixed with \"[glasstrace]\") are never captured.\n */\n\n/**\n * Module-level flag to suppress capture of SDK-internal log messages.\n * Set to `true` before calling `console.warn`/`console.error` from SDK code,\n * then reset to `false` immediately after.\n */\nexport let isGlasstraceLog = false;\n\n/** Saved reference to the original `console.error`. */\nlet originalError: typeof console.error | null = null;\n\n/** Saved reference to the original `console.warn`. */\nlet originalWarn: typeof console.warn | null = null;\n\n/** Whether the console capture is currently installed. */\nlet installed = false;\n\n/** Cached OTel API module reference, resolved at install time. */\nlet otelApi: typeof import(\"@opentelemetry/api\") | null = null;\n\n/**\n * Formats console arguments into a single string for span event attributes.\n * Uses best-effort serialization: strings pass through, Errors preserve their\n * stack trace, and other values are JSON-stringified with a String() fallback.\n */\nfunction formatArgs(args: unknown[]): string {\n return args\n .map((arg) => {\n if (typeof arg === \"string\") return arg;\n if (arg instanceof Error) return arg.stack ?? arg.message;\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n })\n .join(\" \");\n}\n\n/**\n * Returns `true` if the first argument is a string starting with \"[glasstrace]\".\n * Used to skip capture of SDK-internal log messages without requiring every\n * call site to set the `isGlasstraceLog` flag.\n */\nfunction isSdkMessage(args: unknown[]): boolean {\n return typeof args[0] === \"string\" && args[0].startsWith(\"[glasstrace]\");\n}\n\n/**\n * Installs console capture by replacing `console.error` and `console.warn`\n * with wrappers that record span events on the active OTel span.\n *\n * Must be called after OTel is configured so the API module is available.\n * Safe to call multiple times; subsequent calls are no-ops.\n */\nexport async function installConsoleCapture(): Promise<void> {\n if (installed) return;\n\n // Resolve OTel API once at install time. If unavailable, the wrappers\n // will simply call through to the original console methods.\n try {\n otelApi = await import(\"@opentelemetry/api\");\n } catch {\n otelApi = null;\n }\n\n originalError = console.error;\n originalWarn = console.warn;\n installed = true;\n\n console.error = (...args: unknown[]) => {\n // Always call the original first to preserve developer experience\n originalError!.apply(console, args);\n\n // Skip SDK-internal messages and flagged messages\n if (isGlasstraceLog || isSdkMessage(args)) return;\n\n if (otelApi) {\n const span = otelApi.trace.getSpan(otelApi.context.active());\n if (span) {\n span.addEvent(\"console.error\", {\n \"console.message\": formatArgs(args),\n });\n }\n }\n };\n\n console.warn = (...args: unknown[]) => {\n originalWarn!.apply(console, args);\n\n if (isGlasstraceLog || isSdkMessage(args)) return;\n\n if (otelApi) {\n const span = otelApi.trace.getSpan(otelApi.context.active());\n if (span) {\n span.addEvent(\"console.warn\", {\n \"console.message\": formatArgs(args),\n });\n }\n }\n };\n}\n\n/**\n * Restores the original `console.error` and `console.warn` methods.\n * Primarily intended for use in tests.\n */\nexport function uninstallConsoleCapture(): void {\n if (!installed) return;\n\n if (originalError) console.error = originalError;\n if (originalWarn) console.warn = originalWarn;\n\n originalError = null;\n originalWarn = null;\n otelApi = null;\n installed = false;\n}\n\n/**\n * Logs a message from SDK-internal code without triggering console capture.\n *\n * Use this helper in new SDK code instead of bare `console.warn(...)` calls\n * to prevent SDK log messages from being recorded as user-facing span events.\n *\n * @param level - The console log level to use.\n * @param message - The message to log.\n */\nexport function sdkLog(level: \"warn\" | \"info\" | \"error\", message: string): void {\n isGlasstraceLog = true;\n try {\n console[level](message);\n } finally {\n isGlasstraceLog = false;\n }\n}\n","/**\n * Manual error capture API.\n *\n * Provides a simple function for developers to manually record errors\n * as span events, independent of the `consoleErrors` config flag.\n */\n\n/** Lazily cached OTel API module reference. */\nlet otelApi: typeof import(\"@opentelemetry/api\") | null = null;\n\n/** Whether we've already attempted to load OTel. */\nlet otelLoadAttempted = false;\n\n/** Promise for the in-flight OTel load, if any. */\nlet otelLoadPromise: Promise<void> | null = null;\n\n/**\n * Eagerly loads the OTel API module. Call this during SDK initialization\n * so that `captureError` can resolve spans synchronously.\n *\n * @internal\n */\nexport async function _preloadOtelApi(): Promise<void> {\n if (otelLoadAttempted) return;\n otelLoadAttempted = true;\n try {\n otelApi = await import(\"@opentelemetry/api\");\n } catch {\n otelApi = null;\n }\n}\n\n/**\n * Records an error as a span event on the currently active OTel span.\n *\n * Works regardless of the `consoleErrors` configuration — this is an\n * explicit, opt-in API for manual error reporting. If no span is active\n * or OTel is not available, the call is silently ignored.\n *\n * @param error - The error to capture. Accepts `Error` objects, strings, or any value.\n *\n * @example\n * ```ts\n * import { captureError } from \"@glasstrace/sdk\";\n *\n * try {\n * await riskyOperation();\n * } catch (err) {\n * captureError(err);\n * // handle error normally...\n * }\n * ```\n */\nexport function captureError(error: unknown): void {\n // Fast path: OTel already loaded and available\n if (otelApi) {\n recordError(otelApi, error);\n return;\n }\n\n // OTel not yet available — either not attempted or still loading.\n // Queue the error to be recorded once the import resolves.\n // The span context may be lost across the async boundary, but this\n // is best-effort for calls that arrive before OTel is ready.\n if (!otelLoadAttempted) {\n otelLoadPromise ??= _preloadOtelApi();\n }\n if (otelLoadPromise) {\n void otelLoadPromise.then(() => {\n if (otelApi) {\n recordError(otelApi, error);\n }\n });\n }\n}\n\n/**\n * Adds an error event to the active span using the provided OTel API.\n */\nfunction recordError(api: typeof import(\"@opentelemetry/api\"), error: unknown): void {\n try {\n const span = api.trace.getSpan(api.context.active());\n if (!span) return;\n\n const attributes: Record<string, string> = {\n \"error.message\": String(error),\n };\n\n if (error instanceof Error) {\n attributes[\"error.type\"] = error.constructor.name;\n }\n\n span.addEvent(\"glasstrace.error\", attributes);\n } catch {\n // Silently ignore failures\n }\n}\n\n/**\n * Resets internal state. For testing only.\n * @internal\n */\nexport function _resetCaptureErrorForTesting(): void {\n otelApi = null;\n otelLoadAttempted = false;\n otelLoadPromise = null;\n}\n","declare const __SDK_VERSION__: string;\n\nimport type { GlasstraceOptions, AnonApiKey } from \"@glasstrace/protocol\";\nimport { resolveConfig, isProductionDisabled, isAnonymousMode } from \"./env-detection.js\";\nimport type { ResolvedConfig } from \"./env-detection.js\";\nimport { SessionManager } from \"./session.js\";\nimport { getOrCreateAnonKey, readAnonKey } from \"./anon-key.js\";\nimport { loadCachedConfig, performInit, _setCurrentConfig, getActiveConfig } from \"./init-client.js\";\nimport { createDiscoveryHandler } from \"./discovery-endpoint.js\";\nimport { configureOtel, setResolvedApiKey, getResolvedApiKey, notifyApiKeyResolved, resetOtelConfigForTesting } from \"./otel-config.js\";\nimport { installConsoleCapture, uninstallConsoleCapture } from \"./console-capture.js\";\nimport { _preloadOtelApi } from \"./capture-error.js\";\n\n/** Whether console capture has been installed in this registration cycle. */\nlet consoleCaptureInstalled = false;\n\n/** Module-level state tracking for the registered discovery handler. */\nlet discoveryHandler: ((request: Request) => Promise<Response | null>) | null = null;\n\n/** Module-level flag to prevent double registration. */\nlet isRegistered = false;\n\n/** Generation counter to invalidate stale background promises after reset. */\nlet registrationGeneration = 0;\n\n/**\n * The primary SDK entry point called by developers in their `instrumentation.ts`.\n * Orchestrates OTel setup, span processor, init client, anon key, and discovery endpoint.\n *\n * This function is synchronous and MUST NOT throw. The developer's server is never blocked.\n * Background work (key resolution, init call) happens via fire-and-forget promises.\n *\n * @param options - Optional SDK configuration. Environment variables are used as fallbacks.\n *\n * @example\n * ```ts\n * // instrumentation.ts\n * import { registerGlasstrace } from \"@glasstrace/sdk\";\n * registerGlasstrace(); // uses env vars\n * ```\n */\nexport function registerGlasstrace(options?: GlasstraceOptions): void {\n try {\n // Prevent double registration\n if (isRegistered) {\n return;\n }\n\n // Resolve config\n const config = resolveConfig(options);\n if (config.verbose) {\n console.info(\"[glasstrace] Config resolved.\");\n }\n\n // Production check\n if (isProductionDisabled(config)) {\n console.warn(\n \"[glasstrace] Disabled in production. Set GLASSTRACE_FORCE_ENABLE=true to override.\",\n );\n return;\n }\n if (config.verbose) {\n console.info(\"[glasstrace] Not production-disabled.\");\n }\n\n // Determine auth mode\n const anonymous = isAnonymousMode(config);\n let effectiveKey: string | undefined = config.apiKey;\n\n if (effectiveKey) {\n setResolvedApiKey(effectiveKey);\n }\n\n if (config.verbose) {\n console.info(\n `[glasstrace] Auth mode = ${anonymous ? \"anonymous\" : \"dev-key\"}.`,\n );\n }\n\n // Load cached config and apply to in-memory store\n const cachedInitResponse = loadCachedConfig();\n if (cachedInitResponse) {\n _setCurrentConfig(cachedInitResponse);\n }\n if (config.verbose) {\n console.info(\n `[glasstrace] Cached config ${cachedInitResponse ? \"loaded and applied\" : \"not found\"}.`,\n );\n }\n\n // Create SessionManager\n const sessionManager = new SessionManager();\n if (config.verbose) {\n console.info(\"[glasstrace] SessionManager created.\");\n }\n\n isRegistered = true;\n const currentGeneration = registrationGeneration;\n\n // Configure OTel IMMEDIATELY in all modes.\n // OTel is registered before the anon key resolves so that\n // spans are captured from cold start. GlasstraceExporter buffers spans\n // while the key is \"pending\" and flushes them once notifyApiKeyResolved()\n // is called after anonymous key resolution.\n // This is fire-and-forget -- OTel failure must not block init.\n void configureOtel(config, sessionManager).then(\n () => {\n // Preload OTel API for captureError() so it can resolve spans synchronously\n void _preloadOtelApi();\n\n // Check cached config for consoleErrors (may be stale or absent).\n // Re-checked after performInit completes with the authoritative config.\n maybeInstallConsoleCapture();\n\n if (config.verbose) {\n console.info(\"[glasstrace] OTel configured.\");\n }\n },\n (err: unknown) => {\n console.warn(\n `[glasstrace] Failed to configure OTel: ${err instanceof Error ? err.message : String(err)}`,\n );\n },\n );\n\n // Background work: anonymous key resolution, discovery endpoint, init\n if (anonymous) {\n // Register discovery endpoint IMMEDIATELY with async key resolution\n if (isDiscoveryEnabled(config)) {\n let resolvedAnonKey: AnonApiKey | null = null;\n const anonKeyPromise = getOrCreateAnonKey();\n\n // Use getResolvedApiKey() for session ID instead of\n // capturing the mutable effectiveKey closure variable.\n discoveryHandler = createDiscoveryHandler(\n async () => resolvedAnonKey,\n () => sessionManager.getSessionId(getResolvedApiKey()),\n );\n\n if (config.verbose) {\n console.info(\"[glasstrace] Discovery endpoint registered (key pending).\");\n }\n\n // Background: resolve key, update API key, then init\n void (async () => {\n try {\n if (currentGeneration !== registrationGeneration) return;\n\n const anonKey = await anonKeyPromise;\n resolvedAnonKey = anonKey;\n setResolvedApiKey(anonKey);\n notifyApiKeyResolved();\n effectiveKey = anonKey;\n\n if (currentGeneration !== registrationGeneration) return;\n\n // Update the discovery handler to serve the resolved key\n // Use getResolvedApiKey() for canonical key state\n discoveryHandler = createDiscoveryHandler(\n () => Promise.resolve(anonKey),\n () => sessionManager.getSessionId(getResolvedApiKey()),\n );\n\n if (config.verbose) {\n console.info(\"[glasstrace] Background init firing.\");\n }\n\n await performInit(config, anonKey, __SDK_VERSION__);\n\n // Re-check consoleErrors with the authoritative init response config\n maybeInstallConsoleCapture();\n } catch (err) {\n console.warn(\n `[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n })();\n } else {\n // Anonymous + non-dev: no discovery endpoint, just background init\n void (async () => {\n try {\n if (currentGeneration !== registrationGeneration) return;\n\n const anonKey = await getOrCreateAnonKey();\n setResolvedApiKey(anonKey);\n notifyApiKeyResolved();\n effectiveKey = anonKey;\n\n if (currentGeneration !== registrationGeneration) return;\n\n if (config.verbose) {\n console.info(\"[glasstrace] Background init firing.\");\n }\n\n await performInit(config, anonKey, __SDK_VERSION__);\n\n // Re-check consoleErrors with the authoritative init response config\n maybeInstallConsoleCapture();\n } catch (err) {\n console.warn(\n `[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n })();\n }\n } else {\n // Dev key mode: read straggler anon key, then fire init\n void (async () => {\n try {\n if (currentGeneration !== registrationGeneration) return;\n\n let anonKeyForInit: AnonApiKey | null = null;\n try {\n anonKeyForInit = await readAnonKey();\n } catch {\n // Expected when no prior anon key file exists on disk (first run).\n }\n\n if (currentGeneration !== registrationGeneration) return;\n\n if (config.verbose) {\n console.info(\"[glasstrace] Background init firing.\");\n }\n await performInit(config, anonKeyForInit, __SDK_VERSION__);\n\n // Re-check consoleErrors with the authoritative init response config\n maybeInstallConsoleCapture();\n } catch (err) {\n console.warn(\n `[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n })();\n }\n\n // Import graph (coverageMapEnabled) -- placeholder\n if (config.coverageMapEnabled && config.verbose) {\n console.info(\"[glasstrace] Import graph building skipped.\");\n }\n } catch (err) {\n console.warn(\n `[glasstrace] Registration failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\n/**\n * Returns the registered discovery handler, or null if not registered.\n */\nexport function getDiscoveryHandler(): ((request: Request) => Promise<Response | null>) | null {\n return discoveryHandler;\n}\n\n/**\n * Checks the active config and installs console capture if enabled.\n * Idempotent — safe to call multiple times (after OTel config, after init).\n * This ensures console capture is installed whenever authoritative config\n * becomes available, whether from the file cache or the init response.\n */\nfunction maybeInstallConsoleCapture(): void {\n if (consoleCaptureInstalled) return;\n if (getActiveConfig().consoleErrors) {\n consoleCaptureInstalled = true;\n void installConsoleCapture();\n }\n}\n\n/**\n * Returns `true` if the discovery endpoint should be enabled for this environment.\n *\n * Tightened from \"not production\" to explicit development conditions:\n * 1. `GLASSTRACE_DISCOVERY_ENABLED=true` -- explicit override (highest priority)\n * 2. `GLASSTRACE_DISCOVERY_ENABLED=false` -- explicit disable (highest priority)\n * 3. `NODE_ENV` is `\"development\"` or unset, AND `VERCEL_ENV` is not `\"production\"`\n *\n * Environments like `staging`, `test`, and `ci` do not expose the discovery\n * endpoint by default, since it serves the anonymous API key over CORS.\n *\n * @param config - The resolved SDK configuration.\n * @returns Whether the discovery endpoint should be registered.\n */\nfunction isDiscoveryEnabled(config: ResolvedConfig): boolean {\n // Explicit flag takes precedence\n if (process.env.GLASSTRACE_DISCOVERY_ENABLED === \"true\") return true;\n if (process.env.GLASSTRACE_DISCOVERY_ENABLED === \"false\") return false;\n\n // Block production environments\n if (config.nodeEnv === \"production\") return false;\n if (config.vercelEnv === \"production\") return false;\n\n // Only allow in development or when NODE_ENV is unset\n if (config.nodeEnv === \"development\" || config.nodeEnv === undefined) return true;\n\n return false;\n}\n\n/**\n * Resets registration state. For testing only.\n */\nexport function _resetRegistrationForTesting(): void {\n isRegistered = false;\n discoveryHandler = null;\n consoleCaptureInstalled = false;\n registrationGeneration++;\n uninstallConsoleCapture();\n resetOtelConfigForTesting();\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport { execSync } from \"node:child_process\";\nimport {\n SourceMapUploadResponseSchema,\n type SourceMapUploadResponse,\n} from \"@glasstrace/protocol\";\n\nexport interface SourceMapEntry {\n filePath: string;\n content: string;\n}\n\n/**\n * Recursively finds all .map files in the given build directory.\n * Returns relative paths and file contents.\n */\nexport async function collectSourceMaps(\n buildDir: string,\n): Promise<SourceMapEntry[]> {\n const results: SourceMapEntry[] = [];\n\n try {\n await walkDir(buildDir, buildDir, results);\n } catch {\n // Directory doesn't exist or is unreadable — return empty\n return [];\n }\n\n return results;\n}\n\nasync function walkDir(\n baseDir: string,\n currentDir: string,\n results: SourceMapEntry[],\n): Promise<void> {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(currentDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n await walkDir(baseDir, fullPath, results);\n } else if (entry.isFile() && entry.name.endsWith(\".map\")) {\n try {\n const content = await fs.readFile(fullPath, \"utf-8\");\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, \"/\");\n // Strip the trailing .map extension so the key matches the compiled\n // JS path that the runtime uses for stack-frame lookups (e.g.\n // \"static/chunks/main.js\" instead of \"static/chunks/main.js.map\").\n const compiledPath = relativePath.replace(/\\.map$/, \"\");\n results.push({ filePath: compiledPath, content });\n } catch {\n // Skip unreadable files\n }\n }\n }\n}\n\n/**\n * Computes a build hash for source map uploads.\n *\n * First tries `git rev-parse HEAD` to get the git commit SHA.\n * On failure, falls back to a deterministic content hash:\n * sort source map file paths alphabetically, concatenate each as\n * `{relativePath}\\n{fileLength}\\n{fileContent}`, then SHA-256 the result.\n */\nexport async function computeBuildHash(\n maps?: SourceMapEntry[],\n): Promise<string> {\n // Try git first\n try {\n const sha = execSync(\"git rev-parse HEAD\", { encoding: \"utf-8\" }).trim();\n if (sha) {\n return sha;\n }\n } catch {\n // Git not available, fall through to content hash\n }\n\n // Fallback: content-based hash\n const sortedMaps = [...(maps ?? [])].sort((a, b) =>\n a.filePath.localeCompare(b.filePath),\n );\n\n const hashInput = sortedMaps\n .map((m) => `${m.filePath}\\n${m.content.length}\\n${m.content}`)\n .join(\"\");\n\n const hash = crypto.createHash(\"sha256\").update(hashInput).digest(\"hex\");\n return hash;\n}\n\n/**\n * Uploads source maps to the ingestion API.\n *\n * POSTs to `{endpoint}/v1/source-maps` with the API key, build hash,\n * and file entries. Validates the response against SourceMapUploadResponseSchema.\n */\nexport async function uploadSourceMaps(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[],\n): Promise<SourceMapUploadResponse> {\n const body = {\n apiKey,\n buildHash,\n files: maps.map((m) => ({\n filePath: m.filePath,\n sourceMap: m.content,\n })),\n };\n\n const baseUrl = endpoint.replace(/\\/+$/, \"\");\n const response = await fetch(`${baseUrl}/v1/source-maps`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n // Consume the response body to release the connection back to the pool.\n // Without this, the underlying TCP socket stays allocated until GC, which\n // causes connection pool exhaustion under sustained error conditions.\n // Wrapped in try-catch so a stream error doesn't mask the HTTP status error.\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Source map upload failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return SourceMapUploadResponseSchema.parse(json);\n}\n","import { collectSourceMaps, computeBuildHash, uploadSourceMaps } from \"./source-map-uploader.js\";\n\ntype NextConfig = Record<string, unknown>;\ntype WebpackConfigFn = (config: Record<string, unknown>, context: Record<string, unknown>) => Record<string, unknown>;\n\n/** Subset of the webpack context object passed by Next.js to the webpack config function. */\ninterface WebpackContext {\n isServer: boolean;\n dev: boolean;\n [key: string]: unknown;\n}\n\n/** Minimal webpack compiler shape for the afterEmit hook. */\ninterface WebpackCompiler {\n hooks?: {\n afterEmit?: {\n tapPromise?: (name: string, fn: (compilation: unknown) => Promise<void>) => void;\n };\n };\n}\n\n/**\n * Wraps the developer's Next.js config to enable source map generation\n * and upload .map files to the ingestion API at build time.\n *\n * The build NEVER fails because of Glasstrace — all errors are caught\n * and logged as warnings.\n *\n * @param nextConfig - The developer's existing Next.js configuration object.\n * @returns A new config object with source map generation and upload enabled.\n */\nexport function withGlasstraceConfig(nextConfig: NextConfig): NextConfig {\n // Handle null/undefined gracefully\n const config: NextConfig = nextConfig != null ? { ...nextConfig } : {};\n\n // Enable server-side source map generation for Glasstrace uploads.\n // Intentionally does NOT set productionBrowserSourceMaps — that exposes\n // full source code publicly via browser DevTools. Users who want public\n // browser source maps can set it explicitly in their Next.js config.\n\n // Enable server-side source maps (Next.js experimental feature)\n const existingExperimental = (config.experimental as Record<string, unknown>) ?? {};\n config.experimental = { ...existingExperimental, serverSourceMaps: true };\n\n // Capture distDir for source map collection (default: .next)\n const distDir = typeof config.distDir === \"string\" ? config.distDir : \".next\";\n\n // Capture existing webpack config if any\n const existingWebpack = config.webpack as WebpackConfigFn | undefined;\n\n // Register webpack config modifier\n config.webpack = (\n webpackConfig: Record<string, unknown>,\n context: Record<string, unknown>,\n ): Record<string, unknown> => {\n // Call existing webpack config first\n let result = webpackConfig;\n if (typeof existingWebpack === \"function\") {\n result = existingWebpack(webpackConfig, context);\n }\n\n const webpackContext = context as WebpackContext;\n\n // Only run source map upload on client-side production builds (not server, not dev)\n if (!webpackContext.isServer && webpackContext.dev === false) {\n // Register a plugin to collect and upload source maps after compilation\n const plugins = (result.plugins as Array<Record<string, unknown>>) ?? [];\n plugins.push({\n apply(compiler: Record<string, unknown>) {\n const typedCompiler = compiler as WebpackCompiler;\n if (typedCompiler.hooks?.afterEmit?.tapPromise) {\n typedCompiler.hooks.afterEmit.tapPromise(\n \"GlasstraceSourceMapUpload\",\n async () => {\n await handleSourceMapUpload(distDir);\n },\n );\n }\n },\n });\n result.plugins = plugins;\n }\n\n return result;\n };\n\n return config;\n}\n\n/**\n * Collects source map files from the build output directory and uploads\n * them to the Glasstrace ingestion API. Never throws — all errors are\n * caught and logged as warnings so the build is never blocked.\n *\n * Exported for testing only; not part of the public API.\n *\n * @param distDir - The Next.js build output directory (e.g. \".next\").\n */\nexport async function handleSourceMapUpload(distDir: string): Promise<void> {\n try {\n const apiKey = process.env.GLASSTRACE_API_KEY;\n const endpoint =\n process.env.GLASSTRACE_ENDPOINT ?? \"https://api.glasstrace.dev\";\n\n // Anonymous mode: skip upload\n if (!apiKey || apiKey.trim() === \"\") {\n console.info(\n \"[glasstrace] Source map upload skipped (no API key). Stack traces will show compiled locations.\",\n );\n return;\n }\n\n const maps = await collectSourceMaps(distDir);\n\n if (maps.length === 0) {\n console.info(\"[glasstrace] No source map files found. Skipping upload.\");\n return;\n }\n\n const buildHash = await computeBuildHash(maps);\n\n await uploadSourceMaps(apiKey, endpoint, buildHash, maps);\n console.info(\n `[glasstrace] Uploaded ${String(maps.length)} source map(s) for build ${buildHash}.`,\n );\n } catch (error: unknown) {\n // Build must NEVER fail because of Glasstrace\n const message =\n error instanceof Error ? error.message : \"Unknown error\";\n console.warn(\n `[glasstrace] Source map upload failed: ${message}. Build continues normally.`,\n );\n }\n}\n","import * as fs from \"node:fs/promises\";\nimport * as fsSync from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport { createBuildHash, type ImportGraphPayload } from \"@glasstrace/protocol\";\n\n/** Maximum number of test files to process to prevent runaway in large projects */\nconst MAX_TEST_FILES = 5000;\n\n/** Directories to exclude from test file discovery */\nconst EXCLUDED_DIRS = new Set([\"node_modules\", \".next\", \".git\", \"dist\", \".turbo\"]);\n\n/** Conventional test file patterns */\nconst DEFAULT_TEST_PATTERNS = [\n /\\.test\\.tsx?$/,\n /\\.spec\\.tsx?$/,\n];\n\n/**\n * Converts a glob pattern (e.g. \"e2e/**\\/*.ts\") to an anchored RegExp.\n * Uses a placeholder to avoid `*` replacement corrupting the `**\\/` output.\n *\n * @param glob - A file glob pattern such as \"src/**\\/*.test.ts\".\n * @returns A RegExp that matches paths against the glob from start to end.\n */\nfunction globToRegExp(glob: string): RegExp {\n const DOUBLE_STAR_PLACEHOLDER = \"\\0DSTAR\\0\";\n const regexStr = glob\n .replace(/\\*\\*\\//g, DOUBLE_STAR_PLACEHOLDER) // protect **/ first\n .replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\") // escape all regex metacharacters (except *)\n .replace(/\\*/g, \"[^/]+\")\n .replace(new RegExp(DOUBLE_STAR_PLACEHOLDER.replace(/\\0/g, \"\\\\0\"), \"g\"), \"(?:.+/)?\");\n return new RegExp(\"^\" + regexStr + \"$\");\n}\n\n/**\n * Attempts to read include patterns from vitest.config.*, vite.config.*,\n * or jest.config.* files. Returns additional RegExp patterns extracted\n * from the config, or an empty array if no config is found or parsing fails.\n * This is best-effort — it reads the config as text and extracts patterns\n * via regex, without evaluating the JS.\n *\n * For Vitest/Vite configs, looks for `test.include` arrays.\n * For Jest configs, looks for `testMatch` arrays.\n * Does not support `testRegex` (string-based Jest pattern) — that is\n * left as future work.\n */\nfunction loadCustomTestPatterns(projectRoot: string): RegExp[] {\n const configNames = [\n \"vitest.config.ts\",\n \"vitest.config.js\",\n \"vitest.config.mts\",\n \"vitest.config.mjs\",\n \"vite.config.ts\",\n \"vite.config.js\",\n \"vite.config.mts\",\n \"vite.config.mjs\",\n \"jest.config.ts\",\n \"jest.config.js\",\n \"jest.config.mts\",\n \"jest.config.mjs\",\n ];\n\n for (const name of configNames) {\n const configPath = path.join(projectRoot, name);\n let content: string;\n try {\n content = fsSync.readFileSync(configPath, \"utf-8\");\n } catch {\n // Config file does not exist at this path — try next candidate\n continue;\n }\n\n try {\n const isJest = name.startsWith(\"jest.\");\n let includeMatch: RegExpExecArray | null = null;\n\n if (isJest) {\n // Jest: look for testMatch: [...]\n includeMatch = /testMatch\\s*:\\s*\\[([^\\]]*)\\]/s.exec(content);\n } else {\n // Vitest/Vite: look for `test` block's `include` to avoid\n // matching `coverage.include` or other unrelated arrays.\n // Strategy: find `test` property, then look for `include` within\n // the next ~500 chars (heuristic to stay within the test block).\n const testBlockMatch = /\\btest\\s*[:{]\\s*/s.exec(content);\n if (testBlockMatch) {\n const afterTest = content.slice(testBlockMatch.index, testBlockMatch.index + 500);\n includeMatch = /include\\s*:\\s*\\[([^\\]]*)\\]/s.exec(afterTest);\n }\n }\n\n if (!includeMatch) {\n continue;\n }\n\n const arrayContent = includeMatch[1];\n const stringRegex = /['\"]([^'\"]+)['\"]/g;\n const patterns: RegExp[] = [];\n let match: RegExpExecArray | null;\n match = stringRegex.exec(arrayContent);\n while (match !== null) {\n patterns.push(globToRegExp(match[1]));\n match = stringRegex.exec(arrayContent);\n }\n\n if (patterns.length > 0) {\n return patterns;\n }\n } catch {\n // Regex-based config parsing failed — fall through to next config file\n continue;\n }\n }\n\n return [];\n}\n\n/**\n * Discovers test files by scanning the project directory for conventional\n * test file patterns. Also reads vitest/jest config files for custom include\n * patterns and merges them with the defaults. Excludes node_modules/ and .next/.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns Relative POSIX paths from projectRoot, capped at {@link MAX_TEST_FILES}.\n */\nexport async function discoverTestFiles(\n projectRoot: string,\n): Promise<string[]> {\n const customPatterns = loadCustomTestPatterns(projectRoot);\n const testPatterns = [...DEFAULT_TEST_PATTERNS, ...customPatterns];\n const results: string[] = [];\n\n try {\n await walkForTests(projectRoot, projectRoot, results, testPatterns);\n } catch {\n // Project root directory does not exist or is unreadable — return empty\n return [];\n }\n\n return results.slice(0, MAX_TEST_FILES);\n}\n\n/** Recursively walks directories, collecting test file paths into `results`. */\nasync function walkForTests(\n baseDir: string,\n currentDir: string,\n results: string[],\n testPatterns: RegExp[],\n): Promise<void> {\n if (results.length >= MAX_TEST_FILES) {\n return;\n }\n\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(currentDir, { withFileTypes: true });\n } catch {\n // Directory is unreadable (permissions, broken symlink) — skip subtree\n return;\n }\n\n for (const entry of entries) {\n if (results.length >= MAX_TEST_FILES) {\n return;\n }\n\n const fullPath = path.join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n if (EXCLUDED_DIRS.has(entry.name)) {\n continue;\n }\n await walkForTests(baseDir, fullPath, results, testPatterns);\n } else if (entry.isFile()) {\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, \"/\");\n\n // Check if it matches test patterns or is in __tests__\n const isTestFile =\n testPatterns.some((p) => p.test(entry.name) || p.test(relativePath)) ||\n relativePath.includes(\"__tests__\");\n\n if (isTestFile && (entry.name.endsWith(\".ts\") || entry.name.endsWith(\".tsx\"))) {\n results.push(relativePath);\n }\n }\n }\n}\n\n/**\n * Extracts import paths from file content using regex.\n * Handles ES module imports, CommonJS requires, and dynamic imports.\n *\n * @param fileContent - The full text content of a TypeScript/JavaScript file.\n * @returns An array of import path strings as written in the source (e.g. \"./foo\", \"react\").\n */\nexport function extractImports(fileContent: string): string[] {\n const seen = new Set<string>();\n const imports: string[] = [];\n\n /** Adds a path to the result if not already present. */\n const addUnique = (importPath: string): void => {\n if (!seen.has(importPath)) {\n seen.add(importPath);\n imports.push(importPath);\n }\n };\n\n // ES module: import ... from 'path' or import 'path'\n const esImportRegex = /import\\s+(?:(?:[\\w*{}\\s,]+)\\s+from\\s+)?['\"]([^'\"]+)['\"]/g;\n let match: RegExpExecArray | null;\n\n match = esImportRegex.exec(fileContent);\n while (match !== null) {\n addUnique(match[1]);\n match = esImportRegex.exec(fileContent);\n }\n\n // CommonJS: require('path')\n const requireRegex = /require\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n match = requireRegex.exec(fileContent);\n while (match !== null) {\n addUnique(match[1]);\n match = requireRegex.exec(fileContent);\n }\n\n // Dynamic import: import('path')\n const dynamicImportRegex = /import\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n match = dynamicImportRegex.exec(fileContent);\n while (match !== null) {\n addUnique(match[1]);\n match = dynamicImportRegex.exec(fileContent);\n }\n\n return imports;\n}\n\n/**\n * Builds an import graph mapping test file paths to their imported module paths.\n *\n * Discovers test files, reads each, extracts imports, and builds a graph.\n * Computes a deterministic buildHash from the serialized graph content.\n * Individual file read failures are silently skipped.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns An {@link ImportGraphPayload} containing the graph and a deterministic buildHash.\n */\nexport async function buildImportGraph(\n projectRoot: string,\n): Promise<ImportGraphPayload> {\n const testFiles = await discoverTestFiles(projectRoot);\n const graph: Record<string, string[]> = {};\n\n for (const testFile of testFiles) {\n const fullPath = path.join(projectRoot, testFile);\n try {\n const content = await fs.readFile(fullPath, \"utf-8\");\n const imports = extractImports(content);\n graph[testFile] = imports;\n } catch {\n // File is unreadable (permissions, deleted between discovery and read) — skip\n continue;\n }\n }\n\n // Compute deterministic build hash from graph content\n const sortedKeys = Object.keys(graph).sort();\n const serialized = sortedKeys\n .map((key) => `${key}:${JSON.stringify(graph[key])}`)\n .join(\"\\n\");\n const hashHex = crypto\n .createHash(\"sha256\")\n .update(serialized)\n .digest(\"hex\");\n const buildHash = createBuildHash(hashHex);\n\n return { buildHash, graph };\n}\n"],"mappings":"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","names":["cachedPort","cachedPort","import_protocol","import_promises","import_node_path","import_protocol","GLASSTRACE_DIR","import_protocol","otelApi","processor","provider","otelApi","import_protocol","fs","path","crypto","import_protocol"]}
|