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

prebid / Prebid.js / 18136225893

30 Sep 2025 04:07PM UTC coverage: 96.245% (+0.006%) from 96.239%
18136225893

push

github

web-flow
Advertising Bid Adapter: Update the regex while parsing bid.impid to support the change to UUID format for bid ids (#13879)

51890 of 63472 branches covered (81.75%)

1 of 1 new or added line in 1 file covered. (100.0%)

70 existing lines in 12 files now uncovered.

198517 of 206262 relevant lines covered (96.25%)

125.12 hits per line

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

90.91
/modules/id5AnalyticsAdapter.js
1
import buildAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
1✔
2
import {EVENTS} from '../src/constants.js';
3
import adapterManager from '../src/adapterManager.js';
4
import {ajax} from '../src/ajax.js';
5
import {logError, logInfo} from '../src/utils.js';
6
import * as events from '../src/events.js';
7

8
const {
9
  AUCTION_END,
10
  TCF2_ENFORCEMENT,
11
  BID_WON
12
} = EVENTS
1✔
13

14
const GVLID = 131;
1✔
15

16
const STANDARD_EVENTS_TO_TRACK = [
1✔
17
  AUCTION_END,
18
  TCF2_ENFORCEMENT,
19
  BID_WON,
20
];
21

22
const CONFIG_URL_PREFIX = 'https://api.id5-sync.com/analytics'
1✔
23
const TZ = new Date().getTimezoneOffset();
1✔
24
const PBJS_VERSION = 'v' + '$prebid.version$';
1✔
25
const ID5_REDACTED = '__ID5_REDACTED__';
1✔
26
const isArray = Array.isArray;
1✔
27

28
const id5Analytics = Object.assign(buildAdapter({analyticsType: 'endpoint'}), {
1✔
29
  eventsToTrack: STANDARD_EVENTS_TO_TRACK,
30

31
  track: (event) => {
32
    const _this = id5Analytics;
12✔
33

34
    if (!event || !event.args) {
12!
UNCOV
35
      return;
×
36
    }
37

38
    try {
12✔
39
      _this.sendEvent(_this.makeEvent(event.eventType, event.args));
12✔
40
    } catch (error) {
UNCOV
41
      logError('id5Analytics: ERROR', error);
×
UNCOV
42
      _this.sendErrorEvent(error);
×
43
    }
44
  },
45

46
  sendEvent: (eventToSend) => {
47
    // By giving some content this will be automatically a POST
48
    ajax(id5Analytics.options.ingestUrl, null, JSON.stringify(eventToSend));
12✔
49
  },
50

51
  makeEvent: (event, payload) => {
52
    const _this = id5Analytics;
12✔
53
    const filteredPayload = deepTransformingClone(payload,
12✔
54
      transformFnFromCleanupRules(event));
55
    return {
12✔
56
      source: 'pbjs',
57
      event,
58
      payload: filteredPayload,
59
      partnerId: _this.options.partnerId,
60
      meta: {
61
        sampling: _this.options.id5Sampling,
62
        pbjs: PBJS_VERSION,
63
        tz: TZ,
64
      }
65
    };
66
  },
67

68
  sendErrorEvent: (error) => {
69
    const _this = id5Analytics;
×
UNCOV
70
    _this.sendEvent([
×
71
      _this.makeEvent('analyticsError', {
72
        message: error.message,
73
        stack: error.stack,
74
      })
75
    ]);
76
  },
77

78
  random: () => Math.random(),
6✔
79
});
80

81
const ENABLE_FUNCTION = (config) => {
1✔
82
  const _this = id5Analytics;
10✔
83
  _this.options = (config && config.options) || {};
10✔
84

85
  const partnerId = _this.options.partnerId;
10✔
86
  if (typeof partnerId !== 'number') {
10✔
87
    logError('id5Analytics: partnerId in config.options must be a number representing the id5 partner ID');
3✔
88
    return;
3✔
89
  }
90

91
  ajax(`${CONFIG_URL_PREFIX}/${partnerId}/pbjs`, (result) => {
7✔
92
    logInfo('id5Analytics: Received from configuration endpoint', result);
7✔
93

94
    const configFromServer = JSON.parse(result);
7✔
95

96
    const sampling = _this.options.id5Sampling =
7✔
97
      typeof configFromServer.sampling === 'number' ? configFromServer.sampling : 0;
7!
98

99
    if (typeof configFromServer.ingestUrl !== 'string') {
7!
UNCOV
100
      logError('id5Analytics: cannot find ingestUrl in config endpoint response; no analytics will be available');
×
UNCOV
101
      return;
×
102
    }
103
    _this.options.ingestUrl = configFromServer.ingestUrl;
7✔
104

105
    // 3-way fallback for which events to track: server > config > standard
106
    _this.eventsToTrack = configFromServer.eventsToTrack || _this.options.eventsToTrack || STANDARD_EVENTS_TO_TRACK;
7✔
107
    _this.eventsToTrack = isArray(_this.eventsToTrack) ? _this.eventsToTrack : STANDARD_EVENTS_TO_TRACK;
7!
108

109
    logInfo('id5Analytics: Configuration is', _this.options);
7✔
110
    logInfo('id5Analytics: Tracking events', _this.eventsToTrack);
7✔
111
    if (sampling > 0 && _this.random() < (1 / sampling)) {
7✔
112
      // Init the module only if we got lucky
113
      logInfo('id5Analytics: Selected by sampling. Starting up!');
6✔
114

115
      // allow for replacing cleanup rules - remove existing ones and apply from server
116
      if (configFromServer.replaceCleanupRules) {
6✔
117
        cleanupRules = {};
1✔
118
      }
119
      // Merge in additional cleanup rules
120
      if (configFromServer.additionalCleanupRules) {
6✔
121
        const newRules = configFromServer.additionalCleanupRules;
2✔
122
        _this.eventsToTrack.forEach((key) => {
2✔
123
          // Some protective checks in case we mess up server side
124
          if (
6✔
125
            isArray(newRules[key]) &&
8✔
126
            newRules[key].every((eventRules) =>
127
              isArray(eventRules.match) &&
2✔
128
              (eventRules.apply in TRANSFORM_FUNCTIONS))
129
          ) {
130
            logInfo('id5Analytics: merging additional cleanup rules for event ' + key);
2✔
131
            if (!Array.isArray(cleanupRules[key])) {
2✔
132
              cleanupRules[key] = newRules[key];
1✔
133
            } else {
134
              cleanupRules[key].push(...newRules[key]);
1✔
135
            }
136
          }
137
        });
138
      }
139

140
      // Replay all events until now
141
      if (!config.disablePastEventsProcessing) {
6!
UNCOV
142
        events.getEvents().forEach((event) => {
×
UNCOV
143
          if (event && _this.eventsToTrack.indexOf(event.eventType) >= 0) {
×
UNCOV
144
            _this.track(event);
×
145
          }
146
        });
147
      }
148

149
      // Register to the events of interest
150
      _this.handlers = {};
6✔
151
      _this.eventsToTrack.forEach((eventType) => {
6✔
152
        const handler = _this.handlers[eventType] = (args) =>
16✔
153
          _this.track({ eventType, args });
12✔
154
        events.on(eventType, handler);
16✔
155
      });
156
    }
157
  });
158

159
  // Make only one init possible within a lifecycle
160
  _this.enableAnalytics = () => {};
7✔
161
};
162

163
id5Analytics.enableAnalytics = ENABLE_FUNCTION;
1✔
164
id5Analytics.disableAnalytics = () => {
1✔
165
  const _this = id5Analytics;
8✔
166
  // Un-register to the events of interest
167
  _this.eventsToTrack.forEach((eventType) => {
8✔
168
    if (_this.handlers && _this.handlers[eventType]) {
22✔
169
      events.off(eventType, _this.handlers[eventType]);
16✔
170
    }
171
  });
172

173
  // Make re-init possible. Work around the fact that past events cannot be forgotten
174
  _this.enableAnalytics = (config) => {
8✔
175
    config.disablePastEventsProcessing = true;
7✔
176
    ENABLE_FUNCTION(config);
7✔
177
  };
178
};
179

180
adapterManager.registerAnalyticsAdapter({
1✔
181
  adapter: id5Analytics,
182
  code: 'id5Analytics',
183
  gvlid: GVLID
184
});
185

186
export default id5Analytics;
187

188
function redact(obj, key) {
189
  obj[key] = ID5_REDACTED;
15✔
190
}
191

192
function erase(obj, key) {
193
  delete obj[key];
7✔
194
}
195

196
// The transform function matches against a path and applies
197
// required transformation if match is found.
198
function deepTransformingClone(obj, transform, currentPath = []) {
368✔
199
  const result = isArray(obj) ? [] : {};
368✔
200
  const recursable = typeof obj === 'object' && obj !== null;
368✔
201
  if (recursable) {
368✔
202
    const keys = Object.keys(obj);
168✔
203
    if (keys.length > 0) {
168✔
204
      keys.forEach((key) => {
132✔
205
        const newPath = currentPath.concat(key);
356✔
206
        result[key] = deepTransformingClone(obj[key], transform, newPath);
356✔
207
        transform(newPath, result, key);
356✔
208
      });
209
      return result;
132✔
210
    }
211
  }
212
  return obj;
236✔
213
}
214

215
// Every set of rules is an object where "match" is an array and
216
// "apply" is the function to apply in case of match. The function to apply
217
// takes (obj, prop) and transforms property "prop" in object "obj".
218
// The "match" is an array of path parts. Each part is either a string or an array.
219
// In case of array, it represents alternatives which all would match.
220
// Special path part '*' matches any subproperty or array index.
221
// Prefixing a part with "!" makes it negative match (doesn't work with multiple alternatives)
222
let cleanupRules = {};
1✔
223
cleanupRules[AUCTION_END] = [{
1✔
224
  match: [['adUnits', 'bidderRequests'], '*', 'bids', '*', ['userId', 'crumbs'], '!id5id'],
225
  apply: 'redact'
226
}, {
227
  match: [['adUnits', 'bidderRequests'], '*', 'bids', '*', ['userId', 'crumbs'], 'id5id', 'uid'],
228
  apply: 'redact'
229
}, {
230
  match: [['adUnits', 'bidderRequests'], '*', 'bids', '*', 'userIdAsEids', '*', 'uids', '*', ['id', 'ext']],
231
  apply: 'redact'
232
}, {
233
  match: ['bidderRequests', '*', 'gdprConsent', 'vendorData'],
234
  apply: 'erase'
235
}, {
236
  match: ['bidsReceived', '*', ['ad', 'native']],
237
  apply: 'erase'
238
}, {
239
  match: ['noBids', '*', ['userId', 'crumbs'], '*'],
240
  apply: 'redact'
241
}, {
242
  match: ['noBids', '*', 'userIdAsEids', '*', 'uids', '*', ['id', 'ext']],
243
  apply: 'redact'
244
}];
245

246
cleanupRules[BID_WON] = [{
1✔
247
  match: [['ad', 'native']],
248
  apply: 'erase'
249
}];
250

251
const TRANSFORM_FUNCTIONS = {
1✔
252
  'redact': redact,
253
  'erase': erase,
254
};
255

256
// Builds a rule function depending on the event type
257
function transformFnFromCleanupRules(eventType) {
258
  const rules = cleanupRules[eventType] || [];
12✔
259
  return (path, obj, key) => {
12✔
260
    for (let i = 0; i < rules.length; i++) {
356✔
261
      let match = true;
1,947✔
262
      const ruleMatcher = rules[i].match;
1,947✔
263
      const transformation = rules[i].apply;
1,947✔
264
      if (ruleMatcher.length !== path.length) {
1,947✔
265
        continue;
1,615✔
266
      }
267
      for (let fragment = 0; fragment < ruleMatcher.length && match; fragment++) {
332✔
268
        const choices = makeSureArray(ruleMatcher[fragment]);
935✔
269
        match = !choices.every((choice) => choice !== '*' &&
1,067✔
270
          (choice.charAt(0) === '!'
776✔
271
            ? path[fragment] === choice.substring(1)
272
            : path[fragment] !== choice));
273
      }
274
      if (match) {
332✔
275
        const transformfn = TRANSFORM_FUNCTIONS[transformation];
22✔
276
        transformfn(obj, key);
22✔
277
        break;
22✔
278
      }
279
    }
280
  };
281
}
282

283
function makeSureArray(object) {
284
  return isArray(object) ? object : [object];
935✔
285
}
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