The reflect module
Part of the SharedID Edge specification. The general rules for modules, including consent, time limits and modules on /sync, are there.
Name reflect; no purposes, no gvlid. It runs only when listed in SID_MODULES. It re-sets first-party cookies that other scripts on the page wrote, so they become server-set cookies with a new lifetime, as the worker does for the SharedID.
Settings,
SID_MODULE_CONFIG.reflect:{ "cookies": [ … ], "keeper": { … } }.cookieshas 1 to 10 entries, each:name(required): a cookie name (an RFC 6265 token, at most 64 characters). It MUST NOT be_pubcid,_pubcid_sv,_pubcid_optout,ts-ecoreuconsent-v2, nor the worker's configured cookie names, and MUST NOT repeat;maxAgeDays(required): an integer from 1 to 400;domain(optional): a registrable domain without a leading dot, such asexample.com, and the cookie getsDomain=.<domain>; orhost-only, and the cookie gets noDomainattribute whateverSID_COOKIE_DOMAINsays. Without it,SID_COOKIE_DOMAINapplies;httpOnly(optional, defaultfalse);deleteOnOptOut(optional, defaultfalse);restore(optional, defaultfalse): save the cookie in the keeper cookie and restore it from there (below);prefer(optional, default"browser"; only withrestore: true): which value wins when the request carries the cookie and the keeper also has a value for it:"browser","backup"or"older"(below);format(optional; only withprefer: "older"): the value's format,"ga","gcl_au"or"gcl_aw". Without it, the format comes from the name:_gais"ga",_gcl_auis"gcl_au"and_gcl_awis"gcl_aw".prefer: "older"on any other name withoutformatis invalid.
keeper(optional; its defaults apply when it's omitted) configures the keeper cookie:mode(default"master"):"master", one keeper cookie holding every saved value, or"per-cookie", a backup cookie perrestorecookie (see Per-cookie backups below);cookie(default_sid_keep): a cookie name, with the same rules asname, and MUST NOT be one of the configuredcookies;suffix(default_backup): 1 to 16 cookie-name characters; a per-cookie backup is named<name><suffix>. Each backup name MUST be a valid cookie name and MUST NOT be a configured cookie, the keeper cookie or a reserved name, in either mode;maxAgeDays(default400): an integer from 1 to 400;domain(optional): as for a cookie. Without it, the firstrestorecookie'sdomainapplies; without that,SID_COOKIE_DOMAIN.
Any other key, a wrong type or an out-of-range value makes the module an
erroron every call, with no details.Reflection: for each configured cookie the request carries, in configuration order, the module sets
<name>=<value>; Max-Age=<maxAgeDays × 86400>; Expires=…; Path=/; [Domain=.<domain>;] SameSite=Lax; [Secure;] [HttpOnly]. The value MUST be the request's value byte for byte: not decoded, re-encoded, trimmed beyond theCookieheader's own whitespace, or otherwise changed. When the name repeats in theCookieheader (copies set with differentDomains), the first is used.A cookie is not reflected when its value is empty, longer than 1024 characters, quoted, or contains a character that isn't a cookie-octet (RFC 6265). The other cookies are still reflected.
The keeper cookie holds the last known value of each
restorecookie. Its value is the base64url encoding (RFC 4648 section 5, without padding) of a JSON object mapping cookie names to values, with keys in configuration order. It's setHttpOnly, with the keeper'smaxAgeDaysandDomain, and withPath=/,SameSite=LaxandSecure(over https) like the others. Its name and value together MUST NOT exceed 4096 bytes: entries are added in configuration order, and one that would take it over is left out (that cookie is still reflected) while later ones are still tried.Reading the keeper: its value is read byte for byte, like the other cookies. A value that isn't base64url without padding, isn't a JSON object, or is over 4096 characters counts as empty, and so does each entry whose value isn't a string that could be reflected. It's never an error.
Restoring, on each
/v1or/syncrequest where storage is allowed, for eachrestorecookie in configuration order:- the request carries it, the keeper has a value for it, and the backup wins (below): it's restored over the request's value, set with the saved value and its own configured attributes as if reflected, and the saved value stays saved;
- the request carries it with a value that can be reflected: it's reflected, and that value is saved;
- the request doesn't carry it, and the keeper has a value for it: it's restored, set with that value and its own configured attributes as if reflected, and the value stays saved;
- the request carries it with a value that can't be reflected: it's left alone, and its saved value is dropped;
- otherwise nothing is saved for it.
Whether the backup wins over a value the request carries depends on
prefer:"browser": never. The request's value is current (the behaviour above)."backup": always, whatever the request's value is, including one that can't be reflected."older": when both values parse in the cookie'sformatand the saved value's creation time is strictly earlier than the request's. On equal times, or when either value doesn't parse, the request's value wins, as with"browser". A value parses when it matches its format exactly, with the creation time<t>as exactly 10 decimal digits (Unix seconds) no later than 86400 seconds after the worker's current time:"ga":GA1.<d>.<n>.<t>, where<d>is 1 or 2 digits not starting with0and<n>is 1 to 20 digits (the Google Analytics_gacookie,GA1.1.…orGA1.2.…);"gcl_au":1.<d>.<n>.<t>, with<d>and<n>as for"ga"(the Google conversion linker's_gcl_au);"gcl_aw":GCL.<t>.<c>, where<c>is one or more ofA–Z,a–z,0–9,_and-(the Google Ads click cookie_gcl_aw;<t>is the click's time).
Values are compared and copied, never changed: a restored value is the saved value byte for byte.
The keeper is then set with the saved values on every such request, which refreshes its expiry. When nothing is left to save and the request carried a keeper, it's deleted. With no
restorecookie configured, no keeper is set, and one the request carries is deleted.Output: cookies only, so the status is
empty. Nothing is added toeidsorortb2, and neither names nor values are reported.Consent: the module reflects only when
storage.allowedis true: not on opt-out,Sec-GPC: 1,coppa=1, a TC string that doesn't give the SharedID's consent, or GDPR applying without a TC string. It then reflects and restores nothing, and deletes none of the configured cookies, which belong to other scripts. It deletes the keeper cookie, this worker's own store of identifiers, when the request carries it.Opt-out:
{basePath}/optoutdeletes a configured cookie the browser sent only when itsdeleteOnOptOutistrue, with the configuredDomainandHttpOnly. It always deletes the keeper cookie the browser sent, whateverdeleteOnOptOutsays, and also whenreflectisn't inSID_MODULESor no cookie hasrestore(then at the configured or default name).Per-cookie backups (
keeper.mode: "per-cookie"): eachrestorecookie's saved value is held in its own backup cookie instead of the keeper cookie, so each has its own size limit and they're independent. The rules above apply with the backup in place of the keeper's entry:- Reading: a backup's value is read byte for byte and used only when it's reflectable (as for a reflected cookie).
- Writing:
<name><suffix>=<value>; Max-Age=<keeper.maxAgeDays × 86400>; Expires=…; Path=/; [Domain=.<domain>;] SameSite=Lax; [Secure;] HttpOnly, with the saved value byte for byte, on every request where the value is saved, after the reflected cookies.Domainiskeeper.domain, else the cookie's owndomain, elseSID_COOKIE_DOMAIN. A backup the request carried with no value left to save is deleted. - Migration: values are read from both forms. The configured form wins where both have a value, and the other fills gaps; the other form's cookies the request carried are then deleted. So changing
modeloses no saved value. - Consent and opt-out: as for the keeper. When storage isn't allowed, every backup the request carries is deleted, and
{basePath}/optoutdeletes the keeper cookie and every backup, in either mode. That includes the backup of a configured cookie whoserestorewas turned off, when its backup name is a valid cookie name no other cookie uses. - Switching to
master: a per-cookie backup is deleted once its value is in the keeper. One whose value didn't fit in the keeper's 4096 bytes is kept and refreshed.
Duplicates: when the request carries a configured cookie more than once, every copy has the same value, and the copy being set has a
Domain(its owndomain, elseSID_COOKIE_DOMAIN), the module MUST also delete the host-only copy: an empty value,Max-Age=0, noDomain, and the cookie'sHttpOnly. That leaves the domain copy, so the browser keeps one. When the copies' values differ, it deletes neither: another copy may be its owner's newer write (a rotated ID, or its opt-out). The keeper, the worker's own cookie, gets its host-only copy deleted whenever it's duplicated and set on a domain.Attributes: the worker can't see a cookie's original attributes. If they differ (for example the original is host-only and
domainis set), the browser keeps two cookies of that name. Configuration SHOULD match the original'sDomainandHttpOnly.Its
routesarev1andsync: Prebid.js's/synccall on page views reflects, saves and restores cookies without the page calling/v1, as often as the sync response's browser cache allows (see Modules on/sync).