@tamsensedev/dataclient 0.1.0 → 0.1.1

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/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # TAMsense SDK
2
+
3
+ Capture frontend user behavior for TAMsense to detect friction across onboarding, activation, evaluation, and conversion to paid journeys.
4
+
5
+ TAMsense helps SaaS companies turn more free users into paying customers by generating specific product recommendations based on the analysis of user behavior across the entire product
6
+
7
+ **Looking to improve conversion to paid?** Support us on Product Hunt by subscribing for launch updates https://www.producthunt.com/products/tamsense?launch=tamsense
8
+
9
+
10
+ ---
11
+
12
+ ## Why teams use TAMsense SDK
13
+
14
+ Teams use the SDK when they want clear, ready-to-act-on decisions instead of just raw events and dashboards.
15
+
16
+ With TAMsense SDK, you can:
17
+
18
+ - capture real user behavior across onboarding, activation, and evaluation flows
19
+ - give TAMsense behavioral signals in context: where users are in the journey, what they were trying to do, what happened before drop-off, and where friction blocks conversion
20
+ - understand where users struggle on the journey to pay
21
+ - feed the full TAMsense system that generates prioritized recommendations for what to fix first
22
+
23
+ ---
24
+
25
+ ## How it fits into TAMsense
26
+
27
+ TAMsense has three parts:
28
+
29
+ 1. **SDK** — installed on your frontend to capture user actions in context: where they are in the journey, what they are trying to do, and where they hesitate, loop, or drop off
30
+ 2. **Analysis Core** — process behavior data, detect friction patterns, and identify likely conversion blockers
31
+ 3. **Web app** — shows recommendations, priorities, and suggested actions for your team
32
+
33
+ In short:
34
+
35
+ **Install the SDK → capture user behavior → TAMsense analyzes friction → your team gets prioritized solutions**
36
+
37
+ The SDK is open. The analysis layer and recommendation workflow are part of the full TAMsense product.
38
+
39
+ ---
40
+
41
+ ## Best fit
42
+
43
+ TAMsense SDK is a strong fit for:
44
+
45
+ - PLG and SaaS products
46
+ - teams working on onboarding, activation, and conversion to paid
47
+ - product, growth, and founder-led teams that already have traffic and product usage, but still struggle to prioritize what to fix
48
+ - companies that want decision-ready recommendations, not just more dashboards
49
+
50
+ ---
51
+
52
+ ## What this SDK is
53
+
54
+ TAMsense SDK is:
55
+
56
+ - a frontend behavior capture layer for TAMsense
57
+ - a way to collect user interaction data from key product journeys
58
+ - an input to TAMsense analysis agents and recommendation workflows
59
+
60
+ ## What this SDK is not
61
+
62
+ TAMsense SDK is not:
63
+
64
+ - a standalone product analytics platform
65
+ - a session replay replacement
66
+ - the full TAMsense recommendation engine
67
+ - a complete self-serve conversion optimization tool on its own
68
+
69
+ ---
package/dist/index.cjs CHANGED
@@ -795,8 +795,8 @@ function generateId() {
795
795
  }
796
796
 
797
797
  // src/utils/sender.ts
798
- var STORAGE_KEY = "sc2_pending";
799
798
  var MAX_RETRIES = 2;
