# Middleware helper functions

OnExtraction only

Middleware helper functions are available for the OnExtraction hook only.

Middleware helper functions allow data extraction from third-party JavaScript given a document.

By default, third-party JavaScript is not executed, so using these functions you will be able to mock the window object and execute targeted scripts that will populate the mocked object with the data you need to retrieve.

The two helper functions are installMock and execute.

To enable them, add the helpers property as an argument of the hook.

export const onExtraction: onExtractionFunction<ChartbeatProps> = async ({ document, helpers }): Promise<ChartbeatProps | undefined> => ...

TIP

The helper functions are available as the helpers property of the ExtractionArguments, an object passed to the hook on runtime.

# installMock

Use it to get a reference to the window property we are interested in.

Its first parameter is a createMock function. It will mock the object we want to access and it's created from an interface defined in the same Middleware. The second parameter is the name of the target property on the Window object.

interface CustomProp {
  targetData: string;
}

const config = helpers.installMock(
    createMock<CustomProp>(),
    'customProp'
  );

TIP

In this example, we expect a window.customProp object containing a targetData property.

# execute

Use it to run the target JS script.

Its only parameter is a string. The method will execute the script that contains the text matching the input parameter.

helpers.execute('_sf_async_config');

TIP

The execute method parses all the scripts until there's a match with the input string.

# Use helpers to mock a window object

In this example, we'll use a tenant that has a third-party JavaScript that when executed, populates the sfConfig property on the Window object, which contains key information we need to retrieve for our Analytics Provider, precisely the Author and Section parameters.

As we don't have access to the Window object in the OnExtraction hook, we'll use the installMock helper to get a reference to the window property we are interested in.

First, create an interface and name it after the window property you want to mock.

interface SFConfig {
  author: string;
  section: string;
  uid: number,
}

Then, call the installMock method, passing as parameters:

  • The createMock function with the interface you just created.
  • A string with the window property you want to retrieve.
  const config = helpers.installMock(
    createMock<SFConfig>(),
    'sfConfig'
  );

Afterward, use the execute method to run the target script. As a parameter, add a string that partially matches the content of the script.

helpers.execute('_sf_async_config');

Pattern

The parameter of the execute function is a pattern partly matching the content in the script. As most of the scripts are plain <script> tags that don't have attributes, CSS selectors can't be used.

Once the script has run, it will have modified window.sfConfig and its values will be automatically available via the config object.

Your Middleware implementation should look like that:

import { ChartbeatProps } from '@marfeel/analytics-providers-chartbeat';
import { onExtractionFunction } from '@marfeel/middlewares-types';
import { createMock } from 'ts-auto-mock';

interface SFConfig {
  author: string;
  section: string;
  uid: number,
}

export const onExtraction: onExtractionFunction<ChartbeatProps> = async ({ document, helpers }): Promise<ChartbeatProps | undefined> => {
  const config = helpers.installMock(
    createMock<SFConfig>(),
    'sfConfig'
  );

  helpers.execute('_sf_async_config');

  return {
    vars: {
      author: config.author,
      section: config.section
    }
  };
};

# Use helpers to access arguments

You can also use the mock system to access variables used by a third-party script. This is extremely helpful when you have a script where some parameters are passed to a function and you want to extract those values.

<script>
  var id = 'SPHWPJS';
  var params = {
    adsvolume: 100,
    autostart: true,
    html5: true,
    url: 'https://mdstrm.com/video/5f07248bc1d4bc07b4f40991.m3u8',
    volume: 100
  };

  play(id, params);
</script>

Here, you can use a combination of installMock and execute to access the values from the script, much like you would when writing a test.

First, we define a type Play that matches the signature of the function we are interested in.

type Play = (id: string, params: object) => void;

Then we use installMock to create a mock of the play function on the Window object.

const mock = helpers.installMock(
  createMock<Play>(),
  'play'
);

Once we have the mock set up, we'll find the script containing the text play(id, params); and execute it.

helpers.execute('play(id, params);');

Now, we can use the mock object to access the values passed to the function via mock.play.args. It will contain an array of all the calls to the function, and each element will be an array of the arguments passed.

const [[id, params]] = mock.play.args;

In the end, the Middleware should look like that:

import { onExtractionFunction } from '@marfeel/middlewares-types';
import { createMock } from 'ts-auto-mock';

export const onExtraction: onExtractionFunction<ChartbeatProps> = async ({ document, helpers }): Promise<ChartbeatProps | undefined> => {

  type Play = (id: string, params: object) => void;

  const mock = helpers.installMock(
    createMock<Play>(),
    'play'
  );

  helpers.execute('play(id, params);');

  const [[id, params]] = mock.play.args;

  return {
    id: id,
    url: params.url
  };
};

# Use helpers to access redefined variables

You can also access variables that are defined on the window but never used as function arguments like this:

var params = {
  id: 'video-id',
  url: 'https://mdstrm.com/video/5f07248bc1d4bc07b4f40991.m3u8',
};

WARNING

This method only works for variables defined with var; not let or const as those are block-scoped.

First, we create an interface and name it after the var to mock:

interface Params {
  id: string;
  url: string;
}

TIP

Notice how the interface has the same name, params, but is in PascalCase, like all the other TyepScript interfaces!

Then, we use installMock to create a mock of the variable we want from the 3rd party script:

helpers.installmock(
  createmock<Params>(),
  'params'
);

TIP

Notice how the mock is not stored in a variable? This is because the 3rd party script we're about to execute is going to redefine the params variable anyway.

In this variable mocking mechanism, contrary to other methods, the variable value is going to be available directly in the window object.

Why do we even mock, then? Due to JavaScript binding, if we didn't define that params variable before executing the 3rd party script, it would never receive a value. We have to define that variable before the script executes, for it to get updated in our window object.

Weird? I know, but that's JavaScript.

Once we have set up the mock, we use the helpers.execute function to find the script containing the text params:

helpers.execute('params');

And we can finally use the window argument to retrieve the updated values of the variable:

const { id, url } = window.params;

In the end, the Middleware should look like this:

import { onExtractionFunction } from '@marfeel/middlewares-types';
import { createMock } from 'ts-auto-mock';

interface Params {
  id: string;
  url: string;
}

export const onExtraction: onExtractionFunction<ChartbeatProps> = async ({ window, document, helpers }): Promise<ChartbeatProps | undefined> => {

  helpers.installmock(
    createmock<params>(),
    'params'
  );

  helpers.execute('params');

  const { id, url } = window.params;

  return {
    id,
    params
  };
};