Skip to content

Commit ac5c107

Browse files
Galphimblatkachov
authored and
marc_tappx
committed
Admixer ID System: add userId submodule (prebid#6238)
* Migrating to Prebid 1.0 * Migrating to Prebid 1.0 * Fix spec * add gdpr and usp * remove changes in gdpr_hello_world.html * Update gdpr_hello_world.html add spaces * add user syncs * remove comments * tests * admixer id system * admixer id system * admixer id system eids.md userId.md * admixer id system .submodules.json * admixer id system Co-authored-by: atkachov <[email protected]>
1 parent 55831de commit ac5c107

File tree

7 files changed

+315
-31
lines changed

7 files changed

+315
-31
lines changed

modules/.submodules.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"mwOpenLinkIdSystem",
2525
"tapadIdSystem",
2626
"novatiqIdSystem",
27-
"uid2IdSystem"
27+
"uid2IdSystem",
28+
"admixerIdSystem"
2829
],
2930
"adpod": [
3031
"freeWheelAdserverVideo",

modules/admixerIdSystem.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* This module adds AdmixerId to the User ID module
3+
* The {@link module:modules/userId} module is required
4+
* @module modules/admixerIdSubmodule
5+
* @requires module:modules/userId
6+
*/
7+
8+
import * as utils from '../src/utils.js'
9+
import { ajax } from '../src/ajax.js';
10+
import { submodule } from '../src/hook.js';
11+
import {getStorageManager} from '../src/storageManager.js';
12+
13+
export const storage = getStorageManager();
14+
15+
/** @type {Submodule} */
16+
export const admixerIdSubmodule = {
17+
/**
18+
* used to link submodule with config
19+
* @type {string}
20+
*/
21+
name: 'admixerId',
22+
/**
23+
* used to specify vendor id
24+
* @type {number}
25+
*/
26+
gvlid: 511,
27+
/**
28+
* decode the stored id value for passing to bid requests
29+
* @function
30+
* @param {string} value
31+
* @returns {{admixerId:string}}
32+
*/
33+
decode(value) {
34+
return { 'admixerId': value }
35+
},
36+
/**
37+
* performs action to obtain id and return a value in the callback's response argument
38+
* @function
39+
* @param {SubmoduleConfig} [config]
40+
* @param {ConsentData} [consentData]
41+
* @returns {IdResponse|undefined}
42+
*/
43+
getId(config, consentData) {
44+
const {e, p, pid} = (config && config.params) || {};
45+
if (!pid || typeof pid !== 'string') {
46+
utils.logError('admixerId submodule requires partner id to be defined');
47+
return;
48+
}
49+
const gdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0;
50+
const consentString = gdpr ? consentData.consentString : '';
51+
if (gdpr && !consentString) {
52+
utils.logInfo('Consent string is required to call admixer id.');
53+
return;
54+
}
55+
const url = `https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}${e ? `&e=${e}` : ''}${p ? `&p=${p}` : ''}${consentString ? `&cs=${consentString}` : ''}`;
56+
const resp = function(callback) {
57+
if (window.admixTMLoad && window.admixTMLoad.push) {
58+
window.admixTMLoad.push(function() {
59+
window.admixTM.retrieveVisitorId(function(visitorId) {
60+
if (visitorId) {
61+
callback(visitorId);
62+
} else {
63+
callback();
64+
}
65+
});
66+
});
67+
} else {
68+
retrieveVisitorId(url, callback);
69+
}
70+
};
71+
72+
return { callback: resp };
73+
}
74+
};
75+
function retrieveVisitorId(url, callback) {
76+
ajax(url, {
77+
success: response => {
78+
const {setData: {visitorid} = {}} = JSON.parse(response || '{}');
79+
if (visitorid) {
80+
callback(visitorid);
81+
} else {
82+
callback();
83+
}
84+
},
85+
error: error => {
86+
utils.logInfo(`admixerId: fetch encountered an error`, error);
87+
callback();
88+
}
89+
}, undefined, { method: 'GET', withCredentials: true });
90+
}
91+
92+
submodule('userId', admixerIdSubmodule);

modules/userId/eids.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ const USER_IDS_CONFIG = {
206206
getValue: function(data) {
207207
return data.id;
208208
}
209+
},
210+
211+
// Admixer Id
212+
'admixerId': {
213+
source: 'admixer.net',
214+
atype: 3
209215
}
210216
};
211217

