Skip to content

Commit 4eb7515

Browse files
mkikot-pxavetihon
andauthored
HUMAN Security RTD Provider (#12192)
Co-authored-by: yevhen.tykhonov <[email protected]>
1 parent 1944f84 commit 4eb7515

File tree

5 files changed

+614
-0
lines changed

5 files changed

+614
-0
lines changed

modules/.submodules.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"greenbidsRtdProvider",
8181
"growthCodeRtdProvider",
8282
"hadronRtdProvider",
83+
"humansecurityRtdProvider",
8384
"iasRtdProvider",
8485
"idWardRtdProvider",
8586
"imRtdProvider",

modules/humansecurityRtdProvider.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/**
2+
* This module adds humansecurity provider to the real time data module
3+
*
4+
* The {@link module:modules/realTimeData} module is required
5+
* The module will inject the HUMAN Security script into the context where Prebid.js is initialized, enriching bid requests with specific data to provide advanced protection against ad fraud and spoofing.
6+
* @module modules/humansecurityRtdProvider
7+
* @requires module:modules/realTimeData
8+
*/
9+
10+
import { submodule } from '../src/hook.js';
11+
import {
12+
prefixLog,
13+
mergeDeep,
14+
generateUUID,
15+
getWindowSelf,
16+
} from '../src/utils.js';
17+
import { getRefererInfo } from '../src/refererDetection.js';
18+
import { getGlobal } from '../src/prebidGlobal.js';
19+
import { loadExternalScript } from '../src/adloader.js';
20+
21+
/**
22+
* @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule
23+
* @typedef {import('../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig
24+
* @typedef {import('../modules/rtdModule/index.js').UserConsentData} UserConsentData
25+
*/
26+
27+
const SUBMODULE_NAME = 'humansecurity';
28+
const SCRIPT_URL = 'https://sonar.script.ac/prebid/rtd.js';
29+
30+
const { logInfo, logWarn, logError } = prefixLog(`[${SUBMODULE_NAME}]:`);
31+
32+
/** @type {string} */
33+
let clientId = '';
34+
35+
/** @type {boolean} */
36+
let verbose = false;
37+
38+
/** @type {string} */
39+
let sessionId = '';
40+
41+
/** @type {Object} */
42+
let hmnsData = { };
43+
44+
/**
45+
* Submodule registration
46+
*/
47+
function main() {
48+
submodule('realTimeData', /** @type {RtdSubmodule} */ ({
49+
name: SUBMODULE_NAME,
50+
51+
//
52+
init: (config, userConsent) => {
53+
try {
54+
load(config);
55+
return true;
56+
} catch (err) {
57+
logError('init', err.message);
58+
return false;
59+
}
60+
},
61+
62+
getBidRequestData: onGetBidRequestData
63+
}));
64+
}
65+
66+
/**
67+
* Injects HUMAN Security script on the page to facilitate pre-bid signal collection.
68+
* @param {SubmoduleConfig} config
69+
*/
70+
function load(config) {
71+
// By default, this submodule loads the generic implementation script
72+
// only identified by the referrer information. In the future, if publishers
73+
// want to have analytics where their websites are grouped, they can request
74+
// Client ID from HUMAN, pass it here, and it will enable advanced reporting
75+
clientId = config?.params?.clientId || '';
76+
if (clientId && (typeof clientId !== 'string' || !/^\w{3,16}$/.test(clientId))) {
77+
throw new Error(`The 'clientId' parameter must be a short alphanumeric string`);
78+
}
79+
80+
// Load/reset the state
81+
verbose = !!config?.params?.verbose;
82+
sessionId = generateUUID();
83+
hmnsData = {};
84+
85+
// We rely on prebid implementation to get the best domain possible here
86+
// In some cases, it still might be null, though
87+
const refDomain = getRefererInfo().domain || '';
88+
89+
// Once loaded, the implementation script will publish an API using
90+
// the session ID value it was given in data attributes
91+
const scriptAttrs = { 'data-sid': sessionId };
92+
const scriptUrl = `${SCRIPT_URL}?r=${refDomain}${clientId ? `&c=${clientId}` : ''}`;
93+
94+
loadExternalScript(scriptUrl, SUBMODULE_NAME, onImplLoaded, null, scriptAttrs);
95+
}
96+
97+
/**
98+
* The callback to loadExternalScript
99+
* Establishes the bridge between this RTD submodule and the loaded implementation
100+
*/
101+
function onImplLoaded() {
102+
// We then get a hold on this script using the knowledge of this session ID
103+
const wnd = getWindowSelf();
104+
const impl = wnd[`sonar_${sessionId}`];
105+
if (typeof impl !== 'object' || typeof impl.connect !== 'function') {
106+
verbose && logWarn('onload', 'Unable to access the implementation script');
107+
return;
108+
}
109+
110+
// And set up a bridge between the RTD submodule and the implementation.
111+
// The first argument is used to identify the caller.
112+
// The callback might be called multiple times to update the signals
113+
// once more precise information is available.
114+
impl.connect(getGlobal(), onImplMessage);
115+
}
116+
117+
/**
118+
* The bridge function will be called by the implementation script
119+
* to update the token information or report errors
120+
* @param {Object} msg
121+
*/
122+
function onImplMessage(msg) {
123+
if (typeof msg !== 'object') {
124+
return;
125+
}
126+
127+
switch (msg.type) {
128+
case 'hmns': {
129+
hmnsData = mergeDeep({}, msg.data || {});
130+
break;
131+
}
132+
case 'error': {
133+
logError('impl', msg.data || '');
134+
break;
135+
}
136+
case 'warn': {
137+
verbose && logWarn('impl', msg.data || '');
138+
break;
139+
}
140+
case 'info': {
141+
verbose && logInfo('impl', msg.data || '');
142+
break;
143+
}
144+
}
145+
}
146+
147+
/**
148+
* onGetBidRequestData is called once per auction.
149+
* Update the `ortb2Fragments` object with the data from the injected script.
150+
*
151+
* @param {Object} reqBidsConfigObj
152+
* @param {function} callback
153+
* @param {SubmoduleConfig} config
154+
* @param {UserConsentData} userConsent
155+
*/
156+
function onGetBidRequestData(reqBidsConfigObj, callback, config, userConsent) {
157+
// Add device.ext.hmns to the global ORTB data for all vendors to use
158+
// At the time of writing this submodule, "hmns" is an object defined
159+
// internally by humansecurity, and it currently contains "v1" field
160+
// with a token that contains collected signals about this session.
161+
mergeDeep(reqBidsConfigObj.ortb2Fragments.global, { device: { ext: { hmns: hmnsData } } });
162+
callback();
163+
}
164+
165+
/**
166+
* Exporting local (and otherwise encapsulated to this module) functions
167+
* for testing purposes
168+
*/
169+
export const __TEST__ = {
170+
SUBMODULE_NAME,
171+
SCRIPT_URL,
172+
main,
173+
load,
174+
onImplLoaded,
175+
onImplMessage,
176+
onGetBidRequestData
177+
};
178+
179+
main();

0 commit comments

Comments
 (0)