@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,12 +1,15 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (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
  import { AP_TRACKING_STATUS } from '../core/constants.js';
3
3
  import { _ZAP } from '../core/state.js';
4
- import { Console } from '../core/logger.js';
4
+ import { Console, warnAlways } from '../core/logger.js';
5
5
  import { isEmpty, isEmptyObj } from '../core/utils.js';
6
6
  import { opt_in_status } from '../core/consent.js';
7
7
  import { timerHtml, feedbackUIHtml } from './template.js';
8
8
  import { sendFeedbackData } from './send.js';
9
- import { wireDrag, wireUserPicker, wireLogControls, logChoice, userChoice } from './controls.js';
9
+ import { chooseUserForSend, userChoice, wireTimerDrag, wireDrag, wireUserPicker, wireLogControls, logChoice } from './controls.js';
10
+ import { injectStylesheet } from '../core/stylesheet.js';
11
+ import { recordingDomain, loadQuartz, quartzScriptSource } from './quartz.js';
12
+ import css from './styles.js';
10
13
 
11
14
  // Quartz is an optional page-level SDK. Without it every call is a no-op (fix #5).
12
15
  const QUARTZ_NOOP = {
@@ -19,6 +22,12 @@ const QUARTZ_NOOP = {
19
22
  submitRecording() { return Promise.resolve({}); }
20
23
  };
21
24
  const QUARTZ_CONSTANTS_NOOP = { ErrorType: {}, FeedbackState: {} };
25
+ /** From Quartz's RECORDING_IN_PROGRESS until the form has come back from it. */
26
+ var recordingInProgress = false;
27
+ /** Send was pressed during a recording: it is sent, recording and all, once the recording has ended. */
28
+ var sendWhenEnded = false;
29
+ /** How long the form takes to shrink away when a recording starts: the stylesheet's transition. */
30
+ const HIDE_MS = 300;
22
31
  function hasQuartz() {
23
32
  return typeof Quartz !== 'undefined' && !!Quartz; // no i18n
24
33
  }
@@ -37,34 +46,55 @@ function submitFeedback(dataobj) {
37
46
  if (!result) {
38
47
  return null;
39
48
  }
40
- feedback.handleStatusmsg(result.ok, result.message);
41
49
  if (result.ok) {
42
- setTimeout(() => {
43
- feedback.close();
44
- }, 2000);
50
+ // sent: the form goes at once rather than holding the page for the success line
51
+ feedback.close();
52
+ }
53
+ else {
54
+ feedback.handleStatusmsg(result.ok, result.message);
45
55
  }
46
56
  return result;
47
57
  });
48
58
  }
49
- function sendFeedback(feedbackType, feedbackMessage, attachments, logsFiles, includeDyninfo, includeLog) {
59
+ /** Whether a value is an options object: a plain object, not a type number or a message string. */
60
+ function isOptions(value) {
61
+ return value !== null && typeof value === 'object' && !Array.isArray(value); // no i18n
62
+ }
63
+ /** Said once: the old one-argument-after-another form of feedbackFormOpen. */
64
+ var saidOpenForm = false;
65
+ function sendFeedback(options) {
66
+ if (options !== undefined && !isOptions(options)) {
67
+ // the old form, sendFeedback(type, message, ...): sending it as it stands would send an
68
+ // empty report, so it is refused, and says what to pass instead
69
+ return Promise.reject(new Error('AppticsFeedback.sendFeedback: takes one options object, as sendFeedback({ type, message, attachments, logFiles, includeDiagnostics, includeLog, isAnonymous })')); // no i18n
70
+ }
71
+ const o = options || {};
50
72
  const dataobj = {
51
- type: !isEmpty(feedbackType) ? feedbackType : 1,
52
- message: feedbackMessage || '', // no i18n
53
- attachment: attachments || null,
54
- logsFiles: logsFiles || null,
73
+ type: !isEmpty(o.type) ? o.type : 1,
74
+ message: o.message || '', // no i18n
75
+ attachment: o.attachments || null,
76
+ logsFiles: o.logFiles || null,
55
77
  // both flags stay as given: unset lets send.ts decide from what is actually stored
56
- includeDyninfo: includeDyninfo,
57
- includeLog: includeLog,
78
+ includeDyninfo: o.includeDiagnostics,
79
+ includeLog: o.includeLog,
58
80
  quartzid: '' // no i18n
59
81
  };
82
+ // from the active user, or from nobody with isAnonymous; the form's own choice is put back after
83
+ var restoreUser = chooseUserForSend(o.isAnonymous === true);
60
84
  // an app awaiting this must be able to tell its user the feedback did not go: resolving on a
61
85
  // failure would have it say "thanks, sent" for a report the server never received
62
86
  return submitFeedback(dataobj).then(function (result) {
87
+ restoreUser();
88
+ return result;
89
+ }, function (err) {
90
+ restoreUser();
91
+ throw err;
92
+ }).then(function (result) {
63
93
  if (!result) {
64
- throw new Error('apptics.sendFeedback: not sent, the browser is offline or Apptics is disabled for this client'); // no i18n
94
+ throw new Error('AppticsFeedback.sendFeedback: not sent, the browser is offline or Apptics is disabled for this client'); // no i18n
65
95
  }
66
96
  if (!result.ok) {
67
- throw new Error('apptics.sendFeedback: not sent, ' + result.message); // no i18n
97
+ throw new Error('AppticsFeedback.sendFeedback: not sent, ' + result.message); // no i18n
68
98
  }
69
99
  });
70
100
  }
@@ -109,23 +139,54 @@ function newTransfer() {
109
139
  return null;
110
140
  }
111
141
  }
