@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
@@ -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');
@@ -30,15 +30,28 @@ const LABEL = 'AppticsRemoteConfig'; // no i18n
30
30
  /** getupdatesobj caps moduleids at nine entries; this SDK asks about one. */
31
31
  const MODULES = [Number(constants.SDK_MODULE.REMOTE_CONFIG)];
32
32
  var inFlight = null;
33
+ /** Whether the request in flight asked with flagtime 0, which a cold fetch can join. */
34
+ var inFlightWhole = false;
33
35
  /** Bumped by reset(); an answer to a request made before it is not stored. */
34
36
  var generation = 0;
35
37
  /**
36
- * How long a fetch that succeeded on this page answers for later ones, as on Android: a value
37
- * read on every render must not be a request on every render.
38
+ * When getupdates is asked again, after the first of a page load: every POLL_MS. Coming back to
39
+ * the tab asks nothing; the poll's timer carries on. fetchValue() and fetchValues() never ask
40
+ * themselves once a fetch has succeeded: a value read on every render must not be a request on
41
+ * every render.
38
42
  */
39
- const CACHE_MS = 5 * 60 * 1000;
40
- /** When a fetch on this page last succeeded; 0 until one has. */
43
+ const POLL_MS = 3 * 60 * 1000;
44
+ /**
45
+ * coldFetch goes to the network every time, so it is limited: three in any minute, as on Android.
46
+ * One past that is answered from the values this page already has.
47
+ */
48
+ const COLD_LIMIT = 3;
49
+ const COLD_WINDOW_MS = 60 * 1000;
50
+ /** When a fetch on this page last succeeded; 0 until one has, and the next one sends flagtime 0. */
41
51
  var fetchedAt = 0;
52
+ /** When the coldFetch calls of the last minute went out. */
53
+ var coldCalls = [];
54
+ var pollTimer = null;
42
55
  /**
43
56
  * Whether remote config is still worth asking about.
44
57
  *
@@ -58,11 +71,11 @@ function wanted() {
58
71
  * id for "macOS 15", not the small int this template types. languagecode is sent when there is
59
72
  * one, since safe_string cannot match an empty string.
60
73
  */
