@syntrologie/runtime-sdk 2.2.0-canary.9 → 2.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.
@@ -5,6 +5,7 @@
5
5
  * and use the TelemetryClient interface.
6
6
  */
7
7
  import type { PostHog, Properties } from 'posthog-js';
8
+ import type { ConsentGate } from '../consent';
8
9
  import type { CanvasSurface, TelemetryClient } from '../types';
9
10
  export interface PostHogAdapterOptions {
10
11
  /**
@@ -58,6 +59,18 @@ export interface PostHogAdapterOptions {
58
59
  * Used to feed events into the EventBus for runtime decisions.
59
60
  */
60
61
  onCapture?: (eventName: string, properties?: Record<string, unknown>) => void;
62
+ /**
63
+ * Consent gate for GDPR compliance.
64
+ * When provided with requireExplicitConsent, PostHog initialization is
65
+ * deferred until consent is granted.
66
+ */
67
+ consent?: ConsentGate;
68
+ /**
69
+ * Require explicit consent before initializing PostHog.
70
+ * When true, PostHog will not init until consent.grant() is called.
71
+ * @default false
72
+ */
73
+ requireExplicitConsent?: boolean;
61
74
  }
62
75
  interface CanvasAnalyticsPayload extends Properties {
63
76
  rectangleId?: string;
@@ -71,7 +84,13 @@ export declare class PostHogAdapter implements TelemetryClient {
71
84
  private client?;
72
85
  private featureFlagsCallback?;
73
86
  private captureCallback?;
87
+ private consentUnsub?;
74
88
  constructor(options?: PostHogAdapterOptions);
89
+ /**
90
+ * Initialize the PostHog client. Called immediately (no consent gate)
91
+ * or deferred (when consent gate grants).
92
+ */
93
+ private initPostHog;
75
94
  /**
76
95
  * Get all feature flags from PostHog.
77
96
  * Used to extract segment membership flags (in_segment_*).
@@ -0,0 +1,62 @@
1
+ /**
2
+ * ConsentGate - GDPR-compliant consent management for telemetry.
3
+ *
4
+ * Gates PostHog (and any future telemetry provider) initialization on
5
+ * user consent status. Supports:
6
+ * - Explicit consent requirement (opt-in model)
7
+ * - GTM Consent Mode v2 integration (reads analytics_storage from dataLayer)
8
+ * - Programmatic grant/deny API for host page consent banners
9
+ *
10
+ * The ConsentGate lives in the runtime (not the PostHog adapter) because
11
+ * consent is provider-agnostic — if we switch from PostHog to Segment or
12
+ * another provider, consent logic should not change.
13
+ */
14
+ export type ConsentStatus = 'granted' | 'denied' | 'pending';
15
+ export interface ConsentConfig {
16
+ /** Initial consent status. Default: 'pending' */
17
+ initialStatus?: ConsentStatus;
18
+ /** If true, telemetry is blocked until grant() is called. Default: false */
19
+ requireExplicitConsent?: boolean;
20
+ /** Read consent state from GTM's window.dataLayer consent events. Default: false */
21
+ readFromGtmConsentMode?: boolean;
22
+ /** Called whenever consent status changes. */
23
+ onConsentChange?: (status: ConsentStatus) => void;
24
+ }
25
+ type ConsentListener = (status: ConsentStatus) => void;
26
+ export declare class ConsentGate {
27
+ private status;
28
+ private listeners;
29
+ private configCallback?;
30
+ private gtmEnabled;
31
+ private destroyed;
32
+ constructor(config?: ConsentConfig);
33
+ /**
34
+ * Grant consent — enables telemetry collection.
35
+ */
36
+ grant(): void;
37
+ /**
38
+ * Deny consent — disables telemetry collection.
39
+ */
40
+ deny(): void;
41
+ /**
42
+ * Get the current consent status.
43
+ */
44
+ getStatus(): ConsentStatus;
45
+ /**
46
+ * Subscribe to consent status changes.
47
+ * Returns an unsubscribe function.
48
+ */
49
+ subscribe(listener: ConsentListener): () => void;
50
+ /**
51
+ * Poll GTM's dataLayer for consent state.
52
+ * Scans for the most recent consent update event with analytics_storage.
53
+ */
54
+ pollGtmConsent(): void;
55
+ /**
56
+ * Clean up listeners and stop responding to changes.
57
+ */
58
+ destroy(): void;
59
+ private setStatus;
60
+ private notify;
61
+ }
62
+ export {};
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.2.0-canary.9";
13
+ export declare const SDK_VERSION = "2.2.0";
@@ -68,11 +68,16 @@ export interface MountedWidgetHandle {
68
68
  * Allows apps to register custom widgets that can be rendered via
69
69
  * mountWidget actions or directly through the Surfaces system.
70
70
  */
71
+ export type WidgetRegistryListener = (event: {
72
+ type: 'registered' | 'unregistered';
73
+ widgetId: string;
74
+ }) => void;
71
75
  export declare class WidgetRegistry {
72
76
  private widgets;
73
77
  private mountedWidgets;
74
78
  private mountIdCounter;
75
79
  private runtimeRef?;
80
+ private listeners;
76
81
  /**
77
82
  * Bind a runtime reference so it can be injected into widget mount() calls.
78
83
  * Uses `unknown` to avoid circular imports (WidgetRegistry ← runtime.ts).
@@ -133,6 +138,11 @@ export declare class WidgetRegistry {
133
138
  * Get all widgets from a specific source.
134
139
  */
135
140
  getBySource(source: string): WidgetRegistration[];
141
+ /**
142
+ * Subscribe to widget registration changes.
143
+ */
144
+ subscribe(callback: WidgetRegistryListener): () => void;
145
+ private notify;
136
146
  /**
137
147
  * Clean up all mounted widgets.
138
148
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syntrologie/runtime-sdk",
3
- "version": "2.2.0-canary.9",
3
+ "version": "2.2.0",
4
4
  "description": "Syntrologie Runtime SDK for web experimentation and analytics",
5
5
  "license": "Proprietary",
6
6
  "private": false,
@@ -35,12 +35,16 @@
35
35
  "./cdn": {
36
36
  "import": "./dist/smart-canvas.esm.js",
37
37
  "require": "./dist/smart-canvas.js"
38
+ },
39
+ "./build-plugin": {
40
+ "import": "./scripts/syntroReactPlugin.mjs"
38
41
  }
39
42
  },
40
43
  "files": [
41
44
  "dist",
42
45
  "schema",
43
46
  "scripts/validate-config.mjs",
47
+ "scripts/syntroReactPlugin.mjs",
44
48
  "CAPABILITIES.md"
45
49
  ],
46
50
  "scripts": {
@@ -63,12 +67,12 @@
63
67
  "@floating-ui/dom": "^1.7.5",
64
68
  "@growthbook/growthbook": "~1.6.2",
65
69
  "@growthbook/growthbook-react": "^1.6.4",
66
- "@syntrologie/adapt-chatbot": "2.2.0-canary.9",
67
- "@syntrologie/adapt-content": "2.2.0-canary.9",
68
- "@syntrologie/adapt-faq": "2.2.0-canary.9",
69
- "@syntrologie/adapt-gamification": "2.2.0-canary.9",
70
- "@syntrologie/adapt-nav": "2.2.0-canary.9",
71
- "@syntrologie/adapt-overlays": "2.2.0-canary.9",
70
+ "@syntrologie/adapt-chatbot": "2.2.0",
71
+ "@syntrologie/adapt-content": "2.2.0",
72
+ "@syntrologie/adapt-faq": "2.2.0",
73
+ "@syntrologie/adapt-gamification": "2.2.0",
74
+ "@syntrologie/adapt-nav": "2.2.0",
75
+ "@syntrologie/adapt-overlays": "2.2.0",
72
76
  "posthog-js": "~1.302.2",
73
77
  "zod": "^3.25.76"
74
78
  },
@@ -0,0 +1,113 @@
1
+ /**
2
+ * syntroReactPlugin — esbuild plugin for shared React via SynOS
3
+ *
4
+ * Replaces `import { useState } from 'react'` (and react-dom, jsx-runtime)
5
+ * with lazy wrappers that resolve to SynOS.React / SynOS.ReactDOM at call
6
+ * time. This means:
7
+ * - Adaptive modules load and self-register without React being available
8
+ * - React resolves when components actually render
9
+ * - The runtime SDK sets SynOS.React at module scope before any render
10
+ *
11
+ * Used by: build-cdn.js, build-adaptives-only.js, editor-sdk/build.js
12
+ */
13
+ export const syntroReactPlugin = {
14
+ name: 'syntro-react',
15
+ setup(build) {
16
+ build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, (args) => ({
17
+ path: args.path,
18
+ namespace: 'syntro-react',
19
+ }));
20
+
21
+ build.onLoad({ filter: /.*/, namespace: 'syntro-react' }, (args) => {
22
+ if (args.path === 'react/jsx-runtime' || args.path === 'react/jsx-dev-runtime') {
23
+ return {
24
+ contents: `
25
+ function _R() {
26
+ return (typeof SynOS !== 'undefined' && SynOS.React) || {};
27
+ }
28
+ function _jsx(type, props, key) {
29
+ var R = _R();
30
+ var p = props || {};
31
+ var c = p.children;
32
+ delete p.children;
33
+ if (key !== undefined) p.key = key;
34
+ return Array.isArray(c)
35
+ ? R.createElement.apply(null, [type, p].concat(c))
36
+ : c !== undefined
37
+ ? R.createElement(type, p, c)
38
+ : R.createElement(type, p);
39
+ }
40
+ export var jsx = _jsx;
41
+ export var jsxs = _jsx;
42
+ export var Fragment = _R().Fragment;
43
+ `,
44
+ loader: 'js',
45
+ };
46
+ }
47
+ if (args.path === 'react') {
48
+ // Hooks and creation APIs are lazy function wrappers — they resolve
49
+ // SynOS.React at call time (during render), not at module load.
50
+ //
51
+ // Component types (Fragment, Suspense, etc.) are resolved at module
52
+ // evaluation via _R(). This is fine because the runtime SDK always
53
+ // loads before adaptive modules evaluate, so SynOS.React is set.
54
+ // ES module named exports can't be truly lazy (no getter support).
55
+ return {
56
+ contents: `
57
+ function _R() {
58
+ return (typeof SynOS !== 'undefined' && SynOS.React) || {};
59
+ }
60
+
61
+ // Default export — lazy proxy for React.* access
62
+ export default new Proxy({}, { get: function(_, k) { return _R()[k]; } });
63
+
64
+ // Hooks — lazy function wrappers (resolve at call time)
65
+ export function useState() { return _R().useState.apply(null, arguments); }
66
+ export function useEffect() { return _R().useEffect.apply(null, arguments); }
67
+ export function useMemo() { return _R().useMemo.apply(null, arguments); }
68
+ export function useCallback() { return _R().useCallback.apply(null, arguments); }
69
+ export function useRef() { return _R().useRef.apply(null, arguments); }
70
+ export function useContext() { return _R().useContext.apply(null, arguments); }
71
+ export function useReducer() { return _R().useReducer.apply(null, arguments); }
72
+ export function useLayoutEffect() { return _R().useLayoutEffect.apply(null, arguments); }
73
+ export function useId() { return _R().useId.apply(null, arguments); }
74
+
75
+ // Creation APIs — lazy function wrappers
76
+ export function createElement() { return _R().createElement.apply(null, arguments); }
77
+ export function createContext() { return _R().createContext.apply(null, arguments); }
78
+ export function forwardRef() { return _R().forwardRef.apply(null, arguments); }
79
+ export function memo() { return _R().memo.apply(null, arguments); }
80
+ export function lazy() { return _R().lazy.apply(null, arguments); }
81
+ export function isValidElement() { return _R().isValidElement.apply(null, arguments); }
82
+ export function cloneElement() { return _R().cloneElement.apply(null, arguments); }
83
+
84
+ // Component types — resolved at module eval (runtime loads first)
85
+ var _r = _R();
86
+ export var Fragment = _r.Fragment;
87
+ export var Suspense = _r.Suspense;
88
+ export var Children = _r.Children;
89
+ export var Component = _r.Component;
90
+ export var PureComponent = _r.PureComponent;
91
+ `,
92
+ loader: 'js',
93
+ };
94
+ }
95
+ if (args.path === 'react-dom' || args.path.startsWith('react-dom/')) {
96
+ return {
97
+ contents: `
98
+ function _RD() {
99
+ return (typeof SynOS !== 'undefined' && SynOS.ReactDOM) || {};
100
+ }
101
+ export default new Proxy({}, { get: function(_, k) { return _RD()[k]; } });
102
+ export function createRoot() { return _RD().createRoot.apply(null, arguments); }
103
+ export function hydrateRoot() { return _RD().hydrateRoot.apply(null, arguments); }
104
+ export function createPortal() { return _RD().createPortal.apply(null, arguments); }
105
+ export function flushSync() { return _RD().flushSync.apply(null, arguments); }
106
+ `,
107
+ loader: 'js',
108
+ };
109
+ }
110
+ return { contents: 'export default {};', loader: 'js' };
111
+ });
112
+ },
113
+ };