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

prebid / Prebid.js / #283

20 Feb 2025 06:08PM UTC coverage: 90.519% (-0.005%) from 90.524%
#283

push

travis-ci

prebidjs-release
Prebid 9.31.0 release

41886 of 52458 branches covered (79.85%)

62085 of 68588 relevant lines covered (90.52%)

203.0 hits per line

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

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

8
import * as utils from '../src/utils.js';
9
import {submodule} from '../src/hook.js';
10
import {getStorageManager} from '../src/storageManager.js';
11
import {loadExternalScript} from '../src/adloader.js';
12
import {MODULE_TYPE_UID} from '../src/activities/modules.js';
13

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

21
const MODULE_NAME = 'ftrackId';
1✔
22
const LOG_PREFIX = 'FTRACK - ';
1✔
23
const LOCAL_STORAGE_EXP_DAYS = 30;
1✔
24
const LOCAL_STORAGE = 'html5';
1✔
25
const FTRACK_STORAGE_NAME = 'ftrackId';
1✔
26
const FTRACK_PRIVACY_STORAGE_NAME = `${FTRACK_STORAGE_NAME}_privacy`;
1✔
27
const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME});
1✔
28

29
let consentInfo = {
1✔
30
  gdpr: {
31
    applies: 0,
32
    consentString: null,
33
    pd: null
34
  },
35
  usPrivacy: {
36
    value: null
37
  }
38
};
39

