UNPKG

@sprig-technologies/sprig-browser

Version:

npm package for the sprig web sdk

723 lines (722 loc) 38.4 kB
var lt = Object.defineProperty; var ct = (e, t, a) => t in e ? lt(e, t, { enumerable: !0, configurable: !0, writable: !0, value: a }) : e[t] = a; var F = (e, t, a) => ct(e, typeof t != "symbol" ? t + "" : t, a); import { b as l, s as Q, a as m, v as X, c as ut, g as pt, P as xe, r as Be, d as gt, e as V, S as Y, f as mt } from "./metricsReporter-0HbKrvkx.js"; import { d as yt } from "./debounce-CMb8f7wh.js"; var B = ((e) => (e[e.DomContentLoaded = 0] = "DomContentLoaded", e[e.Load = 1] = "Load", e[e.FullSnapshot = 2] = "FullSnapshot", e[e.IncrementalSnapshot = 3] = "IncrementalSnapshot", e[e.Meta = 4] = "Meta", e[e.Custom = 5] = "Custom", e[e.Plugin = 6] = "Plugin", e))(B || {}), k = ((e) => (e[e.Mutation = 0] = "Mutation", e[e.MouseMove = 1] = "MouseMove", e[e.MouseInteraction = 2] = "MouseInteraction", e[e.Scroll = 3] = "Scroll", e[e.ViewportResize = 4] = "ViewportResize", e[e.Input = 5] = "Input", e[e.TouchMove = 6] = "TouchMove", e[e.MediaInteraction = 7] = "MediaInteraction", e[e.StyleSheetRule = 8] = "StyleSheetRule", e[e.CanvasMutation = 9] = "CanvasMutation", e[e.Font = 10] = "Font", e[e.Log = 11] = "Log", e[e.Drag = 12] = "Drag", e[e.StyleDeclaration = 13] = "StyleDeclaration", e[e.Selection = 14] = "Selection", e[e.AdoptedStyleSheet = 15] = "AdoptedStyleSheet", e[e.CustomElement = 16] = "CustomElement", e))(k || {}); const re = (e, t) => t.some((a) => e instanceof a); let Se, be; const se = /* @__PURE__ */ new WeakMap(), Z = /* @__PURE__ */ new WeakMap(), K = /* @__PURE__ */ new WeakMap(); let oe = { get(e, t, a) { if (e instanceof IDBTransaction) { if (t === "done") return se.get(e); if (t === "store") return a.objectStoreNames[1] ? void 0 : a.objectStore(a.objectStoreNames[0]); } return M(e[t]); }, set: (e, t, a) => (e[t] = a, !0), has: (e, t) => e instanceof IDBTransaction && (t === "done" || t === "store") || t in e }; function Me(e) { oe = e(oe); } function wt(e) { return (be || (be = [IDBCursor.prototype.advance, IDBCursor.prototype.continue, IDBCursor.prototype.continuePrimaryKey])).includes(e) ? function(...t) { return e.apply(ie(this), t), M(this.request); } : function(...t) { return M(e.apply(ie(this), t)); }; } function ft(e) { return typeof e == "function" ? wt(e) : (e instanceof IDBTransaction && function(t) { if (se.has(t)) return; const a = new Promise((n, r) => { const s = () => { r(t.error || new DOMException("AbortError", "AbortError")); }; t.oncomplete = () => { n(); }, t.onerror = s, t.onabort = s; }); se.set(t, a); }(e), re(e, Se || (Se = [IDBDatabase, IDBObjectStore, IDBIndex, IDBCursor, IDBTransaction])) ? new Proxy(e, oe) : e); } function M(e) { if (e instanceof IDBRequest) return function(a) { const n = new Promise((r, s) => { a.onsuccess = () => { r(M(a.result)); }, a.onerror = () => { s(a.error); }; }); return K.set(n, a), n; }(e); if (Z.has(e)) return Z.get(e); const t = ft(e); return t !== e && (Z.set(e, t), K.set(t, e)), t; } const ie = (e) => K.get(e); function de(e, { blocked: t } = {}) { const a = indexedDB.deleteDatabase(e); return t && (a.onblocked = (n) => t(n.oldVersion, n)), M(a).then(() => { }); } const ht = ["get", "getKey", "getAll", "getAllKeys", "count"], It = ["put", "add", "delete", "clear"], ee = /* @__PURE__ */ new Map(); function De(e, t) { if (!(e instanceof IDBDatabase) || t in e || typeof t != "string") return; if (ee.get(t)) return ee.get(t); const a = t.replace(/FromIndex$/, ""), n = t !== a, r = It.includes(a); if (!(a in (n ? IDBIndex : IDBObjectStore).prototype) || !r && !ht.includes(a)) return; const s = async function(o, ...d) { const i = this.transaction(o, r ? "readwrite" : "readonly"); let c = i.store; return n && (c = c.index(d.shift())), (await Promise.all([c[a](...d), r && i.done]))[0]; }; return ee.set(t, s), s; } Me((e) => ({ ...e, get: (t, a, n) => De(t, a) || e.get(t, a, n), has: (t, a) => !!De(t, a) || e.has(t, a) })); const vt = ["continue", "continuePrimaryKey", "advance"], Te = {}, le = /* @__PURE__ */ new WeakMap(), Ae = /* @__PURE__ */ new WeakMap(), St = { get(e, t) { if (!vt.includes(t)) return e[t]; let a = Te[t]; return a || (a = Te[t] = function(...n) { le.set(this, Ae.get(this)[t](...n)); }), a; } }; async function* bt(...e) { let t = this; if (t instanceof IDBCursor || (t = await t.openCursor(...e)), !t) return; const a = new Proxy(t, St); for (Ae.set(a, t), K.set(a, ie(t)); t; ) yield a, t = await (le.get(a) || t.continue()), le.delete(a); } function Ee(e, t) { return t === Symbol.asyncIterator && re(e, [IDBIndex, IDBObjectStore, IDBCursor]) || t === "iterate" && re(e, [IDBIndex, IDBObjectStore]); } Me((e) => ({ ...e, get: (t, a, n) => Ee(t, a) ? bt : e.get(t, a, n), has: (t, a) => Ee(t, a) || e.has(t, a) })); const Dt = "sprigReplayIframeLoaded", Tt = "sprigReplayIframeSettings", Et = "sprigReplayIframeTakeFullSnapshot", Ct = "sprigReplayTeardown", fe = [], _e = new class { constructor(e) { F(this, "awaitingResolvers", []); F(this, "activeCount", 0); this.capacity = e; } async acquire() { if (!(this.activeCount < this.capacity)) return new Promise((e) => { this.awaitingResolvers.push(e); }); this.activeCount++; } release() { const e = this.awaitingResolvers.shift(); e && this.activeCount <= this.capacity ? e() : this.activeCount--; } async execute(e) { try { return await this.acquire(), await e(); } finally { this.release(); } } setLimit(e) { this.capacity = e; } }(2), Ne = async ({ apiUrl: e, surveyId: t, uploadId: a, etags: n, headers: r, responseGroupUuid: s, replayDuration: o, eventDigest: d }, i = !1) => { var w; if (!i && !a && !n) return void l.error("UploadErr", { isMobile: i, uploadId: a, etags: n }); l.info("MarkUploadComplete", { surveyId: t }); const c = await Q(`${e}/sdk/1/completeSessionReplay`, { method: "POST", body: JSON.stringify({ etags: n, uploadId: a, responseGroupUuid: s, surveyId: t, replayDuration: o, eventDigest: d, userAgent: (w = window == null ? void 0 : window.navigator) == null ? void 0 : w.userAgent }), headers: r, shouldRetryRequest: !0 }); return l.info("MarkUploadDone", { surveyId: t }), c; }, Rt = (e) => { if (e instanceof Attr) return null; let t = 1; for (let a = e.previousSibling; a; a = a.previousSibling) a.nodeName === e.nodeName && ++t; return t; }, Le = (e) => { if (e === null) return ""; const t = []; if (e instanceof Document) return "/"; for (let a = e; a && !(a instanceof Document) && a !== null; a = a instanceof Attr ? a.ownerElement : a.parentElement) { const n = t[t.length] = { name: void 0, position: null }; switch (a.nodeType) { case Node.TEXT_NODE: n.name = "text()"; break; case Node.ATTRIBUTE_NODE: n.name = "@" + a.nodeName; break; case Node.PROCESSING_INSTRUCTION_NODE: n.name = "processing-instruction()"; break; case Node.COMMENT_NODE: n.name = "comment()"; break; case Node.ELEMENT_NODE: n.name = a.nodeName; } n.position = Rt(a); } return "/" + t.reverse().map((a) => a.position !== null ? `/${a.name}[${a.position}]` : `/${a.name}`).join(""); }, he = (e) => e && e.trim().substring(0, 500).replace(/\s\s+/g, " ").replace(/\r?\n|\r/g, " ").substring(0, 250), C = { capture: !0, passive: !0 }, Pt = ["a", "button", "input", "option", "li", "link"], kt = ["Escape", "Enter", "Backspace", "F5", "Tab"]; let J = !1, D = null, G = null; const Ce = (e) => { var a; if (((a = e.tagName) == null ? void 0 : a.toLowerCase()) === "html") return { element: "html" }; const t = {}; return t.element = ((n) => { if (!n.tagName) return "No tagName"; const r = n.getAttribute("type"); return r ? `${r} ${n.tagName.toLowerCase()}` : n.tagName.toLowerCase(); })(e), t; }, Ut = (e) => { var n; if (!e) return {}; const t = { ...Ce(e) }, a = e.parentElement; if (a && Pt.includes((n = a.tagName) == null ? void 0 : n.toLowerCase())) { const r = Ce(a); Object.assign(t, r); } return t; }, Oe = (e, t) => { var r, s; let a = t.target; var n; t.target === ((r = window.document) == null ? void 0 : r.body) && window.Sprig.pointerDownTarget && (a = window.Sprig.pointerDownTarget), n = { x: t.x, y: t.y, type: e, elementAttributes: Ut(a), windowHeight: window.innerHeight, windowWidth: window.innerWidth, ...a instanceof HTMLElement || a instanceof SVGElement || a instanceof MathMLElement ? { rect: a == null ? void 0 : a.getBoundingClientRect(), xPath: Le(a) } : {} }, (s = n == null ? void 0 : n.elementAttributes) != null && s.text && (n.elementAttributes.text = he(n.elementAttributes.text)), D == null || D("Sprig_Click", n); }, Fe = (e) => { var t; kt.includes(e.key) && (t = { key: e.key }, D == null || D("Sprig_Keystroke", t)); }, xt = () => { var e; window.performance.getEntriesByType("navigation").map((t) => t.type).includes("reload") && (e = { url: window.location.href, currentPageTitle: document.title }, D == null || D("Sprig_Refresh", e)); }, Bt = () => { var e; window.performance.getEntriesByType("navigation").map((t) => t.type).includes("back_forward") && ((e = { curUrl: window.location.href, fromUrl: document.referrer, currentPageTitle: document.title }).currentPageTitle && (e.currentPageTitle = he(e.currentPageTitle)), D == null || D("Sprig_BackForward", e)); }, He = yt((e) => { if (!(e.target instanceof HTMLElement || e.target instanceof Document)) return; let t = e.target; "scrollTop" in t || (t = t.documentElement), G == null || G({ xPath: Le(t), x: t.scrollLeft, y: t.scrollTop, elementAttributes: { targetScrollWidth: t.scrollWidth, targetClientWidth: t.clientWidth, targetScrollHeight: t.scrollHeight, targetClientHeight: t.clientHeight } }); }, 750), je = (Re = "left_click", (e) => Oe(Re, e)); var Re; const Ve = (e) => { e.button === 2 && Oe("right_click", e); }, Ge = (e) => { window.Sprig && (window.Sprig.pointerDownTarget = e.target); }, g = { isRecording: !1, scrollEventUuids: {}, stopRecording: () => { } }, We = () => globalThis.indexedDB && globalThis.IDBKeyRange && globalThis.CompressionStream, I = (() => { const e = m.getItem("sprig.sessionId"); if (e) return l.info("SessionIDFound", { savedSessionId: e }), m.removeItem("sprig.sessionId"), e; const t = X(); return l.info("GeneratedSessionID", { uuid: t }), t; })(), ce = () => { m.setItem("sprig.disableReplayRecording", "disabled"); }, R = () => !!m.getItem("sprig.disableReplayRecording"), z = () => !!m.getItem("sprig.isReplayPaused"); var ke; (ke = globalThis.addEventListener) == null || ke.call(globalThis, "beforeunload", () => { l.info("BeforeUnload", { sessionId: I }), m.setItem("sprig.sessionId", I); }); const U = (e, t) => { var a, n; if (!R() && g.isRecording && !z()) try { (n = (a = globalThis.rrwebRecord) == null ? void 0 : a.addCustomEvent) == null || n.call(a, e, t); } catch (r) { H("Error recording custom event", r); } }, Mt = async (e) => { const { x: t, xPath: a, y: n } = e, r = g.scrollEventUuids[a]; if (r) return b(async () => { var d, i, c, w; const s = await u.openDB(), o = await s.get("events", r); if (o != null && o.event) { const p = JSON.parse(o.event), h = t > ((i = (d = p.data) == null ? void 0 : d.payload) == null ? void 0 : i.x), y = n > ((w = (c = p.data) == null ? void 0 : c.payload) == null ? void 0 : w.y); if (!h && !y) return null; h && (p.data.payload.x = t), y && (p.data.payload.y = n), p.data.payload.elementAttributes = e.elementAttributes, o.event = JSON.stringify(p), await s.put("events", o); } else U("Sprig_Scroll", e); }, "Error updating scroll event"); U("Sprig_Scroll", e); }, $e = () => { g.stopRecording && (g.stopRecording(), g.stopRecording = void 0), g.isRecording = !1, ["cleanupInterval", "inactivityInterval", "pendingCheckInterval"].forEach((e) => { g[e] && (clearInterval(g[e]), g[e] = void 0); }), J && (window.removeEventListener("click", je, C), window.removeEventListener("pointerdown", Ge, C), window.removeEventListener("mousedown", Ve, C), window.removeEventListener("keydown", Fe, C), window.removeEventListener("scroll", He, C), J = !1), fe.forEach((e) => { var t; (t = e.source) == null || t.postMessage({ type: Ct }, { targetOrigin: e.origin }); }); }, At = ["did not allow mutations", "called in an invalid security context"], _t = (e, t, { reportError: a = !0, extraInfo: n = {} }) => { if (!R() && t instanceof Error) { if (ce(), t.name === "VersionError") return l.error("VersionErr", { message: e }), void u.deleteDB(); ((r) => { if (!r) return !0; for (const s of At) if (r.toLowerCase().includes(s)) return !1; return !0; })(t == null ? void 0 : t.message) && (a && globalThis.UserLeap.reportError(e, t, n), u.clearAll()); } }, H = (e, t, { reportError: a } = { reportError: !0 }) => { $e(), l.error("ReplayErr", { code: t.code, name: t.name }), _t(e, t, { reportError: a }); }, b = async (e, t) => { try { return await e(); } catch (a) { H(t, a); } }, ue = () => { g.isRecording && (b(() => { var e, t; return (t = (e = globalThis.rrwebRecord) == null ? void 0 : e.takeFullSnapshot) == null ? void 0 : t.call(e, !0); }, "Error recording full snapshot"), fe.forEach((e) => { var t; (t = e.source) == null || t.postMessage({ type: Et }, { targetOrigin: e.origin }); })); }; let te = 0; (async () => We() && Promise.allSettled([de("replayStorage"), de("sprig.replay")]))(); const u = new class { constructor() { F(this, "wrapTransactionWithCounter", (e) => { var n, r; const t = (r = (n = window.Sprig) == null ? void 0 : n._config) == null ? void 0 : r.outstandingTransactionLimit, a = t === void 0 ? 100 : t; if (a && te > a) { const s = "Too many outstanding transactions"; H(s, new Error(s), { reportError: !1 }); } te++, e.done.finally(() => { te--; }); }); F(this, "getTransaction", async (e) => { const t = (await this.openDB()).transaction(e, "readwrite"); return this.wrapTransactionWithCounter(t), t; }); } openDB() { return function(e, t, { blocked: a, upgrade: n, blocking: r, terminated: s } = {}) { const o = indexedDB.open(e, t), d = M(o); return n && (o.onupgradeneeded = (i) => { n(M(o.result), i.oldVersion, i.newVersion, M(o.transaction), i); }), a && (o.onblocked = (i) => a(i.oldVersion, i.newVersion, i)), d.then((i) => { s && (i.onclose = () => s()), r && (i.onversionchange = (c) => r(c.oldVersion, c.newVersion, c)); }).catch(() => { }), d; }("sprigReplay", 1, { upgrade: (e, t, a) => { if (a === 0 && m.setItem("sprig.pendingCount", "0"), !e.objectStoreNames.contains("events")) { const n = e.createObjectStore("events", { keyPath: "uuid" }); n.createIndex("sessionId", "sessionId"), n.createIndex("timestamp", "timestamp"), n.createIndex("[sessionId+timestamp]", ["sessionId", "timestamp"]); } if (!e.objectStoreNames.contains("chunkUploads")) { const n = e.createObjectStore("chunkUploads", { keyPath: "uuid" }); n.createIndex("sessionId", "sessionId"), n.createIndex("timestamp", "timestamp"), n.createIndex("[sessionId+status]", ["sessionId", "status"]), n.createIndex("[uploadId+status]", ["uploadId", "status"]), n.createIndex("[sessionId+status+uploadId]", ["sessionId", "status", "uploadId"]); } if (!e.objectStoreNames.contains("pendingCaptures")) { const n = e.createObjectStore("pendingCaptures", { keyPath: "uuid" }); n.createIndex("sessionId", "sessionId"), n.createIndex("timestamp", "timestamp"), n.createIndex("[sessionId+targetTimestamp]", ["sessionId", "targetTimestamp"]); } } }); } async deleteDB() { try { await de("sprigReplay"); } catch { } } async bulkAdd(e, t) { const a = await this.getTransaction(e); return Promise.all([...t.map((n) => a.store.add(n)), a.done]); } async clearAll() { const e = (await this.openDB()).transaction(["events", "chunkUploads", "pendingCaptures"], "readwrite"); return this.wrapTransactionWithCounter(e), Promise.all([e.objectStore("events").clear(), e.objectStore("chunkUploads").clear(), e.objectStore("pendingCaptures").clear()]); } async deleteBySessionId(e, t) { const a = IDBKeyRange.only(t), n = await this.getTransaction(e), r = n.store.index("sessionId"); for await (const s of r.iterate(a)) await s.delete(); await n.done; } async updatePartial(e, t, a) { const n = await this.getTransaction(e), r = await n.store.get(t); r && await n.store.put({ ...r, ...a }), await n.done; } async deleteRowsBefore(e, t, a = () => !0) { const n = IDBKeyRange.upperBound(t, !0), r = await this.getTransaction(e), s = r.store.index("timestamp"); for await (const o of s.iterate(n)) a(o.value) && await o.delete(); await r.done; } async getEventsBetween(e, t = Date.now()) { if (e >= t) return Promise.resolve([]); const a = IDBKeyRange.bound([I, e], [I, t], !1, !0); return (await this.openDB()).getAllFromIndex("events", "[sessionId+timestamp]", a); } async updateEventsExpiredAt(e, t, a = 30) { const n = /* @__PURE__ */ new Date(), r = n.setMinutes(n.getMinutes() + (a ?? 30)), s = await this.getTransaction("events"), o = s.store.index("[sessionId+timestamp]"), d = IDBKeyRange.bound([I, e], [I, t], !1, !0); for await (const i of o.iterate(d)) await i.update({ ...i.value, expiredAt: r }); await s.done; } async deleteChunkUploads(e, t) { const a = IDBKeyRange.only([t, e]), n = await this.getTransaction("chunkUploads"); let s = await n.store.index("[uploadId+status]").openCursor(a); for (; s; ) s.delete(), s = await s.continue(); await n.done; } async getChunkUploadsByStatus({ sessionId: e, status: t, uploadId: a }) { const n = (await this.openDB()).transaction("chunkUploads", "readonly"); this.wrapTransactionWithCounter(n); const r = a ? n.store.index("[uploadId+status]") : n.store.index("[sessionId+status]"), s = a ? IDBKeyRange.only([a, t]) : IDBKeyRange.only([e, t]); return r.getAll(s); } async getPendingCaptures(e = {}) { return (await (await this.openDB()).getAllFromIndex("pendingCaptures", "sessionId", I)).filter((a) => !e.beforePresent || a.targetTimestamp < Date.now()).filter((a) => !e.isBeforeType || a.captureParams.replayParams.replayDurationType === "before").filter((a) => !e.isHeatmap || (a.captureParams.isHeatmap ?? !1)); } async tryAddPendingCaptureForSurvey(e) { const { surveyId: t, record: a } = e, n = (await this.openDB()).transaction("pendingCaptures", "readwrite"); this.wrapTransactionWithCounter(n); const r = n.store.index("sessionId"), s = await r.getAll(I), o = !!a.captureParams.isHeatmap; return s.some((d) => d.captureParams.surveyId === t && !!d.captureParams.isHeatmap === o) ? (await n.done, { added: !1 }) : (await n.store.add(a), await n.done, { added: !0 }); } async markPendingCaptureToCanUpload(e) { const t = await this.getTransaction("pendingCaptures"), a = t.store.index("sessionId"); for await (const n of a.iterate(I)) { const r = n.value; r.captureParams.responseGroupId === e && await n.update({ ...r, canUpload: !0 }); } await t.done; } async markPendingHeatmapsReady(e) { if (parseInt(m.getItem("sprig.pendingCount") ?? "0") === 0) return null; const t = Date.now(), a = await this.getTransaction("pendingCaptures"), n = a.store.index("sessionId"); for await (const r of n.iterate(I)) { const s = r.value; !s.captureParams.isHeatmap || e && !e.includes(s.uuid) || await r.update({ ...s, targetTimestamp: t, captureParams: { ...s.captureParams, triggerTimestamp: t, replayParams: { ...s.captureParams.replayParams, replayDurationSeconds: Math.floor((t - s.timestamp) / 1e3) } } }); } await a.done; } }(), x = []; let j, Ke, _ = [], L = !1, N = 0, W = !1, Je = !1; const Ie = []; let q, ze, $, qe, ae = !1; const O = () => W && !L && Date.now() <= q, Nt = ({ apiUrl: e, config: t, triggerSnapshot: a, forceInit: n = !1 }) => { W && !n || (m.isStorageAvailable ? (_ = [], Ie.splice(0), x.splice(0), N = 0, $ = a, Ke = e, j = { responseGroupUuid: t.responseGroupUuid, surveyId: t.surveyId, userAgent: t.userAgent, sdkVersion: t.sdkVersion }, ze = t.maxDurationSeconds, Ht(), W || (qe = globalThis.setInterval(Ft, 500)), W = !0) : L = !0); }, Lt = [k.Drag, k.Input, k.MediaInteraction, k.MouseInteraction, k.MouseMove, k.Scroll, k.Selection, k.TouchMove], Ot = (e) => e.type === B.Custom || e.type === B.IncrementalSnapshot && Lt.includes(e.data.source), ve = (e) => e.some(Ot), Ft = async () => { if (!O()) return void globalThis.clearInterval(qe); if (Qe(), !ve(x)) return; const e = x[0].timestamp; Date.now() - e > 35e3 && ($ == null || $()); }, Qe = async () => { if (_.length || ae) return; ae = !0; const e = await Vt(); if (!e) return void (L = !0); Ie.splice(0, e.length).forEach((t) => t(e.shift())), e.forEach((t) => _.push(t)), ae = !1; }, Ht = () => { const e = m.getItem("sprig.alwayson.info"); if (e) { l.info("Read stored session state", e); const t = JSON.parse(e); L = t.disabled, j = t.metadata, _ = t.uploadUrls, N = t.currentIndex, q = t.expirationTimestamp, t.pendingEventTimestamp && (l.info(`Uploading with pending timestamp: ${t.pendingEventTimestamp}`), jt(t.pendingEventTimestamp)); } else q = 1e3 * ze + Date.now(); }, jt = async (e) => { const t = Date.now(), a = (await u.getEventsBetween(e, t)).map((r) => JSON.parse(r.event)); if (!ve(a)) return; et(a); const n = await Ze(); n && await Ye(n, a); }, Xe = async (e, t) => { try { const a = await e(); if (!a.ok) throw new Error(`Error ${t}`); return a; } catch { L = !0; } }, Ye = async (e, t) => { if (!O() || !e) return; const a = await (async (n) => { const r = new TextEncoder(), s = new CompressionStream("gzip"), o = s.writable.getWriter(), d = r.encode(JSON.stringify(n)); return o.write(d), o.close(), new Uint8Array(await new Response(s.readable).arrayBuffer()); })(t); l.info("Uploading always-on events with presigned url"), await Xe(() => Q(e, { body: a, method: "PUT" }), "uploading always-on with presigned url"); }, Vt = async () => { if (!O()) return; const { surveyId: e, responseGroupUuid: t } = j, a = { responseGroupUuid: t, surveyId: e, index: N + 1 }; l.info("Fetching always-on upload urls", a); const n = await Xe(() => Q(`${Ke}/sdk/1/replayUrls`, { method: "POST", body: JSON.stringify(a), headers: pt(globalThis.UserLeap) }), "fetching always-on signed urls"); if (!n) return; const r = n.json.signedUrls; return l.info("Fetched more always-on upload urls", { body: a, urls: r }), r; }, Ze = async () => { if (_.length) return _.shift(); const e = new Promise((t) => { Ie.push(t); }); return Qe(), e; }, et = (e) => { var r, s, o; const t = e.length ? e[e.length - 1].timestamp : Date.now(), a = N, n = ((s = (r = globalThis.UserLeap) == null ? void 0 : r.config) == null ? void 0 : s.customMetadata) ?? ((o = globalThis.__cfg) == null ? void 0 : o.customMetadata); N++, e.push({ timestamp: t, type: B.Custom, data: { tag: "Sprig_Meta", payload: { ...j, index: a, visitorId: globalThis.UserLeap.visitorId ?? "", timestamp: t, customMetadata: n } } }); }, Gt = (e, t) => { O() && !Je && (e || x.length) && (e && x.length && (async () => { const a = x.splice(0); if (!ve(a)) return; l.info("Capturing always-on event array to upload"), et(a); const n = await Ze(); n && await Ye(n, a); })(), x.push(t)); }; var Ue; (Ue = globalThis.addEventListener) == null || Ue.call(globalThis, "beforeunload", async () => { Je = !0, O() && (l.info("Always On handle page unload"), (() => { let e; x.length && (e = x[0].timestamp); const t = { disabled: L, metadata: j, uploadUrls: _, currentIndex: N, pendingEventTimestamp: e, expirationTimestamp: q }; l.info("Storing session state on unload", t), m.setItem("sprig.alwayson.info", JSON.stringify(t)); })()); }); const tt = async (e, t) => { const a = performance.now(); let n; try { n = await e(); } finally { const r = performance.now() - a; let s = xe[t]; s || (s = Be(t)), s.report(r / 1e3); } return n; }, at = (e, t) => { const a = performance.now(); try { e(); } finally { const n = performance.now() - a; let r = xe[t]; r || (r = Be(t)), r.report(n / 1e3); } }; let nt = 5e3, pe = 6e4, ge = 0, A, me = !1, ye = []; const Wt = (e) => { var t, a, n, r; if ((t = e.event) != null && t.includes("Sprig_Scroll")) { const s = (r = (n = (a = JSON.parse(e.event)) == null ? void 0 : a.data) == null ? void 0 : n.payload) == null ? void 0 : r.xPath; if (!s) return; g.scrollEventUuids[s] = e.uuid; } ye.push(e), me || $t(); }, $t = () => { me = !0, setTimeout(async () => { if (R() || z()) return; const e = ye; ye = [], me = !1, at(async () => { await (async (t) => { const a = t.map((n) => ({ ...n, sessionId: n.sessionId ?? I })); if (a.length !== 0) return b(() => u.bulkAdd("events", a), "Error storing replay events"); })(e); }, "sdk_replay_add_event_batch_seconds"); }, 500); }, Kt = (e, t, a) => { g.cleanupInterval = window.setInterval(() => { const n = Date.now(); tt(() => b(async () => { R() || await Promise.all([u.deleteRowsBefore("events", n - 1e3 * e, (r) => r.expiredAt === void 0 || r.expiredAt < n - 1e3 * e), u.deleteRowsBefore("chunkUploads", n - 1e3 * t), u.deleteRowsBefore("pendingCaptures", n - 1e3 * a, (r) => !r.canUpload)]); }, "Error deleting table rows"), "sdk_replay_cleanup_seconds"), l.debug("CleanupComplete"); }, 3e4); }, Jt = () => { g.pendingCheckInterval = window.setInterval(async () => { b(async () => { await we(); }, "Error initiating pending captures"); }, 5e3); }; let ne = !1; const we = async (e = !1) => { if (!ne) try { ne = !0; const t = parseInt(A ?? "0"); if (t === 0) return; const a = await u.getPendingCaptures({ beforePresent: !0, isBeforeType: e }), n = await u.openDB(); await Promise.all(a.map(async (r) => (await n.delete("pendingCaptures", r.uuid), ot(r.captureParams, r.canUpload)))), A = (t - a.length).toString(), m.setItem("sprig.pendingCount", A); } finally { ne = !1; } }, zt = async (e, t, a, n, r) => { const s = Math.min(e + r, a), o = await tt(() => u.getEventsBetween(e, s), "sdk_replay_get_events_between_seconds"); if (!(o != null && o.length)) return l.debug("NoEventsFound"), { validStartFound: n, events: [] }; if (!n) { l.debug("ValidStartSearch"); let d = -1; return o == null || o.forEach((i, c) => { if (!i.isValidStart) return; const w = i.timestamp <= t; (d < 0 || w) && (d = c); }), d < 0 ? (l.debug("ValidStartNotFound"), { validStartFound: n, events: [] }) : { validStartFound: !0, events: o == null ? void 0 : o.slice(d) }; } return { validStartFound: n, events: o }; }, rt = (e) => Promise.all(e.map(async (t) => { const a = await (async (n) => _e.execute(async () => { var o; l.info("UploadChunkStart", { chunkIndex: n.chunkIndex, surveyId: n.surveyId }); const r = await Q(n.uploadUrl, { body: n.data, method: "PUT" }); l.http("UploadChunkEnd", { url: n.uploadUrl, method: "PUT", status_code: r.status, reason: r.statusText ?? "OK", chunkIndex: n.chunkIndex, surveyId: n.surveyId }); const s = (o = r.headers) == null ? void 0 : o.get("ETag"); if (!s) throw new Error(`Upload response did not include etag for upload ${n.uploadId}, part ${n.chunkIndex}`); return s; }))(t); return await u.updatePartial("chunkUploads", t.uuid, { data: null, etag: a, status: "UploadComplete" }), t.uploadId; })), st = async (e) => { const t = await u.getChunkUploadsByStatus({ status: "UploadComplete", uploadId: e }); if (!(t != null && t.length)) return void l.info("NoChunksForUpload", { uploadId: e }); const a = t.reduce((s, o) => (s.find((d) => d.chunkIndex === o.chunkIndex) || s.push(o), s), []); a.sort((s, o) => s.chunkIndex - o.chunkIndex); const n = a.map((s) => ({ ETag: s.etag, PartNumber: s.chunkIndex })).filter((s) => s.ETag !== null), r = a[0]; await Ne({ apiUrl: r.apiUrl, surveyId: r.surveyId, uploadId: e, responseGroupUuid: r.responseGroupId, etags: n, headers: r.completeUploadHeaders, replayDuration: r.replayDuration }), await u.deleteChunkUploads("UploadComplete", e); }, qt = () => { b(async () => { const e = await u.getChunkUploadsByStatus({ sessionId: I, status: "ReadyForUpload" }); if (!(e != null && e.length)) return; const t = await rt(e); t != null && t.length && await Promise.all(t.map((a) => { if (a) return st(a); })); }, "Error uploading unfinished chunks"); }, Qt = async (e, t) => { const a = t ?? Date.now(); return (async (n, r) => { const s = new TextEncoder(); let o = null; const d = new CompressionStream("gzip"), i = d.writable.getWriter(); let c = !1, w = !1, [p, h] = [0, 0], y = []; for (let P = n - 35e3; P < r; P += pe) { if ({ validStartFound: w, events: y } = await zt(P, n, r, w, pe), !(y != null && y.length)) { l.debug("NoEventsFound"); continue; } p === 0 && (p = y[0].timestamp), h = y[y.length - 1].timestamp; const f = y.map((E) => E.event); f.push(`{"timestamp":${r}}`); const v = `${c ? "," : "["}${f}`, S = s.encode(v); at(() => { i.write(S); }, "sdk_replay_compression_seconds"), c = !0; } if (h - p < nt) return l.debug("ReplayTooShort"), null; const T = s.encode("]"); return i.write(T), i.close(), o = new Uint8Array(await new Response(d.readable).arrayBuffer()), o; })(a - e, a); }, Pe = async (e) => { const { surveyId: t, responseGroupId: a, visitorId: n, apiUrl: r, completeUploadHeaders: s, replayParams: o, triggerTimestamp: d } = e, i = await Qt(1e3 * o.replayDurationSeconds, d); if (!(i != null && i.length)) return void l.info("FileDataEmpty", { surveyId: t }); const c = ((p, h, y) => { const T = p.length, P = 1024 * h * 1024, f = Math.ceil(T / y), v = Math.max(P, f), S = []; let E = 0; for (; E < T; ) S.push(p.slice(E, E + v)), E += v; return S; })(i, o.minimumChunkSizeMb, o.signedUrls.length), w = await Promise.all(c.map(async (p, h) => { const y = X(), T = { apiUrl: r, chunkIndex: h + 1, completeUploadHeaders: s, etag: null, responseGroupId: a, status: "ReadyForUpload", surveyId: t, timestamp: d, totalChunks: c.length, data: p, uploadId: o.uploadId, uploadUrl: o.signedUrls[h].url, uuid: y, visitorId: n }; return await (await u.openDB()).add("chunkUploads", { ...T, sessionId: T.sessionId ?? I }), T; })); await (async (p, h) => { await rt(h), await Promise.all(p.map((y) => st(y))); })([o.uploadId], w); }, ot = async (e, t) => { if (R()) return l.debug("ReplayDisabled-ScheduleOrCapture"); const { isHeatmap: a, isStandalone: n, replayParams: r, triggerTimestamp: s, responseGroupId: o } = e, d = async () => { setTimeout(() => V.removeListener(Y.QuestionAnswered, d), 0), b(async () => { r.replayDurationType === "before" ? await Pe(e) : await u.markPendingCaptureToCanUpload(o); }, "Error in schedule/capture callback"); }; b(async () => { if (r.replayDurationType === "after" || r.replayDurationType === "beforeAndAfter") return !n && !a && V.on(Y.QuestionAnswered, d), void await dt(e); if (n || a || t) await Pe(e), a && Xt(); else { const i = 35 + r.replayDurationSeconds, c = s - 1e3 * i, w = s; await u.updateEventsExpiredAt(c, w, r.expirationTimeLimitMinutes), V.on(Y.QuestionAnswered, d); } }, "Error in scheduling/capturing replay"); }, Xt = async () => { parseInt(A ?? "0") || m.removeItem("sprig.isCapturingHeatmap"), m.getItem("sprig.teardownAfterCapture") && ($e(), it(), m.removeItem("sprig.teardownAfterCapture")); }, it = async () => R() ? l.debug("ReplayDisabled-ClearData") : Promise.all([u.deleteBySessionId("events", I), u.deleteBySessionId("pendingCaptures", I)]).catch((e) => { H("Error clearing user replay data", e); }), dt = async (e) => { if (R()) return; const { isHeatmap: t, surveyId: a } = e, n = { ...e, replayParams: { ...e.replayParams } }; e.replayParams.replayDurationType === "beforeAndAfter" && (n.replayParams.replayDurationSeconds *= 2), n.replayParams.replayDurationType = "before"; const r = e.triggerTimestamp + 1e3 * e.replayParams.replayDurationSeconds; n.triggerTimestamp = r; const s = { canUpload: !1, captureParams: n, sessionId: I, targetTimestamp: r, timestamp: Date.now(), uuid: X() }, { added: o } = await u.tryAddPendingCaptureForSurvey({ surveyId: a, record: s }); o ? (A = (parseInt(A ?? "0") + 1).toString(), m.setItem("sprig.pendingCount", A), t && (ue(), m.setItem("sprig.isCapturingHeatmap", "true"), ge = Date.now(), g.inactivityInterval || (g.inactivityInterval = window.setInterval(() => { var d; d = ge, Date.now() - d >= 3e4 && b(() => u.markPendingHeatmapsReady(), "Error in heatmap inactivity"); }, 1e3)))) : l.info("PendingCaptureExists", { surveyId: a }); }, Yt = Object.freeze(Object.defineProperty({ __proto__: null, RecordEvent: (e) => { U("Sprig_TrackEvent", e); }, RecordPageView: (e) => { e.description && (e.description = he(e.description)), U("Sprig_PageView", e); }, RecordSurveyShown: (e) => { U("Sprig_ShowSurvey", e); }, _completeSessionReplay: async ({ surveyId: e, responseGroupUuid: t, eventDigest: a, headers: n }) => { if (!e || !t) return !1; const r = globalThis.UserLeap._API_URL, s = await Ne({ surveyId: e, responseGroupUuid: t, eventDigest: a, apiUrl: r, headers: n }, !0); return !(s != null && s.error); }, checkPendingHeatmapsUrl: () => R() ? l.debug("ReplayDisabled-PendingHeatmaps") : b(async () => { const e = (await u.getPendingCaptures({ isHeatmap: !0 })).map((t) => ({ eventId: t.captureParams.eventId, uuid: t.uuid })).filter(({ eventId: t }) => !ut(t)).map(({ uuid: t }) => t); return l.info("PendingHeatmapsToComplete", { count: e.length }), e.length && (await u.markPendingHeatmapsReady(e), l.info("MarkedPendingHeatmapsReady")), e.length; }, "Error marking pending heatmaps ready"), clearUserReplayData: it, disableRecording: H, initializeReplay: async ({ maxReplayDurationSeconds: e, maxInflightRequests: t = 2, replaySettings: a, teardownAfter: n = !1, apiUrl: r, alwaysOnConfig: s }) => { if (s && Nt({ apiUrl: r, config: s, triggerSnapshot: () => { ue(); } }), A = m.getItem("sprig.pendingCount"), g.isRecording) return; if (n && m.setItem("sprig.teardownAfterCapture", "true"), R()) return l.debug("ReplayDisabled"); if (await (async () => { var i; if (!We()) return !0; if ((i = globalThis.navigator.storage) != null && i.estimate) try { const { quota: c = 0, usage: w = 0 } = await globalThis.navigator.storage.estimate(), p = (c - w) / 1024 ** 3; return l.info("Storage", { availableGb: p }), p < 0.5; } catch { return !0; } return !1; })()) return l.debug("IDBNotSupported"), ce(); try { const i = await u.openDB(); l.info("DBVersion", { version: i.version }); } catch (i) { return l.error("ReplayOpenErr", { name: i.name }), i.name === "VersionError" && u.deleteDB(), ce(); } b(async () => { await we(!0); }, "Error uploading ready pending captures"); const o = O() ? 30 : 0, d = Math.max(e ?? 0, o); if (!d) return l.debug("MissingDuration"); l.debug("ReplayInit"), await b(async () => { var i; a != null && a.minDuration && (nt = a.minDuration), a != null && a.batchDuration && (pe = a.batchDuration), i = t, _e.setLimit(i), qt(), Kt(d + 35, 1800, d + 35), Jt(); const c = window.UserLeap.replayLibraryURL ?? "https://cdn.sprig.com/dependencies/record.min.js"; if (!window.rrwebRecord) { const { record: f } = await import( /* webpackIgnore: true */ /* @vite-ignore */ c ); window.rrwebRecord = f; } const w = window.rrwebRecord; if (!w) return l.error("RecordScriptFailed"); let p = !0, h = 0; const y = { checkoutEveryNms: 3e4, sampling: { input: "last", scroll: 250, media: 800 }, ...a, mutationQueueEnabled: a == null ? void 0 : a.enableMutationQueue }; var T, P; g.stopRecording = w({ emit: (f, v) => { if (f.type === B.Custom && (ge = Date.now()), R() || z()) return; if (v && f.type === B.Meta) h = performance.now(); else if (v && h && f.type === B.FullSnapshot) { const E = performance.now() - h; gt("sdk_replay_snapshot_seconds", E / 1e3); } const S = p || !!v && f.type === B.Meta; p = !1, Gt(S, f), Wt({ uuid: X(), event: JSON.stringify(f), isValidStart: S, timestamp: Date.now() }); }, ...y }), g.isRecording = !!g.stopRecording, g.isRecording && (((f, v) => { window.addEventListener("message", (S) => { var E; S.data.type === Dt && (fe.push({ source: S.source, origin: S.origin }), (E = S.source) == null || E.postMessage({ type: Tt, settings: f, replayLibraryUrl: v }, { targetOrigin: S.origin })); }); })(y, c), V.on("survey.complete", (f) => { var v; v = { id: f, userAgent: window.navigator.userAgent }, U("Sprig_SubmitSurvey", v); }), T = U, P = Mt, J || (D = T, G = P, window.addEventListener("click", je, C), window.addEventListener("pointerdown", Ge, C), window.addEventListener("mousedown", Ve, C), window.addEventListener("keydown", Fe, C), window.addEventListener("scroll", He, C), J = !0, xt(), Bt())); }, "Error initializing replay"); }, isReplayPaused: z, isReplayRecording: () => g.isRecording, recordFullSnapshot: ue, recordReplayPaused: () => { U("Sprig_ReplayPaused", { timestamp: Date.now() }), m.setItem("sprig.isReplayPaused", "true"); }, recordReplayResumed: () => { m.removeItem("sprig.isReplayPaused"), U("Sprig_ReplayResumed", { timestamp: Date.now() }); }, scheduleCapture: dt, scheduleOrCaptureReplay: ot, tryReplayAction: b, uploadReadyPendingCaptures: we }, Symbol.toStringTag, { value: "Module" })); mt(Yt);