# How to create an ad server provider

WARNING

  • All the ad server providers must be defined within its own repository, including the custom ones.
  • Please, before starting the development of a new ad server, ask ALOT team to create the corresponding repository.
  • Custom ad server providers are not compatible with legacy tenants (non-XPlib). When possible, migrate these tenants to XPLib before creating a legacy custom ad server for them.

# Preliminary steps

  1. Ensure the ad server hasn't been created yet by looking for adserver-providers-[adServerName] in the MarfeelReleases repository (opens new window).

  2. Ask ALOT team to create a new repository for the ad server. You can do it by posting a new message to the ALOT discussion board (opens new window) on GitHub. Please give them as many details as you can:

    • Name of the ad server
    • List of tenants that are going to use it
    • Is the ad server compatible with AMP?
    • If it's a custom ad server, what makes it necessary? Is there already a core version?

# Scaffolding

  1. Once created, clone the repository to your local environment:
git clone git@github.com:Marfeel/NEW_AD_SERVER_REPOSITORY.git
cd NEW_AD_SERVER_REPOSITORY/

It will contain a CODEOWNERS file, a .github folder with the files needed to build and deploy the ad server and a package.json with all the dependencies required to scaffold it.

  1. Install the dependencies:
npm install
  1. Run the CLI scaffold command to generate a preview version of all the required files to develop the ad server:
npm run scaffold

# Implementation

Ad server providers are implemented as an ECMASript module (opens new window) that exposes a Javascript class with some predefined methods.

The entry point of the ad server implementation can be found at src/index.js and it must export a Javascript class with at least 3 mandatory methods.

export default class AdServerName {
  constructor() {}
  define() {}
  display() {}
  destroy() {} //Optional
}

Another auxiliary files can be created too, if necessary:

  • src/helpers.js
  • src/constants.js
  • etc.

# Mandatory methods

# constructor

All the ad server providers are initialized once per placement so that there is a different class instance for every placement. The exact execution time depends on the placement loading strategy.

The constructor receives two parameters:

  • adConfiguration is a JSON object with the configuration specified for the adServer in inventory.json. For example:
{
  "type": "mgid",
  "publisher": "news365.co.za",
  "widget": "178159",
  "container": "M285129ScriptRootC178159"
}
  • marfeelEnvironment contains several utilities that can be used to install scripts and retrieve session information from Marfeel. They are injected into the provider to avoid exposing its source code and to keep the ad server implementation decoupled from core.
{
  consent: {
    getGoogleConsent()
  },
  location: {
    href,
    environment,
    level,
    getSection()
  },
  scripts: {
    installScript(),
    installScriptInNode()
  }
}

Install the ad server library and store the ad configuration in the constructor, so that it can be used later by other methods.

constructor({ publisher, widgetId }, marfeelEnvironment) {
  this.adServerName = AD_SERVER_NAME;
  this.publisher = publisher;
  this.widgetId = widgetId;
  this.marfeel = marfeelEnvironment;

  this.$initialized = this.marfeel.scripts.installScript(AD_SERVER_LIBRARY, AD_SERVER_NAME);
}

# define()

Define is responsible for defining the HTML markup required to display the ad.

It doesn't receive any parameter and must return a Promise resolved with a string containing the markup.

define() {
  return new Promise(
      `<div class='ad_server_class'
      data-publisher='${this.publisher}'
      data-page-url='${this.marfeel.location.href}'
      data-widget-id='${this.widgetId}'></div>`
  );
}

# display()

Display is responsible for triggering the load of the ad. Typically, its actions may be:

  • pushing an object to a command queue,
  • installing a script,
  • doing an HTTP request,
  • etc.

It doesn't receive any parameter and must return a Promise resolved with a boolean (true if the ad has been actually loaded and false otherwise).

Example 1:

display() {
  return this.$intialized.then(() => {
    const command = {
      action: 'loadAd'
      publisher: this.publisher,
      widgetId: this.widgetId
    };

    window._adServerCommanQueue.push(command);

    return true;
  });
}

