@zoho_apptics/apptics-js-sdk 2.0.0-beta → 2.0.0-beta.2

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 (236) hide show
  1. package/CHANGELOG.md +191 -21
  2. package/README.md +519 -333
  3. package/dist/apptics.esm.min.js +2 -2
  4. package/dist/apptics.esm.min.js.map +1 -1
  5. package/dist/apptics.min.js +2 -2
  6. package/dist/apptics.min.js.map +1 -1
  7. package/dist/chunks/lifecycle-C6m1W7jo.min.js +3 -0
  8. package/dist/chunks/lifecycle-C6m1W7jo.min.js.map +1 -0
  9. package/dist/chunks/send-CgUO82ne.min.js +3 -0
  10. package/dist/chunks/send-CgUO82ne.min.js.map +1 -0
  11. package/dist/cjs/core/attempts.js +1 -1
  12. package/dist/cjs/core/batch.js +2 -2
  13. package/dist/cjs/core/consent.d.ts +18 -4
  14. package/dist/cjs/core/consent.js +31 -15
  15. package/dist/cjs/core/constants.d.ts +57 -27
  16. package/dist/cjs/core/constants.js +52 -33
  17. package/dist/cjs/core/db.d.ts +12 -4
  18. package/dist/cjs/core/db.js +167 -34
  19. package/dist/cjs/core/debug.js +1 -1
  20. package/dist/cjs/core/env.d.ts +0 -3
  21. package/dist/cjs/core/env.js +1 -9
  22. package/dist/cjs/core/framework-id.d.ts +1 -1
  23. package/dist/cjs/core/framework-id.js +3 -3
  24. package/dist/cjs/core/guard.d.ts +9 -0
  25. package/dist/cjs/core/guard.js +27 -13
  26. package/dist/cjs/core/hooks.d.ts +7 -4
  27. package/dist/cjs/core/hooks.js +3 -3
  28. package/dist/cjs/core/locks.d.ts +9 -0
  29. package/dist/cjs/core/locks.js +22 -0
  30. package/dist/cjs/core/logger.d.ts +3 -4
  31. package/dist/cjs/core/logger.js +4 -40
  32. package/dist/cjs/core/state.d.ts +9 -6
  33. package/dist/cjs/core/state.js +3 -5
  34. package/dist/cjs/core/storage.d.ts +20 -5
  35. package/dist/cjs/core/storage.js +115 -20
  36. package/dist/cjs/core/stylesheet.d.ts +8 -0
  37. package/dist/cjs/core/stylesheet.js +29 -0
  38. package/dist/cjs/core/transport.d.ts +2 -0
  39. package/dist/cjs/core/transport.js +30 -18
  40. package/dist/cjs/core/utils.js +1 -1
  41. package/dist/cjs/core/validate.js +2 -2
  42. package/dist/cjs/device/appversion.d.ts +10 -6
  43. package/dist/cjs/device/appversion.js +38 -3
  44. package/dist/cjs/device/index.d.ts +28 -2
  45. package/dist/cjs/device/index.js +312 -44
  46. package/dist/cjs/engagement/api-rules.d.ts +32 -67
  47. package/dist/cjs/engagement/api-rules.js +144 -157
  48. package/dist/cjs/engagement/auto-api.js +2 -2
  49. package/dist/cjs/engagement/auto-screen.d.ts +2 -0
  50. package/dist/cjs/engagement/auto-screen.js +67 -3
  51. package/dist/cjs/engagement/defined-events.js +1 -1
  52. package/dist/cjs/engagement/send.js +12 -3
  53. package/dist/cjs/engagement/session.d.ts +29 -3
  54. package/dist/cjs/engagement/session.js +253 -26
  55. package/dist/cjs/engagement/track.d.ts +4 -4
  56. package/dist/cjs/engagement/track.js +58 -30
  57. package/dist/cjs/errors/send.js +2 -2
  58. package/dist/cjs/errors/track.d.ts +2 -1
  59. package/dist/cjs/errors/track.js +49 -7
  60. package/dist/cjs/feedback/controls.d.ts +12 -0
  61. package/dist/cjs/feedback/controls.js +104 -25
  62. package/dist/cjs/feedback/index.d.ts +8 -7
  63. package/dist/cjs/feedback/index.js +32 -13
  64. package/dist/cjs/feedback/logs.d.ts +13 -11
  65. package/dist/cjs/feedback/logs.js +57 -28
  66. package/dist/cjs/feedback/quartz.d.ts +18 -0
  67. package/dist/cjs/feedback/quartz.js +105 -0
  68. package/dist/cjs/feedback/send.d.ts +5 -2
  69. package/dist/cjs/feedback/send.js +30 -28
  70. package/dist/cjs/feedback/styles.d.ts +10 -0
  71. package/dist/cjs/feedback/styles.js +8 -0
  72. package/dist/cjs/feedback/template.d.ts +1 -1
  73. package/dist/cjs/feedback/template.js +20 -12
  74. package/dist/cjs/feedback/ui.d.ts +21 -6
  75. package/dist/cjs/feedback/ui.js +283 -70
  76. package/dist/cjs/heatmap/send.d.ts +2 -0
  77. package/dist/cjs/heatmap/send.js +3 -5
  78. package/dist/cjs/heatmap/track.d.ts +1 -1
  79. package/dist/cjs/heatmap/track.js +87 -16
  80. package/dist/cjs/index.d.ts +16 -63
  81. package/dist/cjs/index.js +144 -95
  82. package/dist/cjs/lifecycle.d.ts +21 -3
  83. package/dist/cjs/lifecycle.js +353 -89
  84. package/dist/cjs/remoteconfig/country.d.ts +4 -0
  85. package/dist/cjs/remoteconfig/country.js +73 -0
  86. package/dist/cjs/remoteconfig/custom.d.ts +7 -7
  87. package/dist/cjs/remoteconfig/custom.js +31 -51
  88. package/dist/cjs/remoteconfig/evaluate.d.ts +3 -3
  89. package/dist/cjs/remoteconfig/evaluate.js +56 -43
  90. package/dist/cjs/remoteconfig/fetch.d.ts +16 -9
  91. package/dist/cjs/remoteconfig/fetch.js +120 -39
  92. package/dist/cjs/remoteconfig/index.d.ts +19 -8
  93. package/dist/cjs/remoteconfig/index.js +81 -39
  94. package/dist/cjs/remoteconfig/store.d.ts +15 -7
  95. package/dist/cjs/remoteconfig/store.js +68 -35
  96. package/dist/cjs/remoteconfig/tz-countries.d.ts +8 -0
  97. package/dist/cjs/remoteconfig/tz-countries.js +13 -0
  98. package/dist/{esm/core → cjs/remotelog}/callsite.d.ts +11 -4
  99. package/dist/cjs/{core → remotelog}/callsite.js +30 -7
  100. package/dist/cjs/remotelog/gate.d.ts +2 -11
  101. package/dist/cjs/remotelog/gate.js +9 -35
  102. package/dist/cjs/remotelog/index.d.ts +4 -21
  103. package/dist/cjs/remotelog/logger.d.ts +11 -17
  104. package/dist/cjs/remotelog/logger.js +31 -59
  105. package/dist/cjs/remotelog/send.js +11 -11
  106. package/dist/cjs/remotelogger/index.d.ts +12 -0
  107. package/dist/cjs/remotelogger/index.js +18 -0
  108. package/dist/cjs/scheduler.d.ts +3 -1
  109. package/dist/cjs/scheduler.js +25 -22
  110. package/dist/cjs/types.d.ts +208 -90
  111. package/dist/cjs/user/index.js +16 -7
  112. package/dist/esm/core/attempts.js +1 -1
  113. package/dist/esm/core/batch.js +2 -2
  114. package/dist/esm/core/consent.d.ts +18 -4
  115. package/dist/esm/core/consent.js +32 -17
  116. package/dist/esm/core/constants.d.ts +57 -27
  117. package/dist/esm/core/constants.js +51 -33
  118. package/dist/esm/core/db.d.ts +12 -4
  119. package/dist/esm/core/db.js +164 -35
  120. package/dist/esm/core/debug.js +1 -1
  121. package/dist/esm/core/env.d.ts +0 -3
  122. package/dist/esm/core/env.js +2 -9
  123. package/dist/esm/core/framework-id.d.ts +1 -1
  124. package/dist/esm/core/framework-id.js +3 -3
  125. package/dist/esm/core/guard.d.ts +9 -0
  126. package/dist/esm/core/guard.js +27 -14
  127. package/dist/esm/core/hooks.d.ts +7 -4
  128. package/dist/esm/core/hooks.js +3 -3
  129. package/dist/esm/core/locks.d.ts +9 -0
  130. package/dist/esm/core/locks.js +20 -0
  131. package/dist/esm/core/logger.d.ts +3 -4
  132. package/dist/esm/core/logger.js +4 -40
  133. package/dist/esm/core/state.d.ts +9 -6
  134. package/dist/esm/core/state.js +3 -5
  135. package/dist/esm/core/storage.d.ts +20 -5
  136. package/dist/esm/core/storage.js +113 -21
  137. package/dist/esm/core/stylesheet.d.ts +8 -0
  138. package/dist/esm/core/stylesheet.js +27 -0
  139. package/dist/esm/core/transport.d.ts +2 -0
  140. package/dist/esm/core/transport.js +30 -19
  141. package/dist/esm/core/utils.js +1 -1
  142. package/dist/esm/core/validate.js +2 -2
  143. package/dist/esm/device/appversion.d.ts +10 -6
  144. package/dist/esm/device/appversion.js +37 -4
  145. package/dist/esm/device/index.d.ts +28 -2
  146. package/dist/esm/device/index.js +313 -49
  147. package/dist/esm/engagement/api-rules.d.ts +32 -67
  148. package/dist/esm/engagement/api-rules.js +143 -155
  149. package/dist/esm/engagement/auto-api.js +3 -3
  150. package/dist/esm/engagement/auto-screen.d.ts +2 -0
  151. package/dist/esm/engagement/auto-screen.js +67 -4
  152. package/dist/esm/engagement/defined-events.js +1 -1
  153. package/dist/esm/engagement/send.js +12 -3
  154. package/dist/esm/engagement/session.d.ts +29 -3
  155. package/dist/esm/engagement/session.js +251 -30
  156. package/dist/esm/engagement/track.d.ts +4 -4
  157. package/dist/esm/engagement/track.js +65 -37
  158. package/dist/esm/errors/send.js +2 -2
  159. package/dist/esm/errors/track.d.ts +2 -1
  160. package/dist/esm/errors/track.js +49 -7
  161. package/dist/esm/feedback/controls.d.ts +12 -0
  162. package/dist/esm/feedback/controls.js +103 -26
  163. package/dist/esm/feedback/index.d.ts +8 -7
  164. package/dist/esm/feedback/index.js +34 -13
  165. package/dist/esm/feedback/logs.d.ts +13 -11
  166. package/dist/esm/feedback/logs.js +59 -30
  167. package/dist/esm/feedback/quartz.d.ts +18 -0
  168. package/dist/esm/feedback/quartz.js +99 -0
  169. package/dist/esm/feedback/send.d.ts +5 -2
  170. package/dist/esm/feedback/send.js +31 -31
  171. package/dist/esm/feedback/styles.d.ts +10 -0
  172. package/dist/esm/feedback/styles.js +4 -0
  173. package/dist/esm/feedback/template.d.ts +1 -1
  174. package/dist/esm/feedback/template.js +20 -12
  175. package/dist/esm/feedback/ui.d.ts +21 -6
  176. package/dist/esm/feedback/ui.js +283 -71
  177. package/dist/esm/heatmap/send.d.ts +2 -0
  178. package/dist/esm/heatmap/send.js +3 -5
  179. package/dist/esm/heatmap/track.d.ts +1 -1
  180. package/dist/esm/heatmap/track.js +87 -16
  181. package/dist/esm/index.d.ts +16 -63
  182. package/dist/esm/index.js +148 -83
  183. package/dist/esm/lifecycle.d.ts +21 -3
  184. package/dist/esm/lifecycle.js +359 -98
  185. package/dist/esm/remoteconfig/country.d.ts +4 -0
  186. package/dist/esm/remoteconfig/country.js +70 -0
  187. package/dist/esm/remoteconfig/custom.d.ts +7 -7
  188. package/dist/esm/remoteconfig/custom.js +32 -52
  189. package/dist/esm/remoteconfig/evaluate.d.ts +3 -3
  190. package/dist/esm/remoteconfig/evaluate.js +57 -44
  191. package/dist/esm/remoteconfig/fetch.d.ts +16 -9
  192. package/dist/esm/remoteconfig/fetch.js +120 -39
  193. package/dist/esm/remoteconfig/index.d.ts +19 -8
  194. package/dist/esm/remoteconfig/index.js +82 -40
  195. package/dist/esm/remoteconfig/store.d.ts +15 -7
  196. package/dist/esm/remoteconfig/store.js +68 -36
  197. package/dist/esm/remoteconfig/tz-countries.d.ts +8 -0
  198. package/dist/esm/remoteconfig/tz-countries.js +11 -0
  199. package/dist/{cjs/core → esm/remotelog}/callsite.d.ts +11 -4
  200. package/dist/esm/{core → remotelog}/callsite.js +30 -8
  201. package/dist/esm/remotelog/gate.d.ts +2 -11
  202. package/dist/esm/remotelog/gate.js +10 -32
  203. package/dist/esm/remotelog/index.d.ts +4 -21
  204. package/dist/esm/remotelog/logger.d.ts +11 -17
  205. package/dist/esm/remotelog/logger.js +33 -61
  206. package/dist/esm/remotelog/send.js +13 -13
  207. package/dist/esm/remotelogger/index.d.ts +12 -0
  208. package/dist/esm/remotelogger/index.js +13 -0
  209. package/dist/esm/scheduler.d.ts +3 -1
  210. package/dist/esm/scheduler.js +26 -24
  211. package/dist/esm/types.d.ts +208 -90
  212. package/dist/esm/user/index.js +18 -8
  213. package/dist/feedback.esm.min.js +2 -2
  214. package/dist/feedback.esm.min.js.map +1 -1
  215. package/dist/remoteconfig.esm.min.js +3 -0
  216. package/dist/remoteconfig.esm.min.js.map +1 -0
  217. package/dist/remotelogger.esm.min.js +3 -0
  218. package/dist/remotelogger.esm.min.js.map +1 -0
  219. package/package.json +16 -17
  220. package/dist/chunks/batch-BU_mTpm1.min.js +0 -3
  221. package/dist/chunks/batch-BU_mTpm1.min.js.map +0 -1
  222. package/dist/chunks/logs-Bea5zUW_.min.js +0 -3
  223. package/dist/chunks/logs-Bea5zUW_.min.js.map +0 -1
  224. package/dist/cjs/auto-init.d.ts +0 -8
  225. package/dist/cjs/auto-init.js +0 -32
  226. package/dist/cjs/heatmap/index.d.ts +0 -16
  227. package/dist/cjs/heatmap/index.js +0 -22
  228. package/dist/cjs/remotelog/index.js +0 -55
  229. package/dist/esm/auto-init.d.ts +0 -8
  230. package/dist/esm/auto-init.js +0 -29
  231. package/dist/esm/heatmap/index.d.ts +0 -16
  232. package/dist/esm/heatmap/index.js +0 -17
  233. package/dist/esm/remotelog/index.js +0 -51
  234. package/dist/heatmap.esm.min.js +0 -3
  235. package/dist/heatmap.esm.min.js.map +0 -1
  236. package/dist/style.css +0 -1
