@bentolabs/sdk 1.2.2 → 2.0.2

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
 
@@ -9149,10 +9151,145 @@ function quickHash(str) {
9149
9151
  }
9150
9152
  return (hash >>> 0).toString(16);
9151
9153
  }
9152
- function buildBentoSelector(element) {
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) {
9153
9263
  const parts = [];
9154
9264
  const tag = element.tagName.toLowerCase();
9155
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
+ }
9156
9293
  const testId = element.getAttribute("data-testid");
9157
9294
  if (testId) {
9158
9295
  parts.push(`testid=${escapeValue(truncate(testId, 50))}`);
@@ -9227,7 +9364,13 @@ function buildBentoSelector(element) {
9227
9364
  parts.push(`text=${escapeValue(truncate(text, 50))}`);
9228
9365
  }
9229
9366
  }
9230
- if (parts.length === 1) {
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) {
9231
9374
  const childSig = getChildSignature(element);
9232
9375
  if (childSig) {
9233
9376
  parts.push(`children=${childSig}`);
@@ -9238,7 +9381,11 @@ function buildBentoSelector(element) {
9238
9381
  }
9239
9382
  return parts.join("|");
9240
9383
  }
9241
- 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) {
9242
9389
  const parser2 = new DOMParser();
9243
9390
  const doc = parser2.parseFromString(outerHTML, "text/html");
9244
9391
  const element = doc.body.firstElementChild;
@@ -9247,6 +9394,9 @@ function generateSelector(outerHTML) {
9247
9394
  }
9248
9395
  return buildBentoSelector(element);
9249
9396
  }
