Customer Experience Script

This documentation explains how to integrate your webpages/portal with Pixel Customer Experience Script (CX Script)

The CX Script is a JS script that runs in the client's browser. The CX Script gathers timings and metrics for any page on which it is placed, and additionally serves to correlate the user between sites on which it is placed. It also allows HTML pages to pass custom data fields. The metrics collected by the script along with Custom Fields sent by the page are sent to Pixel as Client Side Events (CSE) or simply Events.

Other Topics

Customer Experience Script Repo

NOTE : To allow correlation of various ID's in use throughout UHG, the CX Script must be called from the pixel repo servers using the above URLs.
Environment URL Description
Stage https://repo-stg.rakanto.com/rakanto/cx/cx.js Point to this URL for your lower environment
Prod https://repo.rakanto.com/rakanto/cx/cx.js Point to this URL for your production deployment

A Simple Example

  • Make remote calls to the Pixel Repo URLs. Do not download the script or place in your source control.

  • Make CX Script the first thing in your HTML. This helps the CX Script to collect accurate metrics on the page.

    <html>
      <head>
        <script type="text/javascript">
          (function (cx, darids, demarcs) {
            window['RakantoObject'] = 'Rakanto';
    
            window['Rakanto'] = window['Rakanto'] || function () {
              (window['Rakanto'].q = window['Rakanto'].q || []).push([Date.now()].concat(Array.prototype.slice.call(arguments)))
            }, window['Rakanto'].l = Date.now(), window['Rakanto'].demarcs = demarcs;
    
            a = document.createElement('script'),
            a.src = cx,
            a.id = 'rakanto',
            a.async = true,
            (darids)? a.setAttribute('data-px-darids', darids):null,
            m = document.getElementsByTagName('script')[0],
            m.parentNode.insertBefore(a, m);
          })('https://repo-stg.rakanto.com/rakanto/cx/cx.js', 'UHG.Optum.Pixel.CXScriptExample');
        </script>
      </head>
    
      <body>
        <h1>Pixel Customer Experience Script - A simple example</h1>
      </body>
    </html>
    

Just copy the above code in an HTML file and open it in a browser. That's it.

UHG.Optum.Pixel.CXScriptExample in the example above is a Digital Analytics Reporting ID (DARID). Details about DARIDs can be found in later sections.

Viewing collected data in Kibana

Data collected by the CX Script can be viewed and analyzed in the Pixel Self Service Kibana.

STAGE -- Click to View the CSE Self Service STAGE Kibana Desktop

PRODUCTION -- Click to View the CSE Self Service PRODUCTION Kibana Desktop

(Login with your MSID and Password)

Filter in Kibana on YOUR-DARID. Details about DARIDs can be found in later sections.

Note : Please reach out to Pixel team if you need help accessing/using the above Kibanas

Universal Browser ID UBRID

  • The Universal Browser ID is an Anonymous browser Id embedded in the script and stored in a cookie.
  • UBRID represents an Instance of the browser
    • Chrome and Firefox running in same computer will generate different UBRIDs
    • Chrome running in regular mode and in incognito mode will generate different UBRIDs in the same computer
  • Shared by ALL users of that browser instance on that device
    • If Jane Doe and her husband use the same browser to log in to their respective accounts, they both will get the same UBRID assigned

How UBRID works in the browser

UBRID is a unique ID embedded in CX Script source code, this allows it to act as a "Super Cookie".

UBRID is generated in the following format

<UBRID> ::= <Version>-<ServerID>-<PID>-<WorkerPID>-<WorkerTimeStamp>-<SeqNum>-<CurrentTimestamp>

<Version> ::= "v2.0"
<ServerID> ::= MD5 Hash of the name of the server generating UBRID
<PID> ::= Process Id of the Parent Process that generates the UBRID
<WorkerPID> ::= Process Id of the child Process that generates the UBRID.  A parent process usually has 1 to 8 worker processes
<WorkerTimeStamp> ::= The timestamp at which the worker Sequence number started (Please refer to SeqNum below for more information).  This timestamp is UNIX timestamp in milliseconds
<SeqNum> ::= A running number from 1 to 1,000,000,000,000.  The Sequence number is specific to worker process.
             The sequence number will reset to 1 when the process restarts or when the counter reaches 1,000,000,000,000.
             The WorkerTimeStamp will be set to the current time when the sequence number resets.
             In other words, WorkerTimeStamp is the time stamp when the sequence number was 1 for the worker process
