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

Dac ID Module: #9040

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
172 changes: 145 additions & 27 deletions modules/dacIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,111 @@
* @requires module:modules/userId
*/

import { submodule } from '../src/hook.js';
import { getStorageManager } from '../src/storageManager.js';
import {
logError,
logInfo,
logWarn
} from '../src/utils.js';
import {
ajax
} from '../src/ajax.js'
import {
submodule
} from '../src/hook.js';
import {
getStorageManager
} from '../src/storageManager.js';

export const storage = getStorageManager();

export const cookieKey = '_a1_f';
export const FUUID_COOKIE_NAME = '_a1_f';
export const AONEID_COOKIE_NAME = '_a1_d';
export const API_URL = 'https://penta.a.one.impact-ad.jp/aud';
const COOKIES_EXPIRES = 60 * 60 * 24 * 1000; // 24h
const LOG_PREFIX = 'User ID - dacId submodule: ';

/**
* @returns {{fuuid: string, uid: string}} -
*/
function getCookieId() {
return {
fuuid: storage.getCookie(FUUID_COOKIE_NAME),
uid: storage.getCookie(AONEID_COOKIE_NAME)
};
}

/**
* set uid to cookie.
* @param {string} uid -
* @returns {void} -
*/
function setAoneidToCookie(uid) {
if (uid) {
const expires = new Date(Date.now() + COOKIES_EXPIRES).toUTCString();
storage.setCookie(
AONEID_COOKIE_NAME,
uid,
expires,
'none'
);
}
}

/**
* @param {string} oid -
* @param {string} fuuid -
* @returns {string} -
*/
function getApiUrl(oid, fuuid) {
return `${API_URL}?oid=${oid}&fu=${fuuid}`;
}

/**
* @param {string} oid -
* @param {string} fuuid -
* @returns {{callback: function}} -
*/
function fetchAoneId(oid, fuuid) {
return {
callback: (callback) => {
const ret = {
fuuid,
uid: undefined
};
const callbacks = {
success: (response) => {
if (response) {
try {
const responseObj = JSON.parse(response);
if (responseObj.error) {
logWarn(LOG_PREFIX + 'There is no permission to use API: ' + responseObj.error);
return callback(ret);
}
if (!responseObj.uid) {
logWarn(LOG_PREFIX + 'AoneId is null');
return callback(ret);
}
ret.uid = responseObj.uid;
setAoneidToCookie(ret.uid);
} catch (error) {
logError(LOG_PREFIX + error);
}
}
callback(ret);
},
error: (error) => {
logError(LOG_PREFIX + error);
callback(ret);
}
};
const apiUrl = getApiUrl(oid, fuuid);
ajax(apiUrl, callbacks, undefined, {
method: 'GET',
withCredentials: true
});
},
};
}

