@gamaro.io/widget 0.6.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,78 @@
1
+ /**
2
+ * Iframe loader with timeout + retry (issue #867).
3
+ *
4
+ * Promotes a skeleton placeholder into an operational iframe with a per-attempt
5
+ * timeout and a configurable backoff between retries. Single responsibility —
6
+ * the host (`VersiqWidget`) keeps the application FSM, this module owns the
7
+ * mechanics of getting the iframe up.
8
+ *
9
+ * Success criterion: iframe `load` event AND a `versiq:ready` postMessage from
10
+ * the embed React app. The AND is mandatory — a 5xx Next page rendered inside
11
+ * the iframe still fires `load`; relying on that alone would mask deployment
12
+ * failures as silent successes.
13
+ *
14
+ * Source: Harel, *Statecharts: A Visual Formalism for Complex Systems*, 1987
15
+ * (FSM); Akkoyunlu et al., *Some Constraints and Tradeoffs in the Design of
16
+ * Network Communications*, SOSP 1975 (Two-Generals handshake); Martin,
17
+ * *Clean Architecture* §7 (SRP).
18
+ */
19
+ /** Per-attempt timeout (ms). */
20
+ export declare const IFRAME_LOAD_TIMEOUT_MS = 5000;
21
+ /**
22
+ * Backoff delays between retries (ms). Length determines the number of retries
23
+ * (total attempts = length + 1). [1s, 3s] covers the 5-15s Docker redeploy
24
+ * window observed on this project without exceeding the visitor patience
25
+ * threshold (19s wall-time worst case). Aligned with `resolveConfig()` which
26
+ * already does a 1s retry on the config fetch. No jitter — single client per
27
+ * page, no thundering-herd risk.
28
+ */
29
+ export declare const IFRAME_LOAD_BACKOFF_MS: readonly [1000, 3000];
30
+ export type IframeLoadFailureReason = "timeout" | "error";
31
+ export type IframeLoadState = {
32
+ kind: "loading";
33
+ attempt: number;
34
+ } | {
35
+ kind: "loaded";
36
+ iframe: HTMLIFrameElement;
37
+ } | {
38
+ kind: "retrying";
39
+ attempt: number;
40
+ reason: IframeLoadFailureReason;
41
+ } | {
42
+ kind: "failed";
43
+ reason: IframeLoadFailureReason;
44
+ attempts: number;
45
+ };
46
+ export interface IframeLoaderOptions {
47
+ /** Factory producing a fresh iframe on every attempt. */
48
+ buildIframe: () => HTMLIFrameElement;
49
+ /** DOM node the iframe will be appended to. */
50
+ container: HTMLElement;
51
+ /** Allowed origins for the `versiq:ready` ack (passed to `isValidOrigin`). */
52
+ allowedOrigins: string[];
53
+ /** Per-attempt timeout in ms. Defaults to `IFRAME_LOAD_TIMEOUT_MS`. */
54
+ timeoutMs?: number;
55
+ /** Backoff delays between retries; length = number of retries. */
56
+ backoffMs?: readonly number[];
57
+ /** Notified on every FSM transition. */
58
+ onStateChange?: (state: IframeLoadState) => void;
59
+ /** Abort signal — rejects pending attempt and cleans up. */
60
+ signal?: AbortSignal;
61
+ }
62
+ export declare class IframeLoadError extends Error {
63
+ readonly reason: IframeLoadFailureReason;
64
+ readonly attempts: number;
65
+ constructor(reason: IframeLoadFailureReason, attempts: number);
66
+ }
67
+ /**
68
+ * Load an iframe with timeout + retry. Resolves with the live iframe on
69
+ * success; throws `IframeLoadError` (reason + final attempt count) on final
70
+ * failure, or `DOMException("AbortError")` when `opts.signal` aborts.
71
+ *
72
+ * The `versiq:ready` message is observed via a LOCAL listener that is detached
73
+ * the moment the handshake completes — the caller is expected to attach its
74
+ * own applicative listener AFTER this resolves, and to manually set
75
+ * `isReady = true` + send any post-ready commands, since the embed React app
76
+ * only emits `versiq:ready` once on mount.
77
+ */
78
+ export declare function loadIframeWithRetry(opts: IframeLoaderOptions): Promise<HTMLIFrameElement>;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Versiq Widget SDK
3
+ *
4
+ * Entry point for the widget SDK. Supports both auto-initialization from
5
+ * script tags and programmatic usage.
6
+ *
7
+ * Auto-init usage (canonical integrator surface — #726):
8
+ * ```html
9
+ * <script
10
+ * src="https://unpkg.com/@gamaro.io/widget@latest/dist/widget.umd.js"
11
+ * data-api-key="pk_live_xxx">
12
+ * </script>
13
+ * ```
14
+ *
15
+ * The integrator's HTML carries only the publishable key (plus optional
16
+ * host-context attributes like `data-container` / `data-debug` / identity
17
+ * fields). Theme, position, language, showProfile, and the default open
18
+ * state are configured from the admin portal (`/portal/widget`) and
19
+ * resolved at load time via the publishable key — editing them from
20
+ * HTML is no longer supported. The legacy `data-theme` / `data-position` /
21
+ * `data-language` / `data-show-profile` / `data-open` attributes are
22
+ * silently ignored.
23
+ *
24
+ * Programmatic usage (internal dev surface — admin preview, marketing
25
+ * demo — not recommended for partner integrations):
26
+ * ```javascript
27
+ * import { createWidget } from '@gamaro.io/widget';
28
+ *
29
+ * const widget = createWidget({ apiKey: 'pk_live_xxx' });
30
+ * widget.on('qualified', () => sendToAnalytics()); // signal only, no data
31
+ * ```
32
+ *
33
+ * Programmatic `theme` / `position` / ... overrides are preserved for the
34
+ * admin sandbox's live-preview use case (client-wins merge). Partner
35
+ * integrators should configure those fields from the admin portal.
36
+ */
37
+ import { VersiqWidget, createWidget } from "./loader";
38
+ import type { WidgetConfig } from "./types";
39
+ export { createWidget, VersiqWidget };
40
+ export { parseScriptAttributes as __parseScriptAttributes };
41
+ export { getCurrentScript as __getCurrentScript };
42
+ export { autoInit as __autoInit };
43
+ export { themeConfigSchema, widgetPositionSchema, widgetConfigSchema, b2bProfileSchema, widgetMessageSchema, applicationConfigResponseSchema, } from "./types";
44
+ export type { WidgetConfig, WidgetPosition, ColorScheme, ThemeConfig, B2BProfile, WidgetProfile, WidgetMessage, WidgetEvent, WidgetEventType, WidgetEventHandler, VersiqAPI, ApplicationConfigResponse, } from "./types";
45
+ export { fetchApplicationConfig, ConfigFetchError } from "./config";
46
+ /**
47
+ * Parse script tag data attributes to extract widget config (#726).
48
+ *
49
+ * Only host-context attributes are read here — fields that cannot live
50
+ * server-side because they are tied to the integrator's page (DOM
51
+ * selectors, dev flags, HMAC-verified identity). Theme, position,
52
+ * language, showProfile, and the default open state are resolved from
53
+ * the admin portal via `fetchApplicationConfig` in `loader.ts`.
54
+ */
55
+ declare function parseScriptAttributes(script: HTMLScriptElement): WidgetConfig | null;
56
+ /**
57
+ * Find the current script tag.
58
+ */
59
+ declare function getCurrentScript(): HTMLScriptElement | null;
60
+ /**
61
+ * Initialize widgets from script tags with data-api-key attributes.
62
+ * Supports multiple widgets on the same page (multi-instance).
63
+ */
64
+ declare function autoInit(): void;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Widget Loader
3
+ *
4
+ * Creates and manages the widget iframe, handling:
5
+ * - iFrame injection and positioning
6
+ * - postMessage communication
7
+ * - Public API exposure
8
+ */
9
+ import type { WidgetConfig, WidgetEventType, WidgetEventHandler, VersiqAPI, ColorScheme, ThemeConfig } from "./types";
10
+ export declare function __resetInstanceCounter(): void;
11
+ export declare class VersiqWidget implements VersiqAPI {
12
+ readonly version = "0.1.0";
13
+ /**
14
+ * Original config as passed by the caller — preserved separately from
15
+ * `this.config` (which has defaults merged on top) so the server-config
16
+ * merge in `resolveConfig()` can tell "field not set by the caller,
17
+ * use the server value" from "caller explicitly passed a default".
18
+ */
19
+ private originalConfig;
20
+ private config;
21
+ private iframe;
22
+ private container;
23
+ private skeletonButton;
24
+ private isOpen;
25
+ private isReady;
26
+ private isInitialized;
27
+ private isDestroyed;
28
+ private fetchAbortController;
29
+ private iframeLoadAbortController;
30
+ private _applicationId;
31
+ private vertical;
32
+ private instanceId;
33
+ private pendingCommands;
34
+ private eventHandlers;
35
+ private cleanupListener;
36
+ private debugLog;
37
+ private currentViewportMode;
38
+ private mobileMediaQuery;
39
+ private tabletMediaQuery;
40
+ private debounceTimer;
41
+ private viewportChangeHandler;
42
+ get applicationId(): string;
43
+ constructor(config: WidgetConfig);
44
+ /**
45
+ * Minimal placeholder launcher rendered synchronously before the server
46
+ * config arrives. Uses the default position (bottom-right) unless the
47
+ * caller explicitly provided one. Replaced by the real iframe in
48
+ * `init()` after `resolveConfig()` succeeds.
49
+ */
50
+ private renderSkeleton;
51
+ /**
52
+ * Insert the skeleton button into the given container. Extracted so the
53
+ * post-config `init()` can re-create the skeleton inside a fresh container
54
+ * (positioned per the server config) and keep it visible across iframe
55
+ * load + retries (#867 — the skeleton survives until either the iframe
56
+ * resolves or the loader exhausts retries, at which point it mutates to
57
+ * the "unavailable" state).
58
+ */
59
+ private renderSkeletonInto;
60
+ /**
61
+ * Mutate the skeleton to its degraded "unavailable" state (#867). Called
62
+ * when `loadIframeWithRetry` reports a final `failed` transition.
63
+ *
64
+ * Snap the opacity instantly (no 300ms fade) — by the time this fires
65
+ * the user has already waited 9-19s and a slow fade would feel like
66
+ * the widget is still working (Nielsen heuristic #1 — visibility of
67
+ * system status: degraded states must be unambiguous). The
68
+ * `data-state="unavailable"` attribute also gives integrators a CSS
69
+ * hook to override the visual treatment without touching the SDK.
70
+ * No-op in inline mode (the skeleton is never rendered there — the
71
+ * integrator owns the UI).
72
+ */
73
+ private setSkeletonStateUnavailable;
74
+ /**
75
+ * Resolve the widget runtime config from the server (#726). Retries once
76
+ * with a 1s backoff on *transient* failures (network, timeout, 5xx) —
77
+ * 4xx and parse errors are permanent config/setup problems that a
78
+ * retry cannot fix, so we surface them immediately. If both attempts
79
+ * fail, the skeleton stays visible and the widget emits `error` —
80
+ * visible degraded mode rather than a silently absent widget.
81
+ */
82
+ private resolveConfig;
83
+ private isTransientFetchError;
84
+ /**
85
+ * Merge the server-resolved application config into the widget state
86
+ * and promote the skeleton to a fully-initialised iframe.
87
+ *
88
+ * Merge semantics (#726):
89
+ * 1. Fields explicitly passed to `createWidget(...)` win (`originalConfig`).
90
+ * This keeps the admin sandbox's live preview working: unsaved edits
91
+ * stay visible on the preview widget.
92
+ * 2. Otherwise the server's `widget` block (from the publishable key
93
+ * lookup) provides theme/position/language/showProfile/open.
94
+ * 3. Legacy `server.theme` is still merged underneath `server.widget.theme`
95
+ * for backward compatibility with pre-#726 data shapes — it will
96
+ * be removed once all consumers land the new contract.
97
+ */
98
+ private applyServerConfig;
99
+ private flushPendingCommands;
100
+ private init;
101
+ /**
102
+ * Strip any path from `baseUrl` so the resulting string matches the
103
+ * `event.origin` field of a `versiq:ready` postMessage (which is always
104
+ * scheme + host + port, no path). Falls back to the raw value if parsing
105
+ * fails so a malformed config does not silently break the loader gate.
106
+ */
107
+ private resolveReadyOrigin;
108
+ /** Insert the active container in the host DOM at the configured slot. */
109
+ private attachContainerToDom;
110
+ private createContainer;
111
+ private createIframe;
112
+ private buildIframeUrl;
113
+ private setupViewportListener;
114
+ private handleViewportChange;
115
+ private getViewportMode;
116
+ /**
117
+ * Re-apply the container resize transition, gated on the host's current
118
+ * reduced-motion preference. Called before every geometry change (open/close
119
+ * AND viewport-mode change) so a mid-session OS reduced-motion toggle is
120
+ * honoured on whichever path runs next, not only on open/close.
121
+ */
122
+ private applyContainerTransition;
123
+ private updateContainerForViewport;
124
+ private handleMessage;
125
+ private sendConfig;
126
+ private updateVisibility;
127
+ private emit;
128
+ open(): void;
129
+ close(): void;
130
+ reset(): void;
131
+ identify(params: {
132
+ email: string;
133
+ userId?: string;
134
+ userHash: string;
135
+ }): void;
136
+ setTheme(theme: ThemeConfig): void;
137
+ setColorScheme(scheme: ColorScheme): void;
138
+ on<T = unknown>(event: WidgetEventType, handler: WidgetEventHandler<T>): void;
139
+ off<T = unknown>(event: WidgetEventType, handler: WidgetEventHandler<T>): void;
140
+ destroy(): void;
141
+ private log;
142
+ }
143
+ /**
144
+ * Create a new widget instance.
145
+ */
146
+ export declare function createWidget(config: WidgetConfig): VersiqWidget;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Widget Logger
3
+ *
4
+ * Lightweight logger for the widget SDK with consistent prefix.
5
+ * Provides a centralized logging interface for debug, info, warn, and error levels.
6
+ */
7
+ /**
8
+ * Log an error message.
9
+ */
10
+ export declare function logError(msg: string, ...args: unknown[]): void;
11
+ /**
12
+ * Log a warning message.
13
+ */
14
+ export declare function logWarn(msg: string, ...args: unknown[]): void;
15
+ /**
16
+ * Log an info message.
17
+ */
18
+ export declare function logInfo(msg: string, ...args: unknown[]): void;
19
+ /**
20
+ * Log a debug message.
21
+ */
22
+ export declare function logDebug(msg: string, ...args: unknown[]): void;
23
+ /**
24
+ * Create a conditional debug logger based on debug flag.
25
+ */
26
+ export declare function createDebugLogger(enabled: boolean): typeof logDebug;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * postMessage Protocol
3
+ *
4
+ * Handles secure bidirectional communication between the parent page
5
+ * and the widget iframe using the postMessage API.
6
+ */
7
+ import type { WidgetCommand, WidgetEvent } from "./types";
8
+ /**
9
+ * Validate that a message origin is trusted.
10
+ *
11
+ * Exact match only — no subdomain wildcarding. Every real caller compares
12
+ * against a single origin derived from the widget's own configured `baseUrl`
13
+ * (`resolveReadyOrigin()`), never a family of subdomains, so a suffix match
14
+ * only widens the trusted surface without covering a legitimate case.
15
+ */
16
+ export declare function isValidOrigin(origin: string, allowedOrigins: string[]): boolean;
17
+ /**
18
+ * Check if an event contains a valid Versiq message.
19
+ */
20
+ export declare function isVersiqMessage(event: MessageEvent): event is MessageEvent<WidgetCommand | WidgetEvent>;
21
+ /**
22
+ * Send a command to the widget iframe.
23
+ */
24
+ export declare function sendCommand(iframe: HTMLIFrameElement, command: WidgetCommand, targetOrigin: string): void;
25
+ /**
26
+ * Send an event to the parent window.
27
+ * Used by the embedded widget to notify the parent of state changes.
28
+ */
29
+ export declare function sendEvent(event: WidgetEvent, targetOrigin: string): void;
30
+ export interface MessageListenerOptions {
31
+ /** Allowed origins for incoming messages */
32
+ allowedOrigins?: string[];
33
+ /** Callback for received messages */
34
+ onMessage: (message: WidgetCommand | WidgetEvent, origin: string) => void;
35
+ /** Enable debug logging */
36
+ debug?: boolean;
37
+ /** Only accept messages from this window (for multi-instance isolation) */
38
+ sourceWindow?: Window | null;
39
+ }
40
+ /**
41
+ * Create a message listener with origin validation.
42
+ * Returns a cleanup function to remove the listener.
43
+ */
44
+ export declare function createMessageListener(options: MessageListenerOptions): () => void;
45
+ /**
46
+ * Encode theme config for URL parameter.
47
+ */
48
+ export declare function encodeTheme(theme: object): string;
49
+ /**
50
+ * Decode theme config from URL parameter.
51
+ */
52
+ export declare function decodeTheme<T>(encoded: string): T | null;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Widget SDK Types
3
+ *
4
+ * Cross-workspace contracts (viewport, theme, widget config, application
5
+ * config, postMessage protocol) now live in `@gamaro.io/core-types` — this file
6
+ * re-exports them so existing consumers (`apps/marketing`,
7
+ * `apps/app/src/components/portal/sandbox`) keep working unchanged. The only
8
+ * types still defined locally are widget-specific SDK surfaces (B2B profile
9
+ * schemas, `VersiqAPI`, `window` augmentation).
10
+ */
11
+ import { z } from "zod";
12
+ import type { ColorScheme, ThemeConfig, WidgetEventHandler, WidgetEventType } from "@gamaro.io/core-types";
13
+ export { applicationConfigResponseSchema, breakpointsConfigSchema, MESSAGE_TYPES, messageRoleSchema, resolveTheme, responsiveConfigSchema, themeConfigSchema, viewportModeSchema, widgetConfigSchema, widgetMessageSchema, widgetPositionSchema, } from "@gamaro.io/core-types";
14
+ export type { ApplicationConfigResponse, BreakpointsConfig, ColorScheme, MessageRole, MessageType, ResolvedTheme, ResponsiveConfig, ThemeConfig, ThemeDefaults, ViewportMode, WidgetCommand, WidgetConfig, WidgetEvent, WidgetEventHandler, WidgetEventType, WidgetMessage, WidgetPosition, WidgetProfile, WidgetUnavailablePayload, } from "@gamaro.io/core-types";
15
+ /**
16
+ * User type enum.
17
+ */
18
+ export declare const b2bUserTypeSchema: z.ZodEnum<{
19
+ prospect: "prospect";
20
+ partner: "partner";
21
+ unknown: "unknown";
22
+ }>;
23
+ /**
24
+ * Business sector enum.
25
+ */
26
+ export declare const b2bSectorSchema: z.ZodEnum<{
27
+ ecommerce: "ecommerce";
28
+ "real-estate": "real-estate";
29
+ automotive: "automotive";
30
+ travel: "travel";
31
+ recruitment: "recruitment";
32
+ other: "other";
33
+ }>;
34
+ /**
35
+ * Company size enum.
36
+ */
37
+ export declare const b2bCompanySizeSchema: z.ZodEnum<{
38
+ startup: "startup";
39
+ pme: "pme";
40
+ eti: "eti";
41
+ "grand-groupe": "grand-groupe";
42
+ }>;
43
+ /**
44
+ * Monthly traffic enum.
45
+ */
46
+ export declare const b2bMonthlyTrafficSchema: z.ZodEnum<{
47
+ "< 10k": "< 10k";
48
+ "10k-50k": "10k-50k";
49
+ "50k-200k": "50k-200k";
50
+ "> 200k": "> 200k";
51
+ }>;
52
+ /**
53
+ * Business objectives enum.
54
+ */
55
+ export declare const b2bObjectiveSchema: z.ZodEnum<{
56
+ other: "other";
57
+ "lead-qualification": "lead-qualification";
58
+ personalization: "personalization";
59
+ "cart-abandonment": "cart-abandonment";
60
+ conversion: "conversion";
61
+ }>;
62
+ /**
63
+ * E-commerce platform enum.
64
+ */
65
+ export declare const b2bPlatformSchema: z.ZodEnum<{
66
+ shopify: "shopify";
67
+ woocommerce: "woocommerce";
68
+ prestashop: "prestashop";
69
+ custom: "custom";
70
+ none: "none";
71
+ }>;
72
+ /**
73
+ * Implementation timeline enum.
74
+ */
75
+ export declare const b2bTimelineSchema: z.ZodEnum<{
76
+ immediate: "immediate";
77
+ "1-month": "1-month";
78
+ "3-months": "3-months";
79
+ exploring: "exploring";
80
+ }>;
81
+ /**
82
+ * Budget range enum.
83
+ */
84
+ export declare const b2bBudgetRangeSchema: z.ZodEnum<{
85
+ "< 500": "< 500";
86
+ "500-2000": "500-2000";
87
+ "2000-5000": "2000-5000";
88
+ "> 5000": "> 5000";
89
+ }>;
90
+ /**
91
+ * B2B qualification profile schema.
92
+ */
93
+ export declare const b2bProfileSchema: z.ZodObject<{
94
+ userType: z.ZodOptional<z.ZodEnum<{
95
+ prospect: "prospect";
96
+ partner: "partner";
97
+ unknown: "unknown";
98
+ }>>;
99
+ sector: z.ZodOptional<z.ZodEnum<{
100
+ ecommerce: "ecommerce";
101
+ "real-estate": "real-estate";
102
+ automotive: "automotive";
103
+ travel: "travel";
104
+ recruitment: "recruitment";
105
+ other: "other";
106
+ }>>;
107
+ companySize: z.ZodOptional<z.ZodEnum<{
108
+ startup: "startup";
109
+ pme: "pme";
110
+ eti: "eti";
111
+ "grand-groupe": "grand-groupe";
112
+ }>>;
113
+ monthlyTraffic: z.ZodOptional<z.ZodEnum<{
114
+ "< 10k": "< 10k";
115
+ "10k-50k": "10k-50k";
116
+ "50k-200k": "50k-200k";
117
+ "> 200k": "> 200k";
118
+ }>>;
119
+ objectives: z.ZodOptional<z.ZodArray<z.ZodEnum<{
120
+ other: "other";
121
+ "lead-qualification": "lead-qualification";
122
+ personalization: "personalization";
123
+ "cart-abandonment": "cart-abandonment";
124
+ conversion: "conversion";
125
+ }>>>;
126
+ platform: z.ZodOptional<z.ZodEnum<{
127
+ shopify: "shopify";
128
+ woocommerce: "woocommerce";
129
+ prestashop: "prestashop";
130
+ custom: "custom";
131
+ none: "none";
132
+ }>>;
133
+ timeline: z.ZodOptional<z.ZodEnum<{
134
+ immediate: "immediate";
135
+ "1-month": "1-month";
136
+ "3-months": "3-months";
137
+ exploring: "exploring";
138
+ }>>;
139
+ budgetRange: z.ZodOptional<z.ZodEnum<{
140
+ "< 500": "< 500";
141
+ "500-2000": "500-2000";
142
+ "2000-5000": "2000-5000";
143
+ "> 5000": "> 5000";
144
+ }>>;
145
+ }, z.core.$strip>;
146
+ export type B2BProfile = z.infer<typeof b2bProfileSchema>;
147
+ /**
148
+ * Public API exposed on window.Versiq.
149
+ */
150
+ export interface VersiqAPI {
151
+ /** Application ID (available after config resolution) */
152
+ readonly applicationId: string;
153
+ /** Open the widget */
154
+ open(): void;
155
+ /** Close the widget */
156
+ close(): void;
157
+ /** Reset the conversation */
158
+ reset(): void;
159
+ /** Update theme dynamically */
160
+ setTheme(theme: ThemeConfig): void;
161
+ /**
162
+ * Update only the color scheme. Sugar over `setTheme({ colorScheme })` so
163
+ * integrators can wire their own dark-mode toggle without rebuilding the
164
+ * full theme object: `window.Versiq.setColorScheme("dark")`.
165
+ */
166
+ setColorScheme(scheme: ColorScheme): void;
167
+ /** Identify the current user with host-attested HMAC verification */
168
+ identify(params: {
169
+ email: string;
170
+ userId?: string;
171
+ userHash: string;
172
+ }): void;
173
+ /** Subscribe to events */
174
+ on<T = unknown>(event: WidgetEventType, handler: WidgetEventHandler<T>): void;
175
+ /** Unsubscribe from events */
176
+ off<T = unknown>(event: WidgetEventType, handler: WidgetEventHandler<T>): void;
177
+ /** Widget version */
178
+ version: string;
179
+ }
180
+ declare global {
181
+ interface Window {
182
+ Versiq?: VersiqAPI;
183
+ VersiqApps?: Map<string, VersiqAPI>;
184
+ }
185
+ }