@zoho_apptics/apptics-js-sdk 2.0.0-beta.1 → 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 (183) hide show
  1. package/CHANGELOG.md +80 -16
  2. package/README.md +187 -100
  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 +1 -2
  13. package/dist/cjs/core/consent.d.ts +13 -3
  14. package/dist/cjs/core/consent.js +24 -10
  15. package/dist/cjs/core/constants.d.ts +22 -17
  16. package/dist/cjs/core/constants.js +22 -19
  17. package/dist/cjs/core/db.js +1 -1
  18. package/dist/cjs/core/debug.js +1 -1
  19. package/dist/cjs/core/env.d.ts +0 -3
  20. package/dist/cjs/core/env.js +1 -9
  21. package/dist/cjs/core/framework-id.d.ts +1 -1
  22. package/dist/cjs/core/framework-id.js +3 -3
  23. package/dist/cjs/core/guard.js +1 -1
  24. package/dist/cjs/core/hooks.d.ts +5 -0
  25. package/dist/cjs/core/hooks.js +2 -1
  26. package/dist/cjs/core/locks.js +1 -1
  27. package/dist/cjs/core/logger.js +1 -1
  28. package/dist/cjs/core/state.d.ts +6 -4
  29. package/dist/cjs/core/state.js +2 -4
  30. package/dist/cjs/core/storage.js +1 -4
  31. package/dist/cjs/core/stylesheet.js +1 -1
  32. package/dist/cjs/core/transport.js +13 -16
  33. package/dist/cjs/core/utils.js +1 -1
  34. package/dist/cjs/core/validate.js +1 -1
  35. package/dist/cjs/device/appversion.d.ts +10 -1
  36. package/dist/cjs/device/appversion.js +26 -3
  37. package/dist/cjs/device/index.d.ts +17 -2
  38. package/dist/cjs/device/index.js +195 -27
  39. package/dist/cjs/engagement/api-rules.js +1 -1
  40. package/dist/cjs/engagement/auto-api.js +1 -1
  41. package/dist/cjs/engagement/auto-screen.js +1 -1
  42. package/dist/cjs/engagement/defined-events.js +1 -1
  43. package/dist/cjs/engagement/send.js +7 -3
  44. package/dist/cjs/engagement/session.d.ts +17 -0
  45. package/dist/cjs/engagement/session.js +95 -4
  46. package/dist/cjs/engagement/track.d.ts +4 -4
  47. package/dist/cjs/engagement/track.js +15 -12
  48. package/dist/cjs/errors/send.js +2 -2
  49. package/dist/cjs/errors/track.d.ts +2 -1
  50. package/dist/cjs/errors/track.js +6 -4
  51. package/dist/cjs/feedback/controls.d.ts +6 -0
  52. package/dist/cjs/feedback/controls.js +74 -25
  53. package/dist/cjs/feedback/index.js +1 -1
  54. package/dist/cjs/feedback/logs.d.ts +3 -6
  55. package/dist/cjs/feedback/logs.js +8 -18
  56. package/dist/cjs/feedback/quartz.js +1 -1
  57. package/dist/cjs/feedback/send.d.ts +2 -2
  58. package/dist/cjs/feedback/send.js +4 -27
  59. package/dist/cjs/feedback/styles.js +2 -2
  60. package/dist/cjs/feedback/template.d.ts +1 -1
  61. package/dist/cjs/feedback/template.js +9 -1
  62. package/dist/cjs/feedback/ui.d.ts +7 -0
  63. package/dist/cjs/feedback/ui.js +160 -25
  64. package/dist/cjs/heatmap/send.d.ts +2 -0
  65. package/dist/cjs/heatmap/send.js +3 -5
  66. package/dist/cjs/heatmap/track.js +1 -1
  67. package/dist/cjs/index.d.ts +1 -2
  68. package/dist/cjs/index.js +54 -17
  69. package/dist/cjs/lifecycle.d.ts +13 -2
  70. package/dist/cjs/lifecycle.js +122 -54
  71. package/dist/cjs/remoteconfig/country.js +1 -1
  72. package/dist/cjs/remoteconfig/custom.js +1 -1
  73. package/dist/cjs/remoteconfig/evaluate.js +1 -1
  74. package/dist/cjs/remoteconfig/fetch.d.ts +17 -10
  75. package/dist/cjs/remoteconfig/fetch.js +126 -31
  76. package/dist/cjs/remoteconfig/index.d.ts +13 -4
  77. package/dist/cjs/remoteconfig/index.js +36 -20
  78. package/dist/cjs/remoteconfig/store.d.ts +15 -7
  79. package/dist/cjs/remoteconfig/store.js +68 -35
  80. package/dist/cjs/remoteconfig/tz-countries.js +1 -1
  81. package/dist/cjs/{core → remotelog}/callsite.d.ts +5 -4
  82. package/dist/cjs/{core → remotelog}/callsite.js +6 -5
  83. package/dist/cjs/remotelog/gate.js +1 -1
  84. package/dist/cjs/remotelog/logger.js +2 -2
  85. package/dist/cjs/remotelog/send.js +2 -2
  86. package/dist/cjs/remotelogger/index.js +1 -1
  87. package/dist/cjs/scheduler.d.ts +3 -1
  88. package/dist/cjs/scheduler.js +24 -21
  89. package/dist/cjs/types.d.ts +61 -16
  90. package/dist/cjs/user/index.js +6 -2
  91. package/dist/esm/core/attempts.js +1 -1
  92. package/dist/esm/core/batch.js +1 -2
  93. package/dist/esm/core/consent.d.ts +13 -3
  94. package/dist/esm/core/consent.js +24 -11
  95. package/dist/esm/core/constants.d.ts +22 -17
  96. package/dist/esm/core/constants.js +22 -19
  97. package/dist/esm/core/db.js +1 -1
  98. package/dist/esm/core/debug.js +1 -1
  99. package/dist/esm/core/env.d.ts +0 -3
  100. package/dist/esm/core/env.js +2 -9
  101. package/dist/esm/core/framework-id.d.ts +1 -1
  102. package/dist/esm/core/framework-id.js +3 -3
  103. package/dist/esm/core/guard.js +1 -1
  104. package/dist/esm/core/hooks.d.ts +5 -0
  105. package/dist/esm/core/hooks.js +2 -1
  106. package/dist/esm/core/locks.js +1 -1
  107. package/dist/esm/core/logger.js +1 -1
  108. package/dist/esm/core/state.d.ts +6 -4
  109. package/dist/esm/core/state.js +2 -4
  110. package/dist/esm/core/storage.js +1 -4
  111. package/dist/esm/core/stylesheet.js +1 -1
  112. package/dist/esm/core/transport.js +13 -16
  113. package/dist/esm/core/utils.js +1 -1
  114. package/dist/esm/core/validate.js +1 -1
  115. package/dist/esm/device/appversion.d.ts +10 -1
  116. package/dist/esm/device/appversion.js +26 -4
  117. package/dist/esm/device/index.d.ts +17 -2
  118. package/dist/esm/device/index.js +195 -30
  119. package/dist/esm/engagement/api-rules.js +1 -1
  120. package/dist/esm/engagement/auto-api.js +1 -1
  121. package/dist/esm/engagement/auto-screen.js +1 -1
  122. package/dist/esm/engagement/defined-events.js +1 -1
  123. package/dist/esm/engagement/send.js +7 -3
  124. package/dist/esm/engagement/session.d.ts +17 -0
  125. package/dist/esm/engagement/session.js +93 -5
  126. package/dist/esm/engagement/track.d.ts +4 -4
  127. package/dist/esm/engagement/track.js +16 -13
  128. package/dist/esm/errors/send.js +2 -2
  129. package/dist/esm/errors/track.d.ts +2 -1
  130. package/dist/esm/errors/track.js +6 -4
  131. package/dist/esm/feedback/controls.d.ts +6 -0
  132. package/dist/esm/feedback/controls.js +74 -26
  133. package/dist/esm/feedback/index.js +1 -1
  134. package/dist/esm/feedback/logs.d.ts +3 -6
  135. package/dist/esm/feedback/logs.js +8 -18
  136. package/dist/esm/feedback/quartz.js +1 -1
  137. package/dist/esm/feedback/send.d.ts +2 -2
  138. package/dist/esm/feedback/send.js +6 -29
  139. package/dist/esm/feedback/styles.js +2 -2
  140. package/dist/esm/feedback/template.d.ts +1 -1
  141. package/dist/esm/feedback/template.js +9 -1
  142. package/dist/esm/feedback/ui.d.ts +7 -0
  143. package/dist/esm/feedback/ui.js +161 -26
  144. package/dist/esm/heatmap/send.d.ts +2 -0
  145. package/dist/esm/heatmap/send.js +3 -5
  146. package/dist/esm/heatmap/track.js +1 -1
  147. package/dist/esm/index.d.ts +1 -2
  148. package/dist/esm/index.js +57 -20
  149. package/dist/esm/lifecycle.d.ts +13 -2
  150. package/dist/esm/lifecycle.js +126 -60
  151. package/dist/esm/remoteconfig/country.js +1 -1
  152. package/dist/esm/remoteconfig/custom.js +1 -1
  153. package/dist/esm/remoteconfig/evaluate.js +1 -1
  154. package/dist/esm/remoteconfig/fetch.d.ts +17 -10
  155. package/dist/esm/remoteconfig/fetch.js +127 -32
  156. package/dist/esm/remoteconfig/index.d.ts +13 -4
  157. package/dist/esm/remoteconfig/index.js +36 -20
  158. package/dist/esm/remoteconfig/store.d.ts +15 -7
  159. package/dist/esm/remoteconfig/store.js +68 -36
  160. package/dist/esm/remoteconfig/tz-countries.js +1 -1
  161. package/dist/esm/{core → remotelog}/callsite.d.ts +5 -4
  162. package/dist/esm/{core → remotelog}/callsite.js +6 -5
  163. package/dist/esm/remotelog/gate.js +1 -1
  164. package/dist/esm/remotelog/logger.js +2 -2
  165. package/dist/esm/remotelog/send.js +2 -2
  166. package/dist/esm/remotelogger/index.js +1 -1
  167. package/dist/esm/scheduler.d.ts +3 -1
  168. package/dist/esm/scheduler.js +25 -23
  169. package/dist/esm/types.d.ts +61 -16
  170. package/dist/esm/user/index.js +7 -3
  171. package/dist/feedback.esm.min.js +2 -2
  172. package/dist/feedback.esm.min.js.map +1 -1
  173. package/dist/remoteconfig.esm.min.js +3 -0
  174. package/dist/remoteconfig.esm.min.js.map +1 -0
  175. package/dist/remotelogger.esm.min.js +3 -0
  176. package/dist/remotelogger.esm.min.js.map +1 -0
  177. package/package.json +3 -2
  178. package/dist/chunks/lifecycle-VYr43KcB.min.js +0 -3
  179. package/dist/chunks/lifecycle-VYr43KcB.min.js.map +0 -1
  180. package/dist/cjs/auto-init.d.ts +0 -8
  181. package/dist/cjs/auto-init.js +0 -32
  182. package/dist/esm/auto-init.d.ts +0 -8
  183. package/dist/esm/auto-init.js +0 -29
