1(()=>{const __name=(target)=>target;((w, version, schemaVersions, maxEventsPerBatch = 50, maxBatchBodyBytes = 50 * 1024, maxReplayBodyBytes = 512 * 1024, maxReplayEventsPerChunk = 100, maxReplayTotalBytes = 5 * 1024 * 1024, maxReplayChunks = 50, maxReplayDurationMs = 10 * 60 * 1e3, replayFlushIntervalMs = 2e3) => { 2 const existing = w.__lovableEvents || {}; 3 if (existing.__userAppEventsSdkVersion) return; 4 const d = w.document; 5 const nav = w.navigator || {}; 6 const perf = w.performance || { now: /* @__PURE__ */ __name(() => Date.now(), "now") }; 7 const loc = w.location || { pathname: "", href: "" }; 8 const resolveSetTimer = /* @__PURE__ */ __name(() => w.setTimeout || (typeof setTimeout === "function" ? setTimeout : null), "resolveSetTimer"); 9 const resolveClearTimer = /* @__PURE__ */ __name(() => w.clearTimeout || (typeof clearTimeout === "function" ? clearTimeout : null), "resolveClearTimer"); 10 const isInternalPath = /* @__PURE__ */ __name((path) => path === "/__codex_token_check" || path.startsWith("/__l5e/"), "isInternalPath"); 11 if (isInternalPath(loc.pathname)) return; 12 const script = d && d.currentScript; 13 const artifact = script ? { 14 kind: script.getAttribute("data-artifact-kind") || null, 15 id: script.getAttribute("data-artifact-id") || null, 16 commit_sha: script.getAttribute("data-commit-sha") || null, 17 context_token: script.getAttribute("data-context-token") || null 18 } : {}; 19 const replayMode = script?.getAttribute("data-replay") || null; 20 const trackUrl = script?.getAttribute("data-track-url") || "/__l5e/trackevents"; 21 const replayUrl = script?.getAttribute("data-replay-url") || "/__l5e/replay"; 22 const replayScriptUrl = script?.getAttribute("data-replay-script-url") || "/__l5e/rrweb-record.js"; 23 const parseSampleRate = /* @__PURE__ */ __name((value) => { 24 const rate = typeof value === "string" ? Number(value) : 0; 25 if (!Number.isFinite(rate) || rate <= 0) return 0; 26 return rate >= 1 ? 1 : rate; 27 }, "parseSampleRate"); 28 const replaySampleRate = parseSampleRate(script?.getAttribute("data-replay-sample-rate") || null); 29 const replayCrashSampleRate = parseSampleRate(script?.getAttribute("data-replay-crash-sample-rate") || null); 30 const autocaptureEnabled = script?.getAttribute("data-autocapture") === "1"; 31 const randomId = /* @__PURE__ */ __name(() => w.crypto && typeof w.crypto.randomUUID === "function" ? w.crypto.randomUUID() : String(Date.now()) + "-" + Math.random().toString(16).slice(2), "randomId"); 32 const randomTraceHex = /* @__PURE__ */ __name((byteCount) => { 33 const bytes = new Uint8Array(byteCount); 34 if (w.crypto && typeof w.crypto.getRandomValues === "function") { 35 w.crypto.getRandomValues(bytes); 36 } else { 37 for (let index = 0; index < bytes.length; index++) { 38 bytes[index] = Math.floor(Math.random() * 256); 39 } 40 } 41 if (bytes.every((byte) => byte === 0)) bytes[bytes.length - 1] = 1; 42 return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(""); 43 }, "randomTraceHex"); 44 const createTraceContext = /* @__PURE__ */ __name(() => ({ 45 traceId: randomTraceHex(16), 46 rootSpanId: randomTraceHex(8) 47 }), "createTraceContext"); 48 const sessionId = randomId(); 49 let appUserId = null; 50 let pageViewId = randomId(); 51 let capturedClicks = 0; 52 let traceContext = createTraceContext(); 53 let engagementAccruedMs = 0; 54 let engagementSegmentStart = null; 55 const ENGAGEMENT_EMIT_DELAYS_MS = [1e4, 3e4, 6e4, 3e5]; 56 let engagementEmitStep = 0; 57 let engagementTimer = null; 58 const ENGAGEMENT_ACCRUAL_STOP_MS = 144e5; 59 let engagementReportedMs = 0; 60 let engagementCapped = false; 61 const ENGAGEMENT_UNFOCUSED_GRACE_MS = 18e5; 62 let engagementUnfocusedTimer = null; 63 let engagementUnfocusedSpentMs = 0; 64 let engagementUnfocusedArmedAt = 0; 65 let engagementStoppedUnfocused = false; 66 const unitIntervalFor = /* @__PURE__ */ __name((value) => { 67 let hash = 2166136261; 68 for (let index = 0; index < value.length; index++) { 69 hash ^= value.charCodeAt(index); 70 hash = Math.imul(hash, 16777619); 71 } 72 return (hash >>> 0) / 4294967296; 73 }, "unitIntervalFor"); 74 const replaySamplingValue = unitIntervalFor(sessionId); 75 const isReplaySampledIn = replaySampleRate > 0 && replaySamplingValue < replaySampleRate; 76 const isReplayCrashSampledIn = replayCrashSampleRate > 0 && replaySamplingValue < replayCrashSampleRate; 77 const isReplayRecordingAllowed = isReplaySampledIn || isReplayCrashSampledIn; 78 const replayEffectiveSampleRate = isReplaySampledIn ? replaySampleRate : replayCrashSampleRate; 79 let replayId = null; 80 let replayEventCount = 0; 81 let replayStartedAt = 0; 82 let replayFinalized = false;
83 let stopReplayRecorder = null; 84 let replayChunkSequence = 0; 85 let replayThrottledChunks = 0; 86 let replayChunksInFlight = 0; 87 let replayBudgetStopReason = null; 88 let replayUploadedBytes = 0; 89 let replayFlushTimer = null; 90 let replayDurationTimer = null; 91 let replayRecorderActive = false; 92 let replayRecorderLoading = false; 93 let replayHasFullSnapshot = false; 94 let replayUploadEnabled = isReplaySampledIn; 95 let replayCaptureReason = isReplaySampledIn ? "sampled" : null; 96 let replayTriggerEventId = null; 97 let replayTriggerEventName = null; 98 let replayCaptureMetadataEmitted = false; 99 const replayQueue = []; 100 const maxReplayBufferedEvents = Math.max(maxReplayEventsPerChunk, maxReplayEventsPerChunk * maxReplayChunks); 101 const replayMaskTextSelector = [ 102 "[data-lovable-mask]", 103 "[data-sensitive]", 104 "[data-private]", 105 "[data-auth]", 106 "[data-payment]", 107 "[contenteditable]", 108 "[autocomplete]", 109 "[name*='email' i]", 110 "[name*='phone' i]", 111 "[name*='address' i]", 112 "[name*='token' i]", 113 "[name*='secret' i]" 114 ].join(","); 115 const replayBlockSelector = [ 116 "canvas", 117 "embed", 118 "iframe", 119 "object", 120 "video", 121 "[data-lovable-block]", 122 ".rr-block", 123 "[data-payment]", 124 "[data-auth]", 125 "[data-private]" 126 ].join(","); 127 const replayIgnoreSelector = "[data-lovable-ignore],.rrweb-ignore"; 128 const maskReplayText = /* @__PURE__ */ __name((text) => text.replace(/[^\s]/gu, "*"), "maskReplayText"); 129 const queue = []; 130 let flushTimer = null; 131 let sendingDisabled = false; 132 let sendRejectionReported = false; 133 let sendDisabledReported = false; 134 let unloadingPage = false; 135 let immediateErrorFlushes = 0; 136 let errorEventsSeen = 0; 137 const batchSize = Math.max(1, maxEventsPerBatch); 138 const maxImmediateErrorFlushes = 5; 139 const maxErrorEventsPerPageLoad = 200; 140 const projectEventNamePattern = /^project\.[a-z][a-z0-9_]{0,63}(\.[a-z][a-z0-9_]{0,63})*$/u; 141 const schemaVersionFor = /* @__PURE__ */ __name((eventName) => { 142 const schemaVersion = schemaVersions[eventName]; 143 if (typeof schemaVersion === "number" && schemaVersion > 0) return schemaVersion; 144 return projectEventNamePattern.test(eventName) ? 1 : null; 145 }, "schemaVersionFor"); 146 const textEncoder = typeof TextEncoder === "function" ? new TextEncoder() : null; 147 const byteLength = /* @__PURE__ */ __name((value) => textEncoder ? textEncoder.encode(value).length : value.length, "byteLength"); 148 const createBatchBody = /* @__PURE__ */ __name((events) => JSON.stringify({ 149 envelope_schema_version: 1, 150 batch_id: randomId(), 151 sent_at: (/* @__PURE__ */ new Date()).toISOString(), 152 sdk: { name: "lovable-user-app-events", version }, 153 artifact, 154 events 155 }), "createBatchBody"); 156 const report = /* @__PURE__ */ __name((message3) => { 157 const consoleRef = w.console; 158 if (consoleRef && typeof consoleRef.debug === "function") consoleRef.debug(`[lovable-analytics] ${message3}`); 159 }, "report"); 160 const applyRejectedBatchStatus = /* @__PURE__ */ __name((status, routeMarker) => { 161 if ((status === 403 || status === 404) && routeMarker === "rejected") { 162 sendingDisabled = true; 163 queue.length = 0; 164 if (!sendDisabledReported) { 165 sendDisabledReported = true; 166 report(`events disabled for this page load (HTTP ${status})`); 167 } 168 return; 169 } 170 if (sendRejectionReported) return; 171 sendRejectionReported = true; 172 report(`event batch rejected (HTTP ${status})`); 173 }, "applyRejectedBatchStatus"); 174 const keepalivePoolBytes = 64 * 1024; 175 const eventsKeepaliveReserveBytes = 16 * 1024; 176 let keepaliveBytesInFlight = 0; 177 let beaconBytesInFlight = 0; 178 const releaseBeaconBytes = /* @__PURE__ */ __name(() => { 179 keepaliveBytesInFlight -= beaconBytesInFlight; 180 beaconBytesInFlight = 0; 181 }, "releaseBeaconBytes"); 182 const send = /* @__PURE__ */ __name((url, body, options) => { 183 const bodyBytes = byteLength(body); 184 const keepaliveLimitBytes = keepalivePoolBytes - (options.reserveBytes || 0); 185 const allowKeepalive = !!options.keepalive && keepaliveBytesInFlight + bodyBytes <= keepaliveLimitBytes; 186 if (allowKeepalive) keepaliveBytesInFlight += bodyBytes;
187 let charged = allowKeepalive; 188 const release = /* @__PURE__ */ __name(() => { 189 if (!charged) return; 190 charged = false; 191 keepaliveBytesInFlight -= bodyBytes; 192 }, "release"); 193 const BlobCtor = w.Blob || (typeof Blob === "function" ? Blob : null); 194 if (allowKeepalive && options.beacon && nav.sendBeacon && BlobCtor) { 195 try { 196 const ok = nav.sendBeacon(url, new BlobCtor([body], { type: "application/json" })); 197 if (ok) { 198 beaconBytesInFlight += bodyBytes; 199 charged = false; 200 return; 201 } 202 } catch { 203 } 204 } 205 const fetchFn = w.fetch || (typeof fetch === "function" ? fetch : null); 206 if (!fetchFn) { 207 release(); 208 options.onNetworkError?.(); 209 return; 210 } 211 try { 212 Promise.resolve( 213 fetchFn(url, { 214 method: "POST", 215 headers: { "content-type": "application/json" }, 216 body, 217 ...allowKeepalive ? { keepalive: true } : {} 218 }) 219 ).then( 220 (response) => { 221 release(); 222 options.onResponse?.(response); 223 }, 224 () => { 225 release(); 226 options.onNetworkError?.(); 227 } 228 ).catch(() => { 229 }); 230 } catch { 231 release(); 232 options.onNetworkError?.(); 233 } 234 }, "send"); 235 const sendBatch = /* @__PURE__ */ __name((allEvents, unloading) => { 236 const events = allEvents.filter((event) => { 237 try { 238 JSON.stringify(event); 239 return true; 240 } catch { 241 return false; 242 } 243 }); 244 let start = 0; 245 while (start < events.length) { 246 let end = events.length; 247 let body = createBatchBody(events.slice(start, end)); 248 const remaining = keepalivePoolBytes - keepaliveBytesInFlight; 249 const splitLimit = unloading && remaining >= eventsKeepaliveReserveBytes ? Math.min(maxBatchBodyBytes, remaining) : maxBatchBodyBytes; 250 while (end - start > 1 && byteLength(body) > splitLimit) { 251 end = start + Math.max(1, Math.floor((end - start) / 2)); 252 body = createBatchBody(events.slice(start, end)); 253 } 254 send(trackUrl, body, { 255 keepalive: true, 256 beacon: unloading, 257 onResponse: /* @__PURE__ */ __name((response) => { 258 if (!response || response.ok !== false) return; 259 applyRejectedBatchStatus(response.status, response.headers?.get?.("x-lovable-events") ?? null); 260 }, "onResponse") 261 }); 262 start = end; 263 } 264 }, "sendBatch"); 265 const createReplayChunkBody = /* @__PURE__ */ __name((events, chunkID, chunkSequence) => { 266 if (!replayId) return null; 267 return JSON.stringify({ 268 envelope_schema_version: 1, 269 replay_schema_version: 1, 270 replay_id: replayId, 271 chunk_id: chunkID, 272 chunk_sequence: chunkSequence, 273 sent_at: (/* @__PURE__ */ new Date()).toISOString(), 274 sdk: { name: "lovable-user-app-events", version }, 275 artifact, 276 session_id: sessionId, 277 page_view_id: pageViewId, 278 events 279 }); 280 }, "createReplayChunkBody"); 281 const canSendReplayEvent = /* @__PURE__ */ __name((event) => { 282 const body = createReplayChunkBody([event], "00000000-0000-4000-8000-000000000000", replayChunkSequence); 283 return !!body && byteLength(body) <= maxReplayBodyBytes; 284 }, "canSendReplayEvent"); 285 const replayEventType = /* @__PURE__ */ __name((event) => { 286 const record = event && typeof event === "object" ? event : null; 287 return typeof record?.type === "number" ? record.type : null; 288 }, "replayEventType"); 289 const replayDurationMs = /* @__PURE__ */ __name(() => Math.max(0, perf.now() - replayStartedAt), "replayDurationMs"); 290 const clearReplayFlushTimer = /* @__PURE__ */ __name(() => { 291 if (!replayFlushTimer) return; 292 const clearTimer = resolveClearTimer(); 293 if (clearTimer) clearTimer(replayFlushTimer); 294 replayFlushTimer = null; 295 }, "clearReplayFlushTimer"); 296 const clearReplayDurationTimer = /* @__PURE__ */ __name(() => { 297 if (!replayDurationTimer) return; 298 const clearTimer = resolveClearTimer(); 299 if (clearTimer) clearTimer(replayDurationTimer); 300 replayDurationTimer = null; 301 }, "clearReplayDurationTimer"); 302 const stopReplayRecorderSafely = /* @__PURE__ */ __name(() => { 303 try { 304 stopReplayRecorder?.(); 305 } catch { 306 } finally { 307 stopReplayRecorder = null; 308 replayRecorderActive = false;
309 } 310 }, "stopReplayRecorderSafely"); 311 const replayErrorStageFor = /* @__PURE__ */ __name((reason) => { 312 switch (reason) { 313 case "recorder_script_failed": 314 return "recorder_script"; 315 case "recorder_start_failed": 316 return "recorder_start"; 317 case "recorder_runtime_error": 318 return "recorder_runtime"; 319 case "missing_full_snapshot": 320 case "full_snapshot_too_large": 321 return "snapshot"; 322 case "upload_failed": 323 return "upload"; 324 default: 325 return null; 326 } 327 }, "replayErrorStageFor"); 328 const emitReplayErrorFor = /* @__PURE__ */ __name((targetReplayId, reason, eventCount, durationMs, chunkCount, uploadedBytes) => { 329 const stage = replayErrorStageFor(reason); 330 if (!stage) return; 331 enqueue("lovable.replay_error", { 332 replay_id: targetReplayId, 333 reason, 334 stage, 335 recorder: "rrweb", 336 sample_rate: replayEffectiveSampleRate, 337 event_count: eventCount, 338 duration_ms: durationMs, 339 chunk_count: chunkCount, 340 uploaded_bytes: uploadedBytes 341 }); 342 }, "emitReplayErrorFor"); 343 const replayCaptureProperties = /* @__PURE__ */ __name(() => !replayCaptureReason || replayCaptureReason === "sampled" ? {} : { 344 capture_reason: replayCaptureReason, 345 trigger_event_id: replayTriggerEventId, 346 trigger_event_name: replayTriggerEventName 347 }, "replayCaptureProperties"); 348 const emitReplayMetadataFor = /* @__PURE__ */ __name((targetReplayId, status, reason, eventCount, durationMs, chunkCount, uploadedBytes) => { 349 enqueue("lovable.session_replay", { 350 replay_id: targetReplayId, 351 status, 352 reason, 353 recorder: "rrweb", 354 sample_rate: replayEffectiveSampleRate, 355 event_count: eventCount, 356 duration_ms: durationMs, 357 chunk_count: chunkCount, 358 uploaded_bytes: uploadedBytes, 359 ...replayCaptureProperties() 360 }); 361 emitReplayErrorFor(targetReplayId, reason, eventCount, durationMs, chunkCount, uploadedBytes); 362 }, "emitReplayMetadataFor"); 363 const emitReplayMetadata = /* @__PURE__ */ __name((status, reason) => { 364 if (!replayId) return; 365 emitReplayMetadataFor( 366 replayId, 367 status, 368 reason, 369 replayEventCount, 370 replayDurationMs(), 371 replayChunkSequence, 372 replayUploadedBytes 373 ); 374 }, "emitReplayMetadata"); 375 const shouldEmitReplayLifecycleMetadata = /* @__PURE__ */ __name(() => replayUploadEnabled || replayCaptureReason === "sampled", "shouldEmitReplayLifecycleMetadata"); 376 const emitReplayMetadataOrErrorFor = /* @__PURE__ */ __name((targetReplayId, status, reason, eventCount, durationMs, chunkCount, uploadedBytes) => { 377 if (shouldEmitReplayLifecycleMetadata()) { 378 emitReplayMetadataFor(targetReplayId, status, reason, eventCount, durationMs, chunkCount, uploadedBytes); 379 } else { 380 emitReplayErrorFor(targetReplayId, reason, eventCount, durationMs, chunkCount, uploadedBytes); 381 } 382 }, "emitReplayMetadataOrErrorFor"); 383 const stopReplayRecording = /* @__PURE__ */ __name((reason, dropQueuedEvents = false) => { 384 if (!replayId || replayFinalized) return; 385 stopReplayRecorderSafely(); 386 clearReplayDurationTimer(); 387 if (dropQueuedEvents) { 388 replayQueue.length = 0; 389 clearReplayFlushTimer(); 390 } 391 if (shouldEmitReplayLifecycleMetadata()) { 392 emitReplayMetadata("stopped", reason); 393 } 394 replayFinalized = true; 395 }, "stopReplayRecording"); 396 const stopReplayAtDurationLimit = /* @__PURE__ */ __name(() => { 397 if (!replayId || replayFinalized) return; 398 if (!replayUploadEnabled) { 399 stopReplayRecorderSafely(); 400 clearReplayDurationTimer(); 401 return; 402 } 403 stopReplayRecording("duration_limit", true); 404 }, "stopReplayAtDurationLimit"); 405 const markReplayCapturedByClientError = /* @__PURE__ */ __name((triggerEventId) => { 406 if (!replayId || replayFinalized || replayCaptureReason === "client_error") return; 407 replayUploadEnabled = true; 408 replayCaptureReason = "client_error"; 409 replayTriggerEventId = triggerEventId; 410 replayTriggerEventName = "lovable.client_error"; 411 emitReplayCaptureMetadataIfReady(); 412 }, "markReplayCapturedByClientError"); 413 const hasReplayDataToCapture = /* @__PURE__ */ __name(() => replayQueue.length > 0 || replayChunkSequence > 0 || replayUploadedBytes > 0, "hasReplayDataToCapture"); 414 const emitReplayCaptureMetadataIfReady = /* @__PURE__ */ __name(() => { 415 if (!replayId || replayFinalized || replayCaptureReason !== "client_error" || replayCaptureMetadataEmitted || !replayHasFullSnapshot || !hasReplayDataToCapture()) { 416 return false;
417 } 418 replayCaptureMetadataEmitted = true; 419 emitReplayMetadata("captured", "client_error"); 420 flushReplay(true); 421 return true; 422 }, "emitReplayCaptureMetadataIfReady"); 423 const endReplayOnSpentBudget = /* @__PURE__ */ __name((reason) => { 424 if (replayChunksInFlight > 0) { 425 replayBudgetStopReason = reason; 426 return; 427 } 428 stopReplayRecording(reason, true); 429 }, "endReplayOnSpentBudget"); 430 const settleReplayChunk = /* @__PURE__ */ __name(() => { 431 replayChunksInFlight--; 432 if (replayFinalized || replayChunksInFlight > 0 || !replayBudgetStopReason) return; 433 const reason = replayBudgetStopReason; 434 replayBudgetStopReason = null; 435 if (replayThrottledChunks > 0) scheduleReplayFlush(); 436 else stopReplayRecording(reason, true); 437 }, "settleReplayChunk"); 438 const flushReplay = /* @__PURE__ */ __name((preferKeepalive = false) => { 439 clearReplayFlushTimer(); 440 if (!replayUploadEnabled || !replayId || replayQueue.length === 0 || !replayHasFullSnapshot) return; 441 while (!replayFinalized && replayQueue.length > 0) { 442 let count2 = Math.min(replayQueue.length, maxReplayEventsPerChunk); 443 const chunkID = randomId(); 444 const chunkSequence = replayChunkSequence; 445 let body = createReplayChunkBody(replayQueue.slice(0, count2), chunkID, chunkSequence); 446 while (body && count2 > 1 && byteLength(body) > maxReplayBodyBytes) { 447 count2 = Math.max(1, Math.floor(count2 / 2)); 448 body = createReplayChunkBody(replayQueue.slice(0, count2), chunkID, chunkSequence); 449 } 450 if (!body) return; 451 const bodyBytes = byteLength(body); 452 if (bodyBytes > maxReplayBodyBytes) { 453 replayQueue.shift(); 454 replayEventCount = Math.max(0, replayEventCount - 1); 455 continue; 456 } 457 const chunksStored = replayChunkSequence - replayThrottledChunks; 458 if (chunksStored >= maxReplayChunks || replayUploadedBytes + bodyBytes > maxReplayTotalBytes) { 459 endReplayOnSpentBudget(chunksStored >= maxReplayChunks ? "chunk_limit" : "size_limit"); 460 return; 461 } 462 replayQueue.splice(0, count2); 463 replayChunkSequence++; 464 replayUploadedBytes += bodyBytes; 465 replayChunksInFlight++; 466 send(replayUrl, body, { 467 keepalive: preferKeepalive, 468 reserveBytes: eventsKeepaliveReserveBytes, 469 onResponse: /* @__PURE__ */ __name((response) => { 470 const throttled = response?.status === 429; 471 if (throttled) { 472 replayThrottledChunks++; 473 replayUploadedBytes -= bodyBytes; 474 replayEventCount = Math.max(0, replayEventCount - count2); 475 } 476 settleReplayChunk(); 477 if (throttled || response && response.ok !== false) return; 478 const marker = response?.headers?.get?.("x-lovable-events"); 479 const reason = marker === "unavailable" ? "upload_unavailable" : marker === "rejected" ? "upload_rejected" : "upload_failed"; 480 stopReplayRecording(reason, true); 481 }, "onResponse"), 482 onNetworkError: /* @__PURE__ */ __name(() => { 483 stopReplayRecording("upload_failed", true); 484 settleReplayChunk(); 485 }, "onNetworkError") 486 }); 487 if (replayFinalized) return; 488 if (replayChunkSequence - replayThrottledChunks >= maxReplayChunks) { 489 endReplayOnSpentBudget("chunk_limit"); 490 return; 491 } 492 } 493 }, "flushReplay"); 494 const scheduleReplayFlush = /* @__PURE__ */ __name(() => { 495 if (replayFlushTimer) return; 496 const setTimer = resolveSetTimer(); 497 if (!setTimer) { 498 flushReplay(); 499 return; 500 } 501 replayFlushTimer = setTimer(flushReplay, replayFlushIntervalMs); 502 }, "scheduleReplayFlush"); 503 const clearFlushTimer = /* @__PURE__ */ __name(() => { 504 if (!flushTimer) return; 505 const clearTimer = resolveClearTimer(); 506 if (clearTimer) clearTimer(flushTimer); 507 flushTimer = null; 508 }, "clearFlushTimer"); 509 const flush = /* @__PURE__ */ __name((unloading = false) => { 510 clearFlushTimer(); 511 if (sendingDisabled || queue.length === 0) return; 512 const events = queue.splice(0, batchSize); 513 sendBatch(events, unloading || unloadingPage); 514 if (queue.length > 0) scheduleFlush(); 515 }, "flush"); 516 const scheduleFlush = /* @__PURE__ */ __name(() => { 517 if (flushTimer || sendingDisabled) return; 518 const setTimer = resolveSetTimer(); 519 if (!setTimer) { 520 flush(); 521 return; 522 } 523 flushTimer = setTimer(flush, 250); 524 }, "scheduleFlush"); 525 const startEngagementSegment = /* @__PURE__ */ __name(() => { 526 if (engagementSegmentStart === null) engagementSegmentStart = perf.now(); 527 }, "startEngagementSegment"); 528 const stopEngagementSegment = /* @__PURE__ */ __name(() => { 529 engagementSegmentStart = null; 530 }, "stopEngagementSegment"); 531 const clearEngagementTimer = /* @__PURE__ */ __name(() => {
532 const clearTimer = resolveClearTimer(); 533 if (engagementTimer !== null && clearTimer) clearTimer(engagementTimer); 534 engagementTimer = null; 535 }, "clearEngagementTimer"); 536 const takeEngagementDelta = /* @__PURE__ */ __name(() => { 537 if (engagementSegmentStart !== null) { 538 const nowMs = perf.now(); 539 engagementAccruedMs += nowMs - engagementSegmentStart; 540 engagementSegmentStart = nowMs; 541 } 542 const delta = Math.round(engagementAccruedMs); 543 engagementAccruedMs = 0; 544 return delta; 545 }, "takeEngagementDelta"); 546 const emitEngagement = /* @__PURE__ */ __name((reason) => { 547 const delta = takeEngagementDelta(); 548 if (delta <= 0) return; 549 engagementReportedMs += delta; 550 const capping = engagementReportedMs >= ENGAGEMENT_ACCRUAL_STOP_MS; 551 if (capping) { 552 engagementCapped = true; 553 stopEngagementSegment(); 554 clearEngagementTimer(); 555 } 556 enqueue("lovable.page_engagement", { reason: capping ? "capped" : reason, engagement_time_ms: delta }); 557 }, "emitEngagement"); 558 const scheduleEngagementEmit = /* @__PURE__ */ __name(() => { 559 clearEngagementTimer(); 560 const setTimer = resolveSetTimer(); 561 if (!setTimer) return; 562 const delay = ENGAGEMENT_EMIT_DELAYS_MS[Math.min(engagementEmitStep, ENGAGEMENT_EMIT_DELAYS_MS.length - 1)]; 563 engagementTimer = setTimer(() => { 564 engagementEmitStep++; 565 emitEngagement("threshold"); 566 if (engagementCapped) return; 567 scheduleEngagementEmit(); 568 }, delay); 569 }, "scheduleEngagementEmit"); 570 const clearUnfocusedTimer = /* @__PURE__ */ __name(() => { 571 const clearTimer = resolveClearTimer(); 572 if (engagementUnfocusedTimer !== null) { 573 if (clearTimer) clearTimer(engagementUnfocusedTimer); 574 engagementUnfocusedSpentMs += Math.max(0, perf.now() - engagementUnfocusedArmedAt); 575 } 576 engagementUnfocusedTimer = null; 577 }, "clearUnfocusedTimer"); 578 const resetUnfocusedGrace = /* @__PURE__ */ __name(() => { 579 clearUnfocusedTimer(); 580 engagementUnfocusedSpentMs = 0; 581 }, "resetUnfocusedGrace"); 582 const scheduleUnfocusedStop = /* @__PURE__ */ __name(() => { 583 if (engagementUnfocusedTimer !== null) return; 584 if (!d || d.visibilityState === "hidden" || d.hasFocus?.() !== false) return; 585 const setTimer = resolveSetTimer(); 586 if (!setTimer) return; 587 engagementUnfocusedArmedAt = perf.now(); 588 engagementUnfocusedTimer = setTimer( 589 () => { 590 engagementUnfocusedTimer = null; 591 engagementUnfocusedSpentMs = ENGAGEMENT_UNFOCUSED_GRACE_MS; 592 engagementStoppedUnfocused = true; 593 emitEngagement("unfocused"); 594 stopEngagementSegment(); 595 clearEngagementTimer(); 596 }, 597 Math.max(0, ENGAGEMENT_UNFOCUSED_GRACE_MS - engagementUnfocusedSpentMs) 598 ); 599 }, "scheduleUnfocusedStop"); 600 const resumeEngagementMeasurement = /* @__PURE__ */ __name(() => { 601 if (d && d.visibilityState === "hidden") return; 602 if (engagementStoppedUnfocused) return; 603 if (engagementCapped) return; 604 startEngagementSegment(); 605 engagementEmitStep = 0; 606 scheduleEngagementEmit(); 607 scheduleUnfocusedStop(); 608 }, "resumeEngagementMeasurement"); 609 const rotatePageViewId = /* @__PURE__ */ __name(() => { 610 emitEngagement("navigation"); 611 engagementReportedMs = 0; 612 engagementCapped = false; 613 resumeEngagementMeasurement(); 614 pageViewId = randomId(); 615 traceContext = createTraceContext(); 616 capturedClicks = 0; 617 return pageViewId; 618 }, "rotatePageViewId"); 619 const traceFieldsFor = /* @__PURE__ */ __name((eventName) => { 620 if (eventName === "lovable.page_viewed") { 621 return { 622 trace_id: traceContext.traceId, 623 span_id: traceContext.rootSpanId 624 }; 625 } 626 return { 627 trace_id: traceContext.traceId, 628 span_id: randomTraceHex(8), 629 parent_span_id: traceContext.rootSpanId 630 }; 631 }, "traceFieldsFor"); 632 const isErrorEventName = /* @__PURE__ */ __name((eventName) => eventName === "lovable.client_error" || eventName === "lovable.resource_error" || eventName === "lovable.replay_error", "isErrorEventName"); 633 const queueEvent = /* @__PURE__ */ __name((eventName, eventSchemaVersion, properties = {}) => { 634 if (sendingDisabled) return null; 635 if (isErrorEventName(eventName) && ++errorEventsSeen > maxErrorEventsPerPageLoad) { 636 if (eventName === "lovable.client_error") markReplayCapturedByClientError(null); 637 return null; 638 } 639 const flushNow = eventName === "lovable.client_error" && immediateErrorFlushes < maxImmediateErrorFlushes; 640 if (flushNow) immediateErrorFlushes++; 641 const eventId = randomId(); 642 const activeReplayId = replayId && !replayFinalized && (replayUploadEnabled || eventName === "lovable.client_error") ? replayId : null; 643 const event = { 644 event_id: eventId, 645 event_name: eventName, 646 event_schema_version: eventSchemaVersion, 647 event_time: (/* @__PURE__ */ new Date()).toISOString(), 648 session_id: sessionId, 649 page_view_id: pageViewId, 650 ...traceFieldsFor(eventName), 651 properties 652 }; 653 if (activeReplayId) event.replay_id = activeReplayId; 654 if (flushNow) { 655 queue.unshift(event); 656 } else { 657 queue.push(event); 658 } 659 if (eventName === "lovable.client_error") markReplayCapturedByClientError(eventId); 660 if (flushNow || queue.length >= batchSize) { 661 flush(); 662 } else { 663 scheduleFlush(); 664 } 665 return eventId; 666 }, "queueEvent"); 667 const enqueue = /* @__PURE__ */ __name((eventName, properties = {}) => { 668 const eventSchemaVersion = typeof eventName === "string" ? schemaVersionFor(eventName) : null; 669 if (eventSchemaVersion === null) return null; 670 return queueEvent(eventName, eventSchemaVersion, properties); 671 }, "enqueue"); 672 let lastPageViewPath = loc.pathname; 673 const track = /* @__PURE__ */ __name((eventName, properties = {}) => { 674 if (eventName === "lovable.page_viewed") { 675 rotatePageViewId(); 676 if (typeof properties.url_path === "string") lastPageViewPath = properties.url_path; 677 } 678 return enqueue(eventName, properties); 679 }, "track"); 680 const pathFromUrl = /* @__PURE__ */ __name((value) => { 681 try { 682 return new URL(value, loc.href).pathname; 683 } catch { 684 return null; 685 } 686 }, "pathFromUrl"); 687 const hostFromUrl = /* @__PURE__ */ __name((value) => { 688 try { 689 return new URL(value, loc.href).host.replace(/^www\./iu, ""); 690 } catch { 691 return null; 692 } 693 }, "hostFromUrl"); 694 const truncateToJSONBytes = /* @__PURE__ */ __name((input, maxBytes) => { 695 let result = input; 696 while (result && byteLength(JSON.stringify(result)) > maxBytes) { 697 result = result.slice(0, Math.floor(result.length * 0.8)); 698 } 699 return result; 700 }, "truncateToJSONBytes"); 701 const serializeThrowable = /* @__PURE__ */ __name((value) => { 702 const throwable = value && typeof value === "object" ? value : null; 703 const stringifyPrimitive = /* @__PURE__ */ __name((input) => { 704 if (typeof input === "string") return input; 705 if (typeof input === "number" || typeof input === "boolean" || typeof input === "bigint") return String(input); 706 return null; 707 }, "stringifyPrimitive"); 708 const stack = stringifyPrimitive(throwable?.stack); 709 const message3 = stringifyPrimitive(throwable?.message) ?? stringifyPrimitive(value) ?? "Unknown error"; 710 return { 711 message: truncateToJSONBytes(message3.slice(0, 2e3), 2048), 712 name: stringifyPrimitive(throwable?.name), 713 stack: stack ? truncateToJSONBytes(stack.slice(0, 8e3), 8192) : null 714 }; 715 }, "serializeThrowable"); 716 const emitPageView = /* @__PURE__ */ __name((navigationType, rotatePageView = true) => { 717 if (rotatePageView) rotatePageViewId();
718 const pagePath = loc.pathname; 719 const properties = { 720 url_path: pagePath, 721 navigation_type: navigationType 722 }; 723 if (typeof d?.referrer === "string" && d.referrer) { 724 properties.referrer_host = hostFromUrl(d.referrer) ?? ""; 725 properties.referrer_path = pathFromUrl(d.referrer) ?? ""; 726 } 727 lastPageViewPath = pagePath; 728 enqueue("lovable.page_viewed", properties); 729 }, "emitPageView"); 730 const emitPageViewIfPathChanged = /* @__PURE__ */ __name((navigationType) => { 731 if (loc.pathname === lastPageViewPath) return; 732 emitPageView(navigationType); 733 }, "emitPageViewIfPathChanged"); 734 const startReplayRecording = /* @__PURE__ */ __name(() => { 735 const record = replayMode === "rrweb" && isReplayRecordingAllowed && w.rrweb && typeof w.rrweb.record === "function" ? w.rrweb.record : null; 736 if (!record || replayId) return; 737 replayId = randomId(); 738 replayStartedAt = perf.now(); 739 replayFinalized = false; 740 replayChunkSequence = 0; 741 replayThrottledChunks = 0; 742 replayChunksInFlight = 0; 743 replayBudgetStopReason = null; 744 replayUploadedBytes = 0; 745 replayRecorderActive = false; 746 replayHasFullSnapshot = false; 747 replayUploadEnabled = isReplaySampledIn; 748 replayCaptureReason = isReplaySampledIn ? "sampled" : null; 749 replayTriggerEventId = null; 750 replayTriggerEventName = null; 751 replayCaptureMetadataEmitted = false; 752 try { 753 const stop = record({ 754 emit(event) { 755 if (replayFinalized) return; 756 if (!event || typeof event !== "object") return; 757 if (replayDurationMs() >= maxReplayDurationMs) { 758 stopReplayAtDurationLimit(); 759 return; 760 } 761 const eventType = replayEventType(event); 762 if (!canSendReplayEvent(event)) { 763 if (eventType === 2 && !replayHasFullSnapshot) { 764 stopReplayRecording("full_snapshot_too_large", true); 765 } 766 return; 767 } 768 if (eventType === 3 && !replayHasFullSnapshot) return; 769 if (eventType === 2) replayHasFullSnapshot = true; 770 replayEventCount += 1; 771 replayQueue.push(event); 772 while (!replayUploadEnabled && replayQueue.length > maxReplayBufferedEvents) { 773 const removableIndex = replayQueue.findIndex((queuedEvent) => replayEventType(queuedEvent) !== 2); 774 replayQueue.splice(removableIndex >= 0 ? removableIndex : 0, 1); 775 replayEventCount = Math.max(0, replayEventCount - 1); 776 } 777 if (emitReplayCaptureMetadataIfReady()) return; 778 if (!replayUploadEnabled) return; 779 if (replayQueue.length >= maxReplayEventsPerChunk) { 780 flushReplay(); 781 } else { 782 scheduleReplayFlush(); 783 } 784 }, 785 blockClass: "rr-block", 786 blockSelector: replayBlockSelector, 787 collectFonts: false, 788 ignoreClass: "rrweb-ignore", 789 ignoreSelector: replayIgnoreSelector, 790 inlineImages: false, 791 maskAllInputs: true, 792 maskInputFn: maskReplayText, 793 maskInputOptions: { 794 checkbox: false, 795 color: false, 796 date: true, 797 "datetime-local": true, 798 email: true, 799 month: true, 800 number: true, 801 password: true, 802 radio: false, 803 range: false, 804 search: true, 805 select: true, 806 tel: true, 807 text: true, 808 textarea: true, 809 time: true, 810 url: true, 811 week: true 812 }, 813 maskTextClass: "rr-mask", 814 maskTextFn: maskReplayText, 815 maskTextSelector: replayMaskTextSelector, 816 recordAfter: "load", 817 recordCanvas: false, 818 recordCrossOriginIframes: false 819 }); 820 if (replayFinalized) { 821 if (typeof stop === "function") { 822 try { 823 stop(); 824 } catch { 825 } 826 } 827 return; 828 } 829 replayRecorderActive = true; 830 if (typeof stop === "function") stopReplayRecorder = stop; 831 const setTimer = resolveSetTimer(); 832 if (setTimer && typeof record.takeFullSnapshot === "function") { 833 setTimer(() => { 834 if (!replayId || replayFinalized) return; 835 try { 836 record.takeFullSnapshot?.(false); 837 } catch { 838 } 839 }, 1e3); 840 } 841 if (setTimer) { 842 replayDurationTimer = setTimer(stopReplayAtDurationLimit, maxReplayDurationMs); 843 } 844 if (shouldEmitReplayLifecycleMetadata()) { 845 emitReplayMetadata("started", "recorder_available"); 846 } 847 } catch { 848 if (replayId) { 849 emitReplayMetadataOrErrorFor( 850 replayId, 851 "stopped", 852 "recorder_start_failed", 853 replayEventCount, 854 replayDurationMs(), 855 replayChunkSequence, 856 replayUploadedBytes 857 ); 858 } 859 replayId = null; 860 replayEventCount = 0; 861 replayStartedAt = 0; 862 replayFinalized = false;
863 replayHasFullSnapshot = false; 864 replayUploadEnabled = isReplaySampledIn; 865 replayCaptureReason = isReplaySampledIn ? "sampled" : null; 866 replayTriggerEventId = null; 867 replayTriggerEventName = null; 868 stopReplayRecorder = null; 869 replayRecorderActive = false; 870 clearReplayDurationTimer(); 871 } 872 }, "startReplayRecording"); 873 const ensureReplayRecorder = /* @__PURE__ */ __name(() => { 874 if (replayMode !== "rrweb" || !isReplayRecordingAllowed || replayId || replayRecorderLoading) return; 875 if (w.rrweb && typeof w.rrweb.record === "function") { 876 startReplayRecording(); 877 return; 878 } 879 const head = d?.head; 880 if (!d?.createElement || !head?.appendChild) return; 881 const recorderScript = d.createElement("script"); 882 replayRecorderLoading = true; 883 recorderScript.async = true; 884 recorderScript.src = replayScriptUrl; 885 recorderScript.onload = () => { 886 replayRecorderLoading = false; 887 if (!w.rrweb || typeof w.rrweb.record !== "function") { 888 emitReplayMetadataOrErrorFor(randomId(), "stopped", "recorder_script_failed", 0, 0, 0, 0); 889 return; 890 } 891 startReplayRecording(); 892 }; 893 recorderScript.onerror = () => { 894 replayRecorderLoading = false; 895 emitReplayMetadataOrErrorFor(randomId(), "stopped", "recorder_script_failed", 0, 0, 0, 0); 896 }; 897 head.appendChild(recorderScript); 898 }, "ensureReplayRecorder"); 899 const finalizeReplayRecording = /* @__PURE__ */ __name((reason) => { 900 if (!replayId || replayFinalized) return; 901 stopReplayRecorderSafely(); 902 clearReplayDurationTimer(); 903 if (!replayHasFullSnapshot) { 904 stopReplayRecording("missing_full_snapshot", true); 905 return; 906 } 907 flushReplay(true); 908 if (!replayFinalized) stopReplayRecording(reason); 909 }, "finalizeReplayRecording"); 910 const stopReplayRecordingOnRuntimeError = /* @__PURE__ */ __name((event) => { 911 if (!replayId || replayFinalized) return; 912 const errorEvent = event && typeof event === "object" ? event : null; 913 const errorRecord = errorEvent?.error && typeof errorEvent.error === "object" ? errorEvent.error : null; 914 const filename = typeof errorEvent?.filename === "string" ? errorEvent.filename : ""; 915 const stack = typeof errorRecord?.stack === "string" ? errorRecord.stack : ""; 916 const isRecorderError = filename.includes("/__l5e/rrweb-record") || stack.includes("/__l5e/rrweb-record"); 917 if (isRecorderError) { 918 stopReplayRecording("recorder_runtime_error", true); 919 } 920 }, "stopReplayRecordingOnRuntimeError"); 921 const webVitalRating = /* @__PURE__ */ __name((metric, value) => { 922 const thresholds = { 923 CLS: [0.1, 0.25], 924 FCP: [1800, 3e3], 925 INP: [200, 500], 926 LCP: [2500, 4e3], 927 TTFB: [800, 1800] 928 }; 929 const threshold = thresholds[metric]; 930 if (!threshold) return "good"; 931 if (value <= threshold[0]) return "good"; 932 return value <= threshold[1] ? "needs-improvement" : "poor"; 933 }, "webVitalRating"); 934 const webVitalUnit = /* @__PURE__ */ __name((metric) => metric === "CLS" ? "score" : "millisecond", "webVitalUnit"); 935 const emitWebVital = /* @__PURE__ */ __name((metric, value, delta = value, id2 = randomId()) => { 936 if (!Number.isFinite(value) || value < 0) return; 937 enqueue("lovable.web_vital", { 938 metric, 939 value, 940 unit: webVitalUnit(metric), 941 delta, 942 rating: webVitalRating(metric, value), 943 id: id2, 944 navigation_type: "initial_load" 945 }); 946 }, "emitWebVital"); 947 const performanceEntryValue = /* @__PURE__ */ __name((entry, key) => { 948 const value = entry[key]; 949 return typeof value === "number" && Number.isFinite(value) ? value : null; 950 }, "performanceEntryValue"); 951 const observePerformanceEntries = /* @__PURE__ */ __name((entryType, callback) => { 952 const ObserverCtor = w.PerformanceObserver; 953 if (!ObserverCtor) return false; 954 const supportedEntryTypes = ObserverCtor.supportedEntryTypes; 955 if (Array.isArray(supportedEntryTypes) && !supportedEntryTypes.includes(entryType)) return false; 956 try { 957 const observer = new ObserverCtor((list) => { 958 const entries = typeof list.getEntries === "function" ? list.getEntries() : [];
959 for (const entry of entries) { 960 if (entry && typeof entry === "object") callback(entry); 961 } 962 }); 963 observer.observe( 964 entryType === "event" ? { type: entryType, buffered: true, durationThreshold: 40 } : { type: entryType, buffered: true } 965 ); 966 return true; 967 } catch { 968 return false; 969 } 970 }, "observePerformanceEntries"); 971 const observeWebVitals = /* @__PURE__ */ __name(() => { 972 let fcpReported = false; 973 let latestLcp = null; 974 let clsValue = 0; 975 let maxInp = 0; 976 let finalized = false; 977 const metricIds = {}; 978 const reportedValues = {}; 979 const metricId = /* @__PURE__ */ __name((metric) => { 980 metricIds[metric] = metricIds[metric] || randomId(); 981 return metricIds[metric]; 982 }, "metricId"); 983 const reportMetric = /* @__PURE__ */ __name((metric, value) => { 984 if (reportedValues[metric] === value) return; 985 const previousValue = reportedValues[metric]; 986 reportedValues[metric] = value; 987 emitWebVital(metric, value, previousValue === void 0 ? value : value - previousValue, metricId(metric)); 988 }, "reportMetric"); 989 const navigationEntries = typeof perf.getEntriesByType === "function" ? perf.getEntriesByType("navigation") : []; 990 const navigationEntry = navigationEntries[0] && typeof navigationEntries[0] === "object" ? navigationEntries[0] : null; 991 const responseStart = navigationEntry ? performanceEntryValue(navigationEntry, "responseStart") : null; 992 const requestStart = navigationEntry ? performanceEntryValue(navigationEntry, "requestStart") : null; 993 const fetchStart = navigationEntry ? performanceEntryValue(navigationEntry, "fetchStart") : null; 994 const ttfbStart = requestStart ?? fetchStart; 995 if (responseStart !== null && ttfbStart !== null && responseStart >= ttfbStart) { 996 emitWebVital("TTFB", responseStart - ttfbStart); 997 } 998 observePerformanceEntries("paint", (entry) => { 999 if (fcpReported || entry.name !== "first-contentful-paint") return; 1000 const startTime = performanceEntryValue(entry, "startTime"); 1001 if (startTime === null) return; 1002 fcpReported = true; 1003 emitWebVital("FCP", startTime); 1004 }); 1005 observePerformanceEntries("largest-contentful-paint", (entry) => { 1006 latestLcp = performanceEntryValue(entry, "startTime") ?? performanceEntryValue(entry, "renderTime") ?? performanceEntryValue(entry, "loadTime") ?? latestLcp; 1007 }); 1008 const clsObserverStarted = observePerformanceEntries("layout-shift", (entry) => { 1009 if (entry.hadRecentInput === true) return; 1010 const value = performanceEntryValue(entry, "value"); 1011 if (value !== null) clsValue += value; 1012 }); 1013 observePerformanceEntries("event", (entry) => { 1014 const interactionId = performanceEntryValue(entry, "interactionId"); 1015 if (interactionId === null || interactionId <= 0) return; 1016 const duration = performanceEntryValue(entry, "duration"); 1017 if (duration !== null && duration > maxInp) maxInp = duration; 1018 }); 1019 return (final = false) => { 1020 if (finalized) return; 1021 if (latestLcp !== null) reportMetric("LCP", latestLcp); 1022 if (clsObserverStarted) reportMetric("CLS", clsValue); 1023 if (maxInp > 0) reportMetric("INP", maxInp); 1024 if (final) finalized = true; 1025 }; 1026 }, "observeWebVitals"); 1027 const patchHistory = /* @__PURE__ */ __name((method, navigationType) => { 1028 const historyObject = w.history; 1029 const original = historyObject && historyObject[method]; 1030 if (!historyObject || typeof original !== "function") return; 1031 historyObject[method] = function(...args) { 1032 const result = original.apply(this, args); 1033 emitPageViewIfPathChanged(navigationType); 1034 return result; 1035 }; 1036 }, "patchHistory"); 1037 const normalizeCaptureExceptionOptions = /* @__PURE__ */ __name((options) => { 1038 const record = options && typeof options === "object" && !Array.isArray(options) ? options : {}; 1039 const mechanism = typeof record.mechanism === "string" ? record.mechanism : "manual"; 1040 const severity = typeof record.severity === "string" ? record.severity : "error"; 1041 return { 1042 mechanism: ["manual", "onerror", "unhandledrejection", "react_error_boundary"].includes(mechanism) ? mechanism : "manual", 1043 handled: typeof record.handled === "boolean" ? record.handled : true, 1044 severity: ["error", "warning", "info"].includes(severity) ? severity : "error" 1045 }; 1046 }, "normalizeCaptureExceptionOptions"); 1047 w.__lovableEvents = Object.assign(existing, { 1048 __userAppEventsSdkVersion: version, 1049 track, 1050 flush: /* @__PURE__ */ __name(() => flush(), "flush"), 1051 identify(identity) { 1052 const identityRecord = identity && typeof identity === "object" ? identity : null; 1053 appUserId = typeof identityRecord?.user_id === "string" ? identityRecord.user_id : null; 1054 }, 1055 getIdentity() { 1056 return { app_user_id_hash_available: !!appUserId }; 1057 }, 1058 getReplay() { 1059 return { 1060 replay_id: replayId, 1061 active: !!replayId && !replayFinalized && replayRecorderActive, 1062 event_count: replayEventCount 1063 }; 1064 }, 1065 resetIdentity() { 1066 appUserId = null; 1067 }, 1068 captureException(error, context = {}, options = {}) { 1069 const throwable = serializeThrowable(error); 1070 const captureOptions = normalizeCaptureExceptionOptions(options); 1071 let captureContext = context && typeof context === "object" && !Array.isArray(context) ? context : {};
1072 try { 1073 if (byteLength(JSON.stringify(captureContext)) > 4096) captureContext = {}; 1074 } catch { 1075 captureContext = {}; 1076 } 1077 enqueue("lovable.client_error", { 1078 occurrence_id: randomId(), 1079 ...throwable, 1080 ...captureOptions, 1081 context: captureContext 1082 }); 1083 }, 1084 startSpan(name, attributes = {}) { 1085 const startTime = (/* @__PURE__ */ new Date()).toISOString(); 1086 const start = perf.now(); 1087 return { 1088 end(status = "ok") { 1089 enqueue("lovable.frontend_span", { 1090 name, 1091 start_time: startTime, 1092 status, 1093 duration_ms: Math.max(0, perf.now() - start), 1094 attributes 1095 }); 1096 } 1097 }; 1098 } 1099 }); 1100 if (w.addEventListener) { 1101 w.addEventListener("error", stopReplayRecordingOnRuntimeError, true); 1102 } 1103 ensureReplayRecorder(); 1104 const finalizeWebVitals = observeWebVitals(); 1105 if (w.addEventListener) { 1106 w.addEventListener( 1107 "error", 1108 (event) => { 1109 const errorEvent = event && typeof event === "object" ? event : null; 1110 const target = errorEvent?.target && typeof errorEvent.target === "object" ? errorEvent.target : null; 1111 let resourceUrl = null; 1112 if (target && target !== w) { 1113 if (typeof target.src === "string") { 1114 resourceUrl = target.src; 1115 } else if (typeof target.href === "string") { 1116 resourceUrl = target.href; 1117 } 1118 } 1119 if (resourceUrl !== null) { 1120 const resourceType = typeof target?.tagName === "string" ? target.tagName.toLowerCase() : "unknown"; 1121 enqueue("lovable.resource_error", { 1122 resource_type: resourceType, 1123 url_host: hostFromUrl(resourceUrl) ?? "", 1124 failure_type: "load_error", 1125 element: resourceType, 1126 url_path: pathFromUrl(resourceUrl), 1127 mechanism: "resource_load", 1128 handled: false, 1129 severity: "error" 1130 }); 1131 return; 1132 } 1133 const throwable = serializeThrowable(errorEvent?.error || errorEvent?.message); 1134 enqueue("lovable.client_error", { 1135 occurrence_id: randomId(), 1136 ...throwable, 1137 filename: typeof errorEvent?.filename === "string" ? pathFromUrl(errorEvent.filename) : null, 1138 lineno: typeof errorEvent?.lineno === "number" ? errorEvent.lineno : null, 1139 colno: typeof errorEvent?.colno === "number" ? errorEvent.colno : null, 1140 mechanism: "onerror", 1141 handled: false, 1142 severity: "error" 1143 }); 1144 }, 1145 true 1146 ); 1147 w.addEventListener("unhandledrejection", (event) => { 1148 const rejectionEvent = event && typeof event === "object" ? event : null; 1149 const throwable = serializeThrowable(rejectionEvent?.reason); 1150 enqueue("lovable.client_error", { 1151 occurrence_id: randomId(), 1152 ...throwable, 1153 mechanism: "unhandledrejection", 1154 handled: false, 1155 severity: "error" 1156 }); 1157 }); 1158 w.addEventListener("pagehide", () => { 1159 unloadingPage = true; 1160 finalizeWebVitals(true); 1161 finalizeReplayRecording("pagehide"); 1162 emitEngagement("pagehide"); 1163 stopEngagementSegment(); 1164 clearEngagementTimer(); 1165 clearUnfocusedTimer(); 1166 flush(true); 1167 }); 1168 w.addEventListener("blur", () => scheduleUnfocusedStop()); 1169 w.addEventListener("focus", () => { 1170 resetUnfocusedGrace(); 1171 if (!engagementStoppedUnfocused) return; 1172 engagementStoppedUnfocused = false; 1173 resumeEngagementMeasurement(); 1174 }); 1175 w.addEventListener("pageshow", (event) => { 1176 const persisted = event && typeof event === "object" ? event.persisted : false; 1177 if (persisted === true) { 1178 unloadingPage = false; 1179 releaseBeaconBytes(); 1180 resetUnfocusedGrace(); 1181 engagementStoppedUnfocused = false; 1182 resumeEngagementMeasurement(); 1183 } 1184 }); 1185 w.addEventListener("popstate", () => emitPageViewIfPathChanged("popstate")); 1186 w.addEventListener("hashchange", () => emitPageView("hashchange")); 1187 if (autocaptureEnabled) { 1188 const labelMaxLength = 64; 1189 const tagMaxLength = 64; 1190 const urlPathMaxLength = 2048; 1191 const ownTextMaxLength = 200; 1192 const chainMaxDepth = 32; 1193 const clickCapPerPageView = 200; 1194 const optOutMaxDepth = 512; 1195 const pathMaxDepth = 32; 1196 const pathMaxLength = 1024; 1197 const valueBearingTags = { 1198 select: true, 1199 textarea: true, 1200 input: true 1201 }; 1202 const interactiveTags = { 1203 button: true, 1204 a: true, 1205 input: true, 1206 select: true, 1207 textarea: true, 1208 summary: true 1209 }; 1210 const interactiveRoles = { 1211 button: true, 1212 link: true, 1213 menuitem: true, 1214 menuitemcheckbox: true, 1215 menuitemradio: true, 1216 tab: true, 1217 switch: true, 1218 checkbox: true, 1219 radio: true, 1220 option: true 1221 }; 1222 const landmarkTags = { 1223 header: true, 1224 footer: true, 1225 main: true, 1226 nav: true, 1227 aside: true, 1228 section: true, 1229 article: true, 1230 form: true 1231 }; 1232 const jsxSourceKey = typeof Symbol !== "undefined" && typeof Symbol.for === "function" ? /* @__P
1232URE__ */ Symbol.for("__jsxSource__") : null; 1233 const asNode = /* @__PURE__ */ __name((value) => value && typeof value === "object" && typeof value.tagName === "string" ? value : null, "asNode"); 1234 const knownTags = {}; 1235 for (const tag of "html body div span main header footer nav aside section article form p a button input select textarea label fieldset legend optgroup option datalist output progress meter ul ol li dl dt dd table thead tbody tfoot tr td th caption col colgroup h1 h2 h3 h4 h5 h6 br hr pre blockquote figure figcaption small strong em b i u s q cite abbr code kbd samp var mark sub sup time address details summary dialog menu picture source track canvas iframe object embed param map area img video audio template slot noscript script style link meta title base head ins del ruby rt rp bdi bdo wbr data hgroup search fencedframe svg path g circle rect line polyline polygon ellipse text textpath tspan defs use symbol clippath mask pattern lineargradient radialgradient stop filter foreignobject marker image view desc animate animatetransform animatemotion set fegaussianblur feoffset feblend fecolormatrix fecomposite feflood femerge femergenode femorphology fetile feturbulence fedropshadow fedisplacementmap".split(" ")) { 1236 knownTags[tag] = true; 1237 } 1238 const knownRoles = {}; 1239 for (const role of "button link menuitem menuitemcheckbox menuitemradio tab switch checkbox radio option combobox listbox textbox searchbox slider spinbutton treeitem gridcell row columnheader rowheader tooltip dialog alertdialog navigation main banner contentinfo complementary region form search article list listitem table grid tree menu menubar toolbar tabpanel tablist progressbar status alert log timer none presentation".split(" ")) { 1240 knownRoles[role] = true; 1241 } 1242 const knownInputTypes = {}; 1243 for (const type of "button submit reset checkbox radio text password email number tel url search date time datetime-local month week color file range hidden image".split(" ")) { 1244 knownInputTypes[type] = true; 1245 } 1246 const knownAttrValue = /* @__PURE__ */ __name((value, vocabulary) => value !== null && vocabulary[value.toLowerCase()] === true ? value.toLowerCase() : null, "knownAttrValue"); 1247 const tagOf = /* @__PURE__ */ __name((node) => { 1248 const tag = String(node.tagName).toLowerCase(); 1249 return knownTags[tag] === true ? tag : "x"; 1250 }, "tagOf"); 1251 const attrOf = /* @__PURE__ */ __name((node, name) => { 1252 if (typeof node.getAttribute !== "function") return null; 1253 const value = node.getAttribute(name); 1254 return typeof value === "string" && value !== "" ? value : null; 1255 }, "attrOf"); 1256 const hasAttr = /* @__PURE__ */ __name((node, name) => typeof node.getAttribute === "function" && typeof node.getAttribute(name) === "string", "hasAttr"); 1257 const truncateTo = /* @__PURE__ */ __name((value, maxChars) => { 1258 if (typeof value !== "string") return null; 1259 const collapsed = value.replace(/\s+/gu, " ").trim(); 1260 if (!collapsed) return null; 1261 if (collapsed.length <= maxChars) return collapsed; 1262 const points = Array.from(collapsed); 1263 return points.length > maxChars ? points.slice(0, maxChars).join("") : collapsed; 1264 }, "truncateTo"); 1265 const ownTextOf = /* @__PURE__ */ __name((node) => { 1266 if (valueBearingTags[tagOf(node)] === true) return null; 1267 const children = node.childNodes; 1268 if (!children || typeof children.length !== "number") return null; 1269 const list = children; 1270 let text = ""; 1271 for (let index = 0; index < list.length; index++) { 1272 const child = list[index]; 1273 if (child && child.nodeType === 3 && typeof child.nodeValue === "string") text += child.nodeValue; 1274 if (text.length > ownTextMaxLength) return null; 1275 } 1276 return text === "" ? null : text; 1277 }, "ownTextOf"); 1278 const labelOf = /* @__PURE__ */ __name((node) => truncateTo( 1279 attrOf(node, "aria-label") || attrOf(node, "title") || attrOf(node, "alt") || ownTextOf(node), 1280 labelMaxLength 1281 ), "labelOf"); 1282 const nthOfType = /* @__PURE__ */ __name((parent, node, tag) => { 1283 const children = parent.children; 1284 if (!children || typeof children.length !== "number") return ""; 1285 const list = children; 1286 let position = 0; 1287 let total = 0; 1288 for (let index = 0; index < list.length; index++) { 1289 const sibling = asNode(list[index]); 1290 if (!sibling || tagOf(sibling) !== tag) continue; 1291 total++; 1292 if (sibling === node) position = total; 1293 } 1294 return position > 0 ? ":" + position : ""; 1295 }, "nthOfType"); 1296 const pathOf = /* @__PURE__ */ __name((node) => { 1297 const segments = []; 1298 let current = node; 1299 for (let depth = 0; current && depth < pathMaxDepth; depth++) { 1300 const tag = tagOf(current); 1301 if (!tag || tag === "html") break; 1302 const parent = asNode(current.parentElement); 1303 segments.unshift(parent && tag !== "body" ? tag + nthOfType(parent, current, tag) : tag); 1304 if (tag === "body" || !parent) break; 1305 current = parent; 1306 } 1307 if (segments.length === 0) return null; 1308 let path = segments.join(">"); 1309 while (path.length > pathMaxLength && segments.length > 1) { 1310 segments.shift(); 1311 path = segments.join(">"); 1312 } 1313 return path.length > pathMaxLength ? path.slice(path.length - pathMaxLength) : path; 1314 }, "pathOf"); 1315 const componentNameOf = /* @__PURE__ */ __name((node) => { 1316 if (jsxSourceKey === null) return null; 1317 const source = node[jsxSourceKey]; 1318 if (!source || typeof source !== "object") return null; 1319 const name = source.displayName; 1320 return typeof name === "string" && name !== "" ? name : null; 1321 }, "componentNameOf"); 1322 const isInteractive = /* @__PURE__ */ __name((node) => { 1323 if (interactiveTags[tagOf(node)] === true) return true; 1324 const role = attrOf(node, "role"); 1325 return role !== null && interactiveRoles[role.toLowerCase()] === true; 1326 }, "isInteractive"); 1327 const chainFrom = /* @__PURE__ */ __name((event, maxDepth) => { 1328 const composed = typeof event.composedPath === "function" ? event.composedPath() : null; 1329 if (Array.isArray(composed)) { 1330 const nodes2 = []; 1331 for (const entry of composed) { 1332 if (nodes2.length >= maxDepth) return { nodes: nodes2, complete: false }; 1333 const node2 = asNode(entry); 1334 if (node2) nodes2.push(node2); 1335 } 1336 return { nodes: nodes2, complete: true }; 1337 } 1338 const nodes = []; 1339 let node = asNode(event.target); 1340 while (node) { 1341 if (nodes.length >= maxDepth) return { nodes, complete: false }; 1342 nodes.push(node); 1343 node = asNode(node.parentElement); 1344 } 1345 return { nodes, complete: true }; 1346 }, "chainFrom"); 1347 const isPointerType = /* @__PURE__ */ __name((value) => value === "mouse" || value === "touch" || value === "pen", "isPointerType"); 1348 const pointerTypeMaxAgeMs = 2e3; 1349 const forwardedClickMaxAgeMs = 300; 1350 let lastPointerType = ""; 1351 let lastPointerAt = 0; 1352 const recentPointerType = /* @__PURE__ */ __name((maxAgeMs) => lastPointerType !== "" && perf.now() - lastPointerAt <= maxAgeMs ? lastPointerType : "", "recentPointerType"); 1353 w.addEventListener( 1354 "pointerdown", 1355 (event) => { 1356 try { 1357 const pointerEvent = event; 1358 if (pointerEvent.isTrusted !== true) return; 1359 const pointerType = typeof pointerEvent.pointerType === "string" ? pointerEvent.pointerType : ""; 1360 if (isPointerType(pointerType)) { 1361 lastPointerType = pointerType; 1362 lastPointerAt = perf.now(); 1363 } 1364 } catch { 1365 } 1366 }, 1367 true 1368 ); 1369 const roundUnit = /* @__PURE__ */ __name((value) => Math.round(value * 1e3) / 1e3, "roundUnit"); 1370 const numberOr = /* @__PURE__ */ __name((value) => typeof value === "number" && isFinite(value) ? value : null, "numberOr"); 1371 w.addEventListener( 1372 "click", 1373 (event) => { 1374 try { 1375 if (!event || typeof event !== "object") return; 1376 const clickEvent = event; 1377 if (clickEvent.isTrusted !== true) return; 1378 if (capturedClicks >= clickCapPerPageView) return; 1379 const chain = chainFrom(clickEvent, optOutMaxDepth); 1380 if (!chain.complete) return; 1381 let element = null; 1382 let section = null; 1383 for (let depth = 0; depth < chain.nodes.length; depth++) { 1384 const node = chain.nodes[depth]; 1385 if (hasAttr(node, "data-lov-no-capture")) return; 1386 if (depth >= chainMaxDepth) continue; 1387 if (element === null) { 1388 if (isInteractive(node)) element = node; 1389 continue; 1390 } 1391 if (section === null && landmarkTags[tagOf(node)] === true) section = node; 1392 } 1393 if (element === null) return; 1394 const pointerType = typeof clickEvent.pointerType === "string" ? clickEvent.pointerType : ""; 1395 const detail = numberOr(clickEvent.detail) ?? 0; 1396 const forwarded = detail === 0 ? recentPointerType(forwardedClickMaxAgeMs) : ""; 1397 const interactionType = isPointerType(pointerType) ? pointerType : forwarded || (detail === 0 ? "keyboard" : recentPointerType(pointerTypeMaxAgeMs) || "unknown"); 1398 let offsetX = null; 1399 let offsetY = null; 1400 const clientX = numberOr(clickEvent.clientX); 1401 const clientY = numberOr(clickEvent.clientY); 1402 const rect = typeof element.getBoundingClientRect === "function" ? element.getBoundingClientRect() : null; 1403 if (interactionType !== "keyboard" && rect && clientX !== null && clientY !== null && rect.width > 0 && rect.height > 0) { 1404 offsetX = roundUnit(Math.min(1, Math.max(0, (clientX - rect.left) / rect.width))); 1405 offsetY = roundUnit(Math.min(1, Math.max(0, (clientY - rect.top) / rect.height))); 1406 } 1407 const viewport = w; 1408 enqueue("lovable.element_clicked", { 1409 // pathname only, and named url_path so the collector's path sanitizer 1410 // selects it as a second line of defence. 1411 url_path: truncateTo(loc.pathname || "/", urlPathMaxLength) || "/", 1412 element_tag: tagOf(element), 1413 interaction_type: interactionType, 1414 element_role: knownAttrValue(attrOf(element, "role"), knownRoles), 1415 element_type: knownAttrValue(attrOf(element, "type"), knownInputTypes), 1416 element_label: labelOf(element), 1417 element_path: pathOf(element), 1418 component_name: truncateTo(componentNameOf(element), tagMaxLength), 1419 section_tag: section ? truncateTo(tagOf(section), tagMaxLength) : null, 1420 section_label: section ? truncateTo(attrOf(section, "aria-label"), labelMaxLength) : null, 1421 offset_x: offsetX, 1422 offset_y: offsetY, 1423 viewport_width: numberOr(viewport.innerWidth),
1424 viewport_height: numberOr(viewport.innerHeight) 1425 }); 1426 capturedClicks++; 1427 } catch { 1428 } 1429 }, 1430 true 1431 ); 1432 } 1433 } 1434 if (d && typeof d.addEventListener === "function") { 1435 d.addEventListener("visibilitychange", () => { 1436 if (d.visibilityState === "hidden") { 1437 unloadingPage = true; 1438 finalizeWebVitals(); 1439 flushReplay(true); 1440 emitEngagement("hidden"); 1441 stopEngagementSegment(); 1442 clearEngagementTimer(); 1443 clearUnfocusedTimer(); 1444 flush(true); 1445 unloadingPage = false; 1446 return; 1447 } 1448 unloadingPage = false; 1449 releaseBeaconBytes(); 1450 resumeEngagementMeasurement(); 1451 }); 1452 } 1453 patchHistory("pushState", "push_state"); 1454 patchHistory("replaceState", "replace_state"); 1455 enqueue("lovable.session_started", { reason: "new_session" }); 1456 emitPageView("initial_load", false); 1457 resumeEngagementMeasurement(); 1458})(window, "0.2.0-no-device-storage", {"lovable.session_started":1,"lovable.page_viewed":1,"lovable.client_error":1,"lovable.frontend_span":1,"lovable.interaction":1,"lovable.element_clicked":1,"lovable.form_submitted":1,"lovable.resource_error":1,"lovable.session_replay":1,"lovable.replay_error":1,"lovable.web_vital":1,"lovable.page_engagement":1}, 50, 51200, 524288, 100, 5242880, 50, 600000, 2000);})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.