import { defineJourney, journeyStep, type JourneyDef, type JourneyHandle, type JourneyStepOptions, type JourneyStepTarget, type StepHandle, } from '../../core'; import { moduleRuntime } from '../../core/runtime'; /** * Knockout / desktop-legacy bridge on `window.App.Journey`. * Prefer `defineJourney` handles; `journeyStep` / `firstOpenJourneyName` cover multi-journey steps. * Use `getJourneyHandle(name)` when you need the handle from another module. */ interface AppJourneyApi { defineJourney(config: JourneyDef): JourneyHandle; journeyStep( journey: JourneyStepTarget, stepName: string, fn: (step: StepHandle) => T | Promise, opts?: JourneyStepOptions ): Promise; /** First open journey **name** among candidates, or null. */ firstOpenJourneyName(names: readonly string[]): string | null; /** `defineJourney` handle for an open name, or null. */ getJourneyHandle(name: string): JourneyHandle | null; } export type WindowWithAppJourney = Window & { App?: { Journey?: AppJourneyApi } & Record; }; let journeyExposed = false; let windowJourneyApi: AppJourneyApi | undefined; function handleForOpenJourney(name: string): JourneyHandle | null { const open = moduleRuntime.getOpenJourney(name); if (!open) { return null; } return defineJourney(open.config); } /** * Publish the Knockout bridge on window.App.Journey. Idempotent for the real window. * Optional `target` is for tests. Never throws — logs and returns a usable API if publish fails. */ export function exposeAppJourney(target: WindowWithAppJourney = window): AppJourneyApi { if (journeyExposed && target === window && windowJourneyApi) { return windowJourneyApi; } const api: AppJourneyApi = { defineJourney, journeyStep, firstOpenJourneyName: names => moduleRuntime.firstOpenJourneyName(names), getJourneyHandle: handleForOpenJourney, }; try { if (target == null || (typeof target !== 'object' && typeof target !== 'function')) { // eslint-disable-next-line no-console -- soft-skip missing host window console.warn('[journey] exposeAppJourney: skipped — missing'); return api; } target.App = target.App ?? {}; target.App.Journey = Object.freeze(api); if (target === window) { journeyExposed = true; windowJourneyApi = api; } } catch (err) { // eslint-disable-next-line no-console -- soft-fail host wiring console.error('[journey] exposeAppJourney failed', err); } return api; } export { instrumentJquery, type JqueryStatic } from './instrument';