@@ -1,25 +1,24 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
2
- import { REQUIRED_CONFIG, AP_CONFIG, CONFIG_ALIASES, DEFAULT_INACTIVE_MINUTES } from './core/constants.js';
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.2 | (c) Zoho Corporation Private Limited | see LICENSE */
2
+ import { REQUIRED_CONFIG, CONFIG_KEYS, AP_CONFIG, RETIRED_CONFIG_NAMES, DEFAULT_INACTIVE_MINUTES } from './core/constants.js';
3
3
  import { _ZAP } from './core/state.js';
4
- import { warnAlways, Console } from './core/logger.js';
5
- import { isEmpty, getNewUUID, isSuccess } from './core/utils.js';
6
- import { storageReady, user_defaults, tracking_preference, engagement_transactions, error_transactions } from './core/storage.js';
4
+ import { Console, warnAlways } from './core/logger.js';
5
+ import { isEmpty, isSuccess } from './core/utils.js';
6
+ import { storageReady, user_defaults, engagement_transactions, error_transactions, heatmap_transactions, remotelog_transactions, feedbacklog_transactions, tracking_preference } from './core/storage.js';
7
7
  import { hooks } from './core/hooks.js';
8
8
  import { applyDebugConfig } from './core/debug.js';
9
9
  import { getConnectionType, getOrientation } from './core/env.js';
