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 emitPageView = /* @__PURE__ */ __name((navigationType, rotatePageView = true) => { 760 if (rotatePageView) rotatePageViewId(); 761 const pagePath = loc.pathname; 762 const properties = { 763 url_path: pagePath, 764 navigation_type: navigationType 765 }; 766 if (typeof d?.referrer === "string" && d.referrer) { 767 properties.referrer_host = hostFromUrl(d.referrer) ?? ""; 768 properties.referrer_path = pathFromUrl(d.referrer) ?? ""; 769 } 770 Object.assign(properties, campaignPropertiesFrom(loc.search)); 771 lastPageViewPath = pagePath; 772 enqueue("lovable.page_viewed", properties); 773 }, "emitPageView"); 774 const emitPageViewIfPathChanged = /* @__PURE__ */ __name((navigationType) => { 775 if (loc.pathname === lastPageViewPath) return; 776 emitPageView(navigationType); 777 }, "emitPageViewIfPathChanged"); 778 const startReplayRecording = /* @__PURE__ */ __name(() => { 779 const record = replayMode === "rrweb" && isReplayRecordingAllowed && w.rrweb && typeof w.rrweb.record === "function" ? w.rrweb.record : null; 780 if (!record || replayId) return; 781 replayId = randomId(); 782 replayStartedAt = perf.now(); 783 replayFinalized = false; 784 replayChunkSequence = 0; 785 replayThrottledChunks = 0; 786 replayChunksInFlight = 0; 787 replayBudgetStopReason = null; 788 replayUploadedBytes = 0; 789 replayRecorderActive = false; 790 replayHasFullSnapshot = false; 791 replayUploadEnabled = isReplaySampledIn; 792 replayCaptureReason = isReplaySampledIn ? "sampled" : null; 793 replayTriggerEventId = null; 794 replayTriggerEventName = null; 795 replayCaptureMetadataEmitted = false;
796 try { 797 const stop = record({ 798 emit(event) { 799 if (replayFinalized) return; 800 if (!event || typeof event !== "object") return; 801 if (replayDurationMs() >= maxReplayDurationMs) { 802 stopReplayAtDurationLimit(); 803 return; 804 } 805 const eventType = replayEventType(event); 806 if (!canSendReplayEvent(event)) { 807 if (eventType === 2 && !replayHasFullSnapshot) { 808 stopReplayRecording("full_snapshot_too_large", true); 809 } 810 return; 811 } 812 if (eventType === 3 && !replayHasFullSnapshot) return; 813 if (eventType === 2) replayHasFullSnapshot = true; 814 replayEventCount += 1; 815 replayQueue.push(event); 816 while (!replayUploadEnabled && replayQueue.length > maxReplayBufferedEvents) { 817 const removableIndex = replayQueue.findIndex((queuedEvent) => replayEventType(queuedEvent) !== 2); 818 replayQueue.splice(removableIndex >= 0 ? removableIndex : 0, 1); 819 replayEventCount = Math.max(0, replayEventCount - 1); 820 } 821 if (emitReplayCaptureMetadataIfReady()) return; 822 if (!replayUploadEnabled) return; 823 if (replayQueue.length >= maxReplayEventsPerChunk) { 824 flushReplay(); 825 } else { 826 scheduleReplayFlush(); 827 } 828 }, 829 blockClass: "rr-block", 830 blockSelector: replayBlockSelector, 831 collectFonts: false, 832 ignoreClass: "rrweb-ignore", 833 ignoreSelector: replayIgnoreSelector, 834 inlineImages: false, 835 maskAllInputs: true, 836 maskInputFn: maskReplayText, 837 maskInputOptions: { 838 checkbox: false, 839 color: false, 840 date: true, 841 "datetime-local": true, 842 email: true, 843 month: true, 844 number: true, 845 password: true, 846 radio: false, 847 range: false, 848 search: true, 849 select: true, 850 tel: true, 851 text: true, 852 textarea: true, 853 time: true, 854 url: true, 855 week: true 856 }, 857 maskTextClass: "rr-mask", 858 maskTextFn: maskReplayText, 859 maskTextSelector: replayMaskTextSelector, 860 recordAfter: "load", 861 recordCanvas: false, 862 recordCrossOriginIframes: false 863 }); 864 if (replayFinalized) { 865 if (typeof stop === "function") { 866 try { 867 stop(); 868 } catch { 869 } 870 } 871 return; 872 } 873 replayRecorderActive = true; 874 if (typeof stop === "function") stopReplayRecorder = stop; 875 const setTimer = resolveSetTimer(); 876 if (setTimer && typeof record.takeFullSnapshot === "function") { 877 setTimer(() => { 878 if (!replayId || replayFinalized) return; 879 try { 880 record.takeFullSnapshot?.(false); 881 } catch { 882 } 883 }, 1e3); 884 } 885 if (setTimer) { 886 replayDurationTimer = setTimer(stopReplayAtDurationLimit, maxReplayDurationMs); 887 } 888 if (shouldEmitReplayLifecycleMetadata()) { 889 emitReplayMetadata("started", "recorder_available"); 890 } 891 } catch { 892 if (replayId) { 893 emitReplayMetadataOrErrorFor( 894 replayId, 895 "stopped", 896 "recorder_start_failed", 897 replayEventCount, 898 replayDurationMs(), 899 replayChunkSequence, 900 replayUploadedBytes 901 ); 902 } 903 replayId = null; 904 replayEventCount = 0; 905 replayStartedAt = 0; 906 replayFinalized = false; 907 replayHasFullSnapshot = false; 908 replayUploadEnabled = isReplaySampledIn; 909 replayCaptureReason = isReplaySampledIn ? "sampled" : null; 910 replayTriggerEventId = null; 911 replayTriggerEventName = null; 912 stopReplayRecorder = null; 913 replayRecorderActive = false; 914 clearReplayDurationTimer(); 915 } 916 }, "startReplayRecording"); 917 const ensureReplayRecorder = /* @__PURE__ */ __name(() => { 918 if (replayMode !== "rrweb" || !isReplayRecordingAllowed || replayId || replayRecorderLoading) return; 919 if (w.rrweb && typeof w.rrweb.record === "function") { 920 startReplayRecording(); 921 return; 922 } 923 const head = d?.head; 924 if (!d?.createElement || !head?.appendChild) return; 925 const recorderScript = d.createElement("script"); 926 replayRecorderLoading = true; 927 recorderScript.async = true; 928 recorderScript.src = replayScriptUrl; 929 recorderScript.onload = () => { 930 replayRecorderLoading = false;
931 if (!w.rrweb || typeof w.rrweb.record !== "function") { 932 emitReplayMetadataOrErrorFor(randomId(), "stopped", "recorder_script_failed", 0, 0, 0, 0); 933 return; 934 } 935 startReplayRecording(); 936 }; 937 recorderScript.onerror = () => { 938 replayRecorderLoading = false; 939 emitReplayMetadataOrErrorFor(randomId(), "stopped", "recorder_script_failed", 0, 0, 0, 0); 940 }; 941 head.appendChild(recorderScript); 942 }, "ensureReplayRecorder"); 943 const finalizeReplayRecording = /* @__PURE__ */ __name((reason) => { 944 if (!replayId || replayFinalized) return; 945 stopReplayRecorderSafely(); 946 clearReplayDurationTimer(); 947 if (!replayHasFullSnapshot) { 948 stopReplayRecording("missing_full_snapshot", true); 949 return; 950 } 951 flushReplay(true); 952 if (!replayFinalized) stopReplayRecording(reason); 953 }, "finalizeReplayRecording"); 954 const stopReplayRecordingOnRuntimeError = /* @__PURE__ */ __name((event) => { 955 if (!replayId || replayFinalized) return; 956 const errorEvent = event && typeof event === "object" ? event : null; 957 const errorRecord = errorEvent?.error && typeof errorEvent.error === "object" ? errorEvent.error : null; 958 const filename = typeof errorEvent?.filename === "string" ? errorEvent.filename : ""; 959 const stack = typeof errorRecord?.stack === "string" ? errorRecord.stack : ""; 960 const isRecorderError = filename.includes("/__l5e/rrweb-record") || stack.includes("/__l5e/rrweb-record"); 961 if (isRecorderError) { 962 stopReplayRecording("recorder_runtime_error", true); 963 } 964 }, "stopReplayRecordingOnRuntimeError"); 965 const webVitalRating = /* @__PURE__ */ __name((metric, value) => { 966 const thresholds = { 967 CLS: [0.1, 0.25], 968 FCP: [1800, 3e3], 969 INP: [200, 500], 970 LCP: [2500, 4e3], 971 TTFB: [800, 1800] 972 }; 973 const threshold = thresholds[metric]; 974 if (!threshold) return "good"; 975 if (value <= threshold[0]) return "good"; 976 return value <= threshold[1] ? "needs-improvement" : "poor"; 977 }, "webVitalRating"); 978 const webVitalUnit = /* @__PURE__ */ __name((metric) => metric === "CLS" ? "score" : "millisecond", "webVitalUnit"); 979 const performanceEntryValue = /* @__PURE__ */ __name((entry, key) => { 980 const value = entry[key]; 981 return typeof value === "number" && Number.isFinite(value) ? value : null; 982 }, "performanceEntryValue"); 983 const observePerformanceEntries = /* @__PURE__ */ __name((entryType, callback) => { 984 const ObserverCtor = w.PerformanceObserver; 985 if (!ObserverCtor) return false; 986 const supportedEntryTypes = ObserverCtor.supportedEntryTypes; 987 if (Array.isArray(supportedEntryTypes) && !supportedEntryTypes.includes(entryType)) return false; 988 try { 989 const observer = new ObserverCtor((list) => { 990 const entries = typeof list.getEntries === "function" ? list.getEntries() : []; 991 for (const entry of entries) { 992 if (entry && typeof entry === "object") callback(entry); 993 } 994 }); 995 observer.observe( 996 entryType === "event" ? { type: entryType, buffered: true, durationThreshold: 40 } : { type: entryType, buffered: true } 997 ); 998 return true; 999 } catch { 1000 return false; 1001 } 1002 }, "observePerformanceEntries"); 1003 const observeWebVitals = /* @__PURE__ */ __name(() => { 1004 const urlPath = currentUrlPath(); 1005 const emitWebVital = /* @__PURE__ */ __name((metric, value, delta = value, id2 = randomId()) => { 1006 if (!Number.isFinite(value) || value < 0) return; 1007 enqueue("lovable.web_vital", { 1008 url_path: urlPath, 1009 metric, 1010 value, 1011 unit: webVitalUnit(metric), 1012 delta, 1013 rating: webVitalRating(metric, value), 1014 id: id2, 1015 navigation_type: "initial_load" 1016 }); 1017 }, "emitWebVital"); 1018 let fcpReported = false; 1019 let latestLcp = null; 1020 let clsValue = 0; 1021 let maxInp = 0; 1022 let finalized = false; 1023 const metricIds = {}; 1024 const reportedValues = {}; 1025 const metricId = /* @__PURE__ */ __name((metric) => { 1026 metricIds[metric] = metricIds[metric] || randomId(); 1027 return metricIds[metric]; 1028 }, "metricId"); 1029 const reportMetric = /* @__PURE__ */ __name((metric, value) => { 1030 if (reportedValues[metric] === value) return; 1031 const previousValue = reportedValues[metric]; 1032 reportedValues[metric] = value; 1033 emitWebVital(metric, value, previousValue === void 0 ? value : value - previousValue, metricId(metric)); 1034 }, "reportMetric"); 1035 const navigationEntries = typeof perf.getEntriesByType === "function" ? perf.getEntriesByType("navigation") : []; 1036 const navigationEntry = navigationEntries[0] && typeof navigationEntries[0] === "object" ? navigationEntries[0] : null; 1037 const responseStart = navigationEntry ? performanceEntryValue(navigationEntry, "responseStart") : null; 1038 const requestStart = navigationEntry ? performanceEntryValue(navigationEntry, "requestStart") : null; 1039 const fetchStart = navigationEntry ? performanceEntryValue(navigationEntry, "fetchStart") : null; 1040 const ttfbStart = requestStart ?? fetchStart; 1041 if (responseStart !== null && ttfbStart !== null && responseStart >= ttfbStart) { 1042 emitWebVital("TTFB", responseStart - ttfbStart); 1043 } 1044 observePerformanceEntries("paint", (entry) => { 1045 if (fcpReported || entry.name !== "first-contentful-paint") return; 1046 const startTime = performanceEntryValue(entry, "startTime"); 1047 if (startTime === null) return; 1048 fcpReported = true; 1049 emitWebVital("FCP", startTime); 1050 }); 1051 observePerformanceEntries("largest-contentful-paint", (entry) => { 1052 latestLcp = performanceEntryValue(entry, "startTime") ?? performanceEntryValue(entry, "renderTime") ?? performanceEntryValue(entry, "loadTime") ?? latestLcp; 1053 }); 1054 const clsObserverStarted = observePerformanceEntries("layout-shift", (entry) => { 1055 if (entry.hadRecentInput === true) return; 1056 const value = performanceEntryValue(entry, "value"); 1057 if (value !== null) clsValue += value; 1058 }); 1059 observePerformanceEntries("event", (entry) => { 1060 const interactionId = performanceEntryValue(entry, "interactionId"); 1061 if (interactionId === null || interactionId <= 0) return; 1062 const duration = performanceEntryValue(entry, "duration"); 1063 if (duration !== null && duration > maxInp) maxInp = duration; 1064 }); 1065 return (final = false) => { 1066 if (finalized) return; 1067 if (latestLcp !== null) reportMetric("LCP", latestLcp); 1068 if (clsObserverStarted) reportMetric("CLS", clsValue); 1069 if (maxInp > 0) reportMetric("INP", maxInp); 1070 if (final) finalized = true; 1071 }; 1072 }, "observeWebVitals"); 1073 const patchHistory = /* @__PURE__ */ __name((method, navigationType) => { 1074 const historyObject = w.history; 1075 const original = historyObject && historyObject[method]; 1076 if (!historyObject || typeof original !== "function") return; 1077 historyObject[method] = function(...args) { 1078 const result = original.apply(this, args); 1079 emitPageViewIfPathChanged(navigationType); 1080 return result; 1081 }; 1082 }, "patchHistory"); 1083 const normalizeCaptureExceptionOptions = /* @__PURE__ */ __name((options) => { 1084 const record = options && typeof options === "object" && !Array.isArray(options) ? options : {}; 1085 const mechanism = typeof record.mechanism === "string" ? record.mechanism : "manual"; 1086 const severity = typeof record.severity === "string" ? record.severity : "error"; 1087 return { 1088 mechanism: ["manual", "onerror", "unhandledrejection", "react_error_boundary"].includes(mechanism) ? mechanism : "manual", 1089 handled: typeof record.handled === "boolean" ? record.handled : true, 1090 severity: ["error", "warning", "info"].includes(severity) ? severity : "error" 1091 }; 1092 }, "normalizeCaptureExceptionOptions"); 1093 w.__lovableEvents = Object.assign(existing, { 1094 __userAppEventsSdkVersion: version, 1095 track, 1096 flush: /* @__PURE__ */ __name(() => flush(), "flush"), 1097 identify(identity) { 1098 const identityRecord = identity && typeof identity === "object" ? identity : null; 1099 appUserId = typeof identityRecord?.user_id === "string" ? identityRecord.user_id : null; 1100 }, 1101 getIdentity() { 1102 return { app_user_id_hash_available: !!appUserId }; 1103 }, 1104 getReplay() { 1105 return { 1106 replay_id: replayId, 1107 active: !!replayId && !replayFinalized && replayRecorderActive, 1108 event_count: replayEventCount 1109 }; 1110 }, 1111 resetIdentity() { 1112 appUserId = null; 1113 }, 1114 captureException(error, context = {}, options = {}) { 1115 const throwable = serializeThrowable(error); 1116 const captureOptions = normalizeCaptureExceptionOptions(options); 1117 let captureContext = context && typeof context === "object" && !Array.isArray(context) ? context : {};
1118 try { 1119 if (byteLength(JSON.stringify(captureContext)) > 4096) captureContext = {}; 1120 } catch { 1121 captureContext = {}; 1122 } 1123 enqueue("lovable.client_error", { 1124 occurrence_id: randomId(), 1125 ...throwable, 1126 ...captureOptions, 1127 context: captureContext 1128 }); 1129 }, 1130 startSpan(name, attributes = {}) { 1131 const startTime = (/* @__PURE__ */ new Date()).toISOString(); 1132 const start = perf.now(); 1133 return { 1134 end(status = "ok") { 1135 enqueue("lovable.frontend_span", { 1136 name, 1137 start_time: startTime, 1138 status, 1139 duration_ms: Math.max(0, perf.now() - start), 1140 attributes 1141 }); 1142 } 1143 }; 1144 } 1145 }); 1146 if (w.addEventListener) { 1147 w.addEventListener("error", stopReplayRecordingOnRuntimeError, true); 1148 } 1149 ensureReplayRecorder(); 1150 const finalizeWebVitals = observeWebVitals(); 1151 if (w.addEventListener) { 1152 w.addEventListener( 1153 "error", 1154 (event) => { 1155 const errorEvent = event && typeof event === "object" ? event : null; 1156 const target = errorEvent?.target && typeof errorEvent.target === "object" ? errorEvent.target : null; 1157 let resourceUrl = null; 1158 if (target && target !== w) { 1159 if (typeof target.src === "string") { 1160 resourceUrl = target.src; 1161 } else if (typeof target.href === "string") { 1162 resourceUrl = target.href; 1163 } 1164 } 1165 if (resourceUrl !== null) { 1166 const resourceType = typeof target?.tagName === "string" ? target.tagName.toLowerCase() : "unknown"; 1167 enqueue("lovable.resource_error", { 1168 resource_type: resourceType, 1169 url_host: hostFromUrl(resourceUrl) ?? "", 1170 failure_type: "load_error", 1171 element: resourceType, 1172 url_path: pathFromUrl(resourceUrl), 1173 mechanism: "resource_load", 1174 handled: false, 1175 severity: "error" 1176 }); 1177 return; 1178 } 1179 const throwable = serializeThrowable(errorEvent?.error || errorEvent?.message); 1180 enqueue("lovable.client_error", { 1181 occurrence_id: randomId(), 1182 ...throwable, 1183 filename: typeof errorEvent?.filename === "string" ? pathFromUrl(errorEvent.filename) : null, 1184 lineno: typeof errorEvent?.lineno === "number" ? errorEvent.lineno : null, 1185 colno: typeof errorEvent?.colno === "number" ? errorEvent.colno : null, 1186 mechanism: "onerror", 1187 handled: false, 1188 severity: "error" 1189 }); 1190 }, 1191 true 1192 ); 1193 w.addEventListener("unhandledrejection", (event) => { 1194 const rejectionEvent = event && typeof event === "object" ? event : null; 1195 const throwable = serializeThrowable(rejectionEvent?.reason); 1196 enqueue("lovable.client_error", { 1197 occurrence_id: randomId(), 1198 ...throwable, 1199 mechanism: "unhandledrejection", 1200 handled: false, 1201 severity: "error" 1202 }); 1203 }); 1204 w.addEventListener("pagehide", () => { 1205 unloadingPage = true; 1206 finalizeWebVitals(true); 1207 finalizeReplayRecording("pagehide"); 1208 emitEngagement("pagehide"); 1209 stopEngagementSegment(); 1210 clearEngagementTimer(); 1211 clearUnfocusedTimer(); 1212 flush(true); 1213 }); 1214 w.addEventListener("blur", () => scheduleUnfocusedStop()); 1215 w.addEventListener("focus", () => { 1216 resetUnfocusedGrace(); 1217 if (!engagementStoppedUnfocused) return; 1218 engagementStoppedUnfocused = false; 1219 resumeEngagementMeasurement(); 1220 }); 1221 w.addEventListener("pageshow", (event) => { 1222 const persisted = event && typeof event === "object" ? event.persisted : false; 1223 if (persisted === true) { 1224 unloadingPage = false; 1225 releaseBeaconBytes(); 1226 resetUnfocusedGrace(); 1227 engagementStoppedUnfocused = false; 1228 resumeEngagementMeasurement(); 1229 } 1230 }); 1231 w.addEventListener("popstate", () => emitPageViewIfPathChanged("popstate")); 1232 w.addEventListener("hashchange", () => emitPageView("hashchange")); 1233 if (autocaptureEnabled) { 1234 const labelMaxLength = 64; 1235 const tagMaxLength = 64; 1236 const ownTextMaxLength = 200; 1237 const chainMaxDepth = 32; 1238 const clickCapPerPageView = 200; 1239 const optOutMaxDepth = 512; 1240 const pathMaxDepth = 32; 1241 const pathMaxLength = 1024; 1242 const valueBearingTags = { 1243 select: true, 1244 textarea: true, 1245 input: true 1246 }; 1247 const interactiveTags = { 1248 button: true, 1249 a: true, 1250 input: true, 1251 select: true, 1252 textarea: true, 1253 summary: true 1254 }; 1255 const interactiveRoles = { 1256 button: true, 1257 link: true, 1258 menuitem: true, 1259 menuitemcheckbox: true, 1260 menuitemradio: true, 1261 tab: true, 1262 switch: true, 1263 checkbox: true, 1264 radio: true, 1265 option: true 1266 }; 1267 const landmarkTags = { 1268 header: true, 1269 footer: true, 1270 main: true, 1271 nav: true, 1272 aside: true, 1273 section: true, 1274 article: true, 1275 form: true 1276 }; 1277 const jsxSourceKey = typeof Symbol !== "undefined" && typeof Symbol.for === "function" ? /* @__P
1277URE__ */ Symbol.for("__jsxSource__") : null; 1278 const asNode = /* @__PURE__ */ __name((value) => value && typeof value === "object" && typeof value.tagName === "string" ? value : null, "asNode"); 1279 const knownTags = {}; 1280 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(" ")) { 1281 knownTags[tag] = true; 1282 } 1283 const knownRoles = {}; 1284 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(" ")) { 1285 knownRoles[role] = true; 1286 } 1287 const knownInputTypes = {}; 1288 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(" ")) { 1289 knownInputTypes[type] = true; 1290 } 1291 const knownAttrValue = /* @__PURE__ */ __name((value, vocabulary) => value !== null && vocabulary[value.toLowerCase()] === true ? value.toLowerCase() : null, "knownAttrValue"); 1292 const tagOf = /* @__PURE__ */ __name((node) => { 1293 const tag = String(node.tagName).toLowerCase(); 1294 return knownTags[tag] === true ? tag : "x"; 1295 }, "tagOf"); 1296 const attrOf = /* @__PURE__ */ __name((node, name) => { 1297 if (typeof node.getAttribute !== "function") return null; 1298 const value = node.getAttribute(name); 1299 return typeof value === "string" && value !== "" ? value : null; 1300 }, "attrOf"); 1301 const hasAttr = /* @__PURE__ */ __name((node, name) => typeof node.getAttribute === "function" && typeof node.getAttribute(name) === "string", "hasAttr"); 1302 const ownTextOf = /* @__PURE__ */ __name((node) => { 1303 if (valueBearingTags[tagOf(node)] === true) return null; 1304 const children = node.childNodes; 1305 if (!children || typeof children.length !== "number") return null; 1306 const list = children; 1307 let text = ""; 1308 for (let index = 0; index < list.length; index++) { 1309 const child = list[index]; 1310 if (child && child.nodeType === 3 && typeof child.nodeValue === "string") text += child.nodeValue; 1311 if (text.length > ownTextMaxLength) return null; 1312 } 1313 return text === "" ? null : text; 1314 }, "ownTextOf"); 1315 const labelOf = /* @__PURE__ */ __name((node) => truncateToChars( 1316 attrOf(node, "aria-label") || attrOf(node, "title") || attrOf(node, "alt") || ownTextOf(node), 1317 labelMaxLength 1318 ), "labelOf"); 1319 const nthOfType = /* @__PURE__ */ __name((parent, node, tag) => { 1320 const children = parent.children; 1321 if (!children || typeof children.length !== "number") return ""; 1322 const list = children; 1323 let position = 0; 1324 let total = 0; 1325 for (let index = 0; index < list.length; index++) { 1326 const sibling = asNode(list[index]); 1327 if (!sibling || tagOf(sibling) !== tag) continue; 1328 total++; 1329 if (sibling === node) position = total; 1330 } 1331 return position > 0 ? ":" + position : ""; 1332 }, "nthOfType"); 1333 const pathOf = /* @__PURE__ */ __name((node) => { 1334 const segments = []; 1335 let current = node; 1336 for (let depth = 0; current && depth < pathMaxDepth; depth++) { 1337 const tag = tagOf(current); 1338 if (!tag || tag === "html") break; 1339 const parent = asNode(current.parentElement); 1340 segments.unshift(parent && tag !== "body" ? tag + nthOfType(parent, current, tag) : tag); 1341 if (tag === "body" || !parent) break; 1342 current = parent; 1343 } 1344 if (segments.length === 0) return null; 1345 let path = segments.join(">"); 1346 while (path.length > pathMaxLength && segments.length > 1) { 1347 segments.shift(); 1348 path = segments.join(">"); 1349 } 1350 return path.length > pathMaxLength ? path.slice(path.length - pathMaxLength) : path; 1351 }, "pathOf"); 1352 const componentNameOf = /* @__PURE__ */ __name((node) => { 1353 if (jsxSourceKey === null) return null; 1354 const source = node[jsxSourceKey]; 1355 if (!source || typeof source !== "object") return null; 1356 const name = source.displayName; 1357 return typeof name === "string" && name !== "" ? name : null; 1358 }, "componentNameOf"); 1359 const isInteractive = /* @__PURE__ */ __name((node) => { 1360 if (interactiveTags[tagOf(node)] === true) return true; 1361 const role = attrOf(node, "role"); 1362 return role !== null && interactiveRoles[role.toLowerCase()] === true; 1363 }, "isInteractive"); 1364 const chainFrom = /* @__PURE__ */ __name((event, maxDepth) => { 1365 const composed = typeof event.composedPath === "function" ? event.composedPath() : null; 1366 if (Array.isArray(composed)) { 1367 const nodes2 = []; 1368 for (const entry of composed) { 1369 if (nodes2.length >= maxDepth) return { nodes: nodes2, complete: false }; 1370 const node2 = asNode(entry); 1371 if (node2) nodes2.push(node2); 1372 } 1373 return { nodes: nodes2, complete: true }; 1374 } 1375 const nodes = []; 1376 let node = asNode(event.target); 1377 while (node) { 1378 if (nodes.length >= maxDepth) return { nodes, complete: false }; 1379 nodes.push(node); 1380 node = asNode(node.parentElement); 1381 } 1382 return { nodes, complete: true }; 1383 }, "chainFrom"); 1384 const isPointerType = /* @__PURE__ */ __name((value) => value === "mouse" || value === "touch" || value === "pen", "isPointerType"); 1385 const pointerTypeMaxAgeMs = 2e3; 1386 const forwardedClickMaxAgeMs = 300; 1387 let lastPointerType = ""; 1388 let lastPointerAt = 0; 1389 const recentPointerType = /* @__PURE__ */ __name((maxAgeMs) => lastPointerType !== "" && perf.now() - lastPointerAt <= maxAgeMs ? lastPointerType : "", "recentPointerType"); 1390 w.addEventListener( 1391 "pointerdown", 1392 (event) => { 1393 try { 1394 const pointerEvent = event; 1395 if (pointerEvent.isTrusted !== true) return; 1396 const pointerType = typeof pointerEvent.pointerType === "string" ? pointerEvent.pointerType : ""; 1397 if (isPointerType(pointerType)) { 1398 lastPointerType = pointerType; 1399 lastPointerAt = perf.now(); 1400 } 1401 } catch { 1402 } 1403 }, 1404 true 1405 ); 1406 const roundUnit = /* @__PURE__ */ __name((value) => Math.round(value * 1e3) / 1e3, "roundUnit"); 1407 const numberOr = /* @__PURE__ */ __name((value) => typeof value === "number" && isFinite(value) ? value : null, "numberOr"); 1408 w.addEventListener( 1409 "click", 1410 (event) => { 1411 try { 1412 if (!event || typeof event !== "object") return; 1413 const clickEvent = event; 1414 if (clickEvent.isTrusted !== true) return; 1415 if (capturedClicks >= clickCapPerPageView) return; 1416 const chain = chainFrom(clickEvent, optOutMaxDepth); 1417 if (!chain.complete) return; 1418 let element = null; 1419 let section = null; 1420 for (let depth = 0; depth < chain.nodes.length; depth++) { 1421 const node = chain.nodes[depth]; 1422 if (hasAttr(node, "data-lov-no-capture")) return; 1423 if (depth >= chainMaxDepth) continue; 1424 if (element === null) { 1425 if (isInteractive(node)) element = node; 1426 continue; 1427 } 1428 if (section === null && landmarkTags[tagOf(node)] === true) section = node; 1429 } 1430 if (element === null) return; 1431 const pointerType = typeof clickEvent.pointerType === "string" ? clickEvent.pointerType : ""; 1432 const detail = numberOr(clickEvent.detail) ?? 0; 1433 const forwarded = detail === 0 ? recentPointerType(forwardedClickMaxAgeMs) : ""; 1434 const interactionType = isPointerType(pointerType) ? pointerType : forwarded || (detail === 0 ? "keyboard" : recentPointerType(pointerTypeMaxAgeMs) || "unknown"); 1435 let offsetX = null; 1436 let offsetY = null; 1437 const clientX = numberOr(clickEvent.clientX); 1438 const clientY = numberOr(clickEvent.clientY); 1439 const rect = typeof element.getBoundingClientRect === "function" ? element.getBoundingClientRect() : null; 1440 if (interactionType !== "keyboard" && rect && clientX !== null && clientY !== null && rect.width > 0 && rect.height > 0) { 1441 offsetX = roundUnit(Math.min(1, Math.max(0, (clientX - rect.left) / rect.width))); 1442 offsetY = roundUnit(Math.min(1, Math.max(0, (clientY - rect.top) / rect.height))); 1443 } 1444 const viewport = w; 1445 enqueue("lovable.element_clicked", { 1446 // pathname only, and named url_path so the collector's path sanitizer 1447 // selects it as a second line of defence. 1448 url_path: currentUrlPath(), 1449 element_tag: tagOf(element), 1450 interaction_type: interactionType, 1451 element_role: knownAttrValue(attrOf(element, "role"), knownRoles), 1452 element_type: knownAttrValue(attrOf(element, "type"), knownInputTypes), 1453 element_label: labelOf(element), 1454 element_path: pathOf(element), 1455 component_name: truncateToChars(componentNameOf(element), tagMaxLength), 1456 section_tag: section ? truncateToChars(tagOf(section), tagMaxLength) : null, 1457 section_label: section ? truncateToChars(attrOf(section, "aria-label"), labelMaxLength) : null, 1458 offset_x: offsetX, 1459 offset_y: offsetY, 1460 viewport_width: numberOr(viewport.innerWidth),
1461 viewport_height: numberOr(viewport.innerHeight) 1462 }); 1463 capturedClicks++; 1464 } catch { 1465 } 1466 }, 1467 true 1468 ); 1469 } 1470 } 1471 if (d && typeof d.addEventListener === "function") { 1472 d.addEventListener("visibilitychange", () => { 1473 if (d.visibilityState === "hidden") { 1474 unloadingPage = true; 1475 finalizeWebVitals(); 1476 flushReplay(true); 1477 emitEngagement("hidden"); 1478 stopEngagementSegment(); 1479 clearEngagementTimer(); 1480 clearUnfocusedTimer(); 1481 flush(true); 1482 unloadingPage = false; 1483 return; 1484 } 1485 unloadingPage = false; 1486 releaseBeaconBytes(); 1487 resumeEngagementMeasurement(); 1488 }); 1489 } 1490 patchHistory("pushState", "push_state"); 1491 patchHistory("replaceState", "replace_state"); 1492 enqueue("lovable.session_started", { reason: "new_session" }); 1493 emitPageView("initial_load", false); 1494 resumeEngagementMeasurement(); 1495})(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.