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

prebid / Prebid.js / #288

20 Mar 2025 07:47PM UTC coverage: 90.491% (+0.02%) from 90.476%
#288

push

travis-ci

prebidjs-release
Prebid 9.36.0 release

42390 of 53094 branches covered (79.84%)

62871 of 69478 relevant lines covered (90.49%)

226.88 hits per line

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

96.26
/src/storageManager.js
1
import {checkCookieSupport, hasDeviceAccess, logError} from './utils.js';
2
import {bidderSettings} from './bidderSettings.js';
3
import {MODULE_TYPE_BIDDER, MODULE_TYPE_PREBID} from './activities/modules.js';
4
import {isActivityAllowed, registerActivityControl} from './activities/rules.js';
5
import {
6
  ACTIVITY_PARAM_ADAPTER_CODE,
7
  ACTIVITY_PARAM_COMPONENT_TYPE,
8
  ACTIVITY_PARAM_STORAGE_TYPE
9
} from './activities/params.js';
10

11
import {ACTIVITY_ACCESS_DEVICE} from './activities/activities.js';
12
import {config} from './config.js';
13
import adapterManager from './adapterManager.js';
14
import {activityParams} from './activities/activityParams.js';
15

16
export const STORAGE_TYPE_LOCALSTORAGE = 'html5';
1✔
17
export const STORAGE_TYPE_COOKIES = 'cookie';
1✔
18

19
export let storageCallbacks = [];
1✔
20

21
/* eslint-disable no-restricted-properties */
22

23
/*
24
 *  Storage manager constructor. Consumers should prefer one of `getStorageManager` or `getCoreStorageManager`.
25
 */
26
export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = isActivityAllowed} = {}) {
1,132✔
27
  function isValid(cb, storageType) {
28
    let mod = moduleName;
19,172✔
29
    const curBidder = config.getCurrentBidder();
19,172✔
30
    if (curBidder && moduleType === MODULE_TYPE_BIDDER && adapterManager.aliasRegistry[curBidder] === moduleName) {
19,172✔
31
      mod = curBidder;
44✔
32
    }
33
    const result = {
19,172✔
34
      valid: isAllowed(ACTIVITY_ACCESS_DEVICE, activityParams(moduleType, mod, {
35
        [ACTIVITY_PARAM_STORAGE_TYPE]: storageType
36
      }))
37
    };
38
    return cb(result);
19,169✔
39
  }
40

41
  function schedule(operation, storageType, done) {
42
    if (done && typeof done === 'function') {
19,194✔
43
      storageCallbacks.push(function() {
22✔
44
        let result = isValid(operation, storageType);
×
45
        done(result);
×
46
      });
47
    } else {
48
      return isValid(operation, storageType);
19,172✔
49
    }
50
  }
51

52
  /**
53
   * @param {string} key
54
   * @param {string} value
55
   * @param {string} [expires='']
56
   * @param {string} [sameSite='/']
57
   * @param {string} [domain] domain (e.g., 'example.com' or 'subdomain.example.com').
58
   * If not specified, defaults to the host portion of the current document location.
59
   * If a domain is specified, subdomains are always included.
60
   * Domain must match the domain of the JavaScript origin. Setting cookies to foreign domains will be silently ignored.
61
   * @param {function} [done]
62
   */
63
  const setCookie = function (key, value, expires, sameSite, domain, done) {
567✔
64
    let cb = function (result) {
1,380✔
65
      if (result && result.valid) {
1,379✔
66
        const domainPortion = (domain && domain !== '') ? ` ;domain=${encodeURIComponent(domain)}` : '';
1,349✔
67
        const expiresPortion = (expires && expires !== '') ? ` ;expires=${expires}` : '';
1,349✔
68
        const isNone = (sameSite != null && sameSite.toLowerCase() == 'none')
1,349✔
69
        const secure = (isNone) ? '; Secure' : '';
1,349✔
70
        document.cookie = `${key}=${encodeURIComponent(value)}${expiresPortion}; path=/${domainPortion}${sameSite ? `; SameSite=${sameSite}` : ''}${secure}`;
1,349✔
71
      }
72
    }
73
    return schedule(cb, STORAGE_TYPE_COOKIES, done);
1,380✔
74
  };
75

76
  /**
77
   * @param {string} name
78
   * @param {function} [done]
79
   * @returns {(string|null)}
80
   */
81
  const getCookie = function(name, done) {
567✔
82
    let cb = function (result) {
1,494✔
83
      if (result && result.valid) {
1,491✔
84
        let m = window.document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]*)\\s*(;|$)');
1,086✔
85
        return m ? decodeURIComponent(m[2]) : null;
1,086✔
86
      }
87
      return null;
405✔
88
    }
