import { CDN_BASE, CDN_VERSIONS } from './versions.js'; /** * rrweb record options */ export interface IRrwebRecordOptions { emit: (event: any) => void; recordCanvas?: boolean; recordCrossOriginIframes?: boolean; checkoutEveryNms?: number; [key: string]: any; } /** * rrweb record function type */ export type TRrwebRecordFn = (options: IRrwebRecordOptions) => () => void; /** * Bundle type for rrweb recording library */ export interface IRrwebBundle { record: TRrwebRecordFn; } /** * rrweb-player constructor options */ export interface IRrwebPlayerOptions { target: HTMLElement; props: { events: any[]; root?: HTMLElement; showController?: boolean; width?: number; height?: number; [key: string]: any; }; } /** * rrweb-player instance */ export interface IRrwebPlayerInstance { play: () => Promise; pause: () => void; [key: string]: any; } /** * rrweb-player constructor type */ export type TRrwebPlayerConstructor = new (options: IRrwebPlayerOptions) => IRrwebPlayerInstance; /** * Bundle type for rrweb-player */ export interface IRrwebPlayerBundle { default: TRrwebPlayerConstructor; } /** * Singleton service for lazy-loading rrweb libraries from CDN. * * This reduces initial bundle size by loading libraries only when needed. * Libraries are cached after first load to avoid duplicate fetches. * * @example * ```typescript * const libLoader = SioServiceLibLoader.getInstance(); * const rrweb = await libLoader.loadRrweb(); * const stopFn = rrweb.record({ emit: (event) => { ... } }); * ``` */ export class SioServiceLibLoader { private static instance: SioServiceLibLoader; // Cached library references private rrwebLib: IRrwebBundle | null = null; private rrwebPlayerLib: IRrwebPlayerBundle | null = null; // Loading promises to prevent duplicate concurrent loads private rrwebLoadingPromise: Promise | null = null; private rrwebPlayerLoadingPromise: Promise | null = null; private constructor() {} /** * Get the singleton instance of SioServiceLibLoader */ public static getInstance(): SioServiceLibLoader { if (!SioServiceLibLoader.instance) { SioServiceLibLoader.instance = new SioServiceLibLoader(); } return SioServiceLibLoader.instance; } /** * Load rrweb recording library from CDN * @returns Promise resolving to rrweb module with record function */ public async loadRrweb(): Promise { if (this.rrwebLib) { return this.rrwebLib; } if (this.rrwebLoadingPromise) { return this.rrwebLoadingPromise; } this.rrwebLoadingPromise = (async () => { const url = `${CDN_BASE}/rrweb@${CDN_VERSIONS.rrweb}/+esm`; const module = await import(/* @vite-ignore */ url); this.rrwebLib = { record: module.record, }; return this.rrwebLib; })(); return this.rrwebLoadingPromise; } /** * Load rrweb-player from CDN * @returns Promise resolving to rrweb-player module */ public async loadRrwebPlayer(): Promise { if (this.rrwebPlayerLib) { return this.rrwebPlayerLib; } if (this.rrwebPlayerLoadingPromise) { return this.rrwebPlayerLoadingPromise; } this.rrwebPlayerLoadingPromise = (async () => { const url = `${CDN_BASE}/rrweb-player@${CDN_VERSIONS.rrwebPlayer}/+esm`; const module = await import(/* @vite-ignore */ url); // Inject rrweb-player CSS styles await this.injectRrwebPlayerStyles(); this.rrwebPlayerLib = { default: module.default, }; return this.rrwebPlayerLib; })(); return this.rrwebPlayerLoadingPromise; } /** * Inject rrweb-player CSS styles into the document head */ private async injectRrwebPlayerStyles(): Promise { const styleId = 'rrweb-player-cdn-styles'; if (document.getElementById(styleId)) { return; // Already injected } const cssUrl = `${CDN_BASE}/rrweb-player@${CDN_VERSIONS.rrwebPlayer}/dist/style.css`; try { const response = await fetch(cssUrl); const cssText = await response.text(); const style = document.createElement('style'); style.id = styleId; style.textContent = cssText; document.head.appendChild(style); } catch (error) { console.warn('Failed to load rrweb-player styles from CDN:', error); } } /** * Preload all rrweb libraries in parallel * Useful for warming the cache before recording is needed */ public async preloadAll(): Promise { await Promise.all([ this.loadRrweb(), this.loadRrwebPlayer(), ]); } /** * Check if a specific library is already loaded */ public isLoaded(library: 'rrweb' | 'rrwebPlayer'): boolean { switch (library) { case 'rrweb': return this.rrwebLib !== null; case 'rrwebPlayer': return this.rrwebPlayerLib !== null; default: return false; } } }