Skip to content

Commit

Permalink
SemantIQ RTD Provider: initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandr Kim committed Jan 22, 2025
1 parent 9355e47 commit 81af548
Show file tree
Hide file tree
Showing 4 changed files with 627 additions and 0 deletions.
1 change: 1 addition & 0 deletions modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"qortexRtdProvider",
"reconciliationRtdProvider",
"relevadRtdProvider",
"semantiqRtdProvider",
"sirdataRtdProvider",
"symitriDapRtdProvider",
"timeoutRtdProvider",
Expand Down
214 changes: 214 additions & 0 deletions modules/semantiqRtdProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { MODULE_TYPE_RTD } from '../src/activities/modules.js';
import { ajax } from '../src/ajax.js';
import { submodule } from '../src/hook.js';
import { getStorageManager } from '../src/storageManager.js';
import { getWindowLocation, logError, logInfo, logWarn, mergeDeep } from '../src/utils.js';

/**
* @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule
*/

const MODULE_NAME = 'realTimeData';
const SUBMODULE_NAME = 'semantiq';

const LOG_PREFIX = '[SemantIQ RTD Module]: ';
const KEYWORDS_URL = 'https://api.adnz.co/api/ws-semantiq/page-keywords';
const STORAGE_KEY = `adnz_${SUBMODULE_NAME}`;
const AUDIENZZ_COMPANY_ID = 1;

const DEFAULT_TIMEOUT = 1000;

export const storage = getStorageManager({
moduleType: MODULE_TYPE_RTD,
moduleName: SUBMODULE_NAME,
});

/**
* Gets SemantIQ keywords from local storage.
* @param {string} pageUrl
* @returns {Object.<string, string | string[]>}
*/
const getStorageKeywords = (pageUrl) => {
try {
const storageValue = JSON.parse(storage.getDataFromLocalStorage(STORAGE_KEY));

if (storageValue?.url === pageUrl) {
return storageValue.keywords;
}

return null;
} catch (error) {
logError('Unable to get SemantiQ keywords from local storage', error);

return null;
}
};

/**
* Gets URL of the current page.
* @returns {string}
*/
const getPageUrl = () => getWindowLocation().href;

/**
* Gets tenant IDs based on the customer company ID
* @param {number} customerCompanyId
* @returns {number[]}
*/
const getTenantIds = (customerCompanyId) => {
const requiredTenantIds = [AUDIENZZ_COMPANY_ID];

if (customerCompanyId) {
return [...requiredTenantIds, customerCompanyId];
}

return requiredTenantIds;
};

/**
* Gets keywords from cache or SemantIQ service.
* @param {Object} params
* @returns {Promise<Object.<string, string | string[]>>}
*/
const getKeywords = (params) => new Promise((resolve, reject) => {
const pageUrl = getPageUrl();
const storageKeywords = getStorageKeywords(pageUrl);

if (storageKeywords) {
return resolve(storageKeywords);
}

const { companyId } = params;
const tenantIds = getTenantIds(companyId);
const searchParams = new URLSearchParams();

searchParams.append('url', pageUrl);
searchParams.append('tenantIds', tenantIds.join(','));

const requestUrl = `${KEYWORDS_URL}?${searchParams.toString()}`;

const callbacks = {
success(responseText, response) {
try {
if (response.status !== 200) {
throw new Error('Invalid response status');
}

const data = JSON.parse(responseText);

if (!data) {
throw new Error('Failed to parse the response');
}

storage.setDataInLocalStorage(STORAGE_KEY, JSON.stringify({ url: pageUrl, keywords: data }));
resolve(data);
} catch (error) {
reject(error);
}
},
error(error) {
reject(error);
}
}

ajax(requestUrl, callbacks);
});

/**
* Converts a single key-value pair to an ORTB keyword string.
* @param {string} key
* @param {string | string[]} value
* @returns {string}
*/
export const convertSemantiqKeywordToOrtb = (key, value) => {
if (Array.isArray(value) && value.length) {
return value.map((valueItem) => `${key}=${valueItem}`).join(',');
}

return `${key}=${value.length ? value : 'none'}`;
};

/**
* Converts SemantIQ keywords to ORTB format.
* @param {Object.<string, string | string[]>} keywords
* @returns {string}
*/
export const getOrtbKeywords = (keywords) => Object.entries(keywords).map((entry) => {
const [key, values] = entry;

return convertSemantiqKeywordToOrtb(key, values);
}).join(',');

/**
* Module init
* @param {Object} config
* @param {Object} userConsent
* @return {boolean}
*/
const init = (config, userConsent) => true;

/**
* Receives real-time data from SemantIQ service.
* @param {Object} reqBidsConfigObj
* @param {function} onDone
* @param {Object} moduleConfig
*/
const getBidRequestData = (
reqBidsConfigObj,
onDone,
moduleConfig,
) => {
let isDone = false;

const { params = {} } = moduleConfig || {};
const { timeout = DEFAULT_TIMEOUT } = params;

try {
logInfo(LOG_PREFIX, { reqBidsConfigObj });

const { adUnits = [] } = reqBidsConfigObj;

if (!adUnits.length) {
logWarn(LOG_PREFIX, 'No ad units found in the request');
isDone = true;
onDone();
}

getKeywords(params)
.then((keywords) => {
const ortbKeywords = getOrtbKeywords(keywords);
const siteKeywords = reqBidsConfigObj.ortb2Fragments?.global?.site?.keywords;
const updatedGlobalOrtb = { site: { keywords: [siteKeywords, ortbKeywords].filter(Boolean).join(',') } };

mergeDeep(reqBidsConfigObj.ortb2Fragments.global, updatedGlobalOrtb);
})
.catch((error) => {
logError(LOG_PREFIX, error);
})
.finally(() => {
isDone = true;
onDone();
});
} catch (error) {
logError(LOG_PREFIX, error);
isDone = true;
onDone();
}

setTimeout(() => {
if (!isDone) {
logWarn(LOG_PREFIX, 'Timeout exceeded');
isDone = true;
onDone();
}
}, timeout);
}

/** @type {RtdSubmodule} */
export const semantiqRtdSubmodule = {
name: SUBMODULE_NAME,
getBidRequestData,
init,
};

submodule(MODULE_NAME, semantiqRtdSubmodule);
46 changes: 46 additions & 0 deletions modules/semantiqRtdProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Overview

**Module Name:** Semantiq Rtd Provider
**Module Type:** Rtd Provider
**Maintainer:** [Audienzz](https://audienzz.com)

## Description

This module retrieves real-time data from the SemantIQ service and populates ORTB data.

You need to obtain a company ID from [Audienzz](https://audienzz.com) for the module to function properly. Contact [[email protected]](mailto:[email protected]) for details.

## Integration

1. Include the module into your `Prebid.js` build.

```sh
gulp build --modules='rtdModule,semantiqRtdProvider,...'
```

1. Configure the module via `pbjs.setConfig`.

```js
pbjs.setConfig({
...
realTimeData: {
dataProviders: [
{
name: 'semantiq',
waitForIt: true,
params: {
companyId: 12345,
timeout: 1000,
},
},
],
},
});
```

## Parameters

| Name | Required | Description | Type | Default value | Example |
| --------- | -------- | ------------------------------------------------------------ | -------- | ------------- | --------------------- |
| companyId | Yes | Company ID obtained from [Audienzz](https://audienzz.com). | number | - | 12345 |
| timeout | No | The maximum time to wait for a response in milliseconds. | number | 1000 | 3000 |
Loading

0 comments on commit 81af548

Please sign in to comment.