This package compiles the pulse tracker to a IIFE package that is distributed to
our CDN and is intended to be used asynchronously using a short <script>
snipped.
If you have a singe page app you can use this package from the Schibsted CDN or optionally use the underlying tracker NPM module directly in your code.
If you are not yet familiar with Pulse, make sure to read our Introduction to Pulse.
If you are looking to integrate with Pulse and start capturing data, see our Integration Guide.
In order to start tracking events with Pulse for a new website or app, you need to have set up a Pulse project and get the provider id associated with it.
The Integration Guide will walk you through this setup.
This tracker is not opinionated on what events should be sent in what scenario and will not do anything automatically.
If you are unsure about what events should be tracked, see the tagging plan guide in the Pulse Documentation.
After reading this guide you should be able to define and send Pulse events to track user behaviours specific to your website.
We offer a set of tools to help you debug your Pulse integration. Most of these are accessed via the Pulse Console. To learn more about the tools, see their page in this guide.
There are four builds of Pulse Autotracker. One build is intended for the fastest possible load of Pulse and at the same time the least intrusive to the site. The other build have a slightly better API to work with and with some setup on the site it will be as little intrusive as the other method. More about these two builds below.
Each of those builds comes in two varieties, with and without polyfills for the required modern web APIs. Pulse use the APIs fetch and Promise and these APIs are not ubiquitously available in the current browser marked. The default version of all builds includes the necessary code (polyfills) to deal with older browsers. It is however not necessary for pulse to include the code to patch these APIs if the underlying site already have patched the environment.
A -modern.min.js
build is therefor available for sites that have this under
control and have made sure that both fetch
and Promise
is in place before
pulse is loaded. This is done at your own risk so if you are unsure about
this, use the default version.
Promise API | Simpler API / Fast load | |
---|---|---|
All browsers | loader.min.js (~3.5kb synchronously) |
pulse.min.js (~350b synchronously / ~32kb asynchronously) |
Modern browsers | loader-modern.min.js (~600b synchronously) |
pulse-modern.min.js (~350b synchronously / ~22kb asynchronously) |
Place the following in the <head>
section of the site.
<script type="application/javascript">
(function (w, d, n, t, s, a, b) {w[n] = w[n] || function() {
(window[n].q = window[n].q || []).push(arguments)}; a = d.createElement(t);
b = document.getElementsByTagName(t)[0];a.async = 1;a.src = s;b.parentNode.insertBefore(a, b)}
)(window, document, 'pulse', 'script', '//sdk.pulse.schibsted.com/pulse.min.js');
pulse('init', 'CLIENT-ID');
pulse('trackPageView');
</script>
As soon as the first line of the script above have evaluated you will have a
global pulse
method. You can use this immediately to interact with the Pulse
tracker even though the tracker have not downloaded yet. All calls are queued up
and executed some time later. You will also see that this version of pulse
does not return anything.
pulse('init', 'CLIENT-ID') => undefined
You can supply a callback to pulse
to get a hold of a SDK instance. Calling
pulse
with just a function will return the default tracker instance. Note that
calling pulse(() => {})
before calling pulse('init',...)
will fail.
pulse('init', 'CLIENT-ID');
pulse(defaultSDK => console.log('this is a SDK instance', defaultSDK))
If you supply a string with is a valid tracker name as the first argument and a callback function as the second you can pick out the tracker instance you want.
pulse('init', 'CLIENT-ID', null, null, 'tracker1');
pulse('init', 'CLIENT-ID', null, null, 'tracker2');
pulse('tracker1', tracker => console.log('This is tracker 1', tracker));
pulse('tracker2', tracker => console.log('This is tracker 2', tracker));
In the image about you can see the page being downloaded and immediately after it is done a light blue vertical line is shown in the network breakdown. This means that the page is ready to be drawn and the browser does so. At this point two tracker API calls have been queued and the download of the tracker begins.
This download will not interfere with the page in any way. When the download is
done it parses the API call queue created by pulse(...)
calls and executes
them in order. In this case a single event is sent.
Place the following in the <head>
section of the site:
<script type="application/javascript" src="//sdk.pulse.schibsted.com/loader.min.js"></script>
<script type="application/javascript">
pulse('init', 'CLIENT-ID', { eventDebounce: 2000 } );
pulse('trackPageView');
</script>
Using the method above will give a "richer" pulse
function, where the result
of all calls are sent back as Promises and you can get a hold of SDK instances
using the pulse
method. Here are some examples
pulse('init', 'CLIENT-ID')
.then(sdk => console.log('Pulse tracker initialized and here is the instance', sdk),
() => console.error('Something went awry'))
pulse('trackPageView').then(() => console.log('Event successfully sent to the event receiver'))
You can also use pulse
to the a hold of a previously created instance
pulse('init', 'CLIENT-ID', 'tracker1');
pulse('init', 'CLIENT-ID', 'tracker2');
// Get the tracker2 instance
pulse('tracker2')
.then(tracker2 => tracker2.trackPageView())
.then(() => console.log('Event successfully sent to the event receiver'))
In this network diagram you can see that the light blue vertical line is drawn
after both the page and the loader script have been downloaded. This is because
the loader script is included in a synchronous <script>
tag. The loading and
parsing of the loader script will influence the initial rendering of the
page. Note that the absolute time in the diagram is over a emulated GPRS line
and you should consider your self if the loading time is acceptable or not.
The builds for modern browsers will be loaded faster but they assume that the browser is either a modern one or that the page have "upgraded" the browser to add the missing APIs. The following APIs are expected:
fetch
: API Documentation, Browser
compatibilityPromise
: API
Documentation
Browser compatibilityUsing the modern builds on a page that does not have polyfills for the APIs above will not make sense. They are intended for pages or Single Page Apps that already have them in places so it is not necessary to load the polyfills multiple times. It will also be up to the site to ensure that the polyfill code is loaded and evaluated before the tracking code is loaded.
Three arguments trailing 'init'
is passed to the Pulse tracker constructor and
are as following:
String
, requiredSDKConfigInput
, see SDK documentationBuilders
, see SDK documentationString
A final fourth argument is a string naming the tracker. This is only used by
this asynchronous tracker script and is not passed to the tracker instance..
Multiple trackers can be created by supplying a name to the tracker as a fourth
argument. The name of the tracker is prepended to API calls via pulse
.
pulse('init', 'CLIENT-ID', null, null, 'tracker1');
pulse('init', 'CLIENT-ID', null, null, 'tracker2');
pulse('tracker1.trackPageView');
pulse('tracker2.track')
Each event type have their own set of default values based on the assumption of
the environment they are used. Any default will be overridden either by setting
a field permanently in the tracker (pulse('update', { field: 'value' })
) or by
passing data to individual events (pulse('track', 'trackerEvent', { field:
'value'})
).
This is the default event type for the web build of pulse and includes the following data by default. Any of these fields can be overwritten permanently in the tracker instance or on a per event basis.
pulse('track', 'trackerEvent', { type: 'View' }) || pulse('trackPageView') =>
{
"device": { // https://github.schibsted.io/spt-dataanalytics/pulse-event-builders/blob/master/src/device-defaults-browser.js
"@type": "Device",
"environmentId": "sdrn:schibsted:environment:573e1b27-3d67-4a95-a1c7-28b93364097e",
"acceptLanguage": "en-US",
"screenSize": "2560x1440",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.109 Safari/537.36",
"deviceType": "desktop",
"viewportSize": "724x1306",
"localStorageEnabled": true,
"jweIds": "eyJpc3N1ZWRBdCI6IjIwMTctMTAtMThUMTM6MjVaIiwiZW5jIjoiQTEyOENCQy1IUzI1NiIsImFsZyI6ImRpciIsImtpZCI6IjIifQ..DHSFbETg93AXmXT1BVUF_w.KS-s2mZvS1H0euSgfD14F4FqGsEubNJPgT6bXrGhXWWpozKjlZuHrXg-M9d3hw6su8kt1SukZjBl9onD2qikUAqyMGN2qNtZMkHnePDzmHAuMPFT_cOZ9FAvveW8DJMic3YNW6bF77ldd37MsWqAd6iX4Df2DflH_vFgLrlocsA-n9O2UnEeh0_MUnY5gz-2-wt5OQxds_QUkxJB38dXXUBGZsDfyHVDBbi_lgoJoRw.AnceNWrn4-g63J60-XBVmg"
},
"object": { // https://github.schibsted.io/spt-dataanalytics/pulse-event-builders/blob/master/src/object-defaults-browser.js
"@id": "sdrn:pulse-test:content:http://localhost:8080/demopages/fast-load.html",
"@type": "Content",
"name": "Test site for Pulse Autotracker",
"url": "http://localhost:8080/demopages/fast-load.html"
},
"origin": { // https://github.schibsted.io/spt-dataanalytics/pulse-event-builders/blob/master/src/origin-defaults-browser.js
"url": "http://other-page.com"
},
"provider": { // Gets the provider id from the required parameter in 'init' / tracker constructor
"@type": "Organization",
"@id": "sdrn:schibsted:client:pulse-test"
},
"tracker": { // https://github.schibsted.io/spt-dataanalytics/pulse-event-builders/blob/master/src/tracker-defaults-browser.js
"name": "Pulse Node.js SDK",
"type": "JS",
"eventBuilderVersion": "1.0.11",
"version": "4.0.6"
},
"@id": "7118cc69-0131-45dc-9391-a3cacbd9bcb8", // Generated per event
"@type": "View", // Default in 'tracker.trackPageView', have to be
"pageViewId": "b4040ce6-6855-42d4-81f5-02f0d4064d82", // Updated with `tracker.newPageView` or `tracker.trackPageView`
"creationDate": "2017-10-25T11:22:45.564Z", // Calculated as late as possible before sending
"schema": "http://schema.schibsted.com/events/tracker-event.json/107.json"
}
This event is primarily intended to be used in Node environments where there is not much default state to collect.
pulse('track', 'routableEvent') =>
{
"provider": {
"@type": "Organization",
"@id": "sdrn:schibsted:client:pulse-test"
},
"tracker": {
"version": "4.0.6"
},
"@id": "499fd621-03df-464b-b15e-7d00c04d222c",
"creationDate": "2017-10-25T11:32:30.324Z",
"schema": "http://schema.schibsted.com/events/base-routable-event.json/17.json#"
}
A common case of is that some piece of information should be attached to all events, e.g the id of a logged in user. This can be done like so:
pulse('update', { actor: { id: '[email protected]', realm: 'spid.se' }});
And to log out:
pulse('update', { actor: undefined });
See the API documentation of @spt-tracking/pulse-event-builders for a full list of inputs to different event types.
See pulse-sdk-js for a full overview of the tracker api.
Plugins can be provided and instantiated via the pulse
queue.
The example below show a trival plugin
/* global pulse */
class HelloWorldPlugin {
constructor(tracker) {
tracker.track('trackerEvent', {
object: {
custom: {
'sendt_from_plugin': true
}
}
});
}
ping() {
console.log('ping');
}
}
pulse('provide', 'helloWorld', HelloWorldPlugin)
<script type="application/javascript">
pulse('require', 'helloWorld');
</script>
<script type="application/javascript" src="path/to/hello-world-plugin.js" async></script>
The plugin will be instantiated using new
and access to the plugin instance
can be done via pulse
queue like so:
pulse('defaultTracker.helloWorld.ping');
This sections deals with what events should be sent in different scenarios, not how. This package is not suitable for Node.js, see the package pulse-sdk-js for further reading. For a comprehensive guide on what values should be tracked in what situations, see this page.
Tell pulse that a user have logged in by updating the actor
key:
hypoteticalSPiDAPI().then(userId => {
pulse('update', {
actor: {
id: userId,
// 'schibsted.com' for the Swedish SPiD installation, 'spid.no' for the
// Norwegian or the domain of the site if spid is not used, e.g 'subito.it'
realm: 'spid-realm'
}
});
});
Alternatively you can block events from being sent until the user id is resolved:
// This will block events from being sent until the promise from spidAPI() resolves
// Note that you have to handle both success and error when using promises,
// if you do not a unhandled rejected promise will block events forever!
pulse('update', {
actor: hypoteticalSPiDAPI().then(id => ({ id: id, realm: 'spid-realm' }), error => (undefined))
})
You can log out the user by setting the actor
key to undefined
:
pulse('update', { actor: undefined });
Online publishing have a couple of objects that cover most sites: Article
,
Frontpage
and SalesPoster
. The correct object type should be set in pulse:
pulse('update', { object: { type: 'Article' } });
pulse('update', { object: { type: 'Frontpage' } });
pulse('update', { object: { type: 'SalesPoster' } });
Marketplaces have two main object types: ClassifiedAd
and Listing
(search result).
pulse('update', {
object: {
type: 'ClassifiedAd',
id: '..ad-id..',
category: 'car',
name: 'Audi A4'
}
});
pulse('update', {
object: {
type: 'Listing',
id: 'search-query-string',
query: 'search-query-string',
items: [{
type: 'ClassifiedAd',
category: 'car',
name: 'Audi A4'
}]
}
});
Engage requires many specific fields to be added to normal page view events. Some of these fields can be populated by Engage plugins and some have to be provided by the site directly. Below are two examples of the two most typical scenarios, Front page views with visibility tracking of DOM elements and Article views with following activity "pings" that measures how long users stay on a page.
In the examples below a named tracker is created and used. This means it will not influence the default tracker instance. Weather or not this is desirable will wary.
The two first plugins enabled are Local (Storage) History and Engage default values. Please see the repective repos if the default values are appoproiate for your site.
<script>
pulse('init', 'client-id', null, null, 'engage-tracker');
pulse('engage-tracker.update', {
object: {
type: 'Article',
custom: {
'spt:updated': '2017-10-19T13:15:36.026Z',
'spt:authors': ['Name Nameson'],
'spt:wordCount': 2300,
'spt:imageUrl': 'http://example.org/img.jpg',
'spt:shareUrl': {
facebook: 'http://example.org/page',
twitter: 'http://example.org/page'
},
'spt:previewUrl': {
http: 'http://example.org/page',
https: 'https://example.org/page'
}
}
}
});
pulse('engage-tracker.require', 'localHistory');
pulse('engage-tracker.require', 'populateDefaultValues');
pulse('engage-tracker.trackPageView');
pulse('engage-tracker.require', 'engagementTime')
</script>
<script src="//sdk.pulse.schibsted.com/plugins/activity-pings/plugin.js" async></script>
<script src="//sdk.pulse.schibsted.com/plugins/local-history/plugin.js" async></script>
<script src="//sdk.pulse.schibsted.com/plugins/populate-default-engage-values/plugin.js" async></script>
<script>
pulse('init', 'client-id', null, null, 'engage-tracker');
pulse('engage-tracker.update', {
object: {
type: 'Frontpage',
custom: {
'spt:imageUrl': 'http://example.org/img.jpg',
'spt:shareUrl': {
facebook: 'http://example.org/page',
twitter: 'http://example.org/page'
},
'spt:previewUrl': {
http: 'http://example.org/page',
https: 'https://example.org/page'
}
}
}
});
pulse('engage-tracker.require', 'localHistory');
pulse('engage-tracker.require', 'populateDefaultValues');
pulse('engage-tracker.trackPageView');
pulse('engage-tracker.require', 'visibilityTracking');
</script>
<script src="//sdk.pulse.schibsted.com/plugins/visibility-tracking/plugin.js" async></script>
<script src="//sdk.pulse.schibsted.com/plugins/local-history/plugin.js" async></script>
<script src="//sdk.pulse.schibsted.com/plugins/populate-default-engage-values/plugin.js" async></script>
Tracking clicks and views of DOM elements requires a bit of extra effort because
of the schema describing
UIElements. The
object id requires both the type and of the context and the id if the element,
e.g sdrn:aftenposten:frontpage:Forsiden:element:qnp89z
. The
builder
that formats the object type UIElement
expects there is a target
as a part
of the event. The type
and id
of the target
will be included in the id
of the UIElement
. This can be tracked in the following way:
pulse('track', 'engagementEvent', {
action: 'Click',
object: {
type: 'UIElement',
id: 'button-x'
},
target: {
type: 'Article',
id: 'article-id-123',
url: 'http://edxample.org'
}
})
Instead of manually specifying the target
you can pull out the default
object
from the tracker and set it as target
. This can be done in the
following way:
pulse(tracker => {
tracker.evaluateEventInputs().then(eventDefaults => {
tracker.track('engagementEvent', {
action: 'Click',
object: {
type: 'UIElement',
id: 'exit-link',
name: `Exit link`
},
// Setting target to the current page object
target: eventDefaults.object
})
});
});
The tracker supports using the Beacon
API in modern
browsers to send events after a user have
left the site. This is done automatically behind the scenes and can be disabled
by passing useBeaconWhenAvailable: false
to the tracker config.
Actually generating the event on the click is however not done automatically. See beacon-exit.html for a full example. A summarized example:
<script>
function sendClickEvent() {
pulse('track', 'engagementEvent', {
type: 'Engagement',
action: 'Click',
object: {
type: 'UIElement',
id: 'exit-link',
}
});
</script>
<a onClick="sendClickEvent()" href="http://other-domain.com">Leave Site</a>