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