import type { Options as WebworkerOptions } from '../../common/interaction.js'; import AttributeSender from '../modules/attributeSender.js'; import type { Options as NetworkOptions } from '../modules/network.js'; import type TagMatcher from '../modules/tagMatcher.js'; import Logger, { ILogLevel } from './logger.js'; import Message from './messages.gen.js'; import Nodes from './nodes/index.js'; import type { Options as ObserverOptions } from './observer/top_observer.js'; import Observer from './observer/top_observer.js'; import type { Options as SanitizerOptions } from './sanitizer.js'; import Sanitizer from './sanitizer.js'; import type { Options as SessOptions } from './session.js'; import Session from './session.js'; import Ticker from './ticker.js'; import { MaintainerOptions } from './nodes/maintainer.js'; export { InlineCssMode } from './observer/top_observer.js'; export interface StartOptions { userID?: string; metadata?: Record; forceNew?: boolean; sessionHash?: string; assistOnly?: boolean; /** * @deprecated We strongly advise to use .start().then instead. * * This method is kept for snippet compatibility only * */ startCallback?: (result: StartPromiseReturn) => void; } interface OnStartInfo { sessionID: string; sessionToken: string; userUUID: string; } declare const CANCELED: "canceled"; type SuccessfulStart = OnStartInfo & { success: true; }; type UnsuccessfulStart = { reason: typeof CANCELED | string; success: false; }; declare const UnsuccessfulStart: (reason: string) => UnsuccessfulStart; declare const SuccessfulStart: (body: OnStartInfo) => SuccessfulStart; export type StartPromiseReturn = SuccessfulStart | UnsuccessfulStart; type StartCallback = (i: OnStartInfo) => void; type CommitCallback = (messages: Array) => void; declare enum ActivityState { NotActive = 0, Starting = 1, Active = 2, ColdStart = 3 } type AppOptions = { revID: string; node_id: string; session_reset_key: string; session_token_key: string; session_pageno_key: string; session_tabid_key: string; local_uuid_key: string; ingestPoint: string; resourceBaseHref: string | null; __is_snippet: boolean; __debug_report_edp: string | null; __debug__?: ILogLevel; __local_debug?: boolean; localStorage: Storage | null; sessionStorage: Storage | null; forceSingleTab?: boolean; /** Sometimes helps to prevent session breaking due to dict reset */ disableStringDict?: boolean; assistSocketHost?: string; canvas: { disableCanvas?: boolean; /** * If you expect HI-DPI users mostly, this will render canvas * in 1:1 pixel ratio * */ fixedCanvasScaling?: boolean; __save_canvas_locally?: boolean; /** * Use with care since it hijacks one frame each time it captures * snapshot for every canvas * */ useAnimationFrame?: boolean; /** * Use webp unless it produces too big images * @default webp * */ fileExt?: 'webp' | 'png' | 'jpeg' | 'avif'; }; crossdomain?: { /** * @default false * */ enabled?: boolean; /** * used to send message up, will be '*' by default * (check your CSP settings) * @default '*' * */ parentDomain?: string; }; network?: NetworkOptions; /** * use this flag to force angular detection to be offline * * basically goes around window.Zone api changes to mutation observer * and event listeners * */ forceNgOff?: boolean; /** * This option is used to change how tracker handles potentially detached nodes * * defaults here are tested and proven to be lightweight and easy on cpu * * consult the docs before changing it * */ nodes?: { maintainer: Partial; }; } & WebworkerOptions & SessOptions; export type Options = AppOptions & ObserverOptions & SanitizerOptions; export declare const DEFAULT_INGEST_POINT = "https://api.openreplay.com/ingest"; export default class App { private readonly signalError; readonly insideIframe: boolean; readonly nodes: Nodes; readonly ticker: Ticker; readonly projectKey: string; readonly sanitizer: Sanitizer; readonly debug: Logger; readonly notify: Logger; readonly session: Session; readonly localStorage: Storage; readonly sessionStorage: Storage; private readonly messages; /** * we need 2 buffers, so we don't lose anything * @read coldStart implementation * */ private bufferedMessages1; private readonly bufferedMessages2; readonly observer: Observer; private readonly startCallbacks; private readonly stopCallbacks; private readonly commitCallbacks; readonly options: Options; readonly networkOptions?: NetworkOptions; private readonly revID; private activityState; private readonly version; private worker?; attributeSender: AttributeSender; socketMode: boolean; private compressionThreshold; private readonly bc; private readonly contextId; private canvasRecorder; private conditionsManager; private readonly tagWatcher; get tagMatcher(): TagMatcher; private canStart; private rootId; private pageFrames; private frameOderNumber; private frameLevel; private emptyBatchCounter; constructor(projectKey: string, sessionToken: string | undefined, options: Partial, signalError: (error: string, apis: string[]) => void, insideIframe: boolean); /** used by child iframes for crossdomain only */ parentActive: boolean; checkStatus: () => boolean; parentCrossDomainFrameListener: (event: MessageEvent) => void; /** * context ids for iframes, * order is not so important as long as its consistent * */ trackedFrames: string[]; crossDomainIframeListener: (event: MessageEvent) => void; /** * { command : [remaining iframes] } * + order of commands **/ pollingQueue: Record; private readonly addCommand; bootChildrenFrames: () => Promise; killChildrenFrames: () => void; signalIframeTracker: () => void; startTimeout: ReturnType | null; allowAppStart(): void; private checkNodeId; private initWorker; private restart; private handleWorkerMsg; private _debug; send: (message: Message, urgent?: boolean) => void; /** * Normal workflow: add timestamp and tab data to batch, then commit it * every ~30ms * */ private _nCommit; coldStartCommitN: number; /** * Cold start: add timestamp and tab data to both batches * every 2nd tick, ~60ms * this will make batches a bit larger and replay will work with bigger jumps every frame * but in turn we don't overload batch writer on session start with 1000 batches * */ private _cStartCommit; private commit; private postToWorker; private delay; timestamp(): number; safe void>(fn: T): T; attachCommitCallback(cb: CommitCallback): void; attachStartCallback: (cb: StartCallback, useSafe?: boolean) => void; attachStopCallback: (cb: () => any, useSafe?: boolean) => void; attachEventListener: (target: EventTarget, type: string, listener: EventListener, useSafe?: boolean, useCapture?: boolean) => void; checkRequiredVersion(version: string): boolean; private getTrackerInfo; getSessionInfo(): { userUUID: string | null; projectKey: string; revID: string; trackerVersion: string; isSnippet: boolean; sessionID: string | undefined; metadata: Record; userID: string | null; timestamp: number; projectID?: string; }; getSessionToken(): string | undefined; getSessionID(): string | undefined; getSessionURL(options?: { withCurrentTime?: boolean; }): string | undefined; getHost(): string; getProjectKey(): string; getBaseHref(): string; resolveResourceURL(resourceURL: string): string; isServiceURL(url: string): boolean; active(): boolean; resetNextPageSession(flag: boolean): void; coldInterval: ReturnType | null; orderNumber: number; coldStartTs: number; singleBuffer: boolean; private checkSessionToken; /** * start buffering messages without starting the actual session, which gives * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger, * and we will then send buffered batch, so it won't get lost * */ coldStart(startOpts?: StartOptions, conditional?: boolean): Promise; private setupConditionalStart; onSessionSent: () => void; /** * Starts offline session recording * @param {Object} startOpts - options for session start, same as .start() * @param {Function} onSessionSent - callback that will be called once session is fully sent * */ offlineRecording(startOpts: StartOptions | undefined, onSessionSent: () => void): { saveBuffer: () => void; getBuffer: () => Message[]; setBuffer: (buffer: Message[]) => void; }; /** * Saves the captured messages in localStorage (or whatever is used in its place) * * Then, when this.offlineRecording is called, it will preload this messages and clear the storage item * * Keeping the size of local storage reasonable is up to the end users of this library * */ saveBuffer(): void; /** * @returns buffer with stored messages for offline recording * */ getBuffer(): Message[]; /** * Used to set a buffer with messages array * */ setBuffer(buffer: Message[]): void; /** * Uploads the stored session buffer to backend * @returns promise that resolves once messages are loaded, it has to be awaited * so the session can be uploaded properly * @resolve - if messages were loaded in service worker successfully * @reject {string} - error message * */ uploadOfflineRecording(): Promise; prevOpts: StartOptions; private _start; restartCanvasTracking: () => void; flushBuffer: (buffer: Message[]) => Promise; waitStart(): Promise; waitStarted(): Promise; waitStatus(status: ActivityState): Promise; /** * basically we ask other tabs during constructor * and here we just apply 10ms delay just in case * */ start(...args: Parameters): Promise; forceFlushBatch(): void; getTabId(): string; clearBuffers(): void; /** * Creates a named hook that expects event name, data string and msg direction (up/down), * it will skip any message bigger than 5 mb or event name bigger than 255 symbols * @returns {(msgType: string, data: string, dir: "up" | "down") => void} * */ trackWs(channelName: string): (msgType: string, data: string, dir: 'up' | 'down') => void; stop(stopWorker?: boolean): void; }