import { useState } from "react"; import type { SoluCXKey, WidgetData, WidgetOptions, WidgetCallbacks } from "../domain"; import { normalizeWidgetOptions } from "../domain/WidgetOptions"; import { requestWidgetUrl, requestWidgetOptions } from "../services/WidgetBootstrapService"; import type { WidgetStateManager } from "../services/WidgetStateManager"; import type { WidgetValidationService } from "../services/WidgetValidationService"; interface UseWidgetBootstrapParams { soluCXKey: SoluCXKey; data: WidgetData; userId: string; options?: WidgetOptions; callbacks?: WidgetCallbacks; open: () => Promise; hide: () => void; validationService: WidgetValidationService; stateManager: WidgetStateManager; } export const useWidgetBootstrap = ({ soluCXKey, data, userId, options, callbacks, open, hide, validationService, stateManager, }: UseWidgetBootstrapParams): { widgetUri: string | null; widgetOptions: WidgetOptions | null; bootstrap: () => Promise } => { const [widgetUri, setWidgetUri] = useState(null); const [widgetOptions, setWidgetOptions] = useState(null); const bootstrap = async () => { if (!data) { callbacks?.onError?.("Widget data is required but was not provided"); return; } setWidgetUri(null); try { const transactionResult = await validationService.shouldDisplayForTransactionAlreadyAnswered(data.transaction_id); if (!transactionResult.canDisplay) { callbacks?.onBlocked?.(transactionResult.blockReason); hide(); return; } const rawOptions = options && Object.keys(options).length ? options : await requestWidgetOptions(soluCXKey, data.journey); const effectiveOptions = normalizeWidgetOptions(rawOptions); setWidgetOptions(effectiveOptions); // Validate BEFORE preflight to avoid unnecessary API calls at scale const result = await validationService.shouldDisplayWidget(effectiveOptions, data.transaction_id); if (!result.canDisplay) { const blockReason = result?.blockReason; callbacks?.onBlocked?.(blockReason); try { await stateManager.updateTimestamp("lastDisplayAttempt"); } catch (err) { callbacks?.onError?.("Cannot save display attempt timestamp"); } hide(); return; } // Only call preflight after validation passes const widgetUrlPayload = await requestWidgetUrl(soluCXKey, data, userId); if (!widgetUrlPayload.available) { const reason = widgetUrlPayload.reason || "for the given parameters"; callbacks?.onBlocked?.(reason); hide(); return; } if (!widgetUrlPayload.url) { callbacks?.onError?.("Widget URL is missing in the response"); return; } callbacks?.onPreOpen?.(userId); await open(); try { await stateManager.incrementAttempt(); const logs = await stateManager.getLogs(); if (!logs.lastFirstAccess) { await stateManager.updateTimestamp("lastFirstAccess"); } await stateManager.updateTimestamp("lastDisplay"); } catch (err) { callbacks?.onError?.("Error saving display timestamps"); } setWidgetUri(widgetUrlPayload.url); callbacks?.onOpened?.(userId); } catch (error) { let errorMessage = "Unknown error"; if (error instanceof Error) errorMessage = error.message; else if (typeof error === "string") errorMessage = error; else if (error !== null && typeof error === "object") errorMessage = JSON.stringify(error); callbacks?.onError?.(errorMessage); hide(); } }; return { widgetUri, widgetOptions, bootstrap, }; };