40
/** @type {Submodule} */
41
export const ftrackIdSubmodule = {
1✔
42
  /**
43
   * used to link submodule with config
44
   * @type {string}
45
   */
46
  name: `ftrack`,
47

48
  /**
49
   * Decodes the 'value'
50
   * @function decode (required method)
51
   * @param {(Object|string)} value
52
   * @param {SubmoduleConfig|undefined} config
53
   * @returns {(Object|undefined)} an object with the key being ideally camel case
54
   *   similar to the module name and ending in id or Id
55
   */
56
  decode (value, config) {
57
    if (!value) {
5!
58
      return;
×
59
    };
60

61
    const DECODE_RESPONSE = {
5✔
62
      ftrackId: {
63
        uid: '',
64
        ext: {}
65
      }
66
    }
67

68
    // Loop over the value's properties:
69
    // -- if string, assign value as is.
70
    // -- if array, convert to string then assign value.
71
    // -- If neither type, assign value as empty string
72
    for (var key in value) {
5✔
73
      let keyValue = value[key];
19✔
74
      if (Array.isArray(keyValue)) {
19✔
75
        keyValue = keyValue.join('|');
5✔
76
      } else if (typeof value[key] !== 'string') {
14!
77
        // Unexpected value type, should be string or array
78
        keyValue = '';
×
79
      }
80

81
      DECODE_RESPONSE.ftrackId.ext[key] = keyValue;
19✔
82
    }
83

84
    // If we have DeviceId value, assign it to the uid property
85
    if (DECODE_RESPONSE.ftrackId.ext.hasOwnProperty('DeviceID')) {
5✔
86
      DECODE_RESPONSE.ftrackId.uid = DECODE_RESPONSE.ftrackId.ext.DeviceID;
2✔
87
    }
88

89
    return DECODE_RESPONSE;
5✔
90
  },
91

92
  /**
93
   * performs action(s) to obtain ids from D9 and return the Device IDs
94
   * should be the only method that gets a new ID (from ajax calls or a cookie/local storage)
95
   * @function getId (required method)
96
   * @param {SubmoduleConfig} config
97
   * @param {ConsentData} consentData
98
   * @param {(Object|undefined)} cacheIdObj
99
   * @returns {IdResponse|undefined} A response object that contains id and/or callback.
100
   */
101
  getId (config, consentData, cacheIdObj) {
102
    if (this.isConfigOk(config) === false || this.isThereConsent(consentData) === false) return undefined;
8!
103

104
    return {
8✔
105
      callback: function (cb) {
106
        window.D9v = {
8✔
107
          UserID: '99999999999999',
108
          CampID: '3175',
109
          CCampID: '148556'
110
        };
111
        window.D9r = {
8✔
112
          callback: function(response) {
113
            if (response) {
1!
114
              storage.setDataInLocalStorage(`${FTRACK_STORAGE_NAME}_exp`, (new Date(Date.now() + (1000 * 60 * 60 * 24 * LOCAL_STORAGE_EXP_DAYS))).toUTCString());
1✔
115
              storage.setDataInLocalStorage(`${FTRACK_STORAGE_NAME}`, JSON.stringify(response));
1✔
116

117
              storage.setDataInLocalStorage(`${FTRACK_PRIVACY_STORAGE_NAME}_exp`, (new Date(Date.now() + (1000 * 60 * 60 * 24 * LOCAL_STORAGE_EXP_DAYS))).toUTCString());
1✔
118
              storage.setDataInLocalStorage(`${FTRACK_PRIVACY_STORAGE_NAME}`, JSON.stringify(consentInfo));
1✔
119
            };
120

121
            if (typeof cb === 'function') cb(response);
1!
122

123
            return response;
1✔
124
          }
125
        };
126

127
        // If config.params.ids does not exist, set defaults
128
        if (!config.params.hasOwnProperty('ids')) {
8✔
129
          window.D9r.DeviceID = true;
1✔
130
          window.D9r.SingleDeviceID = true;
1✔
131
        } else {
132
          if (config.params.ids.hasOwnProperty('device id') && config.params.ids['device id'] === true) {
7✔
133
            window.D9r.DeviceID = true;
3✔
134
          }
135
          if (config.params.ids.hasOwnProperty('single device id') && config.params.ids['single device id'] === true) {
7✔
136
            window.D9r.SingleDeviceID = true;
3✔
137
          }
138
          if (config.params.ids.hasOwnProperty('household id') && config.params.ids['household id'] === true) {
7✔
139
            window.D9r.HHID = true;
1✔
140
          }
141
        }
142

143
        // Creates an async script element and appends it to the document
144
        loadExternalScript(config.params.url, MODULE_TYPE_UID, MODULE_NAME);
8✔
145
      }
146
    };
147
  },
148

149
  /**
150
   * Called when IDs are already in localStorage
151
   * should just be adding additional data to the cacheIdObj object
152
   * @function extendId (optional method)
153
   * @param {SubmoduleConfig} config
154
   * @param {ConsentData} consentData
155
   * @param {(Object|undefined)} cacheIdObj
156
   * @returns {IdResponse|undefined}
157
   */
158
  extendId (config, consentData, cacheIdObj) {
159
    this.isConfigOk(config);
3✔
160
    return cacheIdObj;
3✔
161
  },
162

163
  /*
164
   * Validates the config, if it is not correct, then info cannot be saved in localstorage
165
   * @function isConfigOk
166
   * @param {SubmoduleConfig} config from HTML
167
   * @returns {true|false}
168
   */
169
  isConfigOk: function(config) {
170
    if (!config.storage || !config.storage.type || !config.storage.name) {
17✔
171
      utils.logError(LOG_PREFIX + 'config.storage required to be set.');
3✔
172
      return false;
3✔
173
    }
174

175
    // in a future release, we may return false if storage type or name are not set as required
176
    if (config.storage.type !== LOCAL_STORAGE) {
14✔
177
      utils.logWarn(LOG_PREFIX + 'config.storage.type recommended to be "' + LOCAL_STORAGE + '".');
1✔
178
    }
179
    // in a future release, we may return false if storage type or name are not set as required
180
    if (config.storage.name !== FTRACK_STORAGE_NAME) {
14✔
181
      utils.logWarn(LOG_PREFIX + 'config.storage.name recommended to be "' + FTRACK_STORAGE_NAME + '".');
1✔
182
    }
183

184
    if (!config.hasOwnProperty('params') || !config.params.hasOwnProperty('url')) {
14✔
185
      utils.logWarn(LOG_PREFIX + 'config.params.url is required for ftrack to run.');
1✔
186
      return false;
1✔
187
    }
188

189
    return true;
13✔
190
  },
191

192
  isThereConsent: function(consentData) {
193
    let consentValue = true;
17✔
194
    const {gdpr, usp} = consentData ?? {};
17✔
195
    /*
196
     * Scenario 1: GDPR
197
     *   if GDPR Applies is true|1, we do not have consent
198
     *   if GDPR Applies does not exist or is false|0, we do not NOT have consent
199
     */
200
    if (gdpr?.gdprApplies === true || gdpr?.gdprApplies === 1) {
17✔
201
      consentInfo.gdpr.applies = 1;
2✔
202
      consentValue = false;
2✔
203
    }
204
    // If consentString exists, then we store it even though we are not using it
205
    if (typeof gdpr?.consentString !== 'undefined' && !utils.isEmpty(gdpr.consentString) && !utils.isEmptyStr(gdpr.consentString)) {
17!
206
      consentInfo.gdpr.consentString = consentData.consentString;
×
207
    }
208

209
    /*
210
     * Scenario 2: CCPA/us_privacy
211
     *   if usp exists (assuming this check determines the location of the device to be within the California)
212
     *     parse the us_privacy string to see if we have consent
213
     *     for version 1 of us_privacy strings, if 'Opt-Out Sale' is 'Y' we do not track
214
     */
215
    let usPrivacyVersion;
216
    // let usPrivacyOptOut;
217
    let usPrivacyOptOutSale;
218
    // let usPrivacyLSPA;
219
    if (typeof usp !== 'undefined' && !utils.isEmpty(usp) && !utils.isEmptyStr(usp)) {
17✔
220
      consentInfo.usPrivacy.value = usp;
3✔
221
      usPrivacyVersion = usp[0];
3✔
222
      // usPrivacyOptOut = usp[1];
223
      usPrivacyOptOutSale = usp[2];
3✔
224
      // usPrivacyLSPA = usp[3];
225
    }
226
    if (usPrivacyVersion == 1 && usPrivacyOptOutSale === 'Y') consentValue = false;
17✔
227

228
    return consentValue;
17✔
229
  },
230
  eids: {
231
    'ftrackId': {
232
      source: 'flashtalking.com',
233
      atype: 1,
234
      getValue: function(data) {
235
        let value = '';
11✔
236
        if (data && data.ext && data.ext.DeviceID) {
11✔
237
          value = data.ext.DeviceID;
7✔
238
        }
239
        return value;
11✔
240
      },
241
      getUidExt: function(data) {
242
        return data && data.ext;
11✔
243
      }
244
    },
245
  }
246
};
247

248
submodule('userId', ftrackIdSubmodule);
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