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

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 (220) hide show
  1. package/CHANGELOG.md +111 -5
  2. package/README.md +399 -300
  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-VYr43KcB.min.js +3 -0
  8. package/dist/chunks/lifecycle-VYr43KcB.min.js.map +1 -0
  9. package/dist/cjs/auto-init.d.ts +3 -3
  10. package/dist/cjs/auto-init.js +1 -1
  11. package/dist/cjs/core/attempts.js +1 -1
  12. package/dist/cjs/core/batch.js +2 -1
  13. package/dist/cjs/core/callsite.d.ts +6 -0
  14. package/dist/cjs/core/callsite.js +25 -3
  15. package/dist/cjs/core/consent.d.ts +5 -1
  16. package/dist/cjs/core/consent.js +8 -6
  17. package/dist/cjs/core/constants.d.ts +35 -10
  18. package/dist/cjs/core/constants.js +31 -15
  19. package/dist/cjs/core/db.d.ts +12 -4
  20. package/dist/cjs/core/db.js +167 -34
  21. package/dist/cjs/core/debug.js +1 -1
  22. package/dist/cjs/core/env.js +1 -1
  23. package/dist/cjs/core/framework-id.d.ts +1 -1
  24. package/dist/cjs/core/framework-id.js +3 -3
  25. package/dist/cjs/core/guard.d.ts +9 -0
  26. package/dist/cjs/core/guard.js +27 -13
  27. package/dist/cjs/core/hooks.d.ts +2 -4
  28. package/dist/cjs/core/hooks.js +2 -3
  29. package/dist/cjs/core/locks.d.ts +9 -0
  30. package/dist/cjs/core/locks.js +22 -0
  31. package/dist/cjs/core/logger.d.ts +3 -4
  32. package/dist/cjs/core/logger.js +4 -40
  33. package/dist/cjs/core/state.d.ts +3 -2
  34. package/dist/cjs/core/state.js +2 -2
  35. package/dist/cjs/core/storage.d.ts +20 -5
  36. package/dist/cjs/core/storage.js +115 -17
  37. package/dist/cjs/core/stylesheet.d.ts +8 -0
  38. package/dist/cjs/core/stylesheet.js +29 -0
  39. package/dist/cjs/core/transport.d.ts +2 -0
  40. package/dist/cjs/core/transport.js +19 -4
  41. package/dist/cjs/core/utils.js +1 -1
  42. package/dist/cjs/core/validate.js +2 -2
  43. package/dist/cjs/device/appversion.d.ts +1 -6
  44. package/dist/cjs/device/appversion.js +13 -1
  45. package/dist/cjs/device/index.d.ts +11 -0
  46. package/dist/cjs/device/index.js +120 -20
  47. package/dist/cjs/engagement/api-rules.d.ts +32 -67
  48. package/dist/cjs/engagement/api-rules.js +144 -157
  49. package/dist/cjs/engagement/auto-api.js +2 -2
  50. package/dist/cjs/engagement/auto-screen.d.ts +2 -0
  51. package/dist/cjs/engagement/auto-screen.js +67 -3
  52. package/dist/cjs/engagement/defined-events.js +1 -1
  53. package/dist/cjs/engagement/send.js +6 -1
  54. package/dist/cjs/engagement/session.d.ts +12 -3
  55. package/dist/cjs/engagement/session.js +161 -25
  56. package/dist/cjs/engagement/track.js +45 -20
  57. package/dist/cjs/errors/send.js +1 -1
  58. package/dist/cjs/errors/track.d.ts +1 -1
  59. package/dist/cjs/errors/track.js +46 -6
  60. package/dist/cjs/feedback/controls.d.ts +6 -0
  61. package/dist/cjs/feedback/controls.js +31 -1
  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 +10 -5
  65. package/dist/cjs/feedback/logs.js +52 -13
  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 +3 -0
  69. package/dist/cjs/feedback/send.js +28 -3
  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 +12 -12
  74. package/dist/cjs/feedback/ui.d.ts +14 -6
  75. package/dist/cjs/feedback/ui.js +126 -48
  76. package/dist/cjs/heatmap/send.js +1 -1
  77. package/dist/cjs/heatmap/track.d.ts +1 -1
  78. package/dist/cjs/heatmap/track.js +87 -16
  79. package/dist/cjs/index.d.ts +17 -63
  80. package/dist/cjs/index.js +104 -92
  81. package/dist/cjs/lifecycle.d.ts +8 -1
  82. package/dist/cjs/lifecycle.js +258 -62
  83. package/dist/cjs/remoteconfig/country.d.ts +4 -0
  84. package/dist/cjs/remoteconfig/country.js +73 -0
  85. package/dist/cjs/remoteconfig/custom.d.ts +7 -7
  86. package/dist/cjs/remoteconfig/custom.js +31 -51
  87. package/dist/cjs/remoteconfig/evaluate.d.ts +3 -3
  88. package/dist/cjs/remoteconfig/evaluate.js +56 -43
  89. package/dist/cjs/remoteconfig/fetch.d.ts +3 -3
  90. package/dist/cjs/remoteconfig/fetch.js +9 -23
  91. package/dist/cjs/remoteconfig/index.d.ts +10 -8
  92. package/dist/cjs/remoteconfig/index.js +65 -39
  93. package/dist/cjs/remoteconfig/store.js +1 -1
  94. package/dist/cjs/remoteconfig/tz-countries.d.ts +8 -0
  95. package/dist/cjs/remoteconfig/tz-countries.js +13 -0
  96. package/dist/cjs/remotelog/gate.d.ts +2 -11
  97. package/dist/cjs/remotelog/gate.js +9 -35
  98. package/dist/cjs/remotelog/index.d.ts +4 -21
  99. package/dist/cjs/remotelog/logger.d.ts +11 -17
  100. package/dist/cjs/remotelog/logger.js +30 -58
  101. package/dist/cjs/remotelog/send.js +10 -10
  102. package/dist/cjs/remotelogger/index.d.ts +12 -0
  103. package/dist/cjs/remotelogger/index.js +18 -0
  104. package/dist/cjs/scheduler.js +2 -2
  105. package/dist/cjs/types.d.ts +155 -82
  106. package/dist/cjs/user/index.js +11 -6
  107. package/dist/esm/auto-init.d.ts +3 -3
  108. package/dist/esm/auto-init.js +1 -1
  109. package/dist/esm/core/attempts.js +1 -1
  110. package/dist/esm/core/batch.js +2 -1
  111. package/dist/esm/core/callsite.d.ts +6 -0
  112. package/dist/esm/core/callsite.js +25 -4
  113. package/dist/esm/core/consent.d.ts +5 -1
  114. package/dist/esm/core/consent.js +9 -7
  115. package/dist/esm/core/constants.d.ts +35 -10
  116. package/dist/esm/core/constants.js +30 -15
  117. package/dist/esm/core/db.d.ts +12 -4
  118. package/dist/esm/core/db.js +164 -35
  119. package/dist/esm/core/debug.js +1 -1
  120. package/dist/esm/core/env.js +1 -1
  121. package/dist/esm/core/framework-id.d.ts +1 -1
  122. package/dist/esm/core/framework-id.js +3 -3
  123. package/dist/esm/core/guard.d.ts +9 -0
  124. package/dist/esm/core/guard.js +27 -14
  125. package/dist/esm/core/hooks.d.ts +2 -4
  126. package/dist/esm/core/hooks.js +2 -3
  127. package/dist/esm/core/locks.d.ts +9 -0
  128. package/dist/esm/core/locks.js +20 -0
  129. package/dist/esm/core/logger.d.ts +3 -4
  130. package/dist/esm/core/logger.js +4 -40
  131. package/dist/esm/core/state.d.ts +3 -2
  132. package/dist/esm/core/state.js +2 -2
  133. package/dist/esm/core/storage.d.ts +20 -5
  134. package/dist/esm/core/storage.js +113 -18
  135. package/dist/esm/core/stylesheet.d.ts +8 -0
  136. package/dist/esm/core/stylesheet.js +27 -0
  137. package/dist/esm/core/transport.d.ts +2 -0
  138. package/dist/esm/core/transport.js +19 -5
  139. package/dist/esm/core/utils.js +1 -1
  140. package/dist/esm/core/validate.js +2 -2
  141. package/dist/esm/device/appversion.d.ts +1 -6
  142. package/dist/esm/device/appversion.js +13 -2
  143. package/dist/esm/device/index.d.ts +11 -0
  144. package/dist/esm/device/index.js +123 -24
  145. package/dist/esm/engagement/api-rules.d.ts +32 -67
  146. package/dist/esm/engagement/api-rules.js +143 -155
  147. package/dist/esm/engagement/auto-api.js +3 -3
  148. package/dist/esm/engagement/auto-screen.d.ts +2 -0
  149. package/dist/esm/engagement/auto-screen.js +67 -4
  150. package/dist/esm/engagement/defined-events.js +1 -1
  151. package/dist/esm/engagement/send.js +6 -1
  152. package/dist/esm/engagement/session.d.ts +12 -3
  153. package/dist/esm/engagement/session.js +162 -29
  154. package/dist/esm/engagement/track.js +52 -27
  155. package/dist/esm/errors/send.js +1 -1
  156. package/dist/esm/errors/track.d.ts +1 -1
  157. package/dist/esm/errors/track.js +46 -6
  158. package/dist/esm/feedback/controls.d.ts +6 -0
  159. package/dist/esm/feedback/controls.js +31 -2
  160. package/dist/esm/feedback/index.d.ts +8 -7
  161. package/dist/esm/feedback/index.js +34 -13
  162. package/dist/esm/feedback/logs.d.ts +10 -5
  163. package/dist/esm/feedback/logs.js +54 -15
  164. package/dist/esm/feedback/quartz.d.ts +18 -0
  165. package/dist/esm/feedback/quartz.js +99 -0
  166. package/dist/esm/feedback/send.d.ts +3 -0
  167. package/dist/esm/feedback/send.js +28 -5
  168. package/dist/esm/feedback/styles.d.ts +10 -0
  169. package/dist/esm/feedback/styles.js +4 -0
  170. package/dist/esm/feedback/template.d.ts +1 -1
  171. package/dist/esm/feedback/template.js +12 -12
  172. package/dist/esm/feedback/ui.d.ts +14 -6
  173. package/dist/esm/feedback/ui.js +126 -49
  174. package/dist/esm/heatmap/send.js +1 -1
  175. package/dist/esm/heatmap/track.d.ts +1 -1
  176. package/dist/esm/heatmap/track.js +87 -16
  177. package/dist/esm/index.d.ts +17 -63
  178. package/dist/esm/index.js +108 -80
  179. package/dist/esm/lifecycle.d.ts +8 -1
  180. package/dist/esm/lifecycle.js +266 -71
  181. package/dist/esm/remoteconfig/country.d.ts +4 -0
  182. package/dist/esm/remoteconfig/country.js +70 -0
  183. package/dist/esm/remoteconfig/custom.d.ts +7 -7
  184. package/dist/esm/remoteconfig/custom.js +32 -52
  185. package/dist/esm/remoteconfig/evaluate.d.ts +3 -3
  186. package/dist/esm/remoteconfig/evaluate.js +57 -44
  187. package/dist/esm/remoteconfig/fetch.d.ts +3 -3
  188. package/dist/esm/remoteconfig/fetch.js +9 -23
  189. package/dist/esm/remoteconfig/index.d.ts +10 -8
  190. package/dist/esm/remoteconfig/index.js +66 -40
  191. package/dist/esm/remoteconfig/store.js +1 -1
  192. package/dist/esm/remoteconfig/tz-countries.d.ts +8 -0
  193. package/dist/esm/remoteconfig/tz-countries.js +11 -0
  194. package/dist/esm/remotelog/gate.d.ts +2 -11
  195. package/dist/esm/remotelog/gate.js +10 -32
  196. package/dist/esm/remotelog/index.d.ts +4 -21
  197. package/dist/esm/remotelog/logger.d.ts +11 -17
  198. package/dist/esm/remotelog/logger.js +32 -60
  199. package/dist/esm/remotelog/send.js +12 -12
  200. package/dist/esm/remotelogger/index.d.ts +12 -0
  201. package/dist/esm/remotelogger/index.js +13 -0
  202. package/dist/esm/scheduler.js +2 -2
  203. package/dist/esm/types.d.ts +155 -82
  204. package/dist/esm/user/index.js +12 -6
  205. package/dist/feedback.esm.min.js +2 -2
  206. package/dist/feedback.esm.min.js.map +1 -1
  207. package/package.json +14 -16
  208. package/dist/chunks/batch-BU_mTpm1.min.js +0 -3
  209. package/dist/chunks/batch-BU_mTpm1.min.js.map +0 -1
  210. package/dist/chunks/logs-Bea5zUW_.min.js +0 -3
  211. package/dist/chunks/logs-Bea5zUW_.min.js.map +0 -1
  212. package/dist/cjs/heatmap/index.d.ts +0 -16
  213. package/dist/cjs/heatmap/index.js +0 -22
  214. package/dist/cjs/remotelog/index.js +0 -55
  215. package/dist/esm/heatmap/index.d.ts +0 -16
  216. package/dist/esm/heatmap/index.js +0 -17
  217. package/dist/esm/remotelog/index.js +0 -51
  218. package/dist/heatmap.esm.min.js +0 -3
  219. package/dist/heatmap.esm.min.js.map +0 -1
  220. package/dist/style.css +0 -1
