-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SemantIQ RTD Provider: initial release
- Loading branch information
Alexandr Kim
committed
Jan 22, 2025
1 parent
57e1767
commit d958dd2
Showing
4 changed files
with
627 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
Oops, something went wrong.