diff --git a/integrationExamples/gpt/cstg_example.html b/integrationExamples/gpt/cstg_example.html index 8ca049a0ed0..2ca8b43b9de 100644 --- a/integrationExamples/gpt/cstg_example.html +++ b/integrationExamples/gpt/cstg_example.html @@ -29,8 +29,8 @@ function updateUID2GuiElements() { console.log('Updating displayed values.'); const uid2 = pbjs.getUserIds().uid2; - document.querySelector('#uid2TargetedAdvertisingReady').innerText = uid2 ? 'yes' : 'no'; - document.querySelector('#uid2AdvertisingToken').innerText = uid2 ? String(uid2.id) : ''; + document.querySelector('#uid2TargetedAdvertisingReady').innerText = uid2 && !uid2.optout ? 'yes' : 'no'; + document.querySelector('#uid2AdvertisingToken').innerText = uid2 && !uid2.optout ? String(uid2.id) : uid2 && uid2.optout ? 'Optout' : ''; if (!uid2) { document.querySelector('#uid2LoginForm').style['display'] = 'block'; document.querySelector('#uid2ClearStorageForm').style['display'] = 'none';; diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 32d2322e9bd..061f0f981e5 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -109,6 +109,10 @@ function decodeImpl(value) { const result = { uid2: { id: value } }; return result; } + if (value.latestToken === 'optout') { + _logInfo('Found optout token. Refresh is unavailable for this token.'); + return { uid2: { optout: true } }; + } if (Date.now() < value.latestToken.identity_expires) { return { uid2: { id: value.latestToken.advertising_token } }; } diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index f3702069d10..808a61cb388 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -187,11 +187,15 @@ if (FEATURES.UID2_CSTG) { clientSideTokenGenerator = { isCSTGOptionsValid(maybeOpts, _logWarn) { if (typeof maybeOpts !== 'object' || maybeOpts === null) { - _logWarn('CSTG opts must be an object'); + _logWarn('CSTG is not being used, but is included in the Prebid.js bundle. You can reduce the bundle size by passing "--disable UID2_CSTG" to the Prebid.js build.'); return false; } const opts = maybeOpts; + if (!opts.serverPublicKey && !opts.subscriptionId) { + _logWarn('CSTG has been enabled but its parameters have not been set.'); + return false; + } if (typeof opts.serverPublicKey !== 'string') { _logWarn('CSTG opts.serverPublicKey must be a string'); return false; diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js index 901e0c57e32..6e5011164cb 100644 --- a/test/spec/modules/uid2IdSystem_spec.js +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -25,6 +25,7 @@ const initialToken = `initial-advertising-token`; const legacyToken = 'legacy-advertising-token'; const refreshedToken = 'refreshed-advertising-token'; const clientSideGeneratedToken = 'client-side-generated-advertising-token'; +const optoutToken = 'optout-token'; const legacyConfigParams = {storage: null}; const serverCookieConfigParams = { uid2ServerCookie: publisherCookieName }; @@ -32,6 +33,7 @@ const newServerCookieConfigParams = { uid2Cookie: publisherCookieName }; const cstgConfigParams = { serverPublicKey: 'UID2-X-L-24B8a/eLYBmRkXA9yPgRZt+ouKbXewG2OPs23+ov3JC8mtYJBCx6AxGwJ4MlwUcguebhdDp2CvzsCgS9ogwwGA==', subscriptionId: 'subscription-id' } const makeUid2IdentityContainer = (token) => ({uid2: {id: token}}); +const makeUid2OptoutContainer = (token) => ({uid2: {optout: true}}); let useLocalStorage = false; const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({ userSync: { auctionDelay: auctionDelayMs, userIds: [{name: 'uid2', params: {storage: useLocalStorage ? 'localStorage' : 'cookie', ...params}}] }, debug, ...extraSettings @@ -49,6 +51,7 @@ const getFromAppropriateStorage = () => { const expectToken = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(makeUid2IdentityContainer(token)); const expectLegacyToken = (bid) => expect(bid.userId).to.deep.include(makeUid2IdentityContainer(legacyToken)); const expectNoIdentity = (bid) => expect(bid).to.not.haveOwnProperty('userId'); +const expectOptout = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(makeUid2OptoutContainer(token)); const expectGlobalToHaveToken = (token) => expect(getGlobal().getUserIds()).to.deep.include(makeUid2IdentityContainer(token)); const expectGlobalToHaveNoUid2 = () => expect(getGlobal().getUserIds()).to.not.haveOwnProperty('uid2'); const expectNoLegacyToken = (bid) => expect(bid.userId).to.not.deep.include(makeUid2IdentityContainer(legacyToken)); @@ -64,6 +67,7 @@ const apiUrl = 'https://prod.uidapi.com/v2/token' const refreshApiUrl = `${apiUrl}/refresh`; const headers = { 'Content-Type': 'application/json' }; const makeSuccessResponseBody = (responseToken) => btoa(JSON.stringify({ status: 'success', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: responseToken } })); +const makeOptoutResponseBody = (token) => btoa(JSON.stringify({ status: 'optout', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: token } })); const cstgApiUrl = `${apiUrl}/client-generate`; const testCookieAndLocalStorage = (description, test, only = false) => { @@ -120,6 +124,7 @@ describe(`UID2 module`, function () { const configureUid2Response = (apiUrl, httpStatus, response) => server.respondWith('POST', apiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); const configureUid2ApiSuccessResponse = (apiUrl, responseToken) => configureUid2Response(apiUrl, 200, makeSuccessResponseBody(responseToken)); const configureUid2ApiFailResponse = (apiUrl) => configureUid2Response(apiUrl, 500, 'Error'); + const configureUid2CstgResponse = (httpStatus, response) => server.respondWith('POST', cstgApiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); // Runs the provided test twice - once with a successful API mock, once with one which returns a server error const testApiSuccessAndFailure = (act, apiUrl, testDescription, failTestDescription, only = false, responseToken = refreshedToken) => { const testFn = only ? it.only : it; @@ -442,6 +447,15 @@ describe(`UID2 module`, function () { }); }); }); + it('Should receive an optout response when the user has opted out.', async function() { + const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true); + configureUid2CstgResponse(200, makeOptoutResponseBody(optoutToken)); + config.setConfig(makePrebidConfig({ uid2Token, ...cstgConfigParams, email: 'optout@test.com' })); + apiHelpers.respondAfterDelay(1, server); + + const bid = await runAuction(); + expectOptout(bid, optoutToken); + }); describe(`when the response doesn't arrive before the auction timer`, function() { testApiSuccessAndFailure(async function() { config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' }));