89
    return schedule(cb, STORAGE_TYPE_COOKIES, done);
1,494✔
90
  };
91

92
  /**
93
   * @param {function} [done]
94
   * @returns {boolean}
95
   */
96
  const cookiesAreEnabled = function (done) {
567✔
97
    let cb = function (result) {
1,143✔
98
      if (result && result.valid) {
1,142✔
99
        return checkCookieSupport();
955✔
100
      }
101
      return false;
187✔
102
    }
103
    return schedule(cb, STORAGE_TYPE_COOKIES, done);
1,143✔
104
  }
105

106
  function storageMethods(name) {
107
    const capName = name.charAt(0).toUpperCase() + name.substring(1);
1,134✔
108
    const backend = () => window[name];
14,692✔
109

110
    const hasStorage = function (done) {
1,134✔
111
      let cb = function (result) {
7,161✔
112
        if (result && result.valid) {
7,158✔
113
          try {
7,115✔
114
            return !!backend();
7,115✔
115
          } catch (e) {
116
            logError(`${name} api disabled`);
6✔
117
          }
118
        }
119
        return false;
49✔
120
      }
121
      return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done);
7,161✔
122
    }
123

124
    return {
1,134✔
125
      [`has${capName}`]: hasStorage,
126
      [`${name}IsEnabled`](done) {
127
        let cb = function (result) {
1,035✔
128
          if (result && result.valid) {
1,027✔
129
            try {
384✔
130
              backend().setItem('prebid.cookieTest', '1');
384✔
131
              return backend().getItem('prebid.cookieTest') === '1';
384✔
132
            } catch (error) {
133
            } finally {
134
              try {
384✔
135
                backend().removeItem('prebid.cookieTest');
384✔
136
              } catch (error) {}
137
            }
138
          }
139
          return false;
643✔
140
        }
141
        return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done);
1,035✔
142
      },
143
      [`setDataIn${capName}`](key, value, done) {
144
        let cb = function (result) {
1,202✔
145
          if (result && result.valid && hasStorage()) {
1,200✔
146
            backend().setItem(key, value);
1,088✔
147
          }
148
        }
149
        return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done);
1,202✔
150
      },
151
      [`getDataFrom${capName}`](key, done) {
152
        let cb = function (result) {
4,514✔
153
          if (result && result.valid && hasStorage()) {
4,509✔
154
            return backend().getItem(key);
4,133✔
155
          }
156
          return null;
376✔
157
        }
158
        return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done);
4,514✔
159
      },
160
      [`removeDataFrom${capName}`](key, done) {
161
        let cb = function (result) {
1,210✔
162
          if (result && result.valid && hasStorage()) {
1,208✔
163
            backend().removeItem(key);
1,204✔
164
          }
165
        }
166
        return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done);
1,210✔
167
      }
168
    }
169
  }
170

171
  /**
172
   * Returns all cookie values from the jar whose names contain the `keyLike`
173
   * Needs to exist in `utils.js` as it follows the StorageHandler interface defined in live-connect-js. If that module were to be removed, this function can go as well.
174
   * @param {string} keyLike
175
   * @param {function} [done]
176
   * @returns {string[]}
177
   */
