replay-tracker
Version:
A lightweight session replay tracker for websites using rrweb
1 lines • 20.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { record } from \"rrweb\";\r\nimport {\r\n S3Client,\r\n PutObjectCommand,\r\n GetObjectCommand,\r\n} from \"@aws-sdk/client-s3\";\r\n\r\n// Define type for rrweb events\r\ninterface RRWebEvent {\r\n type: number;\r\n data?: any;\r\n timestamp: number;\r\n [key: string]: any;\r\n}\r\n\r\nexport interface WasabiConfig {\r\n accessKeyId: string;\r\n secretAccessKey: string;\r\n endpoint: string;\r\n bucket: string;\r\n region?: string;\r\n}\r\n\r\nexport interface TrackerOptions {\r\n apiKey?: string;\r\n endpoint?: string;\r\n sampleRate?: number;\r\n maskText?: boolean;\r\n wasabi?: Partial<WasabiConfig>;\r\n autoUpload?: boolean;\r\n debug?: boolean; // Control whether debug logs are shown\r\n rrwebInstance?: any; // Allow passing rrweb instance directly\r\n}\r\n\r\nexport interface Tracker {\r\n start(): Promise<void>;\r\n stop(): void;\r\n uploadToWasabi(filename?: string): Promise<string | undefined>;\r\n getRecording(filename: string): Promise<any>;\r\n replay(\r\n events: RRWebEvent[],\r\n container: HTMLElement,\r\n options?: any\r\n ): Promise<void>;\r\n}\r\n\r\nexport function createTracker(opts: TrackerOptions = {}): Tracker {\r\n console.log(\"createTracker\", opts);\r\n const options = {\r\n apiKey: \"developer1@\",\r\n autoUpload: true,\r\n debug: false, // Default debug to false\r\n ...opts,\r\n };\r\n\r\n // Store rrweb instance if provided directly\r\n let rrwebInstance = options.rrwebInstance;\r\n\r\n // Custom logger function that respects debug setting\r\n const log = (message: string, data?: any) => {\r\n if (options.debug) {\r\n if (data) {\r\n console.log(`%c${message}`, \"color: #0066FF; font-weight: bold;\", data);\r\n } else {\r\n console.log(`%c${message}`, \"color: #0066FF; font-weight: bold;\");\r\n }\r\n }\r\n };\r\n\r\n // Custom error logger that always shows regardless of debug setting\r\n const logError = (message: string, error?: any) => {\r\n if (error) {\r\n console.error(\r\n `%c[Replay Tracker ERROR] ${message}`,\r\n \"color: #FF0000; font-weight: bold;\",\r\n error\r\n );\r\n } else {\r\n console.error(\r\n `%c[Replay Tracker ERROR] ${message}`,\r\n \"color: #FF0000; font-weight: bold;\"\r\n );\r\n }\r\n };\r\n\r\n log(\"[Replay Tracker] Creating tracker with options:\", opts);\r\n\r\n if (typeof window === \"undefined\") {\r\n log(\r\n \"[Replay Tracker] Non-browser environment detected, returning dummy tracker\"\r\n );\r\n return {\r\n start: async () => {},\r\n stop: () => {},\r\n uploadToWasabi: async () => undefined,\r\n getRecording: async () => undefined,\r\n replay: async () => {},\r\n };\r\n }\r\n\r\n log(\"[Replay Tracker] Initialized with options:\", options);\r\n\r\n let stopFn: (() => void) | undefined;\r\n const events: RRWebEvent[] = [];\r\n let uploadQueue: Array<Promise<string | undefined>> = [];\r\n let isUploading = false;\r\n\r\n // Try to load rrweb\r\n async function loadRrweb() {\r\n if (rrwebInstance) {\r\n return rrwebInstance;\r\n }\r\n\r\n try {\r\n // Check if rrweb is already available in the global scope\r\n if (typeof window !== \"undefined\" && (window as any).rrweb) {\r\n rrwebInstance = (window as any).rrweb;\r\n return rrwebInstance;\r\n }\r\n\r\n // Try dynamic import\r\n rrwebInstance = await import(\"rrweb\");\r\n return rrwebInstance;\r\n } catch (error) {\r\n logError(\r\n \"Failed to load rrweb. Please ensure it's installed in your project:\",\r\n error\r\n );\r\n logError(\"Install it with: npm install rrweb or yarn add rrweb\");\r\n logError(\"Learn more: https://github.com/rrweb-io/rrweb\");\r\n\r\n // Re-throw to handle at call site\r\n throw new Error(\"rrweb not available. See console for details.\");\r\n }\r\n }\r\n\r\n // Get Wasabi configuration\r\n function getWasabiConfig() {\r\n return {\r\n accessKeyId: \"KZJEBQ80IMI8JH1KNI0X\",\r\n secretAccessKey: \"Jom6QsacCpLEmVtPyRfLiAdldHkV3lGu8sAoQvzY\",\r\n endpoint: \"https://s3.ap-southeast-1.wasabisys.com\",\r\n bucket: \"replays\",\r\n region: \"ap-southeast-1\",\r\n };\r\n }\r\n\r\n // Create S3 client for Wasabi\r\n function createS3Client() {\r\n const wasabiConfig = getWasabiConfig();\r\n return new S3Client({\r\n region: wasabiConfig.region,\r\n endpoint: wasabiConfig.endpoint,\r\n credentials: {\r\n accessKeyId: wasabiConfig.accessKeyId,\r\n secretAccessKey: wasabiConfig.secretAccessKey,\r\n },\r\n forcePathStyle: true, // Required for Wasabi\r\n });\r\n }\r\n\r\n // Function to upload to Wasabi\r\n async function uploadToWasabiImpl(\r\n filename = `replay-${new Date().toISOString().replace(/[:.]/g, \"-\")}.json`\r\n ): Promise<string | undefined> {\r\n log(\"[Replay Tracker] Preparing to upload to Wasabi, filename:\", filename);\r\n\r\n if (typeof window === \"undefined\" || events.length === 0) {\r\n log(\r\n \"[Replay Tracker] Upload skipped - non-browser environment or no events\"\r\n );\r\n return undefined;\r\n }\r\n\r\n try {\r\n // Get Wasabi configuration from options or environment variables\r\n const wasabiConfig = getWasabiConfig();\r\n\r\n log(\"[Replay Tracker] Using Wasabi configuration:\", {\r\n endpoint: wasabiConfig.endpoint,\r\n bucket: wasabiConfig.bucket,\r\n region: wasabiConfig.region,\r\n });\r\n\r\n // Validate required configuration\r\n if (\r\n !wasabiConfig.accessKeyId ||\r\n !wasabiConfig.secretAccessKey ||\r\n !wasabiConfig.bucket\r\n ) {\r\n logError(\r\n \"Missing required Wasabi configuration. Check environment variables or options.\"\r\n );\r\n return undefined;\r\n }\r\n\r\n const recordingData = {\r\n events,\r\n metadata: {\r\n apiKey: options.apiKey,\r\n userAgent: window.navigator.userAgent,\r\n timestamp: new Date().toISOString(),\r\n url: window.location.href,\r\n },\r\n };\r\n\r\n log(\r\n `[Replay Tracker] Preparing data for upload. Events count: ${events.length}`\r\n );\r\n const jsonData = JSON.stringify(recordingData);\r\n\r\n // Create S3 client for Wasabi\r\n log(\"[Replay Tracker] Creating S3 client for Wasabi\");\r\n const s3Client = createS3Client();\r\n\r\n // Upload to Wasabi\r\n log(\"[Replay Tracker] Sending data to Wasabi\");\r\n await s3Client.send(\r\n new PutObjectCommand({\r\n Bucket: wasabiConfig.bucket,\r\n Key: filename,\r\n Body: jsonData,\r\n ContentType: \"application/json\",\r\n })\r\n );\r\n\r\n // Generate the URL to the uploaded file\r\n const fileUrl = `https://${\r\n wasabiConfig.bucket\r\n }.${wasabiConfig.endpoint.replace(/^https?:\\/\\//, \"\")}/${filename}`;\r\n\r\n log(\"wasabiConfig\", wasabiConfig);\r\n log(\"filename\", filename);\r\n log(\"fileUrl\", fileUrl);\r\n\r\n log(`[Replay Tracker] Recording uploaded to Wasabi: ${fileUrl}`);\r\n\r\n return fileUrl;\r\n } catch (error) {\r\n logError(\"Failed to upload to Wasabi:\", error);\r\n return undefined;\r\n }\r\n }\r\n\r\n // Function to get recording from Wasabi\r\n async function getRecordingImpl(filename: string): Promise<any> {\r\n log(\"[Replay Tracker] Fetching recording from Wasabi:\", filename);\r\n\r\n try {\r\n const wasabiConfig = getWasabiConfig();\r\n const s3Client = createS3Client();\r\n\r\n // Get object from Wasabi\r\n const response = await s3Client.send(\r\n new GetObjectCommand({\r\n Bucket: wasabiConfig.bucket,\r\n Key: filename,\r\n })\r\n );\r\n\r\n // Parse response body\r\n if (!response.Body) {\r\n throw new Error(\"No body in response\");\r\n }\r\n\r\n // Convert stream to string\r\n const streamToString = async (stream: any): Promise<string> => {\r\n const chunks: Buffer[] = [];\r\n return new Promise((resolve, reject) => {\r\n stream.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\r\n stream.on(\"error\", reject);\r\n stream.on(\"end\", () =>\r\n resolve(Buffer.concat(chunks).toString(\"utf-8\"))\r\n );\r\n });\r\n };\r\n\r\n const bodyContents = await streamToString(response.Body);\r\n const recordingData = JSON.parse(bodyContents);\r\n\r\n log(\"[Replay Tracker] Successfully retrieved recording data\");\r\n\r\n return recordingData;\r\n } catch (error) {\r\n logError(\"Failed to get recording from Wasabi:\", error);\r\n return null;\r\n }\r\n }\r\n\r\n // Function to replay events\r\n async function replayEvents(\r\n events: RRWebEvent[],\r\n container: HTMLElement,\r\n options: any = {}\r\n ) {\r\n log(\"[Replay Tracker] Setting up replay for events:\", events.length);\r\n\r\n try {\r\n // Load rrweb\r\n const rrweb = await loadRrweb();\r\n\r\n // Default options\r\n const defaultOptions = {\r\n speed: 1,\r\n showController: true,\r\n ...options,\r\n };\r\n\r\n log(\"[Replay Tracker] Creating replayer with options:\", defaultOptions);\r\n\r\n // Create new replayer instance using rrweb's Replayer\r\n const replayer = new rrweb.Replayer(events, {\r\n ...defaultOptions,\r\n target: container,\r\n });\r\n\r\n // Start replay\r\n replayer.play();\r\n log(\"[Replay Tracker] Replay started\");\r\n } catch (error) {\r\n logError(\"Failed to initialize replayer:\", error);\r\n }\r\n }\r\n\r\n // Process the upload queue\r\n async function processUploadQueue() {\r\n log(\r\n `[Replay Tracker] Processing upload queue. Queue length: ${uploadQueue.length}`\r\n );\r\n\r\n if (isUploading || uploadQueue.length === 0) {\r\n log(\r\n `[Replay Tracker] Upload queue processing skipped. isUploading: ${isUploading}`\r\n );\r\n return;\r\n }\r\n\r\n isUploading = true;\r\n try {\r\n // Take the next upload task from the queue\r\n const nextUpload = uploadQueue.shift();\r\n if (nextUpload) {\r\n log(\"[Replay Tracker] Processing next upload in queue\");\r\n await nextUpload;\r\n log(\"[Replay Tracker] Upload completed\");\r\n }\r\n } finally {\r\n isUploading = false;\r\n // If there are more items in the queue, process them\r\n if (uploadQueue.length > 0) {\r\n log(\r\n `[Replay Tracker] More items in queue (${uploadQueue.length}), continuing processing`\r\n );\r\n processUploadQueue();\r\n } else {\r\n log(\"[Replay Tracker] Upload queue is now empty\");\r\n }\r\n }\r\n }\r\n\r\n // Function to send events and optionally queue an upload\r\n function send(opts: TrackerOptions, event: RRWebEvent) {\r\n log(\"[Replay Tracker] Recording event:\", {\r\n apiKey: opts.apiKey,\r\n eventType: event.type,\r\n timestamp: new Date().toISOString(),\r\n userAgent:\r\n typeof window !== \"undefined\"\r\n ? window.navigator.userAgent\r\n : \"non-browser environment\",\r\n });\r\n\r\n // If autoUpload is enabled, queue an upload after a batch of events\r\n if (opts.autoUpload && event) {\r\n // We use an event type check to trigger upload on specific events\r\n // For example, upload after every 50 events or on specific event types\r\n const eventType = event.type;\r\n\r\n if (\r\n eventType === 4 || // Mouse interaction events\r\n events.length % 50 === 0 // Or every 50 events\r\n ) {\r\n log(\r\n `[Replay Tracker] Auto upload triggered. Event type: ${eventType}, Events count: ${events.length}`\r\n );\r\n uploadQueue.push(uploadToWasabiImpl());\r\n processUploadQueue();\r\n }\r\n }\r\n }\r\n\r\n return {\r\n async start() {\r\n log(\"[Replay Tracker] Starting recording session\");\r\n if (stopFn) {\r\n log(\"[Replay Tracker] Recording already in progress\");\r\n return;\r\n }\r\n\r\n // Clear previous events when starting a new recording\r\n events.length = 0;\r\n uploadQueue = [];\r\n log(\"[Replay Tracker] Cleared previous events and upload queue\");\r\n\r\n try {\r\n // Load rrweb\r\n const rrweb = await loadRrweb();\r\n\r\n log(\"[Replay Tracker] Initializing rrweb recorder\");\r\n stopFn = rrweb.record({\r\n emit: (event: RRWebEvent) => {\r\n send(options, event);\r\n events.push(event);\r\n },\r\n maskTextSelector: options.maskText ? \"*\" : undefined,\r\n });\r\n log(\"[Replay Tracker] rrweb recorder initialized successfully\");\r\n } catch (error) {\r\n logError(\"Failed to start recording:\", error);\r\n }\r\n },\r\n stop() {\r\n log(\"[Replay Tracker] Stopping recording session\");\r\n if (stopFn) {\r\n stopFn();\r\n stopFn = undefined;\r\n log(\"[Replay Tracker] Recording stopped\");\r\n } else {\r\n log(\"[Replay Tracker] No active recording to stop\");\r\n }\r\n\r\n // If autoUpload is enabled, ensure we upload the final recording\r\n if (options.autoUpload && events.length > 0) {\r\n log(\r\n `[Replay Tracker] Auto uploading final recording. Events count: ${events.length}`\r\n );\r\n uploadQueue.push(uploadToWasabiImpl());\r\n processUploadQueue();\r\n }\r\n },\r\n // Public method kept for compatibility\r\n async uploadToWasabi(\r\n filename = `replay-${new Date().toISOString().replace(/[:.]/g, \"-\")}.json`\r\n ): Promise<string | undefined> {\r\n log(`[Replay Tracker] Manual upload requested. Filename: ${filename}`);\r\n return uploadToWasabiImpl(filename);\r\n },\r\n // Public method to get a recording from Wasabi\r\n async getRecording(filename: string): Promise<any> {\r\n log(`[Replay Tracker] Retrieving recording: ${filename}`);\r\n return getRecordingImpl(filename);\r\n },\r\n // Public method to replay events\r\n async replay(\r\n events: RRWebEvent[],\r\n container: HTMLElement,\r\n options: any = {}\r\n ) {\r\n log(\"[Replay Tracker] Replay method called with events:\", events?.length);\r\n await replayEvents(events, container, options);\r\n },\r\n };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,uBAIO;AAyCA,SAAS,cAAc,OAAuB,CAAC,GAAY;AAChE,UAAQ,IAAI,iBAAiB,IAAI;AACjC,QAAM,UAAU;AAAA,IACd,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,OAAO;AAAA;AAAA,IACP,GAAG;AAAA,EACL;AAGA,MAAI,gBAAgB,QAAQ;AAG5B,QAAM,MAAM,CAAC,SAAiB,SAAe;AAC3C,QAAI,QAAQ,OAAO;AACjB,UAAI,MAAM;AACR,gBAAQ,IAAI,KAAK,OAAO,IAAI,sCAAsC,IAAI;AAAA,MACxE,OAAO;AACL,gBAAQ,IAAI,KAAK,OAAO,IAAI,oCAAoC;AAAA,MAClE;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,CAAC,SAAiB,UAAgB;AACjD,QAAI,OAAO;AACT,cAAQ;AAAA,QACN,4BAA4B,OAAO;AAAA,QACnC;AAAA,QACA;AAAA,MACF;AAAA,IACF,OAAO;AACL,cAAQ;AAAA,QACN,4BAA4B,OAAO;AAAA,QACnC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,mDAAmD,IAAI;AAE3D,MAAI,OAAO,WAAW,aAAa;AACjC;AAAA,MACE;AAAA,IACF;AACA,WAAO;AAAA,MACL,OAAO,YAAY;AAAA,MAAC;AAAA,MACpB,MAAM,MAAM;AAAA,MAAC;AAAA,MACb,gBAAgB,YAAY;AAAA,MAC5B,cAAc,YAAY;AAAA,MAC1B,QAAQ,YAAY;AAAA,MAAC;AAAA,IACvB;AAAA,EACF;AAEA,MAAI,8CAA8C,OAAO;AAEzD,MAAI;AACJ,QAAM,SAAuB,CAAC;AAC9B,MAAI,cAAkD,CAAC;AACvD,MAAI,cAAc;AAGlB,iBAAe,YAAY;AACzB,QAAI,eAAe;AACjB,aAAO;AAAA,IACT;AAEA,QAAI;AAEF,UAAI,OAAO,WAAW,eAAgB,OAAe,OAAO;AAC1D,wBAAiB,OAAe;AAChC,eAAO;AAAA,MACT;AAGA,sBAAgB,MAAM,OAAO,OAAO;AACpC,aAAO;AAAA,IACT,SAAS,OAAO;AACd;AAAA,QACE;AAAA,QACA;AAAA,MACF;AACA,eAAS,sDAAsD;AAC/D,eAAS,+CAA+C;AAGxD,YAAM,IAAI,MAAM,+CAA+C;AAAA,IACjE;AAAA,EACF;AAGA,WAAS,kBAAkB;AACzB,WAAO;AAAA,MACL,aAAa;AAAA,MACb,iBAAiB;AAAA,MACjB,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAGA,WAAS,iBAAiB;AACxB,UAAM,eAAe,gBAAgB;AACrC,WAAO,IAAI,0BAAS;AAAA,MAClB,QAAQ,aAAa;AAAA,MACrB,UAAU,aAAa;AAAA,MACvB,aAAa;AAAA,QACX,aAAa,aAAa;AAAA,QAC1B,iBAAiB,aAAa;AAAA,MAChC;AAAA,MACA,gBAAgB;AAAA;AAAA,IAClB,CAAC;AAAA,EACH;AAGA,iBAAe,mBACb,WAAW,WAAU,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,SAAS,GAAG,CAAC,SACtC;AAC7B,QAAI,6DAA6D,QAAQ;AAEzE,QAAI,OAAO,WAAW,eAAe,OAAO,WAAW,GAAG;AACxD;AAAA,QACE;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,QAAI;AAEF,YAAM,eAAe,gBAAgB;AAErC,UAAI,gDAAgD;AAAA,QAClD,UAAU,aAAa;AAAA,QACvB,QAAQ,aAAa;AAAA,QACrB,QAAQ,aAAa;AAAA,MACvB,CAAC;AAGD,UACE,CAAC,aAAa,eACd,CAAC,aAAa,mBACd,CAAC,aAAa,QACd;AACA;AAAA,UACE;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAEA,YAAM,gBAAgB;AAAA,QACpB;AAAA,QACA,UAAU;AAAA,UACR,QAAQ,QAAQ;AAAA,UAChB,WAAW,OAAO,UAAU;AAAA,UAC5B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UAClC,KAAK,OAAO,SAAS;AAAA,QACvB;AAAA,MACF;AAEA;AAAA,QACE,6DAA6D,OAAO,MAAM;AAAA,MAC5E;AACA,YAAM,WAAW,KAAK,UAAU,aAAa;AAG7C,UAAI,gDAAgD;AACpD,YAAM,WAAW,eAAe;AAGhC,UAAI,yCAAyC;AAC7C,YAAM,SAAS;AAAA,QACb,IAAI,kCAAiB;AAAA,UACnB,QAAQ,aAAa;AAAA,UACrB,KAAK;AAAA,UACL,MAAM;AAAA,UACN,aAAa;AAAA,QACf,CAAC;AAAA,MACH;AAGA,YAAM,UAAU,WACd,aAAa,MACf,IAAI,aAAa,SAAS,QAAQ,gBAAgB,EAAE,CAAC,IAAI,QAAQ;AAEjE,UAAI,gBAAgB,YAAY;AAChC,UAAI,YAAY,QAAQ;AACxB,UAAI,WAAW,OAAO;AAEtB,UAAI,kDAAkD,OAAO,EAAE;AAE/D,aAAO;AAAA,IACT,SAAS,OAAO;AACd,eAAS,+BAA+B,KAAK;AAC7C,aAAO;AAAA,IACT;AAAA,EACF;AAGA,iBAAe,iBAAiB,UAAgC;AAC9D,QAAI,oDAAoD,QAAQ;AAEhE,QAAI;AACF,YAAM,eAAe,gBAAgB;AACrC,YAAM,WAAW,eAAe;AAGhC,YAAM,WAAW,MAAM,SAAS;AAAA,QAC9B,IAAI,kCAAiB;AAAA,UACnB,QAAQ,aAAa;AAAA,UACrB,KAAK;AAAA,QACP,CAAC;AAAA,MACH;AAGA,UAAI,CAAC,SAAS,MAAM;AAClB,cAAM,IAAI,MAAM,qBAAqB;AAAA,MACvC;AAGA,YAAM,iBAAiB,OAAO,WAAiC;AAC7D,cAAM,SAAmB,CAAC;AAC1B,eAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,iBAAO,GAAG,QAAQ,CAAC,UAAkB,OAAO,KAAK,KAAK,CAAC;AACvD,iBAAO,GAAG,SAAS,MAAM;AACzB,iBAAO;AAAA,YAAG;AAAA,YAAO,MACf,QAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,CAAC;AAAA,UACjD;AAAA,QACF,CAAC;AAAA,MACH;AAEA,YAAM,eAAe,MAAM,eAAe,SAAS,IAAI;AACvD,YAAM,gBAAgB,KAAK,MAAM,YAAY;AAE7C,UAAI,wDAAwD;AAE5D,aAAO;AAAA,IACT,SAAS,OAAO;AACd,eAAS,wCAAwC,KAAK;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AAGA,iBAAe,aACbA,SACA,WACAC,WAAe,CAAC,GAChB;AACA,QAAI,kDAAkDD,QAAO,MAAM;AAEnE,QAAI;AAEF,YAAM,QAAQ,MAAM,UAAU;AAG9B,YAAM,iBAAiB;AAAA,QACrB,OAAO;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAGC;AAAA,MACL;AAEA,UAAI,oDAAoD,cAAc;AAGtE,YAAM,WAAW,IAAI,MAAM,SAASD,SAAQ;AAAA,QAC1C,GAAG;AAAA,QACH,QAAQ;AAAA,MACV,CAAC;AAGD,eAAS,KAAK;AACd,UAAI,iCAAiC;AAAA,IACvC,SAAS,OAAO;AACd,eAAS,kCAAkC,KAAK;AAAA,IAClD;AAAA,EACF;AAGA,iBAAe,qBAAqB;AAClC;AAAA,MACE,2DAA2D,YAAY,MAAM;AAAA,IAC/E;AAEA,QAAI,eAAe,YAAY,WAAW,GAAG;AAC3C;AAAA,QACE,kEAAkE,WAAW;AAAA,MAC/E;AACA;AAAA,IACF;AAEA,kBAAc;AACd,QAAI;AAEF,YAAM,aAAa,YAAY,MAAM;AACrC,UAAI,YAAY;AACd,YAAI,kDAAkD;AACtD,cAAM;AACN,YAAI,mCAAmC;AAAA,MACzC;AAAA,IACF,UAAE;AACA,oBAAc;AAEd,UAAI,YAAY,SAAS,GAAG;AAC1B;AAAA,UACE,yCAAyC,YAAY,MAAM;AAAA,QAC7D;AACA,2BAAmB;AAAA,MACrB,OAAO;AACL,YAAI,4CAA4C;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAGA,WAAS,KAAKE,OAAsB,OAAmB;AACrD,QAAI,qCAAqC;AAAA,MACvC,QAAQA,MAAK;AAAA,MACb,WAAW,MAAM;AAAA,MACjB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,WACE,OAAO,WAAW,cACd,OAAO,UAAU,YACjB;AAAA,IACR,CAAC;AAGD,QAAIA,MAAK,cAAc,OAAO;AAG5B,YAAM,YAAY,MAAM;AAExB,UACE,cAAc;AAAA,MACd,OAAO,SAAS,OAAO,GACvB;AACA;AAAA,UACE,uDAAuD,SAAS,mBAAmB,OAAO,MAAM;AAAA,QAClG;AACA,oBAAY,KAAK,mBAAmB,CAAC;AACrC,2BAAmB;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ;AACZ,UAAI,6CAA6C;AACjD,UAAI,QAAQ;AACV,YAAI,gDAAgD;AACpD;AAAA,MACF;AAGA,aAAO,SAAS;AAChB,oBAAc,CAAC;AACf,UAAI,2DAA2D;AAE/D,UAAI;AAEF,cAAM,QAAQ,MAAM,UAAU;AAE9B,YAAI,8CAA8C;AAClD,iBAAS,MAAM,OAAO;AAAA,UACpB,MAAM,CAAC,UAAsB;AAC3B,iBAAK,SAAS,KAAK;AACnB,mBAAO,KAAK,KAAK;AAAA,UACnB;AAAA,UACA,kBAAkB,QAAQ,WAAW,MAAM;AAAA,QAC7C,CAAC;AACD,YAAI,0DAA0D;AAAA,MAChE,SAAS,OAAO;AACd,iBAAS,8BAA8B,KAAK;AAAA,MAC9C;AAAA,IACF;AAAA,IACA,OAAO;AACL,UAAI,6CAA6C;AACjD,UAAI,QAAQ;AACV,eAAO;AACP,iBAAS;AACT,YAAI,oCAAoC;AAAA,MAC1C,OAAO;AACL,YAAI,8CAA8C;AAAA,MACpD;AAGA,UAAI,QAAQ,cAAc,OAAO,SAAS,GAAG;AAC3C;AAAA,UACE,kEAAkE,OAAO,MAAM;AAAA,QACjF;AACA,oBAAY,KAAK,mBAAmB,CAAC;AACrC,2BAAmB;AAAA,MACrB;AAAA,IACF;AAAA;AAAA,IAEA,MAAM,eACJ,WAAW,WAAU,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,SAAS,GAAG,CAAC,SACtC;AAC7B,UAAI,uDAAuD,QAAQ,EAAE;AACrE,aAAO,mBAAmB,QAAQ;AAAA,IACpC;AAAA;AAAA,IAEA,MAAM,aAAa,UAAgC;AACjD,UAAI,0CAA0C,QAAQ,EAAE;AACxD,aAAO,iBAAiB,QAAQ;AAAA,IAClC;AAAA;AAAA,IAEA,MAAM,OACJF,SACA,WACAC,WAAe,CAAC,GAChB;AACA,UAAI,sDAAsDD,SAAQ,MAAM;AACxE,YAAM,aAAaA,SAAQ,WAAWC,QAAO;AAAA,IAC/C;AAAA,EACF;AACF;","names":["events","options","opts"]}