<CurrentTimestamp> ::= Current UNIX timestamp in milliseconds

Example : v2.0-7c1b733fa81543ed7af89b72687005d6-20613-20617-1636659751012-0000076890-1637253735012

Browser gets the CX Script from the Pixel Repo Server that runs on the Rakanto domain UBRID is also set in a cookie in the browser by Rakanto Server. Expiration for that cookie is two years

The expiration of the script itself is set at 30 minutes. So every 30 minutes, the browser will check whether the script has changed. This means whenever a new script version is released, the browser will not have an old version for more than 30 minutes.

When the browser checks for updates, if the script has changed, a new script will be downloaded by the browser. The repo server will check for the cookie and if it is present, it will get the UBRID from the cookie and set it in the Script. If the cookie is not present or if the UBRID is not present in the cookie, a new UBRID will be generated.

The Repo server will set the UBRID in the CX Script itself. So the script has it until browser tries to download the new version of the script.

The CX Script stores the UBRID in a global variable window.Rakanto.ClientSideData.ubrid

The CX Script creates an additional cookie in the website's domain, and places the UBRID and additional information in the new cookie. This cookie is used for our Server Side Processing of the events. This code is also known as Scriptlet

Digital Analytics Reporting ID DARID

The Digital Analytics Reporting ID, is a tag which identifies a business unit within UHG. These IDs are used to identify and group your events in Pixel System.

Note : DARIDS need to be AllowListed! Work with Pixel Team to get the DARID(s) you should tag your pages with
  • An event can contain any number of DARIDs
  • For the events to flow through, at least one of the DARIDs should be AllowListed in Pixel Environment
  • Work with Pixel Team to finalize your DARID BEFORE you start your integration work
  • DARIDS should have minimum 3 characters
  • Only Latin alphanumeric and period(.) are allowed. Anything else is not allowed
  • DARIDs are case-insensitive Examples:
    • Valid.darid1
    • validDarid
    • Invalid-DARID
    • invalid_darid
    • InvalidDarid∆
  • If a DARID is invalid, the CX Script will log the error message to the browser console.

Proposed format

Proposed DARID format consists of 2 main components, Business Information, Application Information

<DARID> ::= <Business Information>.<Application Information>

<Business Information> ::= <Entity>.<Sub-entity>.<Line of Business>

<Entity> ::= "UHG"

<Sub-entity> ::= "UHC" | "Optum"

<Line of Business> ::= "EnI" | "MnR" | "CnS" | "Global" | "Rx"

<Application Information> ::= <Application>[.<Function>][.<GroupLevel1>... .<GroupLevelN>]

<Application> ::= denotes application name and is a required element.

<Function> :== Additional elements in DARID that specifies the subset of Functionality

<GroupLevelx> :== Additional elements in DARID that can be used to group pages or user experience.  Use periods to separate the application from the groups/functions

Examples

Example 1: login page in MnR Member Medicare solution implementation would tag the page with the following DARID UHG.UHC.MnR.MedicareSolutions.Member.LoginPage

Note: Pixel will be generating more DARIDs based on the DARID provided.  For example, the above DARID UHG.UHC.MnR.MedicareSolutions.Member.LoginPage will be parsed into
    UHG.UHC
    UHG.UHC.MnR
    UHG.UHC.MnR.MedicareSolutions
    UHG.UHC.MnR.MedicareSolutions.Member
    UHG.UHC.MnR.MedicareSolutions.Member.LoginPage

Example 2: login page shared by Mnr Medicare Solutions Member page and Acquisition page may pass two DARIDs UHG.UHC.MnR.MedicareSolutions.Member.LoginPage and UHG.UHC.MnR.MedicareSolutions.Acquisition.LoginPage

Note : Pixel will be generating the following DARIDs from the DARIDs provided above
    UHG.UHC
    UHG.UHC.MnR
    UHG.UHC.MnR.MedicareSolutions
    UHG.UHC.MnR.MedicareSolutions.Member
    UHG.UHC.MnR.MedicareSolutions.Member.LoginPage
    UHG.UHC.MnR.MedicareSolutions.Acquisition
    UHG.UHC.MnR.MedicareSolutions.Acquisition.LoginPage

