countly-sdk-web 22.6.1 → 22.6.3

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.
@@ -48,5 +48,5 @@
48
48
  "MD051": true,
49
49
  "MD052": true,
50
50
  "MD053": true
51
- }
51
+ }
52
52
 
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 22.06.3
2
+ - Fixed an issue that arose when sending crashes through a gateway. User agent information is now sent as part of the request.
3
+
4
+ ## 22.06.2
5
+ - ! Minor breaking change ! If no domain whitelist is provided for the heatmaps the SDK will fallback to your server url
6
+ - Fixed a bug where heatmap files were susceptible to DOM XSS
7
+ - Users can now input their domain whitelist for heatmaps feature during init
8
+ - Implementing new Remote Config/AB testing API:
9
+ - Added an init time flag to enable/disable new remote config API (default: disabled)
10
+ - Added a new call to opt in users to the A/B testing for the given keys
11
+ - Added an init time flag to enable/disable automatically opting in users for A/B testing while fetching remote config (with new RC API)(default: enabled)
12
+
1
13
  ## 22.06.1
2
14
  - Added SDK calls to report Feedback widgets manually
3
15
 
package/README.md CHANGED
@@ -1,76 +1,61 @@
1
+ [![Codacy Badge](https://app.codacy.com/project/badge/Grade/79582b7ee7ca4021a3950376402fac00)](https://www.codacy.com/gh/Countly/countly-sdk-web/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Countly/countly-sdk-web&utm_campaign=Badge_Grade)
2
+ [![npm version](https://badge.fury.io/js/countly-sdk-web.svg)](https://badge.fury.io/js/countly-sdk-web)
3
+ [![js delivr](https://data.jsdelivr.com/v1/package/npm/countly-sdk-web/badge)](https://www.jsdelivr.com/package/npm/countly-sdk-web)
4
+
1
5
  # Countly Web SDK
2
- [![Codacy Badge](https://app.codacy.com/project/badge/Grade/79582b7ee7ca4021a3950376402fac00)](https://www.codacy.com/gh/Countly/countly-sdk-web/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Countly/countly-sdk-web&utm_campaign=Badge_Grade) [![npm version](https://badge.fury.io/js/countly-sdk-web.svg)](https://badge.fury.io/js/countly-sdk-web) [![](https://data.jsdelivr.com/v1/package/npm/countly-sdk-web/badge)](https://www.jsdelivr.com/package/npm/countly-sdk-web)
6
+
7
+ This repository contains the Countly Web SDK, which can be integrated into websites and web applications. The Countly Web SDK is intended to be used with [Countly Community Edition](https://github.com/Countly/countly-server) or [Countly Enterprise Edition](https://count.ly/product).
3
8
 
4
9
  ## What is Countly?
5
- [Countly](http://count.ly) is a product analytics solution and innovation enabler that helps teams track product performance and customer journey and behavior across [mobile](https://count.ly/mobile-analytics), [web](http://count.ly/web-analytics), and [desktop](https://count.ly/desktop-analytics) applications. [Ensuring privacy by design](https://count.ly/your-data-your-rules), Countly allows you to innovate and enhance your products to provide personalized and customized customer experiences, and meet key business and revenue goals.
10
+ [Countly](https://count.ly) is a product analytics solution and innovation enabler that helps teams track product performance and customer journey and behavior across [mobile](https://count.ly/mobile-analytics), [web](http://count.ly/web-analytics),
11
+ and [desktop](https://count.ly/desktop-analytics) applications. [Ensuring privacy by design](https://count.ly/privacy-by-design), Countly allows you to innovate and enhance your products to provide personalized and customized customer experiences, and meet key business and revenue goals.
6
12
 
7
13
  Track, measure, and take action - all without leaving Countly.
8
14
 
9
- * **Slack user?** [Join our community](https://slack.count.ly) to ask questions and get answers!
10
- * **Questions?** [Ask in our Community forum](https://support.count.ly/hc/en-us/community/topics)
11
-
12
- ## Implementing Countly SDK in your web pages
13
- There are 3 ways to get Countly SDK.
14
-
15
- ### 1. Available with Countly server
16
- Since Countly server 16.02, Countly Web SDK is available in your Countly server installation in `countly/frontend/express/public/sdk/web/countly.min.js` which should be available through URL as `https://yourserver.com/sdk/web/countly.min.js`
17
-
18
- ### 2. Installation using package managers
19
- bower install countly-sdk-web
20
- or
21
-
22
- npm install countly-sdk-web
23
- or
24
-
25
- yarn add countly-sdk-web
15
+ * **Slack user?** [Join our Slack Community](https://slack.count.ly)
16
+ * **Questions or feature requests?** [Post in our Community Forum](https://support.count.ly/hc/en-us/community/topics)
17
+ * **Looking for the Countly Server?** [Countly Community Edition repository](https://github.com/Countly/countly-server)
18
+ * **Looking for other Countly SDKs?** [An overview of all Countly SDKs for mobile, web and desktop](https://support.count.ly/hc/en-us/articles/360037236571-Downloading-and-Installing-SDKs#officially-supported-sdks)
26
19
 
27
- ### 3. Use a CDN (content delivery network)
28
- Countly web SDK is available on CDNJS. Use either
20
+ ## Integrating Countly SDK in your projects
29
21
 
30
- [https://cdnjs.cloudflare.com/ajax/libs/countly-sdk-web/22.06.1/countly.min.js](https://cdnjs.cloudflare.com/ajax/libs/countly-sdk-web/22.06.1/countly.min.js)
22
+ For a detailed description on how to use this SDK [check out our documentation](https://support.count.ly/hc/en-us/articles/360037441932-Web-analytics-JavaScript-).
31
23
 
32
- or
24
+ For information about how to add the SDK to your project, please check [this section of the documentation](https://support.count.ly/hc/en-us/articles/360037441932-Web-analytics-JavaScript-#adding-the-sdk-to-the-project).
33
25
 
34
- [https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/lib/countly.min.js](https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/lib/countly.min.js)
26
+ You can reach minimal integration info for your website from [this section of the documentation](https://support.count.ly/hc/en-us/articles/360037441932-Web-analytics-JavaScript-#minimal-setup).
35
27
 
36
- ## How to use Countly Web SDK?
37
- Link to the script and call helper methods based on what you want to track: sessions, views, clicks, custom events, user data, etc. and for much more information check out our documentation at [https://support.count.ly/hc/en-us/articles/360037441932-Web-analytics-JavaScript-](https://support.count.ly/hc/en-us/articles/360037441932-Web-analytics-JavaScript-)
28
+ Countly Web SDK has JSDoc3 compatible comments, and you can generate documentation by running `npm run-script docs` or you can use [the online version](https://countly.github.io/countly-sdk-web/).
38
29
 
39
- You can reach minimal integration info for your website from here [https://support.count.ly/hc/en-us/articles/360037441932-Web-analytics-JavaScript-#minimal-setup](https://support.count.ly/hc/en-us/articles/360037441932-Web-analytics-JavaScript-#minimal-setup)
30
+ For an example integration of this SDK, you can have a look [here](https://github.com/Countly/countly-sdk-web/tree/master/examples).
40
31
 
41
- Countly Web SDK has JSDoc3 compatible comments and you can generate documentation by running `npm run-script docs` or access online version at [https://countly.github.io/countly-sdk-web/](https://countly.github.io/countly-sdk-web/)
32
+ This SDK supports the following features:
33
+ * [Analytics](https://support.count.ly/hc/en-us/articles/4431589003545-Analytics)
34
+ * [User Profiles](https://support.count.ly/hc/en-us/articles/4403281285913-User-Profiles)
35
+ * [Crash Reports](https://support.count.ly/hc/en-us/articles/4404213566105-Crashes-Errors)
36
+ * [A/B Testing](https://support.count.ly/hc/en-us/articles/4416496362393-A-B-Testing-)
37
+ * [Performance Monitoring](https://support.count.ly/hc/en-us/articles/4734457847705-Performance)
38
+ * [Feedback Widgets](https://support.count.ly/hc/en-us/articles/4652903481753-Feedback-Surveys-NPS-and-Ratings-)
42
39
 
43
40
  ## Security
44
41
  Security is very important to us. If you discover any issue regarding security, please disclose the information responsibly by sending an email to security@count.ly and **not by creating a GitHub issue**.
45
42
 
46
- ## Other Github resources
47
- Check Countly Community Edition source code here:
48
-
49
- * [Countly Server](https://github.com/Countly/countly-server)
50
-
51
- There are also other Countly SDK repositories below:
52
-
53
- * [Countly iOS SDK](https://github.com/Countly/countly-sdk-ios)
54
- * [Countly Android SDK](https://github.com/Countly/countly-sdk-android)
55
- * [Countly Windows Phone SDK](https://github.com/Countly/countly-sdk-windows-phone)
56
- * [Countly Appcelerator Titanium SDK](https://github.com/euforic/Titanium-Count.ly) (Community supported)
57
- * [Countly Unity3D SDK](https://github.com/Countly/countly-sdk-unity) (Community supported)
58
-
59
- ## How can I help you with your efforts?
60
- Glad you asked. We need ideas, feedbacks and constructive comments. All your suggestions will be taken care with upmost importance. We are on [Twitter](http://twitter.com/gocountly) and [Facebook](https://www.facebook.com/Countly) if you would like to keep up with our fast progress!
61
-
62
43
  ## Badges
63
- If you like Countly, [why not use one of our badges](https://count.ly/brand-assets) and give a link back to us, so others know about this wonderful platform?
44
+ If you like Countly, [why not use one of our badges](https://count.ly/brand-assets) and give a link back to us so others know about this wonderful platform?
64
45
 
65
46
  <a href="https://count.ly/f/badge" rel="nofollow"><img style="width:145px;height:60px" src="https://count.ly/badges/dark.svg?v2" alt="Countly - Product Analytics" /></a>
66
47
 
67
- <a href="https://count.ly/f/badge" rel="nofollow"><img style="width:145px;height:60px" src="https://count.ly/badges/dark.svg" alt="Countly - Product Analytics" /></a>
48
+ ```JS
49
+ <a href="https://count.ly/f/badge" rel="nofollow"><img style="width:145px;height:60px" src="https://count.ly/badges/dark.svg" alt="Countly - Product Analytics" /></a>
50
+ ```
68
51
 
69
52
  <a href="https://count.ly/f/badge" rel="nofollow"><img style="width:145px;height:60px" src="https://count.ly/badges/light.svg?v2" alt="Countly - Product Analytics" /></a>
70
53
 
71
- <a href="https://count.ly/f/badge" rel="nofollow"><img style="width:145px;height:60px" src="https://count.ly/badges/light.svg" alt="Countly - Product Analytics" /></a>
54
+ ```JS
55
+ <a href="https://count.ly/f/badge" rel="nofollow"><img style="width:145px;height:60px" src="https://count.ly/badges/light.svg" alt="Countly - Product Analytics" /></a>
56
+ ```
72
57
 
73
- ## Support
74
- For a public community support page, visit [https://support.count.ly/hc/en-us/community/topics](https://support.count.ly/hc/en-us/community/topics "Countly Community Forum").
58
+ ## How can I help you with your efforts?
59
+ Glad you asked! We need ideas, feedback and constructive comments. All your suggestions will be taken care of with utmost importance. For feature requests and engaging with the community, join [our Slack Community](https://slack.count.ly) or [Community Forum](https://support.count.ly/hc/en-us/community/topics).
75
60
 
76
- [![NPM](https://nodei.co/npm/countly-sdk-web.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/countly-sdk-web/)
61
+ We are on [Twitter](http://twitter.com/gocountly), [Facebook](https://www.facebook.com/Countly) and [LinkedIn](https://www.linkedin.com/company/countly) if you would like to keep up with Countly related updates.
Binary file
@@ -0,0 +1,36 @@
1
+ /* eslint-disable require-jsdoc */
2
+ var Countly = require("../../lib/countly");
3
+ var hp = require("../support/helper");
4
+
5
+ function initMain() {
6
+ Countly.init({
7
+ app_key: "YOUR_APP_KEY",
8
+ url: "https://try.count.ly",
9
+ test_mode: true
10
+ });
11
+ }
12
+
13
+ function cause_error() {
14
+ // eslint-disable-next-line no-undef
15
+ undefined_function();
16
+ }
17
+
18
+ describe("Crashes tests ", () => {
19
+ it("Checks if a caught crash is reported correctly", () => {
20
+ hp.haltAndClearStorage(() => {
21
+ initMain();
22
+ Countly.track_errors();
23
+ try {
24
+ cause_error();
25
+ }
26
+ catch (err) {
27
+ Countly.log_error(err);
28
+ }
29
+ cy.wait(3000).then(() => {
30
+ cy.fetch_local_request_queue().then((rq) => {
31
+ cy.check_crash(rq[0], hp.appKey);
32
+ });
33
+ });
34
+ });
35
+ });
36
+ });
@@ -33,6 +33,32 @@ Cypress.Commands.add("check_request_commons", (testObject, appKey) => {
33
33
  expect(testObject.sdk_version).to.be.ok;
34
34
  });
35
35
 
36
+ /**
37
+ * Checks a crash request for valid/correct formation
38
+ * @param {Object} testObject - crash object to be checked
39
+ */
40
+ Cypress.Commands.add("check_crash", (testObject, appKey) => {
41
+ appKey = appKey || hp.appKey;
42
+ const metrics = JSON.parse(testObject.metrics);
43
+ const crash = JSON.parse(testObject.crash);
44
+ const metricKeys = Object.keys(metrics);
45
+ cy.check_request_commons(testObject, appKey);
46
+ cy.check_commons(testObject);
47
+ expect(metrics._ua).to.be.exist;
48
+ expect(metricKeys.length).to.equal(1);
49
+ expect(crash._app_version).to.be.exist;
50
+ expect(crash._background).to.be.exist;
51
+ expect(crash._error).to.be.exist;
52
+ expect(crash._javascript).to.be.exist;
53
+ expect(crash._nonfatal).to.be.exist;
54
+ expect(crash._not_os_specific).to.be.exist;
55
+ expect(crash._online).to.be.exist;
56
+ expect(crash._opengl).to.be.exist;
57
+ expect(crash._resolution).to.be.exist;
58
+ expect(crash._run).to.be.exist;
59
+ expect(crash._view).to.be.exist;
60
+ });
61
+
36
62
  /**
37
63
  * Checks a queue object for valid/correct begin session, end session and session extension values
38
64
  * @param {Object} queue - queue object to check
@@ -13,6 +13,8 @@
13
13
  app_key: "YOUR_APP_KEY",
14
14
  url: "https://try.count.ly", //your server goes here
15
15
  debug: true,
16
+ rc_automatic_optin_for_ab: true, // set it to false for not opting in users for AB testing while fetching the remote config (only with latest API)
17
+ use_explicit_rc_api: false, // set it to true to use the latest API
16
18
  remote_config: function (err, configs) {
17
19
  //handle initial remote configs here
18
20
  console.log(err, configs);
@@ -31,19 +33,25 @@
31
33
  <center>
32
34
  <img src="./images/team_countly.jpg" id="wallpaper" />
33
35
  <br />
34
- <input type="button" onclick="reloadConfig()" value="Reload Config">
36
+ <input type="button" onclick="fetchConfig()" value="Fetch Config">
35
37
  <input type="button" onclick="getConfig()" value="Get Config">
36
38
  <input type="button" onclick="getConfig('test')" value="Get config for key Test">
39
+ <input type="button" onclick="ab(['key1','key2'])" value="Enroll user to AB test">
37
40
  <p><a href='http://count.ly/'>Count.ly</a></p>
38
41
  </center>
39
42
  <script type='text/javascript'>
40
- //reload configs
41
- function reloadConfig(ob) {
43
+ // fetches all keys
44
+ function fetchConfig() {
42
45
  Countly.fetch_remote_config(function (err, config) {
43
46
  alert(JSON.stringify(config));
44
47
  });
45
48
  }
46
49
 
50
+ // enroll user
51
+ function ab(keyArray) {
52
+ Countly.enrollUserToAb(keyArray);
53
+ }
54
+
47
55
  //get config
48
56
  function getConfig(key) {
49
57
  alert(JSON.stringify(Countly.get_remote_config(key)));
@@ -6,7 +6,7 @@
6
6
  "@testing-library/jest-dom": "^5.16.4",
7
7
  "@testing-library/react": "^13.3.0",
8
8
  "@testing-library/user-event": "^14.2.1",
9
- "countly-sdk-web": "^22.02.4",
9
+ "countly-sdk-web": "^22.06.1",
10
10
  "react": "^18.2.0",
11
11
  "react-dom": "^18.2.0",
12
12
  "react-router-dom": "^5.3.3",
@@ -10,8 +10,8 @@
10
10
  "build": "webpack"
11
11
  },
12
12
  "devDependencies": {
13
- "@babel/core": "^7.18.9",
14
- "@babel/preset-env": "^7.18.9",
13
+ "@babel/core": "^7.19.1",
14
+ "@babel/preset-env": "^7.19.1",
15
15
  "@webpack-cli/init": "^1.1.3",
16
16
  "babel-loader": "^8.2.5",
17
17
  "babel-plugin-syntax-dynamic-import": "^6.18.0",
package/lib/countly.js CHANGED
@@ -175,7 +175,7 @@
175
175
  */
176
176
  Countly.onload = Countly.onload || [];
177
177
 
178
- var SDK_VERSION = "22.06.1";
178
+ var SDK_VERSION = "22.06.3";
179
179
  var SDK_NAME = "javascript_native_web";
180
180
 
181
181
  var urlParseRE = /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/;
@@ -255,6 +255,8 @@
255
255
  this.onload = getConfig("onload", ob, []);
256
256
  this.utm = getConfig("utm", ob, { source: true, medium: true, campaign: true, term: true, content: true });
257
257
  this.ignore_prefetch = getConfig("ignore_prefetch", ob, true);
258
+ this.rcAutoOptinAb = getConfig("rc_automatic_optin_for_ab", ob, true);
259
+ this.useExplicitRcApi = getConfig("use_explicit_rc_api", ob, false);
258
260
  this.debug = getConfig("debug", ob, false);
259
261
  this.test_mode = getConfig("test_mode", ob, false);
260
262
  this.metrics = getConfig("metrics", ob, {});
@@ -278,6 +280,7 @@
278
280
  this.maxBreadcrumbCount = getConfig("max_breadcrumb_count", ob, null);
279
281
  this.maxStackTraceLinesPerThread = getConfig("max_stack_trace_lines_per_thread", ob, configurationDefaultValues.MAX_STACKTRACE_LINES_PER_THREAD);
280
282
  this.maxStackTraceLineLength = getConfig("max_stack_trace_line_length", ob, configurationDefaultValues.MAX_STACKTRACE_LINE_LENGTH);
283
+ this.heatmapWhitelist = getConfig("heatmap_whitelist", ob, []);
281
284
 
282
285
  if (maxCrashLogs && !this.maxBreadcrumbCount) {
283
286
  this.maxBreadcrumbCount = maxCrashLogs;
@@ -291,6 +294,11 @@
291
294
  lsSupport = false;
292
295
  }
293
296
 
297
+ if (!this.rcAutoOptinAb && !this.useExplicitRcApi) {
298
+ log(logLevelEnums.WARNING, "initialize, Auto opting is disabled, switching to explicit RC API");
299
+ this.useExplicitRcApi = true;
300
+ }
301
+
294
302
  if (!Array.isArray(ignoreReferrers)) {
295
303
  ignoreReferrers = [];
296
304
  }
@@ -341,11 +349,25 @@
341
349
  setToken(this.passed_data.token);
342
350
  setValueInStorage("cly_old_token", this.passed_data.token);
343
351
  }
344
- this.passed_data.url = this.passed_data.url || this.url;
345
- if (this.passed_data.purpose === "heatmap") {
346
- this.ignore_visitor = true;
347
- showLoader();
348
- loadJS(this.passed_data.url + "/views/heatmap.js", hideLoader);
352
+ var strippedList = [];
353
+ // if whitelist is provided is an array
354
+ if (Array.isArray(this.heatmapWhitelist)) {
355
+ this.heatmapWhitelist.push(this.url);
356
+ strippedList = this.heatmapWhitelist.map(function(e) {
357
+ // remove trailing slashes from the entries
358
+ return stripTrailingSlash(e);
359
+ });
360
+ }
361
+ else {
362
+ strippedList = [this.url];
363
+ }
364
+ // if the passed url is in the whitelist proceed
365
+ if (strippedList.includes(this.passed_data.url)) {
366
+ if (this.passed_data.purpose === "heatmap") {
367
+ this.ignore_visitor = true;
368
+ showLoader();
369
+ loadJS(this.passed_data.url + "/views/heatmap.js", hideLoader);
370
+ }
349
371
  }
350
372
  }
351
373
  }
@@ -394,6 +416,10 @@
394
416
  if (this.test_mode) {
395
417
  log(logLevelEnums.WARNING, "initialize, test_mode:[" + this.test_mode + "], queues won't be processed");
396
418
  }
419
+ // if test mode is enabled warn the user
420
+ if (this.heatmapWhitelist) {
421
+ log(logLevelEnums.DEBUG, "initialize, heatmap whitelist:[" + JSON.stringify(this.heatmapWhitelist) + "], these domains will be whitelisted");
422
+ }
397
423
  // if storage is se to something other than local storage
398
424
  if (this.storage !== "default") {
399
425
  log(logLevelEnums.DEBUG, "initialize, storage is set to:[" + this.storage + "]");
@@ -407,6 +433,12 @@
407
433
  if (this.remote_config) {
408
434
  log(logLevelEnums.DEBUG, "initialize, remote_config callback provided:[" + !!this.remote_config + "]");
409
435
  }
436
+ if (typeof this.rcAutoOptinAb === "boolean") {
437
+ log(logLevelEnums.DEBUG, "initialize, automatic RC optin is enabled:[" + this.rcAutoOptinAb + "]");
438
+ }
439
+ if (!this.useExplicitRcApi) {
440
+ log(logLevelEnums.WARNING, "initialize, will use legacy RC API. Consider enabling new API during init with use_explicit_rc_api flag");
441
+ }
410
442
  if (this.track_domains) {
411
443
  log(logLevelEnums.DEBUG, "initialize, tracking domain info:[" + this.track_domains + "]");
412
444
  }
@@ -419,6 +451,9 @@
419
451
  if (offlineMode) {
420
452
  log(logLevelEnums.DEBUG, "initialize, offline_mode:[" + offlineMode + "], user info won't be send to the servers");
421
453
  }
454
+ if (offlineMode) {
455
+ log(logLevelEnums.DEBUG, "initialize, stored remote configs:[" + JSON.stringify(remoteConfigs) + "]");
456
+ }
422
457
  // functions, if provided, would be printed as true without revealing their content
423
458
  log(logLevelEnums.DEBUG, "initialize, 'getViewName' callback override provided:[" + !!this.getViewName + "]");
424
459
  log(logLevelEnums.DEBUG, "initialize, 'getSearchQuery' callback override provided:[" + !!this.getSearchQuery + "]");
@@ -671,6 +706,8 @@
671
706
  self.ip_address = undefined;
672
707
  self.ignore_bots = undefined;
673
708
  self.force_post = undefined;
709
+ self.rcAutoOptinAb = undefined;
710
+ self.useExplicitRcApi = undefined;
674
711
  self.remote_config = undefined;
675
712
  self.ignore_visitor = undefined;
676
713
  self.require_consent = undefined;
@@ -1063,6 +1100,7 @@
1063
1100
  // start new session for new ID
1064
1101
  this.begin_session(!autoExtend, true);
1065
1102
  }
1103
+ // if init time remote config was enabled with a callback function, remove currently stored remote configs and fetch remote config again
1066
1104
  if (this.remote_config) {
1067
1105
  remoteConfigs = {};
1068
1106
  setValueInStorage("cly_remote_configs", remoteConfigs);
@@ -1579,46 +1617,102 @@
1579
1617
  crashLogs.push(record);
1580
1618
  }
1581
1619
  };
1582
-
1583
1620
  /**
1584
- * Fetch remote config
1621
+ * Fetch remote config from the server (old one for method=fetch_remote_config API)
1585
1622
  * @param {array=} keys - Array of keys to fetch, if not provided will fetch all keys
1586
1623
  * @param {array=} omit_keys - Array of keys to omit, if provided will fetch all keys except provided ones
1587
1624
  * @param {function=} callback - Callback to notify with first param error and second param remote config object
1588
1625
  * */
1589
1626
  this.fetch_remote_config = function(keys, omit_keys, callback) {
1590
- log(logLevelEnums.INFO, "fetch_remote_config, Fetching remote config");
1591
- if (this.check_consent(featureEnums.REMOTE_CONFIG)) {
1592
- var request = {
1593
- method: "fetch_remote_config"
1594
- };
1595
- if (this.check_consent(featureEnums.SESSIONS)) {
1596
- request.metrics = JSON.stringify(getMetrics());
1627
+ var keysFiltered = null;
1628
+ var omitKeysFiltered = null;
1629
+ var callbackFiltered = null;
1630
+
1631
+ // check first param is truthy
1632
+ if (keys) {
1633
+ // if third parameter is falsy and first param is a function assign it as the callback function
1634
+ if (!callback && typeof keys === "function") {
1635
+ callbackFiltered = keys;
1597
1636
  }
1598
- if (keys) {
1599
- if (!callback && typeof keys === "function") {
1600
- callback = keys;
1601
- keys = null;
1602
- }
1603
- else if (Array.isArray(keys) && keys.length) {
1604
- log(logLevelEnums.INFO, "fetch_remote_config, Keys to fetch: [ " + keys + " ]");
1605
- request.keys = JSON.stringify(keys);
1606
- }
1637
+ // else if first param is an array assign it as 'keys'
1638
+ else if (Array.isArray(keys)) {
1639
+ keysFiltered = keys;
1607
1640
  }
1608
- if (omit_keys) {
1609
- log(logLevelEnums.INFO, "fetch_remote_config, Keys to omit: [ " + omit_keys + " ]");
1610
- if (!callback && typeof omit_keys === "function") {
1611
- callback = omit_keys;
1612
- omit_keys = null;
1613
- }
1614
- else if (Array.isArray(omit_keys) && omit_keys.length) {
1615
- request.omit_keys = JSON.stringify(omit_keys);
1616
- }
1641
+ }
1642
+ // check second param is truthy
1643
+ if (omit_keys) {
1644
+ // if third parameter is falsy and second param is a function assign it as the callback function
1645
+ if (!callback && typeof omit_keys === "function") {
1646
+ callbackFiltered = omit_keys;
1647
+ }
1648
+ // else if second param is an array assign it as 'omit_keys'
1649
+ else if (Array.isArray(omit_keys)) {
1650
+ omitKeysFiltered = omit_keys;
1617
1651
  }
1652
+ }
1653
+ // assign third param as a callback function if it was not assigned yet in first two params
1654
+ if (!callbackFiltered && typeof callback === "function") {
1655
+ callbackFiltered = callback;
1656
+ }
1657
+
1658
+ // use new RC API
1659
+ if (this.useExplicitRcApi) {
1660
+ log(logLevelEnums.INFO, "fetch_remote_config, Fetching remote config");
1661
+ // opt in is true(1) or false(0)
1662
+ var opt = this.rcAutoOptinAb ? 1 : 0;
1663
+ fetch_remote_config_explicit(keysFiltered, omitKeysFiltered, opt, null, callbackFiltered);
1664
+ return;
1665
+ }
1666
+
1667
+ log(logLevelEnums.WARNING, "fetch_remote_config, Fetching remote config, with legacy API");
1668
+ fetch_remote_config_explicit(keysFiltered, omitKeysFiltered, null, "legacy", callbackFiltered);
1669
+ };
1670
+
1671
+ /**
1672
+ * Fetch remote config from the server (new one with method=rc API)
1673
+ * @param {array=} keys - Array of keys to fetch, if not provided will fetch all keys
1674
+ * @param {array=} omit_keys - Array of keys to omit, if provided will fetch all keys except provided ones
1675
+ * @param {number=} optIn - an inter to indicate if the user is opted in for the AB testing or not (1 is opted in, 0 is opted out)
1676
+ * @param {string=} api - which API to use, if not provided would use default ("legacy" is for method="fetch_remote_config", default is method="rc")
1677
+ * @param {function=} callback - Callback to notify with first param error and second param remote config object
1678
+ * */
1679
+ function fetch_remote_config_explicit(keys, omit_keys, optIn, api, callback) {
1680
+ log(logLevelEnums.INFO, "fetch_remote_config_explicit, Fetching sequence initiated");
1681
+ var request = {
1682
+ method: "rc"
1683
+ };
1684
+ // check if keys were provided
1685
+ if (keys.length > 0) {
1686
+ request.keys = JSON.stringify(keys);
1687
+ }
1688
+ // check if omit_keys were provided
1689
+ if (omit_keys.length > 0) {
1690
+ request.omit_keys = JSON.stringify(omit_keys);
1691
+ }
1692
+ var providedCall;
1693
+ // legacy api prompt check
1694
+ if (api === "legacy") {
1695
+ request.method = "fetch_remote_config";
1696
+ }
1697
+ // opted out/in check
1698
+ if (optIn === 0) {
1699
+ request.oi = 0;
1700
+ }
1701
+ if (optIn === 1) {
1702
+ request.oi = 1;
1703
+ }
1704
+ // callback check
1705
+ if (typeof callback === "function") {
1706
+ providedCall = callback;
1707
+ }
1708
+ if (self.check_consent(featureEnums.SESSIONS)) {
1709
+ request.metrics = JSON.stringify(getMetrics());
1710
+ }
1711
+ if (self.check_consent(featureEnums.REMOTE_CONFIG)) {
1618
1712
  prepareRequest(request);
1619
- sendXmlHttpRequest("fetch_remote_config", this.url + readPath, request, function(err, params, responseText) {
1713
+ sendXmlHttpRequest("fetch_remote_config_explicit", self.url + readPath, request, function(err, params, responseText) {
1620
1714
  if (err) {
1621
- log(logLevelEnums.ERROR, "fetch_remote_config, An error occurred: " + err);
1715
+ log(logLevelEnums.ERROR, "fetch_remote_config_explicit, An error occurred: " + err);
1622
1716
  return;
1623
1717
  }
1624
1718
  try {
@@ -1636,30 +1730,61 @@
1636
1730
  setValueInStorage("cly_remote_configs", remoteConfigs);
1637
1731
  }
1638
1732
  catch (ex) {
1639
- log(logLevelEnums.ERROR, "fetch_remote_config, Had an issue while parsing the response: " + ex);
1733
+ log(logLevelEnums.ERROR, "fetch_remote_config_explicit, Had an issue while parsing the response: " + ex);
1640
1734
  }
1641
- if (typeof callback === "function") {
1642
- log(logLevelEnums.INFO, "fetch_remote_config, Callback function is provided");
1643
- callback(err, remoteConfigs);
1735
+ if (providedCall) {
1736
+ log(logLevelEnums.INFO, "fetch_remote_config_explicit, Callback function is provided");
1737
+ providedCall(err, remoteConfigs);
1644
1738
  }
1645
1739
  // JSON array can pass
1646
1740
  }, true);
1647
1741
  }
1648
1742
  else {
1649
- log(logLevelEnums.ERROR, "fetch_remote_config, Remote config requires explicit consent");
1650
- if (typeof callback === "function") {
1651
- callback(new Error("Remote config requires explicit consent"), remoteConfigs);
1743
+ log(logLevelEnums.ERROR, "fetch_remote_config_explicit, Remote config requires explicit consent");
1744
+ if (providedCall) {
1745
+ providedCall(new Error("Remote config requires explicit consent"), remoteConfigs);
1652
1746
  }
1653
1747
  }
1748
+ }
1749
+
1750
+ /**
1751
+ * AB testing key provider, opts the user in for the selected keys
1752
+ * @param {array=} keys - Array of keys opt in FOR
1753
+ * */
1754
+ this.enrollUserToAb = function(keys) {
1755
+ log(logLevelEnums.INFO, "enrollUserToAb, Providing AB test keys to opt in for");
1756
+ if (!keys || !Array.isArray(keys) || keys.length === 0) {
1757
+ log(logLevelEnums.ERROR, "enrollUserToAb, No keys provided");
1758
+ return;
1759
+ }
1760
+ var request = {
1761
+ method: "ab",
1762
+ keys: JSON.stringify(keys)
1763
+ };
1764
+ prepareRequest(request);
1765
+ sendXmlHttpRequest("enrollUserToAb", this.url + readPath, request, function(err, params, responseText) {
1766
+ if (err) {
1767
+ log(logLevelEnums.ERROR, "enrollUserToAb, An error occurred: " + err);
1768
+ return;
1769
+ }
1770
+ try {
1771
+ var resp = JSON.parse(responseText);
1772
+ log(logLevelEnums.DEBUG, "enrollUserToAb, Parsed the response's result: [" + resp.result + "]");
1773
+ }
1774
+ catch (ex) {
1775
+ log(logLevelEnums.ERROR, "enrollUserToAb, Had an issue while parsing the response: " + ex);
1776
+ }
1777
+ // JSON array can pass
1778
+ }, true);
1654
1779
  };
1655
1780
 
1656
1781
  /**
1657
- * Get Remote config object or specific value for provided key
1782
+ * Gets remote config object (all key/value pairs) or specific value for provided key from the storage
1658
1783
  * @param {string=} key - if provided, will return value for key, or return whole object
1659
1784
  * @returns {object} remote configs
1660
1785
  * */
1661
1786
  this.get_remote_config = function(key) {
1662
- log(logLevelEnums.INFO, "get_remote_config, Getting remote config");
1787
+ log(logLevelEnums.INFO, "get_remote_config, Getting remote config from storage");
1663
1788
  if (typeof key !== "undefined") {
1664
1789
  return remoteConfigs[key];
1665
1790
  }
@@ -3227,7 +3352,12 @@
3227
3352
  log(logLevelEnums.ERROR, "Could not get the experimental-webgl context: " + ex);
3228
3353
  }
3229
3354
 
3230
- toRequestQueue({ crash: JSON.stringify(obj) });
3355
+ // send userAgent string with the crash object incase it gets removed by a gateway
3356
+ var req = {};
3357
+ req.crash = JSON.stringify(obj);
3358
+ req.metrics = JSON.stringify({ _ua: metrics._ua });
3359
+
3360
+ toRequestQueue(req);
3231
3361
  }
3232
3362
  };
3233
3363
 
@@ -4279,12 +4409,15 @@
4279
4409
  * @param {number} [conf.max_stack_trace_line_length=200] - maximum amount of characters are allowed per stack trace line. This limits also the crash message length
4280
4410
  * @param {array=} conf.ignore_referrers - array with referrers to ignore
4281
4411
  * @param {boolean} [conf.ignore_prefetch=true] - ignore prefetching and pre rendering from counting as real website visits
4412
+ * @param {boolean} [conf.rc_automatic_optin_for_ab=true] - opts in the user for A/B testing while fetching the remote config (if true)
4413
+ * @param {boolean} [conf.use_explicit_rc_api=false] - set it to true to use the new remote config API
4282
4414
  * @param {boolean} [conf.force_post=false] - force using post method for all requests
4283
4415
  * @param {boolean} [conf.ignore_visitor=false] - ignore this current visitor
4284
4416
  * @param {boolean} [conf.require_consent=false] - Pass true if you are implementing GDPR compatible consent management. It would prevent running any functionality without proper consent
4285
4417
  * @param {boolean} [conf.utm={"source":true, "medium":true, "campaign":true, "term":true, "content":true}] - Object instructing which UTM parameters to track
4286
4418
  * @param {boolean} [conf.use_session_cookie=true] - Use cookie to track session
4287
4419
  * @param {boolean} [conf.enable_orientation_tracking=true] - Enables orientation tracking at the start of a session
4420
+ * @param {array=} [conf.heatmap_whitelist=[]] - array with trustable domains for heatmap reporting
4288
4421
  * @param {number} [conf.session_cookie_timeout=30] - How long till cookie session should expire in minutes
4289
4422
  * @param {boolean|function} [conf.remote_config=false] - Enable automatic remote config fetching, provide callback function to be notified when fetching done
4290
4423
  * @param {string=} [conf.namespace=""] - Have separate namespace of of persistent data
@@ -4448,8 +4581,10 @@
4448
4581
  * @returns {String} modified string
4449
4582
  */
4450
4583
  function stripTrailingSlash(str) {
4451
- if (str.substr(str.length - 1) === "/") {
4452
- return str.substr(0, str.length - 1);
4584
+ if (typeof str === "string") {
4585
+ if (str.substring(str.length - 1) === "/") {
4586
+ return str.substring(0, str.length - 1);
4587
+ }
4453
4588
  }
4454
4589
  return str;
4455
4590
  }