@zoho_apptics/apptics-js-sdk 1.0.1 → 2.0.0-beta

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.
Files changed (226) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/LICENSE +13 -0
  3. package/README.md +747 -11
  4. package/dist/apptics.esm.min.js +3 -0
  5. package/dist/apptics.esm.min.js.map +1 -0
  6. package/dist/apptics.min.js +3 -0
  7. package/dist/apptics.min.js.map +1 -0
  8. package/dist/chunks/batch-BU_mTpm1.min.js +3 -0
  9. package/dist/chunks/batch-BU_mTpm1.min.js.map +1 -0
  10. package/dist/chunks/logs-Bea5zUW_.min.js +3 -0
  11. package/dist/chunks/logs-Bea5zUW_.min.js.map +1 -0
  12. package/dist/cjs/auto-init.d.ts +8 -0
  13. package/dist/cjs/auto-init.js +32 -0
  14. package/dist/cjs/core/attempts.d.ts +28 -0
  15. package/dist/cjs/core/attempts.js +113 -0
  16. package/dist/cjs/core/batch.d.ts +32 -0
  17. package/dist/cjs/core/batch.js +115 -0
  18. package/dist/cjs/core/callsite.d.ts +28 -0
  19. package/dist/cjs/core/callsite.js +93 -0
  20. package/dist/cjs/core/consent.d.ts +23 -0
  21. package/dist/cjs/core/consent.js +130 -0
  22. package/dist/cjs/core/constants.d.ts +212 -0
  23. package/dist/cjs/core/constants.js +299 -0
  24. package/dist/cjs/core/db.d.ts +40 -0
  25. package/dist/cjs/core/db.js +334 -0
  26. package/dist/cjs/core/debug.d.ts +22 -0
  27. package/dist/cjs/core/debug.js +88 -0
  28. package/dist/cjs/core/env.d.ts +28 -0
  29. package/dist/cjs/core/env.js +267 -0
  30. package/dist/cjs/core/framework-id.d.ts +1 -0
  31. package/dist/cjs/core/framework-id.js +9 -0
  32. package/dist/cjs/core/guard.d.ts +2 -0
  33. package/dist/cjs/core/guard.js +72 -0
  34. package/dist/cjs/core/hooks.d.ts +41 -0
  35. package/dist/cjs/core/hooks.js +23 -0
  36. package/dist/cjs/core/logger.d.ts +16 -0
  37. package/dist/cjs/core/logger.js +86 -0
  38. package/dist/cjs/core/state.d.ts +69 -0
  39. package/dist/cjs/core/state.js +78 -0
  40. package/dist/cjs/core/storage.d.ts +46 -0
  41. package/dist/cjs/core/storage.js +229 -0
  42. package/dist/cjs/core/transport.d.ts +83 -0
  43. package/dist/cjs/core/transport.js +441 -0
  44. package/dist/cjs/core/utils.d.ts +26 -0
  45. package/dist/cjs/core/utils.js +175 -0
  46. package/dist/cjs/core/validate.d.ts +18 -0
  47. package/dist/cjs/core/validate.js +233 -0
  48. package/dist/cjs/device/appversion.d.ts +20 -0
  49. package/dist/cjs/device/appversion.js +158 -0
  50. package/dist/cjs/device/index.d.ts +23 -0
  51. package/dist/cjs/device/index.js +372 -0
  52. package/dist/cjs/engagement/api-rules.d.ts +84 -0
  53. package/dist/cjs/engagement/api-rules.js +219 -0
  54. package/dist/cjs/engagement/auto-api.d.ts +7 -0
  55. package/dist/cjs/engagement/auto-api.js +200 -0
  56. package/dist/cjs/engagement/auto-screen.d.ts +16 -0
  57. package/dist/cjs/engagement/auto-screen.js +241 -0
  58. package/dist/cjs/engagement/defined-events.d.ts +23 -0
  59. package/dist/cjs/engagement/defined-events.js +58 -0
  60. package/dist/cjs/engagement/send.d.ts +6 -0
  61. package/dist/cjs/engagement/send.js +99 -0
  62. package/dist/cjs/engagement/session.d.ts +42 -0
  63. package/dist/cjs/engagement/session.js +327 -0
  64. package/dist/cjs/engagement/track.d.ts +56 -0
  65. package/dist/cjs/engagement/track.js +350 -0
  66. package/dist/cjs/errors/send.d.ts +6 -0
  67. package/dist/cjs/errors/send.js +73 -0
  68. package/dist/cjs/errors/track.d.ts +4 -0
  69. package/dist/cjs/errors/track.js +127 -0
  70. package/dist/cjs/feedback/controls.d.ts +31 -0
  71. package/dist/cjs/feedback/controls.js +286 -0
  72. package/dist/cjs/feedback/index.d.ts +8 -0
  73. package/dist/cjs/feedback/index.js +37 -0
  74. package/dist/cjs/feedback/logs.d.ts +40 -0
  75. package/dist/cjs/feedback/logs.js +202 -0
  76. package/dist/cjs/feedback/send.d.ts +25 -0
  77. package/dist/cjs/feedback/send.js +178 -0
  78. package/dist/cjs/feedback/template.d.ts +2 -0
  79. package/dist/cjs/feedback/template.js +162 -0
  80. package/dist/cjs/feedback/ui.d.ts +55 -0
  81. package/dist/cjs/feedback/ui.js +631 -0
  82. package/dist/cjs/heatmap/index.d.ts +16 -0
  83. package/dist/cjs/heatmap/index.js +22 -0
  84. package/dist/cjs/heatmap/send.d.ts +13 -0
  85. package/dist/cjs/heatmap/send.js +82 -0
  86. package/dist/cjs/heatmap/track.d.ts +4 -0
  87. package/dist/cjs/heatmap/track.js +377 -0
  88. package/dist/cjs/index.d.ts +68 -0
  89. package/dist/cjs/index.js +150 -0
  90. package/dist/cjs/lifecycle.d.ts +14 -0
  91. package/dist/cjs/lifecycle.js +538 -0
  92. package/dist/cjs/package.json +3 -0
  93. package/dist/cjs/remoteconfig/custom.d.ts +10 -0
  94. package/dist/cjs/remoteconfig/custom.js +78 -0
  95. package/dist/cjs/remoteconfig/evaluate.d.ts +24 -0
  96. package/dist/cjs/remoteconfig/evaluate.js +171 -0
  97. package/dist/cjs/remoteconfig/fetch.d.ts +24 -0
  98. package/dist/cjs/remoteconfig/fetch.js +214 -0
  99. package/dist/cjs/remoteconfig/index.d.ts +13 -0
  100. package/dist/cjs/remoteconfig/index.js +90 -0
  101. package/dist/cjs/remoteconfig/store.d.ts +18 -0
  102. package/dist/cjs/remoteconfig/store.js +98 -0
  103. package/dist/cjs/remotelog/gate.d.ts +11 -0
  104. package/dist/cjs/remotelog/gate.js +54 -0
  105. package/dist/cjs/remotelog/index.d.ts +25 -0
  106. package/dist/cjs/remotelog/index.js +55 -0
  107. package/dist/cjs/remotelog/logger.d.ts +28 -0
  108. package/dist/cjs/remotelog/logger.js +139 -0
  109. package/dist/cjs/remotelog/send.d.ts +13 -0
  110. package/dist/cjs/remotelog/send.js +153 -0
  111. package/dist/cjs/scheduler.d.ts +8 -0
  112. package/dist/cjs/scheduler.js +78 -0
  113. package/dist/cjs/types.d.ts +312 -0
  114. package/dist/cjs/user/index.d.ts +33 -0
  115. package/dist/cjs/user/index.js +178 -0
  116. package/dist/esm/auto-init.d.ts +8 -0
  117. package/dist/esm/auto-init.js +29 -0
  118. package/dist/esm/core/attempts.d.ts +28 -0
  119. package/dist/esm/core/attempts.js +106 -0
  120. package/dist/esm/core/batch.d.ts +32 -0
  121. package/dist/esm/core/batch.js +108 -0
  122. package/dist/esm/core/callsite.d.ts +28 -0
  123. package/dist/esm/core/callsite.js +89 -0
  124. package/dist/esm/core/consent.d.ts +23 -0
  125. package/dist/esm/core/consent.js +120 -0
  126. package/dist/esm/core/constants.d.ts +212 -0
  127. package/dist/esm/core/constants.js +277 -0
  128. package/dist/esm/core/db.d.ts +40 -0
  129. package/dist/esm/core/db.js +324 -0
  130. package/dist/esm/core/debug.d.ts +22 -0
  131. package/dist/esm/core/debug.js +81 -0
  132. package/dist/esm/core/env.d.ts +28 -0
  133. package/dist/esm/core/env.js +251 -0
  134. package/dist/esm/core/framework-id.d.ts +1 -0
  135. package/dist/esm/core/framework-id.js +7 -0
  136. package/dist/esm/core/guard.d.ts +2 -0
  137. package/dist/esm/core/guard.js +70 -0
  138. package/dist/esm/core/hooks.d.ts +41 -0
  139. package/dist/esm/core/hooks.js +21 -0
  140. package/dist/esm/core/logger.d.ts +16 -0
  141. package/dist/esm/core/logger.js +82 -0
  142. package/dist/esm/core/state.d.ts +69 -0
  143. package/dist/esm/core/state.js +73 -0
  144. package/dist/esm/core/storage.d.ts +46 -0
  145. package/dist/esm/core/storage.js +219 -0
  146. package/dist/esm/core/transport.d.ts +83 -0
  147. package/dist/esm/core/transport.js +425 -0
  148. package/dist/esm/core/utils.d.ts +26 -0
  149. package/dist/esm/core/utils.js +161 -0
  150. package/dist/esm/core/validate.d.ts +18 -0
  151. package/dist/esm/core/validate.js +226 -0
  152. package/dist/esm/device/appversion.d.ts +20 -0
  153. package/dist/esm/device/appversion.js +153 -0
  154. package/dist/esm/device/index.d.ts +23 -0
  155. package/dist/esm/device/index.js +362 -0
  156. package/dist/esm/engagement/api-rules.d.ts +84 -0
  157. package/dist/esm/engagement/api-rules.js +211 -0
  158. package/dist/esm/engagement/auto-api.d.ts +7 -0
  159. package/dist/esm/engagement/auto-api.js +198 -0
  160. package/dist/esm/engagement/auto-screen.d.ts +16 -0
  161. package/dist/esm/engagement/auto-screen.js +238 -0
  162. package/dist/esm/engagement/defined-events.d.ts +23 -0
  163. package/dist/esm/engagement/defined-events.js +55 -0
  164. package/dist/esm/engagement/send.d.ts +6 -0
  165. package/dist/esm/engagement/send.js +96 -0
  166. package/dist/esm/engagement/session.d.ts +42 -0
  167. package/dist/esm/engagement/session.js +315 -0
  168. package/dist/esm/engagement/track.d.ts +56 -0
  169. package/dist/esm/engagement/track.js +340 -0
  170. package/dist/esm/errors/send.d.ts +6 -0
  171. package/dist/esm/errors/send.js +70 -0
  172. package/dist/esm/errors/track.d.ts +4 -0
  173. package/dist/esm/errors/track.js +123 -0
  174. package/dist/esm/feedback/controls.d.ts +31 -0
  175. package/dist/esm/feedback/controls.js +280 -0
  176. package/dist/esm/feedback/index.d.ts +8 -0
  177. package/dist/esm/feedback/index.js +30 -0
  178. package/dist/esm/feedback/logs.d.ts +40 -0
  179. package/dist/esm/feedback/logs.js +188 -0
  180. package/dist/esm/feedback/send.d.ts +25 -0
  181. package/dist/esm/feedback/send.js +175 -0
  182. package/dist/esm/feedback/template.d.ts +2 -0
  183. package/dist/esm/feedback/template.js +159 -0
  184. package/dist/esm/feedback/ui.d.ts +55 -0
  185. package/dist/esm/feedback/ui.js +626 -0
  186. package/dist/esm/heatmap/index.d.ts +16 -0
  187. package/dist/esm/heatmap/index.js +17 -0
  188. package/dist/esm/heatmap/send.d.ts +13 -0
  189. package/dist/esm/heatmap/send.js +79 -0
  190. package/dist/esm/heatmap/track.d.ts +4 -0
  191. package/dist/esm/heatmap/track.js +374 -0
  192. package/dist/esm/index.d.ts +68 -0
  193. package/dist/esm/index.js +122 -0
  194. package/dist/esm/lifecycle.d.ts +14 -0
  195. package/dist/esm/lifecycle.js +534 -0
  196. package/dist/esm/package.json +3 -0
  197. package/dist/esm/remoteconfig/custom.d.ts +10 -0
  198. package/dist/esm/remoteconfig/custom.js +74 -0
  199. package/dist/esm/remoteconfig/evaluate.d.ts +24 -0
  200. package/dist/esm/remoteconfig/evaluate.js +166 -0
  201. package/dist/esm/remoteconfig/fetch.d.ts +24 -0
  202. package/dist/esm/remoteconfig/fetch.js +210 -0
  203. package/dist/esm/remoteconfig/index.d.ts +13 -0
  204. package/dist/esm/remoteconfig/index.js +85 -0
  205. package/dist/esm/remoteconfig/store.d.ts +18 -0
  206. package/dist/esm/remoteconfig/store.js +91 -0
  207. package/dist/esm/remotelog/gate.d.ts +11 -0
  208. package/dist/esm/remotelog/gate.js +48 -0
  209. package/dist/esm/remotelog/index.d.ts +25 -0
  210. package/dist/esm/remotelog/index.js +51 -0
  211. package/dist/esm/remotelog/logger.d.ts +28 -0
  212. package/dist/esm/remotelog/logger.js +136 -0
  213. package/dist/esm/remotelog/send.d.ts +13 -0
  214. package/dist/esm/remotelog/send.js +149 -0
  215. package/dist/esm/scheduler.d.ts +8 -0
  216. package/dist/esm/scheduler.js +74 -0
  217. package/dist/esm/types.d.ts +312 -0
  218. package/dist/esm/user/index.d.ts +33 -0
  219. package/dist/esm/user/index.js +170 -0
  220. package/dist/feedback.esm.min.js +3 -0
  221. package/dist/feedback.esm.min.js.map +1 -0
  222. package/dist/heatmap.esm.min.js +3 -0
  223. package/dist/heatmap.esm.min.js.map +1 -0
  224. package/dist/style.css +1 -0
  225. package/package.json +124 -7
  226. package/src/appticssdk.js +0 -3817
@@ -0,0 +1,13 @@
1
+ import type { Dict, ServerResult } from '../types.js';
2
+ /**
3
+ * Sends queued heatmap records to heatmap/add.
4
+ *
5
+ * Shaped like the error and engagement senders, because the queue behaves the same way: records
6
+ * are stored first and sent on the flush timer, a failed send leaves them for the next one, and
7
+ * `forget` is used on page hide so the last batch survives the page going away.
8
+ *
9
+ * Gated on the server's own heatmap flag (getsdkconfig module 18), not on engagement: an app may
10
+ * want events while declining to have every click's coordinates recorded.
11
+ */
12
+ export declare function sendHeatmapData(forget?: boolean): Promise<void>;
13
+ export declare function requestToSendHeatmaps(_payload: Dict, forget?: boolean): Promise<ServerResult>;
@@ -0,0 +1,82 @@
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
2
+ 'use strict';
3
+
4
+ var state = require('../core/state.js');
5
+ var logger = require('../core/logger.js');
6
+ var utils = require('../core/utils.js');
7
+ var storage = require('../core/storage.js');
8
+ var env = require('../core/env.js');
9
+ var consent = require('../core/consent.js');
10
+ var batch = require('../core/batch.js');
11
+ var transport = require('../core/transport.js');
12
+ var hooks = require('../core/hooks.js');
13
+
14
+ /**
15
+ * Sends queued heatmap records to heatmap/add.
16
+ *
17
+ * Shaped like the error and engagement senders, because the queue behaves the same way: records
18
+ * are stored first and sent on the flush timer, a failed send leaves them for the next one, and
19
+ * `forget` is used on page hide so the last batch survives the page going away.
20
+ *
21
+ * Gated on the server's own heatmap flag (getsdkconfig module 18), not on engagement: an app may
22
+ * want events while declining to have every click's coordinates recorded.
23
+ */
24
+ function sendHeatmapData(forget) {
25
+ if (env.is_apptics_disabled() || !consent.shouldTrackOrSendData() || !state._ZAP._heatmapTracking) {
26
+ return Promise.resolve();
27
+ }
28
+ if (navigator.onLine == false) {
29
+ logger.Console.warn('sendHeatmapData: No Network'); // no i18n
30
+ return Promise.resolve();
31
+ }
32
+ if (state._ZAP._sending) {
33
+ batch.defer(sendHeatmapData);
34
+ return Promise.resolve();
35
+ }
36
+ state._ZAP._sending = true;
37
+ batch.clearDefer();
38
+ return batch.withSendLock('heatmaps', function () {
39
+ return batch.freshItems(storage.heatmap_transactions, function () { return true; },
40
+ // aged by when the record happened, not when its session began: a tab left open for
41
+ // days would otherwise make every new record look older than the retention window
42
+ // and have it dropped unsent
43
+ function (item) { return (item.triggered_time || item.session_start_time); }).then(function (items) {
44
+ if (items.length == 0) {
45
+ return;
46
+ }
47
+ var data = [];
48
+ var sent = [];
49
+ utils.cloneObject(items).forEach(function (item) {
50
+ sent.push({ ...item });
51
+ // `id` is the queue's own key, not something the endpoint declares
52
+ delete item.id;
53
+ data.push(item);
54
+ });
55
+ return batch.awaitUnlessClosing(requestToSendHeatmaps({ data: data }, forget).then(function () {
56
+ return storage.heatmap_transactions.remove(sent);
57
+ }, function (err) {
58
+ // the server will never accept this batch. Keeping it would block every later
59
+ // batch behind it for good, so it is dropped and said out loud
60
+ if (transport.isPermanentRefusal(err)) {
61
+ logger.warnAlways('apptics: the server refused these heatmap records, so they were dropped: ' + String(err && err.message)); // no i18n
62
+ return storage.heatmap_transactions.remove(sent);
63
+ }
64
+ return Promise.reject(err);
65
+ }), forget, 'sendHeatmapData'); // no i18n
66
+ });
67
+ }).catch(function (err) {
68
+ logger.Console.log('sendHeatmapData:' + err); // no i18n
69
+ }).then(function () {
70
+ state._ZAP._sending = false;
71
+ });
72
+ }
73
+ function requestToSendHeatmaps(_payload, forget) {
74
+ var label = 'requestToSendHeatmaps'; // no i18n
75
+ return batch.sendBatch('heatmap', label, _payload, forget); // no i18n
76
+ }
77
+ // the scheduler flushes through this, so importing the heatmap entry is what puts heatmap
78
+ // records on the flush timer at all
79
+ hooks.hooks.sendHeatmaps = sendHeatmapData;
80
+
81
+ exports.requestToSendHeatmaps = requestToSendHeatmaps;
82
+ exports.sendHeatmapData = sendHeatmapData;
@@ -0,0 +1,4 @@
1
+ /** Turns capture on or off. Listeners are passive, so they never delay a click or a scroll. */
2
+ export declare function enableHeatmap(status?: boolean): void;
3
+ /** Forgets what this visit recorded. reset() calls it; the cap starts again from zero. */
4
+ export declare function resetHeatmapSession(): void;
@@ -0,0 +1,377 @@
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
2
+ 'use strict';
3
+
4
+ var constants = require('../core/constants.js');
5
+ var state = require('../core/state.js');
6
+ var logger = require('../core/logger.js');
7
+ var utils = require('../core/utils.js');
8
+ var storage = require('../core/storage.js');
9
+ var session = require('../engagement/session.js');
10
+ var env = require('../core/env.js');
11
+ var consent = require('../core/consent.js');
12
+ var track = require('../engagement/track.js');
13
+ var hooks = require('../core/hooks.js');
14
+ var debug = require('../core/debug.js');
15
+
16
+ /**
17
+ * Where people click and how far they scroll.
18
+ *
19
+ * Two shapes of record, and they behave very differently. A click is one discrete thing and is
20
+ * recorded as it happens. Scrolling fires continuously — hundreds of events for one flick of a
21
+ * wheel — so it is coalesced into one record per gesture: a burst of scrolling that ends when the
22
+ * page has been still for a moment. Recording every scroll event would swamp the queue, the
23
+ * batch and the user's bandwidth, and would say nothing a single record per gesture does not.
24
+ */
25
+ /** How long the page must be still before a scroll burst counts as one finished gesture. */
26
+ const SCROLL_IDLE_MS = 150;
27
+ /** Clicks closer than this in time and space count as one angry repetition. */
28
+ const RAGE_WINDOW_MS = 1000;
29
+ const RAGE_RADIUS_PX = 30;
30
+ const RAGE_COUNT = 3;
31
+ /** How long a click has to change something before it is called dead. */
32
+ const DEAD_CLICK_MS = 1500;
33
+ /** Longest element label we keep; enough to identify a control, short enough not to be content. */
34
+ const LABEL_MAX = 80;
35
+ /** heatMapObjectSDK caps screen, url_path and element_label at 500 characters. */
36
+ const FIELD_MAX = 500;
37
+ var installed = false;
38
+ var recorded = 0;
39
+ /** Recent clicks, for spotting a rage burst. */
40
+ var recentClicks = [];
41
+ var scrollStart = 0;
42
+ var scrollStartX = 0;
43
+ var scrollStartY = 0;
44
+ var scrollTimer = null;
45
+ /**
46
+ * Where the page was before the current gesture began.
47
+ *
48
+ * A scroll event fires *after* the position has already changed, so the first event of a burst
49
+ * reports somewhere the page has moved to, not where it started. Measuring from that loses the
50
+ * first increment of every gesture, and loses a single-event jump — an anchor link or a "back to
51
+ * top" button — completely, which is exactly the movement worth seeing on a scroll map.
52
+ */
53
+ var lastX = 0;
54
+ var lastY = 0;
55
+ /** Whether a record may still be stored, or the visit has produced enough. */
56
+ function underCap() {
57
+ var cap = state.getConfig('heatmap_max_per_session');
58
+ if (typeof cap !== 'number' || cap <= 0) {
59
+ cap = constants.AP_CONFIG.heatmap_max_per_session;
60
+ }
61
+ if (recorded >= cap) {
62
+ if (recorded === cap) {
63
+ recorded++; // said once, not on every event for the rest of the visit
64
+ logger.Console.log('apptics.heatmap: reached ' + cap + ' records for this session, pausing capture'); // no i18n
65
+ }
66
+ return false;
67
+ }
68
+ return true;
69
+ }
70
+ /** The page's full scrollable height, which is what a scroll depth is a fraction of. */
71
+ function contentHeight() {
72
+ var doc = document.documentElement;
73
+ var body = document.body;
74
+ return Math.max(doc ? doc.scrollHeight : 0, body ? body.scrollHeight : 0, doc ? doc.clientHeight : 0);
75
+ }
76
+ /**
77
+ * A 0-1 fraction, to three decimals.
78
+ *
79
+ * Clamped because the inputs can exceed each other: a page that has not laid out yet reports a
80
+ * content height of 0, and a rubber-band scroll puts the viewport bottom past the document end.
81
+ * Both produce a "fraction" far outside 0-1, which is meaningless on a heatmap.
82
+ */
83
+ function fraction(part, whole) {
84
+ if (!whole || whole <= 0) {
85
+ return 0;
86
+ }
87
+ var value = part / whole;
88
+ if (value < 0) {
89
+ value = 0;
90
+ }
91
+ if (value > 1) {
92
+ value = 1;
93
+ }
94
+ return Math.round(value * 1000) / 1000;
95
+ }
96
+ function viewportWidth() {
97
+ return window.innerWidth || (document.documentElement ? document.documentElement.clientWidth : 0) || 1;
98
+ }
99
+ /** Whether the element, or something it sits inside, is meant to be clicked. */
100
+ function clickableAncestor(node) {
101
+ var depth = 0;
102
+ while (node && depth < 10) {
103
+ var tag = node.tagName ? node.tagName.toLowerCase() : ''; // no i18n
104
+ var role = node.getAttribute ? node.getAttribute('role') : null; // no i18n
105
+ if (tag === 'a' || tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea' // no i18n
106
+ || tag === 'summary' || tag === 'label' // no i18n
107
+ || role === 'button' || role === 'link' || role === 'tab' || role === 'menuitem' // no i18n
108
+ || (node.hasAttribute && node.hasAttribute('onclick'))) { // no i18n
109
+ return node;
110
+ }
111
+ node = node.parentElement;
112
+ depth++;
113
+ }
114
+ return null;
115
+ }
116
+ /**
117
+ * A name for the element that was clicked.
118
+ *
119
+ * Attributes an author chose come first, because they are deliberate and stable. Visible text is
120
+ * the last resort and is skipped entirely when the user has opted out of PII: the text of a
121
+ * control can be a person's name, an email address or the contents of their cart.
122
+ */
123
+ function labelFor(node) {
124
+ if (!node) {
125
+ return ''; // no i18n
126
+ }
127
+ var attributes = ['data-ap-label', 'apeventaction', 'aria-label', 'name', 'id', 'title']; // no i18n
128
+ for (var i = 0; i < attributes.length; i++) {
129
+ var value = node.getAttribute ? node.getAttribute(attributes[i]) : null;
130
+ if (!utils.isEmpty(value)) {
131
+ return String(value).slice(0, LABEL_MAX);
132
+ }
133
+ }
134
+ var tag = node.tagName ? node.tagName.toLowerCase() : 'element'; // no i18n
135
+ if (consent.getTrackingStatus() !== constants.AP_TRACKING_STATUS.USAGE_TRACKING_WITH_PII) {
136
+ // without consent for PII the shape of the control is all we say
137
+ var role = node.getAttribute ? node.getAttribute('role') : null; // no i18n
138
+ return utils.isEmpty(role) ? tag : tag + '[' + role + ']'; // no i18n
139
+ }
140
+ var text = (node.textContent || '').replace(/\s+/g, ' ').trim(); // no i18n
141
+ return utils.isEmpty(text) ? tag : text.slice(0, LABEL_MAX);
142
+ }
143
+ /**
144
+ * A stable number for the element, so the console can group every click on one control together.
145
+ * Derived from its position in the tree rather than from its text, so it survives a label change
146
+ * and carries nothing a person typed.
147
+ */
148
+ function triggeredId(node) {
149
+ if (!node) {
150
+ return 0;
151
+ }
152
+ var path = ''; // no i18n
153
+ var current = node;
154
+ var depth = 0;
155
+ while (current && depth < 12) {
156
+ var parent = current.parentElement;
157
+ var index = 0;
158
+ if (parent) {
159
+ var children = parent.children;
160
+ for (var i = 0; i < children.length; i++) {
161
+ if (children[i] === current) {
162
+ index = i;
163
+ break;
164
+ }
165
+ }
166
+ }
167
+ path = '/' + (current.tagName || '?') + ':' + index + path; // no i18n
168
+ current = parent;
169
+ depth++;
170
+ }
171
+ // FNV-1a, kept positive: a short stable number, not a cryptographic digest
172
+ var hash = 2166136261;
173
+ for (var c = 0; c < path.length; c++) {
174
+ hash ^= path.charCodeAt(c);
175
+ hash = (hash * 16777619) >>> 0;
176
+ }
177
+ return hash;
178
+ }
179
+ /** Whether this click is part of a burst of repeated clicks in the same spot. */
180
+ function isRageClick(x, y, at) {
181
+ recentClicks = recentClicks.filter(function (click) { return at - click.at <= RAGE_WINDOW_MS; });
182
+ recentClicks.push({ at: at, x: x, y: y });
183
+ var near = recentClicks.filter(function (click) {
184
+ return Math.abs(click.x - x) <= RAGE_RADIUS_PX && Math.abs(click.y - y) <= RAGE_RADIUS_PX;
185
+ });
186
+ return near.length >= RAGE_COUNT;
187
+ }
188
+ /** The fields every record carries, whatever kind it is. */
189
+ function common() {
190
+ return {
191
+ id: utils.getNewUUID(),
192
+ session_start_time: state._ZAP._current_session.starttime || utils.nowMs(),
193
+ // without this the server has no way to order records within a session: session_start_time
194
+ // is the same for every one of them
195
+ triggered_time: utils.nowMs(),
196
+ // the screen inScreen() or automatic screen tracking last named, or else the page, named
197
+ // as automatic tracking would name it and recorded as a screen view so engagement/add has it
198
+ screen: String(track.screenForRecord()).slice(0, FIELD_MAX),
199
+ url_path: String(window.location.pathname).slice(0, FIELD_MAX),
200
+ screen_width: window.screen ? window.screen.width : 0,
201
+ screen_height: window.screen ? window.screen.height : 0,
202
+ content_height: contentHeight(),
203
+ orientation: env.getOrientation(),
204
+ network_status: env.getNetworkStatus(),
205
+ edge_type: state._ZAP._device_info.connectionType || env.getConnectionType(),
206
+ scroll_offset_x: Math.round(window.scrollX || window.pageXOffset || 0),
207
+ scroll_offset_y: Math.round(window.scrollY || window.pageYOffset || 0),
208
+ };
209
+ }
210
+ function store(record) {
211
+ recorded++;
212
+ debug.noteDebugActivity();
213
+ // a heatmap record is worth a session in the same way an event is
214
+ session.commitPendingSession();
215
+ storage.heatmap_transactions.add(record);
216
+ logger.Console.dir(record);
217
+ }
218
+ function onPointerDown(event) {
219
+ if (!consent.canTrackHeatmap('heatmap') || !underCap()) { // no i18n
220
+ return;
221
+ }
222
+ try {
223
+ var at = utils.nowMs();
224
+ var target = event.target;
225
+ var clickable = clickableAncestor(target);
226
+ var named = clickable || target;
227
+ var x = Math.round(event.clientX);
228
+ var y = Math.round(event.clientY);
229
+ var height = contentHeight() || 1;
230
+ var pointerType = event.pointerType;
231
+ var record = Object.assign(common(), {
232
+ // TODO: send 'buttonclick' for a clickable target once the server accepts it. Held at
233
+ // 'default' for now while heatmap/add is still refusing payloads; `is_clickable`
234
+ // already carries the same information, so nothing is lost meanwhile.
235
+ triggered_type: 'default', // no i18n
236
+ gesture_type: 'click', // no i18n
237
+ gesture_duration: 0,
238
+ input_type: utils.isEmpty(pointerType) ? 'mouse' : String(pointerType), // no i18n
239
+ element_label: labelFor(named),
240
+ triggered_id: triggeredId(named),
241
+ coordinate_x: x,
242
+ coordinate_y: y,
243
+ normalized_x: fraction(x, viewportWidth()),
244
+ normalized_y: fraction((window.scrollY || 0) + y, height),
245
+ velocity: 0,
246
+ is_clickable: clickable ? 1 : 0,
247
+ is_rage_click: isRageClick(x, y, at) ? 1 : 0,
248
+ is_dead_click: 0,
249
+ });
250
+ store(record);
251
+ watchForDeadClick(record, target);
252
+ }
253
+ catch (err) {
254
+ logger.logFailure(constants.AP_LOG_MODULE.HEATMAP, 'apptics.heatmap: click capture failed: ' + String(err)); // no i18n
255
+ }
256
+ }
257
+ /**
258
+ * A click on something that was not clickable and changed nothing is a click someone expected to
259
+ * work. Decided after the fact: the record is already stored, and is updated in place if nothing
260
+ * happened, so the click is never lost waiting to find out.
261
+ */
262
+ function watchForDeadClick(record, target) {
263
+ if (record.is_clickable === 1 || typeof MutationObserver !== 'function') { // no i18n
264
+ return;
265
+ }
266
+ var changed = false;
267
+ var url = window.location.href;
268
+ var observer = new MutationObserver(function () { changed = true; });
269
+ try {
270
+ observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true });
271
+ }
272
+ catch (err) {
273
+ return;
274
+ }
275
+ setTimeout(function () {
276
+ observer.disconnect();
277
+ if (!changed && window.location.href === url) {
278
+ record.is_dead_click = 1;
279
+ storage.heatmap_transactions.update(record);
280
+ }
281
+ }, DEAD_CLICK_MS);
282
+ }
283
+ function onScroll() {
284
+ if (!consent.canTrackHeatmap('heatmap') || !underCap()) { // no i18n
285
+ return;
286
+ }
287
+ if (scrollTimer === null) {
288
+ scrollStart = utils.nowMs();
289
+ scrollStartX = lastX;
290
+ scrollStartY = lastY;
291
+ }
292
+ else {
293
+ clearTimeout(scrollTimer);
294
+ }
295
+ scrollTimer = setTimeout(endScrollGesture, SCROLL_IDLE_MS);
296
+ }
297
+ /** One record for the whole burst, once the page has been still for SCROLL_IDLE_MS. */
298
+ function endScrollGesture() {
299
+ scrollTimer = null;
300
+ try {
301
+ var endX = Math.round(window.scrollX || 0);
302
+ var endY = Math.round(window.scrollY || 0);
303
+ var dx = endX - scrollStartX;
304
+ var dy = endY - scrollStartY;
305
+ lastX = endX;
306
+ lastY = endY;
307
+ if (dx === 0 && dy === 0) {
308
+ return;
309
+ }
310
+ var duration = Math.max(1, utils.nowMs() - scrollStart);
311
+ var distance = Math.sqrt(dx * dx + dy * dy);
312
+ var direction = Math.abs(dy) >= Math.abs(dx)
313
+ ? (dy > 0 ? 'down-scroll' : 'up-scroll') // no i18n
314
+ : (dx > 0 ? 'right-scroll' : 'left-scroll'); // no i18n
315
+ var height = contentHeight() || 1;
316
+ store(Object.assign(common(), {
317
+ // the server accepts only event|default|buttonclick here; what kind of movement it
318
+ // was belongs in gesture_type. input_type is left off rather than invented: a wheel
319
+ // carries no pointer type, and the intended vocabulary has no value for "unknown"
320
+ triggered_type: 'default', // no i18n
321
+ gesture_type: direction,
322
+ gesture_duration: Math.round(duration),
323
+ element_label: '', // no i18n
324
+ triggered_id: 0,
325
+ coordinate_x: endX,
326
+ coordinate_y: endY,
327
+ normalized_x: 0,
328
+ // how far down the page the gesture left them, which is what a scroll map reads
329
+ normalized_y: fraction(endY + (window.innerHeight || 0), height),
330
+ velocity: Math.round((distance / duration) * 1000),
331
+ is_clickable: 0,
332
+ is_rage_click: 0,
333
+ is_dead_click: 0,
334
+ }));
335
+ }
336
+ catch (err) {
337
+ logger.logFailure(constants.AP_LOG_MODULE.HEATMAP, 'apptics.heatmap: scroll capture failed: ' + String(err)); // no i18n
338
+ }
339
+ }
340
+ /** Turns capture on or off. Listeners are passive, so they never delay a click or a scroll. */
341
+ function enableHeatmap(status) {
342
+ var wanted = typeof status === 'boolean' ? status : true; // no i18n
343
+ if (wanted === installed) {
344
+ return;
345
+ }
346
+ if (wanted) {
347
+ // where the page already is, so the first gesture is measured from here and not from 0
348
+ lastX = Math.round(window.scrollX || 0);
349
+ lastY = Math.round(window.scrollY || 0);
350
+ window.addEventListener('pointerdown', onPointerDown, { capture: true, passive: true }); // no i18n
351
+ window.addEventListener('scroll', onScroll, { capture: true, passive: true }); // no i18n
352
+ installed = true;
353
+ logger.Console.log('apptics.heatmap: capturing clicks and scrolls'); // no i18n
354
+ return;
355
+ }
356
+ window.removeEventListener('pointerdown', onPointerDown, true); // no i18n
357
+ window.removeEventListener('scroll', onScroll, true); // no i18n
358
+ if (scrollTimer !== null) {
359
+ clearTimeout(scrollTimer);
360
+ scrollTimer = null;
361
+ }
362
+ installed = false;
363
+ logger.Console.log('apptics.heatmap: capture stopped'); // no i18n
364
+ }
365
+ /** Forgets what this visit recorded. reset() calls it; the cap starts again from zero. */
366
+ function resetHeatmapSession() {
367
+ recorded = 0;
368
+ recentClicks = [];
369
+ lastX = 0;
370
+ lastY = 0;
371
+ }
372
+ // init() and reset() drive capture through these, so neither has to import this module
373
+ hooks.hooks.enableHeatmap = enableHeatmap;
374
+ hooks.hooks.resetHeatmap = resetHeatmapSession;
375
+
376
+ exports.enableHeatmap = enableHeatmap;
377
+ exports.resetHeatmapSession = resetHeatmapSession;
@@ -0,0 +1,68 @@
1
+ import { AP_FEEDBACK_TYPE, AP_TRACKING_STATUS } from './core/constants.js';
2
+ import { init as initImpl, ready as readyImpl, reset as resetImpl } from './lifecycle.js';
3
+ import { enableApiTracking as enableApiTrackingImpl } from './engagement/auto-api.js';
4
+ import { DefinedEvents } from './engagement/defined-events.js';
5
+ import { ApiTrackingBuilder } from './engagement/api-rules.js';
6
+ import { enableGlobalErrorHandler as enableGlobalErrorHandlerImpl } from './errors/track.js';
7
+ import { getUser as getUserImpl, UserBuilder, users } from './user/index.js';
8
+ import { getTrackingStatus as getTrackingStatusImpl, setTrackingStatus as setTrackingStatusImpl } from './core/consent.js';
9
+ import { flush as flushImpl } from './scheduler.js';
10
+ import { addDiagnosticsInfo as addDiagnosticsInfoImpl, addFeedbackLog as addFeedbackLogImpl } from './feedback/logs.js';
11
+ import type { AppticsApi } from './types.js';
12
+ import { disableDebug as disableDebugImpl, enableDebug as enableDebugImpl } from './core/debug.js';
13
+ import { AppticsLogger } from './remotelog/logger.js';
14
+ export type { AppticsLogger as AppticsLoggerApi } from './remotelog/logger.js';
15
+ export type { AppticsApi, AppticsConfig, ErrorLike, FeedbackType, Properties, TrackingStatus, UserInfo, UserValidationResult, } from './types.js';
16
+ /** Android's Apptics: `Apptics.init(config)` starts the SDK. */
17
+ export declare const Apptics: {
18
+ /** Starts the SDK. The config object is required; the console snippet supplies it via auto-init. */
19
+ init: typeof initImpl;
20
+ };
21
+ export declare const ready: typeof readyImpl;
22
+ /** Android's AppticsEvents: `addEvent(name, group?, properties?)`, or `addEvent(DefinedEvents.X, properties?)`. */
23
+ export declare const AppticsEvents: {
24
+ addEvent: AppticsApi["AppticsEvents"]["addEvent"];
25
+ };
26
+ /** Apptics' defined events, as Android's DefinedEvents, to log with AppticsEvents.addEvent. */
27
+ export { DefinedEvents };
28
+ export type { DefinedEvent } from './engagement/defined-events.js';
29
+ /** Android's AppticsScreenTracker: inScreen(name) returns the view's id, outScreen(id) ends it. */
30
+ export declare const AppticsScreenTracker: AppticsApi['AppticsScreenTracker'];
31
+ /** Android's AppticsApiTracker: startTrackApi / endTrackApi, and configure for the rules. */
32
+ export declare const AppticsApiTracker: AppticsApi['AppticsApiTracker'];
33
+ export { ApiTrackingBuilder };
34
+ export type { ApiTrackingConfig } from './engagement/api-rules.js';
35
+ /** Turns automatic API tracking on or off; the `track_api` config flag is the usual way to set it. */
36
+ export declare const enableApiTracking: typeof enableApiTrackingImpl;
37
+ /** Reports an error the app caught, or an `error` / `unhandledrejection` event. */
38
+ export declare const AppticsTrackError: AppticsApi["AppticsTrackError"];
39
+ export declare const enableGlobalErrorHandler: typeof enableGlobalErrorHandlerImpl;
40
+ /** Registers the signed-in user; properties may be a plain object or UserBuilder.build(). */
41
+ export declare const AppticsSetUser: AppticsApi["AppticsSetUser"];
42
+ export declare const getUser: typeof getUserImpl;
43
+ export declare const setTrackingStatus: typeof setTrackingStatusImpl;
44
+ export declare const getTrackingStatus: typeof getTrackingStatusImpl;
45
+ export declare const flush: typeof flushImpl;
46
+ /** Records a line for the next feedback submission. Costs nothing until feedback is sent. */
47
+ export declare const addFeedbackLog: typeof addFeedbackLogImpl;
48
+ /** Records a diagnostics key/value, grouped under `heading`, for the next feedback submission. */
49
+ export declare const addDiagnosticsInfo: typeof addDiagnosticsInfoImpl;
50
+ /**
51
+ * Turns debug mode on; works before init(), and wins over `debug` in init().
52
+ *
53
+ * On, engagement, exception and log go to their `debug/` twins and the answers carry a request id
54
+ * and a link into the server's own logs. It switches itself off after an hour with nothing
55
+ * tracked, so a forgotten tab stops tracing on its own.
56
+ */
57
+ export declare const enableDebug: typeof enableDebugImpl;
58
+ /** Turns debug mode off; works before init(), and wins over `debug` in init(). */
59
+ export declare const disableDebug: typeof disableDebugImpl;
60
+ export declare const reset: typeof resetImpl;
61
+ /**
62
+ * Android's AppticsLogger, shown on the console's Remote Logger page. Off until
63
+ * `AppticsLogger.enable()`; then `AppticsLogger.v / d / i / w / e(tag, message, ...details)`.
64
+ */
65
+ export { AppticsLogger };
66
+ export { AP_FEEDBACK_TYPE, AP_TRACKING_STATUS, UserBuilder, users };
67
+ declare const apptics: AppticsApi;
68
+ export default apptics;