@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,211 +1,199 @@
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
+ import { warnAlways } from '../core/logger.js';
3
+
2
4
  /**
3
- * What API tracking records, and under what name — Android's AppticsApiTracker.configure, built
5
+ * Which API calls are recorded, and under what name: Android's AppticsApiTracker.configure, built
4
6
  * with ApiTrackingBuilder the way user properties are built with UserBuilder:
5
7
  *
6
8
  * configureApiTracking(new ApiTrackingBuilder()
7
- * .allowOnlyDomains('api.zylker.com', 'api.zylker.in')
8
- * .groupDomains('api.zylker.*')
9
- * .ignoreEndpoint('/health', '/internal/**')
10
- * .addPattern('/v2/catalog/{categoryId}/items/{itemId}')
9
+ * .allowRegex(/^https:\/\/api\.zylker\.(com|in)\//)
10
+ * .ignoreEquals('https://api.zylker.com/health')
11
+ * .ignoreRegex(/\/internal\//)
11
12
  * .build());
12
13
  *
13
- * Two jobs. Filtering decides whether a call is recorded at all: ignore rules first, and they
14
- * always win, then the allow lists. Normalization decides the path it is recorded under, so that
15
- * `/users/42/orders` and `/users/43/orders` are one endpoint on the console rather than two: the
16
- * query string goes, an explicit pattern is used when one matches, and otherwise numeric ids,
17
- * UUIDs and JWTs become `*`.
14
+ * Four rules, two kinds by two: an exact URL (equals) or a regular expression (regex), to allow or
15
+ * to ignore. Each is tried on the call's URL as `scheme://host[:port]/path`, with its query and
16
+ * fragment removed, so one rule can name a host, a path or a single endpoint. Ignore rules are
17
+ * tried first and always win; then, when there are allow rules, a call must match one of them.
18
+ *
19
+ * The name a call is recorded under is not configured: the query goes, and numeric ids, UUIDs and
20
+ * JWTs in its path become `*`, so `/users/42/orders` and `/users/43/orders` are one endpoint.
18
21
  *
19
22
  * Both apply to every call, the ones the fetch/XHR wrappers catch and the ones the app reports
20
- * with trackApi() or startTrackApi(), so one set of rules decides what the API page shows.
23
+ * with startTrackApi(), so one set of rules decides what the API page shows.
21
24
  */
22
25
  function emptyConfig() {
23
- return {
24
- ignoreDomains: [], allowOnlyDomains: [], allowOnlyTLDs: [], ignoreTLDs: [],
25
- ignoreDomainPrefixes: [], ignoreDomainSuffixes: [], groupDomains: [], ignoreEndpoints: [],
26
- patterns: [], preserveSegments: [], autoDetection: true,
27
- };
26
+ return { allowEquals: [], ignoreEquals: [], allowRegex: [], ignoreRegex: [] };
28
27
  }
