@syntrologie/runtime-sdk 2.8.0-canary.183 → 2.8.0-canary.185

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.
@@ -73,6 +73,20 @@ export interface PostHogAdapterOptions {
73
73
  * @default false
74
74
  */
75
75
  requireExplicitConsent?: boolean;
76
+ /**
77
+ * PostHog cookieless tracking mode. Captures events with a privacy-
78
+ * preserving session-scoped hash instead of a persistent cookie.
79
+ *
80
+ * - 'always': never use cookies; always cookieless
81
+ * - 'on_reject': use cookies until consent is rejected, then cookieless
82
+ *
83
+ * Recommended pairing for the SDK consent layer: `'on_reject'` so
84
+ * baseline behavioral telemetry flows for all visitors regardless
85
+ * of consent state, with full identified tracking only after grant.
86
+ *
87
+ * @default undefined (cookies used unconditionally)
88
+ */
89
+ cookieless_mode?: 'always' | 'on_reject';
76
90
  /**
77
91
  * Enable behavioral signal detection (hesitation, rage click, etc.)
78
92
  * Pass `true` for defaults, or a partial DetectorConfig to customize thresholds.
@@ -128,6 +142,18 @@ export declare class PostHogAdapter implements TelemetryClient {
128
142
  getSegmentFlags(): Record<string, boolean>;
129
143
  identify(id: string, props?: Properties): void;
130
144
  alias(id: string, aliasId: string): void;
145
+ /**
146
+ * Opt the current visitor INTO capturing. Drives the granted-state
147
+ * transition from the consent gate. When previously cookieless or
148
+ * opted-out, switches to full identified capture.
149
+ */
150
+ optInCapturing(): void;
151
+ /**
152
+ * Opt the current visitor OUT of capturing. Drives the denied-state
153
+ * transition from the consent gate. Stops sending events; existing
154
+ * cookieless/anonymous events remain in the data warehouse.
155
+ */
156
+ optOutCapturing(): void;
131
157
  track(eventName: string, payload?: CanvasAnalyticsPayload): void;
132
158
  trackCanvasOpened(surface: CanvasSurface): void;
133
159
  trackCanvasClosed(surface: CanvasSurface): void;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * ConsentDetector — orchestrates ConsentAdapters.
3
+ *
4
+ * Probes adapters in priority order. First adapter whose `detect()`
5
+ * returns true wins; its state changes are pushed into the gate. If
6
+ * no adapter matches, applies the configured fallback.
7
+ *
8
+ * See docs/plans/current/2026-05-04-sdk-default-instrumentation-spec.md
9
+ * Section X.
10
+ */
11
+ import type { ConsentGate } from './ConsentGate';
12
+ import type { DetectorOptions } from './types';
13
+ export declare class ConsentDetector {
14
+ private readonly options;
15
+ constructor(options: DetectorOptions);
16
+ /**
17
+ * Start the detector. Probes adapters in array order; first match wins.
18
+ * Returns a teardown function that unsubscribes the active adapter.
19
+ *
20
+ * Async because adapters may need async initialization. Bootstrap
21
+ * should NOT await this — feature flags and anonymous telemetry
22
+ * should keep flowing while the detector probes.
23
+ */
24
+ start(gate: ConsentGate): Promise<() => void>;
25
+ private resolveFallback;
26
+ }
@@ -21,6 +21,16 @@ export interface ConsentConfig {
21
21
  readFromGtmConsentMode?: boolean;
22
22
  /** Called whenever consent status changes. */
23
23
  onConsentChange?: (status: ConsentStatus) => void;
24
+ /**
25
+ * Resolves what 'pending' should be treated as for users who have not
26
+ * explicitly granted/denied (no banner interaction yet).
27
+ *
28
+ * For ROW (rest of world): typically returns 'granted' (opt-out regimes).
29
+ * For EU/UK/CH: returns 'denied' (explicit-opt-in regimes).
30
+ *
31
+ * Called lazily by `resolvePending()`. If not provided, defaults to 'denied'.
32
+ */
33
+ pendingResolver?: () => Promise<ConsentStatus>;
24
34
  }
25
35
  type ConsentListener = (status: ConsentStatus) => void;
26
36
  export declare class ConsentGate {
@@ -29,6 +39,7 @@ export declare class ConsentGate {
29
39
  private configCallback?;
30
40
  private gtmEnabled;
31
41
  private destroyed;
42
+ private pendingResolverFn?;
32
43
  constructor(config?: ConsentConfig);
33
44
  /**
34
45
  * Grant consent — enables telemetry collection.
@@ -47,6 +58,27 @@ export declare class ConsentGate {
47
58
  * Returns an unsubscribe function.
48
59
  */
49
60
  subscribe(listener: ConsentListener): () => void;
61
+ /**
62
+ * Subscribe to the next definitive (granted | denied) status change,
63
+ * then auto-unsubscribe. If status is already definitive, fires
64
+ * synchronously (next microtask) with current status.
65
+ *
66
+ * Used by D-2 (URL-param identify) to defer the identify() call
67
+ * until consent is decided.
68
+ */
69
+ subscribeOnce(listener: ConsentListener): () => void;
70
+ /**
71
+ * Resolve what the current 'pending' state should be treated as,
72
+ * via the configured pendingResolver. Returns the current status
73
+ * directly if it's already 'granted' or 'denied'.
74
+ *
75
+ * Defaults to 'denied' if no resolver is configured (conservative).
76
+ */
77
+ resolvePending(): Promise<ConsentStatus>;
78
+ /**
79
+ * Public setter — used by ConsentDetector / adapters to push state changes.
80
+ */
81
+ setStatus(newStatus: ConsentStatus): void;
50
82
  /**
51
83
  * Poll GTM's dataLayer for consent state.
52
84
  * Scans for the most recent consent update event with analytics_storage.
@@ -56,7 +88,7 @@ export declare class ConsentGate {
56
88
  * Clean up listeners and stop responding to changes.
57
89
  */
58
90
  destroy(): void;
59
- private setStatus;
91
+ private applyStatus;
60
92
  private notify;
61
93
  }
62
94
  export {};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * GTM Consent Mode v2 adapter.
3
+ *
4
+ * Reads `analytics_storage` from window.dataLayer consent events.
5
+ * Universal de facto standard — most CMPs (Cookiebot, OneTrust,
6
+ * Iubenda, Termly, etc.) push to dataLayer.
7
+ *
8
+ * See docs/plans/current/2026-05-04-sdk-default-instrumentation-spec.md
9
+ * Section Y.
10
+ */
11
+ import type { ConsentStatus } from '../ConsentGate';
12
+ import type { ConsentAdapter } from '../types';
13
+ export declare class GtmConsentModeAdapter implements ConsentAdapter {
14
+ readonly name = "gtm-consent-mode-v2";
15
+ detect(): boolean;
16
+ initialize(callback: (status: ConsentStatus) => void): () => void;
17
+ private readCurrentState;
18
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * IAB TCF v2 adapter.
3
+ *
4
+ * Reads consent state from window.__tcfapi — the IAB Transparency &
5
+ * Consent Framework standard. Used by all major EU-focused CMPs:
6
+ * OneTrust, Cookiebot, Iubenda, Didomi, Sourcepoint, etc.
7
+ *
8
+ * Maps Purpose 1 (storage access) + Purpose 8 (measure content
9
+ * performance, i.e. analytics) → granted/denied. Both must be granted
10
+ * for analytics tracking to be allowed under TCF.
11
+ *
12
+ * See docs/plans/current/2026-05-04-sdk-default-instrumentation-spec.md
13
+ * Section W.
14
+ */
15
+ import type { ConsentStatus } from '../ConsentGate';
16
+ import type { ConsentAdapter } from '../types';
17
+ export declare class IabTcfAdapter implements ConsentAdapter {
18
+ readonly name = "iab-tcf-v2";
19
+ detect(): boolean;
20
+ initialize(callback: (status: ConsentStatus) => void): () => void;
21
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shopify Customer Privacy adapter.
3
+ *
4
+ * Authoritative on Shopify storefronts. Uses Shopify's built-in
5
+ * customer-privacy API which combines analytics/marketing/preferences/
6
+ * sale-of-data consents into a single boolean via userCanBeTracked().
7
+ *
8
+ * See docs/plans/current/2026-05-04-sdk-default-instrumentation-spec.md
9
+ * Section Z.
10
+ */
11
+ import type { ConsentStatus } from '../ConsentGate';
12
+ import type { ConsentAdapter } from '../types';
13
+ export declare class ShopifyCustomerPrivacyAdapter implements ConsentAdapter {
14
+ readonly name = "shopify-customer-privacy";
15
+ detect(): boolean;
16
+ initialize(callback: (status: ConsentStatus) => void): () => void;
17
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Consent management — public exports.
3
+ *
4
+ * See:
5
+ * - ConsentGate: the runtime state machine for granted/denied/pending
6
+ * - ConsentDetector: orchestrates adapters that detect CMPs on the page
7
+ * - ConsentAdapter: the interface adapters implement
8
+ *
9
+ * Architecture per docs/plans/current/2026-05-04-sdk-default-instrumentation-spec.md.
10
+ */
11
+ export { GtmConsentModeAdapter } from './adapters/gtmConsentMode';
12
+ export { IabTcfAdapter } from './adapters/iabTcf';
13
+ export { ShopifyCustomerPrivacyAdapter } from './adapters/shopify';
14
+ export { ConsentDetector } from './ConsentDetector';
15
+ export type { ConsentConfig, ConsentStatus } from './ConsentGate';
16
+ export { ConsentGate } from './ConsentGate';
17
+ export type { ConsentAdapter, DetectorOptions } from './types';
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Public types for the consent detection framework.
3
+ *
4
+ * See docs/plans/current/2026-05-04-sdk-default-instrumentation-spec.md
5
+ * Section X for the design rationale.
6
+ */
7
+ import type { ConsentStatus } from './ConsentGate';
8
+ export interface ConsentAdapter {
9
+ /** Stable identifier — used in telemetry / logs. */
10
+ readonly name: string;
11
+ /**
12
+ * Synchronous detection: is this CMP currently present on the page?
13
+ * Must be cheap and never throw. Just check for global presence
14
+ * (e.g. `typeof window.Shopify?.customerPrivacy !== 'undefined'`).
15
+ */
16
+ detect(): boolean;
17
+ /**
18
+ * Subscribe to consent state. Returns immediately with a teardown
19
+ * function. The callback fires:
20
+ * - Once with the current best-known state (could be 'pending' if
21
+ * adapter needs an async handshake).
22
+ * - Whenever the state changes (user accepts/rejects, etc).
23
+ *
24
+ * Adapters that need async initialization (e.g. Shopify's
25
+ * loadFeatures) should fire 'pending' synchronously and update via
26
+ * the callback when the async work finishes.
27
+ */
28
+ initialize(callback: (status: ConsentStatus) => void): () => void;
29
+ }
30
+ export interface DetectorOptions {
31
+ /**
32
+ * Adapters in priority order. First adapter whose `detect()` returns
33
+ * true wins.
34
+ */
35
+ adapters: ConsentAdapter[];
36
+ /**
37
+ * Optional callback fired once when the detector picks a winning
38
+ * adapter (or determines no adapter matched). Use for telemetry.
39
+ */
40
+ onAdapterSelected?: (name: string | null) => void;
41
+ /**
42
+ * Status to apply when no adapter matches. Typically wired to a
43
+ * geo-aware function ("granted" outside EU, "denied" inside).
44
+ * Defaults to 'pending' if not provided.
45
+ */
46
+ fallbackStatus?: () => ConsentStatus | Promise<ConsentStatus>;
47
+ }
@@ -75,4 +75,28 @@ export interface TelemetryClient {
75
75
  * Used by audit events to send named events to PostHog.
76
76
  */
77
77
  track?(eventName: string, properties?: Record<string, unknown>): void;
78
+ /**
79
+ * Identify the current visitor with a stable ID.
80
+ * Merges anonymous events into the new person record. Idempotent
81
+ * for the same id. See SDK defaults spec D-1 / D-2.
82
+ */
83
+ identify?(distinctId: string, properties?: Record<string, unknown>): void;
84
+ /**
85
+ * Alias an existing identity to another id (treat as same person).
86
+ * Used when a previously-identified visitor reveals an additional id
87
+ * in the same session (e.g., logs in as bolshoifish then enters
88
+ * parker.lowrey at checkout). PostHog merges both ids into one
89
+ * person record.
90
+ */
91
+ alias?(newDistinctId: string, oldDistinctId?: string): void;
92
+ /**
93
+ * Switch from cookieless / opted-out mode to full identified capture.
94
+ * Driven by the ConsentGate when status transitions to 'granted'.
95
+ */
96
+ optInCapturing?(): void;
97
+ /**
98
+ * Switch off all capturing. Driven by the ConsentGate when status
99
+ * transitions to 'denied'.
100
+ */
101
+ optOutCapturing?(): void;
78
102
  }
package/dist/version.d.ts CHANGED
@@ -10,4 +10,4 @@
10
10
  *
11
11
  * @since 2.0.0
12
12
  */
13
- export declare const SDK_VERSION = "2.8.0-canary.183";
13
+ export declare const SDK_VERSION = "2.8.0-canary.185";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syntrologie/runtime-sdk",
3
- "version": "2.8.0-canary.183",
3
+ "version": "2.8.0-canary.185",
4
4
  "description": "Syntrologie Runtime SDK for web experimentation and analytics",
5
5
  "license": "Proprietary",
6
6
  "private": false,
@@ -39,15 +39,13 @@
39
39
  "files": [
40
40
  "dist",
41
41
  "schema",
42
- "scripts/validate-config.mjs",
43
- "CAPABILITIES.md"
42
+ "scripts/validate-config.mjs"
44
43
  ],
45
44
  "scripts": {
46
45
  "clean": "rm -rf dist",
47
- "aggregate-capabilities": "node ./scripts/aggregate-capabilities.mjs",
48
46
  "generate-schema": "node ./scripts/generate-unified-schema.mjs",
49
47
  "generate-schema:check": "node ./scripts/generate-unified-schema.mjs --check",
50
- "build": "npm run aggregate-capabilities && npm run build:types && npm run build:lib && npm run generate-schema && npm run build:cdn && npm run build:adaptives",
48
+ "build": "npm run build:types && npm run build:lib && npm run generate-schema && npm run build:cdn && npm run build:adaptives",
51
49
  "build:types": "tsc --project tsconfig.build.json",
52
50
  "build:lib": "node ./scripts/build-lib.mjs",
53
51
  "build:cdn": "node ./scripts/build-cdn.js",