Integrate the Connect SDK into your web application

The Connect Web SDK is a JavaScript library that captures visitors' interactions with web applications. The findings are available in Acoustic Connect.

The Connect library supports all types of web applications using standard HTTP and HTTPs protocols. It captures user sessions in all modern browsers.

Prerequisites

Connect subscription

To use the Connect SDK, your company must have an active Connect subscription.

  • With the Premium subscription, you can currently capture one user flow - abandonment. Our Professional Services team will take care of integrating the SDK into your app.
  • The Ultimate subscription supports multiple user flows and is rich in options. You can manage the integration on your own using the current instruction. We will provide assistance at all stages of the implementation.

Connect credentials

You need to obtain Connect credentials to start using the library.

  1. Log in to your Connect account under an administrator account.
  2. Go to Administration > Behavioral settings.
  1. Open the Applications tab. If a new app is required, click to add an app and fill out required settings.
  1. Click the SDK link for your app.
  2. On the JavaScript tab, copy your application key and endpoint URL from a Tealeaf snippet.
Code snippet for web applications

Code snippet for web applications

Initial integration

You can integrate the Connect library into your website in either of the following ways:

  • Acoustic CDN (recommended). If you choose this option, we will be taking care of updates and maintenance. You will only need to add a short JavaScript snippet to the header of your website and submit the parameters that must be customized (your Connect credentials and privacy settings).
  • Self-hosted. You can host the library on your server. It consists of two sections: the core section and the configuration section that you can edit. You will need to overwrite the core section each time a new version is made available.
  1. Open your webpage template and paste the following snippets anywhere within the <head> tag.
<script src="https://cdn.goacoustic.com/connect/latest/acoconnect.js"></script> <script>window.TLT && window.TLT.initLib("YOUR APP KEY","YOUR ENDPOINT URL");</script>
  1. Replace YOUR APP KEY and YOUR ENDPOINT URL with your Connect credentials.
  1. Copy the library from our CDN and upload it to your web server.
  2. In the webpage template, add the following code snippets anywhere within the <head> tag. In the src attribute, specify the path to the library on your server.
<script src="/scripts/acoconnect.js"></script> <script>window.TLT && window.TLT.initLib("YOUR APP KEY","YOUR ENDPOINT URL");</script>
  1. Replace YOUR APP KEY and YOUR ENDPOINT URL with your Connect credentials.

👍

Advice

Do not minify the Configuration section until you complete testing.

Verifying the initial integration

There are several ways to make sure the JavaScript code has been added to your web application.

  • Open the browser developer tools and enter TLT.getLibraryVersion() in the console. The library version should be returned. If the TLT is not defined message is returned, the library is not loaded.
  • Click around on your website. After that, log in to Connect, go to Insights > Sessions > Session search and view the replay of your user session.

Default configuration settings

Below is the default configuration used to initialize the Connect library. These are optimal settings that we have tested in multiple environments. The default configuration also adds the following libraries:

  • Pako 1.0.11 with Dojo/ReactJS fix - to gzip data in the payload back to the collector
  • Hammer.JS 1.1.3 - to capture gestures
  • AJAX Listener - to capture requests and responses
const defaultConfig = {
    core: {
        // Parameterize - customer name and implementation date
        buildNote: "Acoustic Connect UIC " + window.TLT.getLibraryVersion(),
        blockedElements: [],
        ieExcludedLinks: ["a[href*=\"javascript:void\"]", "input[onclick*='javascript:']"],
        blockedUserAgents: [
            { regex: "(Google|Bing|Face|DuckDuck|Yandex|Exa)bot|spider|archiver", flags: "i" },
            "PhantomJS"
        ],
        inactivityTimeout: 1000 * 60 * 29, // 29 minutes, just under default 30 min app timeout

        modules: {
            replay: {
                events: [
                    { name: "change", attachToShadows: true, recurseFrames: true },
                    { name: "click", recurseFrames: true },
                    { name: "dblclick", recurseFrames: true },
                    { name: "contextmenu", recurseFrames: true },
                    { name: "pointerdown", recurseFrames: true },
                    { name: "pointerup", recurseFrames: true },
                    { name: "hashchange", target: window },
                    { name: "focus", recurseFrames: true },
                    { name: "blur", recurseFrames: true },
                    { name: "load", target: window },
                    { name: "unload", target: window },
                    { name: "resize", target: window },
                    { name: "scroll", target: window },
                    { name: "mousemove", recurseFrames: true },
                    { name: "orientationchange", target: window },
                    { name: "touchend" },
                    { name: "touchstart" },
                    { name: "error", target: window },
                    { name: "visibilitychange" }
                ]
            },
            flushQueue: {
                events: [] // Populated by custom logic below for iOS sessions
            },
            overstat: {
                enabled: true,
                events: [
                    { name: "click", recurseFrames: true },
                    { name: "mousemove", recurseFrames: true },
                    { name: "mouseout", recurseFrames: true },
                    { name: "submit", recurseFrames: true }
                ]
            },
            performance: {
                enabled: true,
                events: [
                    { name: "load", target: window },
                    { name: "unload", target: window }
                ]
            },
            ajaxListener: {
                enabled: true,
                events: [
                    { name: "load", target: window },
                    { name: "unload", target: window }
                ]
            },
            gestures: {
                /* This replay rule must also be added to replay gestures events in Connect SaaS
                    <HostProfile name="www.sample.com">
                        <SimulateUIEvents value=".*" uiEvents="gestures,resize,valuechange,click,mouseup,scroll"/>
                    </HostProfile>
                */
                enabled: true,
                events: [
                    { name: "tap", target: window },
                    { name: "hold", target: window },
                    { name: "drag", target: window },
                    { name: "release", target: window },
                    { name: "pinch", target: window }
                ]
            },
            dataLayer: {
                enabled: false,
                events: [
                    { name: "load", target: window },
                    { name: "unload", target: window }
                ]
            },
            TLCookie: {
                enabled: true
            }
        },

        // OPTIONAL - Normalize URL, path, or fragment
        // normalization: {},

        // OPTIONAL - share session identifier with other libraries
        // sessionData: {},

        screenviewAutoDetect: true,
        framesBlacklist: []
    },
    services: {
        queue: {
            asyncReqOnUnload: true,
            useBeacon: true,
            useFetch: true,
            xhrEnabled: true,
            queues: [
                {
                    qid: "DEFAULT",
                    endpoint: "",
                    // Parameterize
                    maxEvents: 30,
                    timerInterval: 60000,
                    maxSize: 300000,
                    checkEndpoint: true,
                    endpointCheckTimeout: 4000,
                    encoder: "gzip"
                }
            ]
        },
        message: {
            privacy: [
                {
                    targets: ["input[type=password]"],
                    maskType: 2
                },
                {
                    targets: ["input[id*=phone]", "input[name*=phone]"],
                    maskType: 4, // replace all digits with X except last 3
                    maskFunction: function (value) {
                        return value.slice(0, -3).replace(/\d/g, "X") + value.slice(-3);
                    }
                },
                {
                    // Whitelist privacy, un-masking only elements not containing PII
                    exclude: true,
                    targets: [
                        // Parameterize - need to be able to dynamically extend/modify this list
                        "input[type=hidden]",
                        "input[type=radio]",
                        "input[type=checkbox]",
                        "input[type=submit]",
                        "input[type=button]",
                        "input[type=search]"
                    ],
                    maskType: 4, // replace all alphas with X and digits with 9
                    maskFunction: function (value) {
                        return value.replace(/[a-z]/gi, "X").replace(/\d/g, "9");
                    }
                }
            ],
            privacyPatterns: [
                // Preemptively block SSN numbers in response
                {
                    pattern: {
                        regex: "\\d{3}-\\d{2}-\\d{4}",
                        flags: "g"
                    },
                    replacement: "XXX-XX-XXXX"
                },
                // Uncomment to block elements with .tlPrivate class
                {
                    pattern: {
                        regex: "<div[^<]*tlPrivate[^<]*>(.+?)</div>",
                        flags: "g"
                    },
                    replacement: function (fullMatch, group1) {
                        let retVal;
                        if (group1.length > 0) {
                            retVal = fullMatch.replace(group1, "xxxxxx");
                            return retVal;
                        }
                        return undefined;
                    }
                },
                {
                    pattern: {
                        regex: "<span[^<]*tlPrivate[^<]*>(.+?)</span>",
                        flags: "g"
                    },
                    replacement: replacementFun
                },
                {
                    pattern: {
                        regex: "<p[^<]*tlPrivate[^<]*>(.+?)</p>",
                        flags: "g"
                    },
                    replacement: replacementFun
                }
                ]
        },
        encoder: {
            gzip: {
                encode: "window.pako.gzip",
                defaultEncoding: "gzip"
            }
        },
        domCapture: {
            // Parameterize
            diffEnabled: true,
            // Options are set to these defaults:
            //
            // maxMutations: 100           // If this threshold is reached, the full DOM is captured instead of a diff
            // maxLength: 1000000          // If this threshold is reached, the DOM snapshot will not be sent
            // captureShadowDOM: false     // Enable ONLY if app is using shadown DOM. Also set allowHitSplit to false in org properties.
            // captureFrames: false        // Enable ONLY if child frames/iframes need to be captured for replay
            // captureDynamicStyles: false // Enable ONLY if dynamic/constructable/CSSOM styles are present
            // captureHREFStyles: false    // Enable ONLY if all styles need to be inserted inline (e.g if CSS files unreachable)
            // removeScripts: true         // Disable ONLY if script tags need to be preserved
            // removeComments: true        // Disable ONLY if comments need to be preserved
            // discardBase64: 0            // Not present by default! Discard all base64 encoded inline image data that exceeds N characters.
            // captureStyle: true          // Disable to remove inline CSS. Reduces the size of the DOM snapshots, but may affect replay.
            // keepImports: false          // Enable to honor the "import" link type, a now deprecated and Chrome specific feature
            //
            // Override as needed below:
            options: {
                maxLength: 10000000,
                captureShadowDOM: true,
                captureDynamicStyles: true,
                captureFrames: true
            }
        },
        browser: {
            normalizeTargetToParentLink: true
        }
    },
    modules: {
        overstat: {
            hoverThreshold: 3000
        },
        performance: {
            calculateRenderTime: true,
            renderTimeThreshold: 600000,
            performanceAlert: {
                enabled: true,
                threshold: 3000,
                maxAlerts: 100,
                // resourceTypes: [], // Capture everything
                blacklist: []
            }
        },
        replay: {
            tab: false,
            domCapture: {
                enabled: true,
                screenviewBlacklist: [],
                triggers: [
                    // Parameterize - need to be able to modify triggers
                    { event: "change" },
                    { event: "click" },
                    { event: "dblclick" },
                    { event: "contextmenu" },
                    { event: "visibilitychange" },
                    {
                        event: "load",
                        fullDOMCapture: true,
                        delay: 300
                    }
                ]
            },
            mousemove: {
                enabled: true,
                sampleRate: 200,
                ignoreRadius: 3
            }
        },
        ajaxListener: {
            urlBlocklist: [
                // Parameterize
                { regex: "brilliantcollector\\.com" }
            ],
            // Parameterize
            filters: [
                {
                    log: {
                        requestHeaders: true,
                        requestData: false,
                        responseHeaders: true,
                        responseData: false
                    }
                }
            ]
        },
        dataLayer: {
            dataObjects: [
                {
                    dataObject: "window.dataLayer",
                    rules: {
                        screenviewBlocklist: [],
                        propertyBlocklist: [],
                        permittedProperties: [],
                        includeEverything: true // default: true
                    }
                }
            ]
        },
        TLCookie: {
            // Parameterize
            appCookieWhitelist: [
                { 
                    regex: ".*" 
                }
            ],
            tlAppKey: "",
            disableTLTDID: false
        }
    }
};

// ----------------------------------------------------------------------------------
// Android and iOS Tuning -----------------------------------------------------------
// ----------------------------------------------------------------------------------
if (window.TLT.utils.isiOS || window.TLT.utils.isAndroid) {
    (function () {
        let uaMatch;

        // Reduce batch size, increase frequency, increase endpoint timeout
        defaultConfig.services.queue.queues[0].maxEvents = 10;
        defaultConfig.services.queue.queues[0].maxSize = 10000;
        defaultConfig.services.queue.queues[0].timerInterval = 10000;
        defaultConfig.services.queue.queues[0].endpointCheckTimeout = 10000;

        if (window.TLT.utils.isiOS) {
            // Disable Beacon in iOS 12 and earlier due to Safari on iOS bug
            uaMatch = window.navigator.userAgent.match(/OS (\d+)_/);
            if (uaMatch && uaMatch[1] < 13) {
                defaultConfig.services.queue.useBeacon = false;
            }
            // Flush queue on visibilitychange as unload is not a reliable trigger in iOS.
            // Requires flushQueue (or digitalData) module and entry in core.modules section.
            if (defaultConfig.core.modules.flushQueue && defaultConfig.core.modules.flushQueue.events) {
                defaultConfig.core.modules.flushQueue.events.push({ name: "visibilitychange" });
            } else {
                console.log("Connect Error: include the flushQueue module!");
            }
        }
    }());
}

