diff --git a/modules/.submodules.json b/modules/.submodules.json index 226673488cf..7e55c353795 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -44,7 +44,8 @@ "verizonMediaIdSystem", "zeotapIdPlusIdSystem", "adqueryIdSystem", - "gravitoIdSystem" + "gravitoIdSystem", + "freepassIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/freepassIdSystem.js b/modules/freepassIdSystem.js new file mode 100644 index 00000000000..d52c537e800 --- /dev/null +++ b/modules/freepassIdSystem.js @@ -0,0 +1,61 @@ +import { submodule } from '../src/hook.js'; +import {generateUUID, logMessage} from '../src/utils.js'; + +const MODULE_NAME = 'freepassId'; + +export const freepassIdSubmodule = { + name: MODULE_NAME, + decode: function (value, config) { + logMessage('Decoding FreePass ID: ', value); + + return { [MODULE_NAME]: value }; + }, + + getId: function (config, consent, cachedIdObject) { + logMessage('Getting FreePass ID using config: ' + JSON.stringify(config)); + + const freepassData = config.params !== undefined ? (config.params.freepassData || {}) : {} + let idObject = {userId: generateUUID()}; + + if (freepassData.commonId !== undefined) { + idObject.commonId = config.params.freepassData.commonId; + } + + if (freepassData.userIp !== undefined) { + idObject.userIp = config.params.freepassData.userIp; + } + + return {id: idObject}; + }, + + extendId: function (config, consent, cachedIdObject) { + let freepassData = config.params.freepassData; + let hasFreepassData = freepassData !== undefined; + if (!hasFreepassData) { + logMessage('No Freepass Data. CachedIdObject will not be extended: ' + JSON.stringify(cachedIdObject)); + return { + id: cachedIdObject + }; + } + + if (freepassData.commonId === cachedIdObject.commonId && freepassData.userIp === cachedIdObject.userIp) { + logMessage('FreePass ID is already up-to-date: ' + JSON.stringify(cachedIdObject)); + return { + id: cachedIdObject + }; + } + + logMessage('Extending FreePass ID object: ' + JSON.stringify(cachedIdObject)); + logMessage('Extending FreePass ID using config: ' + JSON.stringify(config)); + + return { + id: { + commonId: freepassData.commonId, + userIp: freepassData.userIp, + userId: cachedIdObject.userId, + }, + }; + } +}; + +submodule('userId', freepassIdSubmodule); diff --git a/modules/freepassIdSystem.md b/modules/freepassIdSystem.md new file mode 100644 index 00000000000..de0cdf23f68 --- /dev/null +++ b/modules/freepassIdSystem.md @@ -0,0 +1,47 @@ +## FreePass User ID Submodule + +[FreePass](https://freepass-login.com/introduction.html) is a common authentication service operated by Freebit Co., Ltd. Users with a FreePass account do not need to create a new account to use partner services. + +# General Information + +Please contact FreePass before using this ID. + +``` +Module Name: FreePass Id System +Module Type: User Id System +Maintainer: freepass-headerbidding@craid-inc.com +``` + +## Building Prebid with FreePass ID Support + +First, make sure to add the FreePass ID submodule to your Prebid.js package with: + +```shell +gulp build --modules=freepassIdSystem,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'freepassId', + params: { + freepassData: { + commonId: 'fpcommonid123', + userIp: '127.0.0.1' + } + } + }] + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +|--------------------------------|----------|--------|------------------------------------------------------|----------------| +| name | Required | String | The name of this module | `"freepassId"` | +| freepassData | Optional | Object | FreePass data | `{}` | +| freepassData.commonId | Optional | String | Common ID obtained from FreePass | `"abcd1234"` | +| freepassData.userIp | Optional | String | User IP obtained in cooperation with partner service | `"127.0.0.1"` | + diff --git a/test/spec/modules/freepassIdSystem_spec.js b/test/spec/modules/freepassIdSystem_spec.js new file mode 100644 index 00000000000..f7407f5eb94 --- /dev/null +++ b/test/spec/modules/freepassIdSystem_spec.js @@ -0,0 +1,186 @@ +import {freepassIdSubmodule} from 'modules/freepassIdSystem'; +import sinon from 'sinon'; +import * as utils from '../../../src/utils'; + +let expect = require('chai').expect; + +describe('FreePass ID System', function () { + const UUID = '15fde1dc-1861-4894-afdf-b757272f3568'; + + before(function () { + sinon.stub(utils, 'generateUUID').returns(UUID); + sinon.stub(utils, 'logMessage'); + }); + + after(function () { + utils.generateUUID.restore(); + utils.logMessage.restore(); + }); + + describe('freepassIdSubmodule', function () { + it('should expose submodule name', function () { + expect(freepassIdSubmodule.name).to.equal('freepassId'); + }); + }); + + describe('getId', function () { + const config = { + storage: { + name: '_freepassId', + type: 'cookie', + expires: 30 + }, + params: { + freepassData: { + commonId: 'commonId', + userIp: '127.0.0.1' + } + } + }; + + it('should return an IdObject with a UUID', function () { + const objectId = freepassIdSubmodule.getId(config, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.userId).to.equal(UUID); + }); + + it('should include userIp in IdObject', function () { + const objectId = freepassIdSubmodule.getId(config, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.userIp).to.equal('127.0.0.1'); + }); + it('should skip userIp in IdObject if not available', function () { + const localConfig = Object.assign({}, config); + delete localConfig.params.freepassData.userIp; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.userIp).to.be.undefined; + }); + it('should skip userIp in IdObject if freepassData is not available', function () { + const localConfig = JSON.parse(JSON.stringify(config)); + delete localConfig.params.freepassData; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.userIp).to.be.undefined; + }); + it('should skip userIp in IdObject if params is not available', function () { + const localConfig = JSON.parse(JSON.stringify(config)); + delete localConfig.params; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.userIp).to.be.undefined; + }); + it('should include commonId in IdObject', function () { + const objectId = freepassIdSubmodule.getId(config, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.commonId).to.equal('commonId'); + }); + it('should skip commonId in IdObject if not available', function () { + const localConfig = Object.assign({}, config); + delete localConfig.params.freepassData.commonId; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.commonId).to.be.undefined; + }); + it('should skip commonId in IdObject if freepassData is not available', function () { + const localConfig = JSON.parse(JSON.stringify(config)); + delete localConfig.params.freepassData; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.commonId).to.be.undefined; + }); + it('should skip commonId in IdObject if params is not available', function () { + const localConfig = JSON.parse(JSON.stringify(config)); + delete localConfig.params; + const objectId = freepassIdSubmodule.getId(localConfig, undefined); + expect(objectId).to.be.an('object'); + expect(objectId.id).to.be.an('object'); + expect(objectId.id.commonId).to.be.undefined; + }); + }); + + describe('decode', function () { + it('should have module name as property', function () { + const decodedId = freepassIdSubmodule.decode({}, {}); + expect(decodedId).to.be.an('object'); + expect(decodedId).to.have.property('freepassId'); + }); + it('should have IObject as property value', function () { + const idObject = { + commonId: 'commonId', + userIp: '127.0.0.1', + userId: UUID + }; + const decodedId = freepassIdSubmodule.decode(idObject, {}); + expect(decodedId).to.be.an('object'); + expect(decodedId.freepassId).to.be.an('object'); + expect(decodedId.freepassId).to.equal(idObject); + }); + }); + + describe('extendId', function () { + const config = { + storage: { + name: '_freepassId', + type: 'cookie', + expires: 30 + }, + params: { + freepassData: { + commonId: 'commonId', + userIp: '127.0.0.1' + } + } + }; + + it('should return cachedIdObject if there are no changes', function () { + const idObject = freepassIdSubmodule.getId(config, undefined); + const cachedIdObject = Object.assign({}, idObject.id); + const extendedIdObject = freepassIdSubmodule.extendId(config, undefined, cachedIdObject); + expect(extendedIdObject).to.be.an('object'); + expect(extendedIdObject.id).to.be.an('object'); + expect(extendedIdObject.id).to.equal(cachedIdObject); + }); + + it('should return cachedIdObject if there are no new data', function () { + const idObject = freepassIdSubmodule.getId(config, undefined); + const cachedIdObject = Object.assign({}, idObject.id); + const localConfig = JSON.parse(JSON.stringify(config)); + delete localConfig.params.freepassData; + const extendedIdObject = freepassIdSubmodule.extendId(localConfig, undefined, cachedIdObject); + expect(extendedIdObject).to.be.an('object'); + expect(extendedIdObject.id).to.be.an('object'); + expect(extendedIdObject.id).to.equal(cachedIdObject); + }); + + it('should return new commonId if there are changes', function () { + const idObject = freepassIdSubmodule.getId(config, undefined); + const cachedIdObject = Object.assign({}, idObject.id); + const localConfig = JSON.parse(JSON.stringify(config)); + localConfig.params.freepassData.commonId = 'newCommonId'; + const extendedIdObject = freepassIdSubmodule.extendId(localConfig, undefined, cachedIdObject); + expect(extendedIdObject).to.be.an('object'); + expect(extendedIdObject.id).to.be.an('object'); + expect(extendedIdObject.id.commonId).to.equal('newCommonId'); + }); + + it('should return new userIp if there are changes', function () { + const idObject = freepassIdSubmodule.getId(config, undefined); + const cachedIdObject = Object.assign({}, idObject.id); + const localConfig = JSON.parse(JSON.stringify(config)); + localConfig.params.freepassData.userIp = '192.168.1.1'; + const extendedIdObject = freepassIdSubmodule.extendId(localConfig, undefined, cachedIdObject); + expect(extendedIdObject).to.be.an('object'); + expect(extendedIdObject.id).to.be.an('object'); + expect(extendedIdObject.id.userIp).to.equal('192.168.1.1'); + }); + }); +});