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