This guide will walk you through the necessary steps to implement the NPOTag SDK, and start sending your first events. All of the code snippets below will be available for each platform SDK: iOS (Swift), Android (Kotlin) and Web (Typescript).
To install the NPOTag SDK as a dependency of your app or website, simply follow the below steps for your platform:
The web sdk is available via CDN:
<html>
<head>
...
</head>
<body>
// For development:
<script src="https://npotag.npo-data.nl/prod/npo-tag.js"></script>
// For production:
<script src="https://npotag.npo-data.nl/prod/npo-tag.min.js"></script>
...
</body>
</html>
You can also load the script asynchronously. In this case you should wait until the script is loaded before trying to access npotag, as shown below:
<html>
<head>
...
<script lang="text/javascript">
let tag;
// Don't do this, npotag will be undefined at this point
tag = npotag.newTag(...);
function onTagReady() {
// Initialise tag here, npo-tag.min.js will have been loaded and npotag is defined
tag = npotag.newTag(...);
}
</script>
</head>
<body>
<script src="https://npotag.npo-data.nl/prod/npo-tag.min.js" async onload="onTagReady()"></script>
...
</body>
</html>
The web sdk is available as an npm package via a private repository:
If you want to use the package you have to authenticate for this private repository. You need to request an authentication token. This can be done by contacting the DIAZ team (diaz@npo.nl)
You can translate the Authentication Token into a READ token that is valid for a short time by running:
UNIQUE_ID=`hostname -f` && curl -XPOST --data "name=${UNIQUE_ID}" https://<Token>@packagecloud.io/install/repositories/npo-data/npotag-v2-web/tokens.text
When you have received your READ token you need to configure npm to use it. You can do this by editing your .npmrc file to hold the following:
always-auth=true
@npotag:registry=https://packagecloud.io/npo-data/npotag-v2-web/npm/
//packagecloud.io/npo-data/npotag-v2-web/npm/:_authToken={PUT_YOUR_TOKEN_HERE}
NOTE: Starting from version 2.0.0 the NPM scope has changed from
@npo
to@npotag
.
Than you can install the package through npm
npm i @npotag/tag
Only installation via Cocoapods is supported for now. Add the following line to your app’s Podfile and run pod install
:
pod 'NPOTag', :git => 'https://github.com/npo-topspin/npotag-v2-ios'
If you wish to install a specific version of the SDK instead of the latest available one, use the following:
pod 'NPOTag', :git => 'https://github.com/npo-topspin/npotag-v2-ios', :tag => '1.5.2'
build.gradle
file:repositories {
maven {
url "https://packagecloud.io/priv/${token}/npo-data/npotag-v2-android/maven2"
}
}
dependencies {
implementation "nl.npo.tag.sdk:npo-tag-core:<sdk-version>"
implementation "nl.npo.tag.sdk:npo-tag-govolte:<sdk-version>" // optional
implementation "nl.npo.tag.sdk:npo-tag-at-internet:<sdk-version>" // optional
}
Now that you have the SDK installed on your app or website, let’s get started with configuration and sending some events.
The first thing you need to do is to configure the SDK by setting the different global properties that will be sent for each event, select the destinations to which you want to send those events, and optionally enable debug mode for additional informative logging.
You can also disable any functionality related to NMO-DAM measurement by setting disableNMODAM
to true
. This will prevent generation of the NMO ID and make sure it isn’t sent with pageview measurements.
Properties shared between all events are defined in the NPOContext
object. Once set, these properties will be immutable. For more information on context objects and implementation details, please refer to the General Information page.
For traditional applications we recommend that you create an instance of NPOTag
in the window.onload()
function:
<html>
<head>
...
<script lang="text/javascript">
window.onload = function() {
const tag = npotag.newTag(
// Set properties shared by all events
{
brand: 'brand',
brand_id: 1,
platform: 'site', // or 'tvapp' for smarttv
platform_version: '1.2.3',
disableNmoDam: false, // only set this property to true if you will handle NMO DAM measurements yourself
},
// Create the tag plugins you want to use
[
npotag.newGovoltePlugin(),
npotag.newATInternetPlugin(),
]
);
};
</script>
</head>
...
</html>
For single-page-applications we recommend that you maintain an instance of NPOTag
globally. For example in _app.tsx
in NextJS:
// _app.tsx
import { newTag, newGovoltePlugin, newATInternetPlugin, NPOTag } from '@npotag/tag';
// NOTE: The result of `newTag` will be undefined if the browser window is unavailable
// (e.g. in server-side rendering)
export const npoTagInstance: NPOTag | undefined = newTag(
// Set properties shared by all events
{
brand: 'brand',
brand_id: 1,
platform: 'site', // or 'tvapp' for smarttv
platform_version: '1.2.3',
disableNmoDam: false, // only set this property to true if you will handle NMO DAM measurements yourself
},
// Create the tag plugins you want to use
[newGovoltePlugin(), newATInternetPlugin()]
);
For iOS we recommend that you setup the SDK in your AppDelegate’s didFinishLaunchingWithOptions
method.
func application(_ application: UIApplication, didFinishLaunchingWithOptions ...) -> Bool {
// ...
// Set properties shared by all events
let sharedContext = NPOContext(
brand: "Product name",
brandId: 4,
plaform: "app",
platformVersion: "1.2.3",
disableNMODAM: false // Set this to true to disable everything related to NMO DAM
)
// Instantiate plugins that you wish to send events with
let govoltePlugin = GovoltePlugin()
let atInternetPlugin = ATInternetPlugin()
// Will create and configure the shared NPOTag instance
NPOTag.configure(
withSharedContext: sharedContext,
plugins: [ // Pass the plugins you wish to send events with
govoltePlugin,
atInternetPlugin
],
isDebugEnabled: false // Set this to true to enable debug logging
)
return true
}
Fluent Builder Pattern is used to create an instance of NpoTag
. That means all required parameters have to be provided in strict order and there is no way to call build()
before providing all required values.
Instance of NpoTag
should be created as soon as possible to allow it to send potential events left in the queue from a previous session.
class App : Application() {
lateinit var npoTag: NpoTag
override fun onCreate() {
super.onCreate()
npoTag = NpoTag.builder()
.withContext(this)
.withBrand(
brand = "brand",
brandId = 12345
)
.withPlatform(
platform = "app",
version = "1.2.3"
)
.withPluginsFactory { pluginContext ->
setOf(
LoggerPlugin(pluginContext),
GovoltePlugin(
pluginContext = pluginContext,
baseUrl = "https://topspin.npo.nl/",
),
ATInternetPlugin(pluginContext)
)
}
.withEnvironment(EnvironmentType.DEV)
.withDebug(true)
.withLogger(logger) // Optional. Provide your own Logger implementation if you want to handle logs yourself
.withDisabledNmoDam(false)
.build()
}
}
Once the SDK is configured with an NPOContext
instance and desired plugins are enabled, you can start sending events.
Anonymous user activity across sessions is tracked by means of a party id. This is generated by NPOTag and stored in a _dvp
cookie. You can retrieve the party id as follows:
<html>
<head>
...
<script lang="text/javascript">
window.onload = function() {
const tag = npotag.newTag(...);
const partyId = tag.getParty();
};
</script>
</head>
...
</html>
const npoTagInstance: NPOTag | undefined = newTag(...);
const partyId = npoTagInstance.getParty();
let partyId = NPOTag.shared.partyID
getPartyId()
is a time consuming operation and will block current thread, because it involves reading and writing the PartyId
to SharedPreferences
.
It’s recommended to use coroutines as in the example below to prevent the main thread from blocking.
launch(Dispatchers.IO) {
val partyId = npoTag.getPartyId()
}
As mentioned in the General Information page, events are sent via trackers. A tracker is an object responsible for sending events related to a certain context level. For instance, PageTracker
will allow you to send events tied to a page, StreamTracker
will allow you to send events tied to a video being streamed.
Below is how to create a PageTracker
and send page view events tied to a certain page. It will need a PageContext
object that will list all of the properties that will be shared by all events sent from this page.
<html>
<head>
...
<script lang="text/javascript">
window.onload = function() {
...
// Create a PageTracker object, passing it the NPOTag and a PageContext object
const tracker = npotag.newPageTracker(tag, {
page: 'profile_page',
chapter_1: 'home',
chapter_2: 'profile',
// etc...
});
// Send a page view event
tracker.pageView();
};
</script>
</head>
...
</html>
// Create a PageTracker object, passing it the NPOTag and a PageContext object
const tracker = newPageTracker(tag, {
page: 'profile_page',
chapter_1: 'home',
chapter_2: 'profile',
// etc...
});
// Send a page view event
tracker.pageView();
// Create a context object with all property values
// All of the properties are optional
let context = PageContext(
page: "page",
chapter1: "chapter1",
chapter2: "chapter2",
chapter3: "chapter3",
broadcasters: "broadcasters",
program: "program",
contentContextId: "contextId",
queryContext: "queryContext",
condition: "condition",
errorCode: "errorCode",
customLabel1: "label1",
customLabel2: "label2",
customLabel3: "label3",
customLabel4: "label4",
customLabel5: "label5",
location: "location",
referrer: "referrer"
)
// Instantiate the tracker object from the NPOTag shared instance
let tracker = NPOTag.shared.newPageTracker(with: context)
// Send a page view event
tracker.pageView()
// Instantiate the tracker object from the NpoTag instance
val tracker = npoTag.pageTrackerBuilder()
.withPageName("home")
.withContentContextId("homeContextId")
.withCondition("condition")
.withQueryContext("query")
.withErrorCode("errorContext")
.withBroadcasters("broadcaster")
.withProgram("program")
.withChapters(
chapter1 = "C1",
chapter2 = "C2",
chapter3 = "C3"
)
.withCustomLabels(
customLabel1 = "L1",
customLabel2 = "L2",
customLabel3 = "L3",
customLabel4 = "L4",
customLabel5 = "L5"
)
.build()
// Send a page view event
tracker.pageView()
Once created the tracker’s context is immutable, if you need to send events with new properties (because user has switched page for instance), you will need a new tracker instance with a different PageContext
.
The ways this SDK is intended to be used is that you should have one PageTracker
instance per page that is kept alive for the whole lifecycle of the page. Once a new page is reached, a new tracker should be instantiated.
See the contexts documentation for a detailed list of properties available for each context class.
Trackers other than PageTracker
will need to be instantiated from a PageTracker
instance because the events they send will also be tied to a page, so the properties from the parent PageContext
will be inferred. This is why PageTracker
has methods to create other trackers like StreamTracker
or RecommendationTracker
. For details on creating and using these trackers, see Sending events with trackers.
User properties in NPOContext
(user_id
, user_pseudo_id
, user_subscription
and user_profile
) are not directly accessible by implementors. In order to set values for them they will need to use the login
and logout
methods exposed by NPOTag
like so:
<html>
<head>
...
<script lang="text/javascript">
window.onload = function() {
...
// Set custom values for user properties (when a user is logged in)
tag.login({
user_profile: 'example',
user_subscription: 'free',
user_pseudoid: 'example',
user_id: 'example',
});
// Reset all properties to their default value (when a user logs out)
tag.logout();
};
</script>
</head>
...
</html>
// Set custom values for user properties (when a user is logged in)
tag.login({
user_profile: 'example',
user_subscription: 'free',
user_pseudoid: 'example',
user_id: 'example',
});
// Reset all properties to their default value (when a user logs out)
tag.logout();
// Set custom values for user properties (when a user is logged in)
NPOTag.shared.login(
userId: "example",
userProfile: "example",
userSubscription: "free",
userPseudoId: "example"
)
// Reset all properties to their default value (when a user logs out)
NPOTag.shared.logout()
// Set custom values for user properties (when a user is logged in)
npoTag.login(
userId = "example",
userProfile = "example",
userSubscription = "free",
userPseudoId = "example"
)
// Reset all properties to their default value (when a user logs out)
npoTag.logout()
Once set using the login method, user properties will be persisted for the whole platform lifecycle. They will however not be stored in persistent storage so upon a new app start or site opening, these properties will need to be reset.
If you happen to need to transfer sessions between different devices, you will probably need to use the getSessionInfo
feature. This function will generate a string containing all the necessary properties to instantiate the new session at the same point as it was. Note that ‘resuming’ a Tag session is only implemented on web, as the primary use case is to resume a session on a Chromecast device.
let serializedSession = tag.getSerializedSessionInfo();
const serializedSession = tag.getSerializedSessionInfo();
On iOS, some properties are retrieved asynchrounsly, therefore the whole functionality is built in such a way with an escaping closure
NPOTag.shared.getSessionInfo { jsonString in
guard let jsonData = jsonString!.data(using: .utf8),
let dictionary = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
return // If there's an error, the returned string will be nil
}
// Default NPOTag properties
let brand = dictionary["brand"] as? String
let brandId = dictionary["brandId"] as? Int
let platform = dictionary["platform"] as? String
let platformVersion = dictionary["platformVersion"] as? String
// Govolte plugin properties
let govoltePartyId = dictionary["govoltePartyId"] as? String
let sessionId = dictionary["sessionId"] as? String
let npoSubscription = dictionary["npoSubscription"] as? String
let npoUserId = dictionary["npoUserId"] as? String
let npoProfileId = dictionary["npoProfileId"] as? String
let npoPseudoId = dictionary["npoPseudoId"] as? String
// ATInternet plugin properties
let atVisitorId = dictionary["atVisitorId"] as? String
let avSessionId = dictionary["avSessionId"] as? String
// ...
}
This operation is blocking. Never use it on the main thread. It wont return if you do not call it on a background thread.
val sessionInfoJson = npoTag.getSerializedSessionInfo()
Resuming a session can be done using the Web SDK by calling an alternate constructor newTagFromSession
and add the serialized string:
let tag = npotag.newTagFromSession({
...,
serializedSessionInfo: serializedSessionString,
)};
const tag = npotag.newTagFromSession({
...,
serializedSessionInfo: serializedSessionString,
)};