10
- import { getTrackingStatus } from './core/consent.js';
11
- import { restoreServerSettings, checkAndUpdateDeviceInfo, refreshServerSettings, updateDeviceWithApptics, needsDevice, deviceReady } from './device/index.js';
12
- import { registerAppVersion, clearAppVersion } from './device/appversion.js';
13
- import { checkAndUpdateSession, updateLastActiveTimeOfSession, updateScreenWithEndtime, endCurrentSession } from './engagement/session.js';
10
+ import { restoreServerSettings, prepareDevice, startServerSettingChecks, updateDeviceWithApptics, needsDevice, deviceReady } from './device/index.js';
11
+ import { clearAppVersion, keyFingerprint, registerAppVersion, appVersionFailedForNetwork } from './device/appversion.js';
12
+ import { holdTabLock, checkAndUpdateSession, markTabInactive, markTabActive, updateScreenWithEndtime, endCurrentSession, resetInactivity } from './engagement/session.js';
14
13
  import { enableApiTracking } from './engagement/auto-api.js';
15
- import { enableScreenTracking } from './engagement/auto-screen.js';
14
+ import { enableScreenTracking, routePath } from './engagement/auto-screen.js';
16
15
  import { resumeScreen, trackEvent } from './engagement/track.js';
17
16
  import { trackError } from './errors/track.js';
18
17
  import { startTimers, flush, stopTimers } from './scheduler.js';
19
- import './remotelog/index.js';
18
+ import { sendStoredLogs, forgetStoredLogs } from './remotelog/send.js';
20
19
  import { getUserInfo } from './user/index.js';
21
20
  import { clearAttempts } from './core/attempts.js';
22
- import { sendStoredLogs, forgetStoredLogs } from './remotelog/send.js';
21
+ import { FRAMEWORK_ID } from './core/framework-id.js';
23
22
 
24
23
  let listenersAttached = false;
25
24
  /** The config init() was last called with; reset() starts again from it. */
@@ -38,16 +37,29 @@ function isBrowser() {
38
37
  }
39
38
  }
40
39
  /**
41
- * Canonical config: serviceUrl, zak, identifier, appversion.
42
- * The console snippet uses older names (base_domain, token), which are accepted here.
43
- * serviceUrl loses any trailing slash, because request paths add their own.
40
+ * The config as the SDK keeps it: base_url, auth_key and app_domain under their inside names
41
+ * (serviceUrl, zak, identifier; see CONFIG_KEYS), and appversion as text. An earlier name of a
42
+ * key is still read when the key itself is not given, with a warning naming the key to use.
43
+ * The base url loses any trailing slash, because request paths add their own.
44
44
  */