@@ -1,11 +1,12 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
2
- import { AP_LOG_MODULE, AP_CONSTANTS } from '../core/constants.js';
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.1 | (c) Zoho Corporation Private Limited | see LICENSE */
2
+ import { AP_CONSTANTS, AP_LOG_MODULE } from '../core/constants.js';
3
3
  import { _ZAP, getConfig } from '../core/state.js';
4
4
  import { logFailure, Console } from '../core/logger.js';
5
- import { nowMs, isEmpty, getNewUUID } from '../core/utils.js';
6
- import { engagement_transactions, user_defaults } from '../core/storage.js';
5
+ import { isEmpty, getNewUUID, nowMs } from '../core/utils.js';
6
+ import { engagement_transactions, getKeyFromDeviceInfo, setDeviceInfoKey } from '../core/storage.js';
7
7
  import { is_apptics_disabled, getNetworkStatus, getOrientation, getConnectionType, getRam } from '../core/env.js';
8
8
  import { canTrackEngagement } from '../core/consent.js';
9
+ import { withLock } from '../core/locks.js';
9
10
 
10
11
  /** The most recently started item of `type` that is still open (endtime 0). */
11
12
  function latestOpen(type, items) {
@@ -56,10 +57,6 @@ function closeScreensOf(session, time) {
56
57
  }, Promise.resolve());
