@bentolabs/sdk 1.2.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -51,7 +51,9 @@ var index_exports = {};
51
51
  __export(index_exports, {
52
52
  BentoLabsSDK: () => BentoLabsSDK,
53
53
  default: () => index_default,
54
- generateSelector: () => generateSelector
54
+ generateSelector: () => generateSelector,
55
+ generateSelectorFromElement: () => generateSelectorFromElement,
56
+ generateSelectorFromHTML: () => generateSelectorFromHTML
55
57
  });
56
58
  module.exports = __toCommonJS(index_exports);
57
59
 
@@ -9133,10 +9135,161 @@ function getDirectText$1(element) {
9133
9135
  }
9134
9136
  return text.replace(/\s+/g, " ").trim();
9135
9137
  }
9136
- function buildBentoSelector(element) {
9138
+ function getFullText$1(element) {
9139
+ return (element.textContent || "").replace(/\s+/g, " ").trim();
9140
+ }
9141
+ function getChildSignature(element) {
9142
+ const tags = Array.from(element.children).map(
9143
+ (c2) => c2.tagName.toLowerCase()
9144
+ );
9145
+ return tags.join(",");
9146
+ }
9147
+ function quickHash(str) {
9148
+ let hash = 5381;
9149
+ for (let i2 = 0; i2 < str.length; i2++) {
9150
+ hash = (hash << 5) + hash ^ str.charCodeAt(i2);
9151
+ }
9152
+ return (hash >>> 0).toString(16);
9153
+ }
9154
+ function getParentAcrossShadow(element) {
9155
+ if (element.parentElement) {
9156
+ return element.parentElement;
9157
+ }
9158
+ const root2 = element.getRootNode();
9159
+ if (root2 instanceof ShadowRoot) {
9160
+ return root2.host;
9161
+ }
9162
+ return null;
9163
+ }
9164
+ function isPortalContainer(element) {
9165
+ return element.hasAttribute("data-radix-portal") || element.hasAttribute("data-headlessui-portal") || element.hasAttribute("data-floating-ui-portal") || element.hasAttribute("data-portal") || element.id !== "" && element.id.toLowerCase().includes("portal") || element.classList.contains("portal") || element.getAttribute("role") === "dialog";
9166
+ }
9167
+ function findUniqueAncestor(element, maxDepth = 5) {
9168
+ let current = getParentAcrossShadow(element);
9169
+ let depth = 0;
9170
+ while (current && depth < maxDepth) {
9171
+ if (isPortalContainer(current)) {
9172
+ current = getParentAcrossShadow(current);
9173
+ continue;
9174
+ }
9175
+ const tag = current.tagName.toLowerCase();
9176
+ if (current.id && isStableId(current.id)) {
9177
+ return `${tag}#${current.id}`;
9178
+ }
9179
+ const testId = current.getAttribute("data-testid");
9180
+ if (testId) {
9181
+ return `${tag}[data-testid=${testId}]`;
9182
+ }
9183
+ const dataId = current.getAttribute("data-id");
9184
+ if (dataId) {
9185
+ return `${tag}[data-id=${dataId}]`;
9186
+ }
9187
+ const name = current.getAttribute("name");
9188
+ if (name) {
9189
+ return `${tag}[name=${name}]`;
9190
+ }
9191
+ current = getParentAcrossShadow(current);
9192
+ depth++;
9193
+ }
9194
+ return null;
9195
+ }
9196
+ function findSection(element, maxDepth = 15) {
9197
+ const sectionTags = [
9198
+ "header",
9199
+ "nav",
9200
+ "main",
9201
+ "aside",
9202
+ "footer",
9203
+ "section",
9204
+ "article"
9205
+ ];
9206
+ let current = getParentAcrossShadow(element);
9207
+ let depth = 0;
9208
+ while (current && depth < maxDepth) {
9209
+ if (isPortalContainer(current)) {
9210
+ current = getParentAcrossShadow(current);
9211
+ continue;
9212
+ }
9213
+ const tag = current.tagName.toLowerCase();
9214
+ if (sectionTags.includes(tag)) {
9215
+ const label = current.getAttribute("aria-label");
9216
+ return label ? `${tag}[${label}]` : tag;
9217
+ }
9218
+ current = getParentAcrossShadow(current);
9219
+ depth++;
9220
+ }
9221
+ return null;
9222
+ }
9223
+ function findForm(element) {
9224
+ const form = element.closest("form");
9225
+ if (!form) return null;
9226
+ if (form.id && isStableId(form.id)) return form.id;
9227
+ if (form.name) return form.name;
9228
+ return null;
9229
+ }
9230
+ function findRegion(element, maxDepth = 15) {
9231
+ let current = getParentAcrossShadow(element);
9232
+ let depth = 0;
9233
+ while (current && depth < maxDepth) {
9234
+ if (isPortalContainer(current)) {
9235
+ current = getParentAcrossShadow(current);
9236
+ continue;
9237
+ }
9238
+ const role = current.getAttribute("role");
9239
+ if (role === "region" || role === "navigation" || role === "banner" || role === "main" || role === "contentinfo" || role === "complementary") {
9240
+ const label = current.getAttribute("aria-label");
9241
+ return label ? `${role}[${label}]` : role;
9242
+ }
9243
+ current = getParentAcrossShadow(current);
9244
+ depth++;
9245
+ }
9246
+ return null;
9247
+ }
9248
+ function getNthPosition(element) {
9249
+ const parent = element.parentElement;
9250
+ if (!parent) return 1;
9251
+ const tag = element.tagName;
9252
+ const text = getFullText$1(element);
9253
+ let position = 0;
9254
+ for (const sibling of Array.from(parent.children)) {
9255
+ if (sibling.tagName === tag && getFullText$1(sibling) === text) {
9256
+ position++;
9257
+ if (sibling === element) return position;
9258
+ }
9259
+ }
9260
+ return 1;
9261
+ }
9262
+ function buildBentoSelector(element, pathname) {
9137
9263
  const parts = [];
9138
9264
  const tag = element.tagName.toLowerCase();
9139
9265
  parts.push(tag);
9266
+ if (pathname) {
9267
+ parts.push(`page=${escapeValue(pathname)}`);
9268
+ }
9269
+ let hasContext = false;
9270
+ const ancestor = findUniqueAncestor(element, 5);
9271
+ if (ancestor) {
9272
+ parts.push(`ancestor=${escapeValue(ancestor)}`);
9273
+ hasContext = true;
9274
+ } else {
9275
+ const section = findSection(element, 15);
9276
+ if (section) {
9277
+ parts.push(`section=${escapeValue(section)}`);
9278
+ hasContext = true;
9279
+ } else {
9280
+ const form = findForm(element);
9281
+ if (form) {
9282
+ parts.push(`form=${escapeValue(form)}`);
9283
+ hasContext = true;
9284
+ } else {
9285
+ const region = findRegion(element, 15);
9286
+ if (region) {
9287
+ parts.push(`region=${escapeValue(region)}`);
9288
+ hasContext = true;
9289
+ }
9290
+ }
9291
+ }
9292
+ }
9140
9293
  const testId = element.getAttribute("data-testid");
9141
9294
  if (testId) {
9142
9295
  parts.push(`testid=${escapeValue(truncate(testId, 50))}`);
@@ -9185,16 +9338,54 @@ function buildBentoSelector(element) {
9185
9338
  if (title) {
9186
9339
  parts.push(`title=${escapeValue(truncate(title, 50))}`);
9187
9340
  }
9188
- const interactiveTags = ["button", "a", "label", "span", "div", "li", "td", "th", "h1", "h2", "h3", "h4", "h5", "h6", "p"];
9341
+ const interactiveTags = [
9342
+ "button",
9343
+ "a",
9344
+ "label",
9345
+ "span",
9346
+ "div",
9347
+ "li",
9348
+ "td",
9349
+ "th",
9350
+ "h1",
9351
+ "h2",
9352
+ "h3",
9353
+ "h4",
9354
+ "h5",
9355
+ "h6",
9356
+ "p"
9357
+ ];
9189
9358
  if (interactiveTags.includes(tag)) {
9190
- const text = getDirectText$1(element);
9359
+ let text = getDirectText$1(element);
9360
+ if (!text) {
9361
+ text = getFullText$1(element);
9362
+ }
9191
9363
  if (text) {
9192
9364
  parts.push(`text=${escapeValue(truncate(text, 50))}`);
9193
9365
  }
9194
9366
  }
9367
+ if (!hasContext) {
9368
+ const nth = getNthPosition(element);
9369
+ if (nth > 1) {
9370
+ parts.push(`nth=${nth}`);
9371
+ }
9372
+ }
9373
+ if (parts.length === 1 || parts.length === 2 && pathname) {
9374
+ const childSig = getChildSignature(element);
9375
+ if (childSig) {
9376
+ parts.push(`children=${childSig}`);
9377
+ } else {
9378
+ const attrs = Array.from(element.attributes).map((a2) => `${a2.name}=${a2.value}`).sort().join(";");
9379
+ parts.push(`hash=${quickHash(tag + attrs)}`);
9380
+ }
9381
+ }
9195
9382
  return parts.join("|");
9196
9383
  }
9197
- function generateSelector(outerHTML) {
9384
+ function generateSelectorFromElement(element, pathname) {
9385
+ const path = pathname != null ? pathname : typeof window !== "undefined" ? window.location.pathname : void 0;
9386
+ return buildBentoSelector(element, path);
9387
+ }
9388
+ function generateSelectorFromHTML(outerHTML) {
9198
9389
  const parser2 = new DOMParser();
9199
9390
  const doc = parser2.parseFromString(outerHTML, "text/html");
9200
9391
  const element = doc.body.firstElementChild;
@@ -9203,6 +9394,9 @@ function generateSelector(outerHTML) {
9203
9394
  }
9204
9395
  return buildBentoSelector(element);
9205
9396
  }
9397
+ function generateSelector(outerHTML) {
9398
+ return generateSelectorFromHTML(outerHTML);
9399
+ }
9206
9400
  var DEFAULT_OPTIONS = {
9207
9401
  maxTextLength: 500,
9208
9402
  includeOuterHTML: true,
@@ -9232,6 +9426,7 @@ function isInputElement(element) {
9232
9426
  return tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT";
9233
9427
  }
9234
9428
  function extractElementContext(element, options = {}) {
9429
+ var _a2;
9235
9430
  const opts = __spreadValues(__spreadValues({}, DEFAULT_OPTIONS), options.elementContextOptions);
9236
9431
  const {
9237
9432
  isClickEvent = false,
@@ -9295,7 +9490,8 @@ function extractElementContext(element, options = {}) {
9295
9490
  }
9296
9491
  if (isClickEvent) {
9297
9492
  try {
9298
- context.bentoSelector = buildBentoSelector(element);
9493
+ const path = (_a2 = options.pathname) != null ? _a2 : typeof window !== "undefined" ? window.location.pathname : void 0;
9494
+ context.bentoSelector = buildBentoSelector(element, path);
9299
9495
  } catch (e) {
9300
9496
  }
9301
9497
  }
@@ -10131,6 +10327,7 @@ function initMouseInteractionObserver({
10131
10327
  let currentPointerType = null;
10132
10328
  const getHandler = (eventKey) => {
10133
10329
  return (event) => {
10330
+ const pathname = typeof window !== "undefined" ? window.location.pathname : void 0;
10134
10331
  const target = getEventTarget(event);
10135
10332
  if (isBlocked(target, blockClass, blockSelector, true)) {
10136
10333
  return;
@@ -10181,7 +10378,9 @@ function initMouseInteractionObserver({
10181
10378
  elementContextOptions,
10182
10379
  isClickEvent,
10183
10380
  maskInputOptions,
10184
- maskInputFn
10381
+ maskInputFn,
10382
+ pathname
10383
+ // Pass pathname captured at event time
10185
10384
  });
10186
10385
  }
10187
10386
  callbackWrapper(mouseInteractionCb)(__spreadValues(__spreadValues({
@@ -12576,6 +12775,641 @@ var { freezePage } = record;
12576
12775
  var { takeFullSnapshot } = record;
12577
12776
 
12578
12777
  // src/index.ts
12778
+ var PERSISTENCE_KEYS = {
12779
+ DISTINCT_ID: "bentolabs_distinct_id",
12780
+ SUPER_PROPERTIES: "bentolabs_super_properties",
12781
+ OPTED_OUT: "bentolabs_opted_out",
12782
+ USER_PROPERTIES: "bentolabs_user_properties",
12783
+ IS_IDENTIFIED: "bentolabs_is_identified"
12784
+ };
12785
+ var Persistence = class {
12786
+ constructor(mode) {
12787
+ this.mode = mode;
12788
+ this.memoryStorage = /* @__PURE__ */ new Map();
12789
+ }
12790
+ get(key) {
12791
+ if (this.mode === "none") return null;
12792
+ if (this.mode === "memory") {
12793
+ return this.memoryStorage.get(key) || null;
12794
+ }
12795
+ if (this.mode === "localStorage" || this.mode === "localStorage+cookie") {
12796
+ try {
12797
+ const value = localStorage.getItem(key);
12798
+ if (value !== null) return value;
12799
+ } catch (e) {
12800
+ }
12801
+ }
12802
+ if (this.mode === "cookie" || this.mode === "localStorage+cookie") {
12803
+ try {
12804
+ const cookies = document.cookie.split(";");
12805
+ for (const cookie of cookies) {
12806
+ const [cookieKey, cookieValue] = cookie.trim().split("=");
12807
+ if (cookieKey === key) {
12808
+ return decodeURIComponent(cookieValue);
12809
+ }
12810
+ }
12811
+ } catch (e) {
12812
+ }
12813
+ }
12814
+ return null;
12815
+ }
12816
+ set(key, value) {
12817
+ if (this.mode === "none") return;
12818
+ if (this.mode === "memory") {
12819
+ this.memoryStorage.set(key, value);
12820
+ return;
12821
+ }
12822
+ if (this.mode === "localStorage" || this.mode === "localStorage+cookie") {
12823
+ try {
12824
+ localStorage.setItem(key, value);
12825
+ } catch (e) {
12826
+ }
12827
+ }
12828
+ if (this.mode === "cookie" || this.mode === "localStorage+cookie") {
12829
+ try {
12830
+ const expires = /* @__PURE__ */ new Date();
12831
+ expires.setFullYear(expires.getFullYear() + 1);
12832
+ document.cookie = `${key}=${encodeURIComponent(value)};expires=${expires.toUTCString()};path=/;SameSite=Lax`;
12833
+ } catch (e) {
12834
+ }
12835
+ }
12836
+ }
12837
+ remove(key) {
12838
+ if (this.mode === "none") return;
12839
+ if (this.mode === "memory") {
12840
+ this.memoryStorage.delete(key);
12841
+ return;
12842
+ }
12843
+ if (this.mode === "localStorage" || this.mode === "localStorage+cookie") {
12844
+ try {
12845
+ localStorage.removeItem(key);
12846
+ } catch (e) {
12847
+ }
12848
+ }
12849
+ if (this.mode === "cookie" || this.mode === "localStorage+cookie") {
12850
+ try {
12851
+ document.cookie = `${key}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
12852
+ } catch (e) {
12853
+ }
12854
+ }
12855
+ }
12856
+ clear() {
12857
+ if (this.mode === "memory") {
12858
+ this.memoryStorage.clear();
12859
+ return;
12860
+ }
12861
+ Object.values(PERSISTENCE_KEYS).forEach((key) => this.remove(key));
12862
+ }
12863
+ };
12864
+ var NetworkMonitor = class {
12865
+ constructor(options, onCapture) {
12866
+ this.logs = [];
12867
+ this.originalFetch = null;
12868
+ this.originalXHROpen = null;
12869
+ this.originalXHRSend = null;
12870
+ this.isMonitoring = false;
12871
+ var _a2, _b, _c, _d;
12872
+ this.options = {
12873
+ skipUrls: options.skipUrls || ["api.bentolabs.ai"],
12874
+ captureHeaders: (_a2 = options.captureHeaders) != null ? _a2 : false,
12875
+ captureBody: (_b = options.captureBody) != null ? _b : false,
12876
+ maxBodySize: (_c = options.maxBodySize) != null ? _c : 5e3,
12877
+ sampleRate: (_d = options.sampleRate) != null ? _d : 1
12878
+ };
12879
+ this.onCapture = onCapture;
12880
+ }
12881
+ start() {
12882
+ if (this.isMonitoring || typeof window === "undefined") return;
12883
+ this.isMonitoring = true;
12884
+ this.interceptFetch();
12885
+ this.interceptXHR();
12886
+ }
12887
+ stop() {
12888
+ if (!this.isMonitoring) return;
12889
+ this.isMonitoring = false;
12890
+ this.restoreFetch();
12891
+ this.restoreXHR();
12892
+ }
12893
+ getLogs() {
12894
+ return [...this.logs];
12895
+ }
12896
+ clearLogs() {
12897
+ this.logs = [];
12898
+ }
12899
+ shouldSkip(url) {
12900
+ return this.options.skipUrls.some((skipUrl) => url.includes(skipUrl));
12901
+ }
12902
+ shouldSample() {
12903
+ return Math.random() < this.options.sampleRate;
12904
+ }
12905
+ sanitizeUrl(url) {
12906
+ try {
12907
+ const urlObj = new URL(url, window.location.origin);
12908
+ const sensitiveParams = ["token", "key", "secret", "password", "auth", "apikey", "api_key"];
12909
+ sensitiveParams.forEach((param) => {
12910
+ if (urlObj.searchParams.has(param)) {
12911
+ urlObj.searchParams.set(param, "[REDACTED]");
12912
+ }
12913
+ });
12914
+ return urlObj.toString();
12915
+ } catch (e) {
12916
+ return url;
12917
+ }
12918
+ }
12919
+ interceptFetch() {
12920
+ if (typeof fetch === "undefined") return;
12921
+ this.originalFetch = fetch.bind(window);
12922
+ const self = this;
12923
+ window.fetch = async function(input2, init) {
12924
+ const url = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
12925
+ if (self.shouldSkip(url) || !self.shouldSample()) {
12926
+ return self.originalFetch(input2, init);
12927
+ }
12928
+ const startTime = Date.now();
12929
+ const method = (init == null ? void 0 : init.method) || "GET";
12930
+ try {
12931
+ const response = await self.originalFetch(input2, init);
12932
+ const duration = Date.now() - startTime;
12933
+ const log = {
12934
+ url: self.sanitizeUrl(url),
12935
+ method: method.toUpperCase(),
12936
+ status: response.status,
12937
+ duration,
12938
+ responseSize: parseInt(response.headers.get("content-length") || "0", 10),
12939
+ initiator: "fetch",
12940
+ timestamp: Date.now()
12941
+ };
12942
+ self.logs.push(log);
12943
+ if (self.logs.length > 500) self.logs.shift();
12944
+ self.onCapture(log);
12945
+ return response;
12946
+ } catch (error) {
12947
+ const duration = Date.now() - startTime;
12948
+ const log = {
12949
+ url: self.sanitizeUrl(url),
12950
+ method: method.toUpperCase(),
12951
+ status: 0,
12952
+ duration,
12953
+ responseSize: 0,
12954
+ initiator: "fetch",
12955
+ error: error instanceof Error ? error.message : "Unknown error",
12956
+ timestamp: Date.now()
12957
+ };
12958
+ self.logs.push(log);
12959
+ if (self.logs.length > 500) self.logs.shift();
12960
+ self.onCapture(log);
12961
+ throw error;
12962
+ }
12963
+ };
12964
+ }
12965
+ restoreFetch() {
12966
+ if (this.originalFetch && typeof window !== "undefined") {
12967
+ window.fetch = this.originalFetch;
12968
+ this.originalFetch = null;
12969
+ }
12970
+ }
12971
+ interceptXHR() {
12972
+ if (typeof XMLHttpRequest === "undefined") return;
12973
+ this.originalXHROpen = XMLHttpRequest.prototype.open;
12974
+ this.originalXHRSend = XMLHttpRequest.prototype.send;
12975
+ const self = this;
12976
+ XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
12977
+ this._bentoUrl = url.toString();
12978
+ this._bentoMethod = method;
12979
+ this._bentoStartTime = 0;
12980
+ return self.originalXHROpen.call(this, method, url, async != null ? async : true, username, password);
12981
+ };
12982
+ XMLHttpRequest.prototype.send = function(body) {
12983
+ const xhr = this;
12984
+ const url = xhr._bentoUrl;
12985
+ if (self.shouldSkip(url) || !self.shouldSample()) {
12986
+ return self.originalXHRSend.call(this, body);
12987
+ }
12988
+ xhr._bentoStartTime = Date.now();
12989
+ const handleLoadEnd = () => {
12990
+ const duration = Date.now() - xhr._bentoStartTime;
12991
+ const log = {
12992
+ url: self.sanitizeUrl(url),
12993
+ method: xhr._bentoMethod.toUpperCase(),
12994
+ status: xhr.status,
12995
+ duration,
12996
+ responseSize: parseInt(xhr.getResponseHeader("content-length") || "0", 10),
12997
+ initiator: "xhr",
12998
+ timestamp: Date.now()
12999
+ };
13000
+ if (xhr.status === 0) {
13001
+ log.error = "Request failed or was aborted";
13002
+ }
13003
+ self.logs.push(log);
13004
+ if (self.logs.length > 500) self.logs.shift();
13005
+ self.onCapture(log);
13006
+ };
13007
+ xhr.addEventListener("loadend", handleLoadEnd);
13008
+ return self.originalXHRSend.call(this, body);
13009
+ };
13010
+ }
13011
+ restoreXHR() {
13012
+ if (this.originalXHROpen) {
13013
+ XMLHttpRequest.prototype.open = this.originalXHROpen;
13014
+ this.originalXHROpen = null;
13015
+ }
13016
+ if (this.originalXHRSend) {
13017
+ XMLHttpRequest.prototype.send = this.originalXHRSend;
13018
+ this.originalXHRSend = null;
13019
+ }
13020
+ }
13021
+ };
13022
+ var StorageMonitor = class {
13023
+ constructor(options, onCapture) {
13024
+ this.logs = [];
13025
+ this.isMonitoring = false;
13026
+ this.originalLocalStorage = null;
13027
+ this.originalSessionStorage = null;
13028
+ this.originalCookieDescriptor = null;
13029
+ var _a2, _b, _c;
13030
+ this.options = {
13031
+ localStorage: (_a2 = options.localStorage) != null ? _a2 : true,
13032
+ sessionStorage: (_b = options.sessionStorage) != null ? _b : true,
13033
+ cookies: (_c = options.cookies) != null ? _c : true,
13034
+ sensitiveKeys: options.sensitiveKeys || ["password", "token", "secret", "api_key", "apikey", "auth", "credential"]
13035
+ };
13036
+ this.onCapture = onCapture;
13037
+ }
13038
+ start() {
13039
+ if (this.isMonitoring || typeof window === "undefined") return;
13040
+ this.isMonitoring = true;
13041
+ if (this.options.localStorage) this.interceptLocalStorage();
13042
+ if (this.options.sessionStorage) this.interceptSessionStorage();
13043
+ if (this.options.cookies) this.interceptCookies();
13044
+ }
13045
+ stop() {
13046
+ if (!this.isMonitoring) return;
13047
+ this.isMonitoring = false;
13048
+ this.restoreLocalStorage();
13049
+ this.restoreSessionStorage();
13050
+ this.restoreCookies();
13051
+ }
13052
+ getLogs() {
13053
+ return [...this.logs];
13054
+ }
13055
+ clearLogs() {
13056
+ this.logs = [];
13057
+ }
13058
+ isSensitiveKey(key) {
13059
+ const lowerKey = key.toLowerCase();
13060
+ return this.options.sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive.toLowerCase()));
13061
+ }
13062
+ addLog(log) {
13063
+ if (this.isSensitiveKey(log.key) || log.key.startsWith("bentolabs_")) return;
13064
+ this.logs.push(log);
13065
+ if (this.logs.length > 500) this.logs.shift();
13066
+ this.onCapture(log);
13067
+ }
13068
+ interceptLocalStorage() {
13069
+ if (typeof localStorage === "undefined") return;
13070
+ const self = this;
13071
+ this.originalLocalStorage = {
13072
+ setItem: localStorage.setItem.bind(localStorage),
13073
+ getItem: localStorage.getItem.bind(localStorage),
13074
+ removeItem: localStorage.removeItem.bind(localStorage),
13075
+ clear: localStorage.clear.bind(localStorage)
13076
+ };
13077
+ localStorage.setItem = function(key, value) {
13078
+ self.addLog({
13079
+ storageType: "localStorage",
13080
+ operation: "set",
13081
+ key,
13082
+ valueType: typeof value,
13083
+ timestamp: Date.now()
13084
+ });
13085
+ return self.originalLocalStorage.setItem(key, value);
13086
+ };
13087
+ localStorage.getItem = function(key) {
13088
+ self.addLog({
13089
+ storageType: "localStorage",
13090
+ operation: "get",
13091
+ key,
13092
+ timestamp: Date.now()
13093
+ });
13094
+ return self.originalLocalStorage.getItem(key);
13095
+ };
13096
+ localStorage.removeItem = function(key) {
13097
+ self.addLog({
13098
+ storageType: "localStorage",
13099
+ operation: "remove",
13100
+ key,
13101
+ timestamp: Date.now()
13102
+ });
13103
+ return self.originalLocalStorage.removeItem(key);
13104
+ };
13105
+ localStorage.clear = function() {
13106
+ self.addLog({
13107
+ storageType: "localStorage",
13108
+ operation: "clear",
13109
+ key: "*",
13110
+ timestamp: Date.now()
13111
+ });
13112
+ return self.originalLocalStorage.clear();
13113
+ };
13114
+ }
13115
+ restoreLocalStorage() {
13116
+ if (this.originalLocalStorage && typeof localStorage !== "undefined") {
13117
+ localStorage.setItem = this.originalLocalStorage.setItem;
13118
+ localStorage.getItem = this.originalLocalStorage.getItem;
13119
+ localStorage.removeItem = this.originalLocalStorage.removeItem;
13120
+ localStorage.clear = this.originalLocalStorage.clear;
13121
+ this.originalLocalStorage = null;
13122
+ }
13123
+ }
13124
+ interceptSessionStorage() {
13125
+ if (typeof sessionStorage === "undefined") return;
13126
+ const self = this;
13127
+ this.originalSessionStorage = {
13128
+ setItem: sessionStorage.setItem.bind(sessionStorage),
13129
+ getItem: sessionStorage.getItem.bind(sessionStorage),
13130
+ removeItem: sessionStorage.removeItem.bind(sessionStorage),
13131
+ clear: sessionStorage.clear.bind(sessionStorage)
13132
+ };
13133
+ sessionStorage.setItem = function(key, value) {
13134
+ self.addLog({
13135
+ storageType: "sessionStorage",
13136
+ operation: "set",
13137
+ key,
13138
+ valueType: typeof value,
13139
+ timestamp: Date.now()
13140
+ });
13141
+ return self.originalSessionStorage.setItem(key, value);
13142
+ };
13143
+ sessionStorage.getItem = function(key) {
13144
+ self.addLog({
13145
+ storageType: "sessionStorage",
13146
+ operation: "get",
13147
+ key,
13148
+ timestamp: Date.now()
13149
+ });
13150
+ return self.originalSessionStorage.getItem(key);
13151
+ };
13152
+ sessionStorage.removeItem = function(key) {
13153
+ self.addLog({
13154
+ storageType: "sessionStorage",
13155
+ operation: "remove",
13156
+ key,
13157
+ timestamp: Date.now()
13158
+ });
13159
+ return self.originalSessionStorage.removeItem(key);
13160
+ };
13161
+ sessionStorage.clear = function() {
13162
+ self.addLog({
13163
+ storageType: "sessionStorage",
13164
+ operation: "clear",
13165
+ key: "*",
13166
+ timestamp: Date.now()
13167
+ });
13168
+ return self.originalSessionStorage.clear();
13169
+ };
13170
+ }
13171
+ restoreSessionStorage() {
13172
+ if (this.originalSessionStorage && typeof sessionStorage !== "undefined") {
13173
+ sessionStorage.setItem = this.originalSessionStorage.setItem;
13174
+ sessionStorage.getItem = this.originalSessionStorage.getItem;
13175
+ sessionStorage.removeItem = this.originalSessionStorage.removeItem;
13176
+ sessionStorage.clear = this.originalSessionStorage.clear;
13177
+ this.originalSessionStorage = null;
13178
+ }
13179
+ }
13180
+ interceptCookies() {
13181
+ if (typeof document === "undefined") return;
13182
+ const self = this;
13183
+ const descriptor = Object.getOwnPropertyDescriptor(Document.prototype, "cookie");
13184
+ if (!descriptor) return;
13185
+ this.originalCookieDescriptor = descriptor;
13186
+ Object.defineProperty(document, "cookie", {
13187
+ get: function() {
13188
+ return self.originalCookieDescriptor.get.call(this);
13189
+ },
13190
+ set: function(value) {
13191
+ const name = value.split("=")[0].trim();
13192
+ self.addLog({
13193
+ storageType: "cookie",
13194
+ operation: "set",
13195
+ key: name,
13196
+ timestamp: Date.now()
13197
+ });
13198
+ return self.originalCookieDescriptor.set.call(this, value);
13199
+ },
13200
+ configurable: true
13201
+ });
13202
+ }
13203
+ restoreCookies() {
13204
+ if (this.originalCookieDescriptor && typeof document !== "undefined") {
13205
+ Object.defineProperty(document, "cookie", this.originalCookieDescriptor);
13206
+ this.originalCookieDescriptor = null;
13207
+ }
13208
+ }
13209
+ };
13210
+ var ConsoleMonitor = class {
13211
+ constructor(options, onCapture) {
13212
+ this.logs = [];
13213
+ this.isMonitoring = false;
13214
+ this.inStack = false;
13215
+ this.originalConsole = null;
13216
+ this.originalOnError = null;
13217
+ this.originalOnUnhandledRejection = null;
13218
+ var _a2, _b, _c;
13219
+ this.options = {
13220
+ levels: options.levels || ["log", "warn", "error", "info"],
13221
+ maxLogs: (_a2 = options.maxLogs) != null ? _a2 : 500,
13222
+ maxPayloadSize: (_b = options.maxPayloadSize) != null ? _b : 5e3,
13223
+ captureStackTrace: (_c = options.captureStackTrace) != null ? _c : true
13224
+ };
13225
+ this.onCapture = onCapture;
13226
+ }
13227
+ start() {
13228
+ if (this.isMonitoring || typeof console === "undefined") return;
13229
+ this.isMonitoring = true;
13230
+ this.interceptConsole();
13231
+ this.interceptGlobalErrors();
13232
+ }
13233
+ stop() {
13234
+ if (!this.isMonitoring) return;
13235
+ this.isMonitoring = false;
13236
+ this.restoreConsole();
13237
+ this.restoreGlobalErrors();
13238
+ }
13239
+ getLogs() {
13240
+ return [...this.logs];
13241
+ }
13242
+ clearLogs() {
13243
+ this.logs = [];
13244
+ }
13245
+ stringifyArg(arg, seen = /* @__PURE__ */ new WeakSet()) {
13246
+ if (arg === null) return "null";
13247
+ if (arg === void 0) return "undefined";
13248
+ const type = typeof arg;
13249
+ if (type === "string") return arg;
13250
+ if (type === "number" || type === "boolean") return String(arg);
13251
+ if (type === "function") return `[Function: ${arg.name || "anonymous"}]`;
13252
+ if (type === "symbol") return arg.toString();
13253
+ if (type === "object") {
13254
+ if (seen.has(arg)) return "[Circular]";
13255
+ seen.add(arg);
13256
+ if (arg instanceof Error) {
13257
+ return `${arg.name}: ${arg.message}`;
13258
+ }
13259
+ try {
13260
+ const str = JSON.stringify(arg, (key, value) => {
13261
+ if (typeof value === "object" && value !== null) {
13262
+ if (seen.has(value)) return "[Circular]";
13263
+ seen.add(value);
13264
+ }
13265
+ if (typeof value === "function") return `[Function: ${value.name || "anonymous"}]`;
13266
+ if (typeof value === "symbol") return value.toString();
13267
+ return value;
13268
+ });
13269
+ return str.length > this.options.maxPayloadSize ? str.slice(0, this.options.maxPayloadSize) + "..." : str;
13270
+ } catch (e) {
13271
+ return "[Object]";
13272
+ }
13273
+ }
13274
+ return String(arg);
13275
+ }
13276
+ getStackTrace() {
13277
+ if (!this.options.captureStackTrace) return [];
13278
+ try {
13279
+ const stack = new Error().stack || "";
13280
+ const lines = stack.split("\n").slice(3);
13281
+ return lines.slice(0, 5).map((line) => line.trim());
13282
+ } catch (e) {
13283
+ return [];
13284
+ }
13285
+ }
13286
+ addLog(level, args) {
13287
+ if (this.inStack) return;
13288
+ this.inStack = true;
13289
+ try {
13290
+ const payload = args.map((arg) => this.stringifyArg(arg));
13291
+ const log = {
13292
+ level,
13293
+ payload,
13294
+ trace: this.getStackTrace(),
13295
+ timestamp: Date.now()
13296
+ };
13297
+ this.logs.push(log);
13298
+ if (this.logs.length > this.options.maxLogs) this.logs.shift();
13299
+ this.onCapture(log);
13300
+ } finally {
13301
+ this.inStack = false;
13302
+ }
13303
+ }
13304
+ interceptConsole() {
13305
+ const self = this;
13306
+ this.originalConsole = {
13307
+ log: console.log.bind(console),
13308
+ warn: console.warn.bind(console),
13309
+ error: console.error.bind(console),
13310
+ info: console.info.bind(console),
13311
+ debug: console.debug.bind(console)
13312
+ };
13313
+ const levels = this.options.levels;
13314
+ if (levels.includes("log")) {
13315
+ console.log = function(...args) {
13316
+ self.addLog("log", args);
13317
+ return self.originalConsole.log(...args);
13318
+ };
13319
+ }
13320
+ if (levels.includes("warn")) {
13321
+ console.warn = function(...args) {
13322
+ self.addLog("warn", args);
13323
+ return self.originalConsole.warn(...args);
13324
+ };
13325
+ }
13326
+ if (levels.includes("error")) {
13327
+ console.error = function(...args) {
13328
+ self.addLog("error", args);
13329
+ return self.originalConsole.error(...args);
13330
+ };
13331
+ }
13332
+ if (levels.includes("info")) {
13333
+ console.info = function(...args) {
13334
+ self.addLog("info", args);
13335
+ return self.originalConsole.info(...args);
13336
+ };
13337
+ }
13338
+ if (levels.includes("debug")) {
13339
+ console.debug = function(...args) {
13340
+ self.addLog("debug", args);
13341
+ return self.originalConsole.debug(...args);
13342
+ };
13343
+ }
13344
+ }
13345
+ restoreConsole() {
13346
+ if (this.originalConsole) {
13347
+ console.log = this.originalConsole.log;
13348
+ console.warn = this.originalConsole.warn;
13349
+ console.error = this.originalConsole.error;
13350
+ console.info = this.originalConsole.info;
13351
+ console.debug = this.originalConsole.debug;
13352
+ this.originalConsole = null;
13353
+ }
13354
+ }
13355
+ interceptGlobalErrors() {
13356
+ if (typeof window === "undefined") return;
13357
+ const self = this;
13358
+ this.originalOnError = window.onerror;
13359
+ window.onerror = function(message, source, lineno, colno, error) {
13360
+ self.addLog("error", [
13361
+ `Uncaught error: ${message}`,
13362
+ `at ${source}:${lineno}:${colno}`,
13363
+ (error == null ? void 0 : error.stack) || ""
13364
+ ]);
13365
+ if (self.originalOnError && typeof self.originalOnError === "function") {
13366
+ return self.originalOnError(message, source, lineno, colno, error);
13367
+ }
13368
+ return false;
13369
+ };
13370
+ this.originalOnUnhandledRejection = window.onunhandledrejection;
13371
+ window.onunhandledrejection = function(event) {
13372
+ self.addLog("error", [`Unhandled promise rejection: ${self.stringifyArg(event.reason)}`]);
13373
+ if (self.originalOnUnhandledRejection) {
13374
+ self.originalOnUnhandledRejection(event);
13375
+ }
13376
+ };
13377
+ }
13378
+ restoreGlobalErrors() {
13379
+ if (typeof window === "undefined") return;
13380
+ if (this.originalOnError !== null) {
13381
+ window.onerror = this.originalOnError;
13382
+ this.originalOnError = null;
13383
+ }
13384
+ if (this.originalOnUnhandledRejection !== null) {
13385
+ window.onunhandledrejection = this.originalOnUnhandledRejection;
13386
+ this.originalOnUnhandledRejection = null;
13387
+ }
13388
+ }
13389
+ };
13390
+ var PeopleAPI = class {
13391
+ constructor(sdk2) {
13392
+ this.sdk = sdk2;
13393
+ }
13394
+ /**
13395
+ * Set properties on the current user
13396
+ */
13397
+ set(properties) {
13398
+ this.sdk.capture("$set", { $set: properties });
13399
+ }
13400
+ /**
13401
+ * Set properties on the current user only if they haven't been set already
13402
+ */
13403
+ setOnce(properties) {
13404
+ this.sdk.capture("$set", { $set_once: properties });
13405
+ }
13406
+ /**
13407
+ * Remove properties from the current user
13408
+ */
13409
+ unset(propertyNames) {
13410
+ this.sdk.capture("$set", { $unset: propertyNames });
13411
+ }
13412
+ };
12579
13413
  var BentoLabsSDK = class {
12580
13414
  constructor() {
12581
13415
  this.config = {
@@ -12584,43 +13418,114 @@ var BentoLabsSDK = class {
12584
13418
  debug: false,
12585
13419
  batchSize: 50,
12586
13420
  batchInterval: 1e4,
12587
- // 10 seconds
12588
13421
  enableRecording: true,
12589
13422
  maxRetries: 3,
12590
- baseRetryDelay: 1e3
13423
+ baseRetryDelay: 1e3,
13424
+ enableCompression: true,
13425
+ // PostHog-like defaults
13426
+ persistence: "localStorage+cookie",
13427
+ respect_dnt: false,
13428
+ autocapture: true,
13429
+ capture_pageview: true,
13430
+ capture_pageleave: true,
13431
+ // Monitoring defaults
13432
+ capture_network: true,
13433
+ networkOptions: {
13434
+ skipUrls: ["api.bentolabs.ai"],
13435
+ captureHeaders: false,
13436
+ captureBody: false,
13437
+ maxBodySize: 5e3,
13438
+ sampleRate: 1
13439
+ },
13440
+ capture_storage: true,
13441
+ storageOptions: {
13442
+ localStorage: true,
13443
+ sessionStorage: true,
13444
+ cookies: true,
13445
+ sensitiveKeys: ["password", "token", "secret", "api_key", "apikey", "auth", "credential"]
13446
+ },
13447
+ capture_console: true,
13448
+ consoleOptions: {
13449
+ levels: ["log", "warn", "error", "info"],
13450
+ maxLogs: 500,
13451
+ maxPayloadSize: 5e3,
13452
+ captureStackTrace: true
13453
+ }
12591
13454
  };
12592
13455
  this.sessionId = "";
12593
13456
  this.events = [];
12594
13457
  this.isRecording = false;
12595
13458
  this.batchTimer = null;
12596
13459
  this.retryTimer = null;
12597
- this.stopRecording = null;
13460
+ this.stopRecordingFn = null;
13461
+ this.distinctId = "";
13462
+ this.identity = {
13463
+ distinctId: null,
13464
+ properties: {},
13465
+ isIdentified: false
13466
+ };
13467
+ this.superProperties = {};
13468
+ this.persistence = null;
13469
+ this.initialized = false;
13470
+ this.optedOut = false;
13471
+ this.networkMonitor = null;
13472
+ this.storageMonitor = null;
13473
+ this.consoleMonitor = null;
13474
+ this.people = new PeopleAPI(this);
13475
+ this.autocaptureCleanup = null;
12598
13476
  }
12599
13477
  /**
12600
13478
  * Initialize the BentoLabs SDK
12601
- * @param apiKey - Your API key for authentication
12602
- * @param options - Optional configuration options
12603
13479
  */
12604
13480
  init(apiKey, options) {
12605
- var _a2;
12606
- if (this.isRecording || this.stopRecording) {
13481
+ var _a2, _b, _c;
13482
+ if (this.isRecording || this.stopRecordingFn) {
12607
13483
  this.stopRecordingInternal();
12608
13484
  }
12609
13485
  const defaultOptions = {
12610
13486
  endpoint: "https://api.bentolabs.ai",
12611
13487
  debug: false,
12612
13488
  batchSize: 100,
12613
- // Increased from 50 to reduce API calls
12614
- batchInterval: 3e4,
12615
- // 30 seconds (increased from 10s)
13489
+ batchInterval: 5e3,
13490
+ // 5 seconds (PostHog-style)
12616
13491
  enableRecording: true,
12617
13492
  maxRetries: 3,
12618
- baseRetryDelay: 1e3
12619
- // 1 second
13493
+ baseRetryDelay: 1e3,
13494
+ enableCompression: true,
13495
+ // PostHog-like defaults
13496
+ persistence: "localStorage+cookie",
13497
+ respect_dnt: false,
13498
+ autocapture: true,
13499
+ capture_pageview: true,
13500
+ capture_pageleave: true,
13501
+ // Monitoring defaults
13502
+ capture_network: true,
13503
+ networkOptions: {
13504
+ skipUrls: ["api.bentolabs.ai"],
13505
+ captureHeaders: false,
13506
+ captureBody: false,
13507
+ maxBodySize: 5e3,
13508
+ sampleRate: 1
13509
+ },
13510
+ capture_storage: true,
13511
+ storageOptions: {
13512
+ localStorage: true,
13513
+ sessionStorage: true,
13514
+ cookies: true,
13515
+ sensitiveKeys: ["password", "token", "secret", "api_key", "apikey", "auth", "credential"]
13516
+ },
13517
+ capture_console: true,
13518
+ consoleOptions: {
13519
+ levels: ["log", "warn", "error", "info"],
13520
+ maxLogs: 500,
13521
+ maxPayloadSize: 5e3,
13522
+ captureStackTrace: true
13523
+ }
12620
13524
  };
12621
13525
  const mergedOptions = __spreadProps(__spreadValues(__spreadValues({}, defaultOptions), options), {
12622
- // Ensure debug is always a boolean
12623
- debug: (_a2 = options == null ? void 0 : options.debug) != null ? _a2 : defaultOptions.debug
13526
+ networkOptions: __spreadValues(__spreadValues({}, defaultOptions.networkOptions), options == null ? void 0 : options.networkOptions),
13527
+ storageOptions: __spreadValues(__spreadValues({}, defaultOptions.storageOptions), options == null ? void 0 : options.storageOptions),
13528
+ consoleOptions: __spreadValues(__spreadValues({}, defaultOptions.consoleOptions), options == null ? void 0 : options.consoleOptions)
12624
13529
  });
12625
13530
  this.config = {
12626
13531
  apiKey,
@@ -12630,57 +13535,523 @@ var BentoLabsSDK = class {
12630
13535
  batchInterval: mergedOptions.batchInterval,
12631
13536
  enableRecording: mergedOptions.enableRecording,
12632
13537
  maxRetries: mergedOptions.maxRetries,
12633
- baseRetryDelay: mergedOptions.baseRetryDelay
13538
+ baseRetryDelay: mergedOptions.baseRetryDelay,
13539
+ enableCompression: (_a2 = mergedOptions.enableCompression) != null ? _a2 : true,
13540
+ persistence: mergedOptions.persistence,
13541
+ respect_dnt: mergedOptions.respect_dnt,
13542
+ autocapture: mergedOptions.autocapture,
13543
+ capture_pageview: mergedOptions.capture_pageview,
13544
+ capture_pageleave: mergedOptions.capture_pageleave,
13545
+ capture_network: mergedOptions.capture_network,
13546
+ networkOptions: mergedOptions.networkOptions,
13547
+ capture_storage: mergedOptions.capture_storage,
13548
+ storageOptions: mergedOptions.storageOptions,
13549
+ capture_console: mergedOptions.capture_console,
13550
+ consoleOptions: mergedOptions.consoleOptions,
13551
+ bootstrap: mergedOptions.bootstrap
12634
13552
  };
13553
+ if (this.config.respect_dnt && this.isDNTEnabled()) {
13554
+ if (this.config.debug) {
13555
+ console.log("[BentoLabsSDK] Do Not Track is enabled, opting out");
13556
+ }
13557
+ this.optedOut = true;
13558
+ }
13559
+ this.persistence = new Persistence(this.config.persistence);
13560
+ this.loadPersistedState();
13561
+ if ((_b = this.config.bootstrap) == null ? void 0 : _b.distinctID) {
13562
+ this.distinctId = this.config.bootstrap.distinctID;
13563
+ this.identity.distinctId = this.config.bootstrap.distinctID;
13564
+ this.identity.isIdentified = (_c = this.config.bootstrap.isIdentifiedID) != null ? _c : false;
13565
+ this.persistence.set(PERSISTENCE_KEYS.DISTINCT_ID, this.distinctId);
13566
+ if (this.identity.isIdentified) {
13567
+ this.persistence.set(PERSISTENCE_KEYS.IS_IDENTIFIED, "true");
13568
+ }
13569
+ }
13570
+ if (!this.distinctId) {
13571
+ this.distinctId = this.generateAnonymousId();
13572
+ this.persistence.set(PERSISTENCE_KEYS.DISTINCT_ID, this.distinctId);
13573
+ }
12635
13574
  this.sessionId = this.generateSessionId();
13575
+ this.initialized = true;
12636
13576
  if (this.config.debug) {
12637
13577
  console.log("[BentoLabsSDK] Initialized with config:", {
12638
13578
  apiKey: apiKey.substring(0, 8) + "...",
12639
13579
  endpoint: this.config.endpoint,
12640
13580
  debug: this.config.debug,
12641
- sessionId: this.sessionId
13581
+ sessionId: this.sessionId,
13582
+ distinctId: this.distinctId
12642
13583
  });
12643
13584
  }
12644
13585
  this.startRecording();
12645
13586
  this.startBatching();
13587
+ if (this.config.autocapture) {
13588
+ this.setupAutocapture();
13589
+ }
13590
+ if (this.config.capture_pageview) {
13591
+ this.capture("$pageview", {
13592
+ $current_url: typeof window !== "undefined" ? window.location.href : "",
13593
+ $pathname: typeof window !== "undefined" ? window.location.pathname : "",
13594
+ $referrer: typeof document !== "undefined" ? document.referrer : ""
13595
+ });
13596
+ }
13597
+ if (this.config.capture_pageleave && typeof window !== "undefined") {
13598
+ window.addEventListener("beforeunload", this.handlePageLeave.bind(this));
13599
+ }
13600
+ if (this.config.capture_network) {
13601
+ this.startNetworkCapture();
13602
+ }
13603
+ if (this.config.capture_storage) {
13604
+ this.startStorageCapture();
13605
+ }
13606
+ if (this.config.capture_console) {
13607
+ this.startConsoleCapture();
13608
+ }
12646
13609
  }
13610
+ // ============================================================================
13611
+ // Identity Methods
13612
+ // ============================================================================
12647
13613
  /**
12648
- * Generate a unique session ID with 'sess_' prefix
13614
+ * Identify a user with a distinct ID and optional properties
12649
13615
  */
12650
- generateSessionId() {
12651
- const uuid = this.generateUUID();
12652
- return `sess_${uuid}`;
13616
+ identify(distinctId, properties) {
13617
+ var _a2, _b, _c;
13618
+ if (!this.initialized) {
13619
+ console.warn("[BentoLabsSDK] Cannot identify: SDK not initialized");
13620
+ return;
13621
+ }
13622
+ const previousId = this.distinctId;
13623
+ this.distinctId = distinctId;
13624
+ this.identity.distinctId = distinctId;
13625
+ this.identity.isIdentified = true;
13626
+ if (properties) {
13627
+ this.identity.properties = __spreadValues(__spreadValues({}, this.identity.properties), properties);
13628
+ }
13629
+ (_a2 = this.persistence) == null ? void 0 : _a2.set(PERSISTENCE_KEYS.DISTINCT_ID, distinctId);
13630
+ (_b = this.persistence) == null ? void 0 : _b.set(PERSISTENCE_KEYS.IS_IDENTIFIED, "true");
13631
+ if (properties) {
13632
+ (_c = this.persistence) == null ? void 0 : _c.set(PERSISTENCE_KEYS.USER_PROPERTIES, JSON.stringify(this.identity.properties));
13633
+ }
13634
+ this.capture("$identify", __spreadValues({
13635
+ $anon_distinct_id: previousId
13636
+ }, properties));
13637
+ if (this.config.debug) {
13638
+ console.log("[BentoLabsSDK] Identified user:", distinctId);
13639
+ }
12653
13640
  }
12654
13641
  /**
12655
- * Generate a simple UUID v4
13642
+ * Reset the user to an anonymous state
12656
13643
  */
12657
- generateUUID() {
12658
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
12659
- /[xy]/g,
12660
- function(c) {
12661
- const r = Math.random() * 16 | 0;
12662
- const v = c === "x" ? r : r & 3 | 8;
12663
- return v.toString(16);
12664
- }
12665
- );
13644
+ reset() {
13645
+ var _a2, _b;
13646
+ if (!this.initialized) return;
13647
+ this.distinctId = this.generateAnonymousId();
13648
+ this.identity = {
13649
+ distinctId: null,
13650
+ properties: {},
13651
+ isIdentified: false
13652
+ };
13653
+ this.superProperties = {};
13654
+ (_a2 = this.persistence) == null ? void 0 : _a2.clear();
13655
+ (_b = this.persistence) == null ? void 0 : _b.set(PERSISTENCE_KEYS.DISTINCT_ID, this.distinctId);
13656
+ if (this.config.debug) {
13657
+ console.log("[BentoLabsSDK] Reset to anonymous:", this.distinctId);
13658
+ }
12666
13659
  }
12667
13660
  /**
12668
- * Start recording user interactions
13661
+ * Get the current distinct ID
12669
13662
  */
12670
- startRecording() {
12671
- if (!this.config.enableRecording) {
12672
- if (this.config.debug) {
12673
- console.log("[BentoLabsSDK] Recording disabled in configuration");
13663
+ getDistinctId() {
13664
+ return this.distinctId;
13665
+ }
13666
+ // ============================================================================
13667
+ // Super Properties Methods
13668
+ // ============================================================================
13669
+ /**
13670
+ * Register super properties to be sent with every event
13671
+ */
13672
+ register(properties) {
13673
+ var _a2;
13674
+ this.superProperties = __spreadValues(__spreadValues({}, this.superProperties), properties);
13675
+ (_a2 = this.persistence) == null ? void 0 : _a2.set(PERSISTENCE_KEYS.SUPER_PROPERTIES, JSON.stringify(this.superProperties));
13676
+ if (this.config.debug) {
13677
+ console.log("[BentoLabsSDK] Registered super properties:", properties);
13678
+ }
13679
+ }
13680
+ /**
13681
+ * Register super properties only if they haven't been set
13682
+ */
13683
+ registerOnce(properties) {
13684
+ const newProps = {};
13685
+ for (const [key, value] of Object.entries(properties)) {
13686
+ if (!(key in this.superProperties)) {
13687
+ newProps[key] = value;
12674
13688
  }
12675
- return;
12676
13689
  }
13690
+ if (Object.keys(newProps).length > 0) {
13691
+ this.register(newProps);
13692
+ }
13693
+ }
13694
+ /**
13695
+ * Remove a super property
13696
+ */
13697
+ unregister(propertyName) {
13698
+ var _a2;
13699
+ delete this.superProperties[propertyName];
13700
+ (_a2 = this.persistence) == null ? void 0 : _a2.set(PERSISTENCE_KEYS.SUPER_PROPERTIES, JSON.stringify(this.superProperties));
12677
13701
  if (this.config.debug) {
12678
- console.log(
12679
- "[BentoLabsSDK] Starting recording for session:",
12680
- this.sessionId
12681
- );
13702
+ console.log("[BentoLabsSDK] Unregistered super property:", propertyName);
12682
13703
  }
12683
- try {
13704
+ }
13705
+ // ============================================================================
13706
+ // Event Capture
13707
+ // ============================================================================
13708
+ /**
13709
+ * Capture an event
13710
+ */
13711
+ capture(eventName, properties) {
13712
+ if (!this.initialized) {
13713
+ console.warn("[BentoLabsSDK] Cannot capture event: SDK not initialized");
13714
+ return;
13715
+ }
13716
+ if (this.optedOut) {
13717
+ if (this.config.debug) {
13718
+ console.log("[BentoLabsSDK] Event not captured (opted out):", eventName);
13719
+ }
13720
+ return;
13721
+ }
13722
+ const event = {
13723
+ event: eventName,
13724
+ properties: __spreadProps(__spreadValues(__spreadValues({}, this.superProperties), properties), {
13725
+ $current_url: typeof window !== "undefined" ? window.location.href : "",
13726
+ $pathname: typeof window !== "undefined" ? window.location.pathname : ""
13727
+ }),
13728
+ $timestamp: Date.now(),
13729
+ $session_id: this.sessionId,
13730
+ $distinct_id: this.distinctId
13731
+ };
13732
+ this.addEvent({
13733
+ timestamp: event.$timestamp,
13734
+ type: `capture:${eventName}`,
13735
+ data: event,
13736
+ sessionId: this.sessionId
13737
+ });
13738
+ if (this.config.debug) {
13739
+ console.log("[BentoLabsSDK] Captured event:", eventName, properties);
13740
+ }
13741
+ }
13742
+ /**
13743
+ * Track a custom event (legacy method, delegates to capture)
13744
+ */
13745
+ trackCustomEvent(eventName, data) {
13746
+ if (!this.sessionId) {
13747
+ console.warn("[BentoLabsSDK] Cannot track event: SDK not initialized");
13748
+ return;
13749
+ }
13750
+ this.capture(`custom:${eventName}`, data);
13751
+ }
13752
+ // ============================================================================
13753
+ // Opt-in/Opt-out
13754
+ // ============================================================================
13755
+ /**
13756
+ * Opt out of tracking
13757
+ */
13758
+ opt_out_capturing() {
13759
+ var _a2;
13760
+ this.optedOut = true;
13761
+ (_a2 = this.persistence) == null ? void 0 : _a2.set(PERSISTENCE_KEYS.OPTED_OUT, "true");
13762
+ if (this.isRecording) {
13763
+ this.stopRecordingInternal();
13764
+ }
13765
+ this.stopNetworkCapture();
13766
+ this.stopStorageCapture();
13767
+ this.stopConsoleCapture();
13768
+ if (this.config.debug) {
13769
+ console.log("[BentoLabsSDK] Opted out of capturing");
13770
+ }
13771
+ }
13772
+ /**
13773
+ * Opt back into tracking
13774
+ */
13775
+ opt_in_capturing() {
13776
+ var _a2;
13777
+ this.optedOut = false;
13778
+ (_a2 = this.persistence) == null ? void 0 : _a2.remove(PERSISTENCE_KEYS.OPTED_OUT);
13779
+ if (this.initialized && this.config.enableRecording) {
13780
+ this.startRecording();
13781
+ }
13782
+ if (this.config.capture_network) {
13783
+ this.startNetworkCapture();
13784
+ }
13785
+ if (this.config.capture_storage) {
13786
+ this.startStorageCapture();
13787
+ }
13788
+ if (this.config.capture_console) {
13789
+ this.startConsoleCapture();
13790
+ }
13791
+ if (this.config.debug) {
13792
+ console.log("[BentoLabsSDK] Opted back into capturing");
13793
+ }
13794
+ }
13795
+ /**
13796
+ * Check if user has opted out
13797
+ */
13798
+ has_opted_out_capturing() {
13799
+ return this.optedOut;
13800
+ }
13801
+ /**
13802
+ * Check if user has opted in (not opted out)
13803
+ */
13804
+ has_opted_in_capturing() {
13805
+ return !this.optedOut;
13806
+ }
13807
+ // ============================================================================
13808
+ // Monitoring Control
13809
+ // ============================================================================
13810
+ /**
13811
+ * Start network request monitoring
13812
+ */
13813
+ startNetworkCapture() {
13814
+ if (this.networkMonitor) return;
13815
+ this.networkMonitor = new NetworkMonitor(this.config.networkOptions, (log) => {
13816
+ if (!this.optedOut) {
13817
+ this.capture("$network_request", log);
13818
+ }
13819
+ });
13820
+ this.networkMonitor.start();
13821
+ if (this.config.debug) {
13822
+ console.log("[BentoLabsSDK] Network capture started");
13823
+ }
13824
+ }
13825
+ /**
13826
+ * Stop network request monitoring
13827
+ */
13828
+ stopNetworkCapture() {
13829
+ if (this.networkMonitor) {
13830
+ this.networkMonitor.stop();
13831
+ this.networkMonitor = null;
13832
+ if (this.config.debug) {
13833
+ console.log("[BentoLabsSDK] Network capture stopped");
13834
+ }
13835
+ }
13836
+ }
13837
+ /**
13838
+ * Start storage monitoring
13839
+ */
13840
+ startStorageCapture() {
13841
+ if (this.storageMonitor) return;
13842
+ this.storageMonitor = new StorageMonitor(this.config.storageOptions, (log) => {
13843
+ if (!this.optedOut) {
13844
+ this.capture("$storage_event", log);
13845
+ }
13846
+ });
13847
+ this.storageMonitor.start();
13848
+ if (this.config.debug) {
13849
+ console.log("[BentoLabsSDK] Storage capture started");
13850
+ }
13851
+ }
13852
+ /**
13853
+ * Stop storage monitoring
13854
+ */
13855
+ stopStorageCapture() {
13856
+ if (this.storageMonitor) {
13857
+ this.storageMonitor.stop();
13858
+ this.storageMonitor = null;
13859
+ if (this.config.debug) {
13860
+ console.log("[BentoLabsSDK] Storage capture stopped");
13861
+ }
13862
+ }
13863
+ }
13864
+ /**
13865
+ * Start console monitoring
13866
+ */
13867
+ startConsoleCapture() {
13868
+ if (this.consoleMonitor) return;
13869
+ this.consoleMonitor = new ConsoleMonitor(this.config.consoleOptions, (log) => {
13870
+ if (!this.optedOut) {
13871
+ this.capture("$console_log", log);
13872
+ }
13873
+ });
13874
+ this.consoleMonitor.start();
13875
+ if (this.config.debug) {
13876
+ console.log("[BentoLabsSDK] Console capture started");
13877
+ }
13878
+ }
13879
+ /**
13880
+ * Stop console monitoring
13881
+ */
13882
+ stopConsoleCapture() {
13883
+ if (this.consoleMonitor) {
13884
+ this.consoleMonitor.stop();
13885
+ this.consoleMonitor = null;
13886
+ if (this.config.debug) {
13887
+ console.log("[BentoLabsSDK] Console capture stopped");
13888
+ }
13889
+ }
13890
+ }
13891
+ /**
13892
+ * Get network logs
13893
+ */
13894
+ getNetworkLogs() {
13895
+ var _a2;
13896
+ return ((_a2 = this.networkMonitor) == null ? void 0 : _a2.getLogs()) || [];
13897
+ }
13898
+ /**
13899
+ * Get storage logs
13900
+ */
13901
+ getStorageLogs() {
13902
+ var _a2;
13903
+ return ((_a2 = this.storageMonitor) == null ? void 0 : _a2.getLogs()) || [];
13904
+ }
13905
+ /**
13906
+ * Get console logs
13907
+ */
13908
+ getConsoleLogs() {
13909
+ var _a2;
13910
+ return ((_a2 = this.consoleMonitor) == null ? void 0 : _a2.getLogs()) || [];
13911
+ }
13912
+ // ============================================================================
13913
+ // Existing Public Methods (Preserved)
13914
+ // ============================================================================
13915
+ /**
13916
+ * Stop recording and clean up resources
13917
+ */
13918
+ stop() {
13919
+ if (this.config.debug) {
13920
+ console.log("[BentoLabsSDK] Stopping recording and batching");
13921
+ }
13922
+ this.stopRecordingInternal();
13923
+ this.stopNetworkCapture();
13924
+ this.stopStorageCapture();
13925
+ this.stopConsoleCapture();
13926
+ if (this.events.length > 0) {
13927
+ this.sendBatch();
13928
+ }
13929
+ if (this.config.debug) {
13930
+ console.log("[BentoLabsSDK] Stopped successfully");
13931
+ }
13932
+ }
13933
+ /**
13934
+ * Get current session ID
13935
+ */
13936
+ getSessionId() {
13937
+ return this.sessionId;
13938
+ }
13939
+ /**
13940
+ * Check if recording is active
13941
+ */
13942
+ isRecordingActive() {
13943
+ return this.isRecording;
13944
+ }
13945
+ /**
13946
+ * Get current configuration (without exposing sensitive data)
13947
+ */
13948
+ getConfig() {
13949
+ return {
13950
+ apiKey: this.config.apiKey ? this.config.apiKey.substring(0, 8) + "..." : "",
13951
+ endpoint: this.config.endpoint,
13952
+ debug: this.config.debug,
13953
+ batchSize: this.config.batchSize,
13954
+ batchInterval: this.config.batchInterval,
13955
+ enableRecording: this.config.enableRecording,
13956
+ maxRetries: this.config.maxRetries,
13957
+ baseRetryDelay: this.config.baseRetryDelay,
13958
+ enableCompression: this.config.enableCompression,
13959
+ persistence: this.config.persistence,
13960
+ respect_dnt: this.config.respect_dnt,
13961
+ autocapture: this.config.autocapture,
13962
+ capture_pageview: this.config.capture_pageview,
13963
+ capture_pageleave: this.config.capture_pageleave,
13964
+ capture_network: this.config.capture_network,
13965
+ networkOptions: this.config.networkOptions,
13966
+ capture_storage: this.config.capture_storage,
13967
+ storageOptions: this.config.storageOptions,
13968
+ capture_console: this.config.capture_console,
13969
+ consoleOptions: this.config.consoleOptions
13970
+ };
13971
+ }
13972
+ /**
13973
+ * Get current event queue length
13974
+ */
13975
+ getEventQueueLength() {
13976
+ return this.events.length;
13977
+ }
13978
+ /**
13979
+ * Manually trigger a batch send
13980
+ */
13981
+ async flushEvents() {
13982
+ await this.sendBatch();
13983
+ }
13984
+ // ============================================================================
13985
+ // Private Methods
13986
+ // ============================================================================
13987
+ generateSessionId() {
13988
+ const uuid = this.generateUUID();
13989
+ return `sess_${uuid}`;
13990
+ }
13991
+ generateAnonymousId() {
13992
+ const uuid = this.generateUUID();
13993
+ return `anon_${uuid}`;
13994
+ }
13995
+ generateUUID() {
13996
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
13997
+ const r = Math.random() * 16 | 0;
13998
+ const v = c === "x" ? r : r & 3 | 8;
13999
+ return v.toString(16);
14000
+ });
14001
+ }
14002
+ isDNTEnabled() {
14003
+ if (typeof navigator === "undefined") return false;
14004
+ return navigator.doNotTrack === "1" || navigator.doNotTrack === "yes";
14005
+ }
14006
+ loadPersistedState() {
14007
+ if (!this.persistence) return;
14008
+ const storedDistinctId = this.persistence.get(PERSISTENCE_KEYS.DISTINCT_ID);
14009
+ if (storedDistinctId) {
14010
+ this.distinctId = storedDistinctId;
14011
+ this.identity.distinctId = storedDistinctId;
14012
+ }
14013
+ const isIdentified = this.persistence.get(PERSISTENCE_KEYS.IS_IDENTIFIED);
14014
+ if (isIdentified === "true") {
14015
+ this.identity.isIdentified = true;
14016
+ }
14017
+ const storedSuperProps = this.persistence.get(PERSISTENCE_KEYS.SUPER_PROPERTIES);
14018
+ if (storedSuperProps) {
14019
+ try {
14020
+ this.superProperties = JSON.parse(storedSuperProps);
14021
+ } catch (e) {
14022
+ this.superProperties = {};
14023
+ }
14024
+ }
14025
+ const storedUserProps = this.persistence.get(PERSISTENCE_KEYS.USER_PROPERTIES);
14026
+ if (storedUserProps) {
14027
+ try {
14028
+ this.identity.properties = JSON.parse(storedUserProps);
14029
+ } catch (e) {
14030
+ this.identity.properties = {};
14031
+ }
14032
+ }
14033
+ const optedOut = this.persistence.get(PERSISTENCE_KEYS.OPTED_OUT);
14034
+ if (optedOut === "true") {
14035
+ this.optedOut = true;
14036
+ }
14037
+ }
14038
+ startRecording() {
14039
+ if (!this.config.enableRecording) {
14040
+ if (this.config.debug) {
14041
+ console.log("[BentoLabsSDK] Recording disabled in configuration");
14042
+ }
14043
+ return;
14044
+ }
14045
+ if (this.optedOut) {
14046
+ if (this.config.debug) {
14047
+ console.log("[BentoLabsSDK] Recording disabled (opted out)");
14048
+ }
14049
+ return;
14050
+ }
14051
+ if (this.config.debug) {
14052
+ console.log("[BentoLabsSDK] Starting recording for session:", this.sessionId);
14053
+ }
14054
+ try {
12684
14055
  if (this.config.debug) {
12685
14056
  console.log("[BentoLabsSDK] Calling rrweb record()...");
12686
14057
  }
@@ -12699,7 +14070,6 @@ var BentoLabsSDK = class {
12699
14070
  recordCanvas: true,
12700
14071
  collectFonts: true,
12701
14072
  plugins: [],
12702
- // Enable element context capture for rich metadata on interactions
12703
14073
  captureElementContext: true,
12704
14074
  elementContextOptions: {
12705
14075
  maxTextLength: 500,
@@ -12707,33 +14077,28 @@ var BentoLabsSDK = class {
12707
14077
  outerHTMLDepth: 2,
12708
14078
  includeInputValues: true
12709
14079
  },
12710
- // Mask password inputs for security
12711
14080
  maskInputOptions: {
12712
14081
  password: true
12713
14082
  },
12714
- // Sampling and throttling to reduce event volume
12715
14083
  sampling: {
12716
14084
  mousemove: true,
12717
- // Sample mouse movements
12718
14085
  mouseInteraction: { click: false, dblclick: false },
12719
- // Capture all clicks
12720
14086
  scroll: 150,
12721
- // Throttle scroll events to 150ms
12722
14087
  input: "last"
12723
- // Only record final input value
12724
14088
  },
12725
14089
  checkoutEveryNms: 5 * 60 * 1e3
12726
- // Take full snapshot every 5 minutes
12727
14090
  });
12728
14091
  if (typeof stopFn === "function") {
12729
- this.stopRecording = stopFn;
14092
+ this.stopRecordingFn = stopFn;
12730
14093
  this.isRecording = true;
12731
14094
  if (this.config.debug) {
12732
14095
  console.log("[BentoLabsSDK] Recording started successfully");
12733
14096
  }
12734
14097
  } else {
12735
- console.error("[BentoLabsSDK] rrweb record() did not return a stop function. Recording may not have started properly.");
12736
- this.stopRecording = null;
14098
+ console.error(
14099
+ "[BentoLabsSDK] rrweb record() did not return a stop function. Recording may not have started properly."
14100
+ );
14101
+ this.stopRecordingFn = null;
12737
14102
  this.isRecording = false;
12738
14103
  }
12739
14104
  } catch (error) {
@@ -12741,9 +14106,6 @@ var BentoLabsSDK = class {
12741
14106
  this.isRecording = false;
12742
14107
  }
12743
14108
  }
12744
- /**
12745
- * Start batching events for transmission
12746
- */
12747
14109
  startBatching() {
12748
14110
  if (this.config.debug) {
12749
14111
  console.log("[BentoLabsSDK] Starting event batching");
@@ -12755,31 +14117,22 @@ var BentoLabsSDK = class {
12755
14117
  this.sendBatch();
12756
14118
  }, this.config.batchInterval);
12757
14119
  if (this.config.debug) {
12758
- console.log(
12759
- `[BentoLabsSDK] Batch timer set for ${this.config.batchInterval}ms intervals`
12760
- );
14120
+ console.log(`[BentoLabsSDK] Batch timer set for ${this.config.batchInterval}ms intervals`);
12761
14121
  }
12762
14122
  }
12763
- /**
12764
- * Add an event to the events array
12765
- */
12766
14123
  addEvent(event) {
14124
+ if (this.optedOut) return;
12767
14125
  this.events.push(event);
12768
14126
  if (this.config.debug) {
12769
14127
  console.log("[BentoLabsSDK] Event added:", event.type);
12770
14128
  }
12771
14129
  if (this.events.length >= this.config.batchSize) {
12772
14130
  if (this.config.debug) {
12773
- console.log(
12774
- `[BentoLabsSDK] Batch size limit reached (${this.config.batchSize}), sending batch`
12775
- );
14131
+ console.log(`[BentoLabsSDK] Batch size limit reached (${this.config.batchSize}), sending batch`);
12776
14132
  }
12777
14133
  this.sendBatch();
12778
14134
  }
12779
14135
  }
12780
- /**
12781
- * Send batched events to the API with exponential backoff retry
12782
- */
12783
14136
  async sendBatch() {
12784
14137
  const now = Date.now();
12785
14138
  const readyEvents = this.events.filter((event) => {
@@ -12797,12 +14150,11 @@ var BentoLabsSDK = class {
12797
14150
  events: readyEvents,
12798
14151
  timestamp: now,
12799
14152
  userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "Unknown",
12800
- url: typeof window !== "undefined" ? window.location.href : "Unknown"
14153
+ url: typeof window !== "undefined" ? window.location.href : "Unknown",
14154
+ distinctId: this.distinctId
12801
14155
  };
12802
14156
  if (this.config.debug) {
12803
- console.log(
12804
- `[BentoLabsSDK] Sending batch with ${readyEvents.length} events`
12805
- );
14157
+ console.log(`[BentoLabsSDK] Sending batch with ${readyEvents.length} events`);
12806
14158
  }
12807
14159
  try {
12808
14160
  const response = await fetch(`${this.config.endpoint}/events/`, {
@@ -12830,43 +14182,30 @@ var BentoLabsSDK = class {
12830
14182
  nextRetryTime: now + delay
12831
14183
  });
12832
14184
  });
12833
- const eventsToRetry = retryableEvents.filter(
12834
- (event) => event.retryCount <= this.config.maxRetries
12835
- );
14185
+ const eventsToRetry = retryableEvents.filter((event) => event.retryCount <= this.config.maxRetries);
12836
14186
  const droppedEvents = retryableEvents.length - eventsToRetry.length;
12837
14187
  if (droppedEvents > 0 && this.config.debug) {
12838
- console.log(
12839
- `[BentoLabsSDK] Dropped ${droppedEvents} events after max retries`
12840
- );
14188
+ console.log(`[BentoLabsSDK] Dropped ${droppedEvents} events after max retries`);
12841
14189
  }
12842
14190
  this.events.unshift(...eventsToRetry);
12843
14191
  if (this.config.debug && eventsToRetry.length > 0) {
12844
14192
  const nextRetryIn = Math.min(...eventsToRetry.map((e) => e.nextRetryTime)) - now;
12845
- console.log(
12846
- `[BentoLabsSDK] ${eventsToRetry.length} events re-queued for retry in ${nextRetryIn}ms`
12847
- );
14193
+ console.log(`[BentoLabsSDK] ${eventsToRetry.length} events re-queued for retry in ${nextRetryIn}ms`);
12848
14194
  }
12849
14195
  this.scheduleRetry();
12850
14196
  }
12851
14197
  }
12852
- /**
12853
- * Schedule retry attempts for failed events
12854
- */
12855
14198
  scheduleRetry() {
12856
14199
  if (this.retryTimer) {
12857
14200
  clearTimeout(this.retryTimer);
12858
14201
  this.retryTimer = null;
12859
14202
  }
12860
14203
  const now = Date.now();
12861
- const retryableEvents = this.events.filter(
12862
- (event) => "nextRetryTime" in event
12863
- );
14204
+ const retryableEvents = this.events.filter((event) => "nextRetryTime" in event);
12864
14205
  if (retryableEvents.length === 0) {
12865
14206
  return;
12866
14207
  }
12867
- const nextRetryTime = Math.min(
12868
- ...retryableEvents.map((e) => e.nextRetryTime)
12869
- );
14208
+ const nextRetryTime = Math.min(...retryableEvents.map((e) => e.nextRetryTime));
12870
14209
  const delay = Math.max(0, nextRetryTime - now);
12871
14210
  if (this.config.debug) {
12872
14211
  console.log(`[BentoLabsSDK] Scheduling retry in ${delay}ms`);
@@ -12875,13 +14214,10 @@ var BentoLabsSDK = class {
12875
14214
  this.sendBatch();
12876
14215
  }, delay);
12877
14216
  }
12878
- /**
12879
- * Internal method to stop recording without logging (used during re-init)
12880
- */
12881
14217
  stopRecordingInternal() {
12882
- if (this.stopRecording) {
12883
- this.stopRecording();
12884
- this.stopRecording = null;
14218
+ if (this.stopRecordingFn) {
14219
+ this.stopRecordingFn();
14220
+ this.stopRecordingFn = null;
12885
14221
  }
12886
14222
  if (this.batchTimer) {
12887
14223
  clearInterval(this.batchTimer);
@@ -12891,86 +14227,115 @@ var BentoLabsSDK = class {
12891
14227
  clearTimeout(this.retryTimer);
12892
14228
  this.retryTimer = null;
12893
14229
  }
14230
+ if (this.autocaptureCleanup) {
14231
+ this.autocaptureCleanup();
14232
+ this.autocaptureCleanup = null;
14233
+ }
12894
14234
  this.isRecording = false;
12895
14235
  }
12896
- /**
12897
- * Stop recording and clean up resources
12898
- */
12899
- stop() {
12900
- if (this.config.debug) {
12901
- console.log("[BentoLabsSDK] Stopping recording and batching");
12902
- }
12903
- this.stopRecordingInternal();
12904
- if (this.events.length > 0) {
12905
- this.sendBatch();
12906
- }
14236
+ setupAutocapture() {
14237
+ if (typeof document === "undefined") return;
14238
+ const handleClick = (e) => {
14239
+ var _a2;
14240
+ const target = e.target;
14241
+ if (!target) return;
14242
+ const tagName = target.tagName.toLowerCase();
14243
+ const allowedTags = ["a", "button", "input", "select", "textarea"];
14244
+ const isAllowed = allowedTags.includes(tagName) || target.getAttribute("role") === "button" || target.hasAttribute("data-bento-capture");
14245
+ if (!isAllowed) return;
14246
+ let selector = "";
14247
+ try {
14248
+ selector = generateSelectorFromElement(target);
14249
+ } catch (e2) {
14250
+ selector = "";
14251
+ }
14252
+ this.capture("$autocapture", {
14253
+ $event_type: "click",
14254
+ $element_tag: tagName,
14255
+ $element_text: ((_a2 = target.textContent) == null ? void 0 : _a2.slice(0, 100)) || "",
14256
+ $element_classes: target.className || "",
14257
+ $element_id: target.id || "",
14258
+ $bento_selector: selector
14259
+ });
14260
+ };
14261
+ const handleChange = (e) => {
14262
+ const target = e.target;
14263
+ if (!target) return;
14264
+ const tagName = target.tagName.toLowerCase();
14265
+ if (!["input", "select", "textarea"].includes(tagName)) return;
14266
+ if (target.type === "password") return;
14267
+ let selector = "";
14268
+ try {
14269
+ selector = generateSelectorFromElement(target);
14270
+ } catch (e2) {
14271
+ selector = "";
14272
+ }
14273
+ this.capture("$autocapture", {
14274
+ $event_type: "change",
14275
+ $element_tag: tagName,
14276
+ $element_type: target.type || "",
14277
+ $element_name: target.name || "",
14278
+ $element_id: target.id || "",
14279
+ $bento_selector: selector
14280
+ // Don't capture actual value for privacy
14281
+ });
14282
+ };
14283
+ const handleSubmit = (e) => {
14284
+ const target = e.target;
14285
+ if (!target || target.tagName.toLowerCase() !== "form") return;
14286
+ let selector = "";
14287
+ try {
14288
+ selector = generateSelectorFromElement(target);
14289
+ } catch (e2) {
14290
+ selector = "";
14291
+ }
14292
+ this.capture("$autocapture", {
14293
+ $event_type: "submit",
14294
+ $element_tag: "form",
14295
+ $form_name: target.name || "",
14296
+ $form_id: target.id || "",
14297
+ $form_action: target.action || "",
14298
+ $bento_selector: selector
14299
+ });
14300
+ };
14301
+ document.addEventListener("click", handleClick, true);
14302
+ document.addEventListener("change", handleChange, true);
14303
+ document.addEventListener("submit", handleSubmit, true);
14304
+ this.autocaptureCleanup = () => {
14305
+ document.removeEventListener("click", handleClick, true);
14306
+ document.removeEventListener("change", handleChange, true);
14307
+ document.removeEventListener("submit", handleSubmit, true);
14308
+ };
12907
14309
  if (this.config.debug) {
12908
- console.log("[BentoLabsSDK] Stopped successfully");
14310
+ console.log("[BentoLabsSDK] Autocapture setup complete");
12909
14311
  }
12910
14312
  }
12911
- /**
12912
- * Get current session ID
12913
- */
12914
- getSessionId() {
12915
- return this.sessionId;
12916
- }
12917
- /**
12918
- * Check if recording is active
12919
- */
12920
- isRecordingActive() {
12921
- return this.isRecording;
12922
- }
12923
- /**
12924
- * Get current configuration (without exposing sensitive data)
12925
- */
12926
- getConfig() {
12927
- return {
12928
- apiKey: this.config.apiKey ? this.config.apiKey.substring(0, 8) + "..." : "",
12929
- endpoint: this.config.endpoint,
12930
- debug: this.config.debug,
12931
- batchSize: this.config.batchSize,
12932
- batchInterval: this.config.batchInterval,
12933
- enableRecording: this.config.enableRecording,
12934
- maxRetries: this.config.maxRetries,
12935
- baseRetryDelay: this.config.baseRetryDelay
12936
- };
12937
- }
12938
- /**
12939
- * Get current event queue length
12940
- */
12941
- getEventQueueLength() {
12942
- return this.events.length;
12943
- }
12944
- /**
12945
- * Manually trigger a batch send
12946
- */
12947
- async flushEvents() {
12948
- await this.sendBatch();
12949
- }
12950
- /**
12951
- * Track a custom event with custom data
12952
- * @param eventName - Name of the custom event
12953
- * @param data - Custom event data (will be JSON stringified)
12954
- */
12955
- trackCustomEvent(eventName, data) {
12956
- if (!this.sessionId) {
12957
- console.warn("[BentoLabsSDK] Cannot track event: SDK not initialized");
12958
- return;
12959
- }
12960
- this.addEvent({
12961
- timestamp: Date.now(),
12962
- type: `custom:${eventName}`,
12963
- data: data || {},
12964
- sessionId: this.sessionId
14313
+ handlePageLeave() {
14314
+ this.capture("$pageleave", {
14315
+ $current_url: typeof window !== "undefined" ? window.location.href : "",
14316
+ $pathname: typeof window !== "undefined" ? window.location.pathname : ""
12965
14317
  });
12966
- if (this.config.debug) {
12967
- console.log(`[BentoLabsSDK] Custom event tracked: ${eventName}`, data);
14318
+ if (this.events.length > 0 && typeof navigator !== "undefined" && navigator.sendBeacon) {
14319
+ const payload = {
14320
+ sessionId: this.sessionId,
14321
+ events: this.events,
14322
+ timestamp: Date.now(),
14323
+ userAgent: navigator.userAgent,
14324
+ url: window.location.href,
14325
+ distinctId: this.distinctId
14326
+ };
14327
+ navigator.sendBeacon(
14328
+ `${this.config.endpoint}/events/`,
14329
+ new Blob([JSON.stringify(payload)], { type: "application/json" })
14330
+ );
14331
+ this.events = [];
12968
14332
  }
12969
14333
  }
12970
14334
  };
12971
14335
  var sdk = new BentoLabsSDK();
12972
14336
  if (typeof window !== "undefined") {
12973
- window.BentoLabsSDK = __spreadProps(__spreadValues({}, sdk), {
14337
+ window.BentoLabsSDK = {
14338
+ // Core methods
12974
14339
  init: sdk.init.bind(sdk),
12975
14340
  stop: sdk.stop.bind(sdk),
12976
14341
  getSessionId: sdk.getSessionId.bind(sdk),
@@ -12979,8 +14344,34 @@ if (typeof window !== "undefined") {
12979
14344
  getEventQueueLength: sdk.getEventQueueLength.bind(sdk),
12980
14345
  flushEvents: sdk.flushEvents.bind(sdk),
12981
14346
  trackCustomEvent: sdk.trackCustomEvent.bind(sdk),
12982
- generateSelector
12983
- });
14347
+ // Selector generation
14348
+ generateSelector,
14349
+ generateSelectorFromElement,
14350
+ generateSelectorFromHTML,
14351
+ // PostHog-like methods
14352
+ capture: sdk.capture.bind(sdk),
14353
+ identify: sdk.identify.bind(sdk),
14354
+ reset: sdk.reset.bind(sdk),
14355
+ getDistinctId: sdk.getDistinctId.bind(sdk),
14356
+ register: sdk.register.bind(sdk),
14357
+ registerOnce: sdk.registerOnce.bind(sdk),
14358
+ unregister: sdk.unregister.bind(sdk),
14359
+ opt_out_capturing: sdk.opt_out_capturing.bind(sdk),
14360
+ opt_in_capturing: sdk.opt_in_capturing.bind(sdk),
14361
+ has_opted_out_capturing: sdk.has_opted_out_capturing.bind(sdk),
14362
+ has_opted_in_capturing: sdk.has_opted_in_capturing.bind(sdk),
14363
+ people: sdk.people,
14364
+ // Monitoring control
14365
+ startNetworkCapture: sdk.startNetworkCapture.bind(sdk),
14366
+ stopNetworkCapture: sdk.stopNetworkCapture.bind(sdk),
14367
+ startStorageCapture: sdk.startStorageCapture.bind(sdk),
14368
+ stopStorageCapture: sdk.stopStorageCapture.bind(sdk),
14369
+ startConsoleCapture: sdk.startConsoleCapture.bind(sdk),
14370
+ stopConsoleCapture: sdk.stopConsoleCapture.bind(sdk),
14371
+ getNetworkLogs: sdk.getNetworkLogs.bind(sdk),
14372
+ getStorageLogs: sdk.getStorageLogs.bind(sdk),
14373
+ getConsoleLogs: sdk.getConsoleLogs.bind(sdk)
14374
+ };
12984
14375
  }
12985
14376
  var index_default = sdk;
12986
14377
  /*! Bundled license information: