SDK development guide

Follow

Would you like to develop a new SDK for Countly? Then this guide is for you. Before starting, bear in mind that there are a lot of SDKs that Countly has already developed. Please check whether the SDK you are going to develop is not already available.

Initialization

To start making requests to the server, SDK needs 3 things: the URL of the server where you will be making requests, the app_key of the app for which you will be reporting, and your current device_id to uniquely identify this device.

Server URL

The SDK needs to provide the ability for the user to specify the URL for the server where their Countly instance is installed.

App Key

The App key should be provided by the SDK user. Your app should be created on the Countly server. After app creation, the server will provide an app key for the user. The same app key is used for the same app on different platforms.

Device ID

A device ID is required to uniquely identify a device or user. If you have some unique user ID which you are able to retrieve, you may use it. If not, you may provide platform-specific device identification (as Advertising identifier in Google Play Services on Android) or use existing implementations (as OpenUDID).

Other parameters

Of course, you may have other platform-specific parameters, such as adding a debug parameter, which provides different ways for device IDs to be generated. This allows for any other variables or settings upon initialization to be set.

However, there are some cross platform parameters that you will need to allow to set for the SDK users: * country_code - (optional) ISO country code for the user's country * city - (optional) name of the user's city * location - (optional) comma separate lat and lng values.

These parameters, if provided, should be added to all begin_session requests.

More can be found information here.

Countly Code Generator

If you would like to understand how SDKs work by generating mobile or web code for custom events, user profiles, crash reporting, and all other features that come with Countly in general, we suggest you use the Countly Code Generator, which is a point and click service that builds the necessary code for you.

Making Requests

The Countly server is a simple HTTP based REST API server and all SDK requests should be made to /i endpoint with two required parameters: app_key and device_id.

Other optional parameters need to be provided based on what this request should do. You may check list all the parameters that the Countly Server can accept in /i endpoint Server API reference.

In cases where some devices may be offline, etc., and requests should be queued, it is highly recommended you add a timestamp to each request, displaying when it was created.

Encoding URI components

Due to the possible use of the ‘&’ and ‘?’ symbols in encoded JSON strings, SDKs should encode uri components before adding them to the request and sending it to the server.

Using GET or POST

By default, the preferred method is to make GET requests for the Countly servers. However, there may be some length limitation for GET requests based on specific platform or server settings. Thus, the best practice is to make a POST request when the data reaches over 2,000 characters for a single request.

Before making each request, you will need to check if the data you are going to send is less than 2,000 characters. If so, use GET. If you have more characters, use POST.

Additionally, the SDK should be able to switch to post completely if a user should so specify in the SDK configuration/settings.

Parameter tampering

This is one of the preventive measures of Countly. If someone in the middle intercepts the request, it would be possible to change the data in the request and make another request with other data to the server, or simply make random requests to the server through the retrieved app_key.

To prevent this from happening, the SDK should provide the option to send a checksum alongside the request data. To do so, it should be possible for the developer to provide some random string as SALT to the SDK as parameters or configuration options.

If this SALT is provided, right before making the request, the SDK should take all the payload it is about to send (all the data after the ‘?’ symbol in GET requests, including the app_key and device_id or query string encoded body of POST requests) and make a sha256 hash of this data. You should also provide SALT, and append it as checksum256={hash}.

if(salt){
  data += "&checksum256=" + sha256Hash(data + salt);
}

If SALT is not provided, the SDK should make ordinary requests without any checksums.

Request queue

In some cases, users might be offline, thus they are not able to make requests to the server. In other cases, the server may be down or in maintenance, thus unable to accept requests. In both cases, the SDK should handle queuing and persistently storing requests made to the Countly server and should wait for a successful response from the server before removing a request from the queue.

Note that requests should be made in historical order, meaning you must also preserve the order of your queue.

Simple flow on how requests should appear as follows:

  1. Initiating request - either a new event reported or session call, etc.
  2. Creating a payload - take all the parameters (including the current timestamp) and the values needed for a request and generate a payload which will be included in the HTTP request
  3. This payload is inserted into the queue (First In, First Out)
  4. All updates to queue should be persistently stored. Based on the environment, you may directly use storage for the queue
  5. On some other thread there should be a request processor which takes the first request in the queue, applies the checksum if needed, determines the request type (GET or POST) based on the length, and makes the HTTP request 
    • if the request is successful, then it should be removed from the queue and the next request will be processed upon the next iteration
    • if the request failed, the request processor should have a cool down period, lasting a minute or so (configurable value), and it will then try the same request again until it is completed

There are multiple scenarios why a request might fail, so to ensure that the request is successfully delivered to the server SDK, you will need to assure the following has taken place:

  1. The user has internet connection
  2. The HTTP response code was successful (which is any 2xx code or code between 200 <= x < 300)

Additionally, the server replies with a JSON object, which has a property result with a success value. There may be different scenarios, such as when blocking a specific device, the requests from the server configuration with the case "Success" may change to some other value. Therefore, do not rely on the success value wholeheartedly. However, if there is no way to check the HTTP response code, you may check if the response contains JSON with the result property.

{"result":"Success"}

If the response code is within the required interval and the response text is a json object which has only one key "result" (the value of that entry does not matter), then it means the request was successfully delivered to the server and can be removed from the queue.

Queue size limit

We need to limit the queue size, so that it doesn’t overflow, and so that syncing up won’t take too long if some specific server is down for too long. This limit would be in the amount of stored queries, and this limit should be available for the end user to change as the SDK settings.

In case this limit is reached, the SDK should remove older queries and insert new ones. The default limit may change from what the SDK needs, but the suggested limit is 1,000 queries.

Session flow

App Initialization

When an app is initialized, the SDK should then send the begin_session=1 request. This same request should also contain metrics parameters with the maximum metrics described on /i page, which may be collected from this SDK-specific environment/language.

Session update

Each minute of the session should be extended by sending the session_duration request with the amount of seconds that passed since the previous session request (begin_session or session_duration, which ever was last).

Ending session

When the app exits, the SDK should send the end_session=1 request, including the session_duration parameter with how many seconds passed since the last session request (begin_session or session_duration, which ever was last).

Here are a few example requests generated by different session lengths:

2 min 30 second session 30 second session
begin_session=1&metrics={...}
session_duration=60
session_duration=60
end_session=1&session_duration=30

Session cooldown

In some cases, it is difficult to know for sure if a session has ended, such as with web analytics when a user is leaving the page, and whether they will visit another page or not. This is why there is a small cooldown time of 15 seconds. If the end_session request is sent and then the begin_session request is sent within 15 seconds, it will be counted as the same session, and the session duration will extend this session instead of applying it to the new one.

This makes it easier to call the end_session on each page unload without worrying about starting a new session if the user visits another page.

If you don't need this behavior, simply pass the ignore_cooldown=true parameter to all the session requests and the server will not extend the session. Rather, it will always count it as a new session.

The 15-second cooldown is a default value and may be configured on the server, so don't rely on it actually being 15 seconds.

Recording time of data

In order to properly report and process data (especially queued data), you should also provide the time when the data was recorded. You will need to provide 3 parameters with each request:

  • timestamp: 13-digit UTC millisecond unique timestamp of the moment of action
  • hour: Current user local hour (0 - 23)
  • dowCurrent user day of the week (0-sunday, 1 - Monday, ... 6 - Saturday)
  • tz: Current user time zone in minutes (120 for UTC+02, -300 for UTC-05)

As multiple events may be combined in a single request, you should also provide these parameter automatically in every event object.

The suggested millisecond timestamp should be unique, meaning if events were reported in the same timestamp, the SDK should update the millisecond timestamp in the order in which the events were reported. The pseudo code to the unique millisecond timestamp could appear as follows:

//variable to hold last used timestamp
lastMsTs = 0;

function getUniqueMsTimestamp(){
	//get current timestamp in miliseconds
	ts = getMsTimestamp();
  
  //if last used timestamp is equal or greater
  if(lastMsTs >= ts){
  	//increase last used
    lastMsTs++;
  }
  else{
  	//store current timestamp as last used
  	lastMsTs = ts;
  }
  //return timestamp
  return lastMsTs;
}

If it’s impossible to use a millisecond timestamp on a specific platform, you may also use a 10-digit UTC seconds timestamp.

API of the SDK

Depending on the SDK’s environment/language there could be a different set of features supported. Some of these features may be supported on any platform, whereas others are quite platform-specific. For example, a desktop app type may not be providing telecom operator information.

Note that function and argument namings are only examples of what it could be. Try to follow your platform/environment/language best practices when creating and naming functions and variables.