modules/userId/eids.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,19 @@ userIdAsEids = [
171171
atype: 1
172172
}]
173173
},
174-
{
174+
{
175175
source: 'uidapi.com',
176176
uids: [{
177177
id: 'some-random-id-value',
178178
atype: 3
179179
}]
180-
}
180+
},
181+
{
182+
source: 'admixer.net',
183+
uids: [{
184+
id: 'some-random-id-value',
185+
atype: 3
186+
}]
187+
}
181188
]
182189
```

modules/userId/userId.md

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ pbjs.setConfig({
100100
},{
101101
name: 'uid2'
102102
}
103+
}, {
104+
name: 'admixerId',
105+
params: {
106+
pid: "4D393FAC-B6BB-4E19-8396-0A4813607316", // example id
107+
e: "3d400b57e069c993babea0bd9efa79e5dc698e16c042686569faae20391fd7ea", // example hashed email (sha256)
108+
p: "05de6c07eb3ea4bce45adca4e0182e771d80fbb99e12401416ca84ddf94c3eb9" //example hashed phone (sha256)
109+
},
110+
storage: {
111+
type: 'cookie',
112+
name: '__adm__admixer',
113+
expires: 30
114+
}
103115
}],
104116
syncDelay: 5000,
105117
auctionDelay: 1000
@@ -171,18 +183,18 @@ pbjs.setConfig({
171183
expires: 90, // Expiration in days
172184
refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires'
173185
},
174-
}, {
175-
name: 'nextrollId',
176-
params: {
177-
partnerId: "1009", // Set your real NextRoll partner ID here for production
178-
}
179-
}, {
186+
}, {
187+
name: 'nextrollId',
188+
params: {
189+
partnerId: "1009", // Set your real NextRoll partner ID here for production
190+
}
191+
}, {
180192
name: 'criteo',
181193
storage: { // It is best not to specify this parameter since the module needs to be called as many times as possible
182194
type: 'html5',
183195
name: '_criteoId',
184196
expires: 1
185-
}
197+
}
186198
},{
187199
name: "merkleId",
188200
params: {
@@ -196,6 +208,18 @@ pbjs.setConfig({
196208
name: "merkleId",
197209
expires: 30
198210
}
211+
}, {
212+
name: 'admixerId',
213+
params: {
214+
pid: "4D393FAC-B6BB-4E19-8396-0A4813607316", // example id
215+
e: "3d400b57e069c993babea0bd9efa79e5dc698e16c042686569faae20391fd7ea", // example hashed email (sha256)
216+
p: "05de6c07eb3ea4bce45adca4e0182e771d80fbb99e12401416ca84ddf94c3eb9" //example hashed phone (sha256)
217+
},
218+
storage: {
219+
type: 'html5',
220+
name: 'admixerId',
221+
expires: 30
222+
}
199223
}],
200224
syncDelay: 5000
201225
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {admixerIdSubmodule} from 'modules/admixerIdSystem.js';
2+
import * as utils from 'src/utils.js';
3+
import {server} from 'test/mocks/xhr.js';
4+
import {getStorageManager} from '../../../src/storageManager.js';
5+
6+
export const storage = getStorageManager();
7+
8+
const pid = '4D393FAC-B6BB-4E19-8396-0A4813607316';
9+
const getIdParams = {params: {pid: pid}};
10+
describe('admixerId tests', function () {
11+
let logErrorStub;
12+
13+
beforeEach(function () {
14+
logErrorStub = sinon.stub(utils, 'logError');
15+
});
16+
17+
afterEach(function () {
18+
logErrorStub.restore();
19+
});
20+
21+
it('should log an error if pid configParam was not passed when getId', function () {
22+
admixerIdSubmodule.getId();
23+
expect(logErrorStub.callCount).to.be.equal(1);
24+
25+
admixerIdSubmodule.getId({});
26+
expect(logErrorStub.callCount).to.be.equal(2);
27+
28+
admixerIdSubmodule.getId({params: {}});
29+
expect(logErrorStub.callCount).to.be.equal(3);
30+
31+
admixerIdSubmodule.getId({params: {pid: 123}});
32+
expect(logErrorStub.callCount).to.be.equal(4);
33+
});
34+
35+
it('should NOT call the admixer id endpoint if gdpr applies but consent string is missing', function () {
36+
let submoduleCallback = admixerIdSubmodule.getId(getIdParams, { gdprApplies: true });
37+
expect(submoduleCallback).to.be.undefined;
38+
});
39+
40+
it('should call the admixer id endpoint', function () {
41+
let callBackSpy = sinon.spy();
42+
let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback;
43+
submoduleCallback(callBackSpy);
44+
let request = server.requests[0];
45+
expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`);
46+
request.respond(
47+
200,
48+
{},
49+
JSON.stringify({})
50+
);
51+
expect(callBackSpy.calledOnce).to.be.true;
52+
});
53+
54+
it('should call callback with user id', function () {
55+
let callBackSpy = sinon.spy();
56+
let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback;
57+
submoduleCallback(callBackSpy);
58+
let request = server.requests[0];
59+
expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`);
60+
request.respond(
61+
200,
62+
{},
63+
JSON.stringify({setData: {visitorid: '571058d70bce453b80e6d98b4f8a81e3'}})
64+
);
65+
expect(callBackSpy.calledOnce).to.be.true;
66+
expect(callBackSpy.args[0][0]).to.be.eq('571058d70bce453b80e6d98b4f8a81e3');
67+
});
68+
69+
it('should continue to callback if ajax response 204', function () {
70+
let callBackSpy = sinon.spy();
71+
let submoduleCallback = admixerIdSubmodule.getId(getIdParams).callback;
72+
submoduleCallback(callBackSpy);
73+
let request = server.requests[0];
74+
expect(request.url).to.be.eq(`https://inv-nets.admixer.net/cntcm.aspx?ssp=${pid}`);
75+
request.respond(
76+
204
77+
);
78+
expect(callBackSpy.calledOnce).to.be.true;
79+
expect(callBackSpy.args[0][0]).to.be.undefined;
80+
});
81+
});

0 commit comments

Comments
 (0)