Getting started

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).

Installation

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'
  • Obtain a read token for the PackageCloud android repository by contacting DIAZ, ask diaz@npo.nl
  • Add the following dependencies to your 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
}

Usage

Now that you have the SDK installed on your app or website, let’s get started with configuration and sending some events.

SDK Configuration

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.

Party ID

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()
}

PageTracker and page view events

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.

Other trackers and events

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

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.

Export and transfer your session

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,
)};