45
45
  function normaliseConfig(config) {
46
46
  var settings = Object.assign({}, config);
47
- Object.keys(CONFIG_ALIASES).forEach(function (key) {
48
- if (isEmpty(settings[key]) && !isEmpty(settings[CONFIG_ALIASES[key]])) {
49
- settings[key] = settings[CONFIG_ALIASES[key]];
47
+ Object.keys(CONFIG_KEYS).forEach(function (key) {
48
+ var inside = CONFIG_KEYS[key];
49
+ if (!isEmpty(config[key])) {
50
+ settings[inside] = config[key];
51
+ return;
50
52
  }
53
+ delete settings[inside];
54
+ Object.keys(RETIRED_CONFIG_NAMES).forEach(function (old) {
55
+ if (RETIRED_CONFIG_NAMES[old] !== key || isEmpty(config[old])) {
56
+ return;
57
+ }
58
+ warnAlways('Apptics.init: ' + old + ' is now ' + key + '; rename it in your config'); // no i18n
59
+ if (isEmpty(settings[inside])) {
60
+ settings[inside] = config[old];
61
+ }
62
+ });
51
63
  });
52
64
  if (!isEmpty(settings.serviceUrl)) {
53
65
  settings.serviceUrl = String(settings.serviceUrl).replace(/\/+$/, ''); // no i18n
@@ -73,21 +85,62 @@ const APPVERSION_PLACEHOLDERS = ['YOUR_APP_VERSION', 'YOUR_BUILD_OR_APP_VERSION'
73
85
  function isAppVersionPlaceholder(version) {
74
86
  return APPVERSION_PLACEHOLDERS.indexOf(version.toUpperCase()) !== -1;
75
87
  }
88
+ /**
89
+ * The framework id this SDK reports to serviceUrl. The id built into the package is the one
90
+ * sdk-apptics.zoho.com issued at publish time, and localzoho servers don't know it: any
91
+ * localzoho serviceUrl reports 5970 instead, the id apptics.localzoho.com issued for 2.0.0-beta
92
+ * (Browser), so the same package also runs there.
93
+ */
94
+ const LOCALZOHO_FRAMEWORK_ID = 5970;
95
+ function frameworkIdFor(serviceUrl) {
96
+ var host = serviceUrl;
97
+ try {
98
+ host = new URL(serviceUrl).hostname;
99
+ }
100
+ catch (err) {
101
+ // not a url: judge the text as it is
102
+ }
103
+ return host.indexOf('localzoho') !== -1 ? LOCALZOHO_FRAMEWORK_ID : FRAMEWORK_ID; // no i18n
104
+ }
76
105
  /** Which app this browser's stored data belongs to, before app/addversion has named it. */
77
106
  function appKey(settings) {
78
107
  return settings.serviceUrl + '|' + settings.identifier; // no i18n
79
108
  }
109
+ /**
110
+ * The app version stored data belongs to, read before app/addversion replaces it: what tells a
111
+ * new release of the app from a reload. '' on a first visit.
112
+ */
113
+ var previousAppVersion = ''; // no i18n
80
114
  /**
81
115
  * Data stored for another app on this origin is discarded before anything else reads it,
82
116
  * including the stored app/addversion answer that would otherwise hand out that app's ids.
117
+ *
118
+ * Another app is a different base_url, app_domain or auth_key from the ones the stored data was
119
+ * registered with. Everything goes, the queues included, and start-up runs as on a first visit:
120
+ * app/addversion, device/add and the rest. Resolves once the store is empty.
83
121
  */
84
122
  function discardOtherAppData(settings) {
85
- var stored = user_defaults.item('appkey'); // no i18n
86
- if (!isEmpty(stored) && stored !== appKey(settings)) {
87
- Console.log('init: stored data belongs to another app, resetting'); // no i18n
88
- clearStorage();
123
+ // which app it is, and which version, are in app_version_info (serviceUrl, identifier, a
124
+ // fingerprint of zak, and appversion): no keys of their own. 2.0.0-beta's appkey and
125
+ // appversion are read this once
126
+ var info = user_defaults.item('app_version_info') || {}; // no i18n
127
+ var stored = !isEmpty(info.serviceUrl) && !isEmpty(info.identifier)
128
+ ? appKey(info)
129
+ : user_defaults.item('appkey'); // no i18n
130
+ previousAppVersion = String((!isEmpty(info.appversion) ? info.appversion : user_defaults.item('appversion')) || ''); // no i18n
131
+ user_defaults.add('appkey', undefined); // no i18n
132
+ user_defaults.add('appversion', undefined); // no i18n
133
+ var otherApp = !isEmpty(stored) && stored !== appKey(settings);
134
+ // a build that kept no fingerprint cannot tell: hasAppVersion() registers again, and records one
135
+ var otherKey = !isEmpty(info.zakhash) && info.zakhash !== keyFingerprint(settings.zak);
136
+ if (!otherApp && !otherKey) {
137
+ return Promise.resolve();
89
138
  }
90
- user_defaults.add('appkey', appKey(settings)); // no i18n
139
+ Console.log('init: stored data was registered with another ' + (otherApp ? 'base_url or app_domain' : 'auth_key') + ', clearing it and starting as a first visit'); // no i18n
140
+ previousAppVersion = ''; // no i18n
141
+ // the remote config values read from the store, which is about to go
142
+ hooks.resetRemoteConfig();
143
+ return clearStorage();
91
144
  }
92
145
  /**
93
146
  * Timing an app may tune in init(). Each has a range: a value outside it is ignored with a
@@ -131,27 +184,32 @@ function applyIntervals(config) {
131
184
  }
132
185
  }
133
186
  }
187
+ /** Everything the SDK stores: settings, ids and every queue. Resolves once it is all gone. */
134
188
  function clearStorage() {
135
189
  clearAttempts();
136
190
  user_defaults.clear();
137
- engagement_transactions.clear();
138
- error_transactions.clear();
191
+ return Promise.all([
192
+ engagement_transactions, error_transactions, heatmap_transactions, remotelog_transactions, feedbacklog_transactions,
193
+ ].map(function (queue) {
194
+ return queue.clear();
195
+ })).then(function () {
196
+ return undefined;
197
+ });
139
198
  }
140
199
  /**
141
- * Starts the SDK. The config object is required: pass it directly, or let the console snippet
142
- * supply it (auto-init reads window.appticssettings._defaultoptions and calls init with it).
143
- * Returns true when the config has serviceUrl, zak, identifier and appversion and start-up has
200
+ * Starts the SDK. The config object is required: the SDK starts only when the app calls this.
201
+ * Returns true when the config has base_url, auth_key, app_domain and appversion and start-up has
144
202
  * begun (see ready()), false when it was already running, the config is missing or incomplete, or
145
203
  * there is no browser. Start-up calls app/addversion with those four, which answers with aaid, the
146
204
  * portal and project ids and the rest; then device/add registers the browser, as in 1.x.
147
205
  */
148
206
  function init(config) {
149
207
  if (!config || typeof config !== 'object') { // no i18n
150
- warnAlways('Apptics.init: a config object is required, e.g. apptics.init({ serviceUrl, zak, identifier, appversion })'); // no i18n
208
+ warnAlways('Apptics.init: a config object is required, e.g. Apptics.init({ base_url, auth_key, app_domain, appversion })'); // no i18n
151
209
  return false;
152
210
  }
153
211
  if (_ZAP._initialised) {
154
- Console.warn('Apptics.init: already initialised; call apptics.reset() to start again'); // no i18n
212
+ Console.warn('Apptics.init: already initialised; call Apptics.reset() to start again'); // no i18n
155
213
  return false;
156
214
  }
157
215
  if (!isBrowser()) {
@@ -160,7 +218,7 @@ function init(config) {
160
218
  }
161
219
  var settings = normaliseConfig(config);
162
220
  var missing = REQUIRED_CONFIG.filter(function (key) {
163
- return isEmpty(settings[key]);
221
+ return isEmpty(settings[CONFIG_KEYS[key] || key]);
164
222
  });
165
223
  if (missing.length > 0) {
166
224
  warnAlways('Apptics.init: missing required config: ' + missing.join(', ')); // no i18n
@@ -174,16 +232,14 @@ function init(config) {
174
232
  _ZAP._defaultoptions = settings;
175
233
  // every request goes to serviceUrl; there is no other host to learn about
176
234
  _ZAP._defaultoptions.base_domain = settings.serviceUrl;
235
+ // decided per init(), so a reset() or a page on another host never keeps the last one
236
+ _ZAP._frameworkID = frameworkIdFor(settings.serviceUrl);
177
237
  // marks the SDK as started for the checks that need it; app/addversion replaces it with aaid
178
238
  _ZAP._token = settings.identifier;
179
239
  // console_log: the SDK's own activity in the browser console
180
240
  if (!isEmpty(config.console_log)) {
181
241
  _ZAP._config.debug = !!config.console_log;
182
242
  }
183
- // track_session: false records no sessions, as Android's automaticSessionsTrackingStatus
184
- _ZAP._config.track_session = typeof config.track_session === 'boolean' ? config.track_session : AP_CONFIG.track_session; // no i18n
185
- // show_logs: the app's own logger lines in the browser console, as Android's showLogs
186
- _ZAP._config.show_logs = typeof config.show_logs === 'boolean' ? config.show_logs : AP_CONFIG.show_logs; // no i18n
187
243
  if (typeof config.ignore_do_not_track === 'boolean') { // no i18n
188
244
  _ZAP._config.ignore_do_not_track = config.ignore_do_not_track;
189
245
  }
@@ -196,10 +252,14 @@ function init(config) {
196
252
  applyDebugConfig(_ZAP._config.debug_api);
197
253
  // what to call this device in the debug stream; see devicename in core/transport.ts
198
254
  _ZAP._config.device_name = typeof config.device_name === 'string' ? config.device_name : AP_CONFIG.device_name; // no i18n
199
- // remote logging: the SDK's own diagnostics, sent to console/addlog
200
- _ZAP._config.remote_log = AP_CONFIG.remote_log;
201
- if (typeof config.remote_log === 'boolean') { // no i18n
202
- _ZAP._config.remote_log = config.remote_log;
255
+ // the SDK no longer reports its own failures to the Remote Logger page: only the lines an app
256
+ // logs with AppticsLogger go there
257
+ if (config.remote_log !== undefined) {
258
+ warnAlways('Apptics.init: remote_log is no longer read; the Remote Logger page shows the lines your app logs with AppticsLogger'); // no i18n
259
+ }
260
+ // AppticsLogger lines go to the Remote Logger page only, never to the browser console
261
+ if (config.show_logs !== undefined) {
262
+ warnAlways('Apptics.init: show_logs is no longer read; AppticsLogger lines are not printed to the browser console'); // no i18n
203
263
  }
204
264
  // automatic api tracking: the app's own requests, wrapped. Which hosts it records is
205
265
  // configureApiTracking's to say; the two keys that once did it are refused out loud rather
@@ -207,22 +267,42 @@ function init(config) {
207
267
  _ZAP._config.track_api = AP_CONFIG.track_api;
208
268
  ['track_api_hosts', 'track_api_exclude_hosts'].forEach(function (key) {
209
269
  if (config[key] !== undefined) {
210
- warnAlways('Apptics.init: ' + key + ' is no longer supported; use configureApiTracking(new ApiTrackingBuilder().' // no i18n
270
+ warnAlways('Apptics.init: ' + key + ' is no longer supported; use AppticsApi.configure(new AppticsApi.ApiTrackingBuilder().' // no i18n
211
271
  + (key === 'track_api_hosts' ? 'allowOnlyDomains' : 'ignoreDomains') + '(...).build())'); // no i18n
212
272
  }
213
273
  });
274
+ // screen recording is set up in the console now, and arrives with getsdkconfig
275
+ ['DC', 'quartz_workspace_key', 'quartz_department_key'].forEach(function (key) {
276
+ if (config[key] !== undefined) {
277
+ warnAlways('Apptics.init: ' + key + ' is no longer read; screen recording in the feedback form is set up from the Apptics console'); // no i18n
278
+ }
279
+ });
280
+ // sessions are what everything else is recorded in; an app that wants nothing tracked uses
281
+ // AppticsSettings.setTrackingStatus instead
282
+ if (config.track_session !== undefined) {
283
+ warnAlways('Apptics.init: track_session is no longer read; sessions are always recorded. To stop tracking, use AppticsSettings.setTrackingStatus(AppticsSettings.AP_TRACKING_STATUS.NO_TRACKING)'); // no i18n
284
+ }
214
285
  if (typeof config.track_api === 'boolean') { // no i18n
215
286
  _ZAP._config.track_api = config.track_api;
216
287
  }
217
288
  enableApiTracking(_ZAP._config.track_api);
218
- // heatmap capture: a no-op unless the app imported the heatmap entry, which sets the hook
289
+ // heatmap capture: switched through the hook heatmap/track.ts sets
219
290
  _ZAP._config.track_heatmap = typeof config.track_heatmap === 'boolean' // no i18n
220
291
  ? config.track_heatmap
221
292
  : AP_CONFIG.track_heatmap;
222
- hooks.enableHeatmap(_ZAP._config.track_heatmap);
293
+ hooks.trackHeatmap(_ZAP._config.track_heatmap);
223
294
  // automatic screen tracking, on unless the config says false: switched on by start(), once a
224
295
  // screen view can be stored
225
296
  _ZAP._config.track_screen = typeof config.track_screen === 'boolean' ? config.track_screen : AP_CONFIG.track_screen; // no i18n
297
+ _ZAP._config.screen_routes = screenRouteRules(config.screen_routes);
298
+ // uncaught errors and tagged clicks, on unless the config says false: the listeners check
299
+ // these as each error or click happens
300
+ _ZAP._config.track_error = typeof config.track_error === 'boolean' ? config.track_error : AP_CONFIG.track_error; // no i18n
301
+ _ZAP._config.track_click = typeof config.track_click === 'boolean' ? config.track_click : AP_CONFIG.track_click; // no i18n
302
+ // a theme in the config wins; without one, what AppticsFeedback.setFeedbackTheme set stays
303
+ if (config.feedback_theme === 'light' || config.feedback_theme === 'dark' || config.feedback_theme === 'auto') { // no i18n
304
+ _ZAP._config.feedback_theme = config.feedback_theme;
305
+ }
226
306
  applyIntervals(config);
227
307
  lastConfig = config;
228
308
  _ZAP._initialised = true;
@@ -240,22 +320,11 @@ function init(config) {
240
320
  _ZAP._pending = [];
241
321
  return false;
242
322
  }
243
- discardOtherAppData(_ZAP._defaultoptions);
244
- // app/addversion is the first call and everything waits on it: it is what turns the four
245
- // values init() was given into aaid, the portal and project ids every url carries, and
246
- // appreleaseversionid, which metaWebSDKV2 refuses a payload without
247
- return registerAppVersion().then(function (registered) {
323
+ return discardOtherAppData(_ZAP._defaultoptions).then(function () {
248
324
  if (myGeneration !== generation) {
249
325
  return false;
250
326
  }
251
- if (!registered) {
252
- warnAlways('Apptics.init: app/addversion did not return the app ids, not starting'); // no i18n
253
- _ZAP._initialised = false;
254
- _ZAP._token = ''; // no i18n
255
- _ZAP._pending = [];
256
- return false;
257
- }
258
- return start(config, myGeneration);
327
+ return startUp(config, myGeneration);
259
328
  });
260
329
  }).catch(function (err) {
261
330
  Console.error('Apptics.init: start-up failed', err); // no i18n
@@ -263,42 +332,177 @@ function init(config) {
263
332
  });
264
333
  return true;
265
334
  }
335
+ /** Start-up from app/addversion on, once the store holds only this app's data. */
336
+ function startUp(config, myGeneration) {
337
+ // app/addversion is the first call and everything waits on it: it is what turns the four
338
+ // values init() was given into aaid, the portal and project ids every url carries, and
339
+ // appreleaseversionid, which metaWebSDKV2 refuses a payload without
340
+ return registerAppVersion().then(function (registered) {
341
+ if (myGeneration !== generation) {
342
+ return false;
343
+ }
344
+ if (!registered && appVersionFailedForNetwork()) {
345
+ // a first visit with no network: start once it is back, keeping what the app
346
+ // tracks until then queued, rather than giving up for the rest of the page
347
+ warnAlways('Apptics.init: offline, so app/addversion is tried again when the browser is back online; calls made until then are kept'); // no i18n
348
+ return registerWhenOnline(myGeneration).then(function (ok) {
349
+ if (myGeneration !== generation) {
350
+ return false;
351
+ }
352
+ if (!ok) {
353
+ _ZAP._initialised = false;
354
+ _ZAP._token = ''; // no i18n
355
+ _ZAP._pending = [];
356
+ return false;
357
+ }
358
+ return start(config, myGeneration);
359
+ });
360
+ }
361
+ if (!registered) {
362
+ warnAlways('Apptics.init: app/addversion did not return the app ids, not starting'); // no i18n
363
+ _ZAP._initialised = false;
364
+ _ZAP._token = ''; // no i18n
365
+ _ZAP._pending = [];
366
+ return false;
367
+ }
368
+ return start(config, myGeneration);
369
+ });
370
+ }
371
+ /** How often a start-up waiting on the network tries app/addversion again, besides on `online`. */
372
+ const OFFLINE_RETRY_MS = 60 * 1000;
373
+ /**
374
+ * app/addversion again once the network is back: on the `online` event, and every minute for a
375
+ * network that came back without one. Resolves true once registered, false when the server
376
+ * refuses it or a reset()/new init() took over.
377
+ */
378
+ function registerWhenOnline(myGeneration) {
379
+ return new Promise(function (resolve) {
380
+ var timer = null;
381
+ var trying = false;
382
+ function done(ok) {
383
+ window.removeEventListener('online', attempt); // no i18n
384
+ if (timer !== null)
385
+ clearInterval(timer);
386
+ resolve(ok);
387
+ }
388
+ function attempt() {
389
+ if (myGeneration !== generation) {
390
+ done(false);
391
+ return;
392
+ }
393
+ if (trying || navigator.onLine === false) {
394
+ return;
395
+ }
396
+ trying = true;
397
+ registerAppVersion().then(function (registered) {
398
+ trying = false;
399
+ if (registered) {
400
+ done(true);
401
+ }
402
+ else if (!appVersionFailedForNetwork()) {
403
+ warnAlways('Apptics.init: app/addversion did not return the app ids, not starting'); // no i18n
404
+ done(false);
405
+ }
406
+ });
407
+ }
408
+ window.addEventListener('online', attempt); // no i18n
409
+ timer = setInterval(attempt, OFFLINE_RETRY_MS);
410
+ });
411
+ }
412
+ /**
413
+ * screen_routes as init() keeps it: each entry checked, an exact path cleaned up, a regex given as
414
+ * a string compiled. An entry that cannot be used is left out, and said, rather than matching
415
+ * something its author did not mean.
416
+ */
417
+ function screenRouteRules(routes) {
418
+ if (!Array.isArray(routes)) {
419
+ return [];
420
+ }
421
+ var out = [];
422
+ routes.forEach(function (route) {
423
+ if (typeof route === 'string') { // no i18n
424
+ if (route.trim() !== '') { // no i18n
425
+ out.push({ kind: 'pattern', pattern: route.trim() }); // no i18n
426
+ }
427
+ return;
428
+ }
429
+ var rule = route && typeof route === 'object' ? route : null; // no i18n
430
+ var name = rule && typeof rule.name === 'string' ? rule.name.trim() : ''; // no i18n
431
+ if (rule && name !== '' && typeof rule.equals === 'string' && rule.equals.trim() !== '') { // no i18n
432
+ out.push({ kind: 'equals', path: routePath(rule.equals.trim()), name: name }); // no i18n
433
+ return;
434
+ }
435
+ if (rule && name !== '' && rule.regex !== undefined) {
436
+ var regex = null;
437
+ if (rule.regex instanceof RegExp) {
438
+ regex = rule.regex;
439
+ }
440
+ else if (typeof rule.regex === 'string' && rule.regex !== '') { // no i18n
441
+ try {
442
+ regex = new RegExp(rule.regex);
443
+ }
444
+ catch (err) {
445
+ regex = null;
446
+ }
447
+ }
448
+ if (regex) {
449
+ out.push({ kind: 'regex', regex: regex, name: name }); // no i18n
450
+ return;
451
+ }
452
+ }
453
+ warnAlways('Apptics.init: screen_routes entry ' + safeJson(route) + ' is left out: give a route pattern, { equals, name } or { regex, name }'); // no i18n
454
+ });
455
+ return out;
456
+ }
457
+ /** An entry as the warning shows it: a RegExp as its source, which JSON.stringify would hide. */
458
+ function safeJson(value) {
459
+ try {
460
+ return JSON.stringify(value, function (_key, one) { return one instanceof RegExp ? String(one) : one; }) || String(value);
461
+ }
462
+ catch (err) {
463
+ return String(value);
464
+ }
465
+ }
266
466
  /** Resolves true once start-up has finished, false if init() refused or storage is unusable. */
267
467
  function ready() {
268
468
  return _ZAP._readyPromise || Promise.resolve(false);
269
469
  }
270
470
  function start(config, myGeneration) {
471
+ // this tab's lock, before any screen view: it is how other tabs tell this one is still open
472
+ holdTabLock();
271
473
  // app/addversion named the app; data stored for another one was discarded before it ran
272
474
  _ZAP._token = String(_ZAP._defaultoptions.aaid);
273
- user_defaults.add('token', _ZAP._token); // no i18n
274
475
  // last known server settings (getupdates) apply before anything is tracked
275
476
  restoreServerSettings();
276
- // required by init(), so _defaultoptions.appversion is always the app's own version, as the
277
- // text normaliseConfig made of it: there is no default to fall back to, and the stored value
278
- // below is kept only to notice when the version changes
279
- var uuid = user_defaults.item('uuid'); // no i18n
280
- if (isEmpty(uuid)) {
281
- uuid = getNewUUID();
282
- user_defaults.add('uuid', uuid); // no i18n
283
- }
284
- _ZAP._uuid = uuid;
285
- var stored_appversion = user_defaults.item('appversion'); // no i18n
286
- if (isEmpty(stored_appversion)) {
287
- user_defaults.add('appversion', _ZAP._defaultoptions.appversion); // no i18n
288
- stored_appversion = _ZAP._defaultoptions.appversion;
289
- }
290
- var appVersionChanged = stored_appversion != _ZAP._defaultoptions.appversion;
477
+ // the user this browser was tracking as, before getsdkconfig is asked: it carries their ids
478
+ getUserInfo();
479
+ // a new release of the app, against the version the stored data was for (see
480
+ // discardOtherAppData): a first visit has none, and is no change
481
+ var appVersionChanged = previousAppVersion !== '' && previousAppVersion != _ZAP._defaultoptions.appversion; // no i18n
291
482
  _ZAP._ignoreDnt = _ZAP._config.ignore_do_not_track;
292
- tracking_preference.update_status(getTrackingStatus());
293
- checkAndUpdateDeviceInfo();
294
- // returning visit: ask the server for its settings now instead of waiting 2 minutes
295
- // (first visit: device/add triggers it once the ids exist)
296
- refreshServerSettings();
483
+ // the visitor's own stored choice, not getTrackingStatus(): that answers NO_TRACKING while Do
484
+ // Not Track is on, and storing it made one such visit switch tracking off for good
485
+ tracking_preference.update_status(tracking_preference.opt_in_status());
486
+ // the uuid and device info, and a registration on a first visit, under a lock shared with the
487
+ // site's other tabs (see prepareDevice)
488
+ return prepareDevice().then(function () {
489
+ if (myGeneration !== generation) {
490
+ return false;
491
+ }
492
+ return startTracking(appVersionChanged, myGeneration);
493
+ });
494
+ }
495
+ /** The rest of start-up, once the device is set up. */
496
+ function startTracking(appVersionChanged, myGeneration) {
497
+ // getsdkconfig, once per page load and then at each answer's next_check_after. A first visit
498
+ // asked as soon as device/add succeeded (registerDeviceWithApptics) and this does nothing; a
499
+ // returning visit, whose device is registered, asks now, after app/addversion. Until it
500
+ // answers, the settings saved by the last visit apply (restoreServerSettings, in start())
501
+ startServerSettingChecks();
297
502
  if (appVersionChanged) {
298
503
  _ZAP._device_info.appversionid = ''; // no i18n
299
504
  updateDeviceWithApptics(_ZAP._device_info).then(function (result) {
300
505
  if (isSuccess(result)) {
301
- user_defaults.add('appversion', _ZAP._defaultoptions.appversion); // no i18n
302
506
  Console.log('init: appversion updated to ' + _ZAP._defaultoptions.appversion); // no i18n
303
507
  }
304
508
  }).catch(function (err) {
@@ -309,7 +513,6 @@ function start(config, myGeneration) {
309
513
  _ZAP._device_info.connectionType = getConnectionType();
310
514
  attachListeners();
311
515
  _ZAP._properties.orientation = getOrientation();
312
- getUserInfo();
313
516
  return checkAndUpdateSession().then(function () {
314
517
  if (myGeneration !== generation) {
315
518
  return false;
@@ -344,12 +547,13 @@ function start(config, myGeneration) {
344
547
  function reset() {
345
548
  generation++;
346
549
  stopTimers();
550
+ resetInactivity();
347
551
  // the wrappers come off with everything else; the init() below puts them back if the config
348
552
  // asked for them, so a reset that has no config to restore leaves XHR and fetch untouched
349
553
  enableApiTracking(false);
350
554
  enableScreenTracking(false);
351
555
  clearAppVersion();
352
- hooks.enableHeatmap(false);
556
+ hooks.trackHeatmap(false);
353
557
  hooks.resetHeatmap();
354
558
  hooks.resetRemoteConfig();
355
559
  clearStorage();
@@ -365,7 +569,7 @@ function reset() {
365
569
  _ZAP._apid = ''; // no i18n
366
570
  _ZAP._mapid = ''; // no i18n
367
571
  _ZAP._sending = false;
368
- Console.log('apptics.reset()'); // no i18n
572
+ Console.log('Apptics.reset()'); // no i18n
369
573
  // start again with the config init() was called with
370
574
  if (lastConfig) {
371
575
  init(lastConfig);
@@ -374,9 +578,42 @@ function reset() {
374
578
  function isActive() {
375
579
  return _ZAP._ready;
376
580
  }
377
- // Declarative click tracking: <el apeventaction="name" apeventprops='{"k":"v"}'> inside <el apgroupaction="group">
581
+ /**
582
+ * AppticsEvent.setClickTracking: click tracking from markup on or off, as the `track_click`
583
+ * config sets it. The listener stays; it checks the switch on every click.
584
+ */
585
+ function setClickTracking(enabled) {
586
+ _ZAP._config.track_click = typeof enabled === 'boolean' ? enabled : true; // no i18n
587
+ Console.log('click tracking: ' + (_ZAP._config.track_click ? 'on' : 'off')); // no i18n
588
+ }
589
+ /**
590
+ * AppticsScreen.setScreenTracking: automatic screen tracking on or off, as the `track_screen`
591
+ * config sets it. Applied now once the SDK is running; before that it is only noted, and start()
592
+ * switches tracking to it. Like the other switches, the next init() (a reset() too) goes back to
593
+ * what its config says.
594
+ */
595
+ function setScreenTracking(enabled) {
596
+ _ZAP._config.track_screen = typeof enabled === 'boolean' ? enabled : true; // no i18n
597
+ if (isActive()) {
598
+ enableScreenTracking(_ZAP._config.track_screen);
599
+ }
600
+ }
601
+ /**
602
+ * A click-tracking attribute, in either spelling: `apeventaction`, or `data-apeventaction`, which
603
+ * is valid HTML and what React and TypeScript accept without declaring the attribute.
604
+ */
605
+ function trackingAttribute(el, name) {
606
+ var value = el.getAttribute(name);
607
+ return value !== null && value !== '' ? value : el.getAttribute('data-' + name); // no i18n
608
+ }
609
+ /** `[name]` and `[data-name]`, empty values left out, for closest(). */
610
+ function trackingSelector(name) {
611
+ return '[' + name + ']:not([' + name + '=""]),[data-' + name + ']:not([data-' + name + '=""])'; // no i18n
612
+ }
613
+ // Declarative click tracking: <el apeventaction="name" apeventprops='{"k":"v"}'> inside <el apgroupaction="group">,
614
+ // or the same attributes with a data- prefix
378
615
  function onDocumentClick(event) {
379
- if (!window.appticsReadyStatus || !isActive()) {
616
+ if (!window.appticsReadyStatus || !isActive() || !_ZAP._config.track_click) {
380
617
  return;
381
618
  }
382
619
  var start = event.target;
@@ -386,15 +623,16 @@ function onDocumentClick(event) {
386
623
  if (!start) {
387
624
  return;
388
625
  }
389
- var targetElem = start.closest('[apeventaction]:not([apeventaction=""])'); // no i18n
626
+ var targetElem = start.closest(trackingSelector('apeventaction')); // no i18n
390
627
  if (!targetElem) {
391
628
  return;
392
629
  }
393
- var eventName = targetElem.getAttribute('apeventaction') || ''; // no i18n
394
- var groupElem = targetElem.closest('[apgroupaction]'); // no i18n
395
- var groupName = (groupElem && groupElem.getAttribute('apgroupaction')) || AP_CONFIG.default_group_name; // no i18n
630
+ var eventName = trackingAttribute(targetElem, 'apeventaction') || ''; // no i18n
631
+ var groupElem = targetElem.closest(trackingSelector('apgroupaction')); // no i18n
632
+ // no apgroupaction around it: trackEvent refuses it and says the group is missing
633
+ var groupName = (groupElem && trackingAttribute(groupElem, 'apgroupaction')) || undefined; // no i18n
396
634
  var eventProps = {};
397
- var rawProps = targetElem.getAttribute('apeventprops'); // no i18n
635
+ var rawProps = trackingAttribute(targetElem, 'apeventprops'); // no i18n
398
636
  if (!isEmpty(rawProps)) {
399
637
  try {
400
638
  eventProps = JSON.parse(rawProps);
@@ -416,10 +654,18 @@ function onDocumentClick(event) {
416
654
  function onPageHidden() {
417
655
  if (!isActive())
418
656
  return;
657
+ // the moment the user left: the session ends at it if they are not back within inactiveduration
658
+ markTabInactive(sendEndedSession);
419
659
  updateScreenWithEndtime();
420
660
  flush(true);
421
661
  Console.log('page hidden: closing the open screen and flushing'); // no i18n
422
662
  }
663
+ /** A session that inactivity ended while the tab was away: sent now, not when the user returns. */
664
+ function sendEndedSession() {
665
+ if (!isActive())
666
+ return;
667
+ flush();
668
+ }
423
669
  /**
424
670
  * The tab is closing, or the page is being replaced or frozen into the back/forward cache.
425
671
  * The session ends here, and everything queued goes out by keepalive, since a normal
@@ -451,14 +697,18 @@ function attachListeners() {
451
697
  window.addEventListener('blur', function () {
452
698
  if (!isActive())
453
699
  return;
700
+ // focus moving into an iframe on the page blurs the window too, and the user is still here
701
+ if (document.activeElement && document.activeElement.tagName === 'IFRAME')
702
+ return; // no i18n
454
703
  Console.log('Window lost focus, user switched to another window or application'); // no i18n
455
- updateLastActiveTimeOfSession();
704
+ markTabInactive(sendEndedSession);
456
705
  });
457
706
  // Track when the window gains focus (user switches back to the window)
458
707
  window.addEventListener('focus', function () {
459
708
  if (!isActive())
460
709
  return;
461
710
  Console.log('Window regained focus, user switched back to the window'); // no i18n
711
+ markTabActive();
462
712
  checkAndUpdateSession();
463
713
  startTimers();
464
714
  });
@@ -469,6 +719,7 @@ function attachListeners() {
469
719
  if (!isActive() || !event.persisted)
470
720
  return;
471
721
  Console.log('restored from the back/forward cache: starting a new session'); // no i18n
722
+ markTabActive();
472
723
  // the screen the page was left on starts a new view, in the new session
473
724
  checkAndUpdateSession().then(resumeScreen);
474
725
  startTimers();
@@ -480,12 +731,12 @@ function attachListeners() {
480
731
  onPageHidden();
481
732
  return;
482
733
  }
483
- // back in view: the server's tracking flags may have changed while we were away
484
- Console.log('page visible: refreshing the server settings'); // no i18n
734
+ // back in view. getsdkconfig is not asked here: only at its next_check_after, on a tab's
735
+ // first page load, and when a user is registered or getupdates reports a change
736
+ markTabActive();
485
737
  // hiding ended the screen view; the screen the user is back on starts a new one, after the
486
738
  // session is settled so the view belongs to the right one
487
739
  checkAndUpdateSession().then(resumeScreen);
488
- refreshServerSettings();
489
740
  });
490
741
  // if we are running in cordova use ondeviceready otherwise onload
491
742
  window.addEventListener(window.cordova ? 'deviceready' : 'load', function () {
@@ -500,8 +751,8 @@ function attachListeners() {
500
751
  // until it has them: register before flushing, and flush once that has landed
501
752
  if (needsDevice()) {
502
753
  Console.log('back online: registering this device before sending'); // no i18n
503
- checkAndUpdateDeviceInfo();
504
- deviceReady().then(function () {
754
+ // the same locked path as start-up, so two tabs coming back online register it once
755
+ prepareDevice().then(deviceReady).then(function () {
505
756
  if (!isActive())
506
757
  return;
507
758
  flush();
@@ -519,16 +770,26 @@ function attachListeners() {
519
770
  });
520
771
  }
521
772
  window.addEventListener('unhandledrejection', function (event) {
522
- if (isActive() && _ZAP._isGlobalErrorTracking) {
773
+ if (isActive() && _ZAP._config.track_error) {
523
774
  trackError(event);
524
775
  }
525
776
  });
777
+ // in the capture phase: a <script>, <img> or <link> that fails to load fires `error` on the
778
+ // element, and it does not bubble, so a bubbling listener never hears of it. One listener
779
+ // hears both kinds, the window's own errors included, so none is counted twice
526
780
  window.addEventListener('error', function (event) {
527
- if (isActive() && _ZAP._isGlobalErrorTracking) {
528
- trackError(event);
781
+ if (!isActive() || !_ZAP._config.track_error) {
782
+ return;
529
783
  }
530
- });
784
+ var target = event.target;
785
+ if (target && target !== window && typeof target.tagName === 'string') { // no i18n
786
+ // named by describeTarget: Event "error" on <script src="...">
787
+ trackError({ type: 'error', reason: event }); // no i18n
788
+ return;
789
+ }
790
+ trackError(event);
791
+ }, true);
531
792
  window.addEventListener('click', onDocumentClick, true); // no i18n
532
793
  }
533
794
 
534
- export { init, ready, reset };
795
+ export { init, ready, reset, screenRouteRules, setClickTracking, setScreenTracking };