61
- function body() {
74
+ function body(since) {
62
75
  var payload = {
63
76
  appversionid: String(state._ZAP._device_info.appversionid || ''), // no i18n
64
77
  moduleids: MODULES,
65
- flagtime: store.flagtime(),
78
+ flagtime: since,
66
79
  };
67
80
  var language = String(navigator.language || '').slice(0, 10); // no i18n
68
81
  if (language !== '') {
@@ -75,6 +88,9 @@ function body() {
75
88
  *
76
89
  * Only to stop asking: getsdkconfig decides whether the feature is on. A module reported off
77
90
  * here is dropped from the next request, which is what the API asks for.
91
+ *
92
+ * A report that differs from the last one (on until one says otherwise) means the server's
93
+ * settings changed, so getsdkconfig, which decides, is asked now rather than at its next check.
78
94
  */
79
95
  function applyReportedModules(data) {
80
96
  var status = data.tracking_status;
@@ -83,20 +99,35 @@ function applyReportedModules(data) {
83
99
  }
84
100
  var entry = status[constants.SDK_MODULE.REMOTE_CONFIG];
85
101
  if (entry && typeof entry === 'object') {
86
- state._ZAP._remoteConfigReported = entry.enabled !== false;
102
+ var reported = entry.enabled !== false;
103
+ var changed = reported !== state._ZAP._remoteConfigReported;
104
+ state._ZAP._remoteConfigReported = reported;
105
+ if (changed) {
106
+ logger.Console.log(LABEL + ': getupdates reported the module ' + (reported ? 'on' : 'off') + ', asking getsdkconfig'); // no i18n
107
+ hooks.hooks.askServerSettings();
108
+ }
87
109
  }
88
110
  }
89
111
  /**
90
- * Asks for the rule set, if anything has changed since the last one.
112
+ * Asks for the rule set: the whole of it with flagtime 0 on the first fetch of a page load, then
113
+ * only what changed since the last answered request, which is merged into what is stored.
114
+ *
115
+ * Resolves with the answer, whether or not it carried a rule set — a later answer with no
116
+ * `remoteconfig` block means nothing has changed, which is the normal case and not a failure.
91
117
  *
92
- * Resolves with the parameters as they stand afterwards, whether or not the answer carried a new
93
- * rule set — an answer with no `remoteconfig` block means nothing has changed, which is the
94
- * normal case and not a failure.
118
+ * With `whole`, a cold fetch: flagtime 0 whatever this page has fetched already, and the answer
119
+ * replaces the stored set as the first fetch of a page load does.
95
120
  *
96
- * A second caller joins the first rather than making another request.
121
+ * A second caller joins the first rather than making another request. A cold fetch joins one
122
+ * only when it asked with flagtime 0 too, and otherwise asks once it has answered.
97
123
  */
98
- function fetchRemoteConfig() {
124
+ function fetchRemoteConfig(whole) {
99
125
  if (inFlight !== null) {
126
+ if (whole === true && !inFlightWhole) {
127
+ return inFlight.catch(function () { return {}; }).then(function () {
128
+ return fetchRemoteConfig(true);
129
+ });
130
+ }
100
131
  return inFlight;
101
132
  }
102
133
  if (!wanted()) {
@@ -108,7 +139,11 @@ function fetchRemoteConfig() {
108
139
  return blocked;
109
140
  }
110
141
  var asked = generation;
111
- var request = transport.postToApptics('getupdates', body(), transport.apiHeaders(), LABEL).then(function (result) {
142
+ // the first fetch of the page asks for everything; the stored set is a previous visit's
143
+ var askWhole = whole === true || fetchedAt === 0;
144
+ var since = askWhole ? 0 : store.flagtime();
145
+ var askedAt = utils.nowMs();
146
+ var request = transport.postToApptics('getupdates', body(since), transport.apiHeaders(), LABEL).then(function (result) {
112
147
  var data = (result.data || {});
113
148
  if (asked !== generation) {
114
149
  // asked before a reset(): the answer is about the device and app that were just forgotten
@@ -116,15 +151,18 @@ function fetchRemoteConfig() {
116
151
  }
117
152
  applyReportedModules(data);
118
153
  var rules = data.remoteconfig;
119
- var at = utils.nowMs();
120
- fetchedAt = at;
121
- if (rules && typeof rules === 'object') {
122
- store.store(rules, at);
154
+ fetchedAt = utils.nowMs();
155
+ if (askWhole) {
156
+ // with flagtime 0 the answer is the whole set, and no block means there is nothing in it
157
+ store.store(rules && typeof rules === 'object' ? rules : {}, askedAt);
158
+ }
159
+ else if (rules && typeof rules === 'object') {
160
+ store.merge(rules, askedAt);
123
161
  }
124
162
  else {
125
163
  // nothing has changed since our flagtime, which is the answer most of the time
126
- store.storeUnchanged(at);
127
- logger.Console.log(LABEL + ': no changes since ' + store.flagtime()); // no i18n
164
+ store.storeUnchanged(askedAt);
165
+ logger.Console.log(LABEL + ': no changes since ' + since); // no i18n
128
166
  }
129
167
  return data;
130
168
  }).then(function (data) {
@@ -134,13 +172,19 @@ function fetchRemoteConfig() {
134
172
  settle();
135
173
  return Promise.reject(err);
136
174
  });
137
- // a request reset() has disowned must not clear the in-flight slot of one made after it
175
+ // a request reset() has disowned must not clear the in-flight slot of one made after it, nor
176
+ // start the polling again
138
177
  function settle() {
139
178
  if (inFlight === request) {
140
179
  inFlight = null;
180
+ inFlightWhole = false;
181
+ }
182
+ if (asked === generation) {
183
+ schedulePoll();
141
184
  }
142
185
  }
143
186
  inFlight = request;
187
+ inFlightWhole = askWhole;
144
188
  return inFlight;
145
189
  }
146
190
  /**
@@ -150,30 +194,50 @@ function fetchRemoteConfig() {
150
194
  function resetRemoteConfig() {
151
195
  generation++;
152
196
  inFlight = null;
197
+ inFlightWhole = false;
153
198
  fetchedAt = 0;
199
+ coldCalls = [];
200
+ if (pollTimer !== null) {
201
+ clearTimeout(pollTimer);
202
+ pollTimer = null;
203
+ }
154
204
  state._ZAP._remoteConfigReported = true;
155
205
  custom.clearCustomConditionValues();
156
206
  store.clearRemoteConfig();
157
207
  }
158
208
  /**
159
- * Makes sure the values are this page's own: from a fetch that just succeeded, or one within
160
- * CACHE_MS. Resolves true when they are, false when the fetch failed or could not be made — the
161
- * caller then decides whether stored values will do. Never rejects.
209
+ * Makes sure the values are this page's own: from a fetch on this page that succeeded, kept up to
210
+ * date by the polling after it. With `coldFetch`, a fetch with flagtime 0 now, whatever this page
211
+ * already has, whose answer replaces what is stored: at most COLD_LIMIT a minute, and one past
212
+ * that answers from what this page has. Resolves true when they are, false when the
213
+ * fetch failed or could not be made — the caller then decides whether stored values will do.
214
+ * Never rejects.
162
215
  *
163
216
  * Waits for start-up and for the device to be registered, which a first visit is still doing
164
217
  * when ready() resolves: getupdates is refused without the device ids.
165
218
  */
166
- function obtain() {
219
+ function obtain(coldFetch) {
167
220
  return lifecycle.ready().then(function (running) {
168
221
  if (!running) {
169
222
  return false;
170
223
  }
171
- var now = utils.nowMs();
172
- if (fetchedAt > 0 && now - fetchedAt < CACHE_MS) {
224
+ var cold = coldFetch === true;
225
+ if (cold) {
226
+ var now = utils.nowMs();
227
+ coldCalls = coldCalls.filter(function (at) { return now - at < COLD_WINDOW_MS; });
228
+ if (coldCalls.length >= COLD_LIMIT) {
229
+ logger.Console.warn(LABEL + ': coldFetch is limited to ' + COLD_LIMIT + ' a minute; this one is answered from the values this page has'); // no i18n
230
+ cold = false;
231
+ }
232
+ else {
233
+ coldCalls.push(now);
234
+ }
235
+ }
236
+ if (fetchedAt > 0 && !cold) {
173
237
  return true;
174
238
  }
175
239
  return index.deviceReady().then(function () {
176
- return fetchRemoteConfig();
240
+ return fetchRemoteConfig(cold);
177
241
  }).then(function () {
178
242
  return true;
179
243
  }, function (err) {
@@ -182,9 +246,40 @@ function obtain() {
182
246
  });
183
247
  });
184
248
  }
185
- /** Start-up's own fetch, so values are this session's without the app asking. */
249
+ /** A fetch for the polling: failures are logged, and the next tick tries again. */
250
+ function refresh(reason) {
251
+ if (!state._ZAP._ready) {
252
+ return;
253
+ }
254
+ logger.Console.log(LABEL + ': asking for changes (' + reason + ')'); // no i18n
255
+ index.deviceReady().then(function () {
256
+ return fetchRemoteConfig();
257
+ }).catch(function (err) {
258
+ logger.Console.log(LABEL + ': fetch failed: ' + String(err)); // no i18n
259
+ }).then(keepPolling);
260
+ }
261
+ /**
262
+ * A fetch that never went out (offline, the device not registered yet, the server switched it
263
+ * off) schedules nothing itself: the polling carries on regardless, so it picks up again.
264
+ */
265
+ function keepPolling() {
266
+ if (state._ZAP._ready && pollTimer === null && inFlight === null) {
267
+ schedulePoll();
268
+ }
269
+ }
270
+ /** The next poll, POLL_MS after the last fetch settled, answered or not. */
271
+ function schedulePoll() {
272
+ if (pollTimer !== null) {
273
+ clearTimeout(pollTimer);
274
+ }
275
+ pollTimer = setTimeout(function () {
276
+ pollTimer = null;
277
+ refresh('every ' + POLL_MS + ' ms'); // no i18n
278
+ }, POLL_MS);
279
+ }
280
+ /** Start-up's own fetch, so values are this session's without the app asking, then the polling. */
186
281
  function fetchOnStart() {
187
- obtain();
282
+ obtain().then(keepPolling);
188
283
  }
189
284
  hooks.hooks.resetRemoteConfig = resetRemoteConfig;
190
285
  hooks.hooks.remoteConfigStarted = fetchOnStart;
@@ -1,10 +1,19 @@
1
1
  /** AppticsRemoteConfig: fetchValue, fetchValues and setCustomConditionValue, each with the parameter names the console shows. */
2
2
  export declare const AppticsRemoteConfig: {
3
- fetchValue: (name: string, callback?: (value: string | null) => void) => Promise<string | null>;
4
- /** The parameters named, or every parameter when none are: name -> value, null when there is none. */
3
+ /** One parameter's value, null when there is none. coldFetch (false by default) asks the server for the whole set now. */
4
+ fetchValue: {
5
+ (name: string, callback?: (value: string | null) => void, coldFetch?: boolean): Promise<string | null>;
6
+ (name: string, coldFetch: boolean): Promise<string | null>;
7
+ };
8
+ /**
9
+ * The parameters named, or every parameter when none are: name -> value, null when there is
10
+ * none. coldFetch (false by default) asks the server for the whole set now.
11
+ */
5
12
  fetchValues: {
6
- (names?: string[], callback?: (values: Record<string, string | null>) => void): Promise<Record<string, string | null>>;
7
- (callback: (values: Record<string, string | null>) => void): Promise<Record<string, string | null>>;
13
+ (names?: string[], callback?: (values: Record<string, string | null>) => void, coldFetch?: boolean): Promise<Record<string, string | null>>;
14
+ (names: string[], coldFetch: boolean): Promise<Record<string, string | null>>;
15
+ (callback: (values: Record<string, string | null>) => void, coldFetch?: boolean): Promise<Record<string, string | null>>;
16
+ (coldFetch: boolean): Promise<Record<string, string | null>>;
8
17
  };
9
18
  /**
10
19
  * What this device is for a custom condition, by the condition's name, as comma separated
@@ -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
  Object.defineProperty(exports, '__esModule', { value: true });
@@ -17,9 +17,11 @@ var store = require('./store.js');
17
17
  * parameter the console has. Each parameter's value is the console's: the value of the first
18
18
  * condition, in the console's order, that this device matches, else the parameter's default value.
19
19
  * setCustomConditionValue(conditionname, 'a,b') answers the custom conditions.
20
- * The rule set is stored and resolved on the device, so a fetch within five minutes answers without
21
- * waiting on the network, and a failed one answers with the values fetched before. The SDK fetches
22
- * on start-up, and re-applies the rules on its own when AppticsUser.setUser changes the user.
20
+ * The rule set is stored and resolved on the device, so a read answers without waiting on the
21
+ * network, and a failed fetch answers with the values fetched before. The SDK fetches the whole set
22
+ * on start-up, then what changed every 3 minutes; coldFetch (true as
23
+ * the last argument) asks for the whole set now. It re-applies the rules on its own when
24
+ * AppticsUser.setUser changes the user.
23
25
  *
24
26
  * A name the console does not have is null, never undefined: a config system whose failure mode
25
27
  * is `undefined` moves the problem into the app's rendering code.
@@ -43,27 +45,37 @@ function answer(value, callback) {
43
45
  /**
44
46
  * One parameter, fetched for this page, as Android's fetchValue: resolves with its value, or null
45
47
  * when the console has no such parameter. A failed fetch answers with the value fetched before.
48
+ *
49
+ * `coldFetch` (false by default) asks the server now with flagtime 0, for the whole set, and
50
+ * answers from that instead of what this page already has. It may come in place of the callback.
46
51
  */
47
- function fetchValue(name, callback) {
48
- return fetch.obtain().then(function () {
52
+ function fetchValue(name, callback, coldFetch) {
53
+ if (typeof callback === 'boolean') { // no i18n
54
+ // fetchValue(name, coldFetch)
55
+ coldFetch = callback;
56
+ callback = undefined;
57
+ }
58
+ var done = typeof callback === 'function' ? callback : undefined; // no i18n
59
+ return fetch.obtain(coldFetch === true).then(function () {
49
60
  var value = raw(name);
50
- return answer(value === undefined ? null : value, callback);
61
+ return answer(value === undefined ? null : value, done);
51
62
  });
52
63
  }
53
64
  /**
54
65
  * Several parameters at once: `names` lists the ones wanted, and the result has exactly those
55
66
  * names, null for one the console does not have. With no names (or an empty list), every
56
- * parameter the console has. A callback may come first when there are no names.
67
+ * parameter the console has. A callback may come first when there are no names, and `coldFetch`
68
+ * (false by default, as for fetchValue) last, in place of either.
57
69
  */
58
- function fetchValues(names, callback) {
59
- if (typeof names === 'function') { // no i18n
60
- // fetchValues(callback)
61
- callback = names;
62
- names = undefined;
63
- }
64
- var wanted = wantedNames(names);
65
- var done = typeof callback === 'function' ? callback : undefined; // no i18n
66
- return fetch.obtain().then(function () {
70
+ function fetchValues(names, callback, coldFetch) {
71
+ var args = [names, callback, coldFetch];
72
+ // the first boolean is coldFetch, wherever it comes: fetchValues(true), fetchValues(names, true),
73
+ // fetchValues(callback, true)
74
+ var flag = args.filter(function (one) { return typeof one === 'boolean'; })[0]; // no i18n
75
+ var fn = args.filter(function (one) { return typeof one === 'function'; })[0]; // no i18n
76
+ var wanted = wantedNames(typeof names === 'boolean' || typeof names === 'function' ? undefined : names); // no i18n
77
+ var done = fn;
78
+ return fetch.obtain(flag === true).then(function () {
67
79
  var values = store.all();
68
80
  var out = {};
69
81
  (wanted === null ? Object.keys(values) : wanted).forEach(function (name) {
@@ -96,11 +108,15 @@ const fetchValuesGuarded = guard.guard('AppticsRemoteConfig.fetchValues', fetchV
96
108
  const setCustomConditionValueGuarded = guard.guard('AppticsRemoteConfig.setCustomConditionValue', custom.setCustomConditionValue); // no i18n
97
109
  /** AppticsRemoteConfig: fetchValue, fetchValues and setCustomConditionValue, each with the parameter names the console shows. */
98
110
  const AppticsRemoteConfig = {
99
- fetchValue: function fetchValue(name, callback) {
111
+ /** One parameter's value, null when there is none. coldFetch (false by default) asks the server for the whole set now. */
112
+ fetchValue: function fetchValue(name, callback, coldFetch) {
100
113
  return guard.forward(fetchValueGuarded, arguments);
101
114
  },
102
- /** The parameters named, or every parameter when none are: name -> value, null when there is none. */
103
- fetchValues: function fetchValues(names, callback) {
115
+ /**
116
+ * The parameters named, or every parameter when none are: name -> value, null when there is
117
+ * none. coldFetch (false by default) asks the server for the whole set now.
118
+ */
119
+ fetchValues: function fetchValues(names, callback, coldFetch) {
104
120
  return guard.forward(fetchValuesGuarded, arguments);
105
121
  }, // no i18n
106
122
  /**
@@ -1,17 +1,25 @@
1
1
  import type { Dict } from '../types.js';
2
- /** The flagtime to send: 0 until we hold a rule set, which asks the server for everything. */
2
+ /** When the last answered getupdates went out; 0 when nothing is stored. */
3
3
  export declare function flagtime(): number;
4
4
  /** Re-resolves the stored rules against this device, without fetching. */
5
5
  export declare function reresolve(): void;
6
- /** Stores a rule set the server just sent, and resolves it. */
7
- export declare function store(rules: Dict, at: number): void;
8
6
  /**
9
- * Records a fetch that brought no rule set.
7
+ * Stores a whole rule set, the answer to flagtime 0, in place of what was stored, and resolves
8
+ * it. `askedAt` is when the request went out: the next one asks for what changed since then.
9
+ */
10
+ export declare function store(rules: Dict, askedAt: number): void;
11
+ /**
12
+ * Merges what a later getupdates sent, everything added or changed since its flagtime, into the
13
+ * stored rule set, and resolves the result. With nothing stored, it is the whole set.
14
+ */
15
+ export declare function merge(changes: Dict, askedAt: number): void;
16
+ /**
17
+ * Records a later fetch that brought no changes.
10
18
  *
11
- * That is the normal answer when nothing has changed, and it is not a failure: what is stored
12
- * stays, and only the fetch time moves.
19
+ * That is the normal answer, and it is not a failure: what is stored stays, and the next request
20
+ * asks for what changed since this one went out.
13
21
  */
14
- export declare function storeUnchanged(at: number): void;
22
+ export declare function storeUnchanged(askedAt: number): void;
15
23
  /** Forgets the rule set and its values, so the next fetch asks with flagtime 0. */
16
24
  export declare function clearRemoteConfig(): void;
17
25
  /** Every parameter resolved for this device. Loads from storage on the first read. */
@@ -1,21 +1,22 @@
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');
5
5
  var storage = require('../core/storage.js');
6
+ var utils = require('../core/utils.js');
6
7
  var evaluate = require('./evaluate.js');
7
8
 
8
9
  /**
9
10
  * What remote config keeps between visits.
10
11
  *
11
- * The rule set is stored, not just the resolved values, for two reasons. The server only resends
12
- * it when something changed — a fetch with a non-zero `flagtime` and no changes answers with no
13
- * `remoteconfig` block at all — so throwing it away would mean asking for it again with
14
- * flagtime 0 on every visit. And the resolved values depend on the device: an app version or a
15
- * customer group that changed since the last visit has to re-resolve against the same rules.
12
+ * The rule set is stored, not just the resolved values: the values depend on the device, so an
13
+ * app version or a customer group that changed has to re-resolve against the same rules, and a
14
+ * visit that cannot reach the server still has the last visit's values to read.
16
15
  *
17
- * `flagtime` is the whole protocol here: it is the moment this SDK last had a rule set, and
18
- * sending it back is how the server decides whether anything needs saying.
16
+ * `flagtime` is the whole protocol here. The first getupdates of a page load sends 0 and gets the
17
+ * whole rule set, which replaces what is stored. Each later one sends the time the last answered
18
+ * request went out, and gets only what was added or changed since: those parameters and
19
+ * conditions are merged into the stored set, and one that did not change is kept as it was.
19
20
  */
20
21
  const STORE_KEY = 'remoteconfig'; // no i18n
21
22
  var values = {};
@@ -24,55 +25,86 @@ function read() {
24
25
  var saved = storage.user_defaults.item(STORE_KEY);
25
26
  return saved && typeof saved === 'object' ? saved : null;
26
27
  }
27
- /** The flagtime to send: 0 until we hold a rule set, which asks the server for everything. */
28
+ /** When the last answered getupdates went out; 0 when nothing is stored. */
28
29
  function flagtime() {
29
30
  var saved = read();
30
31
  return saved && typeof saved.flagtime === 'number' ? saved.flagtime : 0;
31
32
  }
32
- /**
33
- * The newest `version[].date` in a rule set, which is what the next fetch sends as flagtime.
34
- *
35
- * The array is a publication history and is not promised to be in order, so it is the highest
36
- * date that matters rather than the first or last entry.
37
- */
38
- function publishedAt(rules) {
39
- var versions = Array.isArray(rules.version) ? rules.version : [];
40
- var newest = 0;
41
- versions.forEach(function (one) {
42
- var at = Number(one.date);
43
- if (isFinite(at) && at > newest) {
44
- newest = at;
45
- }
46
- });
47
- return newest;
48
- }
49
33
  /** Re-resolves the stored rules against this device, without fetching. */
50
34
  function reresolve() {
51
35
  var saved = read();
52
36
  values = saved && saved.rules ? evaluate.resolveAll(saved.rules) : {};
53
37
  loaded = true;
54
38
  }
55
- /** Stores a rule set the server just sent, and resolves it. */
56
- function store(rules, at) {
39
+ /**
40
+ * Stores a whole rule set, the answer to flagtime 0, in place of what was stored, and resolves
41
+ * it. `askedAt` is when the request went out: the next one asks for what changed since then.
42
+ */
43
+ function store(rules, askedAt) {
57
44
  evaluate.resetEvaluationWarnings();
58
- var record = { flagtime: publishedAt(rules) || at, rules: rules, fetchedat: at };
45
+ var record = { flagtime: askedAt, rules: rules, fetchedat: utils.nowMs() };
59
46
  storage.user_defaults.add(STORE_KEY, record);
60
47
  values = evaluate.resolveAll(rules);
61
48
  loaded = true;
62
- logger.Console.log('AppticsRemoteConfig: ' + Object.keys(values).length + ' parameters, published ' + record.flagtime); // no i18n
49
+ logger.Console.log('AppticsRemoteConfig: ' + Object.keys(values).length + ' parameters, as of ' + askedAt); // no i18n
50
+ }
51
+ /**
52
+ * `changed` laid over `stored`, matched by `key`: an entry the server resent replaces the one
53
+ * with the same key, in its place; a new one goes on the end; the rest stay as they were.
54
+ */
55
+ function mergeList(stored, changed, key) {
56
+ var out = Array.isArray(stored) ? stored.slice() : [];
57
+ if (!Array.isArray(changed)) {
58
+ return out;
59
+ }
60
+ changed.forEach(function (entry) {
61
+ if (!entry || typeof entry !== 'object') {
62
+ return;
63
+ }
64
+ var id = key(entry);
65
+ var at = id === '' ? -1 : out.findIndex(function (one) { return key(one) === id; });
66
+ if (at >= 0) {
67
+ out[at] = entry;
68
+ }
69
+ else {
70
+ out.push(entry);
71
+ }
72
+ });
73
+ return out;
74
+ }
75
+ /** An entry's id, or its name when it has none, as the key to match a resent entry by. */
76
+ function idOr(idKey, nameKey) {
77
+ return function (entry) {
78
+ var id = entry[idKey] !== undefined && entry[idKey] !== null ? entry[idKey] : entry[nameKey];
79
+ return id === undefined || id === null ? '' : String(id); // no i18n
80
+ };
81
+ }
82
+ /**
83
+ * Merges what a later getupdates sent, everything added or changed since its flagtime, into the
84
+ * stored rule set, and resolves the result. With nothing stored, it is the whole set.
85
+ */
86
+ function merge(changes, askedAt) {
87
+ var saved = read();
88
+ var base = saved && saved.rules && typeof saved.rules === 'object' ? saved.rules : {};
89
+ var rules = Object.assign({}, base, changes);
90
+ rules.all_parameter = mergeList(base.all_parameter, changes.all_parameter, idOr('paramid', 'paramname')); // no i18n
91
+ rules.all_condition = mergeList(base.all_condition, changes.all_condition, idOr('conditionid', 'conditionname')); // no i18n
92
+ rules.version = mergeList(base.version, changes.version, idOr('versionid', 'date')); // no i18n
93
+ store(rules, askedAt);
63
94
  }
64
95
  /**
65
- * Records a fetch that brought no rule set.
96
+ * Records a later fetch that brought no changes.
66
97
  *
67
- * That is the normal answer when nothing has changed, and it is not a failure: what is stored
68
- * stays, and only the fetch time moves.
98
+ * That is the normal answer, and it is not a failure: what is stored stays, and the next request
99
+ * asks for what changed since this one went out.
69
100
  */
70
- function storeUnchanged(at) {
101
+ function storeUnchanged(askedAt) {
71
102
  var saved = read();
72
103
  if (saved === null) {
73
104
  return;
74
105
  }
75
- saved.fetchedat = at;
106
+ saved.flagtime = askedAt;
107
+ saved.fetchedat = utils.nowMs();
76
108
  storage.user_defaults.add(STORE_KEY, saved);
77
109
  }
78
110
  /** Forgets the rule set and its values, so the next fetch asks with flagtime 0. */
@@ -93,6 +125,7 @@ function all() {
93
125
  exports.all = all;
94
126
  exports.clearRemoteConfig = clearRemoteConfig;
95
127
  exports.flagtime = flagtime;
128
+ exports.merge = merge;
96
129
  exports.reresolve = reresolve;
97
130
  exports.store = store;
98
131
  exports.storeUnchanged = storeUnchanged;
@@ -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,10 +1,11 @@
1
1
  /**
2
- * Where a log line was raised, for logSDK's `filename` and `linenumber`.
2
+ * Where in the app an AppticsLogger line was written, for logSDK's `filename`, `linenumber` and
3
+ * `function`: the Remote Logger page shows each line with them.
3
4
  *
4
5
  * There is no caller introspection in a browser, so the only way to answer "which line was this"
5
- * is to throw an Error nobody catches and read its stack. That is cheap enough for a channel that
6
- * only carries the SDK's own failures, but it is never on the app's path: everything here is
7
- * wrapped so a stack we cannot parse costs a line its call site and nothing more.
6
+ * is to make an Error and read its stack, skipping the SDK's own frames. That costs a little on
7
+ * every line the app logs, and nothing on any other call. Everything here is wrapped so a stack
8
+ * that cannot be parsed costs a line its call site and nothing more: the line is still logged.
8
9
  */
9
10
  export interface CallSite {
10
11
  /** The script's basename, not its url: logSDK caps filename at 100 characters. */
@@ -1,13 +1,14 @@
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
  /**
5
- * Where a log line was raised, for logSDK's `filename` and `linenumber`.
5
+ * Where in the app an AppticsLogger line was written, for logSDK's `filename`, `linenumber` and
6
+ * `function`: the Remote Logger page shows each line with them.
6
7
  *
7
8
  * There is no caller introspection in a browser, so the only way to answer "which line was this"
8
- * is to throw an Error nobody catches and read its stack. That is cheap enough for a channel that
9
- * only carries the SDK's own failures, but it is never on the app's path: everything here is
10
- * wrapped so a stack we cannot parse costs a line its call site and nothing more.
9
+ * is to make an Error and read its stack, skipping the SDK's own frames. That costs a little on
10
+ * every line the app logs, and nothing on any other call. Everything here is wrapped so a stack
11
+ * that cannot be parsed costs a line its call site and nothing more: the line is still logged.
11
12
  */
12
13
  const NO_CALL_SITE = { filename: '', linenumber: 0, fn: '' };
13
14
  /**
@@ -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,8 +1,8 @@
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');
5
- var callsite = require('../core/callsite.js');
5
+ var callsite = require('./callsite.js');
6
6
  var storage = require('../core/storage.js');
7
7
  var guard = require('../core/guard.js');
8
8
  var utils = require('../core/utils.js');