Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First Party Data module: Add new module and two submodules to populate defaults and validate ortb2 #6452

Merged
merged 21 commits into from
May 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b30a795
Creating fpd module
mmoschovas Feb 22, 2021
da3f25f
Continued work on FPD module.
mmoschovas Mar 17, 2021
732cb02
Revert userId update. Committed in error
mmoschovas Mar 17, 2021
eaaddbb
Added first party data unit tests and fixed bug
mmoschovas Mar 22, 2021
8e41471
Added an unsubscribe for tests to run properly
mmoschovas Mar 22, 2021
5974f3f
Reworked logic to use bidderRequests hook to update global/bidder con…
mmoschovas Mar 23, 2021
5e6b754
Merge remote-tracking branch 'upstream/master' into fpd_module
mmoschovas Mar 23, 2021
f8741b4
Merge master
mmoschovas Mar 23, 2021
11492e9
Removing unused references. Fixing device data to point to device.h/d…
mmoschovas Mar 29, 2021
9f839e2
Update to include opt out configuration for enrichments/validations
mmoschovas Mar 30, 2021
ef8ed4b
Modified logic to use ortb2 configuration mapping. This will allow fo…
mmoschovas Apr 5, 2021
d9c1eda
Removed LGTM unneeded defensive code for check on object 'cur'
mmoschovas Apr 5, 2021
ae47223
Remove unused conditional
mmoschovas Apr 5, 2021
a63015c
Fix lint error
mmoschovas Apr 5, 2021
27457ad
Updates to remove currency enrichment as well as optout for user object
mmoschovas Apr 12, 2021
afd2c26
Added optout flag to user.yob and user.gender fields
mmoschovas Apr 27, 2021
5ed6071
Added test for arbitrary values
mmoschovas Apr 27, 2021
707f6a5
Broke module out into module and two submodules
mmoschovas Apr 28, 2021
ed37eaf
Merge remote-tracking branch 'upstream/master' into fpd_module
mmoschovas Apr 28, 2021
d455cd4
Updated cur to validate as an array of strings not just a string
mmoschovas May 12, 2021
110ea74
Merge remote-tracking branch 'upstream/master' into fpd_module
mmoschovas May 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,9 @@
"reconciliationRtdProvider",
"geoedgeRtdProvider",
"sirdataRtdProvider"
],
"fpdModule": [
"enrichmentFpdModule",
"validationFpdModule"
]
}
107 changes: 107 additions & 0 deletions modules/enrichmentFpdModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* This module sets default values and validates ortb2 first part data
* @module modules/firstPartyData
*/
import * as utils from '../src/utils.js';
import { submodule } from '../src/hook.js'
import { getRefererInfo } from '../src/refererDetection.js'

let ortb2 = {};
let win = (window === window.top) ? window : window.top;

/**
* Checks for referer and if exists merges into ortb2 global data
*/
function setReferer() {
if (getRefererInfo().referer) utils.mergeDeep(ortb2, { site: { ref: getRefererInfo().referer } });
}

/**
* Checks for canonical url and if exists merges into ortb2 global data
*/
function setPage() {
if (getRefererInfo().canonicalUrl) utils.mergeDeep(ortb2, { site: { page: getRefererInfo().canonicalUrl } });
}