Here is a list of things your SDK could support:

Core features

Core features are the minimal set of features that the SDK should support, and these features are platform independent.

Initialization

In its official SDKs, Countly is used as a singleton object or basically an object with a shared instance. Still, there are some parameters that need to be provided before the SDK can work. Usually there is an init. method which accepts the URL, app key, and device_id (or the SDK generates it itself, if it’s not provided):

Countly.init(string url="https://try.count.ly", string app_key, string device_id, ...)

Session Flows

Most of the official SDKs implement automatic session handling, meaning SDK users don't need to separately bother with session calls. However, it is good practice to provide a way to disable automatic session handling and allow SDK users to make session calls themselves through methods such as:

  • Countly.begin_session()
  • Countly.session_duration(int seconds)
  • Countly.end_session(int seconds)

Here is the documentation showing how you may report sessions through our API.

Device metrics

Metrics should only be reported together with the begin_session=1 parameter on every session start. Collect as many metrics as possible or allow some values to be provided by the user upon initialization. Possible metrics are listed in the API Reference.

One thing that we should agree on is identifying platforms with the same string over all SDKs, so here is the list of how we would suggest identifying platforms for the server through the _os metric.

  • Android - for Android
  • BeOS - for BeOS
  • BlackBerry - for BlackBerry
  • iOS - for iOS
  • Linux - for Linux
  • Open BSD - for Open BSD
  • os/2 - for OS/2
  • macOS - for Mac OS X
  • QNX - for QNX
  • Roku - for Roku
  • SearchBot - for SearchBots
  • Sun OS - for Sun OS
  • Symbian - for Symbian
  • Tizen - for Tizen
  • tvOS - for Apple TV
  • Unix - for Unix
  • Unknown - if operating system is unknown
  • watchOS - for Apple Watch
  • Windows - for Windows
  • Windows Phone for Windows Phone

SDK Metadata

The SDK should send the following metadata with every begin_sessionrequest.

  • SDK name:

Query String Key: sdk_name Query String Value: [language]-[origin]-[platform] Example: &sdk_name=objc-native-ios

  • SDK version:

Query String Key: sdk_version Query String Value: SDK version as string Example: &sdk_version=16.10

Events

Events (or custom events) are the basic Countly reporting tool which reports when something has happened in the app. Someone clicked a button, performed a specific action, etc. All of these examples could be recorded as an event.

Events should be provided by the SDK user who knows what's important for the app to log. Also, events may be used to report some internal Countly events starting with the [CLY]_ prefix, which vary per feature implementation on different platforms.

An event must contain key and count properties. If the count is not provided, it should default to 1. Optionally, a user may also provide the sum property (for example, in-app purchase events), the dur property for recording some duration/period of time and segmentation as a map with keys and values for segmentation.

More on event formatting may be found in the API Reference.

Here are a few example events:

User logged into the game * key = login * count = 1

User completed a level in the game with a score of 500 * key = level_completed * count = 1 * segmentation = {level=2, score=500}

User purchased something in the app worth 2.99 on the main screen * key = purchase * count = 1 * sum = 2.99 * dur = 30 * segmentation = {screen=main}

As you can imagine, your SDK should provide methods to cover these combinations, either by defaulting values or by function parameter overloading, etc.:

  • Countly.event(string key)
  • Countly.event(string key, int count)
  • Countly.event(string key, double sum)
  • Countly.event(string key, double duration)
  • Countly.event(string key, int count, double sum)
  • Countly.event(string key, map segmentation)
  • Countly.event(string key, map segmentation, int count)
  • Countly.event(string key, map segmentation, int count, double sum)
  • Countly.event(string key, map segmentation, int count, double sum, double duration)

Note: count value defaults to 1 internally if not specified.

Timed Events

In short, you may report time with the dur property in an event. It is good practice to allow the user to measure some periods internally using the SDK API. For that purpose, the SDK needs to provide the methods below:

  • startEvent(string key) - which will internally save the event key and current timestamp in the associative array/map.
  • endEvent(string key, map segmentation, int count, double sum) - which will take the event starting timestamp by the event key from the map, get the current timestamp and calculate the duration of the event. It will then fill it up as a dur property and report an event, just as you would report any regular event.
  • endEvent(string key) - which will simply end the event with a 1 as the count, 0 as the sum, and nil as the segmentation values.