Updating other configuration settings

If some other configuration settings are necessary, you can submit them along with your Connect credentials and privacy settings.

<script src="https://cdn.goacoustic.com/connect/latest/acoconnect.js"></script>
<script>
// This obtains default configuration
let config = TLT.getDefaultConfig();
// Then you can adjust configuration settings
// This below will enable dataLayer
config.core.modules.dataLayer.enabled = true;
// This updates the configuration for dataLayer
config.modules.dataLayer.dataObjects[0].rules.filter = [
    {
        matchProperty: 'set', 
        matchValue: 'url_passthrough'
    }
]
/**
 * Api for initLibAdv
 *
 * Initializes the system based on configuration information is passed to the
 * config service to manage it. All modules are started and web events
 * are hooked up. There are also options to add extra libaries needed for AcoConnect.
 *
 * @param {String} [appKey] The application key from SaaS organization.
 * @param {String} [postUrl] The collector url to post data.
 * @param {Object} [newConfig] Configuration object to use without application key and post url.
 * @param {Boolean} [addPako] Whether to add pako.
 * @param {Boolean} [addHammer] Whether to add HammerJS.
 * @param {Boolean} [addAjaxListener] Whether to add ajax listener.
 * @param {Boolean} [addRestartACOCONNECTforSPA] Whether to add Restart ACOCONNECT for SPA helper.
 * @param {String} [remoteConfigUrl] The remote configuration url to be used.
 * @returns {Object} Configuration object used.
 */
window.TLT && window.TLT.initLibAdv("<<YOUR APP KEY>>","<<YOUR ENDPOINT URL>>", config, true, true, true, true, true);
</script>

Further steps

Before releasing your web app with the Connect SDK to production, you must take measures to secure your end users' personally identifiable information (PII). For instructions, see Enable privacy protection in the Connect Web SDK.

Removing the Connect Web SDK from your web application

To remove the Connect library from your web application, do the following:

  1. Remove the Connect script from your webpage template.
  2. Log in to Connect and make sure there are no new sessions from your web application.

📘

Advice

Save a copy of the custom JavaScript file before you get down to uninstallation.