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