57
58
  });
58
59
  }
59
- /** `track_session: false`, Android's automaticSessionsTrackingStatus: no sessions are recorded. */
60
- function sessionsOff() {
61
- return getConfig('track_session') === false; // no i18n
62
- }
63
60
  /**
64
61
  * Track the session.
65
62
  * @param sessionData A set of properties belonging to the session.
@@ -77,13 +74,16 @@ function trackSession(sessionData) {
77
74
  Console.dir(sessionData);
78
75
  return written;
79
76
  }
80
- /** This tab's session when it has one, otherwise the newest open session in storage. */
77
+ /**
78
+ * This tab's session while it is open, otherwise the newest open session in storage: the one the
79
+ * site's other tabs are on, which this tab joins rather than starting a second session beside it.
80
+ */
81
81
  function currentSession() {
82
82
  var mine = tabSessionId();
83
83
  return engagement_transactions.all().then(function (items) {
84
84
  if (mine) {
85
85
  var ours = items.filter(function (item) {
86
- return item.type == AP_CONSTANTS.SESSION && item.id === mine;
86
+ return item.type == AP_CONSTANTS.SESSION && item.id === mine && item.endtime === 0;
87
87
  })[0];
88
88
  if (ours) {
89
89
  return ours;
@@ -92,6 +92,119 @@ function currentSession() {
92
92
  return latestOpen(AP_CONSTANTS.SESSION, items);
93
93
  });
94
94
  }
95
+ /**
96
+ * This page's own id, among the tabs sharing a session. New on every page load, so a reload or a
97
+ * duplicated tab counts as a tab of its own rather than as the one it came from. Made on first use,
98
+ * not when the module loads: getNewUUID reads `screen`, which a server-side render doesn't have.
99
+ */
100
+ var tabIdValue = ''; // no i18n
101
+ function tabId() {
102
+ if (tabIdValue === '') {
103
+ tabIdValue = getNewUUID();
104
+ }
105
+ return tabIdValue;
106
+ }
107
+ /**
108
+ * The tabs of this site using a session. All of them share one session, which ends when the last
109
+ * of them closes (see endCurrentSession). Kept on the stored session, never sent: see
110
+ * sendEngagementData. A session stored by an older SDK has none, and counts as this tab's alone.
111
+ */
112
+ function tabsOf(session) {
113
+ return Array.isArray(session.tabs) ? session.tabs.slice() : [];
114
+ }
115
+ function withThisTab(session) {
116
+ var tabs = tabsOf(session);
117
+ if (tabs.indexOf(tabId()) === -1) {
118
+ tabs.push(tabId());
119
+ }
120
+ return tabs;
121
+ }
122
+ /* ---------------- screen views left open by tabs that are gone ----------------
123
+ * A screen view is ended by the tab that opened it: by the next screen, or as the page hides or
124
+ * closes. A tab that reloads, closes or crashes before that write lands leaves its view open, and
125
+ * nothing ended it until the whole session did, which with tabs sharing one session can be hours.
126
+ *
127
+ * So each tab holds a Web Lock, `apptics-tab-<its id>`, for as long as it is open: the browser
128
+ * lets go of it the moment the tab is gone, however it went. A tab that starts, or comes back into
129
+ * view, ends every open view whose tab no longer holds its lock, at the time that tab was last seen
130
+ * active. Without the Web Locks API, views are left as before and end with their session. */
131
+ const TAB_LOCK_PREFIX = 'apptics-tab-'; // no i18n
132
+ /** A view this young may belong to a tab whose lock is not granted yet: left alone. */
133
+ const YOUNG_VIEW_MS = 5000;
134
+ function tabLocks() {
135
+ var locks = typeof navigator !== 'undefined' ? navigator.locks : undefined; // no i18n
136
+ return locks && typeof locks.request === 'function' && typeof locks.query === 'function' ? locks : undefined; // no i18n
137
+ }
138
+ var holdingTabLock = false;
139
+ /** Takes this tab's lock, held until the page is gone. Once per page. */
140
+ function holdTabLock() {
141
+ var locks = tabLocks();
142
+ if (!locks || holdingTabLock) {
143
+ return;
144
+ }
145
+ holdingTabLock = true;
146
+ locks.request(TAB_LOCK_PREFIX + tabId(), function () {
147
+ return new Promise(function () { });
148
+ }).catch(function () {
149
+ holdingTabLock = false;
150
+ });
151
+ }
152
+ /** Ends the open screen views of tabs that are gone. Resolves right away without Web Locks. */
153
+ function closeScreensOfGoneTabs() {
154
+ var locks = tabLocks();
155
+ if (!locks) {
156
+ return Promise.resolve();
157
+ }
158
+ var mine = tabId();
159
+ return locks.query().then(function (state) {
160
+ var alive = {};
161
+ (state.held || []).concat(state.pending || []).forEach(function (lock) {
162
+ if (lock.name && lock.name.indexOf(TAB_LOCK_PREFIX) === 0) {
163
+ alive[lock.name.slice(TAB_LOCK_PREFIX.length)] = true;
164
+ }
165
+ });
166
+ var now = nowMs();
167
+ return engagement_transactions.all().then(function (items) {
168
+ var gone = items.filter(function (item) {
169
+ return item.type == AP_CONSTANTS.SCREEN && item.endtime === 0 && typeof item.tab === 'string' // no i18n
170
+ && item.tab !== mine && !alive[item.tab] && now - item.starttime > YOUNG_VIEW_MS;
171
+ });
172
+ if (gone.length) {
173
+ Console.log('closeScreensOfGoneTabs: ending ' + gone.length + ' screen view(s) left open by a tab that is gone'); // no i18n
174
+ }
175
+ return gone.reduce(function (done, item) {
176
+ // when that tab was last seen with the view open, not now: the time since is not time spent
177
+ var end = Math.max(item.starttime, Number(item.lastseen) || item.starttime);
178
+ return done.then(function () {
179
+ return engagement_transactions.update(Object.assign({}, item, { endtime: end }));
180
+ });
181
+ }, Promise.resolve());
182
+ });
183
+ }).catch(function (err) {
184
+ Console.log('closeScreensOfGoneTabs: ' + err); // no i18n
185
+ });
186
+ }
187
+ /** Notes on this tab's open view that the tab is still here, the end a gone tab's view is given. */
188
+ function markScreenSeen(time) {
189
+ var current = _ZAP._current_screen;
190
+ if (!current || !current.id || current.endtime) {
191
+ return Promise.resolve();
192
+ }
193
+ return engagement_transactions.all().then(function (items) {
194
+ var open = items.filter(function (item) {
195
+ return item.type == AP_CONSTANTS.SCREEN && item.id === current.id && item.endtime === 0;
196
+ })[0];
197
+ return open ? engagement_transactions.update(Object.assign({}, open, { lastseen: time })) : undefined;
198
+ });
199
+ }
200
+ /**
201
+ * Every change to the session runs under a lock shared by the site's tabs: each one reads the
202
+ * stored session, changes it and writes it back, and two tabs doing that at once would each write
203
+ * over the other's change.
204
+ */
205
+ function withSessionLock(run) {
206
+ return withLock('session', run); // no i18n
207
+ }
95
208
  /** The id of this tab's session. sessionStorage is per tab and survives a reload. */
96
209
  const TAB_KEY = 'apptics-session'; // no i18n
97
210
  function tabSessionId() {
@@ -110,9 +223,9 @@ function rememberTabSession(id) {
110
223
  // the session still works, it just can't be recognised after a reload
111
224
  }
112
225
  }
113
- /** True until the first session on this device has been recorded. */
226
+ /** True until the first session on this device has been recorded: device_info's hadsession. */
114
227
  function isFirstEverSession() {
115
- return isEmpty(user_defaults.item(HAD_SESSION_KEY));
228
+ return getKeyFromDeviceInfo(HAD_SESSION_KEY) !== true;
116
229
  }
117
230
  const HAD_SESSION_KEY = 'hadsession'; // no i18n
118
231
  /**
@@ -149,7 +262,7 @@ function commitPendingSession() {
149
262
  // the flag is set here rather than at the start, so a dropped session does not consume it and
150
263
  // leave the next real session reporting firstsession false
151
264
  if (session.firstsession) {
152
- user_defaults.add(HAD_SESSION_KEY, true);
265
+ setDeviceInfoKey(HAD_SESSION_KEY, true);
153
266
  }
154
267
  return trackSession(session);
155
268
  }
@@ -174,6 +287,7 @@ function startNewSession(time) {
174
287
  firstsession: isFirstEverSession(),
175
288
  networkstatus: getNetworkStatus(),
176
289
  lastactivetime: time,
290
+ tabs: [tabId()],
177
291
  };
178
292
  // ram is optional in sessionWebSDK but checked against ram_rom ('4 GB'), which an empty
179
293
  // string cannot match. navigator.deviceMemory is Chromium-only, so Firefox and Safari have
@@ -200,13 +314,14 @@ function startNewSession(time) {
200
314
  /** The un-deferred path, for a minimum of 0. */
201
315
  function commitNow(session) {
202
316
  if (session.firstsession) {
203
- user_defaults.add(HAD_SESSION_KEY, true);
317
+ setDeviceInfoKey(HAD_SESSION_KEY, true);
204
318
  }
205
319
  return trackSession(session);
206
320
  }
207
321
  /**
208
- * Closes this tab's session, so it can be sent: the tab is closing or the page is being replaced.
209
- * Nothing happens when the session is already closed.
322
+ * This tab is closing, or the page is being replaced. The session ends here when this is the last
323
+ * tab using it, so it can be sent; while another tab of the site is still on it, this tab only
324
+ * leaves it, and the last tab to close ends it. Nothing happens when it is already closed.
210
325
  */
211
326
  function endCurrentSession() {
212
327
  var time = nowMs();
@@ -214,11 +329,23 @@ function endCurrentSession() {
214
329
  if (dropPendingSession()) {
215
330
  return Promise.resolve();
216
331
  }
332
+ return withSessionLock(function () {
333
+ return closeOrLeaveSession(time);
334
+ });
335
+ }
336
+ function closeOrLeaveSession(time) {
217
337
  return currentSession().then(function (_currentSession) {
218
338
  if (!_currentSession || _currentSession.endtime !== 0) {
219
339
  return;
220
340
  }
221
- var closed = Object.assign({}, _currentSession, { endtime: time, lastactivetime: time });
341
+ var others = tabsOf(_currentSession).filter(function (tab) { return tab !== tabId(); });
342
+ if (others.length > 0) {
343
+ var left = Object.assign({}, _currentSession, { tabs: others, lastactivetime: time });
344
+ _ZAP._current_session = left;
345
+ Console.log('endCurrentSession: ' + others.length + ' other tab(s) still on this session, so it stays open'); // no i18n
346
+ return engagement_transactions.update(left);
347
+ }
348
+ var closed = Object.assign({}, _currentSession, { endtime: time, lastactivetime: time, tabs: [] });
222
349
  _ZAP._current_session = closed;
223
350
  Console.log('endCurrentSession: closing the session opened at ' + closed.starttime); // no i18n
224
351
  return engagement_transactions.update(closed).then(function () {
@@ -255,18 +382,21 @@ function closeAbandonedSessions() {
255
382
  }
256
383
  function checkAndUpdateSession() {
257
384
  if (!_ZAP._token) {
258
- logFailure(AP_LOG_MODULE.SESSION, 'checkAndUpdateSession: You must call apptics.init() first'); // no i18n
385
+ logFailure(AP_LOG_MODULE.SESSION, 'checkAndUpdateSession: You must call Apptics.init() first'); // no i18n
259
386
  return Promise.resolve();
260
387
  }
261
388
  if (is_apptics_disabled()) {
262
389
  return Promise.resolve();
263
390
  }
264
- if (sessionsOff()) {
265
- return Promise.resolve();
266
- }
267
391
  var time = nowMs();
268
- // a tab that was killed before it could close its session: close it so it gets sent
269
- return closeAbandonedSessions().then(currentSession).then(function (_currentSession) {
392
+ return withSessionLock(function () {
393
+ return settleSession(time);
394
+ });
395
+ }
396
+ function settleSession(time) {
397
+ // a tab that was killed before it could close its session: close it so it gets sent; and the
398
+ // screen views left open by tabs that are gone, so they end now rather than with the session
399
+ return closeAbandonedSessions().then(closeScreensOfGoneTabs).then(currentSession).then(function (_currentSession) {
270
400
  // a closed session belongs to a tab that has gone: this tab starts its own
271
401
  if (!_currentSession || _currentSession.endtime !== 0) {
272
402
  return startNewSession(time);
@@ -287,13 +417,14 @@ function checkAndUpdateSession() {
287
417
  });
288
418
  }
289
419
  Console.log('checkAndUpdateSession: User is back before ' + _ZAP.inactiveduration + ' minutes so updating the lastactivitytime - ' + time); // no i18n
290
- return updateLastActiveTimeOfSession();
420
+ return touchSession();
291
421
  });
292
422
  }
293
423
  function updateLastActiveTimeOfSession() {
294
- if (sessionsOff()) {
295
- return Promise.resolve();
296
- }
424
+ return withSessionLock(touchSession);
425
+ }
426
+ /** Marks the session active now, with this tab among its tabs. Callers hold the session lock. */
427
+ function touchSession() {
297
428
  var time = nowMs();
298
429
  // a pending session is not in the store yet, so it is updated in place instead
299
430
  if (pendingSession) {
@@ -306,10 +437,12 @@ function updateLastActiveTimeOfSession() {
306
437
  return;
307
438
  }
308
439
  Console.log('updateLastActiveTimeOfSession: Updating the lastactivitytime - ' + time); // no i18n
309
- var _newSession = Object.assign({}, _currentSession, { lastactivetime: time });
440
+ var _newSession = Object.assign({}, _currentSession, { lastactivetime: time, tabs: withThisTab(_currentSession) });
310
441
  _ZAP._current_session = _newSession;
311
442
  return engagement_transactions.update(_newSession);
443
+ }).then(function () {
444
+ return markScreenSeen(time);
312
445
  });
313
446
  }
314
447
 
315
- export { checkAndUpdateSession, closeAbandonedSessions, closeScreen, commitPendingSession, currentSession, dropPendingSession, endCurrentSession, startNewSession, trackSession, updateLastActiveTimeOfSession, updateScreenWithEndtime };
448
+ export { checkAndUpdateSession, closeAbandonedSessions, closeScreen, closeScreensOfGoneTabs, commitPendingSession, currentSession, dropPendingSession, endCurrentSession, holdTabLock, startNewSession, tabId, trackSession, updateLastActiveTimeOfSession, updateScreenWithEndtime };
@@ -1,19 +1,33 @@
1
- /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta | (c) Zoho Corporation Private Limited | see LICENSE */
2
- import { AP_LOG_MODULE, AP_CONFIG, AP_CONSTANTS } from '../core/constants.js';
1
+ /*! @zoho_apptics/apptics-js-sdk v2.0.0-beta.1 | (c) Zoho Corporation Private Limited | see LICENSE */
2
+ import { AP_LOG_MODULE, AP_CONSTANTS, AP_CONFIG } from '../core/constants.js';
3
3
  import { _ZAP } from '../core/state.js';
4
- import { logFailure, Console } from '../core/logger.js';
5
- import { getNewUUID, nowMs, isEmpty } from '../core/utils.js';
4
+ import { logFailure, Console, warnAlways } from '../core/logger.js';
5
+ import { isEmpty, nowMs, getNewUUID } from '../core/utils.js';
6
6
  import { engagement_transactions } from '../core/storage.js';
7
- import { commitPendingSession, closeScreen } from './session.js';
8
- import { getConnectionType, getNetworkStatus, getOrientation } from '../core/env.js';
7
+ import { closeScreen, commitPendingSession, tabId } from './session.js';
8
+ import { getConnectionType, getOrientation, getNetworkStatus } from '../core/env.js';
9
9
  import { canTrackEngagement } from '../core/consent.js';
10
- import { validateEventOrGroup, validateJSON, validateScreenName } from '../core/validate.js';
10
+ import { validateScreenName, validateEventOrGroup, validateJSON } from '../core/validate.js';
11
11
  import { noteDebugActivity } from '../core/debug.js';
12
12
  import { hooks } from '../core/hooks.js';
13
- import { hostAllowed, endpointIgnored, normalizeApiPath, groupedHost } from './api-rules.js';
13
+ import { urlTracked, normalizeApiPath } from './api-rules.js';
14
14
  import { asDefinedEvent } from './defined-events.js';
15
15
  import { automaticScreenName } from './auto-screen.js';
16
16
 
17
+ /** Messages already printed, so a name refused in a loop says so once. */
18
+ var refusedSaid = {};
19
+ /**
20
+ * A call the SDK refused because of what the app passed it (an invalid event, group or screen
21
+ * name): reported as every failure is, and said in the console too, debug or not, once per
22
+ * message. Nothing else tells an integrator why the event or screen never shows up.
23
+ */
24
+ function refused(module, message) {
25
+ logFailure(module, message);
26
+ if (!refusedSaid[message]) {
27
+ refusedSaid[message] = true;
28
+ warnAlways(message);
29
+ }
30
+ }
17
31
  /**
18
32
  * Records an event: `trackEvent(name, group?, properties?)`, or a defined event with
19
33
  * `trackEvent(DefinedEvents.AP_USER_LOGIN, properties?)`, which brings its own group.
@@ -35,14 +49,14 @@ function trackEvent(eventName, groupName, jsonObj) {
35
49
  }
36
50
  else {
37
51
  if (typeof eventName !== 'string' || !validateEventOrGroup(eventName)) { // no i18n
38
- logFailure(AP_LOG_MODULE.EVENT, 'AppticsEvents.addEvent: Invalid eventName: ' + String(eventName)); // no i18n
52
+ refused(AP_LOG_MODULE.EVENT, 'AppticsEvent.addEvent: Invalid eventName: ' + String(eventName)); // no i18n
39
53
  return;
40
54
  }
41
55
  if (groupName === undefined || groupName === null || groupName === '') { // no i18n
42
56
  groupName = AP_CONFIG.default_group_name;
43
57
  }
44
58
  else if (typeof groupName !== 'string' || !validateEventOrGroup(groupName)) { // no i18n
45
- logFailure(AP_LOG_MODULE.EVENT, 'AppticsEvents.addEvent: Invalid groupName for "' + eventName + '": ' + String(groupName)); // no i18n
59
+ refused(AP_LOG_MODULE.EVENT, 'AppticsEvent.addEvent: Invalid groupName for "' + eventName + '": ' + String(groupName)); // no i18n
46
60
  return;
47
61
  }
48
62
  }
@@ -68,7 +82,7 @@ function trackEvent(eventName, groupName, jsonObj) {
68
82
  commitPendingSession();
69
83
  noteDebugActivity();
70
84
  engagement_transactions.add(eventData);
71
- Console.log('AppticsEvents.addEvent(\'' + eventName + '\',\'' + groupName + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
85
+ Console.log('AppticsEvent.addEvent(\'' + eventName + '\',\'' + groupName + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
72
86
  Console.dir(eventData);
73
87
  }
74
88
  /**
@@ -78,7 +92,7 @@ function trackEvent(eventName, groupName, jsonObj) {
78
92
  */
79
93
  function inScreenAt(screenName, jsonObj, id, startedAt) {
80
94
  if (!validateScreenName(screenName)) {
81
- logFailure(AP_LOG_MODULE.SCREEN, 'AppticsScreenTracker.inScreen: Invalid screenName: ' + String(screenName)); // no i18n
95
+ refused(AP_LOG_MODULE.SCREEN, 'AppticsScreen.inScreen: Invalid screenName: ' + String(screenName)); // no i18n
82
96
  return;
83
97
  }
84
98
  // the app named this screen itself: the automatic view of the same navigation gives way to it
@@ -88,7 +102,7 @@ function inScreenAt(screenName, jsonObj, id, startedAt) {
88
102
  /** Ends the screen view inScreen() began, as Android's AppticsScreenTracker.outScreen(id). */
89
103
  function outScreenAt(id, endedAt) {
90
104
  if (isEmpty(id)) {
91
- logFailure(AP_LOG_MODULE.SCREEN, 'AppticsScreenTracker.outScreen: no screen id given'); // no i18n
105
+ logFailure(AP_LOG_MODULE.SCREEN, 'AppticsScreen.outScreen: no screen id given'); // no i18n
92
106
  return;
93
107
  }
94
108
  closeScreen(id, endedAt);
@@ -105,7 +119,7 @@ function recordScreen(screenName, jsonObj, id, startedAt, implicit) {
105
119
  return;
106
120
  }
107
121
  if (!validateScreenName(screenName)) {
108
- logFailure(AP_LOG_MODULE.SCREEN, 'AppticsScreenTracker.inScreen: Invalid screenName: ' + String(screenName)); // no i18n
122
+ refused(AP_LOG_MODULE.SCREEN, 'AppticsScreen.inScreen: Invalid screenName: ' + String(screenName)); // no i18n
109
123
  return;
110
124
  }
111
125
  jsonObj = jsonObj || {};
@@ -122,7 +136,10 @@ function recordScreen(screenName, jsonObj, id, startedAt, implicit) {
122
136
  networkstatus: getNetworkStatus(),
123
137
  orientation: getOrientation(),
124
138
  sessionstarttime: _ZAP._current_session.starttime || time,
125
- customproperties: _jsonObj
139
+ customproperties: _jsonObj,
140
+ // the tab showing it, so a tab that is gone can have its view ended (closeScreensOfGoneTabs).
141
+ // SDK bookkeeping, removed before sending, as lastseen is
142
+ tab: tabId(),
126
143
  };
127
144
  // the screen being left ends where this one starts
128
145
  var previous = _ZAP._current_screen && _ZAP._current_screen.id;
@@ -135,7 +152,7 @@ function recordScreen(screenName, jsonObj, id, startedAt, implicit) {
135
152
  // `implicit`: a view recorded only so a heatmap record has one to belong to. Kept on the
136
153
  // in-memory copy alone, never on the stored item, which goes to the server as it is
137
154
  _ZAP._current_screen = implicit ? Object.assign({}, screenData, { implicit: true }) : screenData;
138
- Console.log('AppticsScreenTracker.inScreen(\'' + screenName + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
155
+ Console.log('AppticsScreen.inScreen(\'' + screenName + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
139
156
  Console.dir(screenData);
140
157
  }
141
158
  /**
@@ -224,16 +241,19 @@ function trackApi(jsonObj) {
224
241
  var hostname = jsonObj.hostname || '';
225
242
  var port = (typeof jsonObj.port === 'number') ? jsonObj.port : 0;
226
243
  var path = jsonObj.path || '';
244
+ // a call reported by its parts, a hostname and a path with no url: its scheme and port are its
245
+ // own (https and 443 unless given), not the page's, which belong to another host
246
+ var partsOnly = isEmpty(jsonObj.url) && !isEmpty(jsonObj.hostname);
227
247
  if (!isEmpty(rawUrl)) {
228
248
  try {
229
249
  var urlObj = new URL(rawUrl, window.location.origin);
230
- if (isEmpty(scheme)) {
250
+ if (isEmpty(scheme) && !partsOnly) {
231
251
  scheme = urlObj.protocol.replace(':', ''); // "https:"
232
252
  }
233
253
  if (isEmpty(hostname)) {
234
254
  hostname = urlObj.hostname;
235
255
  }
236
- if (!port) {
256
+ if (!port && !partsOnly) {
237
257
  if (urlObj.port) {
238
258
  port = parseInt(urlObj.port, 10);
239
259
  }
@@ -254,22 +274,27 @@ function trackApi(jsonObj) {
254
274
  }
255
275
  // only http and https exist as far as the proxy's api_scheme is concerned
256
276
  scheme = (scheme === 'https' || scheme === 'http') ? scheme : 'https'; // no i18n
277
+ if (!port) {
278
+ port = scheme === 'https' ? 443 : 80; // no i18n
279
+ }
257
280
  // a hostname the proxy refuses cannot be repaired without inventing one, so the call is
258
281
  // dropped here and said out loud. localhost and bare IPs land here: api_hostname wants at
259
282
  // least one dot and an alphabetic suffix, which is worth knowing when nothing shows up locally
260
283
  if (!API_HOSTNAME.test(hostname)) {
261
- logFailure(AP_LOG_MODULE.API, 'AppticsApiTracker: hostname not accepted by the server: ' + String(hostname)); // no i18n
284
+ logFailure(AP_LOG_MODULE.API, 'AppticsApi: hostname not accepted by the server: ' + String(hostname)); // no i18n
262
285
  return;
263
286
  }
264
- // configureApiTracking: the app's rules decide whether this call is recorded at all
265
- if (!hostAllowed(hostname) || endpointIgnored(path)) {
266
- Console.log('AppticsApiTracker: ' + hostname + withoutQueryForLog(path) + ' is left out by configureApiTracking'); // no i18n
287
+ // configureApiTracking: the app's rules decide whether this call is recorded at all, tried on
288
+ // the URL as it was called, without its query
289
+ var defaultPort = (scheme === 'https' && port === 443) || (scheme === 'http' && port === 80); // no i18n
290
+ var calledUrl = scheme + '://' + hostname + (port && !defaultPort ? ':' + port : '') + (String(path).charAt(0) === '/' ? '' : '/') + path; // no i18n
291
+ if (!urlTracked(calledUrl)) {
292
+ Console.log('AppticsApi: ' + hostname + withoutQueryForLog(path) + ' is left out by configureApiTracking'); // no i18n
267
293
  return;
268
294
  }
269
- // and the name it is recorded under: ids become *, regional hosts one group, so the console
270
- // groups calls by endpoint rather than by every id that went through it
295
+ // and the name it is recorded under: ids become *, so the console groups calls by endpoint
296
+ // rather than by every id that went through it
271
297
  path = sanitizeApiPath(normalizeApiPath(path));
272
- hostname = groupedHost(hostname);
273
298
  var sessionStart = _ZAP._current_session.starttime || starttime;
274
299
  // no customproperties: apiWebSDK does not declare the key, unlike the screen and event
275
300
  // templates, so anything extra the caller passes is context for the log line only
@@ -294,7 +319,7 @@ function trackApi(jsonObj) {
294
319
  commitPendingSession();
295
320
  noteDebugActivity();
296
321
  engagement_transactions.add(apiData);
297
- Console.log('AppticsApiTracker(\'' + method + ' ' + scheme + '://' + hostname + path + '\')'); // no i18n
322
+ Console.log('AppticsApi(\'' + method + ' ' + scheme + '://' + hostname + path + '\')'); // no i18n
298
323
  Console.dir(apiData);
299
324
  }
300
325
  /** Begun calls by id. Bounded, so an app that starts calls it never ends can't grow it forever. */
@@ -322,7 +347,7 @@ function startTrackApi(url, method) {
322
347
  function endTrackApiAt(id, responseCode, responseMessage, endedAt) {
323
348
  var open = openCalls[id];
324
349
  if (!open) {
325
- logFailure(AP_LOG_MODULE.API, 'AppticsApiTracker.endTrackApi: no call started with id ' + String(id)); // no i18n
350
+ logFailure(AP_LOG_MODULE.API, 'AppticsApi.endTrackApi: no call started with id ' + String(id)); // no i18n
326
351
  return;
327
352
  }
328
353
  delete openCalls[id];
@@ -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.1 | (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';
@@ -1,4 +1,4 @@
1
1
  import type { Properties } from '../types.js';
2
2
  export declare function trackError(errorEvent: any, jsonObj?: Properties): void;
3
- export declare function enableGlobalErrorHandler(status?: boolean): void;
3
+ export declare function trackGlobalErrorHandler(status?: boolean): void;
4
4
  export declare function extractMethodNameFromStackTrace(stackTrace: string): string;
@@ -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.1 | (c) Zoho Corporation Private Limited | see LICENSE */
2
2
  import { AP_CONSTANTS } from '../core/constants.js';
3
3
  import { _ZAP } from '../core/state.js';
4
4
  import { Console } from '../core/logger.js';
@@ -36,19 +36,59 @@ function stackOf(errorEvent) {
36
36
  }
37
37
  /** Said in place of a stack when there is none, so `message` is never sent empty. */
38
38
  const NO_STACK = 'no stack available'; // no i18n
39
+ /** The element an event came from, as `<script src="…">`, so a failed load says what failed. */
40
+ function describeTarget(target) {
41
+ var el = target;
42
+ if (!el || typeof el.tagName !== 'string') { // no i18n
43
+ return ''; // no i18n
44
+ }
45
+ var tag = el.tagName.toLowerCase();
46
+ var url = el.currentSrc || el.src || el.href || ''; // no i18n
47
+ return ' on <' + tag + (url ? (el.href && !el.src ? ' href="' : ' src="') + url + '"' : '') + '>'; // no i18n
48
+ }
49
+ /**
50
+ * What was thrown or rejected, as text an issue can be named by. An Error reads as itself. A
51
+ * promise rejected with an Event - what a failed script or image load hands over - used to be
52
+ * recorded as "[object Event]"; it is named by its type and where it came from instead. Anything
53
+ * else that is an object is shown as its JSON, cut short.
54
+ */
55
+ function describeThrown(thrown) {
56
+ if (thrown === null || thrown === undefined) {
57
+ return ''; // no i18n
58
+ }
59
+ if (typeof thrown !== 'object') { // no i18n
60
+ return String(thrown);
61
+ }
62
+ if (thrown instanceof Error || typeof thrown.message === 'string' && typeof thrown.name === 'string') { // no i18n
63
+ return String(thrown);
64
+ }
65
+ if (typeof Event !== 'undefined' && thrown instanceof Event) { // no i18n
66
+ return 'Event "' + thrown.type + '"' + describeTarget(thrown.target); // no i18n
67
+ }
68
+ var text = String(thrown);
69
+ if (text !== '[object Object]') { // no i18n
70
+ return text;
71
+ }
72
+ try {
73
+ return JSON.stringify(thrown).slice(0, 300);
74
+ }
75
+ catch (err) {
76
+ return text;
77
+ }
78
+ }
39
79
  function trackError(errorEvent, jsonObj) {
40
80
  if (!canTrackErrors('trackError')) { // no i18n
41
81
  return;
42
82
  }
43
83
  if (isEmpty(errorEvent)) {
44
- Console.warn('AppticsTrackError: Invalid error'); // no i18n
84
+ Console.warn('AppticsError.trackError: Invalid error'); // no i18n
45
85
  return;
46
86
  }
47
87
  var _jsonObj = validateJSON(jsonObj);
48
88
  var stack = stackOf(errorEvent);
49
89
  var methodName = extractMethodNameFromStackTrace(stack) || "";
50
90
  var issuetype = errorEvent.type || "";
51
- var thrown = errorEvent.reason || errorEvent.error || errorEvent.message || "";
91
+ var thrown = describeThrown(errorEvent.reason || errorEvent.error || errorEvent.message || "");
52
92
  // "Error: x at handler", or just "Error: x" when the stack names no method. issuename is
53
93
  // required too, so something that carried no message at all is still named rather than blank
54
94
  var described = !isEmpty(String(thrown)) ? String(thrown) : (!isEmpty(issuetype) ? String(issuetype) : 'Unknown error'); // no i18n
@@ -74,10 +114,10 @@ function trackError(errorEvent, jsonObj) {
74
114
  commitPendingSession();
75
115
  noteDebugActivity();
76
116
  error_transactions.add(errorData);
77
- Console.log('AppticsTrackError(\'' + issuetype + '\',\'' + message + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
117
+ Console.log('AppticsError.trackError(\'' + issuetype + '\',\'' + message + '\',' + JSON.stringify(_jsonObj || {}) + ')'); // no i18n
78
118
  Console.dir(errorData);
79
119
  }
80
- function enableGlobalErrorHandler(status) {
120
+ function trackGlobalErrorHandler(status) {
81
121
  _ZAP._isGlobalErrorTracking = typeof status === "boolean" ? status : true; // no i18n
82
122
  }
83
123
  function extractMethodNameFromStackTrace(stackTrace) {
@@ -120,4 +160,4 @@ function extractMethodNameFromStackTrace(stackTrace) {
120
160
  return methodName;
121
161
  }
122
162
 
123
- export { enableGlobalErrorHandler, extractMethodNameFromStackTrace, trackError };
163
+ export { extractMethodNameFromStackTrace, trackError, trackGlobalErrorHandler };
@@ -12,6 +12,12 @@ export declare const logChoice: {
12
12
  includeLog?: boolean;
13
13
  includeDyninfo?: boolean;
14
14
  };
15
+ /**
16
+ * Who a sendFeedback() report, made without the form, is attributed to: nobody when `anonymous`,
17
+ * else the user AppticsUser.setUser set (or the active registered one, status 1). Returns a function
18
+ * that puts back the choice that was there, so a form left open keeps its own.
19
+ */
20
+ export declare function chooseUserForSend(anonymous: boolean): () => void;
15
21
  /**
16
22
  * The "User" dropdown above the description. It lists Anonymous plus every registered user, and
17
23
  * starts on the active one (status 1) unless the caller asked for anonymous.
@@ -1,8 +1,9 @@
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.1 | (c) Zoho Corporation Private Limited | see LICENSE */
2
2
  import { Console } from '../core/logger.js';
3
3
  import { hooks } from '../core/hooks.js';
4
4
  import { user_defaults } from '../core/storage.js';
5
5
  import { isEmpty } from '../core/utils.js';
6
+ import { _ZAP } from '../core/state.js';
6
7
  import { hasLogLines, hasDiagnostics, logText, diagnosticsText } from './logs.js';
7
8
 
8
9
  /**
@@ -23,6 +24,34 @@ function resetUserChoice() {
23
24
  userChoice.userid = ''; // no i18n
24
25
  userChoice.groupid = ''; // no i18n
25
26
  }
27
+ /**
28
+ * Who a sendFeedback() report, made without the form, is attributed to: nobody when `anonymous`,
29
+ * else the user AppticsUser.setUser set (or the active registered one, status 1). Returns a function
30
+ * that puts back the choice that was there, so a form left open keeps its own.
31
+ */
32
+ function chooseUserForSend(anonymous) {
33
+ var before = { anonymous: userChoice.anonymous, userid: userChoice.userid, groupid: userChoice.groupid };
34
+ resetUserChoice();
35
+ if (!anonymous) {
36
+ // the user setUser set on this page, else the active one of those registered before
37
+ var active = !isEmpty(_ZAP._user_info.userid) ? _ZAP._user_info : null;
38
+ storedUsers().forEach(function (user) {
39
+ if (!active && user && user.status == 1 && !isEmpty(user.userid)) {
40
+ active = user;
41
+ }
42
+ });
43
+ if (active) {
44
+ userChoice.anonymous = false;
45
+ userChoice.userid = String(active.userid);
46
+ userChoice.groupid = String(active.groupid || ''); // no i18n
47
+ }
48
+ }
49
+ return function restore() {
50
+ userChoice.anonymous = before.anonymous;
51
+ userChoice.userid = before.userid;
52
+ userChoice.groupid = before.groupid;
53
+ };
54
+ }
26
55
  /** Every distinct registered user, by the address the picker shows. */
27
56
  function storedUsers() {
28
57
  var users = user_defaults.item('users'); // no i18n
@@ -277,4 +306,4 @@ hooks.feedbackUser = function () {
277
306
  return userChoice.anonymous || isEmpty(userChoice.userid) ? null : userChoice;
278
307
  };
279
308
 
280
- export { logChoice, userChoice, wireDrag, wireLogControls, wireUserPicker };
309
+ export { chooseUserForSend, logChoice, userChoice, wireDrag, wireLogControls, wireUserPicker };