9397
+ function generateSelector(outerHTML) {
9398
+ return generateSelectorFromHTML(outerHTML);
9399
+ }
9250
9400
  var DEFAULT_OPTIONS = {
9251
9401
  maxTextLength: 500,
9252
9402
  includeOuterHTML: true,
@@ -9276,6 +9426,7 @@ function isInputElement(element) {
9276
9426
  return tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT";
9277
9427
  }
9278
9428
  function extractElementContext(element, options = {}) {
9429
+ var _a2;
9279
9430
  const opts = __spreadValues(__spreadValues({}, DEFAULT_OPTIONS), options.elementContextOptions);
9280
9431
  const {
9281
9432
  isClickEvent = false,
@@ -9339,7 +9490,8 @@ function extractElementContext(element, options = {}) {
9339
9490
  }
9340
9491
  if (isClickEvent) {
9341
9492
  try {
9342
- 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);
9343
9495
  } catch (e) {
9344
9496
  }
9345
9497
  }
@@ -10175,6 +10327,7 @@ function initMouseInteractionObserver({
10175
10327
  let currentPointerType = null;
10176
10328
  const getHandler = (eventKey) => {
10177
10329
  return (event) => {
10330
+ const pathname = typeof window !== "undefined" ? window.location.pathname : void 0;
10178
10331
  const target = getEventTarget(event);
10179
10332
  if (isBlocked(target, blockClass, blockSelector, true)) {
10180
10333
  return;
@@ -10225,7 +10378,9 @@ function initMouseInteractionObserver({
10225
10378
  elementContextOptions,
10226
10379
  isClickEvent,
10227
10380
  maskInputOptions,
10228
- maskInputFn
10381
+ maskInputFn,
10382
+ pathname
10383
+ // Pass pathname captured at event time
10229
10384
  });
10230
10385
  }
10231
10386
  callbackWrapper(mouseInteractionCb)(__spreadValues(__spreadValues({
@@ -12620,6 +12775,641 @@ var { freezePage } = record;
12620
12775
  var { takeFullSnapshot } = record;
12621
12776
 
12622
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
+ };
12623
13413
  var BentoLabsSDK = class {
12624
13414
  constructor() {
12625
13415
  this.config = {
@@ -12628,43 +13418,115 @@ var BentoLabsSDK = class {
12628
13418
  debug: false,
12629
13419
  batchSize: 50,
12630
13420
  batchInterval: 1e4,
12631
- // 10 seconds
12632
13421
  enableRecording: true,
12633
13422
  maxRetries: 3,
12634
- 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
+ }
12635
13454
  };
12636
13455
  this.sessionId = "";
12637
13456
  this.events = [];
12638
13457
  this.isRecording = false;
12639
13458
  this.batchTimer = null;
12640
13459
  this.retryTimer = null;
12641
- this.stopRecording = null;
13460
+ this.isSending = false;
13461
+ this.stopRecordingFn = null;
13462
+ this.distinctId = "";
13463
+ this.identity = {
13464
+ distinctId: null,
13465
+ properties: {},
13466
+ isIdentified: false
13467
+ };
13468
+ this.superProperties = {};
13469
+ this.persistence = null;
13470
+ this.initialized = false;
13471
+ this.optedOut = false;
13472
+ this.networkMonitor = null;
13473
+ this.storageMonitor = null;
13474
+ this.consoleMonitor = null;
13475
+ this.people = new PeopleAPI(this);
13476
+ this.autocaptureCleanup = null;
12642
13477
  }
12643
13478
  /**
12644
13479
  * Initialize the BentoLabs SDK
12645
- * @param apiKey - Your API key for authentication
12646
- * @param options - Optional configuration options
12647
13480
  */
12648
13481
  init(apiKey, options) {
12649
- var _a2;
12650
- if (this.isRecording || this.stopRecording) {
13482
+ var _a2, _b, _c;
13483
+ if (this.isRecording || this.stopRecordingFn) {
12651
13484
  this.stopRecordingInternal();
12652
13485
  }
12653
13486
  const defaultOptions = {
12654
13487
  endpoint: "https://api.bentolabs.ai",
12655
13488
  debug: false,
12656
13489
  batchSize: 100,
12657
- // Increased from 50 to reduce API calls
12658
- batchInterval: 3e4,
12659
- // 30 seconds (increased from 10s)
13490
+ batchInterval: 5e3,
13491
+ // 5 seconds (PostHog-style)
12660
13492
  enableRecording: true,
12661
13493
  maxRetries: 3,
12662
- baseRetryDelay: 1e3
12663
- // 1 second
13494
+ baseRetryDelay: 1e3,
13495
+ enableCompression: true,
13496
+ // PostHog-like defaults
13497
+ persistence: "localStorage+cookie",
13498
+ respect_dnt: false,
13499
+ autocapture: true,
13500
+ capture_pageview: true,
13501
+ capture_pageleave: true,
13502
+ // Monitoring defaults
13503
+ capture_network: true,
13504
+ networkOptions: {
13505
+ skipUrls: ["api.bentolabs.ai"],
13506
+ captureHeaders: false,
13507
+ captureBody: false,
13508
+ maxBodySize: 5e3,
13509
+ sampleRate: 1
13510
+ },
13511
+ capture_storage: true,
13512
+ storageOptions: {
13513
+ localStorage: true,
13514
+ sessionStorage: true,
13515
+ cookies: true,
13516
+ sensitiveKeys: ["password", "token", "secret", "api_key", "apikey", "auth", "credential"]
13517
+ },
13518
+ capture_console: true,
13519
+ consoleOptions: {
13520
+ levels: ["log", "warn", "error", "info"],
13521
+ maxLogs: 500,
13522
+ maxPayloadSize: 5e3,
13523
+ captureStackTrace: true
13524
+ }
12664
13525
  };
12665
13526
  const mergedOptions = __spreadProps(__spreadValues(__spreadValues({}, defaultOptions), options), {
12666
- // Ensure debug is always a boolean
12667
- debug: (_a2 = options == null ? void 0 : options.debug) != null ? _a2 : defaultOptions.debug
13527
+ networkOptions: __spreadValues(__spreadValues({}, defaultOptions.networkOptions), options == null ? void 0 : options.networkOptions),
13528
+ storageOptions: __spreadValues(__spreadValues({}, defaultOptions.storageOptions), options == null ? void 0 : options.storageOptions),
13529
+ consoleOptions: __spreadValues(__spreadValues({}, defaultOptions.consoleOptions), options == null ? void 0 : options.consoleOptions)
12668
13530
  });
12669
13531
  this.config = {
12670
13532
  apiKey,
@@ -12674,57 +13536,523 @@ var BentoLabsSDK = class {
12674
13536
  batchInterval: mergedOptions.batchInterval,
12675
13537
  enableRecording: mergedOptions.enableRecording,
12676
13538
  maxRetries: mergedOptions.maxRetries,
12677
- baseRetryDelay: mergedOptions.baseRetryDelay
13539
+ baseRetryDelay: mergedOptions.baseRetryDelay,
13540
+ enableCompression: (_a2 = mergedOptions.enableCompression) != null ? _a2 : true,
13541
+ persistence: mergedOptions.persistence,
13542
+ respect_dnt: mergedOptions.respect_dnt,
13543
+ autocapture: mergedOptions.autocapture,
13544
+ capture_pageview: mergedOptions.capture_pageview,
13545
+ capture_pageleave: mergedOptions.capture_pageleave,
13546
+ capture_network: mergedOptions.capture_network,
13547
+ networkOptions: mergedOptions.networkOptions,
13548
+ capture_storage: mergedOptions.capture_storage,
13549
+ storageOptions: mergedOptions.storageOptions,
13550
+ capture_console: mergedOptions.capture_console,
13551
+ consoleOptions: mergedOptions.consoleOptions,
13552
+ bootstrap: mergedOptions.bootstrap
12678
13553
  };
13554
+ if (this.config.respect_dnt && this.isDNTEnabled()) {
13555
+ if (this.config.debug) {
13556
+ console.log("[BentoLabsSDK] Do Not Track is enabled, opting out");
13557
+ }
13558
+ this.optedOut = true;
13559
+ }
13560
+ this.persistence = new Persistence(this.config.persistence);
13561
+ this.loadPersistedState();
13562
+ if ((_b = this.config.bootstrap) == null ? void 0 : _b.distinctID) {
13563
+ this.distinctId = this.config.bootstrap.distinctID;
13564
+ this.identity.distinctId = this.config.bootstrap.distinctID;
13565
+ this.identity.isIdentified = (_c = this.config.bootstrap.isIdentifiedID) != null ? _c : false;
13566
+ this.persistence.set(PERSISTENCE_KEYS.DISTINCT_ID, this.distinctId);
13567
+ if (this.identity.isIdentified) {
13568
+ this.persistence.set(PERSISTENCE_KEYS.IS_IDENTIFIED, "true");
13569
+ }
13570
+ }
13571
+ if (!this.distinctId) {
13572
+ this.distinctId = this.generateAnonymousId();
13573
+ this.persistence.set(PERSISTENCE_KEYS.DISTINCT_ID, this.distinctId);
13574
+ }
12679
13575
  this.sessionId = this.generateSessionId();
13576
+ this.initialized = true;
12680
13577
  if (this.config.debug) {
12681
13578
  console.log("[BentoLabsSDK] Initialized with config:", {
12682
13579
  apiKey: apiKey.substring(0, 8) + "...",
12683
13580
  endpoint: this.config.endpoint,
12684
13581
  debug: this.config.debug,
12685
- sessionId: this.sessionId
13582
+ sessionId: this.sessionId,
13583
+ distinctId: this.distinctId
12686
13584
  });
12687
13585
  }
12688
13586
  this.startRecording();
12689
13587
  this.startBatching();
13588
+ if (this.config.autocapture) {
13589
+ this.setupAutocapture();
13590
+ }
13591
+ if (this.config.capture_pageview) {
13592
+ this.capture("$pageview", {
13593
+ $current_url: typeof window !== "undefined" ? window.location.href : "",
13594
+ $pathname: typeof window !== "undefined" ? window.location.pathname : "",
13595
+ $referrer: typeof document !== "undefined" ? document.referrer : ""
13596
+ });
13597
+ }
13598
+ if (this.config.capture_pageleave && typeof window !== "undefined") {
13599
+ window.addEventListener("beforeunload", this.handlePageLeave.bind(this));
13600
+ }
13601
+ if (this.config.capture_network) {
13602
+ this.startNetworkCapture();
13603
+ }
13604
+ if (this.config.capture_storage) {
13605
+ this.startStorageCapture();
13606
+ }
13607
+ if (this.config.capture_console) {
13608
+ this.startConsoleCapture();
13609
+ }
12690
13610
  }
13611
+ // ============================================================================
13612
+ // Identity Methods
13613
+ // ============================================================================
12691
13614
  /**
12692
- * Generate a unique session ID with 'sess_' prefix
13615
+ * Identify a user with a distinct ID and optional properties
12693
13616
  */
12694
- generateSessionId() {
12695
- const uuid = this.generateUUID();
12696
- return `sess_${uuid}`;
13617
+ identify(distinctId, properties) {
13618
+ var _a2, _b, _c;
13619
+ if (!this.initialized) {
13620
+ console.warn("[BentoLabsSDK] Cannot identify: SDK not initialized");
13621
+ return;
13622
+ }
13623
+ const previousId = this.distinctId;
13624
+ this.distinctId = distinctId;
13625
+ this.identity.distinctId = distinctId;
13626
+ this.identity.isIdentified = true;
13627
+ if (properties) {
13628
+ this.identity.properties = __spreadValues(__spreadValues({}, this.identity.properties), properties);
13629
+ }
13630
+ (_a2 = this.persistence) == null ? void 0 : _a2.set(PERSISTENCE_KEYS.DISTINCT_ID, distinctId);
13631
+ (_b = this.persistence) == null ? void 0 : _b.set(PERSISTENCE_KEYS.IS_IDENTIFIED, "true");
13632
+ if (properties) {
13633
+ (_c = this.persistence) == null ? void 0 : _c.set(PERSISTENCE_KEYS.USER_PROPERTIES, JSON.stringify(this.identity.properties));
13634
+ }
13635
+ this.capture("$identify", __spreadValues({
13636
+ $anon_distinct_id: previousId
13637
+ }, properties));
13638
+ if (this.config.debug) {
13639
+ console.log("[BentoLabsSDK] Identified user:", distinctId);
13640
+ }
12697
13641
  }
12698
13642
  /**
12699
- * Generate a simple UUID v4
13643
+ * Reset the user to an anonymous state
12700
13644
  */
12701
- generateUUID() {
12702
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
12703
- /[xy]/g,
12704
- function(c) {
12705
- const r = Math.random() * 16 | 0;
12706
- const v = c === "x" ? r : r & 3 | 8;
12707
- return v.toString(16);
13645
+ reset() {
13646
+ var _a2, _b;
13647
+ if (!this.initialized) return;
13648
+ this.distinctId = this.generateAnonymousId();
13649
+ this.identity = {
13650
+ distinctId: null,
13651
+ properties: {},
13652
+ isIdentified: false
13653
+ };
13654
+ this.superProperties = {};
13655
+ (_a2 = this.persistence) == null ? void 0 : _a2.clear();
13656
+ (_b = this.persistence) == null ? void 0 : _b.set(PERSISTENCE_KEYS.DISTINCT_ID, this.distinctId);
13657
+ if (this.config.debug) {
13658
+ console.log("[BentoLabsSDK] Reset to anonymous:", this.distinctId);
13659
+ }
13660
+ }
13661
+ /**
13662
+ * Get the current distinct ID
13663
+ */
13664
+ getDistinctId() {
13665
+ return this.distinctId;
13666
+ }
13667
+ // ============================================================================
13668
+ // Super Properties Methods
13669
+ // ============================================================================
13670
+ /**
13671
+ * Register super properties to be sent with every event
13672
+ */
13673
+ register(properties) {
13674
+ var _a2;
13675
+ this.superProperties = __spreadValues(__spreadValues({}, this.superProperties), properties);
13676
+ (_a2 = this.persistence) == null ? void 0 : _a2.set(PERSISTENCE_KEYS.SUPER_PROPERTIES, JSON.stringify(this.superProperties));
13677
+ if (this.config.debug) {
13678
+ console.log("[BentoLabsSDK] Registered super properties:", properties);
13679
+ }
13680
+ }
13681
+ /**
13682
+ * Register super properties only if they haven't been set
13683
+ */
13684
+ registerOnce(properties) {
13685
+ const newProps = {};
13686
+ for (const [key, value] of Object.entries(properties)) {
13687
+ if (!(key in this.superProperties)) {
13688
+ newProps[key] = value;
12708
13689
  }
12709
- );
13690
+ }
13691
+ if (Object.keys(newProps).length > 0) {
13692
+ this.register(newProps);
13693
+ }
12710
13694
  }
12711
13695
  /**
12712
- * Start recording user interactions
13696
+ * Remove a super property
12713
13697
  */
12714
- startRecording() {
12715
- if (!this.config.enableRecording) {
13698
+ unregister(propertyName) {
13699
+ var _a2;
13700
+ delete this.superProperties[propertyName];
13701
+ (_a2 = this.persistence) == null ? void 0 : _a2.set(PERSISTENCE_KEYS.SUPER_PROPERTIES, JSON.stringify(this.superProperties));
13702
+ if (this.config.debug) {
13703
+ console.log("[BentoLabsSDK] Unregistered super property:", propertyName);
13704
+ }
13705
+ }
13706
+ // ============================================================================
13707
+ // Event Capture
13708
+ // ============================================================================
13709
+ /**
13710
+ * Capture an event
13711
+ */
13712
+ capture(eventName, properties) {
13713
+ if (!this.initialized) {
13714
+ console.warn("[BentoLabsSDK] Cannot capture event: SDK not initialized");
13715
+ return;
13716
+ }
13717
+ if (this.optedOut) {
12716
13718
  if (this.config.debug) {
12717
- console.log("[BentoLabsSDK] Recording disabled in configuration");
13719
+ console.log("[BentoLabsSDK] Event not captured (opted out):", eventName);
12718
13720
  }
12719
13721
  return;
12720
13722
  }
13723
+ const event = {
13724
+ event: eventName,
13725
+ properties: __spreadProps(__spreadValues(__spreadValues({}, this.superProperties), properties), {
13726
+ $current_url: typeof window !== "undefined" ? window.location.href : "",
13727
+ $pathname: typeof window !== "undefined" ? window.location.pathname : ""
13728
+ }),
13729
+ $timestamp: Date.now(),
13730
+ $session_id: this.sessionId,
13731
+ $distinct_id: this.distinctId
13732
+ };
13733
+ this.addEvent({
13734
+ timestamp: event.$timestamp,
13735
+ type: `capture:${eventName}`,
13736
+ data: event,
13737
+ sessionId: this.sessionId
13738
+ });
12721
13739
  if (this.config.debug) {
12722
- console.log(
12723
- "[BentoLabsSDK] Starting recording for session:",
12724
- this.sessionId
12725
- );
13740
+ console.log("[BentoLabsSDK] Captured event:", eventName, properties);
12726
13741
  }
12727
- try {
13742
+ }
13743
+ /**
13744
+ * Track a custom event (legacy method, delegates to capture)
13745
+ */
13746
+ trackCustomEvent(eventName, data) {
13747
+ if (!this.sessionId) {
13748
+ console.warn("[BentoLabsSDK] Cannot track event: SDK not initialized");
13749
+ return;
13750
+ }
13751
+ this.capture(`custom:${eventName}`, data);
13752
+ }
13753
+ // ============================================================================
13754
+ // Opt-in/Opt-out
13755
+ // ============================================================================
13756
+ /**
13757
+ * Opt out of tracking
13758
+ */
13759
+ opt_out_capturing() {
13760
+ var _a2;
13761
+ this.optedOut = true;
13762
+ (_a2 = this.persistence) == null ? void 0 : _a2.set(PERSISTENCE_KEYS.OPTED_OUT, "true");
13763
+ if (this.isRecording) {
13764
+ this.stopRecordingInternal();
13765
+ }
13766
+ this.stopNetworkCapture();
13767
+ this.stopStorageCapture();
13768
+ this.stopConsoleCapture();
13769
+ if (this.config.debug) {
13770
+ console.log("[BentoLabsSDK] Opted out of capturing");
13771
+ }
13772
+ }
13773
+ /**
13774
+ * Opt back into tracking
13775
+ */
13776
+ opt_in_capturing() {
13777
+ var _a2;
13778
+ this.optedOut = false;
13779
+ (_a2 = this.persistence) == null ? void 0 : _a2.remove(PERSISTENCE_KEYS.OPTED_OUT);
13780
+ if (this.initialized && this.config.enableRecording) {
13781
+ this.startRecording();
13782
+ }
13783
+ if (this.config.capture_network) {
13784
+ this.startNetworkCapture();
13785
+ }
13786
+ if (this.config.capture_storage) {
13787
+ this.startStorageCapture();
13788
+ }
13789
+ if (this.config.capture_console) {
13790
+ this.startConsoleCapture();
13791
+ }
13792
+ if (this.config.debug) {
13793
+ console.log("[BentoLabsSDK] Opted back into capturing");
13794
+ }
13795
+ }
13796
+ /**
13797
+ * Check if user has opted out
13798
+ */
13799
+ has_opted_out_capturing() {
13800
+ return this.optedOut;
13801
+ }
13802
+ /**
13803
+ * Check if user has opted in (not opted out)
13804
+ */
13805
+ has_opted_in_capturing() {
13806
+ return !this.optedOut;
13807
+ }
13808
+ // ============================================================================
13809
+ // Monitoring Control
13810
+ // ============================================================================
13811
+ /**
13812
+ * Start network request monitoring
13813
+ */
13814
+ startNetworkCapture() {
13815
+ if (this.networkMonitor) return;
13816
+ this.networkMonitor = new NetworkMonitor(this.config.networkOptions, (log) => {
13817
+ if (!this.optedOut) {
13818
+ this.capture("$network_request", log);
13819
+ }
13820
+ });
13821
+ this.networkMonitor.start();
13822
+ if (this.config.debug) {
13823
+ console.log("[BentoLabsSDK] Network capture started");
13824
+ }
13825
+ }
13826
+ /**
13827
+ * Stop network request monitoring
13828
+ */
13829
+ stopNetworkCapture() {
13830
+ if (this.networkMonitor) {
13831
+ this.networkMonitor.stop();
13832
+ this.networkMonitor = null;
13833
+ if (this.config.debug) {
13834
+ console.log("[BentoLabsSDK] Network capture stopped");
13835
+ }
13836
+ }
13837
+ }
13838
+ /**
13839
+ * Start storage monitoring
13840
+ */
13841
+ startStorageCapture() {
13842
+ if (this.storageMonitor) return;
13843
+ this.storageMonitor = new StorageMonitor(this.config.storageOptions, (log) => {
13844
+ if (!this.optedOut) {
13845
+ this.capture("$storage_event", log);
13846
+ }
13847
+ });
13848
+ this.storageMonitor.start();
13849
+ if (this.config.debug) {
13850
+ console.log("[BentoLabsSDK] Storage capture started");
13851
+ }
13852
+ }
13853
+ /**
13854
+ * Stop storage monitoring
13855
+ */
13856
+ stopStorageCapture() {
13857
+ if (this.storageMonitor) {
13858
+ this.storageMonitor.stop();
13859
+ this.storageMonitor = null;
13860
+ if (this.config.debug) {
13861
+ console.log("[BentoLabsSDK] Storage capture stopped");
13862
+ }
13863
+ }
13864
+ }
13865
+ /**
13866
+ * Start console monitoring
13867
+ */
13868
+ startConsoleCapture() {
13869
+ if (this.consoleMonitor) return;
13870
+ this.consoleMonitor = new ConsoleMonitor(this.config.consoleOptions, (log) => {
13871
+ if (!this.optedOut) {
13872
+ this.capture("$console_log", log);
13873
+ }
13874
+ });
13875
+ this.consoleMonitor.start();
13876
+ if (this.config.debug) {
13877
+ console.log("[BentoLabsSDK] Console capture started");
13878
+ }
13879
+ }
13880
+ /**
13881
+ * Stop console monitoring
13882
+ */
13883
+ stopConsoleCapture() {
13884
+ if (this.consoleMonitor) {
13885
+ this.consoleMonitor.stop();
13886
+ this.consoleMonitor = null;
13887
+ if (this.config.debug) {
13888
+ console.log("[BentoLabsSDK] Console capture stopped");
13889
+ }
13890
+ }
13891
+ }
13892
+ /**
13893
+ * Get network logs
13894
+ */
13895
+ getNetworkLogs() {
13896
+ var _a2;
13897
+ return ((_a2 = this.networkMonitor) == null ? void 0 : _a2.getLogs()) || [];
13898
+ }
13899
+ /**
13900
+ * Get storage logs
13901
+ */
13902
+ getStorageLogs() {
13903
+ var _a2;
13904
+ return ((_a2 = this.storageMonitor) == null ? void 0 : _a2.getLogs()) || [];
13905
+ }
13906
+ /**
13907
+ * Get console logs
13908
+ */
13909
+ getConsoleLogs() {
13910
+ var _a2;
13911
+ return ((_a2 = this.consoleMonitor) == null ? void 0 : _a2.getLogs()) || [];
13912
+ }
13913
+ // ============================================================================
13914
+ // Existing Public Methods (Preserved)
13915
+ // ============================================================================
13916
+ /**
13917
+ * Stop recording and clean up resources
13918
+ */
13919
+ stop() {
13920
+ if (this.config.debug) {
13921
+ console.log("[BentoLabsSDK] Stopping recording and batching");
13922
+ }
13923
+ this.stopRecordingInternal();
13924
+ this.stopNetworkCapture();
13925
+ this.stopStorageCapture();
13926
+ this.stopConsoleCapture();
13927
+ if (this.events.length > 0) {
13928
+ this.sendBatch();
13929
+ }
13930
+ if (this.config.debug) {
13931
+ console.log("[BentoLabsSDK] Stopped successfully");
13932
+ }
13933
+ }
13934
+ /**
13935
+ * Get current session ID
13936
+ */
13937
+ getSessionId() {
13938
+ return this.sessionId;
13939
+ }
13940
+ /**
13941
+ * Check if recording is active
13942
+ */
13943
+ isRecordingActive() {
13944
+ return this.isRecording;
13945
+ }
13946
+ /**
13947
+ * Get current configuration (without exposing sensitive data)
13948
+ */
13949
+ getConfig() {
13950
+ return {
13951
+ apiKey: this.config.apiKey ? this.config.apiKey.substring(0, 8) + "..." : "",
13952
+ endpoint: this.config.endpoint,
13953
+ debug: this.config.debug,
13954
+ batchSize: this.config.batchSize,
13955
+ batchInterval: this.config.batchInterval,
13956
+ enableRecording: this.config.enableRecording,
13957
+ maxRetries: this.config.maxRetries,
13958
+ baseRetryDelay: this.config.baseRetryDelay,
13959
+ enableCompression: this.config.enableCompression,
13960
+ persistence: this.config.persistence,
13961
+ respect_dnt: this.config.respect_dnt,
13962
+ autocapture: this.config.autocapture,
13963
+ capture_pageview: this.config.capture_pageview,
13964
+ capture_pageleave: this.config.capture_pageleave,
13965
+ capture_network: this.config.capture_network,
13966
+ networkOptions: this.config.networkOptions,
13967
+ capture_storage: this.config.capture_storage,
13968
+ storageOptions: this.config.storageOptions,
13969
+ capture_console: this.config.capture_console,
13970
+ consoleOptions: this.config.consoleOptions
13971
+ };
13972
+ }
13973
+ /**
13974
+ * Get current event queue length
13975
+ */
13976
+ getEventQueueLength() {
13977
+ return this.events.length;
13978
+ }
13979
+ /**
13980
+ * Manually trigger a batch send
13981
+ */
13982
+ async flushEvents() {
13983
+ await this.sendBatch();
13984
+ }
13985
+ // ============================================================================
13986
+ // Private Methods
13987
+ // ============================================================================
13988
+ generateSessionId() {
13989
+ const uuid = this.generateUUID();
13990
+ return `sess_${uuid}`;
13991
+ }
13992
+ generateAnonymousId() {
13993
+ const uuid = this.generateUUID();
13994
+ return `anon_${uuid}`;
13995
+ }
13996
+ generateUUID() {
13997
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
13998
+ const r = Math.random() * 16 | 0;
13999
+ const v = c === "x" ? r : r & 3 | 8;
14000
+ return v.toString(16);
14001
+ });
14002
+ }
14003
+ isDNTEnabled() {
14004
+ if (typeof navigator === "undefined") return false;
14005
+ return navigator.doNotTrack === "1" || navigator.doNotTrack === "yes";
14006
+ }
14007
+ loadPersistedState() {
14008
+ if (!this.persistence) return;
14009
+ const storedDistinctId = this.persistence.get(PERSISTENCE_KEYS.DISTINCT_ID);
14010
+ if (storedDistinctId) {
14011
+ this.distinctId = storedDistinctId;
14012
+ this.identity.distinctId = storedDistinctId;
14013
+ }
14014
+ const isIdentified = this.persistence.get(PERSISTENCE_KEYS.IS_IDENTIFIED);
14015
+ if (isIdentified === "true") {
14016
+ this.identity.isIdentified = true;
14017
+ }
14018
+ const storedSuperProps = this.persistence.get(PERSISTENCE_KEYS.SUPER_PROPERTIES);
14019
+ if (storedSuperProps) {
14020
+ try {
14021
+ this.superProperties = JSON.parse(storedSuperProps);
14022
+ } catch (e) {
14023
+ this.superProperties = {};
14024
+ }
14025
+ }
14026
+ const storedUserProps = this.persistence.get(PERSISTENCE_KEYS.USER_PROPERTIES);
14027
+ if (storedUserProps) {
14028
+ try {
14029
+ this.identity.properties = JSON.parse(storedUserProps);
14030
+ } catch (e) {
14031
+ this.identity.properties = {};
14032
+ }
14033
+ }
14034
+ const optedOut = this.persistence.get(PERSISTENCE_KEYS.OPTED_OUT);
14035
+ if (optedOut === "true") {
14036
+ this.optedOut = true;
14037
+ }
14038
+ }
14039
+ startRecording() {
14040
+ if (!this.config.enableRecording) {
14041
+ if (this.config.debug) {
14042
+ console.log("[BentoLabsSDK] Recording disabled in configuration");
14043
+ }
14044
+ return;
14045
+ }
14046
+ if (this.optedOut) {
14047
+ if (this.config.debug) {
14048
+ console.log("[BentoLabsSDK] Recording disabled (opted out)");
14049
+ }
14050
+ return;
14051
+ }
14052
+ if (this.config.debug) {
14053
+ console.log("[BentoLabsSDK] Starting recording for session:", this.sessionId);
14054
+ }
14055
+ try {
12728
14056
  if (this.config.debug) {
12729
14057
  console.log("[BentoLabsSDK] Calling rrweb record()...");
12730
14058
  }
@@ -12743,7 +14071,6 @@ var BentoLabsSDK = class {
12743
14071
  recordCanvas: true,
12744
14072
  collectFonts: true,
12745
14073
  plugins: [],
12746
- // Enable element context capture for rich metadata on interactions
12747
14074
  captureElementContext: true,
12748
14075
  elementContextOptions: {
12749
14076
  maxTextLength: 500,
@@ -12751,33 +14078,28 @@ var BentoLabsSDK = class {
12751
14078
  outerHTMLDepth: 2,
12752
14079
  includeInputValues: true
12753
14080
  },
12754
- // Mask password inputs for security
12755
14081
  maskInputOptions: {
12756
14082
  password: true
12757
14083
  },
12758
- // Sampling and throttling to reduce event volume
12759
14084
  sampling: {
12760
14085
  mousemove: true,
12761
- // Sample mouse movements
12762
14086
  mouseInteraction: { click: false, dblclick: false },
12763
- // Capture all clicks
12764
14087
  scroll: 150,
12765
- // Throttle scroll events to 150ms
12766
14088
  input: "last"
12767
- // Only record final input value
12768
14089
  },
12769
14090
  checkoutEveryNms: 5 * 60 * 1e3
12770
- // Take full snapshot every 5 minutes
12771
14091
  });
12772
14092
  if (typeof stopFn === "function") {
12773
- this.stopRecording = stopFn;
14093
+ this.stopRecordingFn = stopFn;
12774
14094
  this.isRecording = true;
12775
14095
  if (this.config.debug) {
12776
14096
  console.log("[BentoLabsSDK] Recording started successfully");
12777
14097
  }
12778
14098
  } else {
12779
- console.error("[BentoLabsSDK] rrweb record() did not return a stop function. Recording may not have started properly.");
12780
- this.stopRecording = null;
14099
+ console.error(
14100
+ "[BentoLabsSDK] rrweb record() did not return a stop function. Recording may not have started properly."
14101
+ );
14102
+ this.stopRecordingFn = null;
12781
14103
  this.isRecording = false;
12782
14104
  }
12783
14105
  } catch (error) {
@@ -12785,9 +14107,6 @@ var BentoLabsSDK = class {
12785
14107
  this.isRecording = false;
12786
14108
  }
12787
14109
  }
12788
- /**
12789
- * Start batching events for transmission
12790
- */
12791
14110
  startBatching() {
12792
14111
  if (this.config.debug) {
12793
14112
  console.log("[BentoLabsSDK] Starting event batching");
@@ -12799,32 +14118,29 @@ var BentoLabsSDK = class {
12799
14118
  this.sendBatch();
12800
14119
  }, this.config.batchInterval);
12801
14120
  if (this.config.debug) {
12802
- console.log(
12803
- `[BentoLabsSDK] Batch timer set for ${this.config.batchInterval}ms intervals`
12804
- );
14121
+ console.log(`[BentoLabsSDK] Batch timer set for ${this.config.batchInterval}ms intervals`);
12805
14122
  }
12806
14123
  }
12807
- /**
12808
- * Add an event to the events array
12809
- */
12810
14124
  addEvent(event) {
14125
+ if (this.optedOut) return;
12811
14126
  this.events.push(event);
12812
14127
  if (this.config.debug) {
12813
14128
  console.log("[BentoLabsSDK] Event added:", event.type);
12814
14129
  }
12815
- if (this.events.length >= this.config.batchSize) {
14130
+ if (this.events.length >= this.config.batchSize && !this.isSending) {
12816
14131
  if (this.config.debug) {
12817
- console.log(
12818
- `[BentoLabsSDK] Batch size limit reached (${this.config.batchSize}), sending batch`
12819
- );
14132
+ console.log(`[BentoLabsSDK] Batch size limit reached (${this.config.batchSize}), sending batch`);
12820
14133
  }
12821
14134
  this.sendBatch();
12822
14135
  }
12823
14136
  }
12824
- /**
12825
- * Send batched events to the API with exponential backoff retry
12826
- */
12827
14137
  async sendBatch() {
14138
+ if (this.isSending) {
14139
+ if (this.config.debug) {
14140
+ console.log("[BentoLabsSDK] Already sending, skipping batch");
14141
+ }
14142
+ return;
14143
+ }
12828
14144
  const now = Date.now();
12829
14145
  const readyEvents = this.events.filter((event) => {
12830
14146
  if ("nextRetryTime" in event) {
@@ -12835,28 +14151,67 @@ var BentoLabsSDK = class {
12835
14151
  if (readyEvents.length === 0) {
12836
14152
  return;
12837
14153
  }
14154
+ this.isSending = true;
12838
14155
  this.events = this.events.filter((event) => !readyEvents.includes(event));
12839
14156
  const payload = {
12840
14157
  sessionId: this.sessionId,
12841
14158
  events: readyEvents,
12842
14159
  timestamp: now,
12843
14160
  userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "Unknown",
12844
- url: typeof window !== "undefined" ? window.location.href : "Unknown"
14161
+ url: typeof window !== "undefined" ? window.location.href : "Unknown",
14162
+ distinctId: this.distinctId
12845
14163
  };
12846
14164
  if (this.config.debug) {
12847
- console.log(
12848
- `[BentoLabsSDK] Sending batch with ${readyEvents.length} events`
12849
- );
14165
+ console.log(`[BentoLabsSDK] Sending batch with ${readyEvents.length} events`);
12850
14166
  }
12851
14167
  try {
14168
+ const jsonPayload = JSON.stringify(payload);
14169
+ let body = jsonPayload;
14170
+ const headers = {
14171
+ "Content-Type": "application/json",
14172
+ Authorization: `Bearer ${this.config.apiKey}`,
14173
+ "X-Session-ID": this.sessionId
14174
+ };
14175
+ if (this.config.enableCompression && typeof CompressionStream !== "undefined") {
14176
+ try {
14177
+ const encoder = new TextEncoder();
14178
+ const data = encoder.encode(jsonPayload);
14179
+ const cs = new CompressionStream("gzip");
14180
+ const writer = cs.writable.getWriter();
14181
+ writer.write(data);
14182
+ writer.close();
14183
+ const compressedChunks = [];
14184
+ const reader = cs.readable.getReader();
14185
+ let result2 = await reader.read();
14186
+ while (!result2.done) {
14187
+ compressedChunks.push(result2.value);
14188
+ result2 = await reader.read();
14189
+ }
14190
+ const totalLength = compressedChunks.reduce((acc, chunk) => acc + chunk.length, 0);
14191
+ const compressedData = new Uint8Array(totalLength);
14192
+ let offset = 0;
14193
+ for (const chunk of compressedChunks) {
14194
+ compressedData.set(chunk, offset);
14195
+ offset += chunk.length;
14196
+ }
14197
+ body = new Blob([compressedData], { type: "application/json" });
14198
+ headers["Content-Encoding"] = "gzip";
14199
+ if (this.config.debug) {
14200
+ const ratio = ((1 - compressedData.length / data.length) * 100).toFixed(1);
14201
+ console.log(
14202
+ `[BentoLabsSDK] Compressed payload: ${data.length} -> ${compressedData.length} bytes (${ratio}% reduction)`
14203
+ );
14204
+ }
14205
+ } catch (compressionError) {
14206
+ if (this.config.debug) {
14207
+ console.warn("[BentoLabsSDK] Compression failed, sending uncompressed:", compressionError);
14208
+ }
14209
+ }
14210
+ }
12852
14211
  const response = await fetch(`${this.config.endpoint}/events/`, {
12853
14212
  method: "POST",
12854
- headers: {
12855
- "Content-Type": "application/json",
12856
- Authorization: `Bearer ${this.config.apiKey}`,
12857
- "X-Session-ID": this.sessionId
12858
- },
12859
- body: JSON.stringify(payload)
14213
+ headers,
14214
+ body
12860
14215
  });
12861
14216
  if (!response.ok) {
12862
14217
  throw new Error(`HTTP error! status: ${response.status}`);
@@ -12865,7 +14220,9 @@ var BentoLabsSDK = class {
12865
14220
  console.log("[BentoLabsSDK] Batch sent successfully");
12866
14221
  }
12867
14222
  } catch (error) {
12868
- console.error("[BentoLabsSDK] Failed to send batch:", error);
14223
+ if (this.config.debug) {
14224
+ console.error("[BentoLabsSDK] Failed to send batch:", error);
14225
+ }
12869
14226
  const retryableEvents = readyEvents.map((event) => {
12870
14227
  const retryCount = "retryCount" in event ? event.retryCount + 1 : 1;
12871
14228
  const delay = this.config.baseRetryDelay * Math.pow(2, retryCount - 1);
@@ -12874,43 +14231,33 @@ var BentoLabsSDK = class {
12874
14231
  nextRetryTime: now + delay
12875
14232
  });
12876
14233
  });
12877
- const eventsToRetry = retryableEvents.filter(
12878
- (event) => event.retryCount <= this.config.maxRetries
12879
- );
14234
+ const eventsToRetry = retryableEvents.filter((event) => event.retryCount <= this.config.maxRetries);
12880
14235
  const droppedEvents = retryableEvents.length - eventsToRetry.length;
12881
14236
  if (droppedEvents > 0 && this.config.debug) {
12882
- console.log(
12883
- `[BentoLabsSDK] Dropped ${droppedEvents} events after max retries`
12884
- );
14237
+ console.log(`[BentoLabsSDK] Dropped ${droppedEvents} events after max retries`);
12885
14238
  }
12886
- this.events.unshift(...eventsToRetry);
12887
- if (this.config.debug && eventsToRetry.length > 0) {
12888
- const nextRetryIn = Math.min(...eventsToRetry.map((e) => e.nextRetryTime)) - now;
12889
- console.log(
12890
- `[BentoLabsSDK] ${eventsToRetry.length} events re-queued for retry in ${nextRetryIn}ms`
12891
- );
14239
+ if (eventsToRetry.length > 0) {
14240
+ this.events.unshift(...eventsToRetry);
14241
+ if (this.config.debug) {
14242
+ const nextRetryIn = Math.min(...eventsToRetry.map((e) => e.nextRetryTime)) - now;
14243
+ console.log(`[BentoLabsSDK] ${eventsToRetry.length} events re-queued for retry in ${nextRetryIn}ms`);
14244
+ }
12892
14245
  }
12893
- this.scheduleRetry();
14246
+ } finally {
14247
+ this.isSending = false;
12894
14248
  }
12895
14249
  }
12896
- /**
12897
- * Schedule retry attempts for failed events
12898
- */
12899
14250
  scheduleRetry() {
12900
14251
  if (this.retryTimer) {
12901
14252
  clearTimeout(this.retryTimer);
12902
14253
  this.retryTimer = null;
12903
14254
  }
12904
14255
  const now = Date.now();
12905
- const retryableEvents = this.events.filter(
12906
- (event) => "nextRetryTime" in event
12907
- );
14256
+ const retryableEvents = this.events.filter((event) => "nextRetryTime" in event);
12908
14257
  if (retryableEvents.length === 0) {
12909
14258
  return;
12910
14259
  }
12911
- const nextRetryTime = Math.min(
12912
- ...retryableEvents.map((e) => e.nextRetryTime)
12913
- );
14260
+ const nextRetryTime = Math.min(...retryableEvents.map((e) => e.nextRetryTime));
12914
14261
  const delay = Math.max(0, nextRetryTime - now);
12915
14262
  if (this.config.debug) {
12916
14263
  console.log(`[BentoLabsSDK] Scheduling retry in ${delay}ms`);
@@ -12919,13 +14266,10 @@ var BentoLabsSDK = class {
12919
14266
  this.sendBatch();
12920
14267
  }, delay);
12921
14268
  }
12922
- /**
12923
- * Internal method to stop recording without logging (used during re-init)
12924
- */
12925
14269
  stopRecordingInternal() {
12926
- if (this.stopRecording) {
12927
- this.stopRecording();
12928
- this.stopRecording = null;
14270
+ if (this.stopRecordingFn) {
14271
+ this.stopRecordingFn();
14272
+ this.stopRecordingFn = null;
12929
14273
  }
12930
14274
  if (this.batchTimer) {
12931
14275
  clearInterval(this.batchTimer);
@@ -12935,86 +14279,115 @@ var BentoLabsSDK = class {
12935
14279
  clearTimeout(this.retryTimer);
12936
14280
  this.retryTimer = null;
12937
14281
  }
14282
+ if (this.autocaptureCleanup) {
14283
+ this.autocaptureCleanup();
14284
+ this.autocaptureCleanup = null;
14285
+ }
12938
14286
  this.isRecording = false;
12939
14287
  }
12940
- /**
12941
- * Stop recording and clean up resources
12942
- */
12943
- stop() {
12944
- if (this.config.debug) {
12945
- console.log("[BentoLabsSDK] Stopping recording and batching");
12946
- }
12947
- this.stopRecordingInternal();
12948
- if (this.events.length > 0) {
12949
- this.sendBatch();
12950
- }
14288
+ setupAutocapture() {
14289
+ if (typeof document === "undefined") return;
14290
+ const handleClick = (e) => {
14291
+ var _a2;
14292
+ const target = e.target;
14293
+ if (!target) return;
14294
+ const tagName = target.tagName.toLowerCase();
14295
+ const allowedTags = ["a", "button", "input", "select", "textarea"];
14296
+ const isAllowed = allowedTags.includes(tagName) || target.getAttribute("role") === "button" || target.hasAttribute("data-bento-capture");
14297
+ if (!isAllowed) return;
14298
+ let selector = "";
14299
+ try {
14300
+ selector = generateSelectorFromElement(target);
14301
+ } catch (e2) {
14302
+ selector = "";
14303
+ }
14304
+ this.capture("$autocapture", {
14305
+ $event_type: "click",
14306
+ $element_tag: tagName,
14307
+ $element_text: ((_a2 = target.textContent) == null ? void 0 : _a2.slice(0, 100)) || "",
14308
+ $element_classes: target.className || "",
14309
+ $element_id: target.id || "",
14310
+ $bento_selector: selector
14311
+ });
14312
+ };
14313
+ const handleChange = (e) => {
14314
+ const target = e.target;
14315
+ if (!target) return;
14316
+ const tagName = target.tagName.toLowerCase();
14317
+ if (!["input", "select", "textarea"].includes(tagName)) return;
14318
+ if (target.type === "password") return;
14319
+ let selector = "";
14320
+ try {
14321
+ selector = generateSelectorFromElement(target);
14322
+ } catch (e2) {
14323
+ selector = "";
14324
+ }
14325
+ this.capture("$autocapture", {
14326
+ $event_type: "change",
14327
+ $element_tag: tagName,
14328
+ $element_type: target.type || "",
14329
+ $element_name: target.name || "",
14330
+ $element_id: target.id || "",
14331
+ $bento_selector: selector
14332
+ // Don't capture actual value for privacy
14333
+ });
14334
+ };
14335
+ const handleSubmit = (e) => {
14336
+ const target = e.target;
14337
+ if (!target || target.tagName.toLowerCase() !== "form") return;
14338
+ let selector = "";
14339
+ try {
14340
+ selector = generateSelectorFromElement(target);
14341
+ } catch (e2) {
14342
+ selector = "";
14343
+ }
14344
+ this.capture("$autocapture", {
14345
+ $event_type: "submit",
14346
+ $element_tag: "form",
14347
+ $form_name: target.name || "",
14348
+ $form_id: target.id || "",
14349
+ $form_action: target.action || "",
14350
+ $bento_selector: selector
14351
+ });
14352
+ };
14353
+ document.addEventListener("click", handleClick, true);
14354
+ document.addEventListener("change", handleChange, true);
14355
+ document.addEventListener("submit", handleSubmit, true);
14356
+ this.autocaptureCleanup = () => {
14357
+ document.removeEventListener("click", handleClick, true);
14358
+ document.removeEventListener("change", handleChange, true);
14359
+ document.removeEventListener("submit", handleSubmit, true);
14360
+ };
12951
14361
  if (this.config.debug) {
12952
- console.log("[BentoLabsSDK] Stopped successfully");
14362
+ console.log("[BentoLabsSDK] Autocapture setup complete");
12953
14363
  }
12954
14364
  }
12955
- /**
12956
- * Get current session ID
12957
- */
12958
- getSessionId() {
12959
- return this.sessionId;
12960
- }
12961
- /**
12962
- * Check if recording is active
12963
- */
12964
- isRecordingActive() {
12965
- return this.isRecording;
12966
- }
12967
- /**
12968
- * Get current configuration (without exposing sensitive data)
12969
- */
12970
- getConfig() {
12971
- return {
12972
- apiKey: this.config.apiKey ? this.config.apiKey.substring(0, 8) + "..." : "",
12973
- endpoint: this.config.endpoint,
12974
- debug: this.config.debug,
12975
- batchSize: this.config.batchSize,
12976
- batchInterval: this.config.batchInterval,
12977
- enableRecording: this.config.enableRecording,
12978
- maxRetries: this.config.maxRetries,
12979
- baseRetryDelay: this.config.baseRetryDelay
12980
- };
12981
- }
12982
- /**
12983
- * Get current event queue length
12984
- */
12985
- getEventQueueLength() {
12986
- return this.events.length;
12987
- }
12988
- /**
12989
- * Manually trigger a batch send
12990
- */
12991
- async flushEvents() {
12992
- await this.sendBatch();
12993
- }
12994
- /**
12995
- * Track a custom event with custom data
12996
- * @param eventName - Name of the custom event
12997
- * @param data - Custom event data (will be JSON stringified)
12998
- */
12999
- trackCustomEvent(eventName, data) {
13000
- if (!this.sessionId) {
13001
- console.warn("[BentoLabsSDK] Cannot track event: SDK not initialized");
13002
- return;
13003
- }
13004
- this.addEvent({
13005
- timestamp: Date.now(),
13006
- type: `custom:${eventName}`,
13007
- data: data || {},
13008
- sessionId: this.sessionId
14365
+ handlePageLeave() {
14366
+ this.capture("$pageleave", {
14367
+ $current_url: typeof window !== "undefined" ? window.location.href : "",
14368
+ $pathname: typeof window !== "undefined" ? window.location.pathname : ""
13009
14369
  });
13010
- if (this.config.debug) {
13011
- console.log(`[BentoLabsSDK] Custom event tracked: ${eventName}`, data);
14370
+ if (this.events.length > 0 && typeof navigator !== "undefined" && navigator.sendBeacon) {
14371
+ const payload = {
14372
+ sessionId: this.sessionId,
14373
+ events: this.events,
14374
+ timestamp: Date.now(),
14375
+ userAgent: navigator.userAgent,
14376
+ url: window.location.href,
14377
+ distinctId: this.distinctId
14378
+ };
14379
+ navigator.sendBeacon(
14380
+ `${this.config.endpoint}/events/`,
14381
+ new Blob([JSON.stringify(payload)], { type: "application/json" })
14382
+ );
14383
+ this.events = [];
13012
14384
  }
13013
14385
  }
13014
14386
  };
13015
14387
  var sdk = new BentoLabsSDK();
13016
14388
  if (typeof window !== "undefined") {
13017
- window.BentoLabsSDK = __spreadProps(__spreadValues({}, sdk), {
14389
+ window.BentoLabsSDK = {
14390
+ // Core methods
13018
14391
  init: sdk.init.bind(sdk),
13019
14392
  stop: sdk.stop.bind(sdk),
13020
14393
  getSessionId: sdk.getSessionId.bind(sdk),
@@ -13023,8 +14396,34 @@ if (typeof window !== "undefined") {
13023
14396
  getEventQueueLength: sdk.getEventQueueLength.bind(sdk),
13024
14397
  flushEvents: sdk.flushEvents.bind(sdk),
13025
14398
  trackCustomEvent: sdk.trackCustomEvent.bind(sdk),
13026
- generateSelector
13027
- });
14399
+ // Selector generation
14400
+ generateSelector,
14401
+ generateSelectorFromElement,
14402
+ generateSelectorFromHTML,
14403
+ // PostHog-like methods
14404
+ capture: sdk.capture.bind(sdk),
14405
+ identify: sdk.identify.bind(sdk),
14406
+ reset: sdk.reset.bind(sdk),
14407
+ getDistinctId: sdk.getDistinctId.bind(sdk),
14408
+ register: sdk.register.bind(sdk),
14409
+ registerOnce: sdk.registerOnce.bind(sdk),
14410
+ unregister: sdk.unregister.bind(sdk),
14411
+ opt_out_capturing: sdk.opt_out_capturing.bind(sdk),
14412
+ opt_in_capturing: sdk.opt_in_capturing.bind(sdk),
14413
+ has_opted_out_capturing: sdk.has_opted_out_capturing.bind(sdk),
14414
+ has_opted_in_capturing: sdk.has_opted_in_capturing.bind(sdk),
14415
+ people: sdk.people,
14416
+ // Monitoring control
14417
+ startNetworkCapture: sdk.startNetworkCapture.bind(sdk),
14418
+ stopNetworkCapture: sdk.stopNetworkCapture.bind(sdk),
14419
+ startStorageCapture: sdk.startStorageCapture.bind(sdk),
14420
+ stopStorageCapture: sdk.stopStorageCapture.bind(sdk),
14421
+ startConsoleCapture: sdk.startConsoleCapture.bind(sdk),
14422
+ stopConsoleCapture: sdk.stopConsoleCapture.bind(sdk),
14423
+ getNetworkLogs: sdk.getNetworkLogs.bind(sdk),
14424
+ getStorageLogs: sdk.getStorageLogs.bind(sdk),
14425
+ getConsoleLogs: sdk.getConsoleLogs.bind(sdk)
14426
+ };
13028
14427
  }
13029
14428
  var index_default = sdk;
13030
14429
  /*! Bundled license information: