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

prebid / Prebid.js / 16277709744

14 Jul 2025 08:57PM UTC coverage: 96.202% (-0.002%) from 96.204%
16277709744

push

github

web-flow
Linting: add stylistc indentation rule (#13585)

* bump coveralls

* eslint fix

* restore

* Update package-lock.json

39192 of 48203 branches covered (81.31%)

1411 of 1477 new or added lines in 73 files covered. (95.53%)

32 existing lines in 12 files now uncovered.

192751 of 200361 relevant lines covered (96.2%)

88.13 hits per line

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

89.53
/modules/storageControl.ts
1
import {config} from '../src/config.js';
2✔
2
import {metadata} from '../libraries/metadata/metadata.js';
3
import {
4
  ACTIVITY_PARAM_COMPONENT,
5
  ACTIVITY_PARAM_COMPONENT_NAME,
6
  ACTIVITY_PARAM_COMPONENT_TYPE,
7
  ACTIVITY_PARAM_STORAGE_KEY,
8
  ACTIVITY_PARAM_STORAGE_TYPE
9
} from '../src/activities/params.js';
10
import {
11
  discloseStorageUse,
12
  STORAGE_TYPE_COOKIES,
13
  STORAGE_TYPE_LOCALSTORAGE,
14
  type StorageDisclosure as Disclosure
15
} from '../src/storageManager.js';
16
import {logWarn, uniques} from '../src/utils.js';
17
import {registerActivityControl} from '../src/activities/rules.js';
18
import {ACTIVITY_ACCESS_DEVICE} from '../src/activities/activities.js';
19
import {addApiMethod} from "../src/prebid.ts";
20
// @ts-expect-error the ts compiler is confused by build-time renaming of summary.mjs to summary.js, reassure it
21
// eslint-disable-next-line prebid/validate-imports
22
import {getStorageDisclosureSummary} from "../libraries/storageDisclosure/summary.js";
23
import {getGlobal} from "../src/prebidGlobal.ts";
24

25
export const ENFORCE_STRICT = 'strict';
2✔
26
export const ENFORCE_ALIAS = 'allowAliases';
2✔
27
export const ENFORCE_OFF = 'off';
2✔
28

29
let enforcement;
30

31
function escapeRegExp(string) {
32
  return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
21✔
33
}
34

35
function matches(params, disclosure) {
36
  if (
28✔
37
    !['cookie', 'web'].includes(disclosure.type) ||
91✔
38
    (disclosure.type === 'cookie' && params[ACTIVITY_PARAM_STORAGE_TYPE] !== STORAGE_TYPE_COOKIES) ||
39
    (disclosure.type === 'web' && params[ACTIVITY_PARAM_STORAGE_TYPE] !== STORAGE_TYPE_LOCALSTORAGE)
40
  ) return false;
14✔
41
  const pattern = new RegExp(`^${disclosure.identifier.split('*').map(escapeRegExp).join('.*?')}$`);
14✔
42
  return pattern.test(params[ACTIVITY_PARAM_STORAGE_KEY]);
14✔
43
}
44

45
export function getDisclosures(params, meta = metadata) {
3,330✔
46
  const matchingDisclosures = [];
3,330✔
47
  const disclosureURLs = {};
3,330✔
48
  const data = meta.getMetadata(params[ACTIVITY_PARAM_COMPONENT_TYPE], params[ACTIVITY_PARAM_COMPONENT_NAME]);
3,330✔
49
  if (!data) return null;
3,330✔
50
  disclosureURLs[params[ACTIVITY_PARAM_COMPONENT_NAME]] = data.disclosureURL;
8✔
51
  if (data.aliasOf) {
8✔
52
    const parent = meta.getMetadata(params[ACTIVITY_PARAM_COMPONENT_TYPE], data.aliasOf);
1✔
53
    if (parent) {
1✔
54
      disclosureURLs[data.aliasOf] = parent.disclosureURL;
1✔
55
    }
56
  }
57
  Object.entries(disclosureURLs).forEach(([componentName, disclosureURL]) => {
9✔
58
    meta.getStorageDisclosure(disclosureURL)
9✔
59
      ?.disclosures
60
      ?.filter(disclosure => matches(params, disclosure))
28✔
61
      ?.forEach(disclosure => {
62
        matchingDisclosures.push({
5✔
63
          [ACTIVITY_PARAM_COMPONENT_NAME]: componentName,
64
          disclosureURL,
65
          disclosure
66
        })
67
      })
68
  })
69
  return {
8✔
70
    matches: matchingDisclosures,
71
    disclosureURLs
72
  };
73
}
74

75
export function checkDisclosure(params, getMatchingDisclosures = getDisclosures) {
6,478✔
76
  let disclosed = false;
6,478✔
77
  let parent = false;
6,478✔
78
  let reason = null;
6,478✔
79
  const key = params[ACTIVITY_PARAM_STORAGE_KEY]
6,478✔
80
  const component = params[ACTIVITY_PARAM_COMPONENT];
6,478✔
81
  if (key) {
6,478✔
82
    const disclosures = getMatchingDisclosures(params);
3,322✔
83
    if (disclosures == null) {
3,322✔
84
      reason = `Cannot determine if storage key "${key}" is disclosed by "${component}" because the necessary metadata is missing - was it included in the build?`
3,321✔
85
    } else {
86
      const {disclosureURLs, matches} = disclosures;
1✔
87
      const moduleName = params[ACTIVITY_PARAM_COMPONENT_NAME]
1✔
88
      for (const {componentName} of matches) {
1✔
89
        if (componentName === moduleName) {
1!
90
          disclosed = true;
1✔
91
        } else {
92
          parent = true;
×
93
          reason = `Storage key "${key}" is disclosed by module "${componentName}", but not by "${moduleName}" itself (the latter is an alias of the former)`
×
94
        }
95
        if (disclosed || parent) break;
1!
96
      }
97
      if (!disclosed && !parent) {
1!
98
        reason = `Storage key "${key}" (for ${params[ACTIVITY_PARAM_STORAGE_TYPE]} storage) is not disclosed by "${component}"`
×
99
        if (disclosureURLs[moduleName]) {
×
100
          reason += ` @ ${disclosureURLs[moduleName]}`
×
101
        } else {
102
          reason += ` - no disclosure URL was provided, or it could not be retrieved`
×
103
        }
104
      }
105
    }
106
  } else {
107
    disclosed = null;
3,156✔
108
  }
109
  return {
6,478✔
110
    disclosed, parent, reason
111
  }
112
}
113

114
export function storageControlRule(getEnforcement = () => enforcement, check = checkDisclosure) {
3,321✔
115
  return function (params) {
7✔
116
    const {disclosed, parent, reason} = check(params);
6,481✔
117
    if (disclosed === null) return;
6,481✔
118
    if (!disclosed) {
3,325✔
119
      const enforcement = getEnforcement();
3,324✔
120
      if (enforcement === ENFORCE_STRICT || (enforcement === ENFORCE_ALIAS && !parent)) return {allow: false, reason};
3,324✔
121
      if (reason) {
3,323✔
122
        logWarn('storageControl:', reason);
3,322✔
123
      }
124
    }
125
  }
126
}
127

128
registerActivityControl(ACTIVITY_ACCESS_DEVICE, 'storageControl', storageControlRule());
2✔
129

130
export type StorageControlConfig = {
131
  /**
132
   * - 'off': logs a warning when an undisclosed storage key is used
133
   * - 'strict': deny access to undisclosed storage keys
134
   * - 'allowAliases': deny access to undisclosed storage keys, unless the use is from an alias of a module that does
135
   *    disclose them
136
   */
137
  enforcement?: typeof ENFORCE_OFF | typeof ENFORCE_ALIAS | typeof ENFORCE_STRICT;
138
}
139

140
declare module '../src/config' {
141
  interface Config {
142
    storageControl: StorageControlConfig
143
  }
144
}
145

146
config.getConfig('storageControl', (cfg) => {
2✔
147
  enforcement = cfg?.storageControl?.enforcement ?? ENFORCE_OFF;
×
148
})
149

150
export function dynamicDisclosureCollector() {
151
  const disclosures = {};
6✔
152
  function mergeDisclosures(left, right) {
153
    const merged = {
217✔
154
      ...left,
155
      purposes: (left.purposes ?? []).concat(right.purposes ?? []).filter(uniques),
434!
156
    };
157
    if (left.type === 'cookie') {
217✔
158
      if (left.maxAgeSeconds != null || right.maxAgeSeconds != null) {
153✔
159
        merged.maxAgeSeconds = (left.maxAgeSeconds ?? 0) > (right.maxAgeSeconds ?? 0) ? left.maxAgeSeconds : right.maxAgeSeconds;
152!
160
      }
161
      if (left.cookieRefresh != null || right.cookieRefresh != null) {
153✔
162
        merged.cookieRefresh = left.cookieRefresh || right.cookieRefresh;
152!
163
      }
164
    }
165
    return merged;
217✔
166
  }
167
  return {
6✔
168
    hook(next, moduleName, disclosure) {
169
      const key = `${disclosure.type}::${disclosure.identifier}`;
217✔
170
      if (!disclosures.hasOwnProperty(key)) {
217✔
171
        disclosures[key] = {
71✔
172
          disclosedBy: [],
173
          ...disclosure
174
        };
175
      }
176
      Object.assign(disclosures[key], mergeDisclosures(disclosures[key], disclosure));
217✔
177
      if (!disclosures[key].disclosedBy.includes(moduleName)) {
217✔
178
        disclosures[key].disclosedBy.push(moduleName);
72✔
179
      }
180
      next(moduleName, disclosure);
217✔
181
    },
182
    getDisclosures() {
183
      return Object.values(disclosures);
4✔
184
    }
185
  }
186
}
187

188
const {hook: discloseStorageHook, getDisclosures: dynamicDisclosures} = dynamicDisclosureCollector();
2✔
189
discloseStorageUse.before(discloseStorageHook);
2✔
190

191
export type StorageDisclosure = Disclosure & {
192
  /**
193
   * URL containing this disclosure, if any.
194
   */
195
  disclosedIn: string | null;
196
  /**
197
   * Names of the modules associated with this disclosure.
198
   */
199
  disclosedBy: string[];
200
}
201

202
function disclosureSummarizer(getDynamicDisclosures = dynamicDisclosures, getSummary = () => getStorageDisclosureSummary(getGlobal().installedModules, metadata.getModuleMetadata)) {
2!
203
  return function() {
2✔
NEW
204
    return [].concat(
×
NEW
205
      getDynamicDisclosures().map(disclosure => ({
×
206
        disclosedIn: null,
207
        ...(disclosure as any)
208
      })),
209
      getSummary()
210
    );
211
  }
212
}
213

214
const getStorageUseDisclosures: () => StorageDisclosure[] = disclosureSummarizer();
2✔
215

216
declare module '../src/prebidGlobal' {
217
  interface PrebidJS {
218
    getStorageUseDisclosures: typeof getStorageUseDisclosures;
219
  }
220
}
221

222
addApiMethod('getStorageUseDisclosures', getStorageUseDisclosures);
2✔
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