If possible, the SDK may provide a way to start multiple timed events with the same key, such as returning an event instance in the method and then calling the end method on that instance.

If not, the following calls should be ignored: 1. events which have already started 2. events which have attempted to start again 3. events which have already ended 4. events which have attempted to end. Otherwise, they will provide an informative error.

User Details

Your SDK does not need to have a platform-specific way to receive user data if it isn’t possible on your platform. However, you will need to provide a way for a developer to pass this information to the SDK and send it to the Countly server.

To do so, you may create a method to accept an object with key/regarding the user, which are described here, or provide a parameterized method to pass the information regarding the user. Note that all fields are optional.

Additionally, there could be custom key values added to the user details. In this case, you would need to provide a means to set them:

  • Countly.user_details(map details)
  • Countly.user_custom_details(map custom_details)

You may find more information on what data may be set for a user by following this link.

Modifying custom data properties

You should also provide an option to modify custom user data, such as by increasing the value on the server by 1, etc. Since there are many operations you could perform with that data, it is recommended to implement a subclass for this API, which may be retrieved through the Countly instance.

The standard methods that should be provided by the SDK are as follows (provided as a pseudo code, naming conventions may differ from platform to platform):

  • Countly.userData.set(string key, string value)
  • Countly.userData.setOnce(string key, string value)
  • Countly.userData.increment(string key)
  • Countly.userData.incrementBy(string key, double value)
  • Countly.userData.multiply(string key, double value)
  • Countly.userData.max(string key, double value)
  • Countly.userData.min(string key, double value)
  • Countly.userData.push(string key, string value)
  • Countly.userData.pushUnique(string key, string value)
  • Countly.userData.pull(string key, string value)
  • Countly.userData.save() //send data to server

Notewhen reporting to the server, assure the push, pushUnique, and pull parameters can provide multiple values for the same property as an array.

Here is more information on how to report this data to server.

Custom Device ID / Changing Device ID

There are 3 main cases where the SDK user might like to provide a custom device ID to identify a device/user.

1. Tracking the same user across multiple devices

In this case, the app developers will need to provide their own way to identify users upon initialization. The Countly SDK needs to provide a way to set the custom device ID upon initialization and store it persistently for the next session use. If there is an already stored device ID, the Countly SDK should primarily use the stored one, unless forced to do otherwise.

2. Tracking multiple users on the same device

In addition to initialization, developers may need to change the device ID while the app is running. For example, when an end user signs out and another end user signs in. In this case, the Countly SDK needs to provide a way to change the device ID at any point while the app is running. It should replace the internally used device ID with the new one, and use it for all new requests, persistently storing it for further sessions. The Countly SDK should follow these steps:

  • Add currently recorded, but not queued, events to the request queue
  • End the current session
  • Clear all started timed-events
  • Change the device ID and store it persistently for further session use
  • Begin a new session with the new device ID

3. Tracking an unauthenticated user who later becomes authenticated

Developers may need to change a device ID to their own internal user ID and merge the server-side data previously generated by a user while he/she was unauthenticated. It is similar to Case 2, but the Countly SDK will need to merge the data on the server as well. In order to make a proper transition, the Countly SDK should follow these steps:

  • Temporarily keep the current device ID
  • Change the device ID and store it persistently for further session use
  • Use the old_device_id API with the temporarily kept, old device ID to merge the data on the server
  • No need to end and restart the current session or clear started timed-events

To summarize, the Countly SDK should provide a proper way to change device IDs for all 3 cases.

Note: If a new and current device ID are exactly the same, then the Countly SDK must ignore this change call.

Recording location

There are 4 location related parameters that can be set in a Countly SDK. It is "country code", "city", "location"(GPS coordinates), "IP" address. Those could be set with functions similar to these:

  • Countly.country_code(string country_code)
  • Countly.city(string city)
  • Countly.location(double latitude, double longitude)
  • Countly.ip_address(string ip_address)

City should always be paired together with country, you can not set only one of them.

When empty "location" is sent, is interpreted as "disable location tracking for this device ID".

Empty country code, city and IP address can not be sent.

Additional parameters

There are also optional, additional parameters. If you can get them on your platform, then you may append them to any/every request. However, if you can’t, it might be a good idea to allow the SDK user to optionally provide such values.

If values are not provided, the Countly server will try to determine them automatically, based on all the other provided data.

Here is more information on possible additional API parameters.

Reserved Segmentation keys

Currently, there are 9 segmentation keys that are reserved for Countly’s internal use. They should be ignored when the SDK user provides them as segmentation for any functionality. The list of keys is as follows:

  • name
  • segment
  • visit
  • start
  • bounce
  • exit
  • view
  • domain
  • dur

Push Notifications

Push notifications are platform-specific and not all platforms have them. However, if your platform does, you would need to register your device to the push notification server and send the token to the Countly server. For more information, please click here for API calls.

From the SDK API point of view, there could be one simple function to enable push notifications for the Countly server:

Countly.enable_push()

Crash reporting

On some platforms the automatic detection of errors and crashes is possible. In this case, your SDK may report them to the Countly server, and just as with other similar functions, this is also optional. If a crash report is not sent, it won't be displayed on the dashboard under the Crashes section. Here is more information on Crash reporting parameters that you may use in your SDK.

In regard to crashes, all information, except the app version and OS, are optional, but you should collect as much information about the device as possible to assure each crash may be more identifiable with additional data. You should also provide a way for users to log errors manually (for example, logging handled exceptions which are not fatal).

Basically, for automatically captured errors, you should set the _nonfatal property to false, whereas on user logged errors the _nonfatal property should be true. You should also provide a way to set custom key/values to be reported as segments with crash reports, either by providing global default segments or setting separately for automatically tracked errors and user logged errors.

Additionally, there should be a way for the SDK user to leave breadcrumbs that would be submitted together with the crash reports. In order to collect breadcrumbs as logs, create an empty array upon initialization and provide a method to add breadcrumbs as strings into that array as elements for log. Also, in the event of a crash, concatenate the array with new line symbols and submit under the _logs property. There is no need to persistently save those logs on a device, as we would like to have a clean log on every app start.

The end API could look like this (but it should be totally based on the specific platform error handling):

  • Countly.enable_auto_error_reporting(map segments)
  • Countly.log_handled_error(string title, string stack, map segments)
  • Countly.log_unhandled_error(string title, string stack, map segments)
  • Countly.add_breadcrumb(string log)

Views

Reporting views would allow you to analyze which views/screens/pages were visited by the app user as well as how long they spent on a specific view. If it is possible to automatically determine when a user visits a specific view in your platform, then you should provide an option to automatically track views. Also, it is important to provide a way to track views manually. Here is more information on view-tracking APIs.

Let's start with manual view tracking, as it should be available on any platform. First, you will need to have 2 internal private properties as string lastView and int lastViewStartTime. Then, create an internal private method reportViewDuration, which checks if lastView is null, and if not, it should report the duration for lastView by calculating it based off the current timestamp and lastViewStartTime.

After those steps, provide a reportView method to set the view name as a string parameter inside this method call reportViewDuration to report the duration of the previous view (if there is one). Then set the provided view name as lastView and the current timestamp as lastViewStartTime. Report the view as an event with the visit property and segment as your platform name. Additionally, if this is the first view a user visits in this app session, then also report the start property as true. You will also need to call reportViewDuration with the app exit event.

After manual view tracking has been implemented, you may also implement automatic view tracking (if it is available on your platform). In order to implement automatic view tracking, you will need to catch your platform's specific event when the view is changed and call your implemented reportView method with the view name.

Additionally, you will need to implement enabling and disabling automatic view tracking, as well as status checking, despite whether automatic view tracking is currently enabled or not.

The pseudo code to implement view tracking could appear as follows:

class Countly {
    String lastView = null;
    int lastViewStartTime = 0;
    boolean autoViewTracking = false;
    
    private void reportViewDuration(){
        if(lastView != null){
             //create event with parameters and 
             //calculating dur as getCurrentTimestamp()-lastViewStartTime
        }
    }
    
    void onAppExit(){
        reportViewDuration();
    }
    
   void onViewChanged(String view){
      if(autoViewTracking)
          reportView(view);
   }
    
    public void reportView(String name){
        //report previous view duration
        reportViewDuration();
        lastView = name;
        lastViewStartTime = getCurrentTimestamp();
        //create event with parameters without duration
       // duration will be calculated on next view start or app exit
    }
    
    public void setAutoViewTracking(boolean enable){
        autoViewTracking = enable;
    } 
    
    public boolean getAutoViewTracking(){
        return autoViewTracking;
    }
}

Additionally, if your platform supports actions on view, such as clicks, you may report them as well. Here is more information on reporting actions for views.

Star Rating

If possible, the SDK should provide a simple 1 through 5 star-rating interface for receiving user feedback about the application. The interface will have a simple message explaining its purpose, a 1 through 5-star meter for receiving users’ ratings, and a dismiss button, in case the user does not wish to give a rating. This star rating has nothing to do with App Store/Google Play Store ratings and reviews. It is just for getting a brief feedback from users to be displayed on the Countly dashboard.

After a user gives a rating, a reserved event will be recorded with [CLY]_star_ratingas the key and following as the segmentation dictionary:

  • platform: on which the application runs
  • app_version: application's version number
  • rating: user's 1-to-5 rating

If a user dismisses the star-rating dialog without giving a rating, an event will not be recorded. The star-rating dialog's message and dismiss button title may be customized using the properties on the initial configuration object.

CountlyConfiguration.starRatingMessage = "Custom Message";
CountlyConfiguration.starRatingDismissButtonTitle = "Custom Dismiss Button Title";

If not explicitly set, the message should read, "How would you rate the app?" and the dismiss button title will read, "Dismiss", or one of the corresponding localized versions depending on the device’s language.

The star-rating dialog may be displayed in 2 ways:

1. Manually by the developer

The star-rating dialog will be displayed when the developers call the specified method, such as askForStarRating. Optionally, there will be a callback method indicating the user's 1-to-5 rating value for the developer in the event the developer would like to use the user's rating.

Countly.askForStarRating(callback);

There is no limit on how many times the star-rating dialog may be manually displayed.

2. Automatically, depending on the session count

The star-rating dialog will be displayed when the application's session count reaches a specified limit; once for each new version of the application. The SDK should keep track of the session count for each app version locally and compare it to the specified count on each app launch. This session count limit may be specified upon initial configuration.

CountlyConfiguration.starRatingSessionCount = 5;

Once the star-rating dialog has been displayed automatically, it will not be displayed again unless there is a new app version.

Upon initial configuration, there should be an optional flag called starRatingDisableAskingForEachAppVersion to force the star-rating dialog to be displayed only once per app lifetime, instead of for each new version.

CountlyConfiguration.starRatingDisableAskingForEachAppVersion = false;

GDPR compatibility

GDPR compatibility is about dividing the SDK functionality into different features and allowing SDK users to ask for consent when using these features. Once consent has been given, only then may the SDK send newly collected (after consent is given) data to the server.

Additionally, the user may change his/her mind during the app run and opt out of some features. Therefore, the SDK should be able to disable these features on run time.

Mostly persistence consent settings should be handled by the client and not by the SDK. However, it may do so if the SDK is required to handle it.

Initial Configuration

Upon initial configuration, there should be a flag (e.g. requiresConsent) to inform the SDK that it requires consent before doing anything.

If this configuration is set, the SDK should not send any data to the server. Even if specific SDK methods (reporting errors, recording custom events, etc.) are manually called, these calls should be ignored until consent is given.

Exposing Available Features for Consent

The SDK should expose all the features it supports for consent in the form of a method, static properties, or constant strings. The developer may check which features are available during development or when creating a consent form UI.

The following are the currently available features:

* sessions - tracking when, how often, and how long users use your app/website

* events - allow custom events to be sent to the server

* locationallows location information to be sent. If consent has not been given, the SDK should force send an empty location upon begin_sessionto prevent the server from determining location via IP addresses

* views - allow tracking of which views/pages a user visits

* scrolls - allow user scrolls for scroll heatmap to be tracked

* clicks - allow user clicks for heatmaps as well as link clicks to be tracked

* forms - allow user's form submissions to be tracked

* crashes - allow crashes, exceptions, and errors to be tracked

* attribution - allows the campaign from which a user came to be tracked

* users - allow collecting/providing user information, including custom properties

* push - allows push notifications

* star-rating - allows their rating and feedback to be sent

* accessory-devices - allow accessories or wearable devices, such as Apple Watches, etc. to be detected

Note that the available features may change depending on platform.

Feature Grouping (optional)

The SDK may also provide features grouping, allowing existing features to be put into groups and the use of these groups to give, cancel, and check consent.

For example, a client may put "sessions","events", and "views" into one group called "activity". After which, they give their consent to "activity", the SDK should then automatically give consent to all underlying features.

Countly.group_features({
    activity:["sessions","events","views"],
    interaction:["scrolls","clicks","forms"]
});

Giving Consent

The SDK should have a method for giving consent, and this method should have feature names or groups as parameters. It may accept a single feature or group as well as multiple features or groups in the form of an array or variable arguments, depending on the SDK language and environment.

At any time during app run, a user may give consent to more features after starting the SDK.

Upon receiving consent, the SDK should immediately begin collecting data allowed by the provided feature(s) and also begin sending the consent approval to the server in the form of consent= {"feature":true}. For the exact feature names, refer to the list above. For example, if features are crashes and users, then the request should contain consent = {"crashes":true,"users":true}. This may be a separate request, or it may be attached to any other SDK request.

If someone attempts to give consent for a second time, the SDK should ignore it.

Checking Consent Status

There should also be a method to check the current consent status for the SDK, returning true if consent was given, and false if not. Checking status for groups should return trueonly if all the underlying features return true.

Removing Consent

The SDK also needs to provide a method to remove consents. It should support the same parameter options as the consent giving method.

Upon receiving the request to remove consent, the SDK should immediately stop collecting data allowed by the provided feature(s) and also send consent removal to the server in the form of consent= {"feature":false} . For example, if the features are crashes and users, then the request should contain consent={"crashes":false,"users":false}.

This may be a separate request, or it may be attached to any other request.

Depending on the SDK structure, the SDK may sync existing requests in the queue. Or, it may ignore requests in the queue and never send them or remove them from the queue.

Both giving consent and removing consent may be combined in a single request as well. If, for example, consent was given for crashes but removed from users, then the request should contain consent={"crashes":true,"users":false}.

Common Flow with Required Consent

1) The Developer sets requiresConsent upon initial configuration: config.requiresConsent = true;.

2) The Developer starts the Countly SDK, but the Countly SDK does nothing related to user tracking, no information is queued or sent to the server.

3) The Developer handles permissions, such as showing the popup to the user and asking for consent as well as persistently storing user choices.

4) Upon receiving consent from a user or storage, the Developer calls the giveConsent method of the Countly SDK, feature names, or groups, depending on the permissions they managed to get.

5) The Countly SDK starts relevant features and also sends a request to the Countly Server (consent={"feature":true}).

6) The Countly SDK checks if FeatureNamehas already been passed to the giveConsent method, and it ignores all repetitive calls.

7) The Countly SDK does not persistently store the status of given consents and expects the developer to call the giveConsent method on each app launch, just as with starting the SDK.

8) If the app user changes his/her mind about consents at a later time, the developer may reflect this to the Countly SDK using the removeConsentmethod, passing feature names or groups.

9) The Countly SDK stops the relevant features and also sends a request to the Countly Server (consent={"feature":false}).

10) The Countly SDK checks if feature names or groups have already been passed to the removeConsent method, and it ignores all repetitive calls. Or, it attempts to cancel consents never given at all.

Remote Config

Automatic Fetch

The Remote Config feature allows app developers to change the behavior and appearance of their applications at any time by creating or updating custom key-value pairs on the Countly Server.

First off, interaction with the Countly Server for the Remote Config feature should be done after you have checked the Remote Config API documentation.

There should be a flag upon initial config to enable the automatic fetching of the remote config upon SDK start. If this flag is set, the SDK will automatically fetch the remote config from the server and store it locally. A locally stored remote config should reflect the server response as is, overwriting any existing remote config. No merging or partial updating. Automatic fetching will be performed only upon SDK start, not with every begin session. There should also be a callback on initial config to inform the developer about the results of automatic fetching the remote config.

e.g. config.enableRemoteConfig = true;

Manual Fetch

There should be a method/function to fetch the remote config manually anytime the developer would like. Just like with automatic fetch, this method will fetch the remote config from the server and store it locally. A locally stored remote config should reflect the server response as is, overwriting any existing remote config. No merging or partial updating. This method should take a callback argument to inform the developer about the results of manually fetching the remote config. Callback on initial config should not be affected by manual fetchings, as it is for automatic fetchings only.

e.g. updateRemoteConfig(callback(){ })

Getting Values

There should be a method to get remote config values for a given key. It will return the value for a given key. If the key does not exist, or the remote config has yet to be fetched, this method should return nil or null or however the platform handles the absence of values. If the server is not reachable, this method should return the last fetched and locally stored value if available.

e.g. remoteConfigValueForKey(key)

Keys and Omit Keys

There should be 2 additional methods for manual fetching: one for specifying which keys will be updated and one for specifying which keys will be ignored.

These methods should take an array of keys as argument, in addition to callbacks, and send requests with keys= or omit_keys= query strings. For the result of these requests, only the keys in the response should be updated in local storage, not a complete overwrite as with an automatic or standard manual fetch.

e.g. updateRemoteConfigForKeysOnly(keys, callback(){ }) e.g. updateRemoteConfigExceptKeys(keys, callback(){ })

Example case: Local storage reflecting server as is (after an automatic or manual fetch):

{
  "a": "x",
  "b": "y",
  "c": "z",
}

Calling update for specified keys only:

updateRemoteConfigForKeysOnly(["a"], callback(){ });

Response:

{
  "a": "xx",
}

Local storage:

{
  "a": "xx",
  "b": "y",
  "c": "z",
}

Consents

In cases where the consentRequiredflag is set upon initial config, fetching the remote config (automatic or manual) will be performed only when there is at least one consent given. Additionally, if sessionsconsent is given, the remote config requests will have metricsinfo, similar to begin session requests.

Device ID Change

After a device ID change, the locally stored remote config should be cleaned, and an automatic fetch should be performed if enabled upon initial config.

Salt

Remote config requests need to include the checksum if enabled upon initial config. As with all other requests, only the query string part will be used to calculate hash.

Application Performance Monitoring

Countly server supports multiple metrics for performance monitoring. They are divided into 3 groups:

  • Custom traces
  • Network request traces
  • Device traces

Trace keys

In some of the exposed functionality, developers can provide the metric key for identifying the tracked thing. There are some requirements that the key must meet. Those requirements will be enforced on the API endpoint, and if the key will be deemed invalid, the trace will be dropped. Therefore it's important to catch invalid keys and warn about them on the SDK side.

In short, the keys have to be 32 or fewer characters long and have to correspond to the following regex:

/^[a-zA-Z][a-zA-Z0-9_]*$/

Unwrapping those rules, we can create the following list:

  • The key has to be 32 or fewer characters long
  • The only characters that the key can contain are Latin letters (uppercase and lowercase), numbers and underscores
  • The key can start only with a letter

API Calls

Currently, there is no batching functionality for APM requests. Every APM data point/trace should be put in the request queue immediately after it is acquired. So that would be after a network request is finished, after a custom trace has ended, every time the device goes to the background or foreground, etc.

APM data is combined into a single JSON object which is set to the "apm" param. So a basic request would look similar to:

/i?app_key=app_key
&device_id=device_id
&dow=dow
&hour=hour
&timestamp=timestamp
&apm={ _apm_params }

Custom traces

These are used as a tool to measure the performance of some running task. At the basic level, this function is similar to timed events. The default metric that gets tracked is the "duration" of the event.  There should be a "startTrace" and "endTrace" call, which start and end the tracking. When ending the tracking, the developer has the option of providing additional metrics. Those metrics are String and Integer/Numeric pairs.

 Sample custom trace API request:

/i?app_key=xyz
&device_id=pts911
&apm={"type":"device",
"name":"forLoopProfiling_1",
"apm_metrics":{"duration": 10, “memory”: 200},
"stz": 1584698900000,
"etz": 1584699900000}
&timestamp=15847000900000

Network traces

Sample network trace request:

/i?app_key=xyz
&device_id=pts911
&apm={"type":"network",
"name":"/count.ly/about",
"apm_metrics":{"response_time":1330,"response_payload_size":120, "response_code": 300, "request_payload_size": 70},
"stz": 1584698900000,
"etz": 1584699900000}
&timestamp=1584698900000

Device traces

Sample device trace request:

/i?app_key=xyz
&device_id=pts911
&apm={"type":"device",
"name":"
app_start",
"apm_metrics":{"duration": 15000},
"stz": 1584698900,
"etz": 1584699900}
&timestamp=1584698900
Was this article helpful?
0 out of 0 found this helpful

Looking for help?