Example 2:

display() {
  return this.marfeel.scripts.installScriptInNode(SORUCE, NODE_SELECTOR, ID)
      .then(() => true)
      .catch(() => false);
}

TIP

For some ad servers the ad is loaded as soon as the markup is generated by the define() method, so they don't require to perform any action in the display() function. In those cases it's ok to just return a resolved Promise:

display() {
  return Promise.resolve(true);
}

If you are not able to know if the ad has loaded succesfully, return true.

# Optional methods

# destroy()

Destroy is responsible for removing the ad server "leftovers" so that the system is ready to load another ad with the same configuration. Typically, this means:

  • removing properties from the window object,
  • sending an HTTP request to notify the ad has been deleted,
  • etc.

For most of the ad servers, this method is not necessary.

It doesn't receive any parameter and shouldn't return anything.

destroy() {
  delete window._adServerCommanQueue;
}

WARNING

destroy() must not be used to clean the HTML node used to display the ad, this is already done by the MadCleaner module at Core level.

# External dependencies

Ad server providers can have external dependencies in addition to the marfeelEnvironment object that is injected into the constructor. They must be distributed as Node packages and defined within the dependencies field in the provider's package.json:

"dependencies": {
  "@marfeel/touch-utils": "^1.0.7-snapshot.21.0"
}

There is already a @marfeel/touch-utils (opens new window) package with common utilities that can be used from any provider. It contains helpers for arrays, scripts, and URLs among others and should cover the needs of most of the ad servers. You can check the source code (opens new window) for more information.

WARNING

Please note that dependencies are not shared among providers, every ad server will have its dependencies compiled within its production bundle. Be careful when working with them to keep the production bundle as light as possible.

# Unit tests

Ad server provider packages must contain unit tests to be deployed, otherwise, the production build process will fail and the package won't be published. They have to be written using JEST (opens new window) and should cover the public API of the ad server.

The test for the ad server implementation entry point is mandatory and can be found at src/index.test.js but there can be other test files too:

  • src/helpers.test.js
  • src/adserver-utils.test.js
  • etc.

Example: MGID unit tests (opens new window)

You can run the test suite by executing the following command at the ad server's root directory:

npx adserver-providers test

# JSON Schemas

In addition to its implementation and unit tests, every ad server provider package must contain a JSON schema (opens new window) to be suitable for production. The schema can be found at schema/index.json and describes which properties and values can be used to configure the ad server on inventory.json files.

{
  "$id": "#adservername",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "title": "Ad Server Name",
  "description": "Ad Server Name for TOUCH [and AMP]",
  "additionalProperties": true,
  "required": [
      "type",
      "publisher",
      "widgetId"
  ],
  "properties": {
      "type": {
          "const": "adservername"
      },
      "publisher": {
          "type": "string"
      },
      "widgetId": {
          "type": "string"
      },
      "width": {
          "type": ["integer","string"],
          "pattern": "^([0-9]+|\\$\\{.+\\})$"
      },
    "height": {
          "type": ["integer","string"],
          "pattern": "^([0-9]+|\\$\\{.+\\})$"
      }
  }
}

WARNING

When migrating an ad server that already existed in MarfeelXP, pay special attention to the new schema.

It must be compatible with the existing inventory.json files using it, otherwise, the incompatible tenants' build will fail due to schema validation.

In order to validate the ad server schema, at least one valid and one wrong test for it must be provided too:

schema/tests/valid/index.test.json

{
  "type" : "adservername",
  "widgetId" : "12345",
  "publisher" : "publisher"
}

schema/tests/wrong/index.test.json

{
  "type" : "wrongtype",
  "widgetId" : "12345",
  "publisher" : "publisher"
}

There can optionally be more *.test.json files for other tests like wrong-type.test.json, lacks-mandatory-property.test.json, etc.

# Debugging on playground

The Ad Server Providers CLI playground command generates a web tool that can be used to debug the ad server without having to integrate it into a tenant.

You can run the following command from the ad server's root directory in order to open the playground in your local environment:

npx adserver-providers playground:start

# Debugging on XPlib tenants

These are the steps to follow in order to integrate a local version of an ad server provider into an XPlib tenant:

  1. Generate a development bundle of the ad server provider by running the following command from its root directory:
npx adserver-providers build:dev
  1. Clone the Media Group repository and install its dependencies by executing the following:
glue clone MEDIAGROUP
cd MEDIAGROUP
npm install
  1. Add the ad server to the dependencies list of the Media Group's package.json:
"dependencies": {
  "@marfeel/adserver-providers-adservername": "^1.0.0"
}
  1. Mark the ad server as a linkable dependency by running the npm link (opens new window) command from its root directory:
npm link
  1. Link the local version of the ad server with the tenant by executing the following command from the Media Group root directory:
npm link @marfeel/adserver-providers-adservername
  1. Compile the tenant by running the following command from the tenant's root:
npm run build

# Extractor

All ad server providers must have an Extractor Provider. It allows Alfred to detect this ad server on the future tenants' page. The extractor must be implemented in the file extractor/index.ts.

Remember to add unit tests to the extractor code. You can run these tests using the same unit test command mentioned above:

npx adserver-providers test

Example: Adsense extractor (opens new window)

# Acid Tests

The Ad Server Providers CLI comes with an acid-test command that can be used to check that everything is in order before deploying the ad server. Since this command uses the production bundle to perform some tests, it's also recommended to execute the build command before:

npx adserver-providers build
npx adserver-providers acid-test

In addition to all the standard acid tests requirements, ad server provider acid tests check the following:

  • The ad server exposes an adServerName instance property with the name of the ad server.
  • The DOM is not manipulated during the execution of the ad server.
  • define() and display() methods don't take more than 10ms to get resolved.
  • There is one extractor/index.ts file with the entry point of the Provider Extractor.
  • There is one extractor/index.test.ts file next to the Extractor entry point with at least one unit test for it.

# Skipping Acid Tests

WARNING

Acid Tests guarantee providers correctness and performance, they should never be avoided, except for extraordinary circumstances and always under ALOT's Team approval.

Although most of the ad servers must pass the acid test to be published, some may contain singular attributes that make them incompatible with certain tests. For example, ScriptLoaderAdServer (opens new window) cannot have an extractor, since its library is not always downloaded from the same domain and, therefore, there is no way to identify the ad server analyzing its HTTP requests.

For those cases, it's possible to create an acid-test.json file in the root folder of the ad server provider containing a skip JSON object with the list of test that must be skipped:

{
  "skip" : {
    "integrity/extractor": true
  }
}

The keys of the skip object must match one of the following:

  • integrity/dom
  • integrity/contract
  • integrity/extractor
  • integrity/schema
  • integrity/scripts

CODEOWNER restricted

This file is protected by CODEOWNERS and must be used only in extraordinary cases, so it's recommended to ask ALOT (opens new window) before using it.

# Deployment

  1. If the ad server has already been deployed before, make sure you've increased its version in the package.json file, following the SemVer spec (opens new window).
  2. Open a Pull Request and merge your commit to the master branch.
  3. The build process will start immediately after the merge. You can follow the deployment status at the Actions tab of the ad server repository.
  4. If everything goes well, the ad server package will be published. If you run npm i @marfeel/adserver-providers-[ad-server-name] inside the the tenant folder, the ad server package should be added to the package.json dependencies successfully.
  5. The ad server's playground will be published to GitHub Pages, under the following URL: https://marfeel.github.io/[ad-server-repository-name]/[ad-server-name]. For example Mgid's playground at GitHub pages (opens new window).

# Compatibility with legacy tenants

Ad server providers are not compatible with legacy tenants. Please, migrate the tenant to XPlib first if you need to use a provider on it.