import type { OpenPanelOptions as OpenPanelBaseOptions, TrackProperties, } from '@openpanel/sdk'; import { OpenPanel as OpenPanelBase } from '@openpanel/sdk'; export type * from '@openpanel/sdk'; export { OpenPanel as OpenPanelBase } from '@openpanel/sdk'; export type SessionReplayOptions = { enabled: boolean; sampleRate?: number; maskAllInputs?: boolean; /** * Mask all text content in the recording. Defaults to true. * When true, all text is replaced with asterisks. */ maskAllText?: boolean; /** * CSS selector for elements whose text should NOT be masked, * even when maskAllText is true. * Example: '[data-openpanel-unmask]' */ unmaskTextSelector?: string; blockSelector?: string; blockClass?: string; ignoreSelector?: string; flushIntervalMs?: number; maxEventsPerChunk?: number; maxPayloadBytes?: number; /** * URL to the replay recorder script. * Only used when loading the SDK via a script tag (IIFE / op1.js). * When using the npm package with a bundler this option is ignored * because the bundler resolves the replay module from the package. */ scriptUrl?: string; }; // Injected at build time only in the IIFE (tracker) build. // In the library build this is `undefined`. declare const __OPENPANEL_REPLAY_URL__: string | undefined; // Capture script element synchronously; currentScript is only set during sync execution. // Used by loadReplayModule() to derive the replay script URL in the IIFE build. const _replayScriptRef: HTMLScriptElement | null = typeof document !== 'undefined' ? (document.currentScript as HTMLScriptElement | null) : null; export type OpenPanelOptions = OpenPanelBaseOptions & { trackOutgoingLinks?: boolean; trackScreenViews?: boolean; trackAttributes?: boolean; trackHashChanges?: boolean; sessionReplay?: SessionReplayOptions; }; function toCamelCase(str: string) { return str.replace(/([-_][a-z])/gi, ($1) => $1.toUpperCase().replace('-', '').replace('_', '') ); } type PendingRevenue = { amount: number; properties?: Record; }; export class OpenPanel extends OpenPanelBase { private lastPath = ''; private debounceTimer: any; private pendingRevenues: PendingRevenue[] = []; constructor(public options: OpenPanelOptions) { super({ sdk: 'web', sdkVersion: process.env.WEB_VERSION!, ...options, }); if (!this.isServer()) { try { const pending = sessionStorage.getItem('openpanel-pending-revenues'); if (pending) { const parsed = JSON.parse(pending); if (Array.isArray(parsed)) { this.pendingRevenues = parsed; } } } catch { this.pendingRevenues = []; } this.setGlobalProperties({ __referrer: document.referrer, }); if (this.options.trackScreenViews) { this.trackScreenViews(); setTimeout(() => this.screenView(), 0); } if (this.options.trackOutgoingLinks) { this.trackOutgoingLinks(); } if (this.options.trackAttributes) { this.trackAttributes(); } if (this.options.sessionReplay?.enabled) { const sampleRate = this.options.sessionReplay.sampleRate ?? 1; const sampled = Math.random() < sampleRate; if (sampled) { this.loadReplayModule().then((mod) => { if (!mod) { return; } mod.startReplayRecorder(this.options.sessionReplay!, (chunk) => { // Replay chunks go through send() and are queued when disabled or waitForProfile // until ready() is called (base SDK also queues replay until sessionId is set). this.send({ type: 'replay', payload: { ...chunk, }, }); }); }); } } } } /** * Load the replay recorder module. * * - **IIFE build (op1.js)**: `__OPENPANEL_REPLAY_URL__` is replaced at * build time with a CDN URL (e.g. `https://openpanel.dev/op1-replay.js`). * The user can also override it via `sessionReplay.scriptUrl`. * We load the IIFE replay script via a classic `