@syntrologie/runtime-sdk 2.17.0 → 2.19.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.
@@ -0,0 +1,51 @@
1
+ import type { CounterSignal, HealthReporterConfig, HistogramSignal, ReporterRuntimeBinding } from './types';
2
+ export declare class HealthReporter {
3
+ private readonly config;
4
+ private readonly resource;
5
+ private readonly flushIntervalMs;
6
+ private readonly counters;
7
+ private readonly histograms;
8
+ private windowStartMs;
9
+ private timer;
10
+ private pagehideHandler;
11
+ private runtime;
12
+ /** Cleanup function for adapters that registered side effects (e.g. the
13
+ * web-vitals long-task observer). Called from `stop()` so SPA re-init
14
+ * via `initHealthReporter` doesn't accumulate observers. */
15
+ private adapterCleanup;
16
+ constructor(config: HealthReporterConfig);
17
+ /**
18
+ * Bind runtime-derived attribute resolvers. Called after the canvas/telemetry
19
+ * are fully initialized. Must never throw — resolvers are wrapped in
20
+ * null-safe access at flush time.
21
+ */
22
+ bindRuntime(binding: ReporterRuntimeBinding): void;
23
+ /**
24
+ * Register a cleanup function to run on `stop()`. Used by adapters
25
+ * (e.g. webVitalsAdapter) that need to release per-bind resources
26
+ * like PerformanceObservers. Replaces any prior cleanup — only one
27
+ * adapter cleanup is tracked.
28
+ */
29
+ attachAdapterCleanup(cleanup: () => void): void;
30
+ increment(signal: CounterSignal, by?: number): void;
31
+ /**
32
+ * @internal — exposed for test inspection only. Mid-window state is intentionally
33
+ * unstable; consumers should subscribe to flush payloads instead.
34
+ */
35
+ snapshotCounters(): Partial<Record<CounterSignal, number>>;
36
+ recordHistogram(signal: HistogramSignal, valueMs: number): void;
37
+ /**
38
+ * @internal — exposed for test inspection only. Mid-window state is intentionally
39
+ * unstable; consumers should subscribe to flush payloads instead.
40
+ */
41
+ snapshotHistograms(): Partial<Record<HistogramSignal, {
42
+ count: number;
43
+ p50: number;
44
+ p95: number;
45
+ }>>;
46
+ flush(): Promise<void>;
47
+ start(): void;
48
+ stop(): void;
49
+ }
50
+ export declare function initHealthReporter(config: HealthReporterConfig): HealthReporter;
51
+ export declare function getHealthReporter(): HealthReporter | null;
@@ -0,0 +1,5 @@
1
+ export { getHealthReporter, HealthReporter, initHealthReporter } from './healthReporter';
2
+ export { createOtlpEmitter } from './otlpEmitter';
3
+ export { getSdkScriptUrl, getSdkScriptUrlPrefix } from './sdkScriptUrl';
4
+ export type { CounterSignal, FlushPayload, HealthReporterConfig, HistogramSignal, ReporterResource, } from './types';
5
+ export { bindWebVitals } from './webVitalsAdapter';
@@ -0,0 +1,39 @@
1
+ import type { FlushPayload } from './types';
2
+ interface AnyValue {
3
+ stringValue?: string;
4
+ intValue?: number;
5
+ doubleValue?: number;
6
+ }
7
+ interface KV {
8
+ key: string;
9
+ value: AnyValue;
10
+ }
11
+ interface OtlpEnvelope {
12
+ resourceLogs: Array<{
13
+ resource: {
14
+ attributes: KV[];
15
+ };
16
+ scopeLogs: Array<{
17
+ scope: {
18
+ name: string;
19
+ };
20
+ logRecords: Array<{
21
+ timeUnixNano: string;
22
+ observedTimeUnixNano: string;
23
+ severityText: 'INFO';
24
+ body: AnyValue;
25
+ attributes: KV[];
26
+ }>;
27
+ }>;
28
+ }>;
29
+ }
30
+ /**
31
+ * @internal — exposed for unit testing only. Production code should use createOtlpEmitter.
32
+ */
33
+ export declare function buildOtlpPayload(flush: FlushPayload): OtlpEnvelope;
34
+ export interface OtlpEmitterConfig {
35
+ endpoint: string;
36
+ fetchImpl?: typeof fetch;
37
+ }
38
+ export declare function createOtlpEmitter(cfg: OtlpEmitterConfig): (p: FlushPayload) => Promise<void>;
39
+ export {};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The full URL of the SDK script, captured at module load. Returns
3
+ * `undefined` when the SDK is bundled into the host app (no separate
4
+ * `<script>` tag) or when running outside a browser.
5
+ */
6
+ export declare function getSdkScriptUrl(): string | undefined;
7
+ /**
8
+ * Directory prefix of the SDK script URL — useful for matching attribution
9
+ * entries from `PerformanceLongTaskTiming` against the SDK bundle and any
10
+ * sibling chunks (source maps, lazy-loaded adaptives) served from the same
11
+ * directory.
12
+ *
13
+ * Example: `https://cdn.syntrologie.com/runtime-sdk/v2/canary/smart-canvas.min.js`
14
+ * → `https://cdn.syntrologie.com/runtime-sdk/v2/canary/`
15
+ */
16
+ export declare function getSdkScriptUrlPrefix(): string | undefined;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Names of signals the SDK emits. Add new entries as the taxonomy grows.
3
+ * Counter signals are unbounded integers; histogram signals are millisecond floats.
4
+ */
5
+ export type CounterSignal = 'bootstrap_ok' | 'bootstrap_errors' | 'flag_fetch_count' | 'flag_fetch_errors' | 'longtasks_self_count'
6
+ /** Cumulative blocking time (ms) from long tasks attributed to the SDK
7
+ * script. Counter rather than histogram so HogQL sum() across windows
8
+ * gives the total — which is what alerts/dashboards actually want. */
9
+ | 'longtasks_self_blocking_ms';
10
+ /**
11
+ * Histogram signals. Web vitals are field-RUM measurements from the customer's
12
+ * browser, not synthetic Lighthouse audits — same metrics Lighthouse reports
13
+ * as "Core Web Vitals from CrUX," but measured live.
14
+ *
15
+ * - lcp_ms — Largest Contentful Paint
16
+ * - inp_ms — Interaction to Next Paint
17
+ * - cls_score — Cumulative Layout Shift (unitless 0..1+)
18
+ * - fcp_ms — First Contentful Paint
19
+ * - ttfb_ms — Time to First Byte
20
+ */
21
+ export type HistogramSignal = 'flag_fetch_latency_ms' | 'lcp_ms' | 'inp_ms' | 'cls_score' | 'fcp_ms' | 'ttfb_ms';
22
+ /**
23
+ * Resource attributes for the OTLP payload. The static fields are set at
24
+ * reporter init; the dynamic fields are resolved at flush time via runtime
25
+ * binding (see HealthReporter.bindRuntime).
26
+ */
27
+ export interface ReporterResource {
28
+ serviceName: 'syntro-runtime-sdk';
29
+ serviceVersion: string;
30
+ /** Customer's PostHog project SDK key. Identifies which PostHog project
31
+ * this telemetry corresponds to. The exporter joins on this. */
32
+ posthogKey: string;
33
+ /** Page origin where the SDK booted (e.g. https://customer.com). */
34
+ hostOrigin: string;
35
+ }
36
+ /**
37
+ * Runtime-derived attributes resolved at flush time. The HealthReporter
38
+ * binds resolvers via bindRuntime() once the canvas/telemetry are ready.
39
+ * Before bindRuntime is called, defaults apply.
40
+ */
41
+ export interface ReporterRuntimeBinding {
42
+ /** PostHog session ID. Returns empty string before telemetry is ready. */
43
+ getSessionId(): string;
44
+ /** Surface context snapshot. Returns null before context is ready. */
45
+ getSurface(): {
46
+ type: 'web' | 'telegram' | 'mcp-app';
47
+ host: string;
48
+ device: 'mobile' | 'tablet' | 'desktop';
49
+ mode: 'agent' | 'mobile' | 'desktop';
50
+ } | null;
51
+ }
52
+ /**
53
+ * In-memory aggregate produced at flush time. Resource is the snapshot
54
+ * of static + runtime-bound fields at flush moment.
55
+ */
56
+ export interface FlushPayload {
57
+ resource: ReporterResource & {
58
+ sessionId: string;
59
+ surfaceType: 'web' | 'telegram' | 'mcp-app' | 'unknown';
60
+ surfaceHost: string;
61
+ surfaceDevice: 'mobile' | 'tablet' | 'desktop' | 'unknown';
62
+ surfaceMode: 'agent' | 'mobile' | 'desktop' | 'unknown';
63
+ };
64
+ windowSeconds: number;
65
+ windowStartUnixMs: number;
66
+ counters: Partial<Record<CounterSignal, number>>;
67
+ /** Histograms expose count, p50, p95 — raw samples are NOT shipped. */
68
+ histograms: Partial<Record<HistogramSignal, {
69
+ count: number;
70
+ p50: number;
71
+ p95: number;
72
+ }>>;
73
+ }
74
+ /** Configuration for the reporter singleton. */
75
+ export interface HealthReporterConfig {
76
+ resource: ReporterResource;
77
+ flushIntervalMs?: number;
78
+ emit: (payload: FlushPayload) => Promise<void>;
79
+ }
@@ -0,0 +1,22 @@
1
+ import type { HealthReporter } from './healthReporter';
2
+ /**
3
+ * Bind the web-vitals callbacks to the given HealthReporter. Each metric
4
+ * fires once per session per metric (with possible BFCache reactivations).
5
+ *
6
+ * Idempotent across `Syntro.init()` re-calls: web-vitals subscriptions are
7
+ * registered once per page; later binds re-route to the most recent
8
+ * reporter via a window-scoped pointer. Long-task observers are per-bind
9
+ * and disposed via the returned cleanup function.
10
+ *
11
+ * Long-task tracking is opt-in via `attributedScriptUrlPrefix`: when the
12
+ * browser exposes long-task attribution and the offending script's URL
13
+ * starts with this prefix, the long task is counted toward the SDK's own
14
+ * blocking time. Pass the SDK script URL prefix (typically the CDN
15
+ * directory the bundle was loaded from).
16
+ *
17
+ * Safe to call in non-browser environments — returns a no-op cleanup
18
+ * function when `window` is unavailable.
19
+ */
20
+ export declare function bindWebVitals(reporter: HealthReporter, options?: {
21
+ attributedScriptUrlPrefix?: string;
22
+ }): () => void;