Web analytics (JavaScript) (24.4.X)

Follow

This documentation is for the Countly Web SDK version 24.4.X. The SDK source code repository can be found here.

Click here, to access the documentation for older SDK versions.

Countly can run with all browsers that supports ECMAScript 5. Minimum versions of major internet browsers that fully support ES5 are:

IE Edge Firefox Firefox (Android) Opera Opera (Mobile) Safari Safari (iOS) Chrome Chrome (Android)
10 12 21 96 15 64 6 6 23 98

To examine the example integrations please have a look here.

Adding the SDK to the Project

To track your web pages, you will need the Countly Web SDK (also known as the Countly JavaScript tracking library). It is automatically hosted on your Countly server as a minified UMD file (at http://yourdomain.com/sdk/web/countly.min.js) and can be updated via the command line. This library also works well with mobile applications that consist of HTML5 views.

Optionally, you may also use package managers to gain access to the SDK:

npm yarn
npm install countly-sdk-web

You can also reach the SDK through CDN:

Latest Specific Version
// latest non minified
countly.js

// latest minified
countly.min.js

Lastly as an alternative option, you may download countly.min.js from our GitHub repository and upload it to any server from where you would like to host it.

SDK Integration

Minimal Setup

You may use the Countly Web SDK synchronously or asynchronously. However the asynchronous usage would benefit from working without blocking content loading. This would also allow you to use Countly while the Countly script has not yet been loaded. This can be done by pushing function calls into the Countly.q queue.

Inserting asynchronous code before closing the "head tags" of your website is suggested, while Synchronous code should be added towards the bottom of the page before closing the head tag. Main logic here is to make the Countly load as soon as possible to start collecting data.

Here you would also need to provide your application key and server URL. Please check here for more information on how to acquire your application key (APP_KEY) and server URL.

An example setup would look like this:

Asynchronous Synchronous
<script type='text/javascript'>
  
// Some default pre init
var Countly = Countly || {};
Countly.q = Countly.q || [];

// Provide your app key that you retrieved from Countly dashboard
Countly.app_key = "YOUR_APP_KEY";

// Provide your server IP or name.
// If you use your own server, make sure you have https enabled if you use
// https below.
Countly.url = "https://yourdomain.com";

// Start pushing function calls to queue
// Track sessions automatically (recommended)
Countly.q.push(['track_sessions']);

//track web page views automatically (recommended)
Countly.q.push(['track_pageview']);

// Load Countly script asynchronously
(function() {
var cly = document.createElement('script'); cly.type = 'text/javascript';
cly.async = true;
// Enter URL of script here (see below for other option)
cly.src = 'https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/lib/countly.min.js';
cly.onload = function(){Countly.init()};
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(cly, s);
})();
</script>

In the above-mentioned example, we used JSDelivr to retrieve the Countly JS SDK. As an alternative, you may also use one of the methods mentioned at the previous section.

If you are in doubt about the correctness of your Countly SDK integration you can learn about the verification methods from here.

SDK Logging

The first thing you should do while integrating our SDK is enabling logging. If logging is enabled, then our SDK will print out debug messages about its internal state and encountered problems.

Set debug option to true at the Countly initialization to enable logging:

Asynchronous Synchronous
//during initialization
Countly.debug = true;

For more information on where to find the SDK logs you can check the documentation here.

Crash Reporting

Countly also provides a way for tracking JavaScript errors on your websites.

Select the following function to automatically capture and report JavaScript errors on your website:

Asynchronous Synchronous
Countly.q.push(['track_errors'])

Additionally, you may add more segments or properties/values to track with error reports by providing an object with a key/values to add to the error reports.

Asynchronous Synchronous
Countly.q.push(['track_errors', {
  "facebook_sdk": "2.3",
  "jquery": "1.8"
}])

Apart from automatically reporting unhandled errors, you may also report handled exceptions to the server, so you may figure out how to handle them later on (or if you even need to figure out how to handle them later on). Optionally, you may once again provide the custom segments to be used in the report (or use the ones provided with the track_error method as default ones).

Countly.log_error (error, segments):

Asynchronous Synchronous
try{
  //do something here
}
catch(ex){
  //report error to Countly
  Countly.q.push(['log_error', ex]);
}

For fatal errors you can use recordError function which takes three parameters; first, an error object with a stack key that has the error message value, second, a boolean which is false to indicate the fatality of the error and lastly segments object (optional) for custom crash segments for any extra information that you want to deliver with custom key value pairs. You can use this same function for nonfatal errors too by just setting the boolean value to true.

Countly.recordError(error, nonFatal, segments):

Asynchronous Synchronous

  const error = {stack: 'Your error message here'};
//report fatal error to Countly
Countly.q.push(['recordError', error, nonFatal, segments]);

To better understand what your users did prior to receiving an error, you may leave breadcrumbs throughout the code on different user actions. These breadcrumbs will then be combined in a single log and reported to the server as well.

Asynchronous Synchronous
Countly.q.push(['add_log', "user clicked button a"]);

Symbolication

Crash symbolication is available for Enterprise Edition users.

If the js files you serve are minified, transpiled or otherwise processed from your source files, you can use symbolication. With symbolication, your stacktraces will correctly point to the lines on the source files, which will help your developers debug crashes and errors. Here's an example:

JS Symbolication input sample JS Symbolication output sample
ReferenceError: undefined_function is not defined
    at r (file:///home/atak/Work/Countly/sample-app/dist/main.js:2:140)
    at HTMLButtonElement.document.getElementById.onclick (file:///home/atak/Work/Countly/sample-app/dist/main.js:2:521)

There are a number of JavaScript build tools and countless way to configure them so laying out the steps for producing a source map file for each one would take quite a while. We will focus on webpack for brevity and simplicity.

When using webpack configuration, the devtool option to generate project source maps, keep in mind that options used to attain more verbose information might take longer to build. You must choose an option that generates the source map as a separate file and not inline with the final js file. After setting this, your builds will produce a source map file ending in .map, which is the source map file you will upload to your Countly server.

To start symbolicating your errors you just need to upload your source map file to your Countly instance via the side menu > Improve > Errors > Manage Symbols. In this view, the source map files you have uploaded are listed. To upload one, you click the "Add Debug Symbol File", fill the symbol type and app version fields accordingly and upload your .map file. With your source map file uploaded, you can symbolicate the crash reports for that app version on Countly. For more information, see Crash Symbolication documentation.

Events

Adding an Event

Events are a way to track any custom actions or other data you would like to track from your website. You may also set segments to be able to view a breakdown of the action by providing the segment values.

All data passed to the Countly instance via the SDK or API should be in UTF-8.

An event consists of a JavaScript object with keys:

  • key - the name of the event (mandatory)
  • count - number of events (default: 1)
  • sum - sum to report with the event (optional)
  • dur - duration expressed in seconds, meant for reporting with the event (optional)
  • segmentation - an object with key/value pairs to report with the event as segments

Here is an example of adding an event with all possible properties:

Asynchronous Synchronous
Countly.q.push(['add_event',{
  "key": "click",
  "count": 1,
  "sum": 1.5,
  "dur": 30,
  "segmentation": {
    "key1": "value1",
    "key2": "value2"
  }
}]);

Timed Events

All events contain an optional duration property that can be set manually or with the help of the Countly web SDK's convenience functions. There are three methods available to use to calculate the duration property: start_event, cancel_event, and end_event.

The expected usage of these methods involves calling start_event for a specific event when it begins and then calling end_event to calculate the duration and create the event. In case you need to cancel a previously-called start_event, you can call cancel_event. However, it's important to note that these methods operate on the memory layer and shouldn't be used to calculate durations in situations where a browser restart occurs.

The start_event method is used to initiate an internal timer within the SDK for a given event name. This timer works by taking the current timestamp and storing it in memory.

Asynchronous Synchronous
Countly.q.push(['start_event', 'timedEvent']);

The cancel_event method erases the timestamp associated with a given event name if a start_event was previously called for that event.

Asynchronous Synchronous
Countly.q.push(['cancel_event', 'timedEvent']);

The end_event method calculates the duration value for the given event name by finding the time difference between when the start_event was called and the current time. It then creates an event for the given name with the calculated duration and adds it to the event queue. You can also pass an event object to this method, and in that case, it will use the key value as the event name.

Asynchronous Synchronous
//end event
Countly.q.push(['end_event', 'timedEvent']);

//or end event with additional data
Countly.q.push(['end_event',{
  "key": "timedEvent",
  "count": 1,
  "sum": 1.5,
  "segmentation": {
  "key1": "value1",
  "key2": "value2"
  }
}]);

Sessions

Automatic Session Tracking

This method will automatically track user sessions by calling begin, extend, and end session methods.

Asynchronous Synchronous
Countly.q.push(['track_sessions']);

Manual Sessions

Beginning a Session

This method would allow you to control sessions manually. Only use this method if you aren’t planning on calling the track_sessions method and set the use_session_cookie setting to false for more granular control of the session.

If noHeartBeat is true, then the Countly Web SDK will not automatically extend the session, meaning you would be the one to do it automatically.

Asynchronous Synchronous
Countly.q.push(['begin_session']);

Extending a session

The Countly SDK will extend the session itself by default (if noHeartBeat was provided in the begin_session), but if you have not selected this option, you may then extend it using this method and provide the seconds since the last begin_session or session_duration call.

Asynchronous Synchronous
Countly.q.push(['session_duration', sec]);

Ending a session

When a visitor is leaving your app or website, you should end their session with this method or by optionally providing the amount of seconds since the last begin session or session_duration calls, which ever came last.

Asynchronous Synchronous
Countly.q.push(['end_session']);

View Tracking

This method will track the current pageview by using location.path as the page name and then reporting it to the server.

Asynchronous Synchronous
Countly.q.push(['track_pageview']);

For Ajax updated contents and single page web applications, pass the page name as a parameter to record the new page view.

Asynchronous Synchronous
Countly.q.push(['track_pageview','pagename']);

Here is a good example if your single-page app uses URL hash.

Asynchronous Synchronous
Countly.q.push(['track_pageview',location.pathname+location.hash]);

$(window).on('hashchange', function() {
  Countly.q.push(['track_pageview',location.pathname+location.hash]);
});

In some cases, you might like to ignore some URLs to exclude from tracking, such as dynamic URLs that include the user ID in the URL, internal URLs, or for any other reason. You may do so by providing another parameter with a list of strings of views to ignore or a list of regular expressions to ignore.

Asynchronous Synchronous
//Ignoring specific page
Countly.q.push(['track_pageview',["/test-page"]]);

//Ignoring multiple specific pages
Countly.q.push(['track_pageview',["/test1", "/test2", "/test3"]]);

//Ignoring all /download/{download_id} pages but not /download page
Countly.q.push(['track_pageview',["/download/*"]]);

//Ignoring specific page while providing custom values (like hash value) for page view
Countly.q.push(['track_pageview', location.pathname+location.hash,["/test-page"]]);

Optionally, you may provide view segments (key/value pairs) to track them with the view (as the third parameter). There is a list of reserved segment keys that should not be used:

  • start
  • visit
  • bounce
  • end
  • name
  • domain
  • view
  • segment
  • platform
Asynchronous Synchronous
//Provide view segments
Countly.q.push(['track_pageview', null, null, {theme:"red", mode:"fullscreen"}]);

Overriding View Name and URL Getters

There are cases when determining the view name requires more complex logic, and in some cases, you will need to separate the URL and the View naming. This is done so you may still have some business logic view names, yet you have the valid URL underneath them to view action maps, such as clicks and scrolls.

To do so you may define the view name and URL getter functions. That way you won’t need to provide separate view names, rather the SDK will use this getter function to receive the proper view name and the URL you would like to use.

Here is an example of how to create a getter for View and URL.

View getter URL getter
Countly.getViewName = function(){
  //get base for our view
  var view = location.pathname;
  
  //if this is a page with dynamic id, we don't need id itself
  if (view.startsWith("/id/")) {
      view = "/id";
  }
  
  //now let's beautify the name
  //remove first character which always is slash (/)
  view = view.substring(1);
  
  //split by sub paths and use spaces instead
  view = view.split("/").join(" ");
  
  //upper case first letter
  view[0].toUpperCase() + view.substring(1)
  
  //return beautified view name
  return view;
}

Device ID Management

Retrieving Current Device ID

Countly offers a convenience method (get_device_id) for you to get the current user's device ID:

var id = Countly.get_device_id();

SDK records the type of an ID. These types are:

  • DEVELOPER_SUPPLIED
  • SDK_GENERATED
  • TEMPORARY_ID

DEVELOPER_SUPPLIED device ID means this ID was assigned by your internal logic. SDK_GENERATED device ID means the ID was generated randomly by the SDK, and TEMPORARY_ID device ID means the SDK is in offline mode.

You can get the device ID type of a user by calling the get_device_id_type function:

var idType = Countly.get_device_id_type();

You can use the DeviceIdType enums to evaluate the device ID type you retrieved:

var idType = Countly.get_device_id_type();
if (idType === Countly.DeviceIdType.SDK_GENERATED) {
  // ...do something
}

Changing Device ID

You can change the device ID of a user with set_id method:

Asynchronous Synchronous
Countly.q.push(['set_id', "newId"]);

This method's effect on the server will be different according to the type of the current ID stored in the SDK at the time you call it:

  • If current stored ID is SDK_GENERATED then in the server all the information recorded for that device ID will be merged to the new ID you provide and old user with the SDK_GENERATED ID will be erased.

  • If the current stored ID is DEVELOPER_SUPPLIED or TEMPORARY_ID then in the server it will also create a new user with this new ID if it does not exist.

If you need a more complicated logic or using the SDK version 24.4.0 then you will need to use this method mentioned here instead.

NOTE: The call will reject invalid device ID values. A valid value is not null, not undefined, of type string and is not an empty string.

Temporary Device ID (Offline mode)

Some cases do exist when you would like the SDK to collect data but not send it to the server until a certain point. Additionally, this mode allows you to delay providing the device_id property until a later time.

E.g. if you would like to track your users with a custom device_id, such as with your internal customer ID, and you may only receive that value as soon as the user logs in. Yet, you would also like to track what the user did before logging in.

Using offline mode within this context allows you to omit the user merging and server overhead that comes with it, including any possibly skewed aggregation data.

If offline mode is entered and consent was enabled, all previously given consent will be removed. This means that all features will cease to function until new consent has been given again. Therefore after entering the offline mode, you should reestablish consent again.

To launch the SDK in offline mode, simply provide the offline_mode config value as true. At this point you may omit providing the device_id value if you would like.

Asynchronous Synchronous
Countly.debug = false;
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://try.count.ly";
Countly.offline_mode = true;

Countly.init();

Or you can enable offline mode at any point later in the SDK.

Asynchronous Synchronous
Countly.q.push(['enable_offline_mode']);

If you would like to disable offline mode and optionally provide the device_id at a later time, you may do so by adhering to the following:

Asynchronous Synchronous
Countly.q.push(['disable_offline_mode', device_id]);

Device ID Generation

By default Countly generates and assigns a random device ID (UUID) to each device that visits your website. This ID is saved at the localstorage of the browser. Assuming the localstorage is not cleared, this device will be recognized with this stored ID from now on.

Limitations to this method:

  • Multiple users using the same device would be seen as a single user
  • When a user reaches to your website from multiple devices or browsers in incognito mode then all devices would be seen as separate users

If these scenarios are an issue of concern to you, you can mitigate them with various device management strategies like:

  • Adding a login/authentication page and assigning device ID there
  • Entering the offline mode (mentioned above) until you identify the user and assigning the ID then

You can also provide an ID during init to prevent the SDK from generating a random ID:

// adding device ID here will prevent the generation of a random ID
Countly.init({
  app_key:"YOUR_APP_KEY",
  url: "https://your.server",
  device_id:"yourDeviceID",
});

You can also provide an ID in the search query of the link that opens your site. If you use the param cly_device_id its value will be set as the device ID instead of generating a random one:

// you can assign a device ID to a user through a link that you have provided to them by adding cly_device_id to your url query
yoursite.com + ?cly_device_id=yourDeviceID

If you want to erase the previously stored device ID from the storage you can set clear_stored_id flag to true at the init config:

// this will erase the stored device ID from the local storage every time the Countly is initialized
Countly.init({
  app_key:"YOUR_APP_KEY",
  url: "https://try.count.ly",
  clear_stored_id: true,
});

Device ID Priority

If you have used multiple methods to set a device ID for your users during your first init, Countly would fall back to the device ID priority hierarchy to assign the the correct ID for your user. This hierarchy is as follows:

URL set ID > Developer set ID > Temp ID (offline mode) > SDK generated ID

User Location

With the SDK integrated into your website, you might want to track your user location. By default, the Countly Server uses the GeoIP database to deduce a user's location from the coming requests (you can check details of it from here). However the SDK provides the ability to set custom locations for your users.

Setting Location

There are 3 location parameters that can be provided with the SDK:

  • Country code in the two-letter, ISO standard ("jp", "gr" etc.)
  • City name ("Kyoto", "Athens" etc.)
  • Your user’s IP address

You can provide any of these information while initializing the SDK:

Asynchronous Synchronous
// you can directly provide the city and the country code
Countly.city = "Tokyo";
Countly.country_code = "jp";
// or you can provide the ip address
Countly.ip_address = "198.168.1.1";
     
// Initialize the SDK

Heatmaps

Heatmaps feature is a web exclusive plugin that helps you to visualize user interactions on your website. Web SDK supports this functionality by providing user click and scroll information to your server. Then from your server, you can trigger Heatmaps overlay to visualize these click clusters and scroll zones on your website.

To display this overlay the SDK loads certain scripts from your server. To ensure the source of these scripts and to enable these scripts to be loaded from somewhere else other than your Countly server, the SDK offers a whitelisting option during the initialization. To whitelist domains other than your Countly server you should provide an array of these domains, as String values, under the 'heatmap_whitelist' flag during the initialization:

Asynchronous Synchronous
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://try.count.ly";
Countly.heatmap_whitelist = ["https://you.domain1.com", "https://you.domain2.com"];

Tracking Clicks

This method will automatically track clicks on the last reported view and display them on the heatmap.

Asynchronous Synchronous
Countly.q.push(['track_clicks']);

In the event you are facing issues with viewing heatmaps, kindly go through this Troubleshooting guide.

Viewing heatmaps with HTTP/HTTPS content:

Note that browsers do not allow loading HTTP iframe content on HTTPS websites. For this reason, if you are using HTTPS on your Countly instance, you will only be able to view HTTPS content and no HTTP page content will be visible.

After you integrate the JS SDK and start sending click data, all generated heatmaps may be viewed under Analytics > Page views, as shown below:

001.png

Tracking Scrolls

This method will automatically track scrolls on the last reported view and display them on the heatmap.

Asynchronous Synchronous
Countly.q.push(['track_scrolls']);

As with Click Heatmaps, collected data is viewable under Analytics > Page views. You may change the heatmap type on the top bar once a view is open.

002.png

Remote Config

Remote Config feature enables you to fetch data that you have created in your server. Depending on the conditions you have set, you can fetch data from your server for the specific users that fits those conditions and process the Remote Config data in anyway you want. Whether to change the background color of your site to showing a certain message, the possibilities are virtually endless. For more information on Remote Config please check here.

While fetching Remote Config, the SDK will automatically enroll the user to A/B testing. But you are able to explicitly enroll (or not) your users to the A/B testing while fetching the remote config values or afterwards. For more information on A/B testing please check here.

Automatic Remote Config

Automatic Remote Config functionality is disabled by default and needs to be explicitly enabled. When automatic Remote Config is enabled, the SDK will try to fetch it upon some specific trigers. For example, after SDK initialization, changing device ID.

You may enable this feature by providing to the remote_config flag a callback function or by setting it to true while initializing the SDK.

If you provide a callback, the callback will be called when the Remote Config is initially loaded and when it is reloaded if you change the device_id. This callback should have two parameters, first is for error, and second is for the Remote Config object.

Asynchronous Synchronous
// in your Countly init script
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://try.count.ly";
Countly.debug = true; Countly.remote_config = true;
// OR
// provide a callback to be notified when configs are loaded Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://try.count.ly";
Countly.debug = true; Countly.remote_config = function(err, remoteConfigs){ if (!err) { //we have our remoteConfigs here console.log(remoteConfigs); } };

Manual Remote Config

If you want, you can manually fetch the Remote Config in order to receive the latest value anytime after the initialization. To do so you have to use the fetch_remote_config call. This method is also used for reloading the values for updating them according to the latest changes you made on your server.

By using this method, you can simply load the entire object or load some specific keys or omit some specific keys in order to decrease the amount of data transfer needed, assuming the values for some of the keys are large. This call will automatically save the fetched keys internally.

Fetch All Keys

Here you so not need to provide any parameters to the call but providing a callback is the recommended practice. This callback should have two parameters, first is for error, and second is for the Remote Config object.

// load the whole configuration object with a callback
Countly.fetch_remote_config(function(err, remoteConfigs){
  if (!err) {
    console.log(remoteConfigs);
// or do something else here if you want with remoteConfigs object }
});

// or whole configuration object with no params Countly.fetch_remote_config();

Fetch Specific Keys

Here the keys should be provided as string values in an array, as the first parameter in fetch_remote_config call. You can provide a callback function as a second parameter. This callback should have two parameters, first is for error, and second is for the Remote Config object.

// load specific keys only, as `key1` and `key2`
Countly.fetch_remote_config(["key1","key2"], function(err, remoteConfigs){
  if (!err) {
    console.log(remoteConfigs);
// or do something else here if you want with remoteConfigs object } });

Fetch All Except Specific Keys

Here the first parameter should be set to 'null' or 'undefined' and the keys that you want to omit must be provided as the second parameter as an array of keys as string. As a third parameter you can provide a callback function. This callback should have two parameters, first is for error, and second is for the Remote Config object.

// load all key values except specific keys, as `key1` and `key2'
Countly.fetch_remote_config(null, ["key1","key2"], function(err, remoteConfigs){
  if (!err) {
    console.log(remoteConfigs);
// or do something else here if you want with remoteConfigs object } });

Accessing Remote Config Values

You may call get_remote_config each time you would like to receive the Remote Config object of a value for a specific key or all keys from your local storage.

This method should be called once the Remote Config have been successfully loaded, or it will simply return an empty object or undefined values.

//get whole Remote Config object
var remoteConfig = Countly.get_remote_config();

//or get value for specific key like 'test'
var test = Countly.get_remote_config("test");

A/B Testing

To do so you have to set the use_explicit_rc_api flag to true during init (by default it is false). This will use the new Remote Config API and enroll your users to the A/B testing if they are eligible. However if you want to use the new API without enrolling your users automatically rc_automatic_optin_for_ab flag should be set to false during init (by default it is true).

If you would like to enroll user to A/B testing without going through the Remote Config API, instead you can use the call enrollUserToAb with keys (an array of string values) that you want to enroll the user to.

// enrolling user for 'key1' and 'key2'
Countly.enrollUserToAb(["key1","key2"]);

Consent

If consents are enabled, to fetch the Remote Config data you have to provide the 'remote-config' consent for this feature to work.

User Feedback

If you want to receive feedback from your users, there are a couple of ways you can do that in Countly. Users can leave feedback through Feedback Widgets (Surveys, NPS, Ratings). With the help of these widgets, you can ask your customers multiple questions and learn about their opinions and preferences in detail.

Feedback Widgets

Feedback Widgets is a Countly Enterprise plugin.

It is possible to display 3 kinds of feedback widgets: NPS, Survey and Rating.

For more detailed information about Feedback Widgets, you can refer to here.

Before any feedback widget can be shown, you need to create them in your Countly dashboard.

All three widgets use the same API to fetch feedbacks from the server as well as to display them to the end user. By default, the created widget will be appended to the end of the html document. In some scenarios you might prefer to have the widget injected in a specific element. For those scenarios we have added optional selectors. The first one is used for selecting an element by it's id and the second one is used to select the element by it's class selector. If you want to inject the feedback widget in a specific element, you can do so by specifying the element ID or the class name. You can also add custom segmentation while presenting a widget.

To use feedback widgets, you need to give "feedback" consent (in case consent is required).

Asynchronous Synchronous
//Fetch user's feedback widgets from the server
Countly.q.push(['get_available_feedback_widgets', feedbackWidgetsCallback]);

// Feedback widget callback function, err is for error and countlyPresentableFeedback contains an array of widhet objects function feedbackWidgetsCallback(countlyPresentableFeedback, err) { if (err) { console.log(err); return; } // Decide which widget to show. Here, the first rating widget is selected. const widgetType = "rating"; const countlyFeedbackWidget = countlyPresentableFeedback.find(widget = widget.type === widgetType); if (!countlyFeedbackWidget) { console.error(`[Countly] No ${widgetType} widget found`); return; } //Define the element ID and the class name (optional, pass undefined if you don't use) const selectorId = "targetIdSelector"; const selectorClass = "targetClassSelector"; // Define the segmentation (optional) const segmentation = { page: "home_page" }; //Display the feedback widget to the end user Countly.present_feedback_widget(countlyFeedbackWidget, selectorId, selectorClass, segmentation); }

Note: Feedback Widgets' show policies are handled internally by the Web SDK.

Manual Reporting

Reporting Feedback Widgets manually consists of 3 main steps:

  1. Fetching widget list from the server with 'get_available_feedback_widgets'
  2. Fetching one widget's data from that list with 'getFeedbackWidgetData'
  3. Reporting that single widget's results with 'reportFeedbackWidgetManually'

At first step, by using the 'get_available_feedback_widgets' function, you can fetch the list of available widgets from your server as an Array of widget Objects. This function takes a callback as a parameter and this callback should have two parameters, first one for the returned list and the second one for the error. Inside your callback you should process this array of widget objects and pick one object that you want to report the results for. This array and the objects that you can pick would look like this:

{
  "result":[
      {
        "_id":"614811419f030e44be07d82f",
        "type":"rating",
        "appearance":{
          "position":"mleft",
          "bg_color":"#fff",
          "text_color":"#ddd",
          "text":"Feedback"
          },
        "tg":["/"],
        "name":"Leave us a feedback"
      },
      {
        "_id":"614811419f030e44be07d839",
        "type":"nps",
        "name":"One response for all",
        "tg":[]
      },
      {
        "_id":"614811429f030e44be07d83d",
        "type":"survey",
        "appearance":{
          "position":"bLeft",
          "show":"uSubmit",
          "color":"#0166D6",
          "logo":null,
          "submit":"Submit",
          "previous":"Previous",
          "next":"Next"
          },
        "name":"Product Feedback example",
        "tg":[]
      }
    ]
  }

Here you would want to pick a widget according to its type and name or any other information you are looking for. For more information on this data please check here.

At second step, by using the 'getFeedbackWidgetData' function, you can fetch the data of the widget of your choice. This functions has two parameters, first one is the widget object obtained from the first step and the second one is a callback with two parameters, one for the retrieved data and second one is for the error. Inside your callback you can process the returned object and gather information necessary for you to provide at the next step.

The third and last step is the part where you would report the results for the widget of your choice by using the 'reportFeedbackWidgetManually' function. This function takes three parameters, first the widget object obtained from the first step, second the widget data from the second step and third the result object that you want to report for your widget. This result object would have different key/value pairs depending on the type of widget you are reporting about so you can reach to an in-depth explanation on how to form this object from here.

And example implementation of the mentioned concepts can be seen here:


// an example of getting the widget list, using it to get widget data and then recording data for it manually. widgetType can be 'nps', 'survey' or 'rating'
function getFeedbackWidgetListAndDoThings(widgetType) {
  // get the widget list
  Countly.get_available_feedback_widgets(
    // callback function, 1st param is the feedback widget list
    function (feedbackList, err) {
      if (err) { // error handling
        console.log(err);
        return;
      }

      // find the widget object with the given widget type. This or a similar implementation can be used while using fetchAndDisplayWidget() as well
      const countlyFeedbackWidget = feedbackList.find(widget = widget.type === widgetType);
      if (!countlyFeedbackWidget) {
        console.error(`[Countly] No ${widgetType} widget found`);
        return;
      }

      // Get data with the widget object
      Countly.getFeedbackWidgetData(CountlyFeedbackWidget,
        // callback function, 1st param is the feedback widget data
        function (feedbackData, err) {
          if (err) { // error handling
            console.error(err);
            return;
          }

          const CountlyWidgetData = feedbackData;
          // record data according to the widget type
          if (CountlyWidgetData.type === 'nps') {
            Countly.reportFeedbackWidgetManually(CountlyFeedbackWidget, CountlyWidgetData, { rating: 3, comment: "comment" });
          } else if (CountlyWidgetData.type === 'survey') {
            var widgetResponse = {};
            // form the key/value pairs according to data
            widgetResponse["answ-" + CountlyWidgetData.questions[0].id] = CountlyWidgetData.questions[0].type === "rating" ? 3 : "answer";
            Countly.reportFeedbackWidgetManually(CountlyFeedbackWidget, CountlyWidgetData, widgetResponse);
          } else if (CountlyWidgetData.type === 'rating') {
            Countly.reportFeedbackWidgetManually(CountlyFeedbackWidget, CountlyWidgetData, { rating: 3, comment: "comment", email: "email", contactMe: true });
          }
        }

      );
    })
}

Consent

If consents are enabled, to use Feedback Widgets you have to provide the 'feedback' and the 'star-rating' consents for this feature to work.

User Profiles

User Details

If you have any details about the user/visitor, you may provide Countly with that information. This will allow you to track every specific user on the "User Profiles" tab, which is available with Countly Enterprise Edition.

If a parameter is set as an empty string, it will be deleted on the server side.

The list of possible parameters you may pass:

Asynchronous Synchronous
Countly.q.push(['user_details',{
  "name": "Arturs Sosins",
  "username": "ar2rsawseen",
  "email": "test@test.com",
  "organization": "Countly",
  "phone": "+37112345678",
  //Web URL to picture
  "picture": "https://pbs.twimg.com/profile_images/1442562237/012_n_400x400.jpg", 
  "gender": "M",
  "byear": 1987, //birth year
  "custom":{
    "key1":"value1",
    "key2":"value2",
    ...
  }
}]);

Modifying Custom Data

Additionally, you may perform different manipulations on custom data values, such as incrementing the current value on the server or storing an array of values under the same property.

After using modifiers, don't forget to call userData.save to send data to server.

The list of available methods may be found below:

Asynchronous Synchronous
Countly.q.push(['userData.set', key, value]) //set custom property
Countly.q.push(['userData.unset', key]) //remove custom property
Countly.q.push(['userData.set_once', key, value]) //set custom property only if property does not exist
Countly.q.push(['userData.increment', key]) //increment value in key by one
Countly.q.push(['userData.increment_by', key, value]) //increment value in key by provided value
Countly.q.push(['userData.multiply', key, value]) //multiply value in key by provided value
Countly.q.push(['userData.max', key, value]) //save max value between current and provided
Countly.q.push(['userData.min', key, value]) //save min value between current and provided
Countly.q.push(['userData.push', key, value]) //add value to key as array element
Countly.q.push(['userData.push_unique', key, value]) //add value to key as array element, but only store unique values in array
Countly.q.push(['userData.pull', key, value]) //remove value from array under property with key as name
Countly.q.push(['userData.save']) //send userData to server

Orientation Tracking

Orientation tracking is enabled by default and will be sent if the required "user" consent is given (if enabled). Countly will report the device orientation once a session starts, and at any time the orientation changes.

You may disable orientation tracking by providing the enable_orientation_tracking setting when initializing the SDK as follows.

Asynchronous Synchronous
// in your Countly init script
Countly.enable_orientation_tracking = false;

In case you need to force reporting orientation, you may call the following method.

Asynchronous Synchronous
//user stored conversion data
Countly.q.push(['report_orientation', "portrait"]);

Application Performance Monitoring

If you want to record some performance metrics regarding your website there are 2 ways to report these performance traces. One way is to construct and report them manually. The other is using a plugin that will utilize BoomerangJS to collect website's performance data and report it as a performance trace.

You can reach to example implementations of APM with BoomerangJS from the following links:
Async Apm Example
Sync Apm Example

Custom Traces

To manually report trace you need to construct the trace object and call a method like this:

Asynchronous Synchronous
//report custom trace
Countly.q.push(["report_trace",{
  type: "device", //device or network
  name: "test call", //use name to identify trace and group them by
  stz: 1234567890123, //start timestamp in milliseconds
  etz: 1234567890123, //end timestamp in milliseconds
  apm_metrics: {
      duration: 1000 //duration of trace
  }
}]);

Whether you are using Countly synchronously or asynchronously, you should always provide the 'duration' key and its value in apm_metrics, otherwise custom traces won't be recorded.

Automatic Device Traces

Automatic trace reporting has two different implementation depending on if you are using Countly synchronously or asynchronously. Normally we would like Countly script to load first and BoomerangJS related scripts right after. 

Asynchronous Implementation

To use automatic device traces in your async Countly implementation you will need to set loadAPMScriptsAsync flag to true in Countly object. This would ensure that the correct script load order is established. You can provide two additional flags to the Countly object. First one is the BoomerangJS script source path as customSourceBoomerang and the second is the countly_boomerang script source path as customSourceCountlyBoomerang. If not provided the SDK would use the latest CDN scripts as the source:

Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.loadAPMScriptsAsync = true;
// Countly.customSourceBoomerang = "../somewhere/boomerang.min.js";
// Countly.customSourceCountlyBoomerang = "../somewhere/countly_boomerang.js";
// ...

Also, in your Countly init script you need to call a method to start reporting 'loading' and 'network' traces automatically:

// enables APM
Countly.q.push(["track_performance"]);

This method accepts a BoomerangJS config object (more information on BoomerangJS) as an optional second parameter. If you are familiar with it, you can modify it on your own depending on your needs (you can find the used files here). By default the SDK would use this configuration:

{
    //page load timing
  RT:{},
  //required for automated networking traces
  instrument_xhr: true,
  captureXhrRequestResponse: true,
  AutoXHR: {
    alwaysSendXhr: true,
    monitorFetch: true,
    captureXhrRequestResponse: true
  },
  //required for screen freeze traces
  Continuity: {
    enabled: true,
    monitorLongTasks: true,
    monitorPageBusy: true,
    monitorFrameRate: true,
    monitorInteractions: true,
    afterOnload: true
  }
}

Synchronous Implementation

To automatically report traces you will need to include 2 additional files in your project directly after declaring the Countly script like this with the correct paths according to your project structure:

// Option 1: You can provide local paths
<script type='text/javascript' src="../plugin/boomerang/boomerang.min.js"></script> <script type='text/javascript' src='../plugin/boomerang/countly_boomerang.js'></script>

// Option 2: Or you can use CDN for path
<script type='text/javascript' src="https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/plugin/boomerang/boomerang.min.js"></script>
<script type='text/javascript' src="https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/plugin/boomerang/countly_boomerang.js"></script>

After that, you would call a method to start reporting 'loading' and 'network' traces automatically. You can optionally provide here a BoomerangJS config object if you are familiar with it as mentioned above at Async implementation. Default usage inside your Countly init script would be like this:

//automatically report traces
Countly.track_performance();

User Consent

This section talks about how to set up GDPR compliant consent management with the Countly Web SDK.

If consent management is enabled then features of the SDK would require consent to be provided before start working. This way you would have full control over your user tracking in every part of your website.

It should be noted that there is no persistency for consent state in the SDK and the developer is fully responsible for saving/keeping the state of the user's consent before providing it to the SDK.

Feature Names

The SDK provides different features for consent. You may check all the supported features for the current SDK by checking the Countly.features property. Here is a list containing all the properties with explanations:

  • sessions - tracks when, how often, and how long users use your website
  • events - allows your events to be sent to the server
  • views - allows for the views/pages accessed by a user to be tracked
  • scrolls - allows a user’s scrolls to be tracked on the heatmap
  • clicks - allows a user’s clicks and link clicks to be tracked on the heatmap
  • forms - allows a user’s form submissions to be tracked
  • crashes - allows JavaScript errors to be tracked
  • attribution - allows direct attribution tracking
  • users - allows user information, including custom properties, to be collected/provided
  • star-rating - allows user rating and feedback tracking through rating widgets
  • feedback - allows survey, nps and rating widgets usage and reporting
  • apm - allows performance tracking of application by recording traces
  • location - allows a user’s location (country, city area) to be recorded
  • remote-config - allows users to download remote config from the server

Setup During Init

To enable consent management mode ( and to disable tracking until consent is given for a specific feature), all you need to do is pass true to the require_consent config flag during SDK initialization.

Asynchronous Synchronous
// in your Countly init script
Countly.require_consent = true;

Changing Consent

Upon a visitor’s arrival to your website, you could check if you already have consent from this visitor. If not, you could present them with a popup explaining what will be tracked and allow them to consent to tracking. When a user selects the consent preferences, you should persistently store it, and on each Countly load, let Countly know for which features the user gave consent by calling the Countly.add_consent method and passing one or multiple features (as an array).

Also you can also allow the user to change their mind regarding separate settings and when changes are going to be made you can call the Countly.add_consent or Countly.remove_consent methods to allow Countly to track specific features or disable tracking for them.

Here is a high-level example of how it could look:

Asynchronous Synchronous
// to add consent {string|array}
Countly.q.push(['add_consent', feature]);

// to remove consent {string|array}
Countly.q.push(['remove_consent', feature]);

Feature Groups

Depending on your website and use case, you may also want to combine some of the features into one using the group_features method.

Grouping features One group for everything
Countly.group_features({
  activity:["sessions","events","views"],
  interaction:["scrolls","clicks","forms"]
});
//After this call Countly.add_consent("activity") to allow "sessions","events","views"
//or call Countly.add_consent("interaction") to allow "scrolls","clicks","forms"
//or call Countly.add_consent("crashes") to allow some separate feature

Security and Privacy

Parameter Tamper Protection

You can set an optional salt for the checksum calculation of request data which will be sent alongside each request, using the &checksum field. You will need to set the exact same salt on the Countly server (Management > Applications> Salt for checksum). When the salt on the Countly server is set, all requests would be checked for the validity of the &checksum field before being processed.

Asynchronous Synchronous
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "https://yourdomain.com";
Countly.salt = "your_salt";
// init after      

Other Features and Notes

SDK Config Parameters Explained

Here are the properties you may set up upon Countly initialization:

  • app_key - mandatory, app key for your app created in Countly
  • device_id - to identify a visitor, will be autogenerated if not provided
  • url - your Countly server URL - you may also use your own server URL or IP here
  • app_version - (optional) the version of your app or website
  • country_code - (optional) country code for your visitor
  • city - (optional) name of the city of your visitor
  • ip_address - (optional) IP address of your visitor
  • debug - output debug info into the console (default: false)
  • ignore_bots - option to ignore traffic from bots (default: true)
  • interval - set an interval for how often inspections should be made to see if there is any data to report and then report it (default: 500 ms)
  • queue_size - the maximum amount of queued requests to store (default: 1000)
  • fail_timeout - set the time to wait in seconds after a failed connection to the server (default: 60 seconds)
  • inactivity_time - the time limit after which a user will be considered inactive if no actions have been made. No mouse movement, scrolling, or keys pressed. Expressed in minutes (default: 20 minutes)
  • session_update - how often a session should be extended, expressed in seconds (default: 60 seconds)
  • max_events - maximum amount of events to send in one batch (default: 100)
  • max_breadcrumb_count - the maximum amount of breadcrumbs to store for crash logs (default: 100)
  • ignore_referrers - array with referrers to ignore (default: none)
  • salt - string salt for checksums (default: none)
  • ignore_prefetch - ignore prefetching and pre-rendering from counting as real website visits (default: true)
  • heatmap_whitelist - Array of trusted domains (as string) that can trigger heatmap script loading. By default the SDK whitelists your server url.
  • force_post - force using post method for all requests (default: false)
  • ignore_visitor - ignore this current visitor (default: false)
  • require_consent - Pass true if you are implementing GDPR compatible consent management. This would prevent running any functionality without proper consent (default: false)
  • utm - object instructing which UTM parameters to track (default: {"source":true, "medium":true, "campaign":true, "term":true, "content":true})
  • use_session_cookie - use cookies to track sessions (default: true)
  • session_cookie_timeout - how long until a cookie session should expire, expressed in minutes (default: 30 minutes)
  • remote_config - enable automatic remote config fetching, provide the callback function to be notified when fetching is complete (default: false)
  • rc_automatic_optin_for_ab - opts in the user for A/B testing while fetching the remote config (default: true)
  • use_explicit_rc_api - set it to true to use the explicit remote config API (default: false)
  • namespace - have a separate namespace for persistent data when using multiple trackers on the same domain
  • track_domains - Set to false to disable domain tracking, so no domain data would be reported (default: true)
  • headers - object to override or add headers to all SDK requests
  • storage - What type of storage to use, by default uses local storage and would fallback to cookies, but you can set values "localstorage" or "cookies" to force only specific storage, or use "none" to not use any storage and keep everything in memory
  • metrics - provide metrics override or custom metrics for this user. For more information on the specific metric keys used by Countly, check here.

Setting up properties on the Countly Web SDK is as follows (use your own server name if not using try.count.ly below):

Asynchronous Synchronous
Countly.debug = false;
Countly.app_key = "YOUR_APP_KEY";
Countly.device_id = "1234-1234-1234-1234";
Countly.url = "https://try.count.ly";
Countly.app_version = "1.2";
Countly.country_code = "LV";
Countly.city = "Riga";
Countly.ip_address = "83.140.15.1";

Example Integrations

Examples are located in examples folder.

Angular folder contains a basic Angular integration.

react folder contains a basic React integration.

Symbolication folder covers crash integrations and reporting.

mpa folder contains basic MPA integration.

example_apm is a simple APM integration.
example_apm_async is a simple async APM integration.
example_formdata is a simple integration to collect data from form data.
example_gdpr is a simple integration to show how giving consent works.
example_multi_instances is a simple demonstration for multi instancing the Countly SDK.
example_opt_out is a simple demonstration to show how to opt out from services.
examples_feedback_widgets is a simple integration for feedback widgets.
example_remote_config is a simple integration for remote config.
example_sync is a sample integration for sync operations.
example_web_worker is a sample for web worker.
example_async is a sample integration for async usage of the SDK.

SDK Storage and Requests

Countly Web SDK stores various information like device ID, request queue, session information and more in your device. This helps Countly to provide data consistency and enable convenience methods like offline mode.

The default storage location of user-specific data, except the session information, is your browser’s local storage. Information stored here is persistent, and as long as it was not erased or overwritten, it will stay on your device indefinitely. However, Countly allows you to change this behavior by selecting persistent cookies as the main storage option or choosing not to store any data at all, depending on your needs. These storage options are mutually exclusive, meaning only one option can be selected at a given time.

If cookies were selected as the main storage medium, persistent cookies have an expiration date and the information stored in them would be rendered obsolete after a while. In case of the session information, it is stored in session cookies and would expire when the tab or browser is closed. Lastly, if you decide not to store any information, all information will stay in memory and would be gone when the memory is cleared.

These options can be selected during the initialization:

Asynchronous Synchronous
//possible options are "localstorage", "cookies" and "none"
Countly.storage = "localstorage";

Automatically Fill User Data

In most cases, you won’t know anything about your users, yet you will still want to try to collect any data possible. We provide 2 helper methods for this exact reason.

Collect User Data From Filled Forms

This method will look into the forms filled out by your users and will try to gather data, such as names, email addresses, usernames, etc.
All forms will automatically be checked, but you have the option to provide a form element if you would like to collect data only from a specific form, or select a method multiple times for different forms. Also, if you are already providing data for users, then you would not want to overwrite it. You may set the third parameter as true to indicate that data found should be stored in custom properties.

Asynchronous Synchronous
//collect data from forms
Countly.q.push(['collect_from_forms']);

//collect data from specific form
Countly.q.push(['collect_from_forms', formElement]);

//collect from forms and report as custom user properties
Countly.q.push(['collect_from_forms', document, true]);

Passwords and other sensitive data will be omitted, however, if you would explicitly like to exclude some form input from being processed, just add the css class cly_user_ignore to that element. Oppositely, you may need to specify data from this input to be collected as the provided key by adding the prefixed css class cly_user_key. Therefore, if you would like to store data as a name, you should specify the cly_user_name css class.

<form method='post' name='test_form'>
  <!-- data will be checked in this input -->
<p><input type="text" name="e" value="myemail@mydomain.com"></p>
  
<!-- ignore this input -->
<p><input type="text" name="e" value="notmyemail@notmydomain.com" class="cly_user_ignore"></p>

<!-- get customfield by class -->
<p><input type="text" name="custom" id="custom" value="value" class="cly_user_key1"></p>

<p><input id="submit-form" type="submit" value="Submit"></p>

</form>

Collect User Data From Facebook

If your website uses the Facebook JavaScript SDK, you may use this helper method to automatically collect user data from their Facebook accounts. Select the method right after Facebook SDK initialization and optionally set the object with custom properties and graph paths for values on where to receive them.

Here is an example how to receive data from Facebook, including locations and time zones as custom properties.

<script src="https://connect.facebook.net/en_US/all.js"></script>
<script type="text/javascript">
FB.init({
  appId: '251676171676751',
  status: true,
  cookie: true,
  oauth: true
});

function CountlyGatherFBData(){
  Countly.collect_from_facebook({"location":"location.name", "tz":"timezone"});
};

FB.getLoginStatus(function(stsResp) {
  if(stsResp.authResponse) {
    CountlyGatherFBData();
  } else {
    FB.login(function(loginResp) {
      if(loginResp.authResponse) {
        CountlyGatherFBData();
      } else {
        alert('Please authorize this application to use it!');
      }
    });
  }
});
</script>

Attribution

When using Countly attribution analytics, you may also report conversions to the Countly server, e.g. when a visitor purchases an item or registers on your site.

If a user visits your website through the Countly campaign link, the campaign information will be automatically stored by default for this user and used when reporting conversion. If conversion has not yet been reported, the campaign information will be overwritten when that user visits through another campaign link. When you report the conversion, it would report the latest campaign user used to access your website.

However, you may also overwrite that data and provide any specific campaign ID for which you would like to report conversion.

Conversion will not be reported if there is no stored campaign data and you have yet to provide a campaign ID.

Note: Conversion for each user may only be reported once, all other conversions will be ignored for that same user.

Asynchronous Synchronous
//user stored conversion data
Countly.q.push(['recordDirectAttribution']);

//or provide campaign id yourself
Countly.q.push(['recordDirectAttribution', "MyCampaignID"]);

Track Link Clicks

This method will track clicks to specific links and will report events with the linkClick key as well as the link's text, ID, and URLs as segments.

All links will automatically be tracked by default for the entire page, but you may provide the parent node as a parameter for which to track link clicks.

Asynchronous Synchronous
Countly.q.push(['track_links']);

As soon as you include this one-liner coder, you will automatically be able to see the "linkClick" event in your dashboard as data flows in with the text, ID, and URLs as segments.

Track Form Submissions

This method will automatically track form submissions and collect form data. It will then input values in the form and report them as a Event with the formSubmit key.

All forms will automatically be tracked by default for the entire page, but you may provide the parent node as a parameter for which to track forms.

The second parameter controls whether to collect hidden inputs or not. Hidden inputs are not collected by default.

Asynchronous Synchronous
//will not collect hidden inputs
Countly.q.push(['track_forms']);

//will collect hidden inputs
Countly.q.push(['track_forms', null, true]);

Using the Web SDK in Webview

If you are going to use the Web SDK in the Webview of your app, there are prerequisites that must be checked to ensure it is fully functioning. There are no known iOS issues at this moment, but some specific settings need to be enabled for Android.

Ensure JavaScript has been enabled for your Webview

myWebView.getSettings().setJavaScriptEnabled(true);

Ensure local storage has been enabled

//change the path to where you want to store local storage data
myWebView.getSettings().setDomStorageEnabled(true);
myWebView.getSettings().setDatabaseEnabled(true);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
  myWebView.getSettings().setDatabasePath("/data/data/" + myWebView.getContext().getPackageName() + "/databases/");
}

If you would like to use Countly both in the native app and Webview, then you would maybe also like to match the device_id between them, so the transitions may be seamless and you may continue to track events and data from both for the same user.

In this case, there are a couple things you should do:

  1. Defer initializing Countly by putting the initialization code in some function.
  2. Do not track sessions in Webview as they are already tracked by the native app.
  3. Pass the device_id to Webview and run the initialization function once Webview has loaded.
<!--Countly script in webview-->
<script type='text/javascript'>
  var Countly = Countly || {};
  Countly.q = Countly.q || [];
  
  //provide countly initialization parameters
  Countly.app_key = "YOUR_APP_KEY";
  Countly.url = "http://yourdomain.com"; 
  
  //track views or anything else you want to track
  Countly.q.push(['track_pageview']);
  
  //function to initialize Countly
  function InitializeCountly(device_id) {
    
    //assign passed device_id
    Countly.device_id = device_id;
    
    //load countly script
    var cly = document.createElement('script'); cly.type = 'text/javascript'; 
    cly.async = true;
    //enter url of script here
    cly.src = 'https://cdn.jsdelivr.net/npm/countly-sdk-web@latest/lib/countly.min.js';
    cly.onload = function(){Countly.init()};
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(cly, s);
  };
</script>

Then, on the native app side, all you have to do is call the JavaScript functions in the Webview and pass on the device_id to complete this function.

Android iOS
myWebView.loadUrl("javascript:InitializeCountly('"+device_id+"');");

Tracking Users with Javascript Disabled

In some cases, a user might have JavaScript disabled, meaning normal ways of tracking those users will prove ineffective. In such a case, you may use the transparent 1px x 1px image hosted on your Countly server as reporting the URL and report all the same parameters as all the SDKs have been described here.

Assuming your Countly is hosted at domain.com and the app_key is "12345", the default setup should look like this:

<noscript><img src='http://domain.com/pixel.png?app_key=12345&begin_session=1'/></noscript>

Simply place it anywhere in your HTML code and it should only work if JavaScript is disabled, which means SDK tracking won't work.

This would then track how many total sessions there are in where users have JavaScript disabled. The server will attach the device_id as a no_js by default and add the user profile name as ‘No JS’, so you may easier identify how many users without JavaScript you have.

However, as mentioned before, this accepts any parameters as a normal SDK endpoint does. Thus, if you dynamically generate data via the server, you may also dynamically generate this URL to provide information that you have about the user. That might be the device_id parameter (for identification), OS, OS version, and any other metrics or information you have, as for example

<noscript><img src='http://domain.com/pixel.png?app_key=12345&device_id=test@test.com&begin_session=1&metrics={"_os":"Android", "_os_version":"4.1"}'/></noscript>

Multiple Trackers on the Same Domain

Sometimes you would like to track different parts of the same domain/website as separate applications.

If you just instantiate the Countly SDK in both places, but with different app IDs, then both of their continuous storages will clash storing information for both apps. There are times when this is exactly what you would like to have happen. Generally, it’s function will share a device_id, and although they might be storing requests together, each will be sent to a different app on the same server.

However, there are situations where you would like to keep them completely separate. To do so you will need to provide a namespace for the different trackers to keep their local storages from clashing.

Simply providing a namespace as the init option will suffice.

Asynchronous Synchronous
Countly.debug = false;
Countly.app_key = "YOUR_APP_KEY";
Countly.device_id = "1234-1234-1234-1234";
Countly.url = "https://try.count.ly";
Countly.namespace = "forum";

Countly.init();

Cross Website/Domain Tracking

You will need to use the same device_id value on the same user in both places to track the same user across different websites or domains.

This may be achieved by passing the device ID as a URL parameter when transferring a user from one website/domain to the other.

Simply take the device ID value from Countly.device_id and pass it as a URL parameter named cly_device_id as follows: http://newdomain.com/?cly_device_id=your-user-device-id.

Tracked Cookie List and Explanations

By default, Countly Web SDK uses local storage to keep information between page views, but if local storage is not available, Web SDK will try to fallback to cookies.

  • cly_queue - a queue of requests that will be made to the server and acknowledged
  • cly_event - a queue of events reported by SDK
  • cly_remote_configs - cached remote config from server
  • cly_ignore - ignore the user and do not track anything if this is set to true
  • cly_id - current user's device id
  • cly_cmp_id - last campaign id user came from
  • cly_cmp_uid - user identifier for last campaign
  • cly_session - timestamp when the last session started so we would not call begin_session on each page load
  • cly_token - token passed for retrieving heat map data from the server
  • cly_old_token - to detect token expiration and reset for action map data

Multi Instancing

You can initialize Countly multiple times at the same page with different app keys to send information to different apps you own and gather data with higher flexibility and precision. These new instances have all functionality of Countly but depending on your init configuration they would behave differently. You can attach different events to different Countly instances to send events to specific applications even from the same button trigger or much more. A simple integration example would be:

Asynchronous Synchronous

Countly = Countly || {};
Countly.q = Countly.q || [];

// initializing first instance, which will be global Countly
Countly.init({
  app_key: "YOUR_APP_KEY_1",
  url: "https://try.count.ly" //your server goes here
})
// report event to first app
Countly.add_event({
  key:"first_app"
});

// initialize second instance for another app 
Countly.q.push(["init", {
  app_key: "YOUR_APP_KEY_2", //must have different APP key
  url: "https://try.count.ly" //your server goes here
}])
// report event to second app asynchronously by passing app key as first argument
Countly.q.push(["YOUR_APP_KEY_2", "add_event", {
  key:"second_app"
}]);
    

SDK Internal Limits

If values or keys provided by the user exceeds certain internal limits, they will be truncated. Please have a look here for the list of properties effected by these limits.

You can override these internal limits during initialization.

Key Length

max_key_length - 128 chars by default. Keys that exceed this limit will be truncated.

Asynchronous Synchronous
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_key_length = 50;

Value Size

max_value_size - 256 chars by default. Values that exceed this limit will be truncated.

Asynchronous Synchronous
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_value_size = 12;

Segmentation Values

max_segmentation_values - 100 dev entries by default. Key/value pairs that exceed this limit in a single event will be removed.

Asynchronous Synchronous
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_segmentation_values = 67;

Breadcrumb Count

max_breadcrumb_count - 100 entries by default. If the limit is exceeded, the oldest entry will be removed from stored breadcrumbs.

Asynchronous Synchronous
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_breadcrumb_count = 45;

Stack Trace Lines Per Thread

max_stack_trace_lines_per_thread - 30 lines by default. Crash stack trace lines that exceed this limit (per thread) will be removed.

Asynchronous Synchronous
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_stack_trace_lines_per_thread = 23;

Stack Trace Line Length

max_stack_trace_line_length - 200 chars by default. Crash stack trace lines that exceed this limit will be truncated.

Asynchronous Synchronous
Countly.app_key = "YOUR_APP_KEY";
Countly.url = "YOUR_SERVER_URL";
Countly.max_stack_trace_line_length = 10;

Setting Maximum Request Queue Size

When you initialize Countly, you can specify a value for the queue_size flag. This flag limits the number of requests that can be stored in the request queue when the Countly server is unavailable or experiencing connection problems.

If the server is down, requests sent to it will be queued on the device. If the number of queued requests becomes excessive, it can cause problems with delivering the requests to the server, and can also take up valuable storage space on the device. To prevent this from happening, the queue_size flag limits the number of requests that can be stored in the queue.

If the number of requests in the queue reaches the queue_size limit, the oldest requests in the queue will be dropped, and the newest requests will take their place. This ensures that the queue doesn't become too large, and that the most recent requests are prioritized for delivery.

If you do not specify a value for the queue_size flag, the default setting of 1,000 will be used.

Asynchronous Synchronous
Countly.queue_size = 5000;

UTM Tags

If you are providing possible users links to your website, and if you would like to track those users who have clicked those links, you can do so by using some utm tags within your links and Countly web SDK would recognize and record these users for you.

The web SDK offers you two options to set these tags in your links. First you can use the default tags that has been set to be recognized by the SDK by default. These tags are 'source', 'medium', 'campaign', 'term' and 'content'. If you add any or all of these tags into your links as query Countly would recognize and record them accordingly. An example usage of the default tags is as follows:


// basic structure of a link that contains all available default tags
yourUrl + ?utm_source=someValue&utm_medium=someValue&utm_campaign=someValue&utm_term=someValue&utm_content=someValue

//or you can omit tags
yourUrl + ?utm_source=someValue&utm_campaign=someValue

// yourUrl is the url of your site and someValue is any string value you want to pass

Second option is to define your custom utm tags during the initialization of Countly and use those tags in your links. You have to provide a map of key value pairs where the key is the tag and the value is 'true' if you want the Countly to check for that key in your links:

Asynchronous Synchronous

Countly.app_key = "YOUR_APP_KEY";
Countly.device_id = "1234-1234-1234-1234";
// add your custom tags and set their value to true
Countly.utm = {tag1: true, tag2: true};

// then your links should look like this:
yourUrl + ?utm_tag1=someValue&utm_tag2=someValue

The values you have provided will be captured and stored as user properties, under the keys 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term' and 'utm_content' if you have used the default tags or as 'utm_yourCustomTag1', 'utm_yourCustomTag1'... if you have used custom tags, together with the corresponding user when a user clicks that link. This will enable you to segment users in all places around the dashboard, where granular data is used and segmentation capabilities are provided.

GA Adapter

If you are using Google Universal Analytics in your website and you would also like to integrate Countly to your project, GA Adapter plugin can help you send your Google Analytics data also to your Countly server without any extra integration. This plugin will recognize your Google Analytics data and convert them into Countly events and send it to your server, so you can observe your analytics data from your Countly dashboard with ease.

There are 3 steps you have to follow to enable the GA Adapter plugin:

  1. First integrate Countly into your project (You can reach details from here)
  2. Then declare the plugin script before your Google Analytics implementation block
  3. Finally, add 'CountlyGAAdapter();' into your Google Analytics snippet

A simple implementation would look something like this:

// ...
// ...    
// ... Countly implementation was here
// </script>

// write the correct path to the GA plugin depending on your project structure
<script src="../plugin/ga_adapter/ga_adapter.js"></script>

// Google Analytics implementation
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

// add this line into your google analytics snippet to use the GA plugin
CountlyGAAdapter();

// now Countly will recognize the GA commands like below and send them to your Countly server too
ga('create', 'UA-56295140-3', 'auto');
ga('send','event','category','action','label');
ga('send','pageview','page.html');
</script>

You can reach to an example implementation of this plugin from the following link:

GA Adapter Example

Running the SDK in Web Worker Context

SDK can be used in a dedicated Worker if needed. Currently Module Workers are not supported so instead the SDK should be imported by importScripts method. It would look something like this:

// Path or URL
importScripts("../path/to/countly.js");

After importing the Countly script in your worker, you can call the Countly methods as usual. However, Countly in Web Workers has limited availability. Currently only the manual tracking methods are supported. Moreover the SDK would not use persistent storage by default. But now it extends its storage methods with which you can provide your own storage logic to make things persistent.

A sample worker (let's say 'worker.js') could look like this:

importScripts("../path/to/countly.js"); // CDN is possible

const STORE={}; // in-memory storage for worker

Countly.init({
  app_key: "YOUR_APP_KEY",
  url: "https://your.domain.countly",
  debug: true,
  storage: {
    // getItem will recieve a string key param with which it should return the item under it
    getItem: function (key) {
      return STORE[key];
    },
    // setItem will recieve two params, a string key and a value of any type, then it should store the value under the key
    setItem: function (key, value) {
      STORE[key] = value;
    },
    // removeItem will recieve a string key with which it should erase the key and any data under that key
    removeItem: function (key) {
      delete STORE[key];
    }
  }
});

onmessage = function (e) {
  console.log(`Worker: Message received from main script:[${JSON.stringify(e.data)}]`);
  
  // Get an process messages to worker
  const data = e.data.data; const type = e.data.type;

  if (type === "event") { // you can send an event
    Countly.add_event(data);
  } else if (type === "view") { // you can record a view
    Countly.track_pageview(data);
  } else if (type === "session") { // you can manually control sessions
    if (data === "begin_session") {
      Countly.begin_session();
      return;
    }
    Countly.end_session(null, true);   
  }
}

In your website, an example communication can happen like this with the worker:

// create a worker
const myWorker = new Worker("worker.js");

// send messages to the Worker
function clickEvent() { // send event
  myWorker.postMessage({ type: "event", data: myEvent });
}
function recordView() { // track views
  myWorker.postMessage({ type: "view", data: "home_page" });
}
function beginSession() { // start a session
  myWorker.postMessage({ type: "session", data: "begin_session" });
}
function endSession() { // end a session
  myWorker.postMessage({ type: "session", data: "end_session" });
}

Opt In / Opt Out

The Countly SDK will always be opt in by default, meaning all tracking is enabled, but you may easily disable all tracking by selecting the opt_out method. It will persistently save settings to prevent tracking after page reloads. Then if you want to opt in again you can use opt_in method to resume tracking at next page load.

Asynchronous Synchronous
// to stop tracking user data at next page load
Countly.q.push(['opt_out']);

// to resume tracking user data at next page load
Countly.q.push(['opt_in']);

Disabling tracking for specific users with these methods can be sufficient for testing or situations where you decide to not track a specific user's information from now on. However, should you desire more granular feature control, checkout the User Consent section.

If you would like to have opt out selected by default, combine these methods with the initial setting ignore_visitor on the Countly init object.

FAQ

Can I integrate Countly Web SDK to my TypeScript Project

TypeScript is a strict syntactical superset of JavaScript. It helps you catch errors early by adding static typing to the language. It compiles down to basic JavaScript so it can be used anywhere JavaScript can run, whether Node.js or browser.

Countly Web SDK is written in basic JavaScript so it is compatible with your TypeScript projects by enabling allowJs in your project's tsconfig.json file. However as we use javascript features that can run in the browser, your project must also be runnable on the browser.

Ignoring your own bots

The default behavior of Countly Web SDK is to ignore bots crawling your site to provide you a more accurate user analytics data. However, Countly can't detect and block all bots crawling the internet as we use the userAgent string to detect bots and spammy bots can hide by providing conventional UserAgent. However, this is not the case for a bot that you have created your own.

To include your bots to be also ignored by the Countly Web SDK you have to include 'CountlySiteBot' in your userAgent string. This enables your SDK to recognize your bot as one of the bots to be ignored and the SDK would stop recording data for your bot.

Why aren’t I able to see AngularJS errors on the Countly dashboard?

AngularJs swallows errors by default. You will need to extend Angular's $exceptionHandler to call Countly.log_error(). For more information, see this blog post.

Incognito mode and ad blockers

Incognito mode prevents the browser from storing cookies and other site data. Cookies are small files that websites use to track user activity, remember preferences, and provide personalized experiences. In incognito mode, cookies are not saved. This means that any information our SDK saves in the browser's local storage would be gone the next time the user opens their incognito browser. These things include things like device ID and event/request queues. Each time a person visits a Countly integrated website in incognito mode, they would be perceived as a new user. This can be mitigated by having an authentication page on your website.

Ad blockers employ various techniques to identify and block unwanted requests, such as analyzing URL patterns, known tracking domains, or specific JavaScript code snippets. They can also detect and block requests made to known analytics or tracking services, making it possible for them to block requests made to the Countly server from our SDKs. This can result in incomplete or missing data, as the blocked requests may not reach the Countly server for processing. This is a possibility, but not all ad blockers would be blocking our requests, depending on their filter settings.

To mitigate this issue to a certain extent, you can enable force using the POST method for all requests with the SDK. Switching to a POST request can make it slightly harder for ad blockers to detect and block the request, as the parameters are sent in the request body instead of the URL. However, it doesn't make the request invisible or immune to blocking. Ad blockers can still analyze network traffic and inspect the request headers and payload, so if your server is Countly hosted and the domain name is in the filter of the ad blocker, it would still be blocked.

What Information is Collected by the SDK?

The data that SDKs gather to carry out their tasks and implement the necessary functionalities is mentioned in here

Comments

0 comments

Please sign in to leave a comment.