forked from prebid/Prebid.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into S2S-1925-symitri-rtd-module
- Loading branch information
Showing
8 changed files
with
252 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/** | ||
* This module adds LinkedIn Ads ID to the User ID module | ||
* The {@link module:modules/userId} module is required | ||
* @module modules/linkedInAdsIdSystem | ||
* @requires module:modules/userId | ||
*/ | ||
|
||
import { logInfo, generateUUID } from '../src/utils.js'; | ||
import { submodule } from '../src/hook.js'; | ||
import { getStorageManager } from '../src/storageManager.js'; | ||
import { MODULE_TYPE_UID } from '../src/activities/modules.js'; | ||
import { uspDataHandler, coppaDataHandler, gdprDataHandler } from '../src/adapterManager.js'; | ||
|
||
const MODULE_NAME = 'linkedInAdsId'; | ||
const STORAGE_KEY = 'li_adsId'; | ||
const LOG_PREFIX = 'LinkedIn Ads ID: '; | ||
const LI_FAT_COOKIE = 'li_fat'; | ||
const LI_GIANT_COOKIE = 'li_giant'; | ||
|
||
export const BrowserStorage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); | ||
|
||
/** @type {Submodule} */ | ||
export const linkedInAdsIdSubmodule = { | ||
|
||
/** | ||
* Used to link submodule with config | ||
* @type {string} | ||
*/ | ||
name: MODULE_NAME, | ||
|
||
/** | ||
* @returns {Object} - Object with li_fat and li_giant as string properties | ||
*/ | ||
getCookieIds() { | ||
return { | ||
li_fat: BrowserStorage.getCookie(LI_FAT_COOKIE), | ||
li_giant: BrowserStorage.getCookie(LI_GIANT_COOKIE), | ||
}; | ||
}, | ||
|
||
/** | ||
* Decode stored value for bid requests | ||
* @param {string} id | ||
* @returns {Object} | ||
*/ | ||
decode(id) { | ||
const cookies = this.getCookieIds(); | ||
logInfo(`${LOG_PREFIX} found Legacy cookies: ${JSON.stringify(cookies)}`); | ||
|
||
const linkedInAdsId = { | ||
li_adsId: id, | ||
ext: { | ||
...cookies, | ||
}, | ||
}; | ||
|
||
return { linkedInAdsId }; | ||
}, | ||
|
||
/** | ||
* Performs actions to obtain `linkedInAdsId` from storage | ||
* @returns { { id: string } | undefined } | ||
*/ | ||
getId(config) { | ||
const localKey = config?.storage?.name || STORAGE_KEY; | ||
let id = BrowserStorage.localStorageIsEnabled() && BrowserStorage.getDataFromLocalStorage(localKey); | ||
|
||
if (this.hasConsent() && !id) { | ||
logInfo(`${LOG_PREFIX} existing id not found, generating a new li_adsId`); | ||
id = generateUUID(); | ||
} | ||
|
||
return { id }; | ||
}, | ||
|
||
/** | ||
* Check all consent types, GDPR, CCPA, including processing TCF v2 consent string | ||
* @returns {boolean} | ||
*/ | ||
hasConsent: function() { | ||
/** | ||
* GDPR check | ||
* Negative of (consentData.gdprApplies === true || consentData.gdprApplies === 1) | ||
*/ | ||
const gdprConsentData = gdprDataHandler.getConsentData(); | ||
const hasGDPRConsent = !(gdprConsentData?.gdprApplies); | ||
|
||
/** | ||
* COPPA - Parental consent for the collection and use of personal data for users under the age of 13, | ||
* as required by the COPPA regulation in the United States. | ||
*/ | ||
const isCoppaApplicable = coppaDataHandler.getCoppa(); | ||
|
||
/** | ||
* CCPA Consent String processing | ||
* https://github.com/InteractiveAdvertisingBureau/USPrivacy/blob/master/CCPA/US%20Privacy%20String.md | ||
*/ | ||
const usPrivacyString = uspDataHandler.getConsentData(); | ||
const hasCCPAConsent = !( | ||
usPrivacyString.length === 4 && | ||
usPrivacyString[1] === 'Y' && // Publisher has provided notice | ||
usPrivacyString[2] === 'Y' // user has made a choice to opt out of sale | ||
); | ||
|
||
return hasGDPRConsent && hasCCPAConsent && !isCoppaApplicable; | ||
}, | ||
|
||
eids: { | ||
'linkedInAdsId': { | ||
source: 'linkedin.com', | ||
getValue: function(data) { | ||
return data.li_adsId; | ||
}, | ||
getUidExt: function(data) { | ||
if (data.ext) { | ||
return data.ext; | ||
} | ||
}, | ||
atype: 1, | ||
}, | ||
} | ||
}; | ||
|
||
submodule('userId', linkedInAdsIdSubmodule); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { | ||
linkedInAdsIdSubmodule, | ||
BrowserStorage | ||
} from 'modules/linkedInAdsIdSystem.js'; | ||
import * as Utils from '../../../src/utils.js'; | ||
import { uspDataHandler, coppaDataHandler, gdprDataHandler } from 'src/adapterManager.js'; | ||
|
||
describe('LinkedIn Ads ID module', () => { | ||
const LI_FAT_COOKIE = 'li_fat'; | ||
const LI_GIANT_COOKIE = 'li_giant'; | ||
const dummyLiFat = '12345abc'; | ||
const dummyLiGiant = '67890xyz'; | ||
|
||
describe('getCookieIds', () => { | ||
it('should return li_fat and li_giant cookies', () => { | ||
const storageStub = sinon.stub(BrowserStorage, 'getCookie'); | ||
|
||
storageStub.withArgs(LI_FAT_COOKIE).returns(dummyLiFat); | ||
storageStub.withArgs(LI_GIANT_COOKIE).returns(dummyLiGiant); | ||
|
||
const cookies = linkedInAdsIdSubmodule.getCookieIds(); | ||
|
||
expect(cookies.li_fat).to.equal(dummyLiFat); | ||
expect(cookies.li_giant).to.equal(dummyLiGiant); | ||
storageStub.restore(); | ||
}); | ||
}); | ||
|
||
describe('decode', () => { | ||
it('should return linkedInAdsId and legacy cookies if available', () => { | ||
const storageStub = sinon.stub(BrowserStorage, 'getCookie'); | ||
storageStub.withArgs(LI_FAT_COOKIE).returns(dummyLiFat); | ||
storageStub.withArgs(LI_GIANT_COOKIE).returns(dummyLiGiant); | ||
|
||
const id = '12345abcde'; | ||
const decoded = linkedInAdsIdSubmodule.decode(id); | ||
|
||
expect(decoded.linkedInAdsId).to.deep.equal({ | ||
li_adsId: id, | ||
ext: { | ||
li_fat: dummyLiFat, | ||
li_giant: dummyLiGiant | ||
} | ||
}); | ||
storageStub.restore(); | ||
}); | ||
}); | ||
|
||
describe('getId', () => { | ||
it('should generate and save a new ID', () => { | ||
const genUuidStub = sinon.stub().returns('dummyId123'); | ||
Utils.generateUUID = genUuidStub; | ||
|
||
const hasConsentStub = sinon.stub(linkedInAdsIdSubmodule, 'hasConsent'); | ||
hasConsentStub.returns(true); | ||
|
||
const id = linkedInAdsIdSubmodule.getId(); | ||
expect(id).to.deep.equal({ | ||
id: 'dummyId123' | ||
}); | ||
|
||
expect(genUuidStub.calledOnce).to.be.true; | ||
hasConsentStub.restore(); | ||
}); | ||
}); | ||
|
||
describe('Consent checks `hasConsent`', () => { | ||
let gdprStub, ccpaStub, coppaStub; | ||
|
||
beforeEach(() => { | ||
gdprStub = sinon.stub(gdprDataHandler, 'getConsentData'); | ||
ccpaStub = sinon.stub(uspDataHandler, 'getConsentData'); | ||
coppaStub = sinon.stub(coppaDataHandler, 'getCoppa'); | ||
}); | ||
|
||
afterEach(() => { | ||
gdprStub.restore(); | ||
ccpaStub.restore(); | ||
coppaStub.restore(); | ||
}); | ||
|
||
it('should return false if GDPR consent missing', () => { | ||
ccpaStub.returns('1YNN'); | ||
gdprStub.returns({gdprApplies: true}); | ||
expect(linkedInAdsIdSubmodule.hasConsent()).to.be.false; | ||
}); | ||
|
||
it('should return false if CCPA opt-out', () => { | ||
ccpaStub.returns('1YYN'); | ||
expect(linkedInAdsIdSubmodule.hasConsent()).to.be.false; | ||
}); | ||
|
||
it('should return false if COPPA applicable', () => { | ||
ccpaStub.returns('1YNN'); | ||
coppaStub.returns(true); | ||
expect(linkedInAdsIdSubmodule.hasConsent()).to.be.false; | ||
}); | ||
|
||
it('should return true if all consents present', () => { | ||
gdprStub.returns({gdprApplies: false}); | ||
ccpaStub.returns('1YNN'); | ||
coppaStub.returns(false); | ||
expect(linkedInAdsIdSubmodule.hasConsent()).to.be.true; | ||
}); | ||
}); | ||
}); |