-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
UID2 & EUID Modules: Add support for EUID and prefer localStorage for…
… both modules. (#9968) * Move the UID2 API client to its own file and refactor some of the UID2 module. * Provide local-storage option in response to complaints that the cookie can grow large enough to cause problems for publishers. * Extract a storagemanager from the UID2 ID module so that code can be re-used for EUID. * Further refactoring. Add basic EUID module based on the refactored code. Still needs tests and testing. * Factor out some shared testing code. Add EUID module tests. * Add EUID markdown docs. Some minor changes. * Fill out EUID module docs. * Rename cookie param for EUID. Fix some docs. * Some EUID docs tweaks. Change UID2 module docs to match EUID docs. Update param to use uid2Cookie instead of uid2ServerCookie (but still fall back to the old value). * Added a test and fixed a bug that caused the server only cookie config to fail for subsequent page views. * Added EUID example. * Update some tests. * Added lint rule exception - it makes sense in this case. * Add missing config for updated test. * Update expected number of Eids in test. * Remove out-of-date TODOs. * Update UID2 module to not run if GDPR applies. Update EUID to check consent. * Remove EUID from a specific test - it no longer works as that test doesn't provide consent data, and the EUID module requires it.
- Loading branch information
1 parent
382599e
commit a2bb2cb
Showing
14 changed files
with
1,008 additions
and
338 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/** | ||
* This module adds EUID ID support to the User ID module. It shares significant functionality with the UID2 module. | ||
* The {@link module:modules/userId} module is required. | ||
* @module modules/euidIdSystem | ||
* @requires module:modules/userId | ||
*/ | ||
|
||
import { logInfo, logWarn, deepAccess } from '../src/utils.js'; | ||
import {submodule} from '../src/hook.js'; | ||
import {getStorageManager} from '../src/storageManager.js'; | ||
import {MODULE_TYPE_UID} from '../src/activities/modules.js'; | ||
|
||
// RE below lint exception: UID2 and EUID are separate modules, but the protocol is the same and shared code makes sense here. | ||
// eslint-disable-next-line prebid/validate-imports | ||
import { Uid2GetId, Uid2CodeVersion } from './uid2IdSystem_shared.js'; | ||
|
||
const MODULE_NAME = 'euid'; | ||
const MODULE_REVISION = Uid2CodeVersion; | ||
const PREBID_VERSION = '$prebid.version$'; | ||
const EUID_CLIENT_ID = `PrebidJS-${PREBID_VERSION}-EUIDModule-${MODULE_REVISION}`; | ||
const GVLID_TTD = 21; // The Trade Desk | ||
const LOG_PRE_FIX = 'EUID: '; | ||
const ADVERTISING_COOKIE = '__euid_advertising_token'; | ||
|
||
// eslint-disable-next-line no-unused-vars | ||
const EUID_TEST_URL = 'https://integ.euid.eu'; | ||
const EUID_PROD_URL = 'https://prod.euid.eu'; | ||
const EUID_BASE_URL = EUID_PROD_URL; | ||
|
||
function createLogger(logger, prefix) { | ||
return function (...strings) { | ||
logger(prefix + ' ', ...strings); | ||
} | ||
} | ||
const _logInfo = createLogger(logInfo, LOG_PRE_FIX); | ||
const _logWarn = createLogger(logWarn, LOG_PRE_FIX); | ||
|
||
export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); | ||
|
||
function hasWriteToDeviceConsent(consentData) { | ||
const gdprApplies = consentData?.gdprApplies === true; | ||
const localStorageConsent = deepAccess(consentData, `vendorData.purpose.consents.1`) | ||
const prebidVendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${GVLID_TTD.toString()}`) | ||
if (gdprApplies && (!localStorageConsent || !prebidVendorConsent)) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
/** @type {Submodule} */ | ||
export const euidIdSubmodule = { | ||
/** | ||
* used to link submodule with config | ||
* @type {string} | ||
*/ | ||
name: MODULE_NAME, | ||
|
||
/** | ||
* Vendor id of The Trade Desk | ||
* @type {Number} | ||
*/ | ||
gvlid: GVLID_TTD, | ||
/** | ||
* decode the stored id value for passing to bid requests | ||
* @function | ||
* @param {string} value | ||
* @returns {{euid:{ id: string } }} or undefined if value doesn't exists | ||
*/ | ||
decode(value) { | ||
const result = decodeImpl(value); | ||
_logInfo('EUID decode returned', result); | ||
return result; | ||
}, | ||
|
||
/** | ||
* performs action to obtain id and return a value. | ||
* @function | ||
* @param {SubmoduleConfig} [configparams] | ||
* @param {ConsentData|undefined} consentData | ||
* @returns {euidId} | ||
*/ | ||
getId(config, consentData) { | ||
if (consentData?.gdprApplies !== true) { | ||
logWarn('EUID is intended for use within the EU. The module will not run when GDPR does not apply.'); | ||
return; | ||
} | ||
if (!hasWriteToDeviceConsent(consentData)) { | ||
// The module cannot operate without this permission. | ||
_logWarn(`Unable to use EUID module due to insufficient consent. The EUID module requires storage permission.`) | ||
return; | ||
} | ||
|
||
const mappedConfig = { | ||
apiBaseUrl: config?.params?.euidApiBase ?? EUID_BASE_URL, | ||
paramToken: config?.params?.euidToken, | ||
serverCookieName: config?.params?.euidCookie, | ||
storage: config?.params?.storage ?? 'localStorage', | ||
clientId: EUID_CLIENT_ID, | ||
internalStorage: ADVERTISING_COOKIE | ||
}; | ||
|
||
const result = Uid2GetId(mappedConfig, storage, _logInfo, _logWarn); | ||
_logInfo(`EUID getId returned`, result); | ||
return result; | ||
}, | ||
}; | ||
|
||
function decodeImpl(value) { | ||
if (typeof value === 'string') { | ||
_logInfo('Found server-only token. Refresh is unavailable for this token.'); | ||
const result = { euid: { id: value } }; | ||
return result; | ||
} | ||
if (Date.now() < value.latestToken.identity_expires) { | ||
return { euid: { id: value.latestToken.advertising_token } }; | ||
} | ||
return null; | ||
} | ||
|
||
// Register submodule for userId | ||
submodule('userId', euidIdSubmodule); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
## EUID User ID Submodule | ||
|
||
EUID requires initial tokens to be generated server-side. The EUID module handles storing, providing, and optionally refreshing them. The module can operate in one of two different modes: *Client Refresh* mode or *Server Only* mode. | ||
|
||
*Server Only* mode was originally referred to as *legacy mode*, but it is a popular mode for new integrations where publishers prefer to handle token refresh server-side. | ||
|
||
## Client Refresh mode | ||
|
||
This is the recommended mode for most scenarios. In this mode, the full response body from the EUID Token Generate or Token Refresh endpoint must be provided to the module. As long as the refresh token remains valid, the module will refresh the advertising token as needed. | ||
|
||
To configure the module to use this mode, you must **either**: | ||
1. Set `params.euidCookie` to the name of the cookie which contains the response body as a JSON string, **or** | ||
2. Set `params.euidToken` to the response body as a JavaScript object. | ||
|
||
### Client refresh cookie example | ||
|
||
In this example, the cookie is called `euid_pub_cookie`. | ||
|
||
Cookie: | ||
``` | ||
euid_pub_cookie={"advertising_token":"...advertising token...","refresh_token":"...refresh token...","identity_expires":1684741472161,"refresh_from":1684741425653,"refresh_expires":1684784643668,"refresh_response_key":"...response key..."} | ||
``` | ||
|
||
Configuration: | ||
``` | ||
pbjs.setConfig({ | ||
userSync: { | ||
userIds: [{ | ||
name: 'euid', | ||
params: { | ||
euidCookie: 'euid_pub_cookie' | ||
} | ||
}] | ||
} | ||
}); | ||
``` | ||
|
||
### Client refresh euidToken example | ||
|
||
Configuration: | ||
``` | ||
pbjs.setConfig({ | ||
userSync: { | ||
userIds: [{ | ||
name: 'euid', | ||
params: { | ||
euidToken: { | ||
'advertising_token': '...advertising token...', | ||
'refresh_token': '...refresh token...', | ||
// etc. - see the Sample Token below for contents of this object | ||
} | ||
} | ||
}] | ||
} | ||
}); | ||
``` | ||
|
||
## Server-Only Mode | ||
|
||
In this mode, only the advertising token is provided to the module. The module will not be able to refresh the token. The publisher is responsible for implementing some other way to refresh the token. | ||
|
||
To configure the module to use this mode, you must **either**: | ||
1. Set a cookie named `__euid_advertising_token` to the advertising token, **or** | ||
2. Set `value` to an ID block containing the advertising token. | ||
|
||
### Server only cookie example | ||
|
||
Cookie: | ||
``` | ||
__euid_advertising_token=...advertising token... | ||
``` | ||
|
||
Configuration: | ||
``` | ||
pbjs.setConfig({ | ||
userSync: { | ||
userIds: [{ | ||
name: 'euid' | ||
}] | ||
} | ||
}); | ||
``` | ||
|
||
### Server only value example | ||
|
||
Configuration: | ||
``` | ||
pbjs.setConfig({ | ||
userSync: { | ||
userIds: [{ | ||
name: 'euid' | ||
value: { | ||
'euid': { | ||
'id': '...advertising token...' | ||
} | ||
} | ||
}] | ||
} | ||
}); | ||
``` | ||
|
||
## Storage | ||
|
||
The module stores a number of internal values. By default, all values are stored in HTML5 local storage. You can switch to cookie storage by setting `params.storage` to `cookie`. The cookie size can be significant and this is not recommended, but is provided as an option if local storage is not an option. | ||
|
||
## Sample token | ||
|
||
`{`<br /> `"advertising_token": "...",`<br /> `"refresh_token": "...",`<br /> `"identity_expires": 1633643601000,`<br /> `"refresh_from": 1633643001000,`<br /> `"refresh_expires": 1636322000000,`<br /> `"refresh_response_key": "wR5t6HKMfJ2r4J7fEGX9Gw=="`<br />`}` | ||
|
||
### Notes | ||
|
||
If you are trying to limit the size of cookies, provide the token in configuration and use the default option of local storage. | ||
|
||
If you provide an expired identity and the module has a valid identity which was refreshed from the identity you provide, it will use the refreshed identity. The module stores the original token used for refreshing the token, and it will use the refreshed tokens as long as the original token matches the one supplied. | ||
|
||
If a new token is supplied which does not match the original token used to generate any refreshed tokens, all stored tokens will be discarded and the new token used instead (refreshed if necessary). | ||
|
||
You can set `params.euidApiBase` to `"https://integ.euid.eu"` during integration testing. Be aware that you must use the same environment (production or integration) here as you use for generating tokens. | ||
|
||
## Parameter Descriptions for the `usersync` Configuration Section | ||
|
||
The below parameters apply only to the EUID User ID Module integration. | ||
|
||
| Param under userSync.userIds[] | Scope | Type | Description | Example | | ||
| --- | --- | --- | --- | --- | | ||
| name | Required | String | ID value for the EUID module - `"euid"` | `"euid"` | | ||
| value | Optional, Server only | Object | An object containing the value for the advertising token. | See the example above. | | ||
| params.euidToken | Optional, Client refresh | Object | The initial EUID token. This should be `body` element of the decrypted response from a call to the `/token/generate` or `/token/refresh` endpoint. | See the sample token above. | | ||
| params.euidCookie | Optional, Client refresh | String | The name of a cookie which holds the initial EUID token, set by the server. The cookie should contain JSON in the same format as the euidToken param. **If euidToken is supplied, this param is ignored.** | See the sample token above. | | ||
| params.euidApiBase | Optional, Client refresh | String | Overrides the default EUID API endpoint. | `"https://prod.euid.eu"` _(default)_| | ||
| params.storage | Optional, Client refresh | String | Specify whether to use `cookie` or `localStorage` for module-internal storage. It is recommended to not provide this and allow the module to use the default. | `localStorage` _(default)_ | |
Oops, something went wrong.