112
- function feedbackOpen(type, message, includeLog, includeDyninfo, attachments, logFiles, isAnonymous) {
142
+ function feedbackFormOpen(options) {
143
+ if (options !== undefined && !isOptions(options) && !saidOpenForm) {
144
+ // the old form, feedbackFormOpen(type, message, ...): the form still opens, with its defaults
145
+ saidOpenForm = true;
146
+ warnAlways('AppticsFeedback.feedbackFormOpen: takes one options object, as feedbackFormOpen({ type, message, includeLog, includeDiagnostics, attachments, logFiles, isAnonymous }); opening it with its defaults'); // no i18n
147
+ }
148
+ const o = isOptions(options) ? options : {};
113
149
  const opts = {
114
- includeLog: includeLog === true,
115
- includeDyninfo: includeDyninfo === true,
116
- isAnonymous: isAnonymous === true,
150
+ includeLog: o.includeLog === true,
151
+ includeDyninfo: o.includeDiagnostics === true,
152
+ isAnonymous: o.isAnonymous === true,
117
153
  };
118
- if (typeof type === 'number')
119
- opts.type = type; // no i18n
120
- if (typeof message === 'string' && message.length > 0)
121
- opts.message = message; // no i18n
122
- if (attachments && attachments.length > 0)
123
- opts.attachments = attachments;
124
- if (logFiles && logFiles.length > 0)
125
- opts.logFiles = logFiles;
154
+ if (typeof o.type === 'number')
155
+ opts.type = o.type; // no i18n
156
+ if (typeof o.message === 'string' && o.message.length > 0)
157
+ opts.message = o.message; // no i18n
158
+ if (o.attachments && o.attachments.length > 0)
159
+ opts.attachments = o.attachments;
160
+ if (o.logFiles && o.logFiles.length > 0)
161
+ opts.logFiles = o.logFiles;
162
+ // the form brings its own styles, so an app imports or links no css
163
+ injectStylesheet(css);
126
164
  feedback.init(opts);
127
165
  }
128
- function feedbackClose() {
166
+ /** The themes the form has: 'auto' follows the visitor's system setting. */
167
+ const FEEDBACK_THEMES = ['light', 'dark', 'auto']; // no i18n
168
+ /**
169
+ * Sets the form's theme, for a page with its own light and dark modes: 'auto' follows the
170
+ * operating system, which is not what such a page shows. Restyles a form or recording timer that
171
+ * is open now, and every one opened after; Apptics.init()'s feedback_theme only sets the first.
172
+ */
173
+ function setFeedbackTheme(theme) {
174
+ if (FEEDBACK_THEMES.indexOf(theme) === -1) {
175
+ Console.warn('AppticsFeedback.setFeedbackTheme: takes \'light\', \'dark\' or \'auto\', not ' + JSON.stringify(theme)); // no i18n
176
+ return;
177
+ }
178
+ _ZAP._config.feedback_theme = theme;
179
+ if (typeof document === 'undefined') { // no i18n
180
+ return;
181
+ }
182
+ ['zasdkFeedbackModal', 'zasdkrectimer'].forEach(function (id) {
183
+ var open = document.getElementById(id);
184
+ if (open) {
185
+ open.setAttribute('data-zasdk-theme', theme); // no i18n
186
+ }
187
+ });
188
+ }
189
+ function feedbackFormClose() {
129
190
  feedback.close();
130
191
  }
131
192
  const feedback = {
@@ -150,31 +211,27 @@ const feedback = {
150
211
  feedback._opts = opts || {};
151
212
  var modal = document.getElementById("zasdkFeedbackModal"); // no i18n
152
213
  if (!isEmpty(modal)) {
214
+ // already open. A recording takes the form out of sight, and one that ended without the
215
+ // form hearing of it would leave it there, every later open a no-op. Asked to open, it
216
+ // comes back; a recording still going carries on, and its end is handled as ever
217
+ if (feedback.isHidden || feedback.isScaled) {
218
+ feedback.showForm();
219
+ }
153
220
  return;
154
221
  }
155
222
  const tempContainer = document.createElement('div'); // no i18n
156
223
  feedback.uploadFileAry = [];
157
224
  tempContainer.id = 'zasdkFeedbackModal'; // no i18n
158
225
  tempContainer.className = 'zasdk-modal'; // no i18n
226
+ tempContainer.setAttribute('data-zasdk-theme', _ZAP._config.feedback_theme); // no i18n
159
227
  tempContainer.innerHTML = feedback.feedbackUIHtml;
160
228
  document.body.appendChild(tempContainer);
161
229
  var modal = document.getElementById("zasdkFeedbackModal"); // no i18n
162
- var defaultoption = _ZAP._defaultoptions;
163
- var qz_ws_key = defaultoption.quartz_workspace_key;
164
- var qz_dp_key = defaultoption.quartz_department_key;
165
- var quartz_DC = {
166
- "Local": "https://quartz.localzoho.com", // no i18n
167
- "US": "https://quartz.zoho.com", // no i18n
168
- "EU": "https://quartz.zoho.eu", // no i18n
169
- "IN": "https://quartz.zoho.in", // no i18n
170
- "AU": "https://quartz.zoho.com.au", // no i18n
171
- "CN": "https://quartz.zoho.com.cn", // no i18n
172
- "CA": "https://quartz.zohocloud.ca", // no i18n
173
- "JP": "https://quartz.zoho.jp", // no i18n
174
- "SA": "https://quartz.zoho.sa", // no i18n
175
- "AE": "https://quartz.zoho.ae" // no i18n
176
- };
177
- var qz_dc_key = (!isEmpty(defaultoption.DC) && defaultoption.DC !== '-') ? (quartz_DC[defaultoption.DC] || '') : ''; // no i18n
230
+ // screen recording as the console set it up for this app (getsdkconfig's quartz block)
231
+ var qz = _ZAP._quartz;
232
+ var qz_ws_key = qz.workspace;
233
+ var qz_dp_key = qz.department;
234
+ var qz_dc_key = recordingDomain(qz.domain);
178
235
  var closeBtn = modal.querySelector("#appticsfeedbackclose"); // no i18n
179
236
  var feedbackTypes = modal.querySelectorAll(".zasdk-feedback-type"); // no i18n
180
237
  feedback.fileInput = modal.querySelector("#zasdkFileInput"); // no i18n
@@ -197,17 +254,37 @@ const feedback = {
197
254
  document.addEventListener('quartz-feedback-state', feedback.bindQuartzEvent); // no i18n
198
255
  document.removeEventListener('quartz-feedback-error', feedback.handleErrorQuartzEvent); // no i18n
199
256
  document.addEventListener('quartz-feedback-error', feedback.handleErrorQuartzEvent); // no i18n
200
- // the consent checkbox and its label only when a recording can actually be made: the Quartz
201
- // script on the page, a DC it knows, and both keys. "-" for any of them turns it off; DC "-"
202
- // has already become '' above. A checkbox that leads only to "unavailable" is not offered
203
- var recordingAvailable = hasQuartz() && !isEmpty(qz_dc_key)
204
- && !isEmpty(qz_ws_key) && qz_ws_key !== "-" && !isEmpty(qz_dp_key) && qz_dp_key !== "-"; // no i18n
205
- modal.querySelector("#zasdkreccheckcnt").style.display = recordingAvailable ? "block" : "none"; // no i18n
257
+ // the consent checkbox and its label only when a recording can actually be made: recording
258
+ // on for the app, its workspace, department and domain, and Quartz on the page. A checkbox
259
+ // that leads only to "unavailable" is not offered. Quartz is loaded here, the first time
260
+ // the form needs it, and the checkbox shows once it has arrived
261
+ var recordingConfigured = qz.enabled && !isEmpty(qz_dc_key) && !isEmpty(qz_ws_key) && !isEmpty(qz_dp_key);
262
+ var recordingBlock = modal.querySelector("#zasdkreccheckcnt"); // no i18n
263
+ recordingBlock.style.display = recordingConfigured && hasQuartz() ? "block" : "none"; // no i18n
264
+ if (recordingConfigured && !hasQuartz()) {
265
+ // quartz_cdn_url, or Zoho's CDN for this serviceUrl when the answer carries none
266
+ loadQuartz(quartzScriptSource(qz.cdnUrl, String(_ZAP._defaultoptions.serviceUrl || ''))).then(function (loaded) {
267
+ if (loaded && recordingBlock.isConnected) {
268
+ recordingBlock.style.display = "block"; // no i18n
269
+ }
270
+ });
271
+ }
206
272
  feedbackbox.addEventListener('transitionend', function (event) {
207
- if (event.propertyName === 'transform' && feedback.isScaled) { // no i18n
208
- feedback.toggleWindowAnimation();
273
+ if (event.propertyName === 'transform') { // no i18n
274
+ feedback.finishHiding();
209
275
  }
210
276
  });
277
+ // the form, opened again while a recording goes on, says so and can stop it
278
+ const recStop = modal.querySelector('#zasdkRecStatusStop'); // no i18n
279
+ recStop.onclick = function () {
280
+ feedback.stopRecording();
281
+ };
282
+ recStop.onkeydown = function (e) {
283
+ if (e.key === 'Enter' || e.key === ' ') { // no i18n
284
+ e.preventDefault();
285
+ feedback.stopRecording();
286
+ }
287
+ };
211
288
  quartzrecbtn.onclick = function (e) {
212
289
  e.stopImmediatePropagation();
213
290
  let recordingDetails = {
@@ -229,8 +306,16 @@ const feedback = {
229
306
  quartz().startRecording(recordingDetails);
230
307
  };
231
308
  closeBtn.onclick = function () {
232
- feedback.close();
309
+ feedback.dismiss();
310
+ };
311
+ // keyboard: Enter or Space on the close control, and Escape anywhere, close the form
312
+ closeBtn.onkeydown = function (e) {
313
+ if (e.key === 'Enter' || e.key === ' ') { // no i18n
314
+ e.preventDefault();
315
+ feedback.dismiss();
316
+ }
233
317
  };
318
+ document.addEventListener('keydown', feedback.onEscape); // no i18n
234
319
  preivewButton.addEventListener('click', function () {
235
320
  quartz().previewVideo();
236
321
  });
@@ -253,6 +338,12 @@ const feedback = {
253
338
  submitbtncnt.classList.add('zasdkbtndisabled'); // no i18n
254
339
  submitbtn.value = '';
255
340
  try {
341
+ if (recordingInProgress) {
342
+ // sent with the recording: stopped first, and sent once Quartz has stored it
343
+ sendWhenEnded = true;
344
+ feedback.stopRecording();
345
+ return;
346
+ }
256
347
  let videoElement = modal.querySelector('#previewVideoElemId'), // no i18n
257
348
  videoVisible = videoElement && videoElement.offsetParent !== null;
258
349
  if (videoVisible) {
@@ -281,6 +372,11 @@ const feedback = {
281
372
  }
282
373
  });
283
374
  descriptionField.addEventListener('input', feedback.validateFeedbackField); // no i18n
375
+ // keyboard and screen-reader users land in the form, not behind it
376
+ try {
377
+ descriptionField.focus({ preventScroll: true });
378
+ }
379
+ catch (err) { /* focus is a courtesy */ }
284
380
  feedbackTypes.forEach(function (type) {
285
381
  type.addEventListener("click", function () {
286
382
  feedbackTypes.forEach(function (t) {
@@ -289,7 +385,7 @@ const feedback = {
289
385
  this.classList.add("zasdk-selected"); // no i18n
290
386
  });
291
387
  });
292
- // what feedbackOpen() was given: select that type, fill the description
388
+ // what feedbackFormOpen() was given: select that type, fill the description
293
389
  if (typeof feedback._opts.type === 'number') { // no i18n
294
390
  var wanted = Object.keys(feedback.typeObj).filter(function (name) {
295
391
  return feedback.typeObj[name] === feedback._opts.type;
@@ -349,6 +445,10 @@ const feedback = {
349
445
  let counterElem = elem.parentElement ? elem.parentElement.querySelector(".zaleftcharcnt") : null; // no i18n
350
446
  if (counterElem) {
351
447
  counterElem.textContent = String(maxLength - val.length); // Update remaining count
448
+ var unit = counterElem.parentElement ? counterElem.parentElement.querySelector(".zaleftchartxt") : null; // no i18n
449
+ if (unit) {
450
+ unit.textContent = maxLength - val.length === 1 ? 'character left' : 'characters left'; // no i18n
451
+ }
352
452
  }
353
453
  });
354
454
  },
@@ -384,7 +484,14 @@ const feedback = {
384
484
  },
385
485
  bindQuartzEvent: function (event) {
386
486
  let feedbackState = event.detail.feedbackState;
487
+ if (feedbackState == quartzConstants().FeedbackState.RECORDING_COMPLETED) {
488
+ // Quartz's own stop control and the browser's "Stop sharing" end the recording without
489
+ // the SDK's timer; this is the only word of it the form gets
490
+ feedback.recordingEnded();
491
+ return;
492
+ }
387
493
  if (feedbackState == quartzConstants().FeedbackState.RECORDING_IN_PROGRESS) {
494
+ recordingInProgress = true;
388
495
  var feedbacktimer = document.getElementById("zasdkrectimer"); // no i18n
389
496
  if (!isEmpty(feedbacktimer)) {
390
497
  feedbacktimer.remove();
@@ -392,8 +499,11 @@ const feedback = {
392
499
  const tempDiv = document.createElement("div"); // no i18n
393
500
  tempDiv.innerHTML = feedback.timerHtml;
394
501
  const node = tempDiv.firstElementChild;
502
+ node.setAttribute('data-zasdk-theme', _ZAP._config.feedback_theme); // no i18n
395
503
  document.body.appendChild(node);
396
504
  var feedbacktimer = document.getElementById("zasdkrectimer"); // no i18n
505
+ // draggable anywhere on the page; wired before the click that stops the recording
506
+ wireTimerDrag(feedbacktimer);
397
507
  feedback.timerDisplay = feedbacktimer.querySelector('#zasdkTimerDisplay'); // no i18n
398
508
  feedback.recordButton = feedbacktimer;
399
509
  feedback.TOTAL_TIME = 5 * 60; // 5 minutes in seconds
@@ -401,13 +511,52 @@ const feedback = {
401
511
  feedback.isRecording = true;
402
512
  feedback.recordButton.addEventListener('click', feedback.toggleRecording); // no i18n
403
513
  feedback.startRecording();
514
+ feedback.updateDisplay();
515
+ feedback.showRecordingStatus(true);
404
516
  feedback.toggleWindowAnimation();
405
517
  }
406
518
  },
519
+ onEscape: function (e) {
520
+ if (e.key === 'Escape') { // no i18n
521
+ feedback.dismiss();
522
+ }
523
+ },
524
+ dismiss: function () {
525
+ // the form's own close (its X, Escape) during a recording puts it out of sight rather than
526
+ // throwing away what was typed: the recording goes on, and the next open brings it all back
527
+ if (recordingInProgress) {
528
+ if (!feedback.isScaled && !feedback.isHidden) {
529
+ feedback.toggleWindowAnimation();
530
+ }
531
+ return;
532
+ }
533
+ feedback.close();
534
+ },
535
+ showRecordingStatus: function (on) {
536
+ var modal = document.getElementById("zasdkFeedbackModal"); // no i18n
537
+ if (!modal) {
538
+ return;
539
+ }
540
+ modal.querySelector("#zasdkrecstatus").style.display = on ? "flex" : "none"; // no i18n
541
+ if (on) {
542
+ modal.querySelector("#zasdkreccheckcnt").style.display = "none"; // no i18n
543
+ }
544
+ },
407
545
  close: function () {
408
546
  var modal = document.getElementById("zasdkFeedbackModal"); // no i18n
547
+ document.removeEventListener('keydown', feedback.onEscape); // no i18n
409
548
  document.removeEventListener('quartz-feedback-state', feedback.bindQuartzEvent); // no i18n
410
549
  quartz().clearFeedback();
550
+ // the recording goes with the form: no timer left on the page, and the next form starts clean
551
+ recordingInProgress = false;
552
+ sendWhenEnded = false;
553
+ feedback.isRecording = false;
554
+ clearInterval(feedback.timerInterval || undefined);
555
+ feedback.timerInterval = null;
556
+ var timer = document.getElementById("zasdkrectimer"); // no i18n
557
+ if (timer) {
558
+ timer.remove();
559
+ }
411
560
  if (!isEmpty(modal)) {
412
561
  modal.remove();
413
562
  }
@@ -416,23 +565,29 @@ const feedback = {
416
565
  var modal = document.getElementById("zasdkFeedbackModal"); // no i18n
417
566
  const box = modal.querySelector('#zasdkFeedbackconatiner'); // no i18n
418
567
  if (feedback.isHidden) {
419
- modal.style.display = 'block'; // no i18n
420
- setTimeout(() => {
421
- box.style.transform = 'scale(1)'; // no i18n
422
- feedback.validateFeedbackField();
423
- }, 10);
424
- feedback.isHidden = false;
425
- feedback.isScaled = false;
568
+ feedback.showForm();
426
569
  }
427
570
  else if (feedback.isScaled) {
428
- modal.style.display = 'none'; // no i18n
429
- feedback.isHidden = true;
571
+ feedback.finishHiding();
430
572
  }
431
573
  else {
574
+ // shrinks away (the stylesheet's transition), and is taken off the page once it has;
575
+ // the timeout covers a page where the transition does not run (reduced motion)
432
576
  box.style.transform = 'scale(0)'; // no i18n
577
+ box.style.opacity = '0'; // no i18n
433
578
  feedback.isScaled = true;
579
+ setTimeout(feedback.finishHiding, HIDE_MS + 50);
434
580
  }
435
581
  },
582
+ finishHiding: function () {
583
+ var modal = document.getElementById("zasdkFeedbackModal"); // no i18n
584
+ // once only, and not when the form was brought back meanwhile
585
+ if (!modal || !feedback.isScaled || feedback.isHidden) {
586
+ return;
587
+ }
588
+ modal.style.display = 'none'; // no i18n
589
+ feedback.isHidden = true;
590
+ },
436
591
  fileAttachment: function () {
437
592
  var uploadedFiles = feedback.fileInput.files;
438
593
  feedback.uploadTotalSize = 0;
@@ -546,16 +701,65 @@ const feedback = {
546
701
  }
547
702
  },
548
703
  stopRecording: function () {
549
- var modal = document.getElementById("zasdkFeedbackModal"); // no i18n
704
+ if (!recordingInProgress) {
705
+ // Quartz has ended it already (its RECORDING_COMPLETED came first): asking again
706
+ // only gets a "no recording in progress" error shown in the form
707
+ return;
708
+ }
550
709
  clearInterval(feedback.timerInterval || undefined);
551
710
  feedback.timerInterval = null;
552
- document.querySelector("#zasdkrectimer").remove(); // no i18n
553
- feedback.toggleWindowAnimation();
711
+ // the preview is read from what Quartz stored, so only once the recording has ended
712
+ Promise.resolve(quartz().endRecording()).catch(function (err) {
713
+ Console.log('stopRecording: ' + err); // no i18n
714
+ }).then(feedback.recordingEnded);
715
+ },
716
+ recordingEnded: function () {
717
+ if (!recordingInProgress) {
718
+ return;
719
+ }
720
+ recordingInProgress = false;
721
+ feedback.isRecording = false;
722
+ clearInterval(feedback.timerInterval || undefined);
723
+ feedback.timerInterval = null;
724
+ var timer = document.getElementById("zasdkrectimer"); // no i18n
725
+ if (timer) {
726
+ timer.remove();
727
+ }
728
+ var modal = document.getElementById("zasdkFeedbackModal"); // no i18n
729
+ if (!modal) {
730
+ return; // the form was closed meanwhile
731
+ }
732
+ var send = sendWhenEnded;
733
+ sendWhenEnded = false;
734
+ feedback.showRecordingStatus(false);
735
+ feedback.showForm();
554
736
  modal.querySelector("#editRecordedVideo").style.display = "block"; // no i18n
555
737
  modal.querySelector("#zasdkreccheckcnt").style.display = "none"; // no i18n
556
738
  let videoElem = modal.querySelector('#previewVideoElemId'); // no i18n
557
- quartz().endRecording();
558
- quartz().setVideoContent(videoElem);
739
+ Promise.resolve(quartz().setVideoContent(videoElem)).catch(function (err) {
740
+ Console.warn('AppticsFeedback: the recording could not be shown: ' + err); // no i18n
741
+ }).then(function () {
742
+ if (send && document.getElementById("zasdkFeedbackModal")) { // no i18n
743
+ feedback.submitRecording();
744
+ }
745
+ });
746
+ },
747
+ showForm: function () {
748
+ // shown outright, not toggled: a recording that ends mid-way through the scale-down would
749
+ // otherwise be toggled out of sight instead of back into it
750
+ var modal = document.getElementById("zasdkFeedbackModal"); // no i18n
751
+ if (!modal) {
752
+ return;
753
+ }
754
+ const box = modal.querySelector('#zasdkFeedbackconatiner'); // no i18n
755
+ feedback.isHidden = false;
756
+ feedback.isScaled = false;
757
+ modal.style.display = 'block'; // no i18n
758
+ setTimeout(() => {
759
+ box.style.transform = 'scale(1)'; // no i18n
760
+ box.style.opacity = '1'; // no i18n
761
+ feedback.validateFeedbackField();
762
+ }, 10);
559
763
  },
560
764
  submitRecording: function () {
561
765
  var modal = document.getElementById("zasdkFeedbackModal"); // no i18n
@@ -596,7 +800,15 @@ const feedback = {
596
800
  updateDisplay: function () {
597
801
  const minutes = Math.floor(feedback.remainingTime / 60);
598
802
  const seconds = feedback.remainingTime % 60;
599
- feedback.timerDisplay.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; // no i18n
803
+ const shown = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; // no i18n
804
+ if (feedback.timerDisplay) {
805
+ feedback.timerDisplay.textContent = shown;
806
+ }
807
+ // and in the form, for when it is opened again mid-recording
808
+ var inForm = document.getElementById("zasdkRecStatusTime"); // no i18n
809
+ if (inForm) {
810
+ inForm.textContent = shown;
811
+ }
600
812
  },
601
813
  validateFeedbackField: function () {
602
814
  feedback.updateLeftCount();
@@ -623,4 +835,4 @@ const feedback = {
623
835
  }
624
836
  };
625
837
 
626
- export { feedback, feedbackClose, feedbackOpen, sendFeedback };
838
+ export { feedback, feedbackFormClose, feedbackFormOpen, sendFeedback, setFeedbackTheme };
@@ -8,6 +8,8 @@ import type { Dict, ServerResult } from '../types.js';
8
8
  *
9
9
  * Gated on the server's own heatmap flag (getsdkconfig module 18), not on engagement: an app may
10
10
  * want events while declining to have every click's coordinates recorded.
11
+ *
12
+ * The scheduler reaches it through hooks.sendHeatmaps, which src/index.ts sets.
11
13
  */
12
14
  export declare function sendHeatmapData(forget?: boolean): Promise<void>;
13
15
  export declare function requestToSendHeatmaps(_payload: Dict, forget?: boolean): Promise<ServerResult>;
@@ -1,4 +1,4 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (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
  import { _ZAP } from '../core/state.js';
3
3
  import { Console, warnAlways } from '../core/logger.js';
4
4
  import { cloneObject } from '../core/utils.js';
@@ -7,7 +7,6 @@ import { is_apptics_disabled } from '../core/env.js';
7
7
  import { shouldTrackOrSendData } from '../core/consent.js';
8
8
  import { defer, clearDefer, withSendLock, freshItems, awaitUnlessClosing, sendBatch } from '../core/batch.js';
9
9
  import { isPermanentRefusal } from '../core/transport.js';
10
- import { hooks } from '../core/hooks.js';
11
10
 
12
11
  /**
13
12
  * Sends queued heatmap records to heatmap/add.
@@ -18,6 +17,8 @@ import { hooks } from '../core/hooks.js';
18
17
  *
19
18
  * Gated on the server's own heatmap flag (getsdkconfig module 18), not on engagement: an app may
20
19
  * want events while declining to have every click's coordinates recorded.
20
+ *
21
+ * The scheduler reaches it through hooks.sendHeatmaps, which src/index.ts sets.
21
22
  */
22
23
  function sendHeatmapData(forget) {
23
24
  if (is_apptics_disabled() || !shouldTrackOrSendData() || !_ZAP._heatmapTracking) {
@@ -72,8 +73,5 @@ function requestToSendHeatmaps(_payload, forget) {
72
73
  var label = 'requestToSendHeatmaps'; // no i18n
73
74
  return sendBatch('heatmap', label, _payload, forget); // no i18n
74
75
  }
75
- // the scheduler flushes through this, so importing the heatmap entry is what puts heatmap
76
- // records on the flush timer at all
77
- hooks.sendHeatmaps = sendHeatmapData;
78
76
 
79
77
  export { requestToSendHeatmaps, sendHeatmapData };
@@ -1,4 +1,4 @@
1
1
  /** Turns capture on or off. Listeners are passive, so they never delay a click or a scroll. */
2
- export declare function enableHeatmap(status?: boolean): void;
2
+ export declare function trackHeatmap(status?: boolean): void;
3
3
  /** Forgets what this visit recorded. reset() calls it; the cap starts again from zero. */
4
4
  export declare function resetHeatmapSession(): void;