forked from prebid/Prebid.js
-
Notifications
You must be signed in to change notification settings - Fork 1
/
quantcastIdSystem.js
230 lines (201 loc) · 7.59 KB
/
quantcastIdSystem.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/**
* This module adds QuantcastID to the User ID module
* The {@link module:modules/userId} module is required
* @module modules/quantcastIdSystem
* @requires module:modules/userId
*/
import {submodule} from '../src/hook.js'
import {getStorageManager} from '../src/storageManager.js';
import { triggerPixel, logInfo } from '../src/utils.js';
import { uspDataHandler, coppaDataHandler, gdprDataHandler } from '../src/adapterManager.js';
import {MODULE_TYPE_UID} from '../src/activities/modules.js';
/**
* @typedef {import('../modules/userId/index.js').Submodule} Submodule
*/
const QUANTCAST_FPA = '__qca';
const DEFAULT_COOKIE_EXP_DAYS = 392; // (13 months - 2 days)
const DAY_MS = 86400000;
const PREBID_PCODE = 'p-KceJUEvXN48CE';
const QSERVE_URL = 'https://pixel.quantserve.com/pixel';
const QUANTCAST_VENDOR_ID = '11';
const PURPOSE_DATA_COLLECT = '1';
const PURPOSE_PRODUCT_IMPROVEMENT = '10';
const QC_TCF_REQUIRED_PURPOSES = [PURPOSE_DATA_COLLECT, PURPOSE_PRODUCT_IMPROVEMENT];
const QC_TCF_CONSENT_FIRST_PURPOSES = [PURPOSE_DATA_COLLECT];
const QC_TCF_CONSENT_ONLY_PUPROSES = [PURPOSE_DATA_COLLECT];
const GDPR_PRIVACY_STRING = gdprDataHandler.getConsentData();
const US_PRIVACY_STRING = uspDataHandler.getConsentData();
const MODULE_NAME = 'quantcastId';
export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME});
export function firePixel(clientId, cookieExpDays = DEFAULT_COOKIE_EXP_DAYS) {
// check for presence of Quantcast Measure tag _qevent obj and publisher provided clientID
if (!window._qevents && clientId && clientId != '') {
var fpa = storage.getCookie(QUANTCAST_FPA);
var fpan = '0';
var domain = quantcastIdSubmodule.findRootDomain();
var now = new Date();
var usPrivacyParamString = '';
var firstPartyParamStrings;
var gdprParamStrings;
if (!fpa) {
var et = now.getTime();
var expires = new Date(et + (cookieExpDays * DAY_MS)).toGMTString();
var rand = Math.round(Math.random() * 2147483647);
fpa = `B0-${rand}-${et}`;
fpan = '1';
storage.setCookie(QUANTCAST_FPA, fpa, expires, '/', domain, null);
}
firstPartyParamStrings = `&fpan=${fpan}&fpa=${fpa}`;
gdprParamStrings = '&gdpr=0';
if (GDPR_PRIVACY_STRING && typeof GDPR_PRIVACY_STRING.gdprApplies === 'boolean' && GDPR_PRIVACY_STRING.gdprApplies) {
gdprParamStrings = `gdpr=1&gdpr_consent=${GDPR_PRIVACY_STRING.consentString}`;
}
if (US_PRIVACY_STRING && typeof US_PRIVACY_STRING === 'string') {
usPrivacyParamString = `&us_privacy=${US_PRIVACY_STRING}`;
}
let url = QSERVE_URL +
'?d=' + domain +
'&client_id=' + clientId +
'&a=' + PREBID_PCODE +
usPrivacyParamString +
gdprParamStrings +
firstPartyParamStrings;
triggerPixel(url);
}
};
export function hasGDPRConsent(gdprConsent) {
// Check for GDPR consent for purpose 1 and 10, and drop request if consent has not been given
// Remaining consent checks are performed server-side.
if (gdprConsent && typeof gdprConsent.gdprApplies === 'boolean' && gdprConsent.gdprApplies) {
if (!gdprConsent.vendorData) {
return false;
}
return checkTCFv2(gdprConsent.vendorData);
}
return true;
}
export function checkTCFv2(vendorData, requiredPurposes = QC_TCF_REQUIRED_PURPOSES) {
var gdprApplies = vendorData.gdprApplies;
var purposes = vendorData.purpose;
var vendors = vendorData.vendor;
var qcConsent = vendors && vendors.consents && vendors.consents[QUANTCAST_VENDOR_ID];
var qcInterest = vendors && vendors.legitimateInterests && vendors.legitimateInterests[QUANTCAST_VENDOR_ID];
var restrictions = vendorData.publisher ? vendorData.publisher.restrictions : {};
if (!gdprApplies) {
return true;
}
return requiredPurposes.map(function(purpose) {
var purposeConsent = purposes.consents ? purposes.consents[purpose] : false;
var purposeInterest = purposes.legitimateInterests ? purposes.legitimateInterests[purpose] : false;
var qcRestriction = restrictions && restrictions[purpose]
? restrictions[purpose][QUANTCAST_VENDOR_ID]
: null;
if (qcRestriction === 0) {
return false;
}
// Seek consent or legitimate interest based on our default legal
// basis for the purpose, falling back to the other if possible.
if (
// we have positive vendor consent
qcConsent &&
// there is positive purpose consent
purposeConsent &&
// publisher does not require legitimate interest
qcRestriction !== 2 &&
// purpose is a consent-first purpose or publisher has explicitly restricted to consent
(QC_TCF_CONSENT_FIRST_PURPOSES.indexOf(purpose) != -1 || qcRestriction === 1)
) {
return true;
} else if (
// publisher does not require consent
qcRestriction !== 1 &&
// we have legitimate interest for vendor
qcInterest &&
// there is legitimate interest for purpose
purposeInterest &&
// purpose's legal basis does not require consent
QC_TCF_CONSENT_ONLY_PUPROSES.indexOf(purpose) == -1 &&
// purpose is a legitimate-interest-first purpose or publisher has explicitly restricted to legitimate interest
(QC_TCF_CONSENT_FIRST_PURPOSES.indexOf(purpose) == -1 || qcRestriction === 2)
) {
return true;
}
return false;
}).reduce(function(a, b) {
return a && b;
}, true);
}
/**
* tests if us_privacy consent string is present, us_privacy applies, and notice_given / do-not-sell is set to yes
* @returns {boolean}
*/
export function hasCCPAConsent(usPrivacyConsent) {
if (
usPrivacyConsent &&
typeof usPrivacyConsent === 'string' &&
usPrivacyConsent.length == 4 &&
usPrivacyConsent.charAt(1) == 'Y' &&
usPrivacyConsent.charAt(2) == 'Y'
) {
return false
}
return true;
}
/** @type {Submodule} */
export const quantcastIdSubmodule = {
/**
* used to link submodule with config
* @type {string}
*/
name: MODULE_NAME,
/**
* Vendor id of Quantcast
* @type {Number}
*/
gvlid: QUANTCAST_VENDOR_ID,
/**
* decode the stored id value for passing to bid requests
* @function
* @returns {{quantcastId: string} | undefined}
*/
decode(value) {
return value;
},
/**
* read Quantcast first party cookie and pass it along in quantcastId
* @function
* @returns {{id: {quantcastId: string} | undefined}}}
*/
getId(config) {
// Consent signals are currently checked on the server side.
let fpa = storage.getCookie(QUANTCAST_FPA);
const coppa = coppaDataHandler.getCoppa();
if (coppa || !hasCCPAConsent(US_PRIVACY_STRING) || !hasGDPRConsent(GDPR_PRIVACY_STRING)) {
var expired = new Date(0).toUTCString();
var domain = quantcastIdSubmodule.findRootDomain();
logInfo('QuantcastId: Necessary consent not present for Id, exiting QuantcastId');
storage.setCookie(QUANTCAST_FPA, '', expired, '/', domain, null);
return undefined;
}
const configParams = (config && config.params) || {};
const storageParams = (config && config.storage) || {};
var clientId = configParams.clientId || '';
var cookieExpDays = storageParams.expires || DEFAULT_COOKIE_EXP_DAYS;
// Callbacks on Event Listeners won't trigger if the event is already complete so this check is required
if (document.readyState === 'complete') {
firePixel(clientId, cookieExpDays);
} else {
window.addEventListener('load', function () {
firePixel(clientId, cookieExpDays);
});
}
return { id: fpa ? { quantcastId: fpa } : undefined };
},
eids: {
'quantcastId': {
source: 'quantcast.com',
atype: 1
},
}
};
submodule('userId', quantcastIdSubmodule);