Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flashtalking FTRACK User ID Submodule: initial release #8063

Merged
merged 9 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"flocIdSystem",
"haloIdSystem",
"id5IdSystem",
"ftrackIdSystem",
"identityLinkIdSystem",
"idxIdSystem",
"imuIdSystem",
Expand Down
184 changes: 184 additions & 0 deletions modules/ftrackIdSystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/**
* This module adds ftrack to the User ID module
* The {@link module:modules/userId} module is required
* @module modules/ftrack
* @requires module:modules/userId
*/

import * as utils from '../src/utils.js';
import { submodule } from '../src/hook.js';
import { getStorageManager } from '../src/storageManager.js';
import { uspDataHandler } from '../src/adapterManager.js';

const MODULE_NAME = 'ftrackId';
const LOG_PREFIX = 'FTRACK - ';
const LOCAL_STORAGE_EXP_DAYS = 30;
const VENDOR_ID = null;
const LOCAL_STORAGE = 'html5';
const FTRACK_STORAGE_NAME = 'ftrackId';
const FTRACK_PRIVACY_STORAGE_NAME = `${FTRACK_STORAGE_NAME}_privacy`;
const FTRACK_URL = 'https://d9.flashtalking.com/d9core';
const storage = getStorageManager(VENDOR_ID, MODULE_NAME);

let consentInfo = {
gdpr: {
applies: 0,
consentString: null,
pd: null
smenzer marked this conversation as resolved.
Show resolved Hide resolved
},
usPrivacy: {
value: null
}
};

/** @type {Submodule} */
export const ftrackIdSubmodule = {
/**
* used to link submodule with config
* @type {string}
*/
name: `ftrack`,

/**
* Decodes the 'value'
* @function decode (required method)
* @param {(Object|string)} value
* @param {SubmoduleConfig|undefined} config
* @returns {(Object|undefined)} an object with the key being ideally camel case
* similar to the module name and ending in id or Id
*/
decode (value, config) {
return {
ftrackId: value
};
},

/**
* performs action(s) to obtain ids from D9 and return the Device IDs
* should be the only method that gets a new ID (from ajax calls or a cookie/local storage)
* @function getId (required method)
* @param {SubmoduleConfig} config
* @param {ConsentData} consentData
* @param {(Object|undefined)} cacheIdObj
* @returns {IdResponse|undefined}
*/
getId (config, consentData, cacheIdObj) {
if (this.isConfigOk(config) === false || this.isThereConsent(consentData) === false) return undefined;

return {
callback: function () {
window.D9v = {
UserID: '99999999999999',
CampID: '3175',
CCampID: '148556'
};
window.D9r = {
DeviceID: true,
SingleDeviceID: true,
callback: function(response) {
if (response) {
storage.setDataInLocalStorage(`${FTRACK_STORAGE_NAME}_exp`, (new Date(Date.now() + (1000 * 60 * 60 * 24 * LOCAL_STORAGE_EXP_DAYS))).toUTCString());
storage.setDataInLocalStorage(`${FTRACK_STORAGE_NAME}`, JSON.stringify(response));

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

return response;
}
};

if (config.params && config.params.url && config.params.url === FTRACK_URL) {
var ftrackScript = document.createElement('script');
ftrackScript.setAttribute('src', config.params.url);
window.document.body.appendChild(ftrackScript);
}
}
};
},

/**
* Called when IDs are already in localStorage
* should just be adding additional data to the cacheIdObj object
* @function extendId (optional method)
* @param {SubmoduleConfig} config
* @param {ConsentData} consentData
* @param {(Object|undefined)} cacheIdObj
* @returns {IdResponse|undefined}
*/
extendId (config, consentData, cacheIdObj) {
this.isConfigOk(config);
return cacheIdObj;
},

/*
* Validates the config, if it is not correct, then info cannot be saved in localstorage
* @function isConfigOk
* @param {SubmoduleConfig} config from HTML
* @returns {true|false}
*/
isConfigOk: function(config) {
if (!config.storage || !config.storage.type || !config.storage.name) {
utils.logError(LOG_PREFIX + 'config.storage required to be set.');
return false;
}

// in a future release, we may return false if storage type or name are not set as required
if (config.storage.type !== LOCAL_STORAGE) {
utils.logWarn(LOG_PREFIX + 'config.storage.type recommended to be "' + LOCAL_STORAGE + '".');
}
// in a future release, we may return false if storage type or name are not set as required
if (config.storage.name !== FTRACK_STORAGE_NAME) {
utils.logWarn(LOG_PREFIX + 'config.storage.name recommended to be "' + FTRACK_STORAGE_NAME + '".');
}

if (!config.hasOwnProperty('params') || !config.params.hasOwnProperty('url') || config.params.url !== FTRACK_URL) {
utils.logWarn(LOG_PREFIX + 'config.params.url is required for ftrack to run. Url should be "' + FTRACK_URL + '".');
return false;
}

return true;
},

isThereConsent: function(consentData) {
let consentValue = true;

/*
* Scenario 1: GDPR
* if GDPR Applies is true|1, we do not have consent
* if GDPR Applies does not exist or is false|0, we do not NOT have consent
*/
if (consentData && consentData.gdprApplies && (consentData.gdprApplies === true || consentData.gdprApplies === 1)) {
consentInfo.gdpr.applies = 1;
consentValue = false;
}
// If consentString exists, then we store it even though we are not using it
if (consentData && consentData.consentString !== 'undefined' && !utils.isEmpty(consentData.consentString) && !utils.isEmptyStr(consentData.consentString)) {
consentInfo.gdpr.consentString = consentData.consentString;
}

/*
* Scenario 2: CCPA/us_privacy
* if usp exists (assuming this check determines the location of the device to be within the California)
* parse the us_privacy string to see if we have consent
* for version 1 of us_privacy strings, if 'Opt-Out Sale' is 'Y' we do not track
*/
const usp = uspDataHandler.getConsentData();
let usPrivacyVersion;
// let usPrivacyOptOut;
let usPrivacyOptOutSale;
// let usPrivacyLSPA;
if (typeof usp !== 'undefined' && !utils.isEmpty(usp) && !utils.isEmptyStr(usp)) {
consentInfo.usPrivacy.value = usp;
usPrivacyVersion = usp[0];
// usPrivacyOptOut = usp[1];
usPrivacyOptOutSale = usp[2];
// usPrivacyLSPA = usp[3];
}
if (usPrivacyVersion == 1 && usPrivacyOptOutSale === 'Y') consentValue = false;

return consentValue;
}
};

submodule('userId', ftrackIdSubmodule);
72 changes: 72 additions & 0 deletions modules/ftrackIdSystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Flashtalking's FTrack Identity Framework User ID Module

*The FTrack Identity Framework User ID Module allows publishers to take advantage of Flashtalking's FTrack ID during the bidding process.*

### [FTrack](https://www.flashtalking.com/identity-framework#FTrack)

Flashtalking’s cookieless tracking technology uses probabilistic device recognition to derive a privacy-friendly persistent ID for each device.

**ANTI-FINGERPRINTING**
Copy link
Collaborator

@patmmccann patmmccann Feb 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the code appears to be an attempt to form a fingerprint. The RTD module you provide sends user segments. Is anything in the paragraph below accurate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patmmccann

Flashtalking's FTrack ID should not be considered fingerprinting software since Fingerprinting, as defined by Google, includes opaque or hidden techniques, tracking in a covert manner, and identity creation without user knowledge or their ability to opt out.

  • Flashtalking deploys a privacy icon (purple shield) on every ad served with FTrack enabled. This icon when moused over announces itself as a "Privacy Notification" and links the user directly to information about what Flashtalking and FTrack are, and aren't.
  • This Consumer Privacy page also links to Flashtalking's privacy policy and opt-out, as well as the AdChoices opt-out and the EDAA's global opt-out site.
  • Additionally, we leverage the FTrack ID to maintain a user's opt-out without reliance on cookies, unlike other vendors who offer "cookie-based" opt-out only for cookieless trackers.
  • Importantly, FTrack does not access PII, including email addresses, nor any sensitive information.
  • The negative concepts of fingerprinting are contingent upon the avoidance of user transparency and opt out. Flashtalking contends that leveraging low-risk identity signals while actively informing users of what their data is being used for, and providing easy, persistent opt-out, is a far more user and privacy-centric system.

We feel it is extremely important to inform potential partners of the difference between fingerprinting as an opaque, hidden, covert tracking system without user knowledge and transparency and FTrack as a consent-driven, privacy-friendly probabilistic ID. We welcome the opportunity to walk through exactly what FTrack can, does, and doesn't do in terms of user data and our extensive system-wide privacy tools designed to maximize transparency around our tools to users on the open web.

Copy link
Collaborator

@patmmccann patmmccann Feb 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this in committee and we're not comfortable with the collect signals functionality of your code. You still have a couple of options that could lead to a merge. Many of your peer identity providers simply assume in their submodules that code that is not linked nor contained within this project has run. Eg #7985 provides a reference implementation of both assuming that the publisher has integrated directly with the identity provider, and an implementation that facilitates an integration without referencing the script directly, by making its location a configuration parameter. Many other identity provider reference implementations choose one of these two avenues. If you discover other code in this project collecting signals in the manner of this PR, feel free to flag.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @patmmccann . I shared your feedback with our "committee" and we will make adjustments based on your feedback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patmmccann The config and code have been changed. Hopefully the change successfully addresses the points the committee made about the previous commit. Thanks

FTrack operates in strict compliance with [Google’s definition of anti-fingerprinting](https://blog.google/products/ads-commerce/2021-01-privacy-sandbox/). FTrack does not access PII or sensitive information and provides consumers with notification and choice on every impression. We do not participate in the types of activities that most concern privacy advocates (profiling consumers, building audience segments, and/or monetizing consumer data).

**GDPR COMPLIANT**
Flashtalking is integrated with the IAB EU’s Transparency & Consent Framework (TCF) and operates on a Consent legal basis where required. As a Data Processor under GDPR, Flashtalking does not combine data across customers nor sell data to third parties.

---

### Support or Maintenance:

Questions? Comments? Bugs? Praise? Please contact FlashTalking's Prebid Support at [prebid-support@flashtalking.com](mailto:prebid-support@flashtalking.com)

---

### FTrack User ID Configuration

The following configuration parameters are available:

```javascript
pbjs.setConfig({
userSync: {
userIds: [{
name: 'FTrack',
params: {
url: 'https://d9.flashtalking.com/d9core' // required, if not populated ftrack will not run
},
storage: {
type: 'html5', // "html5" is the required storage type
name: 'FTrackId', // "FTrackId" is the required storage name
expires: 90, // storage lasts for 90 days
refreshInSeconds: 8*3600 // refresh ID every 8 hours to ensure it's fresh
}
}],
auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules
}
});
```

| Param under userSync.userIds[] | Scope | Type | Description | Example |
| :-- | :-- | :-- | :-- | :-- |
| name | Required | String | The name of this module: `"FTrack"` | `"FTrack"` |
| storage | Required | Object | Storage settings for how the User ID module will cache the FTrack ID locally | |
| storage.type | Required | String | This is where the results of the user ID will be stored. FTrack **requires** `"html5"`. | `"html5"` |
| storage.name | Required | String | The name of the local storage where the user ID will be stored. FTrack **requires** `"FTrackId"`. | `"FTrackId"` |
| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. FTrack recommends `90`. | `90` |
| storage.refreshInSeconds | Optional | Integer | How many seconds until the FTrack ID will be refreshed. FTrack strongly recommends 8 hours between refreshes | `8*3600` |

---

### Privacy Policies.

Complete information available on the Flashtalking [privacy policy page](https://www.flashtalking.com/privacypolicy).

#### OPTING OUT OF INTEREST-BASED ADVERTISING & COLLECTION OF PERSONAL INFORMATION

Please visit our [Opt Out Page](https://www.flashtalking.com/optout).

#### REQUEST REMOVAL OF YOUR PERSONAL DATA (WHERE APPLICABLE)

You may request by emailing [mailto:privacy@flashtalking.com](privacy@flashtalking.com).

#### GDPR

In its current state, Flashtalking’s FTrack Identity Framework User ID Module does not create an ID if a user's consentData is "truthy" (true, 1). In other words, if GDPR applies in any way to a user, FTrack does not create an ID.
14 changes: 14 additions & 0 deletions modules/userId/eids.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ const USER_IDS_CONFIG = {
}
},

// ftrack
'ftrackId': {
source: 'flashtalking.com',
smenzer marked this conversation as resolved.
Show resolved Hide resolved
atype: 1,
getValue: function(data) {
return data.uid
},
getUidExt: function(data) {
if (data.ext) {
return data.ext;
}
}
},

// parrableId
'parrableId': {
source: 'parrable.com',
Expand Down
7 changes: 7 additions & 0 deletions modules/userId/eids.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ userIdAsEids = [
}]
},

{
source: 'flashtalking.com',
uids: [{
id: 'some-random-id-value',
atype: 1
},

{
source: 'parrable.com',
uids: [{
Expand Down
11 changes: 11 additions & 0 deletions modules/userId/userId.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ pbjs.setConfig({
expires: 90, // Expiration in days
refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires'
},
}, {
name: "ftrackId",
storage: {
type: "html5",
name: "ftrackId",
expires: 90,
refreshInSeconds: 8*3600
},
params: {
url: 'https://d9.flashtalking.com/d9core', // required, if not populated ftrack will not run
}
}, {
name: 'parrableId',
params: {
Expand Down
Loading