@@ -28,7 +28,22 @@ export declare function getUpdatesFromServer(): Promise<Dict>;
28
28
  */
29
29
  export declare function restoreServerSettings(): void;
30
30
  /**
31
- * Fetches the server settings now if the device is registered. On a first visit the ids don't
32
- * exist yet; registerDeviceWithApptics calls this as soon as device/add succeeds.
31
+ * next_check_after as a time: the server's value is milliseconds, either a timestamp or a wait
32
+ * from now. Anything below 1e12 (2001 as a timestamp) can only be a wait.
33
+ */
34
+ export declare function nextCheckTime(value: unknown, now: number): number;
35
+ /**
36
+ * Asks for the server settings now if the device is registered, then schedules the next ask
37
+ * for the time the answer gave in next_check_after. On a first visit the ids don't exist yet;
38
+ * registerDeviceWithApptics calls this as soon as device/add succeeds.
33
39
  */
34
40
  export declare function refreshServerSettings(): void;
41
+ /**
42
+ * Start-up's getsdkconfig. Asked on a first visit and on the first page load in each tab; any
43
+ * other load (a reload, a link to another page of the site, back/forward) uses the saved answer
44
+ * and waits for its next_check_after, unless that has already passed. Nothing happens if one has
45
+ * gone out already on this load (after device/add).
46
+ */
47
+ export declare function startServerSettingChecks(): void;
48
+ /** reset(): no more checks, and the next init() asks again straight away. */
49
+ export declare function stopServerSettingChecks(): void;
@@ -1,4 +1,4 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.1 | (c) Zoho Corporation Private Limited | see LICENSE */
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.2 | (c) Zoho Corporation Private Limited | see LICENSE */
2
2
  'use strict';
