diff --git a/modules/dacIdSystem.js b/modules/dacIdSystem.js index 73b5c7420cf..856e1976bb1 100644 --- a/modules/dacIdSystem.js +++ b/modules/dacIdSystem.js @@ -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 = { /** @@ -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); diff --git a/modules/dacIdSystem.md b/modules/dacIdSystem.md index 0239b4557e9..c78b8ff2741 100644 --- a/modules/dacIdSystem.md +++ b/modules/dacIdSystem.md @@ -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 @@ -17,7 +17,10 @@ The following configuration parameters are available: pbjs.setConfig({ userSync: { userIds: [{ - name: 'dacId' + name: 'dacId', + params: { + 'oid': '55h67qm4ck37vyz5' + } }] } }); @@ -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"` | \ No newline at end of file diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index c07911c9e1f..4c5f1687641 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -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 diff --git a/test/spec/modules/dacIdSystem_spec.js b/test/spec/modules/dacIdSystem_spec.js index d78b4a69000..0246e65a310 100644 --- a/test/spec/modules/dacIdSystem_spec.js +++ b/test/spec/modules/dacIdSystem_spec.js @@ -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 () { @@ -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 () { diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index e41764b2876..59bd4a80081 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -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'; @@ -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'); }); });