import type { WidgetCallbacks, WidgetData, WidgetOptions, WidgetSamplerLog, WidgetType } from "./domain"; import { normalizeWidgetOptions } from "./domain/WidgetOptions"; import { AsyncStorageService } from "./services/storage/AsyncStorageService"; import { buildStorageId } from "./services/storage/StorageIdBuilder"; import { WidgetStateManager, type TimestampField } from "./services/WidgetStateManager"; export interface WidgetConfig { instanceKey: string; type: WidgetType; data: WidgetData; options?: WidgetOptions; callbacks: WidgetCallbacks; } type ShowHandler = (config: WidgetConfig) => void; type DismissHandler = () => void; let registeredShowHandler: ShowHandler | null = null; let registeredDismissHandler: DismissHandler | null = null; export function _registerHost(show: ShowHandler, dismiss: DismissHandler) { registeredShowHandler = show; registeredDismissHandler = dismiss; } export function _unregisterHost() { registeredShowHandler = null; registeredDismissHandler = null; } export class SoluCXWidget { private _instanceKey: string = ""; private _type: WidgetType = "bottom"; private _data: WidgetData = {}; private _options: WidgetOptions | null = {}; private _callbacks: WidgetCallbacks = {}; private constructor(instanceKey: string) { this._instanceKey = instanceKey; } // --- Builder methods --- static create(instanceKey: string): SoluCXWidget { return new SoluCXWidget(instanceKey); } setData(data: WidgetData): this { this._data = { ...data }; return this; } setOptions(options: WidgetOptions): this { const isObjectValid = !!options && Object.keys(options).length > 0; this._options = isObjectValid ? normalizeWidgetOptions(options) : null; return this; } setCallbacks(callbacks: WidgetCallbacks): this { this._callbacks = { ...callbacks }; return this; } setType(type: WidgetType): this { this._type = type; return this; } show(): void { if (!registeredShowHandler) { throw new Error("[SoluCXWidget] Cannot show widget — no is mounted. " + "Add at the root of your app."); } registeredShowHandler({ instanceKey: this._instanceKey, type: this._type, data: { ...this._data }, options: { ...this._options }, callbacks: { ...this._callbacks }, }); this._cleanup(); } // --- Static: dismiss --- static dismiss(): void { registeredDismissHandler?.(); } // --- Log management (instance methods) --- /** * Build the storage ID from the builder state. * Uses instanceKey + journey (from data) + userId (from data). */ private _buildStorageId(): string { return buildStorageId(this._instanceKey, this._data); } private _createStateManager(): WidgetStateManager { const storageId = this._buildStorageId(); const storageService = new AsyncStorageService(storageId); return new WidgetStateManager(storageService, storageId); } /** * Override a specific event timestamp. * Uses instanceKey from create() and journey/userId from setData(). * * @param field - The timestamp field to override (e.g., 'lastFirstAccess', 'lastDismiss') * @param date - The date to set (Date object or timestamp in milliseconds). Use 0 to clear. */ async overrideTimestamp(field: TimestampField, date: Date | number): Promise { await this._createStateManager().overrideTimestamp(field, date); return this; } /** * Get the current widget logs. * Uses instanceKey from create() and journey/userId from setData(). */ async getWidgetLogs(): Promise { return this._createStateManager().getLogs(); } /** * Clear all widget logs. * Uses instanceKey from create() and journey/userId from setData(). */ async clearWidgetLogs(): Promise { await this._createStateManager().clearLogs(); return this; } // --- Private --- private _cleanup(): void { this._type = "bottom"; this._data = {}; this._options = {}; this._callbacks = {}; } }