29
- /** Lower-cased, trimmed, non-empty strings: a host list is compared case-insensitively. */
30
- function cleanList(values, lower) {
28
+ /**
29
+ * A URL as the rules compare it: `scheme://host[:port]/path`, the scheme and host lower-cased,
30
+ * the default port dropped, the query, fragment and a trailing slash removed. '' when it is not
31
+ * an absolute http(s) URL.
32
+ */
33
+ function ruleUrl(raw) {
34
+ try {
35
+ var url = new URL(String(raw).trim());
36
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') { // no i18n
37
+ return ''; // no i18n
38
+ }
39
+ var path = url.pathname.length > 1 ? url.pathname.replace(/\/+$/, '') : url.pathname; // no i18n
40
+ return url.protocol + '//' + url.host.toLowerCase() + path; // no i18n
41
+ }
42
+ catch (err) {
43
+ return ''; // no i18n
44
+ }
45
+ }
46
+ /** The exact URLs given, as rules compare them; anything that is not an absolute URL is refused, and said. */
47
+ function cleanUrls(method, values) {
31
48
  var out = [];
32
49
  values.forEach(function (value) {
33
- if (typeof value !== 'string') { // no i18n
50
+ var url = typeof value === 'string' ? ruleUrl(value) : ''; // no i18n
51
+ if (url === '') { // no i18n
52
+ warnAlways('ApiTrackingBuilder.' + method + ': ' + JSON.stringify(value) + ' is not a full http(s) URL, as https://api.example.com/health, and is left out'); // no i18n
34
53
  return;
35
54
  }
36
- var one = value.trim();
37
- if (one !== '') { // no i18n
38
- out.push(lower ? one.toLowerCase() : one);
55
+ if (out.indexOf(url) === -1) {
56
+ out.push(url);
39
57
  }
40
58
  });
41
59
  return out;
42
60
  }
43
- /** A TLD as `.com`, whether it was given as `.com` or `com`. */
44
- function asTld(value) {
45
- return value.charAt(0) === '.' ? value : '.' + value; // no i18n
61
+ /** The regular expressions given, a RegExp or its source as a string; an invalid one is refused, and said. */
62
+ function cleanRegexes(method, values) {
63
+ var out = [];
64
+ values.forEach(function (value) {
65
+ if (value instanceof RegExp) {
66
+ out.push(value);
67
+ return;
68
+ }
69
+ if (typeof value === 'string' && value !== '') { // no i18n
70
+ try {
71
+ out.push(new RegExp(value));
72
+ return;
73
+ }
74
+ catch (err) {
75
+ // falls through to the warning
76
+ }
77
+ }
78
+ warnAlways('ApiTrackingBuilder.' + method + ': ' + String(value) + ' is not a regular expression, and is left out'); // no i18n
79
+ });
80
+ return out;
46
81
  }
47
- /** Builds the rules for configureApiTracking, with AppticsApiTracker.configure's names. */
82
+ /**
83
+ * Builds the rules configure takes. Each method may be called more than once, and takes any number
84
+ * of values: `new ApiTrackingBuilder().allowEquals(a, b).ignoreRegex(/x/).build()`.
85
+ */
48
86
  class ApiTrackingBuilder {
49
87
  constructor() {
50
88
  this.rules = emptyConfig();
51
89
  }
52
- /** Never records these hosts. */
53
- ignoreDomains(...domains) { this.rules.ignoreDomains.push(...cleanList(domains, true)); return this; }
54
- /** Records only these hosts; every other is skipped. */
55
- allowOnlyDomains(...domains) { this.rules.allowOnlyDomains.push(...cleanList(domains, true)); return this; }
56
- /** Records only hosts ending in these TLDs, e.g. `.com`. */
57
- allowOnlyTLDs(...tlds) { this.rules.allowOnlyTLDs.push(...cleanList(tlds, true).map(asTld)); return this; }
58
- /** Never records hosts ending in these TLDs, e.g. `.local`. */
59
- ignoreTLDs(...tlds) { this.rules.ignoreTLDs.push(...cleanList(tlds, true).map(asTld)); return this; }
60
- /** Never records hosts starting with these, e.g. `dev.`. */
61
- ignoreDomainPrefixes(...prefixes) { this.rules.ignoreDomainPrefixes.push(...cleanList(prefixes, true)); return this; }
62
- /** Never records hosts ending with these, e.g. `.cdn.net`. */
63
- ignoreDomainSuffixes(...suffixes) { this.rules.ignoreDomainSuffixes.push(...cleanList(suffixes, true)); return this; }
64
- /** Records the hosts a pattern such as `api.myapp.*` matches under that one name. */
65
- groupDomains(...patterns) { this.rules.groupDomains.push(...cleanList(patterns, true)); return this; }
66
- /** Never records these paths: exact, `*` or `{name}` for one segment, `**` at the end for a prefix. */
67
- ignoreEndpoint(...paths) { this.rules.ignoreEndpoints.push(...cleanList(paths, false)); return this; }
68
- /** Records a path this pattern matches as the pattern, with each `{name}` as `*`. Tried before auto-detection. */
69
- addPattern(...patterns) { this.rules.patterns.push(...cleanList(patterns, false)); return this; }
70
- /** Segments never turned into `*`, e.g. `v1` or `404`. */
71
- preserveSegments(...segments) { this.rules.preserveSegments.push(...cleanList(segments, false)); return this; }
72
- /** `false`: only addPattern paths are normalized; every other path is recorded as it is. */
73
- autoDetection(enabled) { this.rules.autoDetection = enabled !== false; return this; }
90
+ /** Records only these URLs (and those any allowRegex matches): `https://api.example.com/orders`. */
91
+ allowEquals(...urls) { this.rules.allowEquals.push(...cleanUrls('allowEquals', urls)); return this; }
92
+ /** Never records these URLs: `https://api.example.com/health`. */
93
+ ignoreEquals(...urls) { this.rules.ignoreEquals.push(...cleanUrls('ignoreEquals', urls)); return this; }
94
+ /** Records only URLs these match (and those any allowEquals names): `/^https:\/\/api\.example\.com\//`. */
95
+ allowRegex(...patterns) { this.rules.allowRegex.push(...cleanRegexes('allowRegex', patterns)); return this; }
96
+ /** Never records URLs these match: `/\/internal\//`. */
97
+ ignoreRegex(...patterns) { this.rules.ignoreRegex.push(...cleanRegexes('ignoreRegex', patterns)); return this; }
74
98
  build() {
75
- var built = emptyConfig();
76
- Object.keys(built).forEach((key) => {
77
- var value = this.rules[key];
78
- built[key] = Array.isArray(value) ? value.slice() : value;
79
- });
80
- return built;
99
+ return {
100
+ allowEquals: this.rules.allowEquals.slice(),
101
+ ignoreEquals: this.rules.ignoreEquals.slice(),
102
+ allowRegex: this.rules.allowRegex.slice(),
103
+ ignoreRegex: this.rules.ignoreRegex.slice(),
104
+ };
81
105
  }
82
106
  }
107
+ /**
108
+ * 2.0.0-beta's builder methods, now gone. Called, each throws a TypeError that names what to use
109
+ * instead: a rule silently missing would record what the app meant to leave out.
110
+ */
111
+ const RETIRED_METHODS = Object.freeze({
112
+ ignoreDomains: 'ignoreRegex', // no i18n
113
+ allowOnlyDomains: 'allowRegex', // no i18n
114
+ allowOnlyTLDs: 'allowRegex', // no i18n
115
+ ignoreTLDs: 'ignoreRegex', // no i18n
116
+ ignoreDomainPrefixes: 'ignoreRegex', // no i18n
117
+ ignoreDomainSuffixes: 'ignoreRegex', // no i18n
118
+ groupDomains: '', // no i18n
119
+ ignoreEndpoint: 'ignoreEquals or ignoreRegex', // no i18n
120
+ addPattern: '', // no i18n
121
+ preserveSegments: '', // no i18n
122
+ autoDetection: '', // no i18n
123
+ });
124
+ Object.keys(RETIRED_METHODS).forEach(function (name) {
125
+ Object.defineProperty(ApiTrackingBuilder.prototype, name, {
126
+ configurable: true,
127
+ value: function () {
128
+ var instead = RETIRED_METHODS[name];
129
+ throw new TypeError('ApiTrackingBuilder.' + name + '() is no longer supported: ' // no i18n
130
+ + (instead ? 'use ' + instead + ', which match the URL without its query' : 'ids, UUIDs and tokens in a path become * on their own')); // no i18n
131
+ },
132
+ });
133
+ });
83
134
  var current = emptyConfig();
135
+ /** The values of `list` that are of the kind `keep` accepts. */
136
+ function listOf(value, keep) {
137
+ return Array.isArray(value) ? value.filter(keep) : [];
138
+ }
84
139
  /**
85
140
  * Applies the rules, replacing any given before. Takes what build() returned, or the builder
86
141
  * itself. Call it once, before the requests it should cover; it works before init().
87
142
  */
88
143
  function configureApiTracking(config) {
89
- var built = config instanceof ApiTrackingBuilder ? config.build() : config;
90
- var next = emptyConfig();
91
- if (built && typeof built === 'object') { // no i18n
92
- Object.keys(next).forEach(function (key) {
93
- var value = built[key];
94
- if (key === 'autoDetection') { // no i18n
95
- next.autoDetection = value !== false;
96
- }
97
- else if (Array.isArray(value)) {
98
- next[key] = value.slice();
99
- }
100
- });
144
+ var built = (config instanceof ApiTrackingBuilder ? config.build() : config);
145
+ if (!built || typeof built !== 'object') { // no i18n
146
+ current = emptyConfig();
147
+ return;
101
148
  }
102
- current = next;
149
+ var isString = function (one) { return typeof one === 'string'; }; // no i18n
150
+ var isRegExp = function (one) { return one instanceof RegExp; };
151
+ current = {
152
+ // a hand-written object gets the same clean-up the builder gives
153
+ allowEquals: cleanUrls('allowEquals', listOf(built.allowEquals, isString)), // no i18n
154
+ ignoreEquals: cleanUrls('ignoreEquals', listOf(built.ignoreEquals, isString)), // no i18n
155
+ allowRegex: listOf(built.allowRegex, isRegExp).concat(cleanRegexes('allowRegex', listOf(built.allowRegex, isString))), // no i18n
156
+ ignoreRegex: listOf(built.ignoreRegex, isRegExp).concat(cleanRegexes('ignoreRegex', listOf(built.ignoreRegex, isString))), // no i18n
157
+ };
103
158
  }
104
- /* ---------------- hosts ---------------- */
105
- function endsWithAny(host, list) {
106
- return list.some(function (one) { return host.length >= one.length && host.slice(host.length - one.length) === one; });
159
+ /** Whether a regular expression matches, whatever its flags: a /g one keeps a position between tests. */
160
+ function regexMatches(re, url) {
161
+ re.lastIndex = 0;
162
+ return re.test(url);
107
163
  }
108
- /** Whether calls to this host are recorded. Ignore rules are checked first and always win. */
109
- function hostAllowed(hostname) {
110
- var host = String(hostname || '').toLowerCase(); // no i18n
111
- if (current.ignoreDomains.indexOf(host) !== -1
112
- || endsWithAny(host, current.ignoreTLDs)
113
- || endsWithAny(host, current.ignoreDomainSuffixes)
114
- || current.ignoreDomainPrefixes.some(function (prefix) { return host.indexOf(prefix) === 0; })) {
115
- return false;
116
- }
117
- if (current.allowOnlyDomains.length > 0 && current.allowOnlyDomains.indexOf(host) === -1) {
164
+ /**
165
+ * Whether a call to `url` is recorded. Ignore rules are tried first and always win; then, when
166
+ * there are allow rules, the URL must match one. A URL that cannot be read is not recorded.
167
+ */
168
+ function urlTracked(url) {
169
+ var target = ruleUrl(url);
170
+ if (target === '') { // no i18n
118
171
  return false;
119
172
  }
120
- if (current.allowOnlyTLDs.length > 0 && !endsWithAny(host, current.allowOnlyTLDs)) {
173
+ if (current.ignoreEquals.indexOf(target) !== -1 || current.ignoreRegex.some(function (re) { return regexMatches(re, target); })) {
121
174
  return false;
122
175
  }
123
- return true;
124
- }
125
- function wildcardHost(pattern) {
126
- var source = pattern.split('*').map(function (part) {
127
- return part.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); // no i18n
128
- }).join('[a-z0-9.-]+'); // no i18n
129
- return new RegExp('^' + source + '$'); // no i18n
130
- }
131
- /** The name a host is recorded under: the first groupDomains pattern it matches, or itself. */
132
- function groupedHost(hostname) {
133
- var host = String(hostname || '').toLowerCase(); // no i18n
134
- for (var i = 0; i < current.groupDomains.length; i++) {
135
- var pattern = current.groupDomains[i];
136
- if (pattern.indexOf('*') !== -1 && wildcardHost(pattern).test(host)) { // no i18n
137
- return pattern;
138
- }
176
+ if (current.allowEquals.length === 0 && current.allowRegex.length === 0) {
177
+ return true;
139
178
  }
140
- return hostname;
179
+ return current.allowEquals.indexOf(target) !== -1 || current.allowRegex.some(function (re) { return regexMatches(re, target); });
141
180
  }
142
- /* ---------------- paths ---------------- */
181
+ /* ---------------- the name a call is recorded under ---------------- */
143
182
  /** The path without its query string or fragment, which is where ids and tokens travel. */
144
183
  function withoutQuery(path) {
145
184
  var cut = String(path || '').split(/[?#]/)[0]; // no i18n
146
185
  return cut === '' ? '/' : cut; // no i18n
147
186
  }
148
- function segmentsOf(path) {
149
- var trimmed = path.length > 1 ? path.replace(/\/+$/, '') : path; // no i18n
150
- return trimmed.split('/').slice(1); // no i18n
151
- }
152
- const ONE_SEGMENT = /^(\*|\{[^/{}]*\})$/;
153
- /** Whether a path matches a pattern: `*` and `{name}` are one segment, a final `**` is the rest. */
154
- function matches(pattern, path) {
155
- var want = segmentsOf(pattern);
156
- var have = segmentsOf(path);
157
- for (var i = 0; i < want.length; i++) {
158
- var part = want[i];
159
- if (part === '**' && i === want.length - 1) { // no i18n
160
- return true;
161
- }
162
- if (i >= have.length) {
163
- return false;
164
- }
165
- if (ONE_SEGMENT.test(part)) {
166
- if (have[i] === '') { // no i18n
167
- return false;
168
- }
169
- continue;
170
- }
171
- if (part !== have[i]) {
172
- return false;
173
- }
174
- }
175
- return want.length === have.length;
176
- }
177
- /** Whether ignoreEndpoint leaves this path out. Matched on the path as called, query removed. */
178
- function endpointIgnored(path) {
179
- var bare = withoutQuery(path);
180
- return current.ignoreEndpoints.some(function (pattern) { return matches(pattern, bare); });
181
- }
182
187
  const NUMERIC = /^\d+$/;
183
188
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
184
189
  // three base64url parts, the first starting eyJ — the encoding of '{"' every JWT header opens
185
190
  // with. Without that start, a file name such as jquery.min.js would pass for a token
186
191
  const JWT = /^eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
187
- /**
188
- * The path a call is recorded under. The query goes; then the first addPattern that matches is
189
- * used, with each `{name}` as `*` (the server's path rule has no room for braces); otherwise, with
190
- * autoDetection on, numeric ids, UUIDs and JWTs become `*`, except preserveSegments.
191
- */
192
+ /** The path a call is recorded under: the query goes, and numeric ids, UUIDs and JWTs become `*`. */
192
193
  function normalizeApiPath(path) {
193
- var bare = withoutQuery(path);
194
- for (var i = 0; i < current.patterns.length; i++) {
195
- var pattern = current.patterns[i];
196
- if (matches(pattern, bare)) {
197
- return pattern.replace(/\{[^/{}]*\}/g, '*'); // no i18n
198
- }
199
- }
200
- if (!current.autoDetection) {
201
- return bare;
202
- }
203
- return bare.split('/').map(function (segment) {
204
- if (segment === '' || current.preserveSegments.indexOf(segment) !== -1) { // no i18n
205
- return segment;
206
- }
207
- return NUMERIC.test(segment) || UUID.test(segment) || JWT.test(segment) ? '*' : segment; // no i18n
194
+ return withoutQuery(path).split('/').map(function (segment) {
195
+ return segment !== '' && (NUMERIC.test(segment) || UUID.test(segment) || JWT.test(segment)) ? '*' : segment; // no i18n
208
196
  }).join('/'); // no i18n
209
197
  }
210
198
 
211
- export { ApiTrackingBuilder, configureApiTracking, endpointIgnored, groupedHost, hostAllowed, normalizeApiPath, withoutQuery };
199
+ export { ApiTrackingBuilder, configureApiTracking, normalizeApiPath, ruleUrl, urlTracked, withoutQuery };
@@ -1,9 +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 { _ZAP, getConfig } from '../core/state.js';
3
3
  import { Console } from '../core/logger.js';
4
4
  import { nowMs, isEmpty } from '../core/utils.js';
5
5
  import { trackApi } from './track.js';
6
- import { hostAllowed, endpointIgnored } from './api-rules.js';
6
+ import { urlTracked } from './api-rules.js';
7
7
 
8
8
  /**
9
9
  * Automatic API tracking: wraps XMLHttpRequest and fetch so calls the app makes are recorded
@@ -64,7 +64,7 @@ function shouldTrack(rawUrl) {
64
64
  }
65
65
  // configureApiTracking's rules: checked here too so a call left out is not even timed. trackApi checks them again, for the
66
66
  // calls an app reports itself
67
- return hostAllowed(absolute.hostname) && !endpointIgnored(absolute.pathname);
67
+ return urlTracked(absolute.href);
68
68
  }
69
69
  /** One finished call. Never throws: a tracking failure must not surface in the app's call. */
70
70
  function record(method, url, starttime, responsecode, responsemessage) {
@@ -1,3 +1,5 @@
1
+ /** A path as screen_routes compares it: no query or fragment, no trailing slash but on the root. */
2
+ export declare function routePath(path: string): string;
1
3
  /**
2
4
  * Starts or stops automatic screen tracking. Starting records the screen the page is on, unless
3
5
  * the app has already called inScreen() for it. Safe to call repeatedly: wrapping history twice
@@ -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 { Console } from '../core/logger.js';
3
3
  import { hooks } from '../core/hooks.js';
4
4
  import { _ZAP } from '../core/state.js';
@@ -30,9 +30,14 @@ import { recordScreen } from './track.js';
30
30
  * On by default: most apps never call inScreen() themselves, and a screen-less session says
31
31
  * little. An app that wants only its own calls sets `track_screen: false`.
32
32
  */
33
+ /** A path as screen_routes compares it: no query or fragment, no trailing slash but on the root. */
34
+ function routePath(path) {
35
+ var bare = String(path || '').split(/[?#]/)[0] || '/'; // no i18n
36
+ return bare.length > 1 ? bare.replace(/\/+$/, '') : bare; // no i18n
37
+ }
33
38
  /** The most a screen name may be, and the characters it may hold (see validateScreenName). */
34
39
  const NAME_MAX = 99;
35
- const NAME_DISALLOWED = /[^\w\s.\-&/:]/g;
40
+ const NAME_DISALLOWED = /[^\w\s.\-&/:*]/g;
36
41
  /** How long an automatic view waits for the app's own inScreen() of the same navigation. */
37
42
  const SETTLE_MS = 300;
38
43
  var installed = false;
@@ -80,13 +85,61 @@ function cleanName(raw) {
80
85
  }
81
86
  return name.replace(NAME_DISALLOWED, '_').trim().slice(0, NAME_MAX); // no i18n
82
87
  }
88
+ /** Whether a pattern segment is a wildcard: `{id}`, `:id` or `*`. */
89
+ function isWildcard(segment) {
90
+ return segment === '*' || /^\{[^/]*\}$/.test(segment) || /^:[^/]+$/.test(segment); // no i18n
91
+ }
92
+ /** Whether a route pattern matches a path: every segment, `{id}`, `:id` and `*` matching any one. */
93
+ function patternMatches(pattern, path) {
94
+ var segments = routePath(pattern).split('/'); // no i18n
95
+ var actual = path.split('/'); // no i18n
96
+ if (segments.length !== actual.length) {
97
+ return false;
98
+ }
99
+ for (var j = 0; j < segments.length; j++) {
100
+ var seg = segments[j];
101
+ if (!isWildcard(seg) && seg !== actual[j]) {
102
+ return false;
103
+ }
104
+ }
105
+ return true;
106
+ }
107
+ /**
108
+ * The name screen_routes gives a path, or the path itself when no entry matches. Tried in order:
109
+ * a route pattern records the path with its variable segments as `*` (`/users/{id}/orders`
110
+ * claims `/users/1/orders`, not `/users/1/orders/2`); an exact path or a regular expression
111
+ * records the name the entry gives. Matched on the path without its query.
112
+ */
113
+ function applyRoutes(path) {
114
+ var routes = _ZAP._config.screen_routes;
115
+ if (!routes || routes.length === 0) {
116
+ return path;
117
+ }
118
+ var bare = routePath(path);
119
+ for (var i = 0; i < routes.length; i++) {
120
+ var route = routes[i];
121
+ if (route.kind === 'equals' && route.path === bare) { // no i18n
122
+ return route.name;
123
+ }
124
+ if (route.kind === 'regex') { // no i18n
125
+ route.regex.lastIndex = 0; // a /g regular expression keeps a position between tests
126
+ if (route.regex.test(bare)) {
127
+ return route.name;
128
+ }
129
+ }
130
+ if (route.kind === 'pattern' && patternMatches(route.pattern, bare)) { // no i18n
131
+ return routePath(route.pattern).split('/').map((s) => (isWildcard(s) ? '*' : s)).join('/'); // no i18n
132
+ }
133
+ }
134
+ return path;
135
+ }
83
136
  /**
84
137
  * The name for the page as it is now, from its url, or '' to record nothing. Never throws. An app
85
138
  * that wants its own names calls inScreen() as the route changes, which replaces this view.
86
139
  */
87
140
  function currentName() {
88
141
  try {
89
- return cleanName(nameFromUrl(new URL(window.location.href)));
142
+ return cleanName(applyRoutes(nameFromUrl(new URL(window.location.href))));
90
143
  }
91
144
  catch (err) {
92
145
  return ''; // no i18n
@@ -138,8 +191,18 @@ function cancelPending() {
138
191
  * Collects the history calls and events of one navigation into a single check. The first call of
139
192
  * a navigation starts it afresh: a inScreen() from before it belonged to the previous screen,
140
193
  * and a view still waiting is a page left too soon to count.
194
+ *
195
+ * A history call that leaves the screen where it is, is not a navigation. Next.js and other
196
+ * routers replaceState the same url right after a route change, to store their state or the
197
+ * scroll position. Treated as one, it cancelled the view still waiting to settle, and the check
198
+ * that followed found the url already seen and recorded nothing: the page went untracked and the
199
+ * view before it was never ended.
141
200
  */
142
201
  function navigated() {
202
+ var name = currentName();
203
+ if (name !== '' && (name === pendingName || (pendingName === '' && name === lastName))) { // no i18n
204
+ return;
205
+ }
143
206
  if (timer === null) {
144
207
  claimed = false;
145
208
  cancelPending();
@@ -235,4 +298,4 @@ function automaticScreenName() {
235
298
  }
236
299
  hooks.screenTrackedByApp = screenTrackedByApp;
237
300
 
238
- export { automaticScreenName, enableScreenTracking };
301
+ export { automaticScreenName, enableScreenTracking, routePath };
@@ -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
  /**
3
3
  * Apptics' defined events, as Android's DefinedEvents: ready-made names in reserved `AP_` groups,
4
4
  * logged with trackEvent like any other event and never tracked automatically.
@@ -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_LOG_MODULE, AP_CONSTANTS } from '../core/constants.js';
3
3
  import { _ZAP } from '../core/state.js';
4
4
  import { Console, logFailure, warnAlways } from '../core/logger.js';
@@ -56,6 +56,11 @@ function sendEngagementData(forget) {
56
56
  sent.push(itemWithId);
57
57
  delete item.id;
58
58
  delete item.type;
59
+ // which tabs share a session, which tab shows a screen and when it was last seen: the
60
+ // SDK's own bookkeeping, not part of the payload
61
+ delete item.tabs;
62
+ delete item.tab;
63
+ delete item.lastseen;
59
64
  });
60
65
  if (eventObj.length > 0)
61
66
  _payload.events = eventObj;
@@ -15,8 +15,16 @@ export declare function updateScreenWithEndtime(): Promise<void>;
15
15
  * @param sessionData A set of properties belonging to the session.
16
16
  */
17
17
  export declare function trackSession(sessionData: Dict): Promise<void>;
18
- /** This tab's session when it has one, otherwise the newest open session in storage. */
18
+ /**
19
+ * This tab's session while it is open, otherwise the newest open session in storage: the one the
20
+ * site's other tabs are on, which this tab joins rather than starting a second session beside it.
21
+ */
19
22
  export declare function currentSession(): Promise<QueueItem | undefined>;
23
+ export declare function tabId(): string;
24
+ /** Takes this tab's lock, held until the page is gone. Once per page. */
25
+ export declare function holdTabLock(): void;
26
+ /** Ends the open screen views of tabs that are gone. Resolves right away without Web Locks. */
27
+ export declare function closeScreensOfGoneTabs(): Promise<void>;
20
28
  /**
21
29
  * Writes the waiting session, if there is one. Called by the timer and by anything that tracks an
22
30
  * item, so an event in the first seconds takes its session with it.
@@ -28,8 +36,9 @@ export declare function dropPendingSession(): boolean;
28
36
  export declare function hasPendingSession(): boolean;
29
37
  export declare function startNewSession(time: number): Promise<void>;
30
38
  /**
31
- * Closes this tab's session, so it can be sent: the tab is closing or the page is being replaced.
32
- * Nothing happens when the session is already closed.
39
+ * This tab is closing, or the page is being replaced. The session ends here when this is the last
40
+ * tab using it, so it can be sent; while another tab of the site is still on it, this tab only
41
+ * leaves it, and the last tab to close ends it. Nothing happens when it is already closed.
33
42
  */
34
43
  export declare function endCurrentSession(): Promise<void>;
35
44
  /**