@zoho_apptics/apptics-js-sdk 1.0.2 → 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 -1
@@ -0,0 +1,340 @@
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
2
+ import { AP_LOG_MODULE, AP_CONFIG, AP_CONSTANTS } from '../core/constants.js';
3
+ import { _ZAP } from '../core/state.js';
4
+ import { logFailure, Console } from '../core/logger.js';
5
+ import { getNewUUID, nowMs, isEmpty } from '../core/utils.js';
6
+ import { engagement_transactions } from '../core/storage.js';
7
+ import { commitPendingSession, closeScreen } from './session.js';
8
+ import { getConnectionType, getNetworkStatus, getOrientation } from '../core/env.js';
9
+ import { canTrackEngagement } from '../core/consent.js';
10
+ import { validateEventOrGroup, validateJSON, validateScreenName } from '../core/validate.js';
11
+ import { noteDebugActivity } from '../core/debug.js';
12
+ import { hooks } from '../core/hooks.js';
13
+ import { hostAllowed, endpointIgnored, normalizeApiPath, groupedHost } from './api-rules.js';
14
+ import { asDefinedEvent } from './defined-events.js';
15
+ import { automaticScreenName } from './auto-screen.js';
16
+
17
+ /**
18
+ * Records an event: `trackEvent(name, group?, properties?)`, or a defined event with
19
+ * `trackEvent(DefinedEvents.AP_USER_LOGIN, properties?)`, which brings its own group.
20
+ *
21
+ * As on Android, an invalid name or group is not logged. A group left out is `default`, so the
22
+ * one-argument form keeps working; a group given but invalid is a mistake worth being told about,
23
+ * not one to file quietly under `default`.
24
+ */
25
+ function trackEvent(eventName, groupName, jsonObj) {
26
+ if (!canTrackEngagement('trackEvent')) { // no i18n
27
+ return;
28
+ }
29
+ var defined = asDefinedEvent(eventName);
30
+ if (defined) {
31
+ // the defined event's own name and group, which alone may carry the reserved AP_ prefix
32
+ jsonObj = (groupName && typeof groupName === 'object' ? groupName : jsonObj); // no i18n
33
+ groupName = defined.group;
34
+ eventName = defined.event;
35
+ }
36
+ else {
37
+ if (typeof eventName !== 'string' || !validateEventOrGroup(eventName)) { // no i18n
38
+ logFailure(AP_LOG_MODULE.EVENT, 'AppticsEvents.addEvent: Invalid eventName: ' + String(eventName)); // no i18n
39
+ return;
40
+ }
41
+ if (groupName === undefined || groupName === null || groupName === '') { // no i18n
42
+ groupName = AP_CONFIG.default_group_name;
43
+ }
44
+ else if (typeof groupName !== 'string' || !validateEventOrGroup(groupName)) { // no i18n
45
+ logFailure(AP_LOG_MODULE.EVENT, 'AppticsEvents.addEvent: Invalid groupName for "' + eventName + '": ' + String(groupName)); // no i18n
46
+ return;
47
+ }
48
+ }
49
+ jsonObj = jsonObj || {};
50
+ var _jsonObj = validateJSON(jsonObj);
51
+ // epoch time in seconds
52
+ var time = nowMs();
53
+ var eventData = {
54
+ id: getNewUUID(),
55
+ type: AP_CONSTANTS.EVENT,
56
+ group: groupName,
57
+ event: eventName,
58
+ starttime: time,
59
+ endtime: time,
60
+ edge: _ZAP._device_info.connectionType || getConnectionType(),
61
+ networkstatus: getNetworkStatus(),
62
+ screen: _ZAP._current_screen.screen || '', // no i18n
63
+ sessionstarttime: _ZAP._current_session.starttime || time,
64
+ customproperties: _jsonObj
65
+ };
66
+ // save the event
67
+ // the session may still be waiting out its minimum; this event is what earns it
68
+ commitPendingSession();
69
+ noteDebugActivity();
70
+ engagement_transactions.add(eventData);
71
+ Console.log('AppticsEvents.addEvent(\'' + eventName + '\',\'' + groupName + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
72
+ Console.dir(eventData);
73
+ }
74
+ /**
75
+ * The app's own screen view with a known id and start. The public inScreen takes the id and the
76
+ * time when the app calls it, so the id it hands back is the view's even when start-up is still
77
+ * running and this runs a moment later.
78
+ */
79
+ function inScreenAt(screenName, jsonObj, id, startedAt) {
80
+ if (!validateScreenName(screenName)) {
81
+ logFailure(AP_LOG_MODULE.SCREEN, 'AppticsScreenTracker.inScreen: Invalid screenName: ' + String(screenName)); // no i18n
82
+ return;
83
+ }
84
+ // the app named this screen itself: the automatic view of the same navigation gives way to it
85
+ hooks.screenTrackedByApp();
86
+ recordScreen(screenName, jsonObj, id, startedAt);
87
+ }
88
+ /** Ends the screen view inScreen() began, as Android's AppticsScreenTracker.outScreen(id). */
89
+ function outScreenAt(id, endedAt) {
90
+ if (isEmpty(id)) {
91
+ logFailure(AP_LOG_MODULE.SCREEN, 'AppticsScreenTracker.outScreen: no screen id given'); // no i18n
92
+ return;
93
+ }
94
+ closeScreen(id, endedAt);
95
+ }
96
+ /**
97
+ * Queues a screen view and makes it the current screen.
98
+ *
99
+ * The view is stored open (endtime 0) and closed when the next screen starts, when the page is
100
+ * hidden or closed, when its session ends, or by outScreen() — which is what gives it a duration.
101
+ * An open view is not sent until it is closed, as an open session is not.
102
+ */
103
+ function recordScreen(screenName, jsonObj, id, startedAt, implicit) {
104
+ if (!canTrackEngagement('inScreen')) { // no i18n
105
+ return;
106
+ }
107
+ if (!validateScreenName(screenName)) {
108
+ logFailure(AP_LOG_MODULE.SCREEN, 'AppticsScreenTracker.inScreen: Invalid screenName: ' + String(screenName)); // no i18n
109
+ return;
110
+ }
111
+ jsonObj = jsonObj || {};
112
+ var _jsonObj = validateJSON(jsonObj);
113
+ // epoch time in milliseconds
114
+ var time = typeof startedAt === 'number' ? startedAt : nowMs(); // no i18n
115
+ var screenData = {
116
+ id: id || getNewUUID(),
117
+ type: AP_CONSTANTS.SCREEN,
118
+ screen: screenName,
119
+ starttime: time,
120
+ endtime: 0,
121
+ edge: _ZAP._device_info.connectionType || getConnectionType(),
122
+ networkstatus: getNetworkStatus(),
123
+ orientation: getOrientation(),
124
+ sessionstarttime: _ZAP._current_session.starttime || time,
125
+ customproperties: _jsonObj
126
+ };
127
+ // the screen being left ends where this one starts
128
+ var previous = _ZAP._current_screen && _ZAP._current_screen.id;
129
+ if (previous && previous !== screenData.id) {
130
+ closeScreen(previous, time);
131
+ }
132
+ commitPendingSession();
133
+ noteDebugActivity();
134
+ engagement_transactions.add(screenData);
135
+ // `implicit`: a view recorded only so a heatmap record has one to belong to. Kept on the
136
+ // in-memory copy alone, never on the stored item, which goes to the server as it is
137
+ _ZAP._current_screen = implicit ? Object.assign({}, screenData, { implicit: true }) : screenData;
138
+ Console.log('AppticsScreenTracker.inScreen(\'' + screenName + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
139
+ Console.dir(screenData);
140
+ }
141
+ /**
142
+ * The screen a heatmap record belongs to, making sure engagement/add has a view of it: the console
143
+ * places a record by its screen, and a screen that was never sent as a view leaves it nowhere.
144
+ *
145
+ * A screen the app or automatic screen tracking named is used as it is — that one already has its
146
+ * view. Otherwise (automatic tracking off and no inScreen() call) the page is named the way
147
+ * automatic tracking would name it, and a view is recorded for it: once per screen, a new one when
148
+ * the path changes, and given way as soon as anything else names the screen.
149
+ */
150
+ function screenForRecord() {
151
+ var current = _ZAP._current_screen;
152
+ if (current && !isEmpty(current.screen) && !current.implicit) {
153
+ return String(current.screen);
154
+ }
155
+ var name = automaticScreenName() || '/'; // no i18n
156
+ if (!current || !current.id || current.screen !== name || current.endtime) {
157
+ recordScreen(name, undefined, undefined, undefined, true);
158
+ }
159
+ return name;
160
+ }
161
+ /**
162
+ * Back in view after being hidden: the hide ended the screen view, so the screen the user is on
163
+ * starts a new one, as an Android activity coming back to the foreground does.
164
+ */
165
+ function resumeScreen() {
166
+ var current = _ZAP._current_screen;
167
+ if (!current || !current.id || !current.endtime || isEmpty(current.screen)) {
168
+ return;
169
+ }
170
+ recordScreen(String(current.screen));
171
+ }
172
+ /**
173
+ * What the proxy accepts as an api item's hostname and path (security-regex.xml: api_hostname,
174
+ * api_urlpath). It validates these before the server sees them, and a rejected item is dropped
175
+ * there — nothing reaches the console and nothing says why. Checking here means trackApi can
176
+ * repair or refuse the value while there is still a caller to tell.
177
+ */
178
+ const API_HOSTNAME = /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
179
+ const API_PATH_DISALLOWED = /[^a-zA-Z0-9\-._*\/]+/g;
180
+ /**
181
+ * Rewrites a path to the characters the proxy allows, one `*` per run of anything else, so an id
182
+ * that happens to hold an email or an encoded character costs that segment's detail rather than
183
+ * the whole call. Segment boundaries survive, which is what the console groups by.
184
+ */
185
+ function sanitizeApiPath(path) {
186
+ if (isEmpty(path)) {
187
+ return '/'; // no i18n
188
+ }
189
+ var cleaned = path.replace(API_PATH_DISALLOWED, '*'); // no i18n
190
+ return cleaned.charAt(0) === '/' ? cleaned : '/' + cleaned; // no i18n
191
+ }
192
+ /** A path for a log line, without the query, which can carry tokens. */
193
+ function withoutQueryForLog(path) {
194
+ return String(path || '').split(/[?#]/)[0]; // no i18n
195
+ }
196
+ function trackApi(jsonObj) {
197
+ if (!canTrackEngagement('trackApi')) { // no i18n
198
+ return;
199
+ }
200
+ jsonObj = jsonObj || {};
201
+ // Extract raw values from jsonObj (from XHR/fetch hook or manual calls)
202
+ var rawUrl = jsonObj.url || jsonObj.path || '';
203
+ var method = (jsonObj.method || 'GET').toString();
204
+ var responsecode = (typeof jsonObj.responsecode === 'number') ? jsonObj.responsecode :
205
+ (typeof jsonObj.status === 'number') ? jsonObj.status : 0;
206
+ // the template requires this key, and fetch leaves statusText empty on HTTP/2, so a
207
+ // placeholder stands in rather than risking the item being refused for an empty value
208
+ var responsemessage = jsonObj.responsemessage || jsonObj.statusText || jsonObj.error || '-';
209
+ // Time fields (ms since epoch, consistent with other trackers)
210
+ var now = nowMs();
211
+ var starttime = (typeof jsonObj.starttime === 'number') ? jsonObj.starttime : now;
212
+ var endtime;
213
+ if (typeof jsonObj.endtime === 'number') {
214
+ endtime = jsonObj.endtime;
215
+ }
216
+ else if (typeof jsonObj.duration === 'number') {
217
+ endtime = starttime + jsonObj.duration;
218
+ }
219
+ else {
220
+ endtime = now;
221
+ }
222
+ // URL breakdown: scheme, hostname, port, path
223
+ var scheme = jsonObj.scheme || '';
224
+ var hostname = jsonObj.hostname || '';
225
+ var port = (typeof jsonObj.port === 'number') ? jsonObj.port : 0;
226
+ var path = jsonObj.path || '';
227
+ if (!isEmpty(rawUrl)) {
228
+ try {
229
+ var urlObj = new URL(rawUrl, window.location.origin);
230
+ if (isEmpty(scheme)) {
231
+ scheme = urlObj.protocol.replace(':', ''); // "https:"
232
+ }
233
+ if (isEmpty(hostname)) {
234
+ hostname = urlObj.hostname;
235
+ }
236
+ if (!port) {
237
+ if (urlObj.port) {
238
+ port = parseInt(urlObj.port, 10);
239
+ }
240
+ else {
241
+ port = (urlObj.protocol === 'https:') ? 443 : 80;
242
+ }
243
+ }
244
+ if (isEmpty(path)) {
245
+ path = urlObj.pathname;
246
+ }
247
+ }
248
+ catch (e) {
249
+ // Fallback: treat rawUrl as path if parsing fails
250
+ if (isEmpty(path)) {
251
+ path = rawUrl;
252
+ }
253
+ }
254
+ }
255
+ // only http and https exist as far as the proxy's api_scheme is concerned
256
+ scheme = (scheme === 'https' || scheme === 'http') ? scheme : 'https'; // no i18n
257
+ // a hostname the proxy refuses cannot be repaired without inventing one, so the call is
258
+ // dropped here and said out loud. localhost and bare IPs land here: api_hostname wants at
259
+ // least one dot and an alphabetic suffix, which is worth knowing when nothing shows up locally
260
+ if (!API_HOSTNAME.test(hostname)) {
261
+ logFailure(AP_LOG_MODULE.API, 'AppticsApiTracker: hostname not accepted by the server: ' + String(hostname)); // no i18n
262
+ return;
263
+ }
264
+ // configureApiTracking: the app's rules decide whether this call is recorded at all
265
+ if (!hostAllowed(hostname) || endpointIgnored(path)) {
266
+ Console.log('AppticsApiTracker: ' + hostname + withoutQueryForLog(path) + ' is left out by configureApiTracking'); // no i18n
267
+ return;
268
+ }
269
+ // and the name it is recorded under: ids become *, regional hosts one group, so the console
270
+ // groups calls by endpoint rather than by every id that went through it
271
+ path = sanitizeApiPath(normalizeApiPath(path));
272
+ hostname = groupedHost(hostname);
273
+ var sessionStart = _ZAP._current_session.starttime || starttime;
274
+ // no customproperties: apiWebSDK does not declare the key, unlike the screen and event
275
+ // templates, so anything extra the caller passes is context for the log line only
276
+ var apiData = {
277
+ id: getNewUUID(),
278
+ type: AP_CONSTANTS.API,
279
+ // Required by backend schema
280
+ scheme: scheme,
281
+ port: port,
282
+ hostname: hostname,
283
+ path: path,
284
+ method: method,
285
+ responsecode: responsecode,
286
+ responsemessage: responsemessage,
287
+ starttime: starttime,
288
+ endtime: endtime,
289
+ sessionstarttime: sessionStart,
290
+ networkstatus: getNetworkStatus(),
291
+ edge: _ZAP._device_info.connectionType || getConnectionType()
292
+ };
293
+ // save the api
294
+ commitPendingSession();
295
+ noteDebugActivity();
296
+ engagement_transactions.add(apiData);
297
+ Console.log('AppticsApiTracker(\'' + method + ' ' + scheme + '://' + hostname + path + '\')'); // no i18n
298
+ Console.dir(apiData);
299
+ }
300
+ /** Begun calls by id. Bounded, so an app that starts calls it never ends can't grow it forever. */
301
+ var openCalls = {};
302
+ var openOrder = [];
303
+ const MAX_OPEN_CALLS = 500;
304
+ /**
305
+ * Begins timing one API call, as Android's AppticsApiTracker.startTrackApi(url, method): returns
306
+ * the id to hand to endTrackApi once the response is in. Records nothing by itself.
307
+ */
308
+ function startTrackApi(url, method) {
309
+ var id = getNewUUID();
310
+ openCalls[id] = { url: String(url || ''), method: String(method || 'GET').toUpperCase(), starttime: nowMs() }; // no i18n
311
+ openOrder.push(id);
312
+ if (openOrder.length > MAX_OPEN_CALLS) {
313
+ delete openCalls[openOrder.shift()];
314
+ }
315
+ return id;
316
+ }
317
+ /**
318
+ * Finishes a call startTrackApi began and records it, timed from start to `endedAt`. The public
319
+ * wrapper takes `endedAt` when the app calls it, so a call ended while start-up is still running
320
+ * keeps its real duration even though it is recorded a moment later.
321
+ */
322
+ function endTrackApiAt(id, responseCode, responseMessage, endedAt) {
323
+ var open = openCalls[id];
324
+ if (!open) {
325
+ logFailure(AP_LOG_MODULE.API, 'AppticsApiTracker.endTrackApi: no call started with id ' + String(id)); // no i18n
326
+ return;
327
+ }
328
+ delete openCalls[id];
329
+ openOrder = openOrder.filter(function (one) { return one !== id; });
330
+ trackApi({
331
+ url: open.url,
332
+ method: open.method,
333
+ responsecode: typeof responseCode === 'number' ? responseCode : 0, // no i18n
334
+ responsemessage: responseMessage,
335
+ starttime: open.starttime,
336
+ endtime: endedAt,
337
+ });
338
+ }
339
+
340
+ export { endTrackApiAt, inScreenAt, outScreenAt, recordScreen, resumeScreen, screenForRecord, startTrackApi, trackApi, trackEvent };
@@ -0,0 +1,6 @@
1
+ import type { Dict, ServerResult } from '../types.js';
2
+ /**
3
+ * Sends pending errors to the Apptics API.
4
+ */
5
+ export declare function sendErrorData(forget?: boolean): Promise<void>;
6
+ export declare function requestToSendErrors(_payload: Dict, forget?: boolean): Promise<ServerResult>;
@@ -0,0 +1,70 @@
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (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 { error_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
+
11
+ /**
12
+ * Sends pending errors to the Apptics API.
13
+ */
14
+ function sendErrorData(forget) {
15
+ // errortracking off (getupdates): queued errors stay until tracking is back on or they expire
16
+ if (is_apptics_disabled() || !shouldTrackOrSendData() || !_ZAP._errorTracking) {
17
+ return Promise.resolve();
18
+ }
19
+ if (navigator.onLine == false) {
20
+ Console.warn('sendErrorData: No Network'); // no i18n
21
+ return Promise.resolve();
22
+ }
23
+ // if we're busy sending, retry in .5s
24
+ if (_ZAP._sending) {
25
+ defer(sendErrorData);
26
+ return Promise.resolve();
27
+ }
28
+ _ZAP._sending = true;
29
+ clearDefer();
30
+ return withSendLock('errors', function () {
31
+ return freshItems(error_transactions, function (item) { return item.happenedat != 0; }, function (item) { return item.happenedat; }).then(function (error_items) {
32
+ if (error_items.length == 0) {
33
+ return;
34
+ }
35
+ let exceptiondetails = [];
36
+ var sent = [];
37
+ cloneObject(error_items).forEach(function (item) {
38
+ sent.push({ ...item });
39
+ if (!item.hasOwnProperty('screenname')) { // no i18n
40
+ item.screenname = ''; // no i18n
41
+ }
42
+ delete item.id;
43
+ delete item.type;
44
+ exceptiondetails.push(item);
45
+ });
46
+ return awaitUnlessClosing(requestToSendErrors({ exceptiondetails: exceptiondetails }, forget).then(function () {
47
+ return error_transactions.remove(sent);
48
+ }, function (err) {
49
+ // the server will never accept this batch. Keeping it would block every later
50
+ // batch behind it for good, so it is dropped and said out loud
51
+ if (isPermanentRefusal(err)) {
52
+ warnAlways('apptics: the server refused these errors, so they were dropped: ' + String(err && err.message)); // no i18n
53
+ return error_transactions.remove(sent);
54
+ }
55
+ return Promise.reject(err);
56
+ }), forget, 'sendErrorData'); // no i18n
57
+ });
58
+ }).catch(function (err) {
59
+ Console.log('sendErrorData:' + err); // no i18n
60
+ }).then(function () {
61
+ _ZAP._sending = false;
62
+ });
63
+ }
64
+ // Function to send error data to server
65
+ function requestToSendErrors(_payload, forget) {
66
+ var label = 'requestToSendErrors'; // no i18n
67
+ return sendBatch('error', label, _payload, forget); // no i18n
68
+ }
69
+
70
+ export { requestToSendErrors, sendErrorData };
@@ -0,0 +1,4 @@
1
+ import type { Properties } from '../types.js';
2
+ export declare function trackError(errorEvent: any, jsonObj?: Properties): void;
3
+ export declare function enableGlobalErrorHandler(status?: boolean): void;
4
+ export declare function extractMethodNameFromStackTrace(stackTrace: string): string;
@@ -0,0 +1,123 @@
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
2
+ import { AP_CONSTANTS } from '../core/constants.js';
3
+ import { _ZAP } from '../core/state.js';
4
+ import { Console } from '../core/logger.js';
5
+ import { isEmpty, nowMs, getNewUUID } from '../core/utils.js';
6
+ import { error_transactions } from '../core/storage.js';
7
+ import { commitPendingSession } from '../engagement/session.js';
8
+ import { getConnectionType, getNetworkStatus } from '../core/env.js';
9
+ import { canTrackErrors } from '../core/consent.js';
10
+ import { validateJSON } from '../core/validate.js';
11
+ import { noteDebugActivity } from '../core/debug.js';
12
+
13
+ /**
14
+ * The stack to report. Three sources, in order of how much they tell us:
15
+ *
16
+ * `reason.stack` and `error.stack` are where a promise rejection and a window error event keep it.
17
+ * `errorEvent.stack` covers an Error handed straight to trackError — `trackError(new Error('x'))`
18
+ * is the first thing anyone writes, and reading only the wrapped forms lost its trace silently.
19
+ *
20
+ * Failing all of those, a cross-origin script error or a non-Error throw carries no stack at all,
21
+ * and the event's own filename, line and column are the only detail there is, so they are written
22
+ * as a one-frame stack rather than discarded.
23
+ */
24
+ function stackOf(errorEvent) {
25
+ var stack = errorEvent.reason?.stack || errorEvent.error?.stack || errorEvent.stack || ''; // no i18n
26
+ if (!isEmpty(stack)) {
27
+ return stack;
28
+ }
29
+ var file = errorEvent.filename || errorEvent.fileName || ''; // no i18n
30
+ if (isEmpty(file)) {
31
+ return ''; // no i18n
32
+ }
33
+ var line = errorEvent.lineno !== undefined ? errorEvent.lineno : errorEvent.lineNumber;
34
+ var column = errorEvent.colno !== undefined ? errorEvent.colno : errorEvent.columnNumber;
35
+ return 'at ' + file + (isEmpty(line) ? '' : ':' + line) + (isEmpty(column) ? '' : ':' + column); // no i18n
36
+ }
37
+ /** Said in place of a stack when there is none, so `message` is never sent empty. */
38
+ const NO_STACK = 'no stack available'; // no i18n
39
+ function trackError(errorEvent, jsonObj) {
40
+ if (!canTrackErrors('trackError')) { // no i18n
41
+ return;
42
+ }
43
+ if (isEmpty(errorEvent)) {
44
+ Console.warn('AppticsTrackError: Invalid error'); // no i18n
45
+ return;
46
+ }
47
+ var _jsonObj = validateJSON(jsonObj);
48
+ var stack = stackOf(errorEvent);
49
+ var methodName = extractMethodNameFromStackTrace(stack) || "";
50
+ var issuetype = errorEvent.type || "";
51
+ var thrown = errorEvent.reason || errorEvent.error || errorEvent.message || "";
52
+ // "Error: x at handler", or just "Error: x" when the stack names no method. issuename is
53
+ // required too, so something that carried no message at all is still named rather than blank
54
+ var described = !isEmpty(String(thrown)) ? String(thrown) : (!isEmpty(issuetype) ? String(issuetype) : 'Unknown error'); // no i18n
55
+ var message = described + (isEmpty(methodName) ? "" : ' at ' + methodName);
56
+ // epoch time in seconds
57
+ var time = nowMs();
58
+ var errorData = {
59
+ id: getNewUUID(),
60
+ type: AP_CONSTANTS.ERROR,
61
+ edge: _ZAP._device_info.connectionType || getConnectionType(),
62
+ networkstatus: getNetworkStatus(),
63
+ happenedat: time,
64
+ issuename: message,
65
+ // the template requires this key, so a stackless error still says something: an empty value
66
+ // risks the proxy refusing the item, and errors travel in batches
67
+ message: !isEmpty(stack) ? stack : (!isEmpty(String(thrown)) ? String(thrown) : NO_STACK),
68
+ screenname: _ZAP._current_screen.screen || window.location.pathname,
69
+ sessionstarttime: _ZAP._current_session.starttime || time,
70
+ customproperties: _jsonObj
71
+ };
72
+ // save the error
73
+ // an error is worth a session even in the first seconds of a visit
74
+ commitPendingSession();
75
+ noteDebugActivity();
76
+ error_transactions.add(errorData);
77
+ Console.log('AppticsTrackError(\'' + issuetype + '\',\'' + message + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
78
+ Console.dir(errorData);
79
+ }
80
+ function enableGlobalErrorHandler(status) {
81
+ _ZAP._isGlobalErrorTracking = typeof status === "boolean" ? status : true; // no i18n
82
+ }
83
+ function extractMethodNameFromStackTrace(stackTrace) {
84
+ let methodName = ''; // no i18n
85
+ if (isEmpty(stackTrace)) {
86
+ return methodName;
87
+ }
88
+ const lines = stackTrace.split('\n'); // no i18n
89
+ if (lines.length < 2) {
90
+ return methodName; // Not enough information in the stack trace
91
+ }
92
+ // The method name is typically included in the second line
93
+ const methodNamePattern1 = /at (\S+) \(/;
94
+ const methodNamePattern2 = /^(\w+)@/;
95
+ for (let line of lines) {
96
+ const match1 = line.match(methodNamePattern1);
97
+ const match2 = line.match(methodNamePattern2);
98
+ // For browsers like Chrome, Ula
99
+ if (match1 && match1[1]) {
100
+ // Return the method name
101
+ const urlRegex = /\((.*)\)/;
102
+ // Extract the URL from the error string
103
+ const urlMatch = urlRegex.exec(line);
104
+ // Extracted URL
105
+ const url = urlMatch ? urlMatch[1] : 'Unknown URL'; // no i18n
106
+ methodName = match1[1];
107
+ url.split('/').pop(); // no i18n
108
+ methodName = methodName; // + '@'+ fileNameAndLine;
109
+ break;
110
+ }
111
+ // For browsers like Safari
112
+ if (match2 && match2[1]) {
113
+ // Return the method name
114
+ methodName = match2[1];
115
+ line.split('@')[1].split('/').pop(); // no i18n
116
+ methodName = methodName; // + '@'+ fileNameAndLine; // no i18n
117
+ break;
118
+ }
119
+ }
120
+ return methodName;
121
+ }
122
+
123
+ export { enableGlobalErrorHandler, extractMethodNameFromStackTrace, trackError };
@@ -0,0 +1,31 @@
1
+ import type { Dict } from '../types.js';
2
+ /** Who the next submission is attributed to: the picked user, or nobody. */
3
+ export interface UserChoice {
4
+ anonymous: boolean;
5
+ userid: string;
6
+ groupid: string;
7
+ }
8
+ /** Read by core/transport when it builds the feedback request's parameters. */
9
+ export declare const userChoice: UserChoice;
10
+ /** Whether the next submission carries the stored logs and diagnostics. Unset means "decide from what is stored". */
11
+ export declare const logChoice: {
12
+ includeLog?: boolean;
13
+ includeDyninfo?: boolean;
14
+ };
15
+ /**
16
+ * The "User" dropdown above the description. It lists Anonymous plus every registered user, and
17
+ * starts on the active one (status 1) unless the caller asked for anonymous.
18
+ */
19
+ export declare function wireUserPicker(modal: HTMLElement, forceAnonymous: boolean): void;
20
+ /**
21
+ * The two "Include …" switches and the viewer behind their View links. A row is shown only when
22
+ * there is something to include, and is then on by default, so a submission carries what the app
23
+ * recorded unless the user turns it off.
24
+ */
25
+ export declare function wireLogControls(modal: HTMLElement, opts: Dict): void;
26
+ /**
27
+ * Drags the dialog by its header. The right/bottom placement from the stylesheet is converted to
28
+ * an explicit left/top on the first drag, so the offsets have something to add to, and the box is
29
+ * kept inside the viewport.
30
+ */
31
+ export declare function wireDrag(box: HTMLElement): void;