178
  const findSimilarCookies = function(keyLike, done) {
567✔
179
    let cb = function (result) {
55✔
180
      if (result && result.valid) {
55!
181
        const all = [];
55✔
182
        if (hasDeviceAccess()) {
55!
183
          const cookies = document.cookie.split(';');
55✔
184
          while (cookies.length) {
55✔
185
            const cookie = cookies.pop();
233✔
186
            let separatorIndex = cookie.indexOf('=');
233✔
187
            separatorIndex = separatorIndex < 0 ? cookie.length : separatorIndex;
233✔
188
            const cookieName = decodeURIComponent(cookie.slice(0, separatorIndex).replace(/^\s+/, ''));
233✔
189
            if (cookieName.indexOf(keyLike) >= 0) {
233✔
190
              all.push(decodeURIComponent(cookie.slice(separatorIndex + 1)));
4✔
191
            }
192
          }
193
        }
194
        return all;
55✔
195
      }
196
    }
197

198
    return schedule(cb, STORAGE_TYPE_COOKIES, done);
55✔
199
  }
200

201
  return {
567✔
202
    setCookie,
203
    getCookie,
204
    cookiesAreEnabled,
205
    ...storageMethods('localStorage'),
206
    ...storageMethods('sessionStorage'),
207
    findSimilarCookies
208
  }
209
}
210

211
/**
212
 * Get a storage manager for a particular module.
213
 *
214
 * Either bidderCode or a combination of moduleType + moduleName must be provided. The former is a shorthand
215
 *  for `{moduleType: 'bidder', moduleName: bidderCode}`.
216
 *
217
 */
218
export function getStorageManager({moduleType, moduleName, bidderCode} = {}) {
×
219
  function err() {
220
    throw new Error(`Invalid invocation for getStorageManager: must set either bidderCode, or moduleType + moduleName`)
×
221
  }
222
  if (bidderCode) {
543✔
223
    if ((moduleType && moduleType !== MODULE_TYPE_BIDDER) || moduleName) err()
87!
224
    moduleType = MODULE_TYPE_BIDDER;
87✔
225
    moduleName = bidderCode;
87✔
226
  } else if (!moduleName || !moduleType) {
456!
227
    err()
×
228
  }
229
  return newStorageManager({moduleType, moduleName});
543✔
230
}
231

232
/**
233
 * Get a storage manager for "core" (vendorless, or first-party) modules. Shorthand for `getStorageManager({moduleName, moduleType: 'core'})`.
234
 *
235
 * @param {string} moduleName Module name
236
 */
237
export function getCoreStorageManager(moduleName) {
238
  return newStorageManager({moduleName: moduleName, moduleType: MODULE_TYPE_PREBID});
14✔
239
}
240

241
/**
242
 * Block all access to storage when deviceAccess = false
243
 */
244
export function deviceAccessRule() {
245
  if (!hasDeviceAccess()) {
19,165✔
246
    return {allow: false}
1✔
247
  }
248
}
249
registerActivityControl(ACTIVITY_ACCESS_DEVICE, 'deviceAccess config', deviceAccessRule);
1✔
250

251
/**
252
 * By default, deny bidders accessDevice unless they enable it through bidderSettings
253
 *
254
 * // TODO: for backwards compat, the check is done on the adapter - rather than bidder's code.
255
 */
256
export function storageAllowedRule(params, bs = bidderSettings) {
19,163✔
257
  if (params[ACTIVITY_PARAM_COMPONENT_TYPE] !== MODULE_TYPE_BIDDER) return;
19,195✔
258
  let allow = bs.get(params[ACTIVITY_PARAM_ADAPTER_CODE], 'storageAllowed');
5,721✔
259
  if (!allow || allow === true) {
5,721✔
260
    allow = !!allow
5,709✔
261
  } else {
262
    const storageType = params[ACTIVITY_PARAM_STORAGE_TYPE];
12✔
263
    allow = Array.isArray(allow) ? allow.some((e) => e === storageType) : allow === storageType;
12✔
264
  }
265
  if (!allow) {
5,721✔
266
    return {allow};
1,804✔
267
  }
268
}
269

270
registerActivityControl(ACTIVITY_ACCESS_DEVICE, 'bidderSettings.*.storageAllowed', storageAllowedRule);
1✔
271

272
export function resetData() {
273
  storageCallbacks = [];
45✔
274
}
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