Stage Environment

In the stage environment, the CX Script will automatically send in a special DARID UHG.Optum.Pixel.SelfService along with the DARIDs set in the page. This DARID (UHG.Optum.Pixel.SelfService) is allow listed in Pixel Environment allowing events to flow through the Pixel Pipeline in the stage environment without needing any additional setup.

Production Environment

Clients will need to work with Pixel team to make sure their primary DARID is Allow listed in Pixel Production environment.

How the CX Script chooses which DARID to use

Darids may be declared in different places depending on their expected lifetime. When the CX Script reports something the DARID set used is chosen in the following precedence, highest to lowest.

  1. As a direct input into an API call.

    See custom data

  2. From attributes of the script tag The code to include the script on your page. This is the easiest way call the CX Script.

    A Simple Example

  3. From a session object Store the DARIDs in sessionStorage. CX Script will use the DARIDs defined in Session Storage

    <html>
      <head>
          <script type="text/javascript">
            sessionStorage.setItem('darids','UHG.Optum.Pixel.CXScriptExample.DaridInSessionStorage');
            (function (cx, darids, demarcs) {
              window['RakantoObject'] = 'Rakanto';
    
              window['Rakanto'] = window['Rakanto'] || function () {
                (window['Rakanto'].q = window['Rakanto'].q || []).push([Date.now()].concat(Array.prototype.slice.call(arguments)))
              }, window['Rakanto'].l = Date.now(), window['Rakanto'].demarcs = demarcs;
    
              a = document.createElement('script'),
              a.src = cx,
              a.id = 'rakanto',
              a.async = true,
              (darids)? a.setAttribute('data-px-darids', darids):null,
              m = document.getElementsByTagName('script')[0],
              m.parentNode.insertBefore(a, m);
            })('https://repo-stg.rakanto.com/rakanto/cx/cx.js');
          </script>
      </head>
    
      <body>
          <h1>Pixel Customer Experience Script</h1>
          <h2>Darids from session object</h2>
      </body>
    </html>
    
  4. From the page data layer.

    Pass one or more DARIDs using the data layer (window.optumPageDataLayer). The window.optumPageDataLayer is used to set DARIDs that gets sent in the pixel call

    <html>
        <head>
            <script type='text/javascript'>
                window.optumPageDataLayer = {
                    darids: ['UHG.Optum.Pixel.CXScriptExample.DaridsFromPageDataLayer']
                };
                (function (cx, darids, demarcs) {
                  window['RakantoObject'] = 'Rakanto';
    
                  window['Rakanto'] = window['Rakanto'] || function () {
                    (window['Rakanto'].q = window['Rakanto'].q || []).push([Date.now()].concat(Array.prototype.slice.call(arguments)))
                  }, window['Rakanto'].l = Date.now(), window['Rakanto'].demarcs = demarcs;
    
                  a = document.createElement('script'),
                  a.src = cx,
                  a.id = 'rakanto',
                  a.async = true,
                  (darids)? a.setAttribute('data-px-darids', darids):null,
                  m = document.getElementsByTagName('script')[0],
                  m.parentNode.insertBefore(a, m);
                })('https://repo-stg.rakanto.com/rakanto/cx/cx.js');
            </script>
        </head>
        <body>
            <h1>Pixel Customer Experience Script</h1>
              <h2>Darids from Page Data Layer</h2>
        </body>
    </html>
    

Test Site

In Stage and Dev environments there are test pages, which send requests to Kibana. You can model your calls after these pages if you would like.

https://repo-stg.rakanto.com/rakanto/js-test https://repo-dev.rakanto.com/rakanto/js-test

Custom Fields or Custom Data

In addition to DARIDs partitioning your data for reporting, custom data may be sent along as well. The purpose of which may be for A/B testing, reporting on site specific metrics, or just to capture site specific information.

The custom data is an object in the form of

CUSTOM_DATA_OBJECT = {'namespace':'darid_from_darid_list_on_page','key1':'value1','key2':'value2', ... }

custom fields will show up in Event as:
    CF_*namespace*_key1=value1
  1. The custom data object must not exceed 3000 bytes in total. For example the following string is 62 bytes long:
     {'namespace':'darid_from_darid_list_on_page','key1':'value1'}
    
    If the field exceeds that length, an error message is placed in JavaScript log, and the base64 encoded customData is set to cx_error_${event}` = 'send custom data size exceeded limit' this will show up in event.
  2. The namespace is a required field and is used to generate a namespaced custom field as part of the event. These fields will be top level searchable fields in Kibana.
  3. The namespace provided in pxCustomData should match one of the DARIDs in the "darids" list
  4. The namespace will be used to coin the field name for the custom field It will take the form CF_namespace_key1=value1
  5. If namespace is not provided in pxCustomData, you will see "you must include a namespace in the custom data message." in console log You also will see an error message "error: Custom fields namespace is empty" in the event
  6. If the provided namespace does not match with one of the DARIDs, you will get an error message "error: Custom fields namespace is invalid" in the Event

Anti-patterns

NOTE : Please do not send custom data messages from events which will rAPIdly fire, such as mouse movements.
We have tested against very large loads, however when we tested this exact scenario, one browser was generating over 1000 requests per second.

If custom data is defined in multiple places which one is used

Custom Data may be declared in different places depending on their expected lifetime. The key values in these locations are not "merged".

When the CX Script has Custom Data set in multiple locations, the CX script selects the location to represent custom data, in the following precedence, highest to lowest.

  • api call
  • session storage
  • page data layer

Sample implementation of custom data

```javascript
CUSTOM_DATA_OBJECT = {"namespace":"darid_from_darid_list_on_page","key1":"value1","key2":"value2", ... }
```
  1. API call

    Make a method call from your JavaScript.

    window.Rakanto.event("sendCustomData",CUSTOM_DATA_OBJECT)
    
  2. sessionStorage.getItem("pxCustomData");

    Session storage only accepts STRINGS. A STRING must be made from your CUSTOM_DATA_OBJECT

    To do this, JSON.stringify your CUSTOM_DATA_OBJECT first.

    Assign that string to the sessionStorage.pxCustomData object, the custom data will be sent on each pixel request, overriding any custom data on optumPageDataLayer.

    sessionStorage.setItem( "pxCustomData", JSON.stringify( CUSTOM_DATA_OBJECT ) )
    

    Example

    <html>
      <head>
        <script type="text/javascript">
          sessionStorage.setItem('darids','UHG.Optum.Pixel.CXScriptExample.CustomData');
          custom_data_object = {'namespace':'UHG.Optum.Pixel.CXScriptExample.CustomData','key1':'value1'}
          sessionStorage.setItem('pxCustomData', JSON.stringify(custom_data_object) );
          (function (cx, darids, demarcs) {
            window['RakantoObject'] = 'Rakanto';
    
            window['Rakanto'] = window['Rakanto'] || function () {
              (window['Rakanto'].q = window['Rakanto'].q || []).push([Date.now()].concat(Array.prototype.slice.call(arguments)))
            }, window['Rakanto'].l = Date.now(), window['Rakanto'].demarcs = demarcs;
    
            a = document.createElement('script'),
            a.src = cx,
            a.id = 'rakanto',
            a.async = true,
            (darids)? a.setAttribute('data-px-darids', darids):null,
            m = document.getElementsByTagName('script')[0],
            m.parentNode.insertBefore(a, m);
          })('https://repo-stg.rakanto.com/rakanto/cx/cx.js');
        </script>
      </head>
      <body>
          <h1>Pixel Customer Experience Script</h1>
          <h2>Custom Data</h2>
      </body>
    </html>
    
  3. window.optumPageDataLayer

    The window.optumPageDataLayer can also have a DARID list, used for calls on the page. If custom data is specified, those key values are sent as well.

    How to send custom data:

    • Create an object in the form of
      {"namespace":"YOURDARID, "key1":"value1", ... }
      
    • Assign it to sessionStorage.pxCustomData, or add an element called pxCustomData to the optumPageDataLayer, or set it with an API call.
    • It will show up in Kibana as:
      CF_YOURDARID_key1
      
      The custom data will be sent with each Pixel request.
    <html>
      <head>
        <title>CX Script Page</title>
        <script type="text/javascript">
          window.optumPageDataLayer = {
            darids: ["UHG.Optum.Pixel.CXScriptExample.CustomData"],
            pxCustomData: {"namespace":"UHG.Optum.Pixel.CXScriptExample.CustomData","key1":"value1"}
          };
          (function (cx, darids, demarcs) {
            window['RakantoObject'] = 'Rakanto';
    
            window['Rakanto'] = window['Rakanto'] || function () {
              (window['Rakanto'].q = window['Rakanto'].q || []).push([Date.now()].concat(Array.prototype.slice.call(arguments)))
            }, window['Rakanto'].l = Date.now(), window['Rakanto'].demarcs = demarcs;
    
            a = document.createElement('script'),
            a.src = cx,
            a.id = 'rakanto',
            a.async = true,
            (darids)? a.setAttribute('data-px-darids', darids):null,
            m = document.getElementsByTagName('script')[0],
            m.parentNode.insertBefore(a, m);
            })('https://repo-stg.rakanto.com/rakanto/cx/cx.js');
        </script>
      </head>
      <body>
        <h1>Pixel Customer Experience Script</h1>
        </p>(Darids from a Window Page Data Layer)</p>
      </body>
    </html>
    
  4. Using in an Angular site.

see Single Page Apps

Testing the setUserIdentity data

  • open Chrome inspector and view the network tab, click record

  • in a Chrome console window, type:

      window.Rakanto.event("setUserIdentity",{ "authSystem": "SomeAuthSystem", "userId": "bob.dobbs", ... })
    
      // or set more than one auth system at once..
      window.Rakanto.event("setUserIdentity",{ "authSystem": "SomeAuthSystem", "userId": "bob.dobbs", ... },{ "authSystem": "AnotherAuthSystem", "userId": "bob.dobbs", ...})
    
      // or send an array of authsystems..
      window.Rakanto.event("setUserIdentity",[{ "authSystem": "SomeAuthSystem", "userId": "bob.dobbs", ... },{ "authSystem": "AnotherAuthSystem", "userId": "bob.dobbs", ...}])
    

Testing the customData data

  • open Chrome inspector and view the network tab, click record

  • in a Chrome console window, type:

      window.Rakanto.event("sendCustomData",{ "namespace": ONE_OF_YOUR_DARIDS, "key1": "value1", "key2": "value2", ... })
    

    OR

  • To verify custom data, on macOS:

    In Chrome inspector, look at the get request issued. Copy the encryptedData value, from the querystring in headers

    run the command in osx terminal
    
    pbpaste | base64 -D -
    

    This will base64 decode the value, your original hash data will show up.

Viewing current and historical window.location

The history of browser pages, or SPA app pages viewed are often important fields for correlating a user journey through the app. We capture URL in multiple places, and at multiple times a user interacts with a page.

pageURL is window.location, at the time the page was loaded.

  • It is set as a top level field.
  • On every event. This is used as the location for a startPageLoad event, when the actual html of the page changes.

The startPageLoad event contains the userPageHistory, an array, of arrays.

  • The first element of each sub-array is the same as the pageURL

The startViewLoad event contains the userViewHistory, an array of arrays

  • The userViewHistory field has the same fields, but is available on view changes, AND Page changes.
  • The userViewHistory field has the same structure as userPageHistory, however its url is generated from the current window location, which the SPA changes.
  • The userViewURLHistoryField, containing userViewHistory field descriptions ['URL', 'timestampMS', 'event'] is also sent;

We DO NOT send corresponding top level current VIEW.location fields similar to pageURL with userViewHistory generating events.

We DO send pageURL with EVERY event. However the pageURL is a snapshot in time, when the page was loaded, If it is the same as the current viewHistoryURL, it's purely coincidental

Single Page Applications

The cx script exposes a few global methods you can use to send events. First you need to allow those global methods within your app.

declare global {
  interface Window {
    Rakanto: any;
  }
}

Capturing view change events

The following code is a sample app-routing.module.ts it sends a starViewEvent every time the router detects a navigation change. The most likely event you want to monitor.

export class YourRoutingModule {

  constructor(private router: Router) {
    router.events.subscribe((event: Event) => {
      // console.log(event);
      if (event instanceof NavigationEnd) {

        (async() => {
          // console.log("waiting for window.Rakanto.event() to be available.");
          while(!(window.hasOwnProperty("Rakanto") && window.Rakanto.hasOwnProperty('event'))){
            await new Promise(resolve => setTimeout(resolve, 20));
          }
          window.Rakanto.event('startViewLoad');
        })();

      }
    });
  }

pxStartViewLoad will reset timers, and update the page history with the current view url. A startViewLoad event reflecting these will be sent.

Capturing how long a view took to load

This code will send a 'viewLoaded' event. This event shows how long the view took to load.

window.Rakanto.event("viewLoaded")

Single page app custom data example

This example supports sending a custom data event with every mouse click. The event contains the tagName of the element clicked, though this could be changed to also contain the class, div, or even custom HTML attributes, where you can choose your own tags.

import { Directive, HostListener } from '@angular/core';

declare global {
  interface Window {
    Rakanto: any;
   }
}

@Directive({
  selector: '[pixelCX]'
})
export class PixelCXDirective {

  @HostListener('window:click', ['$event'])
  onClick(event: any) {
    this.log(`You clicked on ${event.target.tagName}`);

    let CUSTOM_DATA_OBJECT = {
      "namespace": "UHG.Optum.Pixel",
      "action": `clicked ${event.target.tagName}`
    };
    window.Rakanto.event("sendCustomData", CUSTOM_DATA_OBJECT);
  }
}

Look in your browsers "developer tools : network inspector" for a "cx_collector" event. You will notice custom_data in the event is encrypted. If you need to verify the call, you will need to look at decrypted data in Kibana.

From Kibana Filter on the page darid, and look for your particular call. The decrypted content will show.

We sent the previous call with the namespace

"UHG.Optum.Pixel"

Custom Data field will show in Kibana as:

"CF_UHG.Optum.Pixel_action": "clicked ${event.target.tagName}",

Caching

The CX Script server includes a header

add_header Cache-Control 'private';

This header makes it so the CX Script is not cached by edge caches, and is only cached by the browser. The CX Script is custom for each user. There are variables which are inserted into the script by the CX server, These variables are used for a correlation ID, API endpoints and other things.

When the cache expires a session is checked when the new file is downloaded, based on the session values, variables are inserted into the new script.

Opting out of data collection

Requirements from the State of California and European Union nations require us to provide a method for opting out of data collection. Please provide your customers with a link to https://cse.rakanto.com/optout.htm to facilitate opting out of data analytics. Data is still collected for security purposes, however it will not be visible to the business.

Users may change their opt-out preference at any time. The opt-out feature is tied to the pixel-ubrid cookie, set by the cx script. If this cookie is reset, opt out preferences will be forgotten until a user log's in.

For sites using the CX script, If a user opts out on any UHG or Optum site, they are opted out of all sites.

Release notes

Version 4.1.2

  • Moves sessionStorage variables out of the queue and into the payload
  • overrides push command on window.Rakanto.q to allow for sessionStorage variable assignment
  • New field: eventEnqueuedWhenSent
  • New unit tests for event count sessionStorage objects

Version 4.1.1

  • Enhancement to 4.1.0 field eventEnqueuedCount, ensuring the value is accurate even in the event of a session reset
  • Will maintain consistency for event history, in the case of optumPixelIds being altered/reset mid-session

Version 4.1.0

  • TWO new fields will be introduced, event_enqueued_count (the count of events added to the queue in the browser) and event_sent_count (the count of events successfully sent from the browser).

Version 4.0.0

  • Encryption mechanism was changed from using RSA to ECC. RSA is no longer supported.
  • Ensured the version and commit_hash are sent as static compile time strings, this fixes an incongruity which appeared sometimes in events.
  • Added additional rules for darid validation on the client side, so developers get more rapid feedback of darid issues.
  • Updated documentation to show how to send JSON payloads in Custom Fields.
  • Fix cases where userViewUrlHistory was sometimes not showing up.

Version 3.2.10

  • Changes to comply with codeql recommendations.
  • Changes to correct correct page counts.
  • Updated pixel session id to be generated from a standard UUID library.
  • Removed the ability to pin to other version of the cx script other than the current release.

Version 3.2.9

  • Bugfix.

Version 3.2.8

  • Fixed a bug to accomodate when the CX is called using a Date Object, rather than the documented Milli's since epoch.

Version 3.2.7

  • Changed implementation to get more accurate timings on pages.
  • Added a Window.Rakanto.event method. Users can call this method with arbitrary events, If the CX is familiar with the event, it will send it. This decouples the invocation of an api, from the availability of it. New event types can be implemented before the cx script has been modified to process them.

Version 3.2.6

  • Added support of opt-out functionality. Cookie partitioning makes it harder to allow a user to opt out of data collection across sites. We added support to facilitate cross site data collection opt out.
  • Added support for single page applications, cx will now send a 'startViewLoad' event when hooked up to the router in a SPA.
  • Added support to send additional user identities as a payload in a 'setUserIdentity' event. These profiles are added to the pixel lookup service where they supplement the current user profile.

Version 3.2.5

  • Added fixes to address cookie partitioning
  • Change documentation, add how to add link decoration to pages.
  • Fix docker reload script and add new cx to archive.
  • Add a window.Rakanto.ubrid object to be the ACTUAL adjudicated UBRID.
  • Incrementally extend the UBRID cookie expiration date on every cx script request.
  • Set first party cookies in script for pixel-ubrid and RakantoClientSideData.
  • Added cookie endpoint API, and event to ping it, to set first party cookie using a CNAME this allows cookies to last much longer in Safari.
  • Extract ReferrerUbrid from querystring if present, setup rules to determine whether script or cookie or query string UBRID is used.
  • Don't send referrerUbrid event if the referred UBRID == current window.rakanto_ubrid.
  • A referrerUbrid event is sent only when the referrerUbrid differs from the pages existing UBRID.
  • Audited use of session values in code, places where it wasn't absolutly needed to use a session were factored out.
  • Change how cxScriptDownloadTime is calculated to not use sessions.
  • Changed times for networklatencymillis to be always a number, it was a string. Transformer can handle both.
  • Set up link decoration with UBRID, allowing UBRIDs to be passed from page to page, even across domains.
  • Allow multiple links to be specified in the link decoration, set as "demarcs" passed in via script invocation as either a single link string, or an array of strings for links, if a demarc is contained in a link, anchored to left, read to right, the UBRID is appened to the querystring. This works if either link is a full link or just the pathname.

Version 3.2.4

  • added support to pass in UBRID via query param

Version 3.2.3

  • (bugfix) UIP fixes.

Version 3.2.2

  • (bugfix) CX script sending custom_data, if available, with ALL non uip events.

Version 3.2.1

  • (bugfix) Fixing uip Buffer cache flushing

Version 3.2.0

  • customData inside events sent by the CXScript now renamed to encryptedData, and is formatted differently
  • API for sending custom data remains unchanged.

Version 3.1.5

  • Bug fixes for various page compatibility issues, and better error reporting

Version 3.1.4

  • CX script now optionally supports UIP.
  • Supports page hit counters, events counters for each page.

Version 3.1.3

  • CX script now support opt-out functionality.

Version 3.1.2

  • Now supports multiple endpoints.
  • Payload transmitted back to CSE collector is in JSON format.

Version 3.1.1

  • customData fields are now encrypted using a random 4096bit key.

Version 3.1.0

  • Documentation is more professional looking using Markdown backed templating.
  • Added async support to invocation of script, allowing events to be queued while the cx.js is download and run.
  • Added configurable send/check interval to the cx.js.
  • Added event queuing for cx.js.
  • Added structure to pass UBRID to any server via window.Rakanto.ClientSideData object.
  • Added Git commit hash for CXScript to the Window.Rakanto.ClientSideData object.
  • Added mechanism to retrieve data back from CSE-Listener events.
  • Added network latency calculations to all calls. The latency for the most recent call is sent in the next call.
  • Added Saucelabs integration tests for sync and async usages of script, Tested on 1207 browser/os/version combinations of Safari, Chrome, IE, Edge, Firefox, Samsung, Android, iOS, Windows, macOS.
  • Limited size of POST customdata requests to 8k, GET custom data requests to 2K
  • Changed backend call to the CSE-Listener to be async XHR POST based with fallback logic on POST requests to use async XHR GET requests.
  • Changed CSE-Listener to support multiple methods for API calls.
  • Added better DARID validation.
  • Added an automatic DARID for stage, and dev events. Easier setup for new customers.
  • Changed how darids are namespaced, went to a model similar to java namespaces, company.division.team.application.page.thing ...
  • More understandable customdata DARID rules and error messages. Custom Data events now require you to choose from available page darids, and set the customdata namespace to that value.
  • Updated cx.js dependencies to newer versions.
  • Updated support for docker environment for testing.