799
+ var BEACON_MAX_SIZE = 6e4;
800
800
  var Sender = class {
801
801
  constructor(endpoint, apiKey, batchSize, sessionId, deviceId, flushInterval) {
802
802
  this.endpoint = endpoint;
@@ -804,13 +804,7 @@ var Sender = class {
804
804
  this.batchSize = batchSize;
805
805
  this.sessionId = sessionId;
806
806
  this.deviceId = deviceId;
807
- this.restoreFromStorage();
808
807
  this.timer = setInterval(() => this.flush(), flushInterval);
809
- document.addEventListener("visibilitychange", () => {
810
- if (document.visibilityState === "hidden") {
811
- this.flush();
812
- }
813
- });
814
808
  }
815
809
  endpoint;
816
810
  apiKey;
@@ -820,24 +814,9 @@ var Sender = class {
820
814
  queue = [];
821
815
  timer = null;
822
816
  isFlushing = false;
823
- restoreFromStorage() {
824
- try {
825
- localStorage.removeItem(STORAGE_KEY);
826
- } catch {
827
- }
828
- }
829
- saveToStorage() {
830
- if (this.queue.length === 0) {
831
- return;
832
- }
833
- try {
834
- localStorage.setItem(STORAGE_KEY, JSON.stringify(this.queue));
835
- } catch {
836
- }
837
- }
838
817
  add(event) {
839
818
  this.queue.push(event);
840
- if (this.queue.length >= this.batchSize) {
819
+ if (event.event === "rrweb" || this.queue.length >= this.batchSize) {
841
820
  this.flush();
842
821
  }
843
822
  }
@@ -849,11 +828,10 @@ var Sender = class {
849
828
  const events = this.queue.splice(0);
850
829
  const batch = this.buildBatch(events);
851
830
  const json = JSON.stringify(batch);
852
- const url = `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`;
831
+ const url = this.buildUrl();
853
832
  const success = await this.send(json, url);
854
833
  if (!success) {
855
834
  this.queue.unshift(...events);
856
- this.saveToStorage();
857
835
  }
858
836
  this.isFlushing = false;
859
837
  }
@@ -862,15 +840,38 @@ var Sender = class {
862
840
  return;
863
841
  }
864
842
  const events = this.queue.splice(0);
843
+ if (events.length === 0) return;
844
+ const url = this.buildUrl();
845
+ let chunk = [];
846
+ let chunkSize = 0;
847
+ for (const event of events) {
848
+ const eventJson = JSON.stringify(event);
849
+ if (chunkSize + eventJson.length > BEACON_MAX_SIZE && chunk.length > 0) {
850
+ this.sendBeacon(chunk, url);
851
+ chunk = [];
852
+ chunkSize = 0;
853
+ }
854
+ chunk.push(event);
855
+ chunkSize += eventJson.length;
856
+ }
857
+ if (chunk.length > 0) {
858
+ this.sendBeacon(chunk, url);
859
+ }
860
+ }
861
+ destroy() {
862
+ if (this.timer) {
863
+ clearInterval(this.timer);
864
+ }
865
+ this.flushSync();
866
+ }
867
+ sendBeacon(events, url) {
865
868
  const batch = this.buildBatch(events);
866
869
  const json = JSON.stringify(batch);
867
- const url = `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`;
868
870
  const blob = new Blob([json], { type: "application/json" });
869
- const sent = navigator.sendBeacon(url, blob);
870
- if (!sent) {
871
- this.queue.unshift(...events);
872
- this.saveToStorage();
873
- }
871
+ navigator.sendBeacon(url, blob);
872
+ }
873
+ buildUrl() {
874
+ return `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`;
874
875
  }
875
876
  buildBatch(events) {
876
877
  return {
@@ -908,20 +909,14 @@ var Sender = class {
908
909
  }
909
910
  return false;
910
911
  }
911
- destroy() {
912
- if (this.timer) {
913
- clearInterval(this.timer);
914
- }
915
- this.flushSync();
916
- }
917
912
  };
918
913
 
919
914
  // src/index.ts
920
915
  var defaults = {
921
916
  endpoint: "https://my.tamsense.com/api/scenes2",
922
917
  debug: false,
923
- batchSize: 10,
924
- flushInterval: 1e4,
918
+ batchSize: 5,
919
+ flushInterval: 5e3,
925
920
  checkpointInterval: 3e4,
926
921
  mutationDebounce: 200,
927
922
  inputDebounce: 1e3,
@@ -951,13 +946,14 @@ var DataClient = class {
951
946
  const rrwebTracker = new RrwebTracker(this.config, this.sender);
952
947
  this.trackers = [snapshotTracker, mutationTracker, actionTracker, rrwebTracker];
953
948
  this.trackers.forEach((t) => t.start());
954
- const onUnload = () => {
949
+ const onLeave = () => {
955
950
  this.trackers.forEach((t) => t.beforeUnload?.());
956
951
  this.sender.flushSync();
957
952
  };
958
- window.addEventListener("beforeunload", onUnload);
959
- window.addEventListener("pagehide", onUnload);
960
- console.log(`[dataclient] Initialized. Session: ${sessionId}, Device: ${deviceId}`);
953
+ document.addEventListener("visibilitychange", () => {
954
+ if (document.visibilityState === "hidden") onLeave();
955
+ });
956
+ window.addEventListener("pagehide", onLeave);
961
957
  }
962
958
  setUser(userId) {
963
959
  this.sender.add({ event: "identify", timestamp: (/* @__PURE__ */ new Date()).toISOString(), user_id: userId });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/dom/mask.ts","../src/dom/icon.ts","../src/dom/serializer.ts","../src/dom/viewport.ts","../src/trackers/rrweb.ts","../src/trackers/action.ts","../src/trackers/mutation.ts","../src/trackers/snapshot.ts","../src/utils/identity.ts","../src/utils/sender.ts"],"sourcesContent":["import type { Config, Tracker } from './types'\r\nimport { ActionTracker } from './trackers/action'\r\nimport { MutationTracker } from './trackers/mutation'\r\nimport { RrwebTracker } from './trackers/rrweb'\r\nimport { SnapshotTracker } from './trackers/snapshot'\r\nimport { generateId, getDeviceId } from './utils/identity'\r\nimport { Sender } from './utils/sender'\r\n\r\nexport type * from './types'\r\n\r\nconst defaults: Config = {\r\n endpoint: 'https://my.tamsense.com/api/scenes2',\r\n debug: false,\r\n batchSize: 10,\r\n flushInterval: 10000,\r\n checkpointInterval: 30000,\r\n mutationDebounce: 200,\r\n inputDebounce: 1000,\r\n sessionIdKey: 'sc2_sid',\r\n deviceIdKey: 'sc2_did',\r\n apiKey: '',\r\n}\r\n\r\nexport class DataClient {\r\n private sender: Sender\r\n private trackers: Tracker[] = []\r\n private config: Config\r\n\r\n constructor(options?: Partial<Config>) {\r\n this.config = { ...defaults, ...options }\r\n\r\n const sessionId = generateId()\r\n const deviceId = getDeviceId(this.config.deviceIdKey)\r\n\r\n this.sender = new Sender(\r\n this.config.endpoint,\r\n this.config.apiKey,\r\n this.config.batchSize,\r\n sessionId,\r\n deviceId,\r\n this.config.flushInterval,\r\n )\r\n\r\n const snapshotTracker = new SnapshotTracker(this.config, this.sender)\r\n const mutationTracker = new MutationTracker(this.config, this.sender, () => snapshotTracker.markMutation())\r\n const actionTracker = new ActionTracker(this.config, this.sender)\r\n const rrwebTracker = new RrwebTracker(this.config, this.sender)\r\n\r\n this.trackers = [snapshotTracker, mutationTracker, actionTracker, rrwebTracker]\r\n this.trackers.forEach(t => t.start())\r\n\r\n const onUnload = () => {\r\n this.trackers.forEach(t => t.beforeUnload?.())\r\n this.sender.flushSync()\r\n }\r\n window.addEventListener('beforeunload', onUnload)\r\n window.addEventListener('pagehide', onUnload)\r\n\r\n console.log(`[dataclient] Initialized. Session: ${sessionId}, Device: ${deviceId}`)\r\n }\r\n\r\n setUser(userId: string) {\r\n this.sender.add({ event: 'identify', timestamp: new Date().toISOString(), user_id: userId })\r\n }\r\n\r\n excludeSession(reason = '') {\r\n this.sender.add({ event: 'exclude', timestamp: new Date().toISOString(), reason })\r\n this.destroy()\r\n }\r\n\r\n destroy() {\r\n this.trackers.forEach(t => t.stop())\r\n this.trackers = []\r\n this.sender.destroy()\r\n }\r\n}\r\n","export const MASK_ATTR = 'dataclient-mask'\r\nexport const MASK_SELECTOR = `[${MASK_ATTR}]`\r\n\r\nexport const SKIP_TAGS = new Set(['script', 'style', 'noscript', 'svg', 'link', 'meta', 'head'])\r\n\r\nexport const RECORD_ATTRS = new Set([\r\n 'class',\r\n 'role',\r\n 'href',\r\n 'type',\r\n 'placeholder',\r\n 'disabled',\r\n 'hidden',\r\n 'aria-label',\r\n 'aria-selected',\r\n 'aria-expanded',\r\n 'aria-invalid',\r\n 'aria-busy',\r\n 'aria-checked',\r\n 'aria-hidden',\r\n 'value',\r\n 'name',\r\n 'id',\r\n 'for',\r\n 'target',\r\n 'data-state',\r\n MASK_ATTR,\r\n])\r\n\r\nexport const WATCH_ATTRS = [\r\n 'class',\r\n 'role',\r\n 'href',\r\n 'disabled',\r\n 'hidden',\r\n 'placeholder',\r\n 'aria-label',\r\n 'aria-selected',\r\n 'aria-expanded',\r\n 'aria-invalid',\r\n 'aria-busy',\r\n 'aria-checked',\r\n 'aria-hidden',\r\n 'data-state',\r\n 'value',\r\n]\r\n\r\nexport const TEXT_INPUT_TYPES = new Set(['text', 'email', 'password', 'search', 'tel', 'url', 'number'])\r\n\r\nexport const INTERACTIVE_TAGS = new Set(['button', 'a', 'input', 'select', 'textarea'])\r\nexport const INTERACTIVE_ROLES = new Set(['button', 'link', 'tab', 'menuitem', 'checkbox', 'radio', 'switch', 'option'])\r\n","import { MASK_SELECTOR } from '../constants'\r\n\r\nexport function isMasked(el: Element | Node | null): boolean {\r\n if (!el)\r\n return false\r\n const element = el.nodeType === Node.ELEMENT_NODE\r\n ? el as Element\r\n : el.parentElement\r\n return !!element?.closest(MASK_SELECTOR)\r\n}\r\n\r\nexport function maskText(text: string): string {\r\n if (!text) {\r\n return text\r\n }\r\n const visible = Math.max(1, Math.ceil(text.length * 0.2))\r\n return text.slice(0, visible) + '*'.repeat(text.length - visible)\r\n}\r\n\r\nexport function maskValue(el: Element | null, value: string): string {\r\n return isMasked(el) ? maskText(value) : value\r\n}\r\n","const ICON_CLASS_PATTERNS = [\r\n /\\bfa-([a-z0-9-]+)\\b/,\r\n /\\bmdi-([a-z0-9-]+)\\b/,\r\n /\\bbi-([a-z0-9-]+)\\b/,\r\n /\\bicon-([a-z0-9-]+)\\b/,\r\n /\\blucide-([a-z0-9-]+)\\b/,\r\n /\\bri-([a-z0-9-]+)\\b/,\r\n /\\btabler-icon-([a-z0-9-]+)\\b/,\r\n /\\bi-[a-z0-9-]+[:/]([a-z0-9-]+)\\b/,\r\n]\r\n\r\nconst MATERIAL_CLASS_PATTERN = /\\b(?:material-icons|material-symbols-[a-z]+)\\b/\r\nconst SVG_USE_HREF_PATTERN = /#(.+)/\r\n\r\nconst MAX_ICON_IMG_SIZE = 48\r\n\r\nexport function getElementIcon(el: HTMLElement): string | null {\r\n if (!el) {\r\n return null\r\n }\r\n\r\n const direct = detectIcon(el)\r\n if (direct) {\r\n return direct\r\n }\r\n\r\n for (const child of el.children) {\r\n const icon = detectIcon(child as HTMLElement)\r\n if (icon) {\r\n return icon\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\nfunction detectIcon(el: HTMLElement): string | null {\r\n if (!el?.tagName) {\r\n return null\r\n }\r\n\r\n const tag = el.tagName.toLowerCase()\r\n\r\n if (tag === 'svg') {\r\n return detectSvgIcon(el)\r\n }\r\n\r\n if (tag === 'i' || tag === 'span' || el.classList.contains('iconify')) {\r\n return detectFontIcon(el)\r\n }\r\n\r\n if (tag === 'img') {\r\n return detectImgIcon(el as HTMLImageElement)\r\n }\r\n\r\n const svg = el.querySelector('svg')\r\n if (svg) {\r\n return detectSvgIcon(svg)\r\n }\r\n\r\n return null\r\n}\r\n\r\nfunction detectSvgIcon(svg: Element): string | null {\r\n const lucide = svg.getAttribute('data-lucide')\r\n if (lucide) {\r\n return lucide\r\n }\r\n\r\n const dataIcon = svg.getAttribute('data-icon')\r\n if (dataIcon) {\r\n return dataIcon\r\n }\r\n\r\n const use = svg.querySelector('use')\r\n if (use) {\r\n const href = use.getAttribute('href') || use.getAttribute('xlink:href')\r\n if (href) {\r\n const match = href.match(SVG_USE_HREF_PATTERN)\r\n if (match) {\r\n return match[1]\r\n }\r\n }\r\n }\r\n\r\n const ariaLabel = svg.getAttribute('aria-label')\r\n if (ariaLabel) {\r\n return ariaLabel\r\n }\r\n\r\n const cls = typeof svg.className === 'string' ? svg.className : svg.getAttribute('class') || ''\r\n const fromClass = extractIconNameFromClass(cls)\r\n if (fromClass) {\r\n return fromClass\r\n }\r\n\r\n return null\r\n}\r\n\r\nfunction detectFontIcon(el: HTMLElement): string | null {\r\n const cls = typeof el.className === 'string' ? el.className : el.getAttribute('class') || ''\r\n\r\n if (MATERIAL_CLASS_PATTERN.test(cls)) {\r\n const text = el.textContent?.trim()\r\n if (text) {\r\n return text\r\n }\r\n }\r\n\r\n return extractIconNameFromClass(cls)\r\n}\r\n\r\nfunction detectImgIcon(img: HTMLImageElement): string | null {\r\n if (img.width > MAX_ICON_IMG_SIZE || img.height > MAX_ICON_IMG_SIZE) {\r\n return null\r\n }\r\n\r\n if (img.naturalWidth > MAX_ICON_IMG_SIZE || img.naturalHeight > MAX_ICON_IMG_SIZE) {\r\n return null\r\n }\r\n\r\n const alt = img.alt?.trim()\r\n if (alt) {\r\n return alt\r\n }\r\n\r\n const src = img.getAttribute('src')\r\n if (src) {\r\n const filename = src.split('/').pop()?.split('?')[0]?.split('.')[0]\r\n if (filename) {\r\n return filename\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\nfunction extractIconNameFromClass(cls: string): string | null {\r\n for (const pattern of ICON_CLASS_PATTERNS) {\r\n const match = cls.match(pattern)\r\n if (match) {\r\n return match[1]\r\n }\r\n }\r\n return null\r\n}\r\n","import type { SerializedNode } from '../types'\r\nimport { RECORD_ATTRS, SKIP_TAGS } from '../constants'\r\nimport { getElementIcon } from './icon'\r\nimport { isMasked, maskText } from './mask'\r\n\r\nlet nextId = 1\r\nconst nodeToId = new WeakMap<Node, number>()\r\nconst idToNode = new Map<number, Node>()\r\n\r\nexport function resetIds() {\r\n nextId = 1\r\n idToNode.clear()\r\n}\r\n\r\nexport function assignId(node: Node): number {\r\n const existing = nodeToId.get(node)\r\n if (existing) {\r\n return existing\r\n }\r\n const id = nextId++\r\n nodeToId.set(node, id)\r\n idToNode.set(id, node)\r\n return id\r\n}\r\n\r\nexport function getNodeId(node: Node): number | null {\r\n return nodeToId.get(node) ?? null\r\n}\r\n\r\nexport function getNodeById(id: number): Node | null {\r\n return idToNode.get(id) ?? null\r\n}\r\n\r\nexport function removeNodeId(id: number) {\r\n const node = idToNode.get(id)\r\n if (node) {\r\n nodeToId.delete(node)\r\n idToNode.delete(id)\r\n }\r\n}\r\n\r\nfunction isVisible(el: HTMLElement): boolean {\r\n if (el.hidden)\r\n return false\r\n if (el.getAttribute('aria-hidden') === 'true')\r\n return false\r\n if (!el.offsetParent && el.tagName !== 'BODY' && getComputedStyle(el).position !== 'fixed') {\r\n return false\r\n }\r\n return true\r\n}\r\n\r\nfunction getDirectText(el: HTMLElement): string {\r\n let text = ''\r\n for (const child of el.childNodes) {\r\n if (child.nodeType === Node.TEXT_NODE) {\r\n const t = child.textContent?.trim()\r\n if (t) {\r\n text += (text ? ' ' : '') + t\r\n }\r\n }\r\n }\r\n const truncated = text.slice(0, 200)\r\n return isMasked(el) ? maskText(truncated) : truncated\r\n}\r\n\r\nfunction getAttrs(el: HTMLElement): Record<string, string> | undefined {\r\n const attrs: Record<string, string> = {}\r\n let hasAttrs = false\r\n const masked = isMasked(el)\r\n\r\n for (const name of RECORD_ATTRS) {\r\n const value = el.getAttribute(name)\r\n if (value !== null && value !== '') {\r\n let v = value.slice(0, 200)\r\n if (masked && (name === 'value' || name === 'placeholder')) {\r\n v = maskText(v)\r\n }\r\n attrs[name] = v\r\n hasAttrs = true\r\n }\r\n }\r\n\r\n return hasAttrs ? attrs : undefined\r\n}\r\n\r\nfunction getRect(el: HTMLElement): SerializedNode['rect'] {\r\n const r = el.getBoundingClientRect()\r\n if (r.width === 0 && r.height === 0) {\r\n return undefined\r\n }\r\n return {\r\n x: Math.round(r.left + window.scrollX),\r\n y: Math.round(r.top + window.scrollY),\r\n w: Math.round(r.width),\r\n h: Math.round(r.height),\r\n }\r\n}\r\n\r\nexport function serializeNode(el: HTMLElement): SerializedNode | null {\r\n const tag = el.tagName?.toLowerCase()\r\n if (!tag || SKIP_TAGS.has(tag)) {\r\n return null\r\n }\r\n if (!isVisible(el)) {\r\n return null\r\n }\r\n\r\n const id = assignId(el)\r\n const text = getDirectText(el)\r\n const icon = getElementIcon(el)\r\n const attrs = getAttrs(el)\r\n const rect = getRect(el)\r\n\r\n const children: number[] = []\r\n for (const child of el.children) {\r\n const serialized = serializeNode(child as HTMLElement)\r\n if (serialized) {\r\n children.push(serialized.id)\r\n }\r\n }\r\n\r\n const node: SerializedNode = { id, tag }\r\n if (text) {\r\n node.text = text\r\n }\r\n if (icon) {\r\n node.icon = icon\r\n }\r\n if (attrs) {\r\n node.attrs = attrs\r\n }\r\n if (rect) {\r\n node.rect = rect\r\n }\r\n if (children.length > 0) {\r\n node.children = children\r\n }\r\n\r\n return node\r\n}\r\n\r\nexport function serializeTree(root: HTMLElement): SerializedNode[] {\r\n const nodes: SerializedNode[] = []\r\n\r\n function walk(el: HTMLElement) {\r\n const node = serializeNode(el)\r\n if (!node) {\r\n return\r\n }\r\n nodes.push(node)\r\n for (const child of el.children) {\r\n walk(child as HTMLElement)\r\n }\r\n }\r\n\r\n walk(root)\r\n return nodes\r\n}\r\n","import type { Viewport } from '../types'\r\n\r\nexport function getViewport(): Viewport {\r\n return {\r\n scrollX: Math.round(window.scrollX),\r\n scrollY: Math.round(window.scrollY),\r\n width: window.innerWidth,\r\n height: window.innerHeight,\r\n }\r\n}\r\n","import type { Config, RrwebEvent, Tracker } from '../types'\r\nimport type { Sender } from '../utils/sender'\r\nimport { record } from 'rrweb'\r\nimport { MASK_SELECTOR } from '../constants'\r\n\r\nexport class RrwebTracker implements Tracker {\r\n private stopFn: (() => void) | null = null\r\n\r\n constructor(\r\n private config: Config,\r\n private sender: Sender,\r\n ) {}\r\n\r\n start() {\r\n this.stopFn = record({\r\n emit: (event) => {\r\n const rrwebEvent: RrwebEvent = {\r\n event: 'rrweb',\r\n timestamp: new Date().toISOString(),\r\n rrwebEvent: event,\r\n }\r\n this.sender.add(rrwebEvent)\r\n },\r\n recordCrossOriginIframes: false,\r\n recordCanvas: false,\r\n maskTextSelector: MASK_SELECTOR,\r\n maskInputOptions: { password: true },\r\n maskTextFn: text => '*'.repeat(text.length),\r\n sampling: {\r\n mousemove: false,\r\n mouseInteraction: true,\r\n scroll: 500,\r\n input: 'last',\r\n },\r\n }) ?? null\r\n\r\n if (this.config.debug)\r\n console.log('[dataclient] rrweb recording started')\r\n }\r\n\r\n stop() {\r\n if (this.stopFn) {\r\n this.stopFn()\r\n this.stopFn = null\r\n }\r\n }\r\n\r\n static addMarker(tag: string, payload: unknown) {\r\n record.addCustomEvent(tag, payload)\r\n }\r\n}\r\n","import type { ActionEvent, Config, Tracker } from '../types'\r\nimport type { Sender } from '../utils/sender'\r\nimport { INTERACTIVE_ROLES, INTERACTIVE_TAGS, TEXT_INPUT_TYPES } from '../constants'\r\nimport { isMasked, maskText } from '../dom/mask'\r\nimport { getNodeId } from '../dom/serializer'\r\nimport { getViewport } from '../dom/viewport'\r\nimport { RrwebTracker } from './rrweb'\r\n\r\nexport class ActionTracker implements Tracker {\r\n private inputTimers = new WeakMap<HTMLElement, ReturnType<typeof setTimeout>>()\r\n private pendingInputs = new Set<HTMLElement>()\r\n\r\n private handleClick = (e: Event) => this.onClick(e)\r\n private handleInput = (e: Event) => this.onInput(e)\r\n private handleChange = (e: Event) => this.onChange(e)\r\n\r\n constructor(\r\n private config: Config,\r\n private sender: Sender,\r\n ) {}\r\n\r\n start() {\r\n document.addEventListener('click', this.handleClick, true)\r\n document.addEventListener('input', this.handleInput, true)\r\n document.addEventListener('change', this.handleChange, true)\r\n }\r\n\r\n stop() {\r\n document.removeEventListener('click', this.handleClick, true)\r\n document.removeEventListener('input', this.handleInput, true)\r\n document.removeEventListener('change', this.handleChange, true)\r\n }\r\n\r\n beforeUnload() {\r\n for (const el of this.pendingInputs) {\r\n const timer = this.inputTimers.get(el)\r\n if (timer) {\r\n clearTimeout(timer)\r\n }\r\n this.inputTimers.delete(el)\r\n this.recordInput(el)\r\n }\r\n this.pendingInputs.clear()\r\n }\r\n\r\n private onClick(e: Event) {\r\n const raw = e.target as HTMLElement\r\n if (!raw) {\r\n return\r\n }\r\n\r\n const tag = raw.tagName?.toLowerCase()\r\n if (tag === 'body' || tag === 'html') {\r\n return\r\n }\r\n\r\n const el = this.findMeaningfulElement(raw)\r\n const info = this.getElementInfo(el)\r\n\r\n const action: ActionEvent = {\r\n event: 'action',\r\n timestamp: new Date().toISOString(),\r\n type: 'click',\r\n targetId: info.targetId,\r\n tag: info.tag,\r\n text: info.text,\r\n url: location.href,\r\n state: info.state,\r\n viewport: getViewport(),\r\n }\r\n\r\n if (this.config.debug) {\r\n console.log(`[dataclient] click: ${info.tag} \"${info.text}\"`)\r\n }\r\n\r\n this.sender.add(action)\r\n RrwebTracker.addMarker('click', { tag: info.tag, text: info.text, label: `Click: ${info.text || info.tag}` })\r\n }\r\n\r\n private onInput(e: Event) {\r\n const target = e.target as HTMLElement\r\n if (!target) {\r\n return\r\n }\r\n\r\n const tag = target.tagName?.toLowerCase()\r\n if (tag === 'input') {\r\n const type = (target as HTMLInputElement).type?.toLowerCase() || 'text'\r\n if (!TEXT_INPUT_TYPES.has(type)) {\r\n return\r\n }\r\n }\r\n else if (tag !== 'textarea') {\r\n return\r\n }\r\n\r\n this.pendingInputs.add(target)\r\n\r\n const prev = this.inputTimers.get(target)\r\n if (prev) {\r\n clearTimeout(prev)\r\n }\r\n\r\n this.inputTimers.set(target, setTimeout(() => {\r\n this.inputTimers.delete(target)\r\n this.pendingInputs.delete(target)\r\n this.recordInput(target)\r\n }, this.config.inputDebounce))\r\n }\r\n\r\n private recordInput(target: HTMLElement) {\r\n const rawValue = (target as HTMLInputElement).value || ''\r\n if (!rawValue) {\r\n return\r\n }\r\n\r\n const info = this.getElementInfo(target)\r\n const type = (target as HTMLInputElement).type?.toLowerCase() || ''\r\n const value = (info.masked || type === 'password') ? maskText(rawValue) : rawValue\r\n\r\n const action: ActionEvent = {\r\n event: 'action',\r\n timestamp: new Date().toISOString(),\r\n type: 'input',\r\n targetId: info.targetId,\r\n tag: info.tag,\r\n text: info.text,\r\n url: location.href,\r\n value,\r\n length: rawValue.length,\r\n viewport: getViewport(),\r\n }\r\n\r\n if (this.config.debug) {\r\n console.log(`[dataclient] input: ${info.tag} \"${info.text}\" → ${value.length} chars`)\r\n }\r\n\r\n this.sender.add(action)\r\n RrwebTracker.addMarker('input', { tag: info.tag, text: info.text, label: `Input: ${info.text || info.tag}` })\r\n }\r\n\r\n private onChange(e: Event) {\r\n const target = e.target as HTMLElement\r\n if (!target) {\r\n return\r\n }\r\n\r\n const info = this.getElementInfo(target)\r\n const tag = target.tagName?.toLowerCase()\r\n\r\n const action: ActionEvent = {\r\n event: 'action',\r\n timestamp: new Date().toISOString(),\r\n type: 'change',\r\n targetId: info.targetId,\r\n tag: info.tag,\r\n text: info.text,\r\n url: location.href,\r\n viewport: getViewport(),\r\n }\r\n\r\n if (tag === 'select') {\r\n const selectValue = (target as HTMLSelectElement).value\r\n action.value = info.masked ? maskText(selectValue) : selectValue\r\n }\r\n else if (tag === 'input') {\r\n const type = (target as HTMLInputElement).type\r\n if (type === 'checkbox' || type === 'radio') {\r\n action.checked = (target as HTMLInputElement).checked\r\n }\r\n }\r\n\r\n if (this.config.debug) {\r\n console.log(`[dataclient] change: ${info.tag} \"${info.text}\"`)\r\n }\r\n\r\n this.sender.add(action)\r\n RrwebTracker.addMarker('change', { tag: info.tag, text: info.text, label: `Change: ${info.text || info.tag}` })\r\n }\r\n\r\n private getElementInfo(el: HTMLElement) {\r\n const tag = el.tagName?.toLowerCase() || ''\r\n let text = ''\r\n\r\n if (tag === 'input' || tag === 'textarea') {\r\n text = (el as HTMLInputElement).placeholder || el.getAttribute('aria-label') || ''\r\n }\r\n else {\r\n for (const child of el.childNodes) {\r\n if (child.nodeType === Node.TEXT_NODE) {\r\n const t = child.textContent?.trim()\r\n if (t) {\r\n text += (text ? ' ' : '') + t\r\n }\r\n }\r\n }\r\n if (!text) {\r\n text = el.textContent?.trim().slice(0, 100) || ''\r\n }\r\n }\r\n\r\n const masked = isMasked(el)\r\n const finalText = masked ? maskText(text) : text\r\n\r\n return {\r\n tag,\r\n text: finalText.slice(0, 100),\r\n targetId: getNodeId(el),\r\n state: (el as HTMLButtonElement).disabled ? 'disabled' : 'enabled',\r\n masked,\r\n }\r\n }\r\n\r\n private findMeaningfulElement(el: HTMLElement): HTMLElement {\r\n let current: HTMLElement | null = el\r\n while (current && current !== document.body) {\r\n if (INTERACTIVE_TAGS.has(current.tagName?.toLowerCase())) {\r\n return current\r\n }\r\n const role = current.getAttribute('role')\r\n if (role && INTERACTIVE_ROLES.has(role)) {\r\n return current\r\n }\r\n current = current.parentElement\r\n }\r\n return el\r\n }\r\n}\r\n","import type { AttrChange, Config, MutationAdd, MutationEvent, TextChange, Tracker } from '../types'\r\nimport type { Sender } from '../utils/sender'\r\nimport { WATCH_ATTRS } from '../constants'\r\nimport { getNodeId, removeNodeId, serializeNode } from '../dom/serializer'\r\n\r\nexport class MutationTracker implements Tracker {\r\n private observer: MutationObserver | null = null\r\n private debounceTimer: ReturnType<typeof setTimeout> | null = null\r\n\r\n private pendingAdds: MutationAdd[] = []\r\n private pendingRemoves: number[] = []\r\n private pendingTextChanges: TextChange[] = []\r\n private pendingAttrChanges: AttrChange[] = []\r\n\r\n constructor(\r\n private config: Config,\r\n private sender: Sender,\r\n private onMutation: () => void,\r\n ) {}\r\n\r\n start() {\r\n this.observer = new MutationObserver(mutations => this.handleMutations(mutations))\r\n this.observer.observe(document.body, {\r\n childList: true,\r\n subtree: true,\r\n characterData: true,\r\n attributes: true,\r\n attributeFilter: WATCH_ATTRS,\r\n })\r\n }\r\n\r\n stop() {\r\n this.observer?.disconnect()\r\n this.observer = null\r\n if (this.debounceTimer) {\r\n clearTimeout(this.debounceTimer)\r\n }\r\n }\r\n\r\n beforeUnload() {\r\n if (this.debounceTimer) {\r\n clearTimeout(this.debounceTimer)\r\n this.flush()\r\n }\r\n }\r\n\r\n private flush() {\r\n this.debounceTimer = null\r\n\r\n if (\r\n this.pendingAdds.length === 0\r\n && this.pendingRemoves.length === 0\r\n && this.pendingTextChanges.length === 0\r\n && this.pendingAttrChanges.length === 0\r\n ) {\r\n return\r\n }\r\n\r\n const mutation: MutationEvent = {\r\n event: 'mutation',\r\n timestamp: new Date().toISOString(),\r\n adds: this.pendingAdds,\r\n removes: this.pendingRemoves,\r\n text_changes: this.pendingTextChanges,\r\n attr_changes: this.pendingAttrChanges,\r\n }\r\n\r\n if (this.config.debug) {\r\n console.log(`[dataclient] mutation: +${this.pendingAdds.length} -${this.pendingRemoves.length} text:${this.pendingTextChanges.length} attr:${this.pendingAttrChanges.length}`)\r\n }\r\n\r\n this.sender.add(mutation)\r\n this.onMutation()\r\n\r\n this.pendingAdds = []\r\n this.pendingRemoves = []\r\n this.pendingTextChanges = []\r\n this.pendingAttrChanges = []\r\n }\r\n\r\n private scheduleFlush() {\r\n if (this.debounceTimer) {\r\n clearTimeout(this.debounceTimer)\r\n }\r\n this.debounceTimer = setTimeout(() => this.flush(), this.config.mutationDebounce)\r\n }\r\n\r\n private handleMutations(mutations: MutationRecord[]) {\r\n for (const m of mutations) {\r\n for (const node of m.addedNodes) {\r\n if (node.nodeType !== Node.ELEMENT_NODE) {\r\n continue\r\n }\r\n const el = node as HTMLElement\r\n const serialized = serializeNode(el)\r\n if (!serialized) {\r\n continue\r\n }\r\n\r\n const parentId = getNodeId(el.parentElement!)\r\n if (parentId === null) {\r\n continue\r\n }\r\n\r\n this.pendingAdds.push({ parentId, node: serialized })\r\n }\r\n\r\n for (const node of m.removedNodes) {\r\n if (node.nodeType !== Node.ELEMENT_NODE) {\r\n continue\r\n }\r\n const id = getNodeId(node)\r\n if (id !== null) {\r\n this.pendingRemoves.push(id)\r\n removeNodeId(id)\r\n }\r\n }\r\n\r\n if (m.type === 'characterData' && m.target.parentElement) {\r\n const parentId = getNodeId(m.target.parentElement)\r\n if (parentId !== null) {\r\n const text = m.target.textContent?.trim().slice(0, 200) || ''\r\n this.pendingTextChanges.push({ id: parentId, text })\r\n }\r\n }\r\n\r\n if (m.type === 'attributes' && m.attributeName) {\r\n const id = getNodeId(m.target)\r\n if (id !== null) {\r\n const value = (m.target as HTMLElement).getAttribute(m.attributeName)\r\n this.pendingAttrChanges.push({\r\n id,\r\n attr: m.attributeName,\r\n value: value?.slice(0, 200) ?? null,\r\n })\r\n }\r\n }\r\n }\r\n\r\n if (\r\n this.pendingAdds.length > 0\r\n || this.pendingRemoves.length > 0\r\n || this.pendingTextChanges.length > 0\r\n || this.pendingAttrChanges.length > 0\r\n ) {\r\n this.scheduleFlush()\r\n }\r\n }\r\n}\r\n","import type { Config, SnapshotEvent, Tracker } from '../types'\r\nimport type { Sender } from '../utils/sender'\r\nimport { resetIds, serializeTree } from '../dom/serializer'\r\nimport { getViewport } from '../dom/viewport'\r\n\r\nexport class SnapshotTracker implements Tracker {\r\n private lastUrl = ''\r\n private urlPollTimer: ReturnType<typeof setInterval> | null = null\r\n private checkpointTimer: ReturnType<typeof setInterval> | null = null\r\n private hasMutations = false\r\n\r\n constructor(\r\n private config: Config,\r\n private sender: Sender,\r\n ) {}\r\n\r\n start() {\r\n this.lastUrl = location.href\r\n this.recordSnapshot()\r\n\r\n this.urlPollTimer = setInterval(() => this.pollUrl(), 500)\r\n this.checkpointTimer = setInterval(() => this.checkpoint(), this.config.checkpointInterval)\r\n }\r\n\r\n stop() {\r\n if (this.urlPollTimer) {\r\n clearInterval(this.urlPollTimer)\r\n }\r\n if (this.checkpointTimer) {\r\n clearInterval(this.checkpointTimer)\r\n }\r\n }\r\n\r\n beforeUnload() {\r\n if (this.hasMutations) {\r\n this.recordSnapshot()\r\n }\r\n }\r\n\r\n markMutation() {\r\n this.hasMutations = true\r\n }\r\n\r\n private recordSnapshot() {\r\n resetIds()\r\n\r\n const snapshot: SnapshotEvent = {\r\n event: 'snapshot',\r\n timestamp: new Date().toISOString(),\r\n url: location.href,\r\n title: document.title,\r\n tree: serializeTree(document.body),\r\n viewport: getViewport(),\r\n }\r\n\r\n if (this.config.debug) {\r\n console.log(`[dataclient] snapshot: ${location.href} (${snapshot.tree.length} nodes)`)\r\n }\r\n\r\n this.sender.add(snapshot)\r\n this.sender.flush()\r\n this.hasMutations = false\r\n }\r\n\r\n private pollUrl() {\r\n if (location.href !== this.lastUrl) {\r\n const prev = this.lastUrl\r\n this.lastUrl = location.href\r\n if (prev) {\r\n if (this.config.debug) {\r\n console.log(`[dataclient] URL changed: ${prev} → ${location.href}`)\r\n }\r\n this.recordSnapshot()\r\n }\r\n }\r\n }\r\n\r\n private checkpoint() {\r\n if (this.hasMutations) {\r\n if (this.config.debug) {\r\n console.log('[dataclient] checkpoint snapshot')\r\n }\r\n this.recordSnapshot()\r\n }\r\n }\r\n}\r\n","export function getDeviceId(key: string): string {\r\n let id = localStorage.getItem(key)\r\n if (!id) {\r\n id = generateId()\r\n localStorage.setItem(key, id)\r\n }\r\n return id\r\n}\r\n\r\nexport function generateId(): string {\r\n const ts = Date.now().toString(36)\r\n const rand = Math.random().toString(36).substring(2, 12)\r\n return `${ts}-${rand}`\r\n}\r\n","import type { SceneBatch, SceneEvent } from '../types'\r\n\r\nconst STORAGE_KEY = 'sc2_pending'\r\nconst MAX_RETRIES = 2\r\n\r\nexport class Sender {\r\n private queue: SceneEvent[] = []\r\n private timer: ReturnType<typeof setInterval> | null = null\r\n private isFlushing = false\r\n\r\n constructor(\r\n private endpoint: string,\r\n private apiKey: string,\r\n private batchSize: number,\r\n private sessionId: string,\r\n private deviceId: string,\r\n flushInterval: number,\r\n ) {\r\n this.restoreFromStorage()\r\n this.timer = setInterval(() => this.flush(), flushInterval)\r\n\r\n document.addEventListener('visibilitychange', () => {\r\n if (document.visibilityState === 'hidden') {\r\n this.flush()\r\n }\r\n })\r\n }\r\n\r\n private restoreFromStorage() {\r\n try {\r\n localStorage.removeItem(STORAGE_KEY)\r\n }\r\n catch {}\r\n }\r\n\r\n private saveToStorage() {\r\n if (this.queue.length === 0) {\r\n return\r\n }\r\n try {\r\n localStorage.setItem(STORAGE_KEY, JSON.stringify(this.queue))\r\n }\r\n catch {}\r\n }\r\n\r\n add(event: SceneEvent) {\r\n this.queue.push(event)\r\n if (this.queue.length >= this.batchSize) {\r\n this.flush()\r\n }\r\n }\r\n\r\n async flush() {\r\n if (this.queue.length === 0 || this.isFlushing) {\r\n return\r\n }\r\n\r\n this.isFlushing = true\r\n\r\n const events = this.queue.splice(0)\r\n const batch = this.buildBatch(events)\r\n const json = JSON.stringify(batch)\r\n const url = `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`\r\n\r\n const success = await this.send(json, url)\r\n\r\n if (!success) {\r\n this.queue.unshift(...events)\r\n this.saveToStorage()\r\n }\r\n\r\n this.isFlushing = false\r\n }\r\n\r\n flushSync() {\r\n if (this.queue.length === 0) {\r\n return\r\n }\r\n\r\n const events = this.queue.splice(0)\r\n const batch = this.buildBatch(events)\r\n const json = JSON.stringify(batch)\r\n const url = `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`\r\n\r\n const blob = new Blob([json], { type: 'application/json' })\r\n const sent = navigator.sendBeacon(url, blob)\r\n\r\n if (!sent) {\r\n this.queue.unshift(...events)\r\n this.saveToStorage()\r\n }\r\n }\r\n\r\n private buildBatch(events: SceneEvent[]): SceneBatch {\r\n return {\r\n session_id: this.sessionId,\r\n device_id: this.deviceId,\r\n events,\r\n sent_at: new Date().toISOString(),\r\n page_url: location.href,\r\n user_agent: navigator.userAgent,\r\n screen: {\r\n width: screen.width,\r\n height: screen.height,\r\n viewport_width: window.innerWidth,\r\n viewport_height: window.innerHeight,\r\n },\r\n }\r\n }\r\n\r\n private async send(json: string, url: string): Promise<boolean> {\r\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\r\n try {\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: json,\r\n keepalive: true,\r\n })\r\n if (response.ok) {\r\n return true\r\n }\r\n }\r\n catch {}\r\n\r\n if (attempt < MAX_RETRIES) {\r\n await new Promise(r => setTimeout(r, (attempt + 1) * 200))\r\n }\r\n }\r\n return false\r\n }\r\n\r\n destroy() {\r\n if (this.timer) {\r\n clearInterval(this.timer)\r\n }\r\n this.flushSync()\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/dom/mask.ts","../src/dom/icon.ts","../src/dom/serializer.ts","../src/dom/viewport.ts","../src/trackers/rrweb.ts","../src/trackers/action.ts","../src/trackers/mutation.ts","../src/trackers/snapshot.ts","../src/utils/identity.ts","../src/utils/sender.ts"],"sourcesContent":["import type { Config, Tracker } from './types'\nimport { ActionTracker } from './trackers/action'\nimport { MutationTracker } from './trackers/mutation'\nimport { RrwebTracker } from './trackers/rrweb'\nimport { SnapshotTracker } from './trackers/snapshot'\nimport { generateId, getDeviceId } from './utils/identity'\nimport { Sender } from './utils/sender'\n\nexport type * from './types'\n\nconst defaults: Config = {\n endpoint: 'https://my.tamsense.com/api/scenes2',\n debug: false,\n batchSize: 5,\n flushInterval: 5000,\n checkpointInterval: 30000,\n mutationDebounce: 200,\n inputDebounce: 1000,\n sessionIdKey: 'sc2_sid',\n deviceIdKey: 'sc2_did',\n apiKey: '',\n}\n\nexport class DataClient {\n private sender: Sender\n private trackers: Tracker[] = []\n private config: Config\n\n constructor(options?: Partial<Config>) {\n this.config = { ...defaults, ...options }\n\n const sessionId = generateId()\n const deviceId = getDeviceId(this.config.deviceIdKey)\n\n this.sender = new Sender(\n this.config.endpoint,\n this.config.apiKey,\n this.config.batchSize,\n sessionId,\n deviceId,\n this.config.flushInterval,\n )\n\n const snapshotTracker = new SnapshotTracker(this.config, this.sender)\n const mutationTracker = new MutationTracker(this.config, this.sender, () => snapshotTracker.markMutation())\n const actionTracker = new ActionTracker(this.config, this.sender)\n const rrwebTracker = new RrwebTracker(this.config, this.sender)\n\n this.trackers = [snapshotTracker, mutationTracker, actionTracker, rrwebTracker]\n this.trackers.forEach(t => t.start())\n\n const onLeave = () => {\n this.trackers.forEach(t => t.beforeUnload?.())\n this.sender.flushSync()\n }\n\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') onLeave()\n })\n window.addEventListener('pagehide', onLeave)\n }\n\n setUser(userId: string) {\n this.sender.add({ event: 'identify', timestamp: new Date().toISOString(), user_id: userId })\n }\n\n excludeSession(reason = '') {\n this.sender.add({ event: 'exclude', timestamp: new Date().toISOString(), reason })\n this.destroy()\n }\n\n destroy() {\n this.trackers.forEach(t => t.stop())\n this.trackers = []\n this.sender.destroy()\n }\n}\n","export const MASK_ATTR = 'dataclient-mask'\r\nexport const MASK_SELECTOR = `[${MASK_ATTR}]`\r\n\r\nexport const SKIP_TAGS = new Set(['script', 'style', 'noscript', 'svg', 'link', 'meta', 'head'])\r\n\r\nexport const RECORD_ATTRS = new Set([\r\n 'class',\r\n 'role',\r\n 'href',\r\n 'type',\r\n 'placeholder',\r\n 'disabled',\r\n 'hidden',\r\n 'aria-label',\r\n 'aria-selected',\r\n 'aria-expanded',\r\n 'aria-invalid',\r\n 'aria-busy',\r\n 'aria-checked',\r\n 'aria-hidden',\r\n 'value',\r\n 'name',\r\n 'id',\r\n 'for',\r\n 'target',\r\n 'data-state',\r\n MASK_ATTR,\r\n])\r\n\r\nexport const WATCH_ATTRS = [\r\n 'class',\r\n 'role',\r\n 'href',\r\n 'disabled',\r\n 'hidden',\r\n 'placeholder',\r\n 'aria-label',\r\n 'aria-selected',\r\n 'aria-expanded',\r\n 'aria-invalid',\r\n 'aria-busy',\r\n 'aria-checked',\r\n 'aria-hidden',\r\n 'data-state',\r\n 'value',\r\n]\r\n\r\nexport const TEXT_INPUT_TYPES = new Set(['text', 'email', 'password', 'search', 'tel', 'url', 'number'])\r\n\r\nexport const INTERACTIVE_TAGS = new Set(['button', 'a', 'input', 'select', 'textarea'])\r\nexport const INTERACTIVE_ROLES = new Set(['button', 'link', 'tab', 'menuitem', 'checkbox', 'radio', 'switch', 'option'])\r\n","import { MASK_SELECTOR } from '../constants'\r\n\r\nexport function isMasked(el: Element | Node | null): boolean {\r\n if (!el)\r\n return false\r\n const element = el.nodeType === Node.ELEMENT_NODE\r\n ? el as Element\r\n : el.parentElement\r\n return !!element?.closest(MASK_SELECTOR)\r\n}\r\n\r\nexport function maskText(text: string): string {\r\n if (!text) {\r\n return text\r\n }\r\n const visible = Math.max(1, Math.ceil(text.length * 0.2))\r\n return text.slice(0, visible) + '*'.repeat(text.length - visible)\r\n}\r\n\r\nexport function maskValue(el: Element | null, value: string): string {\r\n return isMasked(el) ? maskText(value) : value\r\n}\r\n","const ICON_CLASS_PATTERNS = [\r\n /\\bfa-([a-z0-9-]+)\\b/,\r\n /\\bmdi-([a-z0-9-]+)\\b/,\r\n /\\bbi-([a-z0-9-]+)\\b/,\r\n /\\bicon-([a-z0-9-]+)\\b/,\r\n /\\blucide-([a-z0-9-]+)\\b/,\r\n /\\bri-([a-z0-9-]+)\\b/,\r\n /\\btabler-icon-([a-z0-9-]+)\\b/,\r\n /\\bi-[a-z0-9-]+[:/]([a-z0-9-]+)\\b/,\r\n]\r\n\r\nconst MATERIAL_CLASS_PATTERN = /\\b(?:material-icons|material-symbols-[a-z]+)\\b/\r\nconst SVG_USE_HREF_PATTERN = /#(.+)/\r\n\r\nconst MAX_ICON_IMG_SIZE = 48\r\n\r\nexport function getElementIcon(el: HTMLElement): string | null {\r\n if (!el) {\r\n return null\r\n }\r\n\r\n const direct = detectIcon(el)\r\n if (direct) {\r\n return direct\r\n }\r\n\r\n for (const child of el.children) {\r\n const icon = detectIcon(child as HTMLElement)\r\n if (icon) {\r\n return icon\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\nfunction detectIcon(el: HTMLElement): string | null {\r\n if (!el?.tagName) {\r\n return null\r\n }\r\n\r\n const tag = el.tagName.toLowerCase()\r\n\r\n if (tag === 'svg') {\r\n return detectSvgIcon(el)\r\n }\r\n\r\n if (tag === 'i' || tag === 'span' || el.classList.contains('iconify')) {\r\n return detectFontIcon(el)\r\n }\r\n\r\n if (tag === 'img') {\r\n return detectImgIcon(el as HTMLImageElement)\r\n }\r\n\r\n const svg = el.querySelector('svg')\r\n if (svg) {\r\n return detectSvgIcon(svg)\r\n }\r\n\r\n return null\r\n}\r\n\r\nfunction detectSvgIcon(svg: Element): string | null {\r\n const lucide = svg.getAttribute('data-lucide')\r\n if (lucide) {\r\n return lucide\r\n }\r\n\r\n const dataIcon = svg.getAttribute('data-icon')\r\n if (dataIcon) {\r\n return dataIcon\r\n }\r\n\r\n const use = svg.querySelector('use')\r\n if (use) {\r\n const href = use.getAttribute('href') || use.getAttribute('xlink:href')\r\n if (href) {\r\n const match = href.match(SVG_USE_HREF_PATTERN)\r\n if (match) {\r\n return match[1]\r\n }\r\n }\r\n }\r\n\r\n const ariaLabel = svg.getAttribute('aria-label')\r\n if (ariaLabel) {\r\n return ariaLabel\r\n }\r\n\r\n const cls = typeof svg.className === 'string' ? svg.className : svg.getAttribute('class') || ''\r\n const fromClass = extractIconNameFromClass(cls)\r\n if (fromClass) {\r\n return fromClass\r\n }\r\n\r\n return null\r\n}\r\n\r\nfunction detectFontIcon(el: HTMLElement): string | null {\r\n const cls = typeof el.className === 'string' ? el.className : el.getAttribute('class') || ''\r\n\r\n if (MATERIAL_CLASS_PATTERN.test(cls)) {\r\n const text = el.textContent?.trim()\r\n if (text) {\r\n return text\r\n }\r\n }\r\n\r\n return extractIconNameFromClass(cls)\r\n}\r\n\r\nfunction detectImgIcon(img: HTMLImageElement): string | null {\r\n if (img.width > MAX_ICON_IMG_SIZE || img.height > MAX_ICON_IMG_SIZE) {\r\n return null\r\n }\r\n\r\n if (img.naturalWidth > MAX_ICON_IMG_SIZE || img.naturalHeight > MAX_ICON_IMG_SIZE) {\r\n return null\r\n }\r\n\r\n const alt = img.alt?.trim()\r\n if (alt) {\r\n return alt\r\n }\r\n\r\n const src = img.getAttribute('src')\r\n if (src) {\r\n const filename = src.split('/').pop()?.split('?')[0]?.split('.')[0]\r\n if (filename) {\r\n return filename\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\nfunction extractIconNameFromClass(cls: string): string | null {\r\n for (const pattern of ICON_CLASS_PATTERNS) {\r\n const match = cls.match(pattern)\r\n if (match) {\r\n return match[1]\r\n }\r\n }\r\n return null\r\n}\r\n","import type { SerializedNode } from '../types'\r\nimport { RECORD_ATTRS, SKIP_TAGS } from '../constants'\r\nimport { getElementIcon } from './icon'\r\nimport { isMasked, maskText } from './mask'\r\n\r\nlet nextId = 1\r\nconst nodeToId = new WeakMap<Node, number>()\r\nconst idToNode = new Map<number, Node>()\r\n\r\nexport function resetIds() {\r\n nextId = 1\r\n idToNode.clear()\r\n}\r\n\r\nexport function assignId(node: Node): number {\r\n const existing = nodeToId.get(node)\r\n if (existing) {\r\n return existing\r\n }\r\n const id = nextId++\r\n nodeToId.set(node, id)\r\n idToNode.set(id, node)\r\n return id\r\n}\r\n\r\nexport function getNodeId(node: Node): number | null {\r\n return nodeToId.get(node) ?? null\r\n}\r\n\r\nexport function getNodeById(id: number): Node | null {\r\n return idToNode.get(id) ?? null\r\n}\r\n\r\nexport function removeNodeId(id: number) {\r\n const node = idToNode.get(id)\r\n if (node) {\r\n nodeToId.delete(node)\r\n idToNode.delete(id)\r\n }\r\n}\r\n\r\nfunction isVisible(el: HTMLElement): boolean {\r\n if (el.hidden)\r\n return false\r\n if (el.getAttribute('aria-hidden') === 'true')\r\n return false\r\n if (!el.offsetParent && el.tagName !== 'BODY' && getComputedStyle(el).position !== 'fixed') {\r\n return false\r\n }\r\n return true\r\n}\r\n\r\nfunction getDirectText(el: HTMLElement): string {\r\n let text = ''\r\n for (const child of el.childNodes) {\r\n if (child.nodeType === Node.TEXT_NODE) {\r\n const t = child.textContent?.trim()\r\n if (t) {\r\n text += (text ? ' ' : '') + t\r\n }\r\n }\r\n }\r\n const truncated = text.slice(0, 200)\r\n return isMasked(el) ? maskText(truncated) : truncated\r\n}\r\n\r\nfunction getAttrs(el: HTMLElement): Record<string, string> | undefined {\r\n const attrs: Record<string, string> = {}\r\n let hasAttrs = false\r\n const masked = isMasked(el)\r\n\r\n for (const name of RECORD_ATTRS) {\r\n const value = el.getAttribute(name)\r\n if (value !== null && value !== '') {\r\n let v = value.slice(0, 200)\r\n if (masked && (name === 'value' || name === 'placeholder')) {\r\n v = maskText(v)\r\n }\r\n attrs[name] = v\r\n hasAttrs = true\r\n }\r\n }\r\n\r\n return hasAttrs ? attrs : undefined\r\n}\r\n\r\nfunction getRect(el: HTMLElement): SerializedNode['rect'] {\r\n const r = el.getBoundingClientRect()\r\n if (r.width === 0 && r.height === 0) {\r\n return undefined\r\n }\r\n return {\r\n x: Math.round(r.left + window.scrollX),\r\n y: Math.round(r.top + window.scrollY),\r\n w: Math.round(r.width),\r\n h: Math.round(r.height),\r\n }\r\n}\r\n\r\nexport function serializeNode(el: HTMLElement): SerializedNode | null {\r\n const tag = el.tagName?.toLowerCase()\r\n if (!tag || SKIP_TAGS.has(tag)) {\r\n return null\r\n }\r\n if (!isVisible(el)) {\r\n return null\r\n }\r\n\r\n const id = assignId(el)\r\n const text = getDirectText(el)\r\n const icon = getElementIcon(el)\r\n const attrs = getAttrs(el)\r\n const rect = getRect(el)\r\n\r\n const children: number[] = []\r\n for (const child of el.children) {\r\n const serialized = serializeNode(child as HTMLElement)\r\n if (serialized) {\r\n children.push(serialized.id)\r\n }\r\n }\r\n\r\n const node: SerializedNode = { id, tag }\r\n if (text) {\r\n node.text = text\r\n }\r\n if (icon) {\r\n node.icon = icon\r\n }\r\n if (attrs) {\r\n node.attrs = attrs\r\n }\r\n if (rect) {\r\n node.rect = rect\r\n }\r\n if (children.length > 0) {\r\n node.children = children\r\n }\r\n\r\n return node\r\n}\r\n\r\nexport function serializeTree(root: HTMLElement): SerializedNode[] {\r\n const nodes: SerializedNode[] = []\r\n\r\n function walk(el: HTMLElement) {\r\n const node = serializeNode(el)\r\n if (!node) {\r\n return\r\n }\r\n nodes.push(node)\r\n for (const child of el.children) {\r\n walk(child as HTMLElement)\r\n }\r\n }\r\n\r\n walk(root)\r\n return nodes\r\n}\r\n","import type { Viewport } from '../types'\r\n\r\nexport function getViewport(): Viewport {\r\n return {\r\n scrollX: Math.round(window.scrollX),\r\n scrollY: Math.round(window.scrollY),\r\n width: window.innerWidth,\r\n height: window.innerHeight,\r\n }\r\n}\r\n","import type { Config, RrwebEvent, Tracker } from '../types'\r\nimport type { Sender } from '../utils/sender'\r\nimport { record } from 'rrweb'\r\nimport { MASK_SELECTOR } from '../constants'\r\n\r\nexport class RrwebTracker implements Tracker {\r\n private stopFn: (() => void) | null = null\r\n\r\n constructor(\r\n private config: Config,\r\n private sender: Sender,\r\n ) {}\r\n\r\n start() {\r\n this.stopFn = record({\r\n emit: (event) => {\r\n const rrwebEvent: RrwebEvent = {\r\n event: 'rrweb',\r\n timestamp: new Date().toISOString(),\r\n rrwebEvent: event,\r\n }\r\n this.sender.add(rrwebEvent)\r\n },\r\n recordCrossOriginIframes: false,\r\n recordCanvas: false,\r\n maskTextSelector: MASK_SELECTOR,\r\n maskInputOptions: { password: true },\r\n maskTextFn: text => '*'.repeat(text.length),\r\n sampling: {\r\n mousemove: false,\r\n mouseInteraction: true,\r\n scroll: 500,\r\n input: 'last',\r\n },\r\n }) ?? null\r\n\r\n if (this.config.debug)\r\n console.log('[dataclient] rrweb recording started')\r\n }\r\n\r\n stop() {\r\n if (this.stopFn) {\r\n this.stopFn()\r\n this.stopFn = null\r\n }\r\n }\r\n\r\n static addMarker(tag: string, payload: unknown) {\r\n record.addCustomEvent(tag, payload)\r\n }\r\n}\r\n","import type { ActionEvent, Config, Tracker } from '../types'\r\nimport type { Sender } from '../utils/sender'\r\nimport { INTERACTIVE_ROLES, INTERACTIVE_TAGS, TEXT_INPUT_TYPES } from '../constants'\r\nimport { isMasked, maskText } from '../dom/mask'\r\nimport { getNodeId } from '../dom/serializer'\r\nimport { getViewport } from '../dom/viewport'\r\nimport { RrwebTracker } from './rrweb'\r\n\r\nexport class ActionTracker implements Tracker {\r\n private inputTimers = new WeakMap<HTMLElement, ReturnType<typeof setTimeout>>()\r\n private pendingInputs = new Set<HTMLElement>()\r\n\r\n private handleClick = (e: Event) => this.onClick(e)\r\n private handleInput = (e: Event) => this.onInput(e)\r\n private handleChange = (e: Event) => this.onChange(e)\r\n\r\n constructor(\r\n private config: Config,\r\n private sender: Sender,\r\n ) {}\r\n\r\n start() {\r\n document.addEventListener('click', this.handleClick, true)\r\n document.addEventListener('input', this.handleInput, true)\r\n document.addEventListener('change', this.handleChange, true)\r\n }\r\n\r\n stop() {\r\n document.removeEventListener('click', this.handleClick, true)\r\n document.removeEventListener('input', this.handleInput, true)\r\n document.removeEventListener('change', this.handleChange, true)\r\n }\r\n\r\n beforeUnload() {\r\n for (const el of this.pendingInputs) {\r\n const timer = this.inputTimers.get(el)\r\n if (timer) {\r\n clearTimeout(timer)\r\n }\r\n this.inputTimers.delete(el)\r\n this.recordInput(el)\r\n }\r\n this.pendingInputs.clear()\r\n }\r\n\r\n private onClick(e: Event) {\r\n const raw = e.target as HTMLElement\r\n if (!raw) {\r\n return\r\n }\r\n\r\n const tag = raw.tagName?.toLowerCase()\r\n if (tag === 'body' || tag === 'html') {\r\n return\r\n }\r\n\r\n const el = this.findMeaningfulElement(raw)\r\n const info = this.getElementInfo(el)\r\n\r\n const action: ActionEvent = {\r\n event: 'action',\r\n timestamp: new Date().toISOString(),\r\n type: 'click',\r\n targetId: info.targetId,\r\n tag: info.tag,\r\n text: info.text,\r\n url: location.href,\r\n state: info.state,\r\n viewport: getViewport(),\r\n }\r\n\r\n if (this.config.debug) {\r\n console.log(`[dataclient] click: ${info.tag} \"${info.text}\"`)\r\n }\r\n\r\n this.sender.add(action)\r\n RrwebTracker.addMarker('click', { tag: info.tag, text: info.text, label: `Click: ${info.text || info.tag}` })\r\n }\r\n\r\n private onInput(e: Event) {\r\n const target = e.target as HTMLElement\r\n if (!target) {\r\n return\r\n }\r\n\r\n const tag = target.tagName?.toLowerCase()\r\n if (tag === 'input') {\r\n const type = (target as HTMLInputElement).type?.toLowerCase() || 'text'\r\n if (!TEXT_INPUT_TYPES.has(type)) {\r\n return\r\n }\r\n }\r\n else if (tag !== 'textarea') {\r\n return\r\n }\r\n\r\n this.pendingInputs.add(target)\r\n\r\n const prev = this.inputTimers.get(target)\r\n if (prev) {\r\n clearTimeout(prev)\r\n }\r\n\r\n this.inputTimers.set(target, setTimeout(() => {\r\n this.inputTimers.delete(target)\r\n this.pendingInputs.delete(target)\r\n this.recordInput(target)\r\n }, this.config.inputDebounce))\r\n }\r\n\r\n private recordInput(target: HTMLElement) {\r\n const rawValue = (target as HTMLInputElement).value || ''\r\n if (!rawValue) {\r\n return\r\n }\r\n\r\n const info = this.getElementInfo(target)\r\n const type = (target as HTMLInputElement).type?.toLowerCase() || ''\r\n const value = (info.masked || type === 'password') ? maskText(rawValue) : rawValue\r\n\r\n const action: ActionEvent = {\r\n event: 'action',\r\n timestamp: new Date().toISOString(),\r\n type: 'input',\r\n targetId: info.targetId,\r\n tag: info.tag,\r\n text: info.text,\r\n url: location.href,\r\n value,\r\n length: rawValue.length,\r\n viewport: getViewport(),\r\n }\r\n\r\n if (this.config.debug) {\r\n console.log(`[dataclient] input: ${info.tag} \"${info.text}\" → ${value.length} chars`)\r\n }\r\n\r\n this.sender.add(action)\r\n RrwebTracker.addMarker('input', { tag: info.tag, text: info.text, label: `Input: ${info.text || info.tag}` })\r\n }\r\n\r\n private onChange(e: Event) {\r\n const target = e.target as HTMLElement\r\n if (!target) {\r\n return\r\n }\r\n\r\n const info = this.getElementInfo(target)\r\n const tag = target.tagName?.toLowerCase()\r\n\r\n const action: ActionEvent = {\r\n event: 'action',\r\n timestamp: new Date().toISOString(),\r\n type: 'change',\r\n targetId: info.targetId,\r\n tag: info.tag,\r\n text: info.text,\r\n url: location.href,\r\n viewport: getViewport(),\r\n }\r\n\r\n if (tag === 'select') {\r\n const selectValue = (target as HTMLSelectElement).value\r\n action.value = info.masked ? maskText(selectValue) : selectValue\r\n }\r\n else if (tag === 'input') {\r\n const type = (target as HTMLInputElement).type\r\n if (type === 'checkbox' || type === 'radio') {\r\n action.checked = (target as HTMLInputElement).checked\r\n }\r\n }\r\n\r\n if (this.config.debug) {\r\n console.log(`[dataclient] change: ${info.tag} \"${info.text}\"`)\r\n }\r\n\r\n this.sender.add(action)\r\n RrwebTracker.addMarker('change', { tag: info.tag, text: info.text, label: `Change: ${info.text || info.tag}` })\r\n }\r\n\r\n private getElementInfo(el: HTMLElement) {\r\n const tag = el.tagName?.toLowerCase() || ''\r\n let text = ''\r\n\r\n if (tag === 'input' || tag === 'textarea') {\r\n text = (el as HTMLInputElement).placeholder || el.getAttribute('aria-label') || ''\r\n }\r\n else {\r\n for (const child of el.childNodes) {\r\n if (child.nodeType === Node.TEXT_NODE) {\r\n const t = child.textContent?.trim()\r\n if (t) {\r\n text += (text ? ' ' : '') + t\r\n }\r\n }\r\n }\r\n if (!text) {\r\n text = el.textContent?.trim().slice(0, 100) || ''\r\n }\r\n }\r\n\r\n const masked = isMasked(el)\r\n const finalText = masked ? maskText(text) : text\r\n\r\n return {\r\n tag,\r\n text: finalText.slice(0, 100),\r\n targetId: getNodeId(el),\r\n state: (el as HTMLButtonElement).disabled ? 'disabled' : 'enabled',\r\n masked,\r\n }\r\n }\r\n\r\n private findMeaningfulElement(el: HTMLElement): HTMLElement {\r\n let current: HTMLElement | null = el\r\n while (current && current !== document.body) {\r\n if (INTERACTIVE_TAGS.has(current.tagName?.toLowerCase())) {\r\n return current\r\n }\r\n const role = current.getAttribute('role')\r\n if (role && INTERACTIVE_ROLES.has(role)) {\r\n return current\r\n }\r\n current = current.parentElement\r\n }\r\n return el\r\n }\r\n}\r\n","import type { AttrChange, Config, MutationAdd, MutationEvent, TextChange, Tracker } from '../types'\r\nimport type { Sender } from '../utils/sender'\r\nimport { WATCH_ATTRS } from '../constants'\r\nimport { getNodeId, removeNodeId, serializeNode } from '../dom/serializer'\r\n\r\nexport class MutationTracker implements Tracker {\r\n private observer: MutationObserver | null = null\r\n private debounceTimer: ReturnType<typeof setTimeout> | null = null\r\n\r\n private pendingAdds: MutationAdd[] = []\r\n private pendingRemoves: number[] = []\r\n private pendingTextChanges: TextChange[] = []\r\n private pendingAttrChanges: AttrChange[] = []\r\n\r\n constructor(\r\n private config: Config,\r\n private sender: Sender,\r\n private onMutation: () => void,\r\n ) {}\r\n\r\n start() {\r\n this.observer = new MutationObserver(mutations => this.handleMutations(mutations))\r\n this.observer.observe(document.body, {\r\n childList: true,\r\n subtree: true,\r\n characterData: true,\r\n attributes: true,\r\n attributeFilter: WATCH_ATTRS,\r\n })\r\n }\r\n\r\n stop() {\r\n this.observer?.disconnect()\r\n this.observer = null\r\n if (this.debounceTimer) {\r\n clearTimeout(this.debounceTimer)\r\n }\r\n }\r\n\r\n beforeUnload() {\r\n if (this.debounceTimer) {\r\n clearTimeout(this.debounceTimer)\r\n this.flush()\r\n }\r\n }\r\n\r\n private flush() {\r\n this.debounceTimer = null\r\n\r\n if (\r\n this.pendingAdds.length === 0\r\n && this.pendingRemoves.length === 0\r\n && this.pendingTextChanges.length === 0\r\n && this.pendingAttrChanges.length === 0\r\n ) {\r\n return\r\n }\r\n\r\n const mutation: MutationEvent = {\r\n event: 'mutation',\r\n timestamp: new Date().toISOString(),\r\n adds: this.pendingAdds,\r\n removes: this.pendingRemoves,\r\n text_changes: this.pendingTextChanges,\r\n attr_changes: this.pendingAttrChanges,\r\n }\r\n\r\n if (this.config.debug) {\r\n console.log(`[dataclient] mutation: +${this.pendingAdds.length} -${this.pendingRemoves.length} text:${this.pendingTextChanges.length} attr:${this.pendingAttrChanges.length}`)\r\n }\r\n\r\n this.sender.add(mutation)\r\n this.onMutation()\r\n\r\n this.pendingAdds = []\r\n this.pendingRemoves = []\r\n this.pendingTextChanges = []\r\n this.pendingAttrChanges = []\r\n }\r\n\r\n private scheduleFlush() {\r\n if (this.debounceTimer) {\r\n clearTimeout(this.debounceTimer)\r\n }\r\n this.debounceTimer = setTimeout(() => this.flush(), this.config.mutationDebounce)\r\n }\r\n\r\n private handleMutations(mutations: MutationRecord[]) {\r\n for (const m of mutations) {\r\n for (const node of m.addedNodes) {\r\n if (node.nodeType !== Node.ELEMENT_NODE) {\r\n continue\r\n }\r\n const el = node as HTMLElement\r\n const serialized = serializeNode(el)\r\n if (!serialized) {\r\n continue\r\n }\r\n\r\n const parentId = getNodeId(el.parentElement!)\r\n if (parentId === null) {\r\n continue\r\n }\r\n\r\n this.pendingAdds.push({ parentId, node: serialized })\r\n }\r\n\r\n for (const node of m.removedNodes) {\r\n if (node.nodeType !== Node.ELEMENT_NODE) {\r\n continue\r\n }\r\n const id = getNodeId(node)\r\n if (id !== null) {\r\n this.pendingRemoves.push(id)\r\n removeNodeId(id)\r\n }\r\n }\r\n\r\n if (m.type === 'characterData' && m.target.parentElement) {\r\n const parentId = getNodeId(m.target.parentElement)\r\n if (parentId !== null) {\r\n const text = m.target.textContent?.trim().slice(0, 200) || ''\r\n this.pendingTextChanges.push({ id: parentId, text })\r\n }\r\n }\r\n\r\n if (m.type === 'attributes' && m.attributeName) {\r\n const id = getNodeId(m.target)\r\n if (id !== null) {\r\n const value = (m.target as HTMLElement).getAttribute(m.attributeName)\r\n this.pendingAttrChanges.push({\r\n id,\r\n attr: m.attributeName,\r\n value: value?.slice(0, 200) ?? null,\r\n })\r\n }\r\n }\r\n }\r\n\r\n if (\r\n this.pendingAdds.length > 0\r\n || this.pendingRemoves.length > 0\r\n || this.pendingTextChanges.length > 0\r\n || this.pendingAttrChanges.length > 0\r\n ) {\r\n this.scheduleFlush()\r\n }\r\n }\r\n}\r\n","import type { Config, SnapshotEvent, Tracker } from '../types'\r\nimport type { Sender } from '../utils/sender'\r\nimport { resetIds, serializeTree } from '../dom/serializer'\r\nimport { getViewport } from '../dom/viewport'\r\n\r\nexport class SnapshotTracker implements Tracker {\r\n private lastUrl = ''\r\n private urlPollTimer: ReturnType<typeof setInterval> | null = null\r\n private checkpointTimer: ReturnType<typeof setInterval> | null = null\r\n private hasMutations = false\r\n\r\n constructor(\r\n private config: Config,\r\n private sender: Sender,\r\n ) {}\r\n\r\n start() {\r\n this.lastUrl = location.href\r\n this.recordSnapshot()\r\n\r\n this.urlPollTimer = setInterval(() => this.pollUrl(), 500)\r\n this.checkpointTimer = setInterval(() => this.checkpoint(), this.config.checkpointInterval)\r\n }\r\n\r\n stop() {\r\n if (this.urlPollTimer) {\r\n clearInterval(this.urlPollTimer)\r\n }\r\n if (this.checkpointTimer) {\r\n clearInterval(this.checkpointTimer)\r\n }\r\n }\r\n\r\n beforeUnload() {\r\n if (this.hasMutations) {\r\n this.recordSnapshot()\r\n }\r\n }\r\n\r\n markMutation() {\r\n this.hasMutations = true\r\n }\r\n\r\n private recordSnapshot() {\r\n resetIds()\r\n\r\n const snapshot: SnapshotEvent = {\r\n event: 'snapshot',\r\n timestamp: new Date().toISOString(),\r\n url: location.href,\r\n title: document.title,\r\n tree: serializeTree(document.body),\r\n viewport: getViewport(),\r\n }\r\n\r\n if (this.config.debug) {\r\n console.log(`[dataclient] snapshot: ${location.href} (${snapshot.tree.length} nodes)`)\r\n }\r\n\r\n this.sender.add(snapshot)\r\n this.sender.flush()\r\n this.hasMutations = false\r\n }\r\n\r\n private pollUrl() {\r\n if (location.href !== this.lastUrl) {\r\n const prev = this.lastUrl\r\n this.lastUrl = location.href\r\n if (prev) {\r\n if (this.config.debug) {\r\n console.log(`[dataclient] URL changed: ${prev} → ${location.href}`)\r\n }\r\n this.recordSnapshot()\r\n }\r\n }\r\n }\r\n\r\n private checkpoint() {\r\n if (this.hasMutations) {\r\n if (this.config.debug) {\r\n console.log('[dataclient] checkpoint snapshot')\r\n }\r\n this.recordSnapshot()\r\n }\r\n }\r\n}\r\n","export function getDeviceId(key: string): string {\r\n let id = localStorage.getItem(key)\r\n if (!id) {\r\n id = generateId()\r\n localStorage.setItem(key, id)\r\n }\r\n return id\r\n}\r\n\r\nexport function generateId(): string {\r\n const ts = Date.now().toString(36)\r\n const rand = Math.random().toString(36).substring(2, 12)\r\n return `${ts}-${rand}`\r\n}\r\n","import type { SceneBatch, SceneEvent } from '../types'\n\nconst MAX_RETRIES = 2\nconst BEACON_MAX_SIZE = 60000\n\nexport class Sender {\n private queue: SceneEvent[] = []\n private timer: ReturnType<typeof setInterval> | null = null\n private isFlushing = false\n\n constructor(\n private endpoint: string,\n private apiKey: string,\n private batchSize: number,\n private sessionId: string,\n private deviceId: string,\n flushInterval: number,\n ) {\n this.timer = setInterval(() => this.flush(), flushInterval)\n }\n\n add(event: SceneEvent) {\n this.queue.push(event)\n if (event.event === 'rrweb' || this.queue.length >= this.batchSize) {\n this.flush()\n }\n }\n\n async flush() {\n if (this.queue.length === 0 || this.isFlushing) {\n return\n }\n\n this.isFlushing = true\n\n const events = this.queue.splice(0)\n const batch = this.buildBatch(events)\n const json = JSON.stringify(batch)\n const url = this.buildUrl()\n\n const success = await this.send(json, url)\n\n if (!success) {\n this.queue.unshift(...events)\n }\n\n this.isFlushing = false\n }\n\n flushSync() {\n if (this.queue.length === 0) {\n return\n }\n\n const events = this.queue.splice(0)\n if (events.length === 0) return\n\n const url = this.buildUrl()\n\n let chunk: SceneEvent[] = []\n let chunkSize = 0\n\n for (const event of events) {\n const eventJson = JSON.stringify(event)\n if (chunkSize + eventJson.length > BEACON_MAX_SIZE && chunk.length > 0) {\n this.sendBeacon(chunk, url)\n chunk = []\n chunkSize = 0\n }\n chunk.push(event)\n chunkSize += eventJson.length\n }\n\n if (chunk.length > 0) {\n this.sendBeacon(chunk, url)\n }\n }\n\n destroy() {\n if (this.timer) {\n clearInterval(this.timer)\n }\n this.flushSync()\n }\n\n private sendBeacon(events: SceneEvent[], url: string) {\n const batch = this.buildBatch(events)\n const json = JSON.stringify(batch)\n const blob = new Blob([json], { type: 'application/json' })\n navigator.sendBeacon(url, blob)\n }\n\n private buildUrl(): string {\n return `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`\n }\n\n private buildBatch(events: SceneEvent[]): SceneBatch {\n return {\n session_id: this.sessionId,\n device_id: this.deviceId,\n events,\n sent_at: new Date().toISOString(),\n page_url: location.href,\n user_agent: navigator.userAgent,\n screen: {\n width: screen.width,\n height: screen.height,\n viewport_width: window.innerWidth,\n viewport_height: window.innerHeight,\n },\n }\n }\n\n private async send(json: string, url: string): Promise<boolean> {\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: json,\n keepalive: true,\n })\n if (response.ok) {\n return true\n }\n }\n catch {}\n\n if (attempt < MAX_RETRIES) {\n await new Promise(r => setTimeout(r, (attempt + 1) * 200))\n }\n }\n return false\n }\n}\n"],"mappings":"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","names":[]}
@@ -13093,8 +13093,8 @@ var dataclient = (() => {
13093
13093
  }
13094
13094
 
13095
13095
  // src/utils/sender.ts
13096
- var STORAGE_KEY = "sc2_pending";
13097
13096
  var MAX_RETRIES = 2;
13097
+ var BEACON_MAX_SIZE = 6e4;
13098
13098
  var Sender = class {
13099
13099
  constructor(endpoint, apiKey, batchSize, sessionId, deviceId, flushInterval) {
13100
13100
  this.endpoint = endpoint;
@@ -13102,13 +13102,7 @@ var dataclient = (() => {
13102
13102
  this.batchSize = batchSize;
13103
13103
  this.sessionId = sessionId;
13104
13104
  this.deviceId = deviceId;
13105
- this.restoreFromStorage();
13106
13105
  this.timer = setInterval(() => this.flush(), flushInterval);
13107
- document.addEventListener("visibilitychange", () => {
13108
- if (document.visibilityState === "hidden") {
13109
- this.flush();
13110
- }
13111
- });
13112
13106
  }
13113
13107
  endpoint;
13114
13108
  apiKey;
@@ -13118,24 +13112,9 @@ var dataclient = (() => {
13118
13112
  queue = [];
13119
13113
  timer = null;
13120
13114
  isFlushing = false;
13121
- restoreFromStorage() {
13122
- try {
13123
- localStorage.removeItem(STORAGE_KEY);
13124
- } catch {
13125
- }
13126
- }
13127
- saveToStorage() {
13128
- if (this.queue.length === 0) {
13129
- return;
13130
- }
13131
- try {
13132
- localStorage.setItem(STORAGE_KEY, JSON.stringify(this.queue));
13133
- } catch {
13134
- }
13135
- }
13136
13115
  add(event) {
13137
13116
  this.queue.push(event);
13138
- if (this.queue.length >= this.batchSize) {
13117
+ if (event.event === "rrweb" || this.queue.length >= this.batchSize) {
13139
13118
  this.flush();
13140
13119
  }
13141
13120
  }
@@ -13147,11 +13126,10 @@ var dataclient = (() => {
13147
13126
  const events = this.queue.splice(0);
13148
13127
  const batch = this.buildBatch(events);
13149
13128
  const json = JSON.stringify(batch);
13150
- const url = `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`;
13129
+ const url = this.buildUrl();
13151
13130
  const success = await this.send(json, url);
13152
13131
  if (!success) {
13153
13132
  this.queue.unshift(...events);
13154
- this.saveToStorage();
13155
13133
  }
13156
13134
  this.isFlushing = false;
13157
13135
  }
@@ -13160,15 +13138,38 @@ var dataclient = (() => {
13160
13138
  return;
13161
13139
  }
13162
13140
  const events = this.queue.splice(0);
13141
+ if (events.length === 0) return;
13142
+ const url = this.buildUrl();
13143
+ let chunk = [];
13144
+ let chunkSize = 0;
13145
+ for (const event of events) {
13146
+ const eventJson = JSON.stringify(event);
13147
+ if (chunkSize + eventJson.length > BEACON_MAX_SIZE && chunk.length > 0) {
13148
+ this.sendBeacon(chunk, url);
13149
+ chunk = [];
13150
+ chunkSize = 0;
13151
+ }
13152
+ chunk.push(event);
13153
+ chunkSize += eventJson.length;
13154
+ }
13155
+ if (chunk.length > 0) {
13156
+ this.sendBeacon(chunk, url);
13157
+ }
13158
+ }
13159
+ destroy() {
13160
+ if (this.timer) {
13161
+ clearInterval(this.timer);
13162
+ }
13163
+ this.flushSync();
13164
+ }
13165
+ sendBeacon(events, url) {
13163
13166
  const batch = this.buildBatch(events);
13164
13167
  const json = JSON.stringify(batch);
13165
- const url = `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`;
13166
13168
  const blob2 = new Blob([json], { type: "application/json" });
13167
- const sent = navigator.sendBeacon(url, blob2);
13168
- if (!sent) {
13169
- this.queue.unshift(...events);
13170
- this.saveToStorage();
13171
- }
13169
+ navigator.sendBeacon(url, blob2);
13170
+ }
13171
+ buildUrl() {
13172
+ return `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`;
13172
13173
  }
13173
13174
  buildBatch(events) {
13174
13175
  return {
@@ -13206,20 +13207,14 @@ var dataclient = (() => {
13206
13207
  }
13207
13208
  return false;
13208
13209
  }
13209
- destroy() {
13210
- if (this.timer) {
13211
- clearInterval(this.timer);
13212
- }
13213
- this.flushSync();
13214
- }
13215
13210
  };
13216
13211
 
13217
13212
  // src/index.ts
13218
13213
  var defaults = {
13219
13214
  endpoint: "https://my.tamsense.com/api/scenes2",
13220
13215
  debug: false,
13221
- batchSize: 10,
13222
- flushInterval: 1e4,
13216
+ batchSize: 5,
13217
+ flushInterval: 5e3,
13223
13218
  checkpointInterval: 3e4,
13224
13219
  mutationDebounce: 200,
13225
13220
  inputDebounce: 1e3,
@@ -13249,13 +13244,14 @@ var dataclient = (() => {
13249
13244
  const rrwebTracker = new RrwebTracker(this.config, this.sender);
13250
13245
  this.trackers = [snapshotTracker, mutationTracker, actionTracker, rrwebTracker];
13251
13246
  this.trackers.forEach((t) => t.start());
13252
- const onUnload = () => {
13247
+ const onLeave = () => {
13253
13248
  this.trackers.forEach((t) => t.beforeUnload?.());
13254
13249
  this.sender.flushSync();
13255
13250
  };
13256
- window.addEventListener("beforeunload", onUnload);
13257
- window.addEventListener("pagehide", onUnload);
13258
- console.log(`[dataclient] Initialized. Session: ${sessionId}, Device: ${deviceId}`);
13251
+ document.addEventListener("visibilitychange", () => {
13252
+ if (document.visibilityState === "hidden") onLeave();
13253
+ });
13254
+ window.addEventListener("pagehide", onLeave);
13259
13255
  }
13260
13256
  setUser(userId) {
13261
13257
  this.sender.add({ event: "identify", timestamp: (/* @__PURE__ */ new Date()).toISOString(), user_id: userId });