3
3
 
4
4
  var constants = require('../core/constants.js');
@@ -108,10 +108,18 @@ function checkAndUpdateDeviceInfo() {
108
108
  storage.user_defaults.add('device_info', state._ZAP._device_info); // no i18n
109
109
  addOrUpdateDeviceID(state._ZAP._device_info, previous);
110
110
  }
111
+ /**
112
+ * Whether device/add has registered this device: it has a deviceid. anonymousid is not required:
113
+ * not every Apptics server sends one, nothing the SDK sends carries it, and requiring it had a
114
+ * server that leaves it out register the same device again on every page load.
115
+ */
116
+ function isRegistered(info) {
117
+ return !!info && !utils.isEmpty(info.deviceid);
118
+ }
111
119
  function addOrUpdateDeviceID(device_info, previous) {
112
120
  var old_device_info = previous !== undefined ? previous : storage.user_defaults.item('device_info') || {}; // no i18n
113
121
  // Compare the old and new device infos and update the server if anything changed in the new.
114
- if (device_info.deviceid == null || device_info.frameworkid == null || device_info.anonymousid == null) {
122
+ if (!isRegistered(device_info) || device_info.frameworkid == null) {
115
123
  device_info.frameworkid = state._ZAP._frameworkID;
116
124
  logger.Console.log('addOrUpdateDeviceID: Initiated register the device'); // no i18n
117
125
  registerDeviceWithApptics().then(function (result_device_info) {
@@ -188,7 +196,7 @@ function useStoredUuid() {
188
196
  function adoptRegisteredDevice() {
189
197
  return storage.reloadSettings(['device_info']).then(function () {
190
198
  var stored = storage.user_defaults.item('device_info') || {}; // no i18n
191
- if (utils.isEmpty(stored.deviceid) || utils.isEmpty(stored.anonymousid)) {
199
+ if (!isRegistered(stored)) {
192
200
  return null;
193
201
  }
194
202
  // the device is registered under the uuid the other tab sent
@@ -213,7 +221,7 @@ function adoptRegisteredDevice() {
213
221
  */
214
222
  function prepareDevice() {
215
223
  var cached = storage.user_defaults.item('device_info') || {}; // no i18n
216
- if (!utils.isEmpty(cached.deviceid) && !utils.isEmpty(cached.anonymousid)) {
224
+ if (isRegistered(cached)) {
217
225
  useStoredUuid();
218
226
  checkAndUpdateDeviceInfo();
219
227
  return Promise.resolve();
@@ -278,7 +286,7 @@ function registerDeviceWithApptics() {
278
286
  if (offline) {
279
287
  return offline;
280
288
  }
281
- if (state._ZAP._device_info.deviceid && state._ZAP._device_info.anonymousid) {
289
+ if (isRegistered(state._ZAP._device_info)) {
282
290
  return Promise.resolve(state._ZAP._device_info);
283
291
  }
284
292
  return new Promise((resolve, reject) => {
@@ -306,8 +314,10 @@ function registerDeviceWithApptics() {
306
314
  }).then(function (result) {
307
315
  settleRegistration(true, result);
308
316
  resolve(result);
309
- // first visit: the ids exist now, so fetch the server settings right away
310
- refreshServerSettings();
317
+ // first visit: the ids exist now, so fetch the server settings right away. A device
318
+ // registered again later (its details or the framework id changed) asks nothing more:
319
+ // start-up has already asked on this page load
320
+ startServerSettingChecks();
311
321
  }).catch(function (err) {
312
322
  settleRegistration(false, err);
313
323
  reject(err);
@@ -339,6 +349,12 @@ function getUpdatesFromServer() {
339
349
  return transport.postToApptics('sdkconfig', '', transport.apiHeaders(), label).then(function (result) {
340
350
  var data = result.data || {};
341
351
  applyServerFlags(data);
352
+ if (state._ZAP._nextConfigCheck === 0) {
353
+ // no next_check_after: getupdates_interval from now, stored so a later page load
354
+ // can tell whether the answer is still current
355
+ state._ZAP._nextConfigCheck = utils.nowMs() + state.getConfig('getupdates_interval'); // no i18n
356
+ state._ZAP._trackinfo.next_check_after = state._ZAP._nextConfigCheck;
357
+ }
342
358
  // the visitor's tracking status lives in trackinfo too, and is not the server's to replace
343
359
  var saved = storage.storedTrackingStatus();
344
360
  storage.user_defaults.add('trackinfo', saved === undefined ? state._ZAP._trackinfo : Object.assign({}, state._ZAP._trackinfo, { tracking_status: saved })); // no i18n
@@ -374,6 +390,10 @@ function applyServerFlags(data) {
374
390
  if (data.hasOwnProperty('tracking_enabled')) { // no i18n
375
391
  state._ZAP._shouldTrack = data.tracking_enabled !== false;
376
392
  }
393
+ else if (data.hasOwnProperty('shouldtrack')) { // no i18n
394
+ // what this SDK saves, read back on the next visit
395
+ state._ZAP._shouldTrack = data.shouldtrack !== false;
396
+ }
377
397
  else {
378
398
  // the old shape: archived versions and untracked customer groups
379
399
  var versionArchived = !data.hasOwnProperty('versionarchivestatus') || utils.isEmpty(data.versionarchivestatus)
@@ -383,9 +403,14 @@ function applyServerFlags(data) {
383
403
  state._ZAP._shouldTrack = versionArchived || !customerGroupTracking;
384
404
  }
385
405
  if (hasModules) {
386
- // sessions carry screens, events and api calls, so module 11 gates all of engagement
387
- state._ZAP._engagementTracking = moduleEnabled(status, constants.SDK_MODULE.SESSION);
388
- state._ZAP._errorTracking = moduleEnabled(status, constants.SDK_MODULE.CRASH);
406
+ // each kind of engagement item has its own module, so the server can turn one off alone
407
+ state._ZAP._engagementTracking = {
408
+ event: moduleEnabled(status, constants.SDK_MODULE.EVENT),
409
+ api: moduleEnabled(status, constants.SDK_MODULE.API),
410
+ screen: moduleEnabled(status, constants.SDK_MODULE.SCREEN),
411
+ session: moduleEnabled(status, constants.SDK_MODULE.SESSION),
412
+ };
413
+ state._ZAP._errorTracking = moduleEnabled(status, constants.SDK_MODULE.JS_ERROR);
389
414
  state._ZAP._heatmapTracking = moduleEnabled(status, constants.SDK_MODULE.HEATMAP);
390
415
  state._ZAP._remoteLoggerEnabled = moduleEnabled(status, constants.SDK_MODULE.REMOTE_LOGGER);
391
416
  state._ZAP._feedbackEnabled = moduleEnabled(status, constants.SDK_MODULE.FEEDBACK);
@@ -394,11 +419,22 @@ function applyServerFlags(data) {
394
419
  state._ZAP._remoteConfigEnabled = moduleEnabled(status, constants.SDK_MODULE.REMOTE_CONFIG);
395
420
  }
396
421
  else {
397
- state._ZAP._engagementTracking = data.engagementtracking !== false;
398
- state._ZAP._errorTracking = data.errortracking !== false;
399
- state._ZAP._heatmapTracking = data.heatmaptracking !== false;
400
- state._ZAP._remoteLoggerEnabled = data.remoteloggerenabled !== false;
401
- state._ZAP._feedbackEnabled = data.feedbackenabled !== false;
422
+ // this SDK saves one flag per kind under engagement; an earlier build saved one
423
+ // engagementtracking flag for all four
424
+ var saved = (data.engagement && typeof data.engagement === 'object' // no i18n
425
+ ? data.engagement : null);
426
+ var kindOn = function (kind) {
427
+ return saved ? saved[kind] !== false : data.engagementtracking !== false;
428
+ };
429
+ state._ZAP._engagementTracking = { event: kindOn('event'), api: kindOn('api'), screen: kindOn('screen'), session: kindOn('session') }; // no i18n
430
+ // the same for the others: read the short key this SDK saves, else the earlier build's
431
+ var flagOn = function (key, oldKey) {
432
+ return (data.hasOwnProperty(key) ? data[key] : data[oldKey]) !== false;
433
+ };
434
+ state._ZAP._errorTracking = flagOn('error', 'errortracking'); // no i18n
435
+ state._ZAP._heatmapTracking = flagOn('heatmap', 'heatmaptracking'); // no i18n
436
+ state._ZAP._remoteLoggerEnabled = flagOn('remotelogger', 'remoteloggerenabled'); // no i18n
437
+ state._ZAP._feedbackEnabled = flagOn('feedback', 'feedbackenabled'); // no i18n
402
438
  }
403
439
  // screen recording, set up in the console: everything the form needs comes from here
404
440
  var quartz = (data.quartz && typeof data.quartz === 'object' ? data.quartz : {});
@@ -412,16 +448,15 @@ function applyServerFlags(data) {
412
448
  domain: text(quartz.quartz_domain),
413
449
  cdnUrl: text(quartz.quartz_cdn_url),
414
450
  };
415
- // when to ask again, which replaces polling on a fixed interval
416
- if (typeof data.next_check_after === 'number' && data.next_check_after > 0) {
417
- state._ZAP._nextConfigCheck = data.next_check_after;
418
- }
451
+ // when to ask again, which replaces polling on a fixed interval. Kept as a timestamp, so the
452
+ // stored copy reads back the same. An answer without one falls back to getupdates_interval
453
+ state._ZAP._nextConfigCheck = nextCheckTime(data.next_check_after, utils.nowMs());
419
454
  state._ZAP._trackinfo = {
420
- engagementtracking: state._ZAP._engagementTracking,
421
- errortracking: state._ZAP._errorTracking,
422
- heatmaptracking: state._ZAP._heatmapTracking,
423
- remoteloggerenabled: state._ZAP._remoteLoggerEnabled,
424
- feedbackenabled: state._ZAP._feedbackEnabled,
455
+ engagement: state._ZAP._engagementTracking,
456
+ error: state._ZAP._errorTracking,
457
+ heatmap: state._ZAP._heatmapTracking,
458
+ remotelogger: state._ZAP._remoteLoggerEnabled,
459
+ feedback: state._ZAP._feedbackEnabled,
425
460
  // in the server's own shape, so restoring it on the next visit reads it the same way
426
461
  quartz: {
427
462
  quartz_enabled: state._ZAP._quartz.enabled,
@@ -442,31 +477,164 @@ function restoreServerSettings() {
442
477
  var saved = storage.user_defaults.item('trackinfo'); // no i18n
443
478
  if (saved && typeof saved === 'object') { // no i18n
444
479
  applyServerFlags(saved);
480
+ // saved by an earlier build under the old names: store it again in this SDK's shape now,
481
+ // rather than at the next getsdkconfig, which may be a while off. The visitor's tracking
482
+ // status is kept as it is
483
+ if (OLD_TRACKINFO_KEYS.some(function (key) { return saved.hasOwnProperty(key); })) {
484
+ storage.user_defaults.add('trackinfo', saved.tracking_status === undefined ? state._ZAP._trackinfo // no i18n
485
+ : Object.assign({}, state._ZAP._trackinfo, { tracking_status: saved.tracking_status }));
486
+ }
445
487
  logger.Console.log('restoreServerSettings: ' + JSON.stringify(state._ZAP._trackinfo)); // no i18n
446
488
  }
447
489
  }
490
+ /** trackinfo keys an earlier build saved, which this SDK saves under shorter names. */
491
+ var OLD_TRACKINFO_KEYS = ['engagementtracking', 'errortracking', 'heatmaptracking', 'remoteloggerenabled', 'feedbackenabled']; // no i18n
492
+ /**
493
+ * next_check_after as a time: the server's value is milliseconds, either a timestamp or a wait
494
+ * from now. Anything below 1e12 (2001 as a timestamp) can only be a wait.
495
+ */
496
+ function nextCheckTime(value, now) {
497
+ if (typeof value !== 'number' || !(value > 0)) { // no i18n
498
+ return 0;
499
+ }
500
+ return value >= 1e12 ? value : now + value;
501
+ }
502
+ /** setTimeout's longest wait; a longer one fires straight away. */
503
+ var MAX_TIMEOUT = 2147483647;
504
+ var configTimer = null;
505
+ var configInFlight = false;
506
+ /** Bumped by stopServerSettingChecks(), so an answer to an ask from before a reset schedules nothing. */
507
+ var configRound = 0;
508
+ /** Whether this page load has asked, or set the timer from the saved answer instead. */
509
+ var configAsked = false;
510
+ /** A user registered, or getupdates reported a change, while a getsdkconfig was in flight: asked again when it answers. */
511
+ var configAskAgain = false;
512
+ /** When the next getsdkconfig is due, as a timestamp. 0 until the first answer. */
513
+ var configDueAt = 0;
448
514
  /**
449
- * Fetches the server settings now if the device is registered. On a first visit the ids don't
450
- * exist yet; registerDeviceWithApptics calls this as soon as device/add succeeds.
515
+ * Asks for the server settings now if the device is registered, then schedules the next ask
516
+ * for the time the answer gave in next_check_after. On a first visit the ids don't exist yet;
517
+ * registerDeviceWithApptics calls this as soon as device/add succeeds.
451
518
  */
452
519
  function refreshServerSettings() {
453
- if (utils.isEmpty(transport.getHeaderParams('apid')) && utils.isEmpty(transport.getHeaderParams('mapid'))) { // no i18n
520
+ if (configInFlight || !transport.hasDeviceId() || (utils.isEmpty(transport.getHeaderParams('apid')) && utils.isEmpty(transport.getHeaderParams('mapid')))) { // no i18n
454
521
  return;
455
522
  }
523
+ if (configTimer !== null) {
524
+ clearTimeout(configTimer);
525
+ configTimer = null;
526
+ }
527
+ configInFlight = true;
528
+ configAsked = true;
529
+ markTabAsked();
530
+ var round = configRound;
531
+ var askedAt = utils.nowMs();
456
532
  getUpdatesFromServer().catch(function (err) {
457
533
  logger.Console.log(err);
534
+ }).then(function () {
535
+ if (round !== configRound) {
536
+ // stopServerSettingChecks() ran while this was in flight
537
+ return;
538
+ }
539
+ configInFlight = false;
540
+ if (configAskAgain) {
541
+ configAskAgain = false;
542
+ refreshServerSettings();
543
+ return;
544
+ }
545
+ // an answer without a usable next_check_after, or a failed call, waits getupdates_interval
546
+ configDueAt = state._ZAP._nextConfigCheck > utils.nowMs() ? state._ZAP._nextConfigCheck : askedAt + state.getConfig('getupdates_interval'); // no i18n
547
+ scheduleServerSettingCheck();
458
548
  });
459
549
  }
550
+ /**
551
+ * getsdkconfig now, not at the next check: setUser registered a user, or getupdates reported a
552
+ * module changed.
553
+ */
554
+ function askServerSettings() {
555
+ if (configInFlight) {
556
+ // the one in flight went out before the change: ask again once it answers
557
+ configAskAgain = true;
558
+ return;
559
+ }
560
+ refreshServerSettings();
561
+ }
562
+ /**
563
+ * Start-up's getsdkconfig. Asked on a first visit and on the first page load in each tab; any
564
+ * other load (a reload, a link to another page of the site, back/forward) uses the saved answer
565
+ * and waits for its next_check_after, unless that has already passed. Nothing happens if one has
566
+ * gone out already on this load (after device/add).
567
+ */
568
+ function startServerSettingChecks() {
569
+ if (configAsked) {
570
+ return;
571
+ }
572
+ if (tabHasAsked() && state._ZAP._nextConfigCheck > utils.nowMs()) {
573
+ logger.Console.log('getsdkconfig: the saved answer is still current'); // no i18n
574
+ configAsked = true;
575
+ configDueAt = state._ZAP._nextConfigCheck;
576
+ scheduleServerSettingCheck();
577
+ return;
578
+ }
579
+ refreshServerSettings();
580
+ }
581
+ /** sessionStorage: per tab, and kept across a reload and the site's own links. */
582
+ var TAB_ASKED_KEY = '_apptics_sdkconfig_asked'; // no i18n
583
+ /** Whether getsdkconfig has gone out in this tab before, on an earlier page load. */
584
+ function tabHasAsked() {
585
+ try {
586
+ return sessionStorage.getItem(TAB_ASKED_KEY) === '1'; // no i18n
587
+ }
588
+ catch (e) {
589
+ return false; // no sessionStorage (private mode, sandboxed frame): every load asks
590
+ }
591
+ }
592
+ function markTabAsked() {
593
+ try {
594
+ sessionStorage.setItem(TAB_ASKED_KEY, '1'); // no i18n
595
+ }
596
+ catch (e) {
597
+ // as above: without it, every load asks
598
+ }
599
+ }
600
+ /** Sets the timer for the next getsdkconfig, at configDueAt. */
601
+ function scheduleServerSettingCheck() {
602
+ if (configTimer !== null) {
603
+ clearTimeout(configTimer);
604
+ }
605
+ var wait = Math.min(Math.max(configDueAt - utils.nowMs(), 0), MAX_TIMEOUT);
606
+ logger.Console.log('getsdkconfig: next check in ' + wait + ' ms'); // no i18n
607
+ configTimer = setTimeout(function () {
608
+ configTimer = null;
609
+ refreshServerSettings();
610
+ }, wait);
611
+ }
612
+ /** reset(): no more checks, and the next init() asks again straight away. */
613
+ function stopServerSettingChecks() {
614
+ if (configTimer !== null) {
615
+ clearTimeout(configTimer);
616
+ }
617
+ configTimer = null;
618
+ configRound++;
619
+ configInFlight = false;
620
+ configAsked = false;
621
+ configAskAgain = false;
622
+ configDueAt = 0;
623
+ }
460
624
  hooks.hooks.updateDevice = updateDeviceWithApptics;
461
625
  hooks.hooks.refreshDeviceInfo = checkAndUpdateDeviceInfo;
626
+ hooks.hooks.askServerSettings = askServerSettings;
462
627
 
463
628
  exports.addOrUpdateDeviceID = addOrUpdateDeviceID;
464
629
  exports.checkAndUpdateDeviceInfo = checkAndUpdateDeviceInfo;
465
630
  exports.deviceReady = deviceReady;
466
631
  exports.getUpdatesFromServer = getUpdatesFromServer;
467
632
  exports.needsDevice = needsDevice;
633
+ exports.nextCheckTime = nextCheckTime;
468
634
  exports.prepareDevice = prepareDevice;
469
635
  exports.refreshServerSettings = refreshServerSettings;
470
636
  exports.registerDeviceWithApptics = registerDeviceWithApptics;
471
637
  exports.restoreServerSettings = restoreServerSettings;
638
+ exports.startServerSettingChecks = startServerSettingChecks;
639
+ exports.stopServerSettingChecks = stopServerSettingChecks;
472
640
  exports.updateDeviceWithApptics = updateDeviceWithApptics;
@@ -1,4 +1,4 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.1 | (c) Zoho Corporation Private Limited | see LICENSE */
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.2 | (c) Zoho Corporation Private Limited | see LICENSE */
2
2
  'use strict';
3
3
 
4
4
  var logger = require('../core/logger.js');
@@ -1,4 +1,4 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.1 | (c) Zoho Corporation Private Limited | see LICENSE */
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.2 | (c) Zoho Corporation Private Limited | see LICENSE */
2
2
  'use strict';
3
3
 
4
4
  var state = require('../core/state.js');
@@ -1,4 +1,4 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.1 | (c) Zoho Corporation Private Limited | see LICENSE */
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.2 | (c) Zoho Corporation Private Limited | see LICENSE */
2
2
  'use strict';
3
3
 
4
4
  var logger = require('../core/logger.js');
@@ -1,4 +1,4 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.1 | (c) Zoho Corporation Private Limited | see LICENSE */
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.2 | (c) Zoho Corporation Private Limited | see LICENSE */
2
2
  'use strict';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.1 | (c) Zoho Corporation Private Limited | see LICENSE */
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.2 | (c) Zoho Corporation Private Limited | see LICENSE */
2
2
  'use strict';
3
3
 
4
4
  var constants = require('../core/constants.js');
@@ -15,8 +15,7 @@ var transport = require('../core/transport.js');
15
15
  * Sends pending engagements to the Apptics API.
16
16
  */
17
17
  function sendEngagementData(forget) {
18
- // engagementtracking off (getupdates): queued items stay until tracking is back on or they expire
19
- if (env.is_apptics_disabled() || !consent.shouldTrackOrSendData() || !state._ZAP._engagementTracking) {
18
+ if (env.is_apptics_disabled() || !consent.shouldTrackOrSendData()) {
20
19
  return Promise.resolve();
21
20
  }
22
21
  if (navigator.onLine == false) {
@@ -39,6 +38,11 @@ function sendEngagementData(forget) {
39
38
  var _payload = {};
40
39
  var sent = [];
41
40
  utils.cloneObject(engagement_items).forEach(function (item) {
41
+ // a kind the server has turned off (getsdkconfig's event, api, screen or session
42
+ // module) stays queued until it is back on or the item expires
43
+ if (state._ZAP._engagementTracking[item.type] === false) {
44
+ return;
45
+ }
42
46
  const itemWithId = { ...item };
43
47
  if (item.type == constants.AP_CONSTANTS.EVENT) {
44
48
  eventObj.push(item);
@@ -35,10 +35,27 @@ export declare function dropPendingSession(): boolean;
35
35
  /** Whether a session is waiting to be written. Used by tests and by the page-closing path. */
36
36
  export declare function hasPendingSession(): boolean;
37
37
  export declare function startNewSession(time: number): Promise<void>;
38
+ /**
39
+ * The tab went inactive. That moment becomes the session's lastactivetime, the time it ends at if
40
+ * the user does not come back within inactiveduration. If they do not, the session is ended at it
41
+ * then, and `onEnded` sends it, rather than staying open until they return or the tab closes.
42
+ * Going inactive twice (a window blur, then the tab hidden) keeps the first moment.
43
+ */
44
+ export declare function markTabInactive(onEnded: () => void): Promise<void>;
45
+ /**
46
+ * The tab is active again. The caller then settles the session (checkAndUpdateSession): back
47
+ * within inactiveduration it continues, later it has ended and a new one starts.
48
+ */
49
+ export declare function markTabActive(): void;
50
+ /** reset(): no timer left running, and the tab counts as active. */
51
+ export declare function resetInactivity(): void;
38
52
  /**
39
53
  * This tab is closing, or the page is being replaced. The session ends here when this is the last
40
54
  * tab using it, so it can be sent; while another tab of the site is still on it, this tab only
41
55
  * leaves it, and the last tab to close ends it. Nothing happens when it is already closed.
56
+ *
57
+ * A tab closed while inactive ends the session when the user left (its lastactivetime), not when
58
+ * the tab closed: the time it sat hidden or in the background is not session time.
42
59
  */
43
60
  export declare function endCurrentSession(): Promise<void>;
44
61
  /**
@@ -1,4 +1,4 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.1 | (c) Zoho Corporation Private Limited | see LICENSE */
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.2 | (c) Zoho Corporation Private Limited | see LICENSE */
2
2
  'use strict';
3
3
 
4
4
  var constants = require('../core/constants.js');
@@ -64,7 +64,7 @@ function closeScreensOf(session, time) {
64
64
  * @param sessionData A set of properties belonging to the session.
65
65
  */
66
66
  function trackSession(sessionData) {
67
- if (!consent.canTrackEngagement('trackSession')) { // no i18n
67
+ if (!consent.canTrackEngagement('trackSession', constants.AP_CONSTANTS.SESSION)) { // no i18n
68
68
  return Promise.resolve();
69
69
  }
70
70
  if (sessionData && typeof sessionData !== 'object') { // no i18n
@@ -320,26 +320,114 @@ function commitNow(session) {
320
320
  }
321
321
  return trackSession(session);
322
322
  }
323
+ /** Whether this tab is inactive: hidden, minimised, or its window has lost focus. */
324
+ var inactive = false;
325
+ var inactivityTimer = null;
326
+ /**
327
+ * The tab went inactive. That moment becomes the session's lastactivetime, the time it ends at if
328
+ * the user does not come back within inactiveduration. If they do not, the session is ended at it
329
+ * then, and `onEnded` sends it, rather than staying open until they return or the tab closes.
330
+ * Going inactive twice (a window blur, then the tab hidden) keeps the first moment.
331
+ */
332
+ function markTabInactive(onEnded) {
333
+ var first = !inactive;
334
+ inactive = true;
335
+ if (inactivityTimer !== null) {
336
+ clearTimeout(inactivityTimer);
337
+ }
338
+ inactivityTimer = setTimeout(function () {
339
+ inactivityTimer = null;
340
+ endInactiveSession().then(function (ended) {
341
+ if (ended) {
342
+ onEnded();
343
+ }
344
+ });
345
+ }, state._ZAP.inactiveduration * 60 * 1000 + 1000);
346
+ return first ? updateLastActiveTimeOfSession() : Promise.resolve();
347
+ }
348
+ /**
349
+ * The tab is active again. The caller then settles the session (checkAndUpdateSession): back
350
+ * within inactiveduration it continues, later it has ended and a new one starts.
351
+ */
352
+ function markTabActive() {
353
+ inactive = false;
354
+ if (inactivityTimer !== null) {
355
+ clearTimeout(inactivityTimer);
356
+ inactivityTimer = null;
357
+ }
358
+ }
359
+ /** reset(): no timer left running, and the tab counts as active. */
360
+ function resetInactivity() {
361
+ markTabActive();
362
+ }
363
+ /**
364
+ * inactiveduration has passed with the tab inactive: the session ends at its lastactivetime, when
365
+ * the user left, so the time away is not counted. Resolves true when it ended one. While another
366
+ * tab of the site is still on the session, this tab only leaves it; one whose lastactivetime is
367
+ * recent is in use elsewhere and is left alone.
368
+ */
369
+ function endInactiveSession() {
370
+ if (!state._ZAP._token || env.is_apptics_disabled()) {
371
+ return Promise.resolve(false);
372
+ }
373
+ var ended = false;
374
+ return withSessionLock(function () {
375
+ return currentSession().then(function (_currentSession) {
376
+ if (!_currentSession || _currentSession.endtime !== 0) {
377
+ return;
378
+ }
379
+ var lastActive = Number(_currentSession.lastactivetime) || utils.nowMs();
380
+ if (utils.nowMs() - lastActive < state._ZAP.inactiveduration * 60 * 1000) {
381
+ return;
382
+ }
383
+ var others = tabsOf(_currentSession).filter(function (tab) { return tab !== tabId(); });
384
+ if (others.length > 0) {
385
+ var left = Object.assign({}, _currentSession, { tabs: others });
386
+ state._ZAP._current_session = left;
387
+ return storage.engagement_transactions.update(left);
388
+ }
389
+ var endAt = Math.max(lastActive, Number(_currentSession.starttime) || lastActive);
390
+ var closed = Object.assign({}, _currentSession, { endtime: endAt, tabs: [] });
391
+ state._ZAP._current_session = closed;
392
+ ended = true;
393
+ logger.Console.log('session: inactive for ' + state._ZAP.inactiveduration + ' minutes, so it ends when the user left, ' + endAt); // no i18n
394
+ return storage.engagement_transactions.update(closed).then(function () {
395
+ return closeScreensOf(closed, endAt);
396
+ });
397
+ });
398
+ }).then(function () {
399
+ return ended;
400
+ });
401
+ }
323
402
  /**
324
403
  * This tab is closing, or the page is being replaced. The session ends here when this is the last
325
404
  * tab using it, so it can be sent; while another tab of the site is still on it, this tab only
326
405
  * leaves it, and the last tab to close ends it. Nothing happens when it is already closed.
406
+ *
407
+ * A tab closed while inactive ends the session when the user left (its lastactivetime), not when
408
+ * the tab closed: the time it sat hidden or in the background is not session time.
327
409
  */
328
410
  function endCurrentSession() {
329
411
  var time = utils.nowMs();
412
+ var wasInactive = inactive;
413
+ markTabActive();
330
414
  // nothing was tracked and the minimum was never reached: this visit is not reported at all
331
415
  if (dropPendingSession()) {
332
416
  return Promise.resolve();
333
417
  }
334
418
  return withSessionLock(function () {
335
- return closeOrLeaveSession(time);
419
+ return closeOrLeaveSession(time, wasInactive);
336
420
  });
337
421
  }
338
- function closeOrLeaveSession(time) {
422
+ function closeOrLeaveSession(now, wasInactive) {
339
423
  return currentSession().then(function (_currentSession) {
340
424
  if (!_currentSession || _currentSession.endtime !== 0) {
341
425
  return;
342
426
  }
427
+ var lastActive = Number(_currentSession.lastactivetime) || 0;
428
+ var time = wasInactive && lastActive > 0
429
+ ? Math.max(Math.min(lastActive, now), Number(_currentSession.starttime) || 0)
430
+ : now;
343
431
  var others = tabsOf(_currentSession).filter(function (tab) { return tab !== tabId(); });
344
432
  if (others.length > 0) {
345
433
  var left = Object.assign({}, _currentSession, { tabs: others, lastactivetime: time });
@@ -456,6 +544,9 @@ exports.currentSession = currentSession;
456
544
  exports.dropPendingSession = dropPendingSession;
457
545
  exports.endCurrentSession = endCurrentSession;
458
546
  exports.holdTabLock = holdTabLock;
547
+ exports.markTabActive = markTabActive;
548
+ exports.markTabInactive = markTabInactive;
549
+ exports.resetInactivity = resetInactivity;
459
550
  exports.startNewSession = startNewSession;
460
551
  exports.tabId = tabId;
461
552
  exports.trackSession = trackSession;
@@ -1,12 +1,12 @@
1
1
  import type { Dict, Properties } from '../types.js';
2
2
  import { type DefinedEvent } from './defined-events.js';
3
3
  /**
4
- * Records an event: `trackEvent(name, group?, properties?)`, or a defined event with
4
+ * Records an event: `trackEvent(name, group, properties?)`, or a defined event with
5
5
  * `trackEvent(DefinedEvents.AP_USER_LOGIN, properties?)`, which brings its own group.
6
6
  *
7
- * As on Android, an invalid name or group is not logged. A group left out is `default`, so the
8
- * one-argument form keeps working; a group given but invalid is a mistake worth being told about,
9
- * not one to file quietly under `default`.
7
+ * The group is required. An event without one, or with an invalid name or group, is not logged,
8
+ * and the console says why (debug or not): filing it under a group the app never named would hide
9
+ * the mistake.
10
10
  */
11
11
  export declare function trackEvent(eventName: string | DefinedEvent, groupName?: string | Properties, jsonObj?: Properties): void;
12
12
  /** The app's own screen view. Automatic screen tracking calls recordScreen() instead. */