@tamsensedev/dataclient 0.1.10 → 0.2.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.d.cts CHANGED
@@ -94,6 +94,7 @@ interface Config {
94
94
  sessionIdKey: string;
95
95
  deviceIdKey: string;
96
96
  apiKey: string;
97
+ scoped: string;
97
98
  }
98
99
  interface Tracker {
99
100
  start: () => void;
@@ -123,11 +124,25 @@ declare class DataClient {
123
124
  private deviceId;
124
125
  private idleTimer;
125
126
  private userId;
127
+ private rootEl;
128
+ private scopeObserver;
129
+ private scopeCheckScheduled;
130
+ private activityHandler;
131
+ private activityTarget;
126
132
  constructor(options?: Partial<Config>);
127
133
  setUser(userId: string): void;
128
134
  excludeSession(reason?: string): void;
135
+ private normalizeScoped;
136
+ private startScopedMode;
137
+ private findRoot;
138
+ private scheduleScopeCheck;
139
+ private handleScopeChange;
140
+ private onRootAppeared;
141
+ private onRootDisappeared;
129
142
  private onActivity;
130
143
  private resetIdleTimer;
144
+ private attachActivityListeners;
145
+ private detachActivityListeners;
131
146
  private startSession;
132
147
  private stopSession;
133
148
  }
package/dist/index.d.ts CHANGED
@@ -94,6 +94,7 @@ interface Config {
94
94
  sessionIdKey: string;
95
95
  deviceIdKey: string;
96
96
  apiKey: string;
97
+ scoped: string;
97
98
  }
98
99
  interface Tracker {
99
100
  start: () => void;
@@ -123,11 +124,25 @@ declare class DataClient {
123
124
  private deviceId;
124
125
  private idleTimer;
125
126
  private userId;
127
+ private rootEl;
128
+ private scopeObserver;
129
+ private scopeCheckScheduled;
130
+ private activityHandler;
131
+ private activityTarget;
126
132
  constructor(options?: Partial<Config>);
127
133
  setUser(userId: string): void;
128
134
  excludeSession(reason?: string): void;
135
+ private normalizeScoped;
136
+ private startScopedMode;
137
+ private findRoot;
138
+ private scheduleScopeCheck;
139
+ private handleScopeChange;
140
+ private onRootAppeared;
141
+ private onRootDisappeared;
129
142
  private onActivity;
130
143
  private resetIdleTimer;
144
+ private attachActivityListeners;
145
+ private detachActivityListeners;
131
146
  private startSession;
132
147
  private stopSession;
133
148
  }
@@ -12654,6 +12654,8 @@ var dataclient = (() => {
12654
12654
  sender;
12655
12655
  stopFn = null;
12656
12656
  start() {
12657
+ const scoped = this.config.scoped;
12658
+ const blockSelector = scoped ? `:not([${scoped}]):not([${scoped}] *):not(:has([${scoped}]))` : void 0;
12657
12659
  this.stopFn = record({
12658
12660
  emit: (event) => {
12659
12661
  const rrwebEvent = {
@@ -12668,6 +12670,7 @@ var dataclient = (() => {
12668
12670
  maskTextSelector: MASK_SELECTOR,
12669
12671
  maskInputOptions: { password: true },
12670
12672
  maskTextFn: (text) => "*".repeat(text.length),
12673
+ blockSelector,
12671
12674
  sampling: {
12672
12675
  mousemove: false,
12673
12676
  mouseInteraction: true,
@@ -12691,26 +12694,33 @@ var dataclient = (() => {
12691
12694
 
12692
12695
  // src/trackers/action.ts
12693
12696
  var ActionTracker = class {
12694
- constructor(config, sender) {
12697
+ constructor(config, sender, root2) {
12695
12698
  this.config = config;
12696
12699
  this.sender = sender;
12700
+ this.root = root2;
12697
12701
  }
12698
12702
  config;
12699
12703
  sender;
12704
+ root;
12700
12705
  inputTimers = /* @__PURE__ */ new WeakMap();
12701
12706
  pendingInputs = /* @__PURE__ */ new Set();
12702
12707
  handleClick = (e) => this.onClick(e);
12703
12708
  handleInput = (e) => this.onInput(e);
12704
12709
  handleChange = (e) => this.onChange(e);
12710
+ get listenerTarget() {
12711
+ return this.config.scoped ? this.root : document;
12712
+ }
12705
12713
  start() {
12706
- document.addEventListener("click", this.handleClick, true);
12707
- document.addEventListener("input", this.handleInput, true);
12708
- document.addEventListener("change", this.handleChange, true);
12714
+ const target = this.listenerTarget;
12715
+ target.addEventListener("click", this.handleClick, true);
12716
+ target.addEventListener("input", this.handleInput, true);
12717
+ target.addEventListener("change", this.handleChange, true);
12709
12718
  }
12710
12719
  stop() {
12711
- document.removeEventListener("click", this.handleClick, true);
12712
- document.removeEventListener("input", this.handleInput, true);
12713
- document.removeEventListener("change", this.handleChange, true);
12720
+ const target = this.listenerTarget;
12721
+ target.removeEventListener("click", this.handleClick, true);
12722
+ target.removeEventListener("input", this.handleInput, true);
12723
+ target.removeEventListener("change", this.handleChange, true);
12714
12724
  }
12715
12725
  beforeUnload() {
12716
12726
  for (const el of this.pendingInputs) {
@@ -12864,7 +12874,7 @@ var dataclient = (() => {
12864
12874
  }
12865
12875
  findMeaningfulElement(el) {
12866
12876
  let current = el;
12867
- while (current && current !== document.body) {
12877
+ while (current && current !== this.root && current !== document.body) {
12868
12878
  if (INTERACTIVE_TAGS.has(current.tagName?.toLowerCase())) {
12869
12879
  return current;
12870
12880
  }
@@ -12880,13 +12890,15 @@ var dataclient = (() => {
12880
12890
 
12881
12891
  // src/trackers/mutation.ts
12882
12892
  var MutationTracker = class {
12883
- constructor(config, sender, onMutation) {
12893
+ constructor(config, sender, root2, onMutation) {
12884
12894
  this.config = config;
12885
12895
  this.sender = sender;
12896
+ this.root = root2;
12886
12897
  this.onMutation = onMutation;
12887
12898
  }
12888
12899
  config;
12889
12900
  sender;
12901
+ root;
12890
12902
  onMutation;
12891
12903
  observer = null;
12892
12904
  debounceTimer = null;
@@ -12896,7 +12908,7 @@ var dataclient = (() => {
12896
12908
  pendingAttrChanges = [];
12897
12909
  start() {
12898
12910
  this.observer = new MutationObserver((mutations) => this.handleMutations(mutations));
12899
- this.observer.observe(document.body, {
12911
+ this.observer.observe(this.root, {
12900
12912
  childList: true,
12901
12913
  subtree: true,
12902
12914
  characterData: true,
@@ -13001,12 +13013,14 @@ var dataclient = (() => {
13001
13013
 
13002
13014
  // src/trackers/snapshot.ts
13003
13015
  var SnapshotTracker = class {
13004
- constructor(config, sender) {
13016
+ constructor(config, sender, root2) {
13005
13017
  this.config = config;
13006
13018
  this.sender = sender;
13019
+ this.root = root2;
13007
13020
  }
13008
13021
  config;
13009
13022
  sender;
13023
+ root;
13010
13024
  lastUrl = "";
13011
13025
  urlPollTimer = null;
13012
13026
  checkpointTimer = null;
@@ -13040,7 +13054,7 @@ var dataclient = (() => {
13040
13054
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
13041
13055
  url: location.href,
13042
13056
  title: document.title,
13043
- tree: serializeTree(document.body),
13057
+ tree: serializeTree(this.root),
13044
13058
  viewport: getViewport()
13045
13059
  };
13046
13060
  if (this.config.debug) {
@@ -13213,8 +13227,10 @@ var dataclient = (() => {
13213
13227
  inputDebounce: 1e3,
13214
13228
  sessionIdKey: "sc2_sid",
13215
13229
  deviceIdKey: "sc2_did",
13216
- apiKey: ""
13230
+ apiKey: "",
13231
+ scoped: ""
13217
13232
  };
13233
+ var VALID_ATTR_NAME = /^[a-z][\w-]*$/i;
13218
13234
  var DataClient = class {
13219
13235
  sender = null;
13220
13236
  trackers = [];
@@ -13222,13 +13238,21 @@ var dataclient = (() => {
13222
13238
  deviceId;
13223
13239
  idleTimer = null;
13224
13240
  userId = null;
13241
+ rootEl = null;
13242
+ scopeObserver = null;
13243
+ scopeCheckScheduled = false;
13244
+ activityHandler = null;
13245
+ activityTarget = null;
13225
13246
  constructor(options) {
13226
13247
  this.config = { ...defaults, ...options };
13248
+ this.config.scoped = this.normalizeScoped(this.config.scoped);
13227
13249
  this.deviceId = getDeviceId(this.config.deviceIdKey);
13228
- this.startSession();
13229
- document.addEventListener("click", () => this.onActivity(), true);
13230
- document.addEventListener("input", () => this.onActivity(), true);
13231
- document.addEventListener("change", () => this.onActivity(), true);
13250
+ if (this.config.scoped) {
13251
+ this.startScopedMode();
13252
+ } else {
13253
+ this.startSession(document.body);
13254
+ this.attachActivityListeners(document);
13255
+ }
13232
13256
  }
13233
13257
  setUser(userId) {
13234
13258
  this.userId = userId;
@@ -13237,10 +13261,86 @@ var dataclient = (() => {
13237
13261
  excludeSession(reason = "") {
13238
13262
  this.sender?.add({ event: "exclude", timestamp: (/* @__PURE__ */ new Date()).toISOString(), reason });
13239
13263
  this.stopSession();
13264
+ if (this.scopeObserver) {
13265
+ this.scopeObserver.disconnect();
13266
+ this.scopeObserver = null;
13267
+ }
13268
+ this.detachActivityListeners();
13269
+ }
13270
+ normalizeScoped(value) {
13271
+ if (!value) {
13272
+ return "";
13273
+ }
13274
+ if (!VALID_ATTR_NAME.test(value)) {
13275
+ if (this.config.debug) {
13276
+ console.warn(`[dataclient] invalid "scoped" attribute name: ${JSON.stringify(value)}. Falling back to non-scoped mode.`);
13277
+ }
13278
+ return "";
13279
+ }
13280
+ return value;
13281
+ }
13282
+ startScopedMode() {
13283
+ const initial = this.findRoot();
13284
+ if (initial) {
13285
+ this.onRootAppeared(initial);
13286
+ }
13287
+ this.scopeObserver = new MutationObserver(() => this.scheduleScopeCheck());
13288
+ this.scopeObserver.observe(document.body, { childList: true, subtree: true });
13289
+ if (this.config.debug) {
13290
+ console.log(`[dataclient] scoped mode: watching for [${this.config.scoped}]`);
13291
+ }
13292
+ }
13293
+ findRoot() {
13294
+ return document.querySelector(`[${this.config.scoped}]`);
13295
+ }
13296
+ scheduleScopeCheck() {
13297
+ if (this.scopeCheckScheduled) {
13298
+ return;
13299
+ }
13300
+ this.scopeCheckScheduled = true;
13301
+ queueMicrotask(() => {
13302
+ this.scopeCheckScheduled = false;
13303
+ this.handleScopeChange();
13304
+ });
13305
+ }
13306
+ handleScopeChange() {
13307
+ const current = this.findRoot();
13308
+ if (current === this.rootEl) {
13309
+ return;
13310
+ }
13311
+ if (this.rootEl) {
13312
+ this.onRootDisappeared();
13313
+ }
13314
+ if (current) {
13315
+ this.onRootAppeared(current);
13316
+ }
13317
+ }
13318
+ onRootAppeared(root2) {
13319
+ this.rootEl = root2;
13320
+ this.attachActivityListeners(root2);
13321
+ this.startSession(root2);
13322
+ if (this.config.debug) {
13323
+ console.log(`[dataclient] root [${this.config.scoped}] appeared`);
13324
+ }
13325
+ }
13326
+ onRootDisappeared() {
13327
+ if (this.config.debug) {
13328
+ console.log(`[dataclient] root [${this.config.scoped}] removed`);
13329
+ }
13330
+ this.detachActivityListeners();
13331
+ this.stopSession();
13332
+ this.rootEl = null;
13240
13333
  }
13241
13334
  onActivity() {
13242
13335
  if (!this.sender) {
13243
- this.startSession();
13336
+ if (this.config.scoped) {
13337
+ if (!this.rootEl) {
13338
+ return;
13339
+ }
13340
+ this.startSession(this.rootEl);
13341
+ } else {
13342
+ this.startSession(document.body);
13343
+ }
13244
13344
  }
13245
13345
  this.resetIdleTimer();
13246
13346
  }
@@ -13249,7 +13349,26 @@ var dataclient = (() => {
13249
13349
  clearTimeout(this.idleTimer);
13250
13350
  this.idleTimer = setTimeout(() => this.stopSession(), this.config.idleTimeout);
13251
13351
  }
13252
- startSession() {
13352
+ attachActivityListeners(target) {
13353
+ this.detachActivityListeners();
13354
+ const handler = () => this.onActivity();
13355
+ target.addEventListener("click", handler, true);
13356
+ target.addEventListener("input", handler, true);
13357
+ target.addEventListener("change", handler, true);
13358
+ this.activityHandler = handler;
13359
+ this.activityTarget = target;
13360
+ }
13361
+ detachActivityListeners() {
13362
+ if (!this.activityHandler || !this.activityTarget) {
13363
+ return;
13364
+ }
13365
+ this.activityTarget.removeEventListener("click", this.activityHandler, true);
13366
+ this.activityTarget.removeEventListener("input", this.activityHandler, true);
13367
+ this.activityTarget.removeEventListener("change", this.activityHandler, true);
13368
+ this.activityHandler = null;
13369
+ this.activityTarget = null;
13370
+ }
13371
+ startSession(root2) {
13253
13372
  const sessionId = generateId();
13254
13373
  this.sender = new Sender(
13255
13374
  this.config.endpoint,
@@ -13259,9 +13378,9 @@ var dataclient = (() => {
13259
13378
  this.deviceId,
13260
13379
  this.config.flushInterval
13261
13380
  );
13262
- const snapshotTracker = new SnapshotTracker(this.config, this.sender);
13263
- const mutationTracker = new MutationTracker(this.config, this.sender, () => snapshotTracker.markMutation());
13264
- const actionTracker = new ActionTracker(this.config, this.sender);
13381
+ const snapshotTracker = new SnapshotTracker(this.config, this.sender, root2);
13382
+ const mutationTracker = new MutationTracker(this.config, this.sender, root2, () => snapshotTracker.markMutation());
13383
+ const actionTracker = new ActionTracker(this.config, this.sender, root2);
13265
13384
  const rrwebTracker = new RrwebTracker(this.config, this.sender);
13266
13385
  this.trackers = [snapshotTracker, mutationTracker, actionTracker, rrwebTracker];
13267
13386
  this.trackers.forEach((t) => t.start());
@@ -13287,6 +13406,7 @@ var dataclient = (() => {
13287
13406
  clearTimeout(this.idleTimer);
13288
13407
  this.idleTimer = null;
13289
13408
  }
13409
+ this.trackers.forEach((t) => t.beforeUnload?.());
13290
13410
  this.trackers.forEach((t) => t.stop());
13291
13411
  this.trackers = [];
13292
13412
  if (this.sender) {
@@ -13294,7 +13414,7 @@ var dataclient = (() => {
13294
13414
  this.sender = null;
13295
13415
  }
13296
13416
  if (this.config.debug) {
13297
- console.log("[dataclient] Session stopped (idle timeout)");
13417
+ console.log("[dataclient] Session stopped");
13298
13418
  }
13299
13419
  }
13300
13420
  };