• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

prebid / Prebid.js / 24017714090

06 Apr 2026 03:40AM UTC coverage: 96.341% (-0.001%) from 96.342%
24017714090

Pull #14640

github

4a7e91
web-flow
Merge branch 'master' into feature/rediads-id-system-userid-module
Pull Request #14640: Add Rediads user ID submodule and docs examples

42663 of 52540 branches covered (81.2%)

115 of 121 new or added lines in 2 files covered. (95.04%)

2 existing lines in 1 file now uncovered.

216831 of 225067 relevant lines covered (96.34%)

71.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.16
/modules/rediadsIdSystem.js
1
/**
1✔
2
 * This module adds Rediads ID support to the User ID module
3
 * The {@link module:modules/userId} module is required.
4
 * @module modules/rediadsIdSystem
5
 * @requires module:modules/userId
6
 */
7

8
import { submodule } from '../src/hook.js';
9
import { VENDORLESS_GVLID } from '../src/consentHandler.js';
10
import { cyrb53Hash, generateUUID, isPlainObject, isStr } from '../src/utils.js';
11

12
/**
13
 * @typedef {import('../modules/userId/index.js').Submodule} Submodule
14
 * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig
15
 * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData
16
 * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse
17
 */
18

19
const MODULE_NAME = 'rediadsId';
1✔
20
const DEFAULT_SOURCE = 'rediads.com';
1✔
21
const DEFAULT_ATYPE = 1;
1✔
22
const DEFAULT_EXPIRES_DAYS = 30;
1✔
23
const DEFAULT_REFRESH_SECONDS = 3600;
1✔
24

25
function normalizeStoredId(storedId) {
26
  if (isPlainObject(storedId)) {
11✔
27
    return storedId;
5✔
28
  }
29

30
  if (isStr(storedId)) {
6✔
31
    try {
1✔
32
      const parsed = JSON.parse(storedId);
1✔
NEW
33
      if (isPlainObject(parsed)) {
×
NEW
34
        return parsed;
×
35
      }
36
    } catch (e) {}
37
  }
38
}
39

40
function getConsentHash(consentData = {}) {
6!
41
  return `hash_${cyrb53Hash(JSON.stringify({
6✔
42
    gdpr: consentData.gdpr?.consentString || null,
11✔
43
    gpp: consentData.gpp?.gppString || null,
11✔
44
    usp: consentData.usp || null,
11✔
45
    coppa: consentData.coppa === true
46
  }))}`;
47
}
48

49
function hasTcfPurpose1Consent(gdprConsent) {
50
  return gdprConsent?.vendorData?.purpose?.consents?.[1] === true ||
1✔
51
    gdprConsent?.vendorData?.purposeConsents?.[1] === true;
52
}
53

54
function canWriteStorage(consentData = {}) {
7!
55
  if (consentData.coppa === true) {
7!
NEW
56
    return false;
×
57
  }
58

59
  const gdprConsent = consentData.gdpr;
7✔
60
  if (gdprConsent?.gdprApplies === true) {
7✔
61
    return isStr(gdprConsent?.consentString) &&
1✔
62
      gdprConsent.consentString.length > 0 &&
63
      hasTcfPurpose1Consent(gdprConsent);
64
  }
65

66
  return true;
6✔
67
}
68

69
function canShareId(consentData = {}) {
6!
70
  if (consentData.coppa === true) {
6!
NEW
71
    return false;
×
72
  }
73

74
  if (isStr(consentData.usp) && consentData.usp.charAt(2) === 'Y') {
6✔
75
    return false;
1✔
76
  }
77

78
  return true;
5✔
79
}
80

81
function ensureStorageDefaults(config) {
82
  if (isPlainObject(config?.storage)) {
7✔
83
    if (config.storage.expires == null) {
2✔
84
      config.storage.expires = DEFAULT_EXPIRES_DAYS;
2✔
85
    }
86
    if (config.storage.refreshInSeconds == null) {
2✔
87
      config.storage.refreshInSeconds = DEFAULT_REFRESH_SECONDS;
2✔
88
    }
89
  }
90
}
91

92
function buildStoredId(config, consentData, existingId) {
93
  const params = config?.params || {};
6✔
94
  const source = params.source || DEFAULT_SOURCE;
6✔
95
  const expiresDays = config?.storage?.expires ?? DEFAULT_EXPIRES_DAYS;
6✔
96
  const refreshInSeconds = config?.storage?.refreshInSeconds ?? DEFAULT_REFRESH_SECONDS;
6✔
97
  const now = Date.now();
6✔
98

99
  return {
6✔
100
    id: existingId || `ruid_${generateUUID()}`,
10✔
101
    source,
102
    atype: DEFAULT_ATYPE,
103
    canShare: canShareId(consentData),
104
    consentHash: getConsentHash(consentData),
105
    refreshAfter: now + (refreshInSeconds * 1000),
106
    expiresAt: now + (expiresDays * 24 * 60 * 60 * 1000)
107
  };
108
}
109

110
/** @type {Submodule} */
111
export const rediadsIdSubmodule = {
1✔
112
  name: MODULE_NAME,
113
  gvlid: VENDORLESS_GVLID,
114

115
  decode(value) {
116
    const storedId = normalizeStoredId(value);
5✔
117
    if (!isStr(storedId?.id) || storedId.id.length === 0) {
5✔
118
      return undefined;
3✔
119
    }
120

121
    return {
2✔
122
      rediadsId: {
123
        uid: storedId.id,
124
        source: storedId.source || DEFAULT_SOURCE,
2!
125
        atype: storedId.atype || DEFAULT_ATYPE,
2!
126
        ext: {
127
          canShare: storedId.canShare !== false,
128
          consentHash: storedId.consentHash,
129
          refreshAfter: storedId.refreshAfter
130
        }
131
      }
132
    };
133
  },
134

135
  getId(config, consentData, storedId) {
136
    ensureStorageDefaults(config);
6✔
137

138
    if (!canWriteStorage(consentData)) {
6✔
139
      return undefined;
1✔
140
    }
141

142
    const normalized = normalizeStoredId(storedId);
5✔
143
    return {
5✔
144
      id: buildStoredId(config, consentData, normalized?.id)
145
    };
146
  },
147

148
  extendId(config, consentData, storedId) {
149
    ensureStorageDefaults(config);
1✔
150

151
    if (!canWriteStorage(consentData)) {
1!
NEW
152
      return undefined;
×
153
    }
154

155
    const normalized = normalizeStoredId(storedId);
1✔
156
    if (!isStr(normalized?.id) || normalized.id.length === 0) {
1!
NEW
157
      return this.getId(config, consentData, storedId);
×
158
    }
159

160
    return {
1✔
161
      id: buildStoredId(config, consentData, normalized.id)
162
    };
163
  },
164

165
  eids: {
166
    rediadsId(values) {
167
      return values
3✔
168
        .filter((value) => isStr(value?.uid) && value.ext?.canShare !== false)
3✔
169
        .map((value) => ({
2✔
170
          source: value.source || DEFAULT_SOURCE,
2!
171
          uids: [{
172
            id: value.uid,
173
            atype: value.atype || DEFAULT_ATYPE
2!
174
          }]
175
        }));
176
    }
177
  }
178
};
179

180
submodule('userId', rediadsIdSubmodule);
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc