@sprig-technologies/sprig-browser
Version:
npm package for the sprig web sdk
723 lines (722 loc) • 38.4 kB
JavaScript
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);