export const dacIdSystemSubmodule = {
/**
Expand All @@ -20,38 +119,57 @@ export const dacIdSystemSubmodule = {
name: 'dacId',

/**
* performs action to obtain id
* @function
* @returns { {id: {dacId: string}} | undefined }
* decode the stored id value for passing to bid requests
* @param { {fuuid: string, uid: string} } id
* @returns { {dacId: {fuuid: string, dacId: string} } | undefined }
*/
getId: function() {
const newId = storage.getCookie(cookieKey);
if (!newId) {
return undefined;
}
const result = {
dacId: newId
decode(id) {
if (id && typeof id === 'object') {
return {
dacId: {
fuuid: id.fuuid,
id: id.uid
}
}
}
return {id: result};
},

/**
* decode the stored id value for passing to bid requests
* performs action to obtain id
* @function
* @param { {dacId: string} } value
* @returns { {dacId: {id: string} } | undefined }
* @returns { {id: {fuuid: string, uid: string}} | undefined }
*/
decode: function(value) {
if (value && typeof value === 'object') {
const result = {};
if (value.dacId) {
result.id = value.dacId
}
return {dacId: result};
getId(config) {
const cookie = getCookieId();

if (!cookie.fuuid) {
logInfo(LOG_PREFIX + 'There is no fuuid in cookie')
return undefined;
}
return undefined;
},

}
if (cookie.fuuid && cookie.uid) {
logInfo(LOG_PREFIX + 'There is fuuid and AoneId in cookie')
return {
id: {
fuuid: cookie.fuuid,
uid: cookie.uid
}
};
}

const configParams = (config && config.params) || {};
if (!configParams || typeof configParams.oid !== 'string') {
logWarn(LOG_PREFIX + 'oid is not defined');
return {
id: {
fuuid: cookie.fuuid,
uid: undefined
}
};
}

return fetchAoneId(configParams.oid, cookie.fuuid);
}
};

submodule('userId', dacIdSystemSubmodule);
9 changes: 7 additions & 2 deletions modules/dacIdSystem.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## AudienceOne User ID Submodule

AudienceOne ID, provided by [D.A.Consortium Inc.](https://www.dac.co.jp/), is ID for ad targeting by using 1st party cookie.
Please contact D.A.Consortium Inc. before using this ID.
Please visit [https://solutions.dac.co.jp/audienceone](https://solutions.dac.co.jp/audienceone) and request your Owner ID to get started.

## Building Prebid with AudienceOne ID Support

Expand All @@ -17,7 +17,10 @@ The following configuration parameters are available:
pbjs.setConfig({
userSync: {
userIds: [{
name: 'dacId'
name: 'dacId',
params: {
'oid': '55h67qm4ck37vyz5'
}
}]
}
});
Expand All @@ -26,3 +29,5 @@ pbjs.setConfig({
| Param under userSync.userIds[] | Scope | Type | Description | Example |
| --- | --- | --- | --- | --- |
| name | Required | String | The name of this module. | `"dacId"` |
| params | Required | Object | Details of module params. | |
| params.oid | Required | String | This is the Owner ID value obtained via D.A.Consortium Inc. | `"55h67qm4ck37vyz5"` |
11 changes: 7 additions & 4 deletions modules/yieldoneBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,13 @@ export const spec = {
}

// DACID
const dacId = deepAccess(bidRequest, 'userId.dacId.id');
if (isStr(dacId) && !isEmpty(dacId)) {
payload.dac_id = dacId;
payload.fuuid = dacId;
const fuuid = deepAccess(bidRequest, 'userId.dacId.fuuid');
const dacid = deepAccess(bidRequest, 'userId.dacId.id');
if (isStr(fuuid) && !isEmpty(fuuid)) {
payload.fuuid = fuuid;
}
if (isStr(dacid) && !isEmpty(dacid)) {
payload.dac_id = dacid;
}

// ID5
Expand Down
104 changes: 93 additions & 11 deletions test/spec/modules/dacIdSystem_spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { dacIdSystemSubmodule, storage, cookieKey } from 'modules/dacIdSystem.js';
import {
dacIdSystemSubmodule,
storage,
FUUID_COOKIE_NAME,
AONEID_COOKIE_NAME
} from 'modules/dacIdSystem.js';
import { server } from 'test/mocks/xhr.js';

const DACID_DUMMY_VALUE = 'dacIdTest';
const FUUID_DUMMY_VALUE = 'dacIdTest';
const AONEID_DUMMY_VALUE = '12345'
const DACID_DUMMY_OBJ = {
dacId: DACID_DUMMY_VALUE
fuuid: FUUID_DUMMY_VALUE,
uid: AONEID_DUMMY_VALUE
};

describe('dacId module', function () {
Expand All @@ -23,24 +31,98 @@ describe('dacId module', function () {
''
]

const configParamTestCase = {
params: {
oid: [
'637c1b6fc26bfad0', // valid
'e8316b39c08029e1' // invalid
]
}
}

describe('getId()', function () {
it('should return the uid when it exists in cookie', function () {
getCookieStub.withArgs(cookieKey).returns(DACID_DUMMY_VALUE);
it('should return undefined when oid & fuuid not exist', function () {
// no oid, no fuuid
const id = dacIdSystemSubmodule.getId();
expect(id).to.be.deep.equal({id: {dacId: DACID_DUMMY_VALUE}});
expect(id).to.equal(undefined);
});

cookieTestCasesForEmpty.forEach(testCase => it('should return the uid when it not exists in cookie', function () {
getCookieStub.withArgs(cookieKey).returns(testCase);
it('should return fuuid when oid not exists but fuuid exists', function () {
// no oid, fuuid
getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(FUUID_DUMMY_VALUE);
const id = dacIdSystemSubmodule.getId();
expect(id).to.be.deep.equal(undefined);
expect(id).to.be.deep.equal({
id: {
fuuid: FUUID_DUMMY_VALUE,
uid: undefined
}
});
});

it('should return fuuid when oid is invalid but fuuid exists', function () {
// invalid oid, fuuid, no AoneId
getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(FUUID_DUMMY_VALUE);
const id = dacIdSystemSubmodule.getId(configParamTestCase.params.oid[1]);
expect(id).to.be.deep.equal({
id: {
fuuid: FUUID_DUMMY_VALUE,
uid: undefined
}
});
});

cookieTestCasesForEmpty.forEach(testCase => it('should return undefined when fuuid not exists', function () {
// valid oid, no fuuid, no AoneId
getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(testCase);
const id = dacIdSystemSubmodule.getId(configParamTestCase.params.oid[0]);
expect(id).to.equal(undefined);
}));

it('should return AoneId when AoneId not exists', function () {
// valid oid, fuuid, no AoneId
getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(FUUID_DUMMY_VALUE);
const callbackSpy = sinon.spy();
const callback = dacIdSystemSubmodule.getId({params: {oid: configParamTestCase.params.oid[0]}}).callback;
callback(callbackSpy);
const request = server.requests[0];
request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({'uid': AONEID_DUMMY_VALUE}));
expect(callbackSpy.lastCall.lastArg).to.deep.equal({fuuid: 'dacIdTest', uid: AONEID_DUMMY_VALUE});
});

cookieTestCasesForEmpty.forEach(testCase => it('should return undefined when AoneId not exists & API result is empty', function () {
// valid oid, fuuid, no AoneId, API result empty
getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(FUUID_DUMMY_VALUE);
const callbackSpy = sinon.spy();
const callback = dacIdSystemSubmodule.getId({params: {oid: configParamTestCase.params.oid[0]}}).callback;
callback(callbackSpy);
const request = server.requests[0];
request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({'uid': testCase}));
expect(callbackSpy.lastCall.lastArg).to.deep.equal({fuuid: 'dacIdTest', uid: undefined});
}));

it('should return the fuuid & AoneId when they exist', function () {
// valid oid, fuuid, AoneId
getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(FUUID_DUMMY_VALUE);
getCookieStub.withArgs(AONEID_COOKIE_NAME).returns(AONEID_DUMMY_VALUE);
const id = dacIdSystemSubmodule.getId(configParamTestCase.params.oid[0]);
expect(id).to.be.deep.equal({
id: {
fuuid: FUUID_DUMMY_VALUE,
uid: AONEID_DUMMY_VALUE
}
});
});
});

describe('decode()', function () {
it('should return the uid when it exists in cookie', function () {
it('should return fuuid & AoneId when they exist', function () {
const decoded = dacIdSystemSubmodule.decode(DACID_DUMMY_OBJ);
expect(decoded).to.be.deep.equal({dacId: {id: DACID_DUMMY_VALUE}});
expect(decoded).to.be.deep.equal({
dacId: {
fuuid: FUUID_DUMMY_VALUE,
id: AONEID_DUMMY_VALUE
}
});
});

it('should return the undefined when decode id is not "string"', function () {
Expand Down
5 changes: 2 additions & 3 deletions test/spec/modules/yieldoneBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { expect } from 'chai';
import { spec } from 'modules/yieldoneBidAdapter.js';
import { newBidder } from 'src/adapters/bidderFactory.js';
import { deepClone } from 'src/utils.js';

const ENDPOINT = 'https://y.one.impact-ad.jp/h_bid';
const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync';
Expand Down Expand Up @@ -428,12 +427,12 @@ describe('yieldoneBidAdapter', function() {
const bidRequests = [
{
params: {placementId: '0'},
userId: {dacId: {id: 'dacId_sample'}},
userId: {dacId: {fuuid: 'fuuid_sample', id: 'dacId_sample'}},
},
];
const request = spec.buildRequests(bidRequests, bidderRequest);
expect(request[0].data.fuuid).to.equal('fuuid_sample');
expect(request[0].data.dac_id).to.equal('dacId_sample');
expect(request[0].data.fuuid).to.equal('dacId_sample');
});
});

Expand Down