Skip to content

Commit

Permalink
Add lmpIdSystem userId submodule (prebid#11431)
Browse files Browse the repository at this point in the history
Co-authored-by: Antoine Niek <antoineniek@gmail.com>
  • Loading branch information
2 people authored and DecayConstant committed Jul 18, 2024
1 parent 5ae9101 commit 1bf4e67
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 0 deletions.
1 change: 1 addition & 0 deletions modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"justIdSystem",
"kinessoIdSystem",
"liveIntentIdSystem",
"lmpIdSystem",
"lockrAIMIdSystem",
"lotamePanoramaIdSystem",
"merkleIdSystem",
Expand Down
61 changes: 61 additions & 0 deletions modules/lmpIdSystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* This module adds lmpId support to the User ID module
* The {@link module:modules/userId} module is required.
* @module modules/lmpIdSystem
* @requires module:modules/userId
*/

import { submodule } from '../src/hook.js';
import { MODULE_TYPE_UID } from '../src/activities/modules.js';
import { getStorageManager } from '../src/storageManager.js';

const MODULE_NAME = 'lmpid';
const STORAGE_KEY = '__lmpid';
export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME });

function readFromLocalStorage() {
return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(STORAGE_KEY) : null;
}

function getLmpid() {
return window[STORAGE_KEY] || readFromLocalStorage();
}

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

/**
* decode the stored id value for passing to bid requests
* @function
* @param { string | undefined } value
* @return { {lmpid: string} | undefined }
*/
decode(value) {
return value ? { lmpid: value } : undefined;
},

/**
* Retrieve the LMPID
* @function
* @param {SubmoduleConfig} config
* @return {{id: string | undefined} | undefined}
*/
getId(config) {
const id = getLmpid();
return id ? { id } : undefined;
},

eids: {
'lmpid': {
source: 'loblawmedia.ca',
atype: 3
},
}
};

submodule('userId', lmpIdSubmodule);
27 changes: 27 additions & 0 deletions modules/lmpIdSystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# LMPID

The Loblaw Media Private ID (LMPID) is the Loblaw Advance identity solution deployed by its media partners. LMPID leverages encrypted user registration information to provide a privacy-conscious, secure, and reliable identifier to power Loblaw Advance's digital advertising ecosystem.

## LMPID Registration

If you're a media company looking to partner with Loblaw Advance, please reach out to us through our [Contact page](https://www.loblawadvance.ca/contact-us)

## LMPID Configuration

First, make sure to add the LMPID submodule to your Prebid.js package with:

```
gulp build --modules=lmpIdSystem,userId
```

The following configuration parameters are available:

```javascript
pbjs.setConfig({
userSync: {
userIds: [{
name: 'lmpid'
}]
}
});
```
3 changes: 3 additions & 0 deletions modules/userId/userId.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,9 @@ pbjs.setConfig({
},
{
name: 'naveggId',
},
{
name: 'lmpid',
}],
syncDelay: 5000
}
Expand Down
124 changes: 124 additions & 0 deletions test/spec/modules/lmpIdSystem_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { expect } from 'chai';
import { find } from 'src/polyfill.js';
import { config } from 'src/config.js';
import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js';
import { storage, lmpIdSubmodule } from 'modules/lmpIdSystem.js';
import { mockGdprConsent } from '../../helpers/consentData.js';

function getConfigMock() {
return {
userSync: {
syncDelay: 0,
userIds: [{
name: 'lmpid'
}]
}
}
}

function getAdUnitMock(code = 'adUnit-code') {
return {
code,
mediaTypes: { banner: {}, native: {} },
sizes: [
[300, 200],
[300, 600]
],
bids: [{
bidder: 'sampleBidder',
params: { placementId: 'banner-only-bidder' }
}]
};
}

describe('LMPID System', () => {
let getDataFromLocalStorageStub, localStorageIsEnabledStub;
let windowLmpidStub;

beforeEach(() => {
window.__lmpid = undefined;
windowLmpidStub = sinon.stub(window, '__lmpid');
getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage');
localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled');
});

afterEach(() => {
getDataFromLocalStorageStub.restore();
localStorageIsEnabledStub.restore();
windowLmpidStub.restore();
});

describe('LMPID: test "getId" method', () => {
it('prefers the window cached LMPID', () => {
localStorageIsEnabledStub.returns(true);
getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid');

windowLmpidStub.value('lmpid');
expect(lmpIdSubmodule.getId()).to.deep.equal({ id: 'lmpid' });
});

it('fallbacks on localStorage when window cache is falsy', () => {
localStorageIsEnabledStub.returns(true);
getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid');

windowLmpidStub.value('');
expect(lmpIdSubmodule.getId()).to.deep.equal({ id: 'stored-lmpid' });

windowLmpidStub.value(false);
expect(lmpIdSubmodule.getId()).to.deep.equal({ id: 'stored-lmpid' });
});

it('fallbacks only if localStorageIsEnabled', () => {
localStorageIsEnabledStub.returns(false);
getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid');

expect(lmpIdSubmodule.getId()).to.be.undefined;
});
});

describe('LMPID: test "decode" method', () => {
it('provides the lmpid from a stored object', () => {
expect(lmpIdSubmodule.decode('lmpid')).to.deep.equal({ lmpid: 'lmpid' });
});
});

describe('LMPID: requestBids hook', () => {
let adUnits;
let sandbox;

beforeEach(() => {
sandbox = sinon.sandbox.create();
mockGdprConsent(sandbox);
adUnits = [getAdUnitMock()];
init(config);
setSubmoduleRegistry([lmpIdSubmodule]);
getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid');
localStorageIsEnabledStub.returns(true);
config.setConfig(getConfigMock());
});

afterEach(() => {
sandbox.restore();
});

it('when a stored LMPID exists it is added to bids', (done) => {
requestBidsHook(() => {
adUnits.forEach(unit => {
unit.bids.forEach(bid => {
expect(bid).to.have.deep.nested.property('userId.lmpid');
expect(bid.userId.lmpid).to.equal('stored-lmpid');
const lmpidAsEid = find(bid.userIdAsEids, e => e.source == 'loblawmedia.ca');
expect(lmpidAsEid).to.deep.equal({
source: 'loblawmedia.ca',
uids: [{
id: 'stored-lmpid',
atype: 3,
}]
});
});
});
done();
}, { adUnits });
});
});
});

0 comments on commit 1bf4e67

Please sign in to comment.