@tamsensedev/dataclient 0.1.10 → 0.2.3

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