/**
* Checks for canonical url and if exists retrieves domain and merges into ortb2 global data
*/
function setDomain() {
let parseDomain = function(url) {
if (!url || typeof url !== 'string' || url.length === 0) return;

var match = url.match(/^(?:https?:\/\/)?(?:www\.)?(.*?(?=(\?|\#|\/|$)))/i);

return match && match[1];
};

let domain = parseDomain(getRefererInfo().canonicalUrl)

if (domain) utils.mergeDeep(ortb2, { site: { domain: domain } });
}

/**
* Checks for screen/device width and height and sets dimensions
*/
function setDimensions() {
let width;
let height;

try {
width = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth;
height = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight;
} catch (e) {
width = window.innerWidth || window.document.documentElement.clientWidth || window.document.body.clientWidth;
height = window.innerHeight || window.document.documentElement.clientHeight || window.document.body.clientHeight;
}

utils.mergeDeep(ortb2, { device: { w: width, h: height } });
}

/**
* Scans page for meta keywords, and if exists, merges into site.keywords
*/
function setKeywords() {
let keywords;

try {
keywords = win.document.querySelector("meta[name='keywords']");
} catch (e) {
keywords = window.document.querySelector("meta[name='keywords']");
}

if (keywords && keywords.content) utils.mergeDeep(ortb2, { site: { keywords: keywords.content.replace(/\s/g, '') } });
}

/**
* Resets modules global ortb2 data
*/
const resetOrtb2 = () => { ortb2 = {} };

function runEnrichments() {
setReferer();
setPage();
setDomain();
setDimensions();
setKeywords();

return ortb2;
}

/**
* Sets default values to ortb2 if exists and adds currency and ortb2 setConfig callbacks on init
*/
export function initSubmodule(fpdConf, data) {
resetOrtb2();

return (!fpdConf.skipEnrichments) ? utils.mergeDeep(runEnrichments(), data) : data;
}

/** @type {firstPartyDataSubmodule} */
export const enrichmentsSubmodule = {
name: 'enrichments',
queue: 2,
init: initSubmodule
}

submodule('firstPartyData', enrichmentsSubmodule)
58 changes: 58 additions & 0 deletions modules/fpdModule/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* This module sets default values and validates ortb2 first part data
* @module modules/firstPartyData
*/
import { config } from '../../src/config.js';
import { module, getHook } from '../../src/hook.js';
import { getGlobal } from '../../src/prebidGlobal.js';
import { addBidderRequests } from '../../src/auction.js';

let submodules = [];

/**
* enable submodule in User ID
* @param {RtdSubmodule} submodule
*/
export function registerSubmodules(submodule) {
submodules.push(submodule);
}

export function init() {
let modConf = config.getConfig('firstPartyData') || {};
let ortb2 = config.getConfig('ortb2') || {};

submodules.sort((a, b) => {
return ((a.queue || 1) - (b.queue || 1));
}).forEach(submodule => {
ortb2 = submodule.init(modConf, ortb2);
});

config.setConfig({ortb2});
}

/**
* BidderRequests hook to intiate module and reset modules ortb2 data object
*/
function addBidderRequestHook(fn, bidderRequests) {
init();
fn.call(this, bidderRequests);
// Removes hook after run
addBidderRequests.getHooks({ hook: addBidderRequestHook }).remove();
}

/**
* Sets bidderRequests hook
*/
function setupHook() {
getHook('addBidderRequests').before(addBidderRequestHook);
}

module('firstPartyData', registerSubmodules);

// Runs setupHook on initial load
setupHook();

/**
* Global function to reinitiate module
*/
(getGlobal()).refreshFpd = setupHook;
46 changes: 46 additions & 0 deletions modules/fpdModule/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Overview

```
Module Name: First Party Data Module
```

# Description

Module to perform the following functions to allow for consistent set of first party data using the following submodules.

Enrichment Submodule:
- populate available data into object: referer, meta-keywords, cur

Validation Submodule:
- verify OpenRTB datatypes, remove/warn any that are likely to choke downstream readers
- verify that certain OpenRTB attributes are not specified
- optionally suppress user FPD based on the existence of _pubcid_optout


1. Module initializes on first load and set bidRequestHook
2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations dependant on submodule
3. After hook complete, it is disabled - meaning module only runs on first auction
4. To reinitiate the module, run pbjs.refreshFPD(), which allows module to rerun as if initial load


This module will automatically run first party data enrichments and validations dependant on which submodules are included. There is no configuration required. In order to load the module and submodule(s) and opt out of either enrichements or validations, use the below opt out configuration

# Module Control Configuration

```

pbjs.setConfig({
firstPartyData: {
skipValidations: true, // default to false
skipEnrichments: true // default to false
}
});

```

# Requirements

At least one of the submodules must be included in order to successfully run the corresponding above operations.

enrichmentFpdModule
validationFpdModule
125 changes: 125 additions & 0 deletions modules/validationFpdModule/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Data type map
*/
const TYPES = {
string: 'string',
object: 'object',
number: 'number',
};

/**
* Template to define what ortb2 attributes should be validated
* Accepted fields:
* -- invalid - {Boolean} if true, field is not valid
* -- type - {String} valid data type of field
* -- isArray - {Boolean} if true, field must be an array
* -- childType - {String} used in conjuction with isArray: true, defines valid type of array indices
* -- children - {Object} defines child properties needed to be validated (used only if type: object)
* -- required - {Array} array of strings defining any required properties for object (used only if type: object)
* -- optoutApplies - {Boolean} if true, optout logic will filter if applicable (currently only applies to user object)
*/
export const ORTB_MAP = {
imp: {
invalid: true
},
cur: {
type: TYPES.object,
isArray: true,
childType: TYPES.string
},
device: {
type: TYPES.object,
children: {
w: { type: TYPES.number },
h: { type: TYPES.number }
}
},
site: {
type: TYPES.object,
children: {
name: { type: TYPES.string },
domain: { type: TYPES.string },
page: { type: TYPES.string },
ref: { type: TYPES.string },
keywords: { type: TYPES.string },
search: { type: TYPES.string },
cat: {
type: TYPES.object,
isArray: true,
childType: TYPES.string
},
sectioncat: {
type: TYPES.object,
isArray: true,
childType: TYPES.string
},
pagecat: {
type: TYPES.object,
isArray: true,
childType: TYPES.string
},
content: {
type: TYPES.object,
isArray: false,
children: {
data: {
type: TYPES.object,
isArray: true,
childType: TYPES.object,
required: ['name', 'segment'],
children: {
segment: {
type: TYPES.object,
isArray: true,
childType: TYPES.object,
required: ['id'],
children: {
id: { type: TYPES.string }
}
},
name: { type: TYPES.string },
ext: { type: TYPES.object },
}
}
}
},
publisher: {
type: TYPES.object,
isArray: false
},
}
},
user: {
type: TYPES.object,
children: {
yob: {
type: TYPES.number,
optoutApplies: true
},
gender: {
type: TYPES.string,
optoutApplies: true
},
keywords: { type: TYPES.string },
data: {
type: TYPES.object,
isArray: true,
childType: TYPES.object,
required: ['name', 'segment'],
children: {
segment: {
type: TYPES.object,
isArray: true,
childType: TYPES.object,
required: ['id'],
children: {
id: { type: TYPES.string }
}
},
name: { type: TYPES.string },
ext: { type: TYPES.object },
}
}
}
}
}
Loading