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