-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Anonymised id system module #15492
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
Anonymised id system module #15492
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // the augmentation in this file only applies where the spec is part of the program | ||
| import type {} from './userId/spec.js'; | ||
|
|
||
| export type AnonymisedIdSystemModuleName = 'anonymisedId'; | ||
|
|
||
| declare module './userId/spec' { | ||
| interface UserId { | ||
| anonymisedId: string; | ||
| } | ||
|
|
||
| interface ProvidersToId { | ||
| anonymisedId: 'anonymisedId'; | ||
| } | ||
|
|
||
| interface ProviderParams { | ||
| anonymisedId: never; | ||
| } | ||
| } | ||
|
|
||
| export {}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| /** | ||
| * This module adds the Anonymised ID to the User ID module | ||
| * The {@link module:modules/userId} module is required | ||
| * @module modules/anonymisedIdSystem | ||
| * @requires module:modules/userId | ||
| */ | ||
| import { submodule } from '../src/hook.js'; | ||
| import { getStorageManager } from '../src/storageManager.js'; | ||
| import { MODULE_TYPE_UID } from '../src/activities/modules.js'; | ||
| import { logInfo, logWarn } from '../src/utils.js'; | ||
|
|
||
| const MODULE_NAME = 'anonymisedId'; | ||
| const GVLID = 1116; | ||
| const EID_SOURCE = 'anonymised.io'; | ||
| const LOG_PREFIX = 'User ID - anonymisedId submodule: '; | ||
|
|
||
| /** | ||
| * Local storage key holding the CUID. It is written by the Anonymised Marketing Tag when the user | ||
| * signs in, and removed by it on sign-out or when consent is withdrawn. This module only reads it. | ||
| */ | ||
| export const STORAGE_KEY = 'anon-cuid'; | ||
|
|
||
| /** | ||
| * Generous upper bound on the identifier length, to keep a corrupted value from bloating every | ||
| * bid request. | ||
| */ | ||
| export const MAX_ID_LENGTH = 100; | ||
|
|
||
| export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); | ||
|
|
||
| /** | ||
| * Characters that cannot occur in a raw identifier, and whose presence means the value was | ||
| * serialised rather than written as-is - a JSON object, array, or quoted scalar. Passing such a | ||
| * value on would send bidders an ID that matches nothing. | ||
| */ | ||
| const ENCODED_VALUE_CHARS = /[\s{}[\]"']/; | ||
|
|
||
| /** | ||
| * The Marketing Tag writes the CUID as a plain string. Validation is deliberately loose - it | ||
| * rejects the values that would be harmful to pass on (empty, serialised, or implausibly long) | ||
| * without pinning the identifier's format, which is owned by the tag and can change on a much | ||
| * faster release cycle than this module. | ||
| * @param {*} value | ||
| * @returns {boolean} | ||
| */ | ||
| export function isValidId(value) { | ||
| return typeof value === 'string' && | ||
| value.length > 0 && | ||
| value.length <= MAX_ID_LENGTH && | ||
| !ENCODED_VALUE_CHARS.test(value); | ||
| } | ||
|
|
||
| export const anonymisedIdSubmodule = { | ||
| /** | ||
| * used to link submodule with config | ||
| * @type {string} | ||
| */ | ||
| name: MODULE_NAME, | ||
|
|
||
| /** | ||
| * IAB Global Vendor List ID | ||
| * @type {number} | ||
| */ | ||
| gvlid: GVLID, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the storageControl module is included, its default strict rule denies keyed storage reads unless module metadata has a AGENTS.md reference: AGENTS.md:L75-L75 Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. anonymisedIdSystem declares gvlid: 1116, and metadata/compileMetadata.mjs resolves the disclosure URL from the GVL when a module doesn't set one: Vendor 1116 has deviceStorageDisclosureUrl registered as https://cdn1.anonymised.io/deviceStorage.json, which declares anon-cuid (type: "web", domains: ["*"]) — so storageControl matches it and the read is allowed. The disclosureURL property is an override for vendors without a GVL-registered URL (adplusIdSystem, jixieIdSystem); setting it here would take precedence over the GVL lookup and pin a URL the GVL is authoritative for. anonymisedRtdProvider already works this way — gvlid only.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @codex op is correct, the URL is only useful in absence of a gvlid, the build process accesses the URL in the gvl There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Summary
Testing
|
||
|
|
||
| /** | ||
| * Read the CUID that the Anonymised Marketing Tag stored on this domain. This is a synchronous | ||
| * read with no network call: when the tag has not written an ID yet - because it is not installed, | ||
| * or the user is not signed in - there is simply no ID for this page view. | ||
| * @function | ||
| * @param {Object} [config] this submodule's publisher configuration | ||
| * @returns {{id: string} | undefined} | ||
| */ | ||
| getId(config) { | ||
| if (config?.storage) { | ||
| logWarn(`${LOG_PREFIX}this module must be configured without "storage". The Anonymised Marketing Tag owns this ID and removes it on sign-out; a copy kept by Prebid.js would outlive that removal and keep sending the ID of a signed-out user.`); | ||
|
patmmccann marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| const stored = storage.getDataFromLocalStorage(STORAGE_KEY); | ||
| const cuid = typeof stored === 'string' ? stored.trim() : null; | ||
|
|
||
| if (!cuid) { | ||
| // No ID is the expected state for a signed-out user, so this is not a warning: it is also | ||
| // what a reader sees when device access is denied, and it is most of the traffic. | ||
| logInfo(`${LOG_PREFIX}no ID in localStorage["${STORAGE_KEY}"] - the user is signed out, the Anonymised Marketing Tag is not installed on this page, or device access is not permitted`); | ||
| return undefined; | ||
| } | ||
|
|
||
| if (!isValidId(cuid)) { | ||
| logWarn(`${LOG_PREFIX}ignoring malformed value in localStorage["${STORAGE_KEY}"]`); | ||
| return undefined; | ||
| } | ||
|
|
||
| logInfo(`${LOG_PREFIX}ID found`); | ||
| return { id: cuid }; | ||
| }, | ||
|
|
||
| /** | ||
| * decode the stored id value for passing to bid requests | ||
| * @function | ||
| * @param {string} value | ||
| * @returns {{anonymisedId: string} | undefined} | ||
| */ | ||
| decode(value) { | ||
| return isValidId(value) ? { [MODULE_NAME]: value } : undefined; | ||
| }, | ||
|
|
||
| eids: { | ||
| [MODULE_NAME]: { | ||
| source: EID_SOURCE, | ||
| atype: 1 | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| submodule('userId', anonymisedIdSubmodule); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| # Overview | ||
|
|
||
| Module Name: anonymisedIdSystem | ||
| Module Type: UserID Module | ||
| Maintainer: support@anonymised.io | ||
|
|
||
| # Description | ||
|
|
||
| Anonymised is a data anonymization technology for privacy-preserving advertising. | ||
|
|
||
| The Anonymised User ID submodule exposes the CUID - the identifier that the | ||
| [Anonymised Marketing Tag](https://support.anonymised.io/integrate/marketing-tag?t=LPukVCXzSIcRoal5jggyeg) | ||
| assigns when a user signs in - to bid adapters as an OpenRTB Extended ID under the source | ||
| `anonymised.io`. | ||
|
|
||
| The submodule performs no network calls. It reads the identifier that the Marketing Tag has already | ||
| stored on the publisher's own domain, in `localStorage` under the key `anon-cuid`, and passes it to | ||
| the bid stream. When the Marketing Tag is not installed, or the user is not signed in, no ID is read | ||
| and no EID is added. | ||
|
|
||
| ### Prerequisite | ||
|
|
||
| The Anonymised Marketing Tag must be installed on the page. This submodule does not load it. The tag | ||
| can be installed [natively](https://support.anonymised.io/integrate/install-the-anonymised-tag-natively?t=LPukVCXzSIcRoal5jggyeg) | ||
| or through the [`anonymisedRtdProvider`](anonymisedRtdProvider.md) module's `tagConfig` parameter. | ||
|
|
||
| # Building Prebid with Anonymised ID support | ||
|
|
||
| ```bash | ||
| gulp build --modules=userId,anonymisedIdSystem | ||
| ``` | ||
|
|
||
| # Configuration | ||
|
|
||
| ```javascript | ||
| pbjs.setConfig({ | ||
| userSync: { | ||
| userIds: [{ | ||
| name: 'anonymisedId' | ||
| }] | ||
| } | ||
| }); | ||
| ``` | ||
|
|
||
| | Param under userSync.userIds[] | Scope | Type | Description | Example | | ||
| | --- | --- | --- | --- | --- | | ||
| | name | Required | String | The name of this module. | `'anonymisedId'` | | ||
|
|
||
| The submodule takes no `params`. | ||
|
|
||
| ### Do not configure `storage` | ||
|
|
||
| This submodule manages the identifier itself and must be configured **without** a `storage` object. | ||
|
|
||
| The Marketing Tag is the single source of truth for the CUID: it writes the identifier on sign-in and | ||
| removes it on sign-out and on consent withdrawal. If Prebid.js were allowed to keep its own copy, that | ||
| copy would outlive the removal and the submodule would keep sending a stale identifier to bidders | ||
| until Prebid's own expiry elapsed. Reading the value fresh on every initialization makes removal take | ||
| effect immediately. | ||
|
|
||
| ### Do not set `userSync.ppid` to `anonymised.io` | ||
|
|
||
| The Marketing Tag sets the Google Ad Manager Publisher Provided ID itself, as part of its SignalLift | ||
| feature. Pointing `userSync.ppid` at `anonymised.io` makes Prebid.js set the PPID as well, which | ||
| produces two problems: | ||
|
|
||
| - Prebid.js strips non-alphanumeric characters from an ID before setting it as the PPID, while the | ||
| Marketing Tag sends the identifier unmodified. The same user would be represented by two different | ||
| PPIDs depending on which code path ran, splitting Google Ad Manager audiences and reporting. | ||
| - The Marketing Tag applies its own logic when deciding whether a PPID should be set at all. Prebid.js | ||
| is not aware of that logic and would bypass it. | ||
|
|
||
| The division is: the Marketing Tag owns the identifier sent to **Google Ad Manager**; this submodule | ||
| owns the identifier sent to **bidders**. | ||
|
|
||
| ### Single-page applications | ||
|
|
||
| `getId` is called when the User ID module initializes and is not re-run for subsequent auctions. If a | ||
| user signs in after that point, call `pbjs.refreshUserIds({ submoduleNames: ['anonymisedId'] })` to | ||
| pick up the new identifier. Always pass `submoduleNames` - an unscoped refresh re-initializes every | ||
| configured ID submodule, including those that make network requests. | ||
|
|
||
| ### Subdomains | ||
|
|
||
| The identifier is read from `localStorage`, which is scoped to a single origin. A publisher serving | ||
| the same user from more than one subdomain will have an identifier available on each subdomain only | ||
| after the Marketing Tag has run there. | ||
|
|
||
| ### Data deletion | ||
|
|
||
| Deletion requests are handled by the Marketing Tag, which owns the user's session and every | ||
| identifier derived from it. This submodule stores nothing of its own and therefore implements no | ||
| `onDataDeletionRequest` callback. | ||
|
|
||
| ### Vendor and storage disclosure | ||
|
|
||
| The submodule declares GVL ID `1116`. Its first-party storage use is disclosed at | ||
| [https://cdn1.anonymised.io/deviceStorage.json](https://cdn1.anonymised.io/deviceStorage.json). | ||
|
|
||
| For any questions or assistance with integrating Prebid, `anonymisedIdSystem`, or the Anonymised | ||
| Marketing Tag, please contact an [Anonymised representative](mailto:support@anonymised.io). |
Uh oh!
There was an error while loading. Please reload this page.