From 63887ea23baaed7cf4111857ee1dc4874dd4868d Mon Sep 17 00:00:00 2001 From: Jeff Palladino Date: Wed, 7 Aug 2024 11:18:06 -0600 Subject: [PATCH 01/10] symitriDapRtdModule-enable-secure-ad-receipt --- modules/symitriDapRtdProvider.js | 44 ++++++++++++- modules/symitriDapRtdProvider.md | 109 +++++++++++++++++++++---------- 2 files changed, 116 insertions(+), 37 deletions(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 1921bbddf4c..598693f612d 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -131,14 +131,37 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { /** * Module init - * @param {Object} provider + * @param {Object} config * @param {Object} userConsent * @return {boolean} */ - function init(provider, userConsent) { + function init(config, userConsent) { if (dapUtils.checkConsent(userConsent) === false) { return false; } + + if (window.pbjs?.onEvent && config?.params?.apiAuthToken) { + window.pbjs.onEvent('bidWon', function(bid) { + logInfo('DEBUG(###onBidWon###)'); + logInfo(bid); + let bidjson = JSON.stringify(bid); + let cb = { + success: (response, request) => { + logInfo('DEBUG(###Winning Bid SAVED###)'); + }, + error: (request, error) => { + logInfo(error); + logInfo('DEBUG(###Save Winning Bid FAILED###)'); + } + }; + let url = 'https://ProdSymPrebidEventhub1.servicebus.windows.net/prebid-said-1/messages'; + ajax(url, cb, bidjson, { + method: 'POST', + customHeaders: {'Content-Type': 'application/atom+xml;type=entry;charset=utf-8', 'Authorization': config.params.apiAuthToken} + }); + }); + } + return true; } @@ -159,7 +182,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { api_version: rtdConfig.params.apiVersion, domain: rtdConfig.params.domain, segtax: rtdConfig.params.segtax, - identity: {type: rtdConfig.params.identityType} + identity: {type: rtdConfig.params.identityType, identity: rtdConfig.params.identityValue, selector: rtdConfig.params.identitySelector}, }; let refreshMembership = true; let token = dapUtils.dapGetTokenFromLocalStorage(); @@ -596,7 +619,22 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { if (typeof (identity.identity) != typeof (undefined)) { apiParams.identity = identity.identity; + } else if (identity.type == 'email' && typeof (identity.selector) != typeof (undefined) && identity.selector.trim() !== '') { + let identityElement = document.querySelector(identity.selector); + let identityEmail = identityElement.innerHTML; + if (identityElement.tagName == 'INPUT') { + identityEmail = identityElement.value; + } + + if (identityEmail) { + const EMAIL_VALIDATION_REGEX = /((([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,}))/i; + const regex = new RegExp(EMAIL_VALIDATION_REGEX); + if (regex.test(identityEmail)) { + apiParams.identity = identityEmail; + } + } } + if (typeof (identity.attributes) != typeof (undefined)) { apiParams.attributes = identity.attributes; } diff --git a/modules/symitriDapRtdProvider.md b/modules/symitriDapRtdProvider.md index 654f7b25bfd..da3d82f774c 100644 --- a/modules/symitriDapRtdProvider.md +++ b/modules/symitriDapRtdProvider.md @@ -1,49 +1,90 @@ -### Overview +--- +layout: page_v2 +title: Symitri DAP Real Time Data Provider Module +display_name: Symitri DAP Real Time Data Provider Module +description: Symitri DAP Real Time Data Provider Module +page_type: module +module_type: rtd +module_code : symitriDapRtdProvider +enable_download : true +vendor_specific: true +sidebarType : 1 +--- - Symitri DAP Real time data Provider automatically invokes the DAP APIs and submit audience segments and the SAID to the bid-stream. +# Symitri DAP Real Time Data Provider Module -### Integration +{:.no_toc} - 1) Build the symitriDapRTD module into the Prebid.js package with: +* TOC +{:toc} - ``` - gulp build --modules=symitriDapRtdProvider,... - ``` +The Symitri Data Activation Platform (DAP) is a privacy-first system that protects end-user privacy by only allowing them to be targeted as part of a larger cohort. Symitri DAP Real time data Provider automatically invokes the DAP APIs and submit audience segments and the Secure Ad ID(SAID) to the bid-stream. SAID is a JWT/JWE which carries with it the cohorts and only a side-car or trusted server in the demand-side platform is allowed to see its contents. - 2) Use `setConfig` to instruct Prebid.js to initilaize the symitriDapRtdProvider module, as specified below. +## Publisher Usage + +1. Build the symitriDapRTD module into the Prebid.js package with: + + ```bash + gulp build --modules=symitriDapRtdProvider,... + ``` + +2. Use `setConfig` to instruct Prebid.js to initilaize the symitriDapRtdProvider module, as specified below. ### Configuration +```javascript +pbjs.setConfig({ + realTimeData: { + auctionDelay: 2000, + dataProviders: [ + { + name: "symitriDap", + waitForIt: true, + params: { + apiHostname: '', + apiVersion: "x1", + apiAuthToken: '', + domain: 'your-domain.com', + identityType: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', + identityValue: '' + identitySelector: '#user-profile-email', + segtax: 501, + dapEntropyUrl: 'https://sym-dist.symitri.net/dapentropy.js', + dapEntropyTimeout: 1500 + } + } + ] + } +}); ``` - pbjs.setConfig({ - realTimeData: { - auctionDelay: 2000, - dataProviders: [ - { - name: "dap", - waitForIt: true, - params: { - apiHostname: '', - apiVersion: "x1", - domain: 'your-domain.com', - identityType: 'email' | 'mobile' | ... | 'dap-signature:1.3.0', - segtax: 504, - dapEntropyUrl: 'https://sym-dist.symitri.net/dapentropy.js', - dapEntropyTimeout: 1500 // Maximum time for dapentropy to run - } - } - ] - } - }); - ``` - -Please reach out to your Symitri account representative(Prebid@symitri.com) to get provisioned on the DAP platform. +Please reach out to your Symitri account representative() to get provisioned on the DAP platform. + +**Config Syntax details:** + +{: .table .table-bordered .table-striped } +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Symitri Dap Rtd module name | 'symitriDap' always| +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| apiHostname | String | Hostname provided by Symitri | Please reach out to your Symitri account representative() for this value| +| apiVersion | String | This holds the API version | It should be "x1" always | +| apiAuthToken | String | Symitri API AuthToken | Please reach out to your Symitri account representative() for this value | +| domain | String | The domain name of your webpage | | +| identityType | String | Something like this 'email', 'mobile', ... 'dap-signature:1.0.0' | | +| identityValue | String | This is optional field to pass user email/mobile. Will be used only if identityType is email or mobile | | +| identitySelector | String | This is optional field to pass javascript query selector to extract user email/mobile from DOM tree. Will be used only if identityType is email or mobile and identityValue is not set. | | +| segtax | Integer | The taxonomy for Symitri | The value should be 501 | +| dapEntropyUrl | String | URL to dap entropy script | Optional if the script is directly included on the webpage. Contact your Symitri account rep for more details | +| dapEntropyTimeout | Integer | Maximum time allotted for the entropy calculation to happen | | ### Testing + To view an example of available segments returned by dap: + +```bash +gulp serve --modules=rtdModule,symitriDapRtdProvider,appnexusBidAdapter,sovrnBidAdapter ``` -‘gulp serve --modules=rtdModule,symitriDapRtdProvider,appnexusBidAdapter,sovrnBidAdapter’ -``` + and then point your browser at: -"http://localhost:9999/integrationExamples/gpt/symitridap_segments_example.html" +"" From 9ce9c355abc50eeadb102170f561e2f54e1eb0b8 Mon Sep 17 00:00:00 2001 From: Jeff Palladino Date: Wed, 7 Aug 2024 13:53:19 -0600 Subject: [PATCH 02/10] WIP - adding unit tests --- modules/symitriDapRtdProvider.js | 86 +++++++++++-------- .../modules/symitriDapRtdProvider_spec.js | 22 ++++- 2 files changed, 69 insertions(+), 39 deletions(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 598693f612d..04ece905a32 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -129,6 +129,31 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { setTimeout(dapUtils.callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent); } + /** + * Listen for bidWon Events to record SAID receipt + * @param {string} authToken + */ + function onBidWonListener (authToken) { + window.pbjs.onEvent('bidWon', function(bid) { + const url = 'https://ProdSymPrebidEventhub1.servicebus.windows.net/prebid-said-1/messages'; + const bidjson = JSON.stringify(bid); + const cb = { + success: (response, request) => { + logInfo('DEBUG(###Winning Bid SAVED###)'); + }, + error: (request, error) => { + logInfo(error); + logInfo('DEBUG(###Save Winning Bid FAILED###)'); + } + }; + + ajax(url, cb, bidjson, { + method: 'POST', + customHeaders: {'Content-Type': 'application/atom+xml;type=entry;charset=utf-8', 'Authorization': authToken} + }); + }); + } + /** * Module init * @param {Object} config @@ -140,27 +165,12 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return false; } - if (window.pbjs?.onEvent && config?.params?.apiAuthToken) { - window.pbjs.onEvent('bidWon', function(bid) { - logInfo('DEBUG(###onBidWon###)'); - logInfo(bid); - let bidjson = JSON.stringify(bid); - let cb = { - success: (response, request) => { - logInfo('DEBUG(###Winning Bid SAVED###)'); - }, - error: (request, error) => { - logInfo(error); - logInfo('DEBUG(###Save Winning Bid FAILED###)'); - } - }; - let url = 'https://ProdSymPrebidEventhub1.servicebus.windows.net/prebid-said-1/messages'; - ajax(url, cb, bidjson, { - method: 'POST', - customHeaders: {'Content-Type': 'application/atom+xml;type=entry;charset=utf-8', 'Authorization': config.params.apiAuthToken} - }); - }); - } + const authToken = config?.param?.apiAuthToken + window.console.log('AuthToken: ', authToken) + window.console.log('window.pbjs.onEvent: ', window.pbjs?.onEvent) + // if (window.pbjs?.onEvent && authToken) { + onBidWonListener(authToken); + // } return true; } @@ -820,6 +830,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { addRealTimeData, getRealTimeData, generateRealTimeData, + onBidWonListener, rtdSubmodule, storage, dapUtils, @@ -833,18 +844,21 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { }; } -export const { - addRealTimeData, - getRealTimeData, - generateRealTimeData, - rtdSubmodule: symitriDapRtdSubmodule, - storage, - dapUtils, - DAP_TOKEN, - DAP_MEMBERSHIP, - DAP_ENCRYPTED_MEMBERSHIP, - DAP_SS_ID, - DAP_DEFAULT_TOKEN_TTL, - DAP_MAX_RETRY_TOKENIZE, - DAP_CLIENT_ENTROPY -} = createRtdProvider('symitriDap', 'symitridap', 'Symitri'); +// export const { +// addRealTimeData, +// getRealTimeData, +// generateRealTimeData, +// onBidWonListener, +// rtdSubmodule: symitriDapRtdSubmodule, +// storage, +// dapUtils, +// DAP_TOKEN, +// DAP_MEMBERSHIP, +// DAP_ENCRYPTED_MEMBERSHIP, +// DAP_SS_ID, +// DAP_DEFAULT_TOKEN_TTL, +// DAP_MAX_RETRY_TOKENIZE, +// DAP_CLIENT_ENTROPY +// } = createRtdProvider('symitriDap', 'symitridap', 'Symitri'); + +export const symitriDapRtd = createRtdProvider('symitriDap', 'symitridap', 'Symitri'); diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index 79ebfd707c7..00e70f599f7 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -1,5 +1,5 @@ import {config} from 'src/config.js'; -import { +import symitriDapRtd, { dapUtils, generateRealTimeData, symitriDapRtdSubmodule, @@ -37,11 +37,12 @@ describe('symitriDapRtdProvider', function() { }; const cmoduleConfig = { - 'name': 'dap', + 'name': 'symitriDap', 'waitForIt': true, 'params': { 'apiHostname': 'prebid.dap.akadns.net', 'apiVersion': 'x1', + 'apiAuthToken': 'Token 1234', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', 'segtax': 503 @@ -49,7 +50,7 @@ describe('symitriDapRtdProvider', function() { } const emoduleConfig = { - 'name': 'dap', + 'name': 'symitriDap', 'waitForIt': true, 'params': { 'apiHostname': 'prebid.dap.akadns.net', @@ -597,4 +598,19 @@ describe('symitriDapRtdProvider', function() { expect(symitriDapRtdSubmodule.init(null, {'usp': '1YNY'})).to.equal(true); }); }); + + describe('Test onEvent BidWon binding', function () { + it('Handle onBiWon when authToken is present', function () { + window.pbjs = { + onEvent: () => {} + } + sampleGdprConsentConfig.gdpr.consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; + symitriDapRtdSubmodule.init(cmoduleConfig, sampleUspConsentConfig); + // const mockListener = { + // 'method': onBidWonListener + // } + const spyListener = sinon.spy(symitriDapRtd, 'onBidWonListener'); + sinon.assert.calledOnce(spyListener); + }); + }); }); From dc24e903826a89513c7b53bbcf222b26630f7dd2 Mon Sep 17 00:00:00 2001 From: Jeff Palladino Date: Wed, 7 Aug 2024 17:31:23 -0600 Subject: [PATCH 03/10] Add unit test --- modules/symitriDapRtdProvider.js | 88 ++++++------ .../modules/symitriDapRtdProvider_spec.js | 136 ++++++++++++++++-- 2 files changed, 170 insertions(+), 54 deletions(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 04ece905a32..5d272489af3 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -165,21 +165,19 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return false; } - const authToken = config?.param?.apiAuthToken - window.console.log('AuthToken: ', authToken) - window.console.log('window.pbjs.onEvent: ', window.pbjs?.onEvent) - // if (window.pbjs?.onEvent && authToken) { - onBidWonListener(authToken); - // } + if (window.pbjs && window.pbjs.onEvent && config && config.params && config.params.apiAuthToken) { + const { apiAuthToken } = config.params; + this.onBidWonListener(apiAuthToken); + } return true; } - /** @type {RtdSubmodule} */ const rtdSubmodule = { name: SUBMODULE_NAME, getBidRequestData: getRealTimeData, - init: init + init: init, + onBidWonListener: onBidWonListener }; submodule(MODULE_NAME, rtdSubmodule); @@ -559,6 +557,28 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return [ config, false ]; }, + addIdentifier: function(identity, apiParams) { + if (typeof (identity.identity) != typeof (undefined)) { + apiParams.identity = identity.identity; + } else if (identity.type == 'email' && typeof (identity.selector) != typeof (undefined) && identity.selector.trim() !== '') { + let identityElement = document.querySelector(identity.selector); + let identityEmail = identityElement.innerHTML; + if (identityElement.tagName == 'INPUT') { + identityEmail = identityElement.value; + } + + if (identityEmail) { + const EMAIL_VALIDATION_REGEX = /((([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,}))/i; + const regex = new RegExp(EMAIL_VALIDATION_REGEX); + if (regex.test(identityEmail)) { + apiParams.identity = identityEmail; + } + } + } + + return apiParams + }, + /** * SYNOPSIS * @@ -627,23 +647,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { 'type': identity.type, }; - if (typeof (identity.identity) != typeof (undefined)) { - apiParams.identity = identity.identity; - } else if (identity.type == 'email' && typeof (identity.selector) != typeof (undefined) && identity.selector.trim() !== '') { - let identityElement = document.querySelector(identity.selector); - let identityEmail = identityElement.innerHTML; - if (identityElement.tagName == 'INPUT') { - identityEmail = identityElement.value; - } - - if (identityEmail) { - const EMAIL_VALIDATION_REGEX = /((([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,}))/i; - const regex = new RegExp(EMAIL_VALIDATION_REGEX); - if (regex.test(identityEmail)) { - apiParams.identity = identityEmail; - } - } - } + apiParams = this.addIdentifier(identity, apiParams); if (typeof (identity.attributes) != typeof (undefined)) { apiParams.attributes = identity.attributes; @@ -844,21 +848,19 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { }; } -// export const { -// addRealTimeData, -// getRealTimeData, -// generateRealTimeData, -// onBidWonListener, -// rtdSubmodule: symitriDapRtdSubmodule, -// storage, -// dapUtils, -// DAP_TOKEN, -// DAP_MEMBERSHIP, -// DAP_ENCRYPTED_MEMBERSHIP, -// DAP_SS_ID, -// DAP_DEFAULT_TOKEN_TTL, -// DAP_MAX_RETRY_TOKENIZE, -// DAP_CLIENT_ENTROPY -// } = createRtdProvider('symitriDap', 'symitridap', 'Symitri'); - -export const symitriDapRtd = createRtdProvider('symitriDap', 'symitridap', 'Symitri'); +export const { + addRealTimeData, + getRealTimeData, + generateRealTimeData, + onBidWonListener, + rtdSubmodule: symitriDapRtdSubmodule, + storage, + dapUtils, + DAP_TOKEN, + DAP_MEMBERSHIP, + DAP_ENCRYPTED_MEMBERSHIP, + DAP_SS_ID, + DAP_DEFAULT_TOKEN_TTL, + DAP_MAX_RETRY_TOKENIZE, + DAP_CLIENT_ENTROPY +} = createRtdProvider('symitriDap', 'symitridap', 'Symitri'); diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index 00e70f599f7..558d2c80a96 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -1,14 +1,18 @@ import {config} from 'src/config.js'; -import symitriDapRtd, { +import { dapUtils, generateRealTimeData, symitriDapRtdSubmodule, + onBidWonListener, storage, DAP_MAX_RETRY_TOKENIZE, DAP_SS_ID, DAP_TOKEN, DAP_MEMBERSHIP, DAP_ENCRYPTED_MEMBERSHIP } from 'modules/symitriDapRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; import {hook} from '../../../src/hook.js'; +import { EVENTS } from 'src/constants.js'; const responseHeader = {'Content-Type': 'application/json'}; +let events = require('src/events'); + describe('symitriDapRtdProvider', function() { const testReqBidsConfigObj = { adUnits: [ @@ -600,17 +604,127 @@ describe('symitriDapRtdProvider', function() { }); describe('Test onEvent BidWon binding', function () { - it('Handle onBiWon when authToken is present', function () { - window.pbjs = { - onEvent: () => {} + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(window.pbjs, 'onEvent').callsFake(() => {}); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('onBidWonListener called when authToken is present', function () { + let listernerStub = sandbox.stub(symitriDapRtdSubmodule, 'onBidWonListener'); + symitriDapRtdSubmodule.init(cmoduleConfig); + sinon.assert.callCount(listernerStub, 1); + }); + + it('onBidWonListener called authToken is NOT present', function () { + let listernerStub = sandbox.stub(symitriDapRtdSubmodule, 'onBidWonListener'); + symitriDapRtdSubmodule.init(); + sinon.assert.callCount(listernerStub, 0); + }); + }); + + describe('Test identifier is added properly to apiParams', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('passed identifier is handled', function () { + const test_identity = 'test@example.valid'; + let identity = { + identity: test_identity + }; + let apiParams = { + 'type': identity.type, + }; + apiParams = dapUtils.addIdentifier(identity, apiParams); + expect(apiParams.identity).is.equal(test_identity); + }); + + it('passed undefined identifier is handled', function () { + const test_identity = undefined; + let identity = { + identity: test_identity } - sampleGdprConsentConfig.gdpr.consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; - symitriDapRtdSubmodule.init(cmoduleConfig, sampleUspConsentConfig); - // const mockListener = { - // 'method': onBidWonListener - // } - const spyListener = sinon.spy(symitriDapRtd, 'onBidWonListener'); - sinon.assert.calledOnce(spyListener); + let apiParams = { + 'type': identity.type, + }; + apiParams = dapUtils.addIdentifier(identity, apiParams); + expect(apiParams.identity).is.undefined; + }); + + it('valid email is queried from selector', function () { + let id_selector = 'input#email'; + const test_identity = 'test@example.valid'; + + let documentStub = sandbox.stub(document, 'querySelector'); + documentStub.withArgs(id_selector).returns({ + innerHTML: test_identity + }); + + let identity = { + type: 'email', + selector: id_selector + } + + let apiParams = { + 'type': identity.type, + }; + + apiParams = dapUtils.addIdentifier(identity, apiParams); + expect(apiParams.identity).is.equal(test_identity); + }); + + it('invalid email is queried from selector', function () { + let id_selector = 'input#email'; + const test_identity = '@testexample.invalid@'; + + let documentStub = sandbox.stub(document, 'querySelector'); + documentStub.withArgs(id_selector).returns({ + innerHTML: test_identity + }); + + let identity = { + type: 'email', + selector: id_selector + } + + let apiParams = { + 'type': identity.type, + }; + + apiParams = dapUtils.addIdentifier(identity, apiParams); + expect(apiParams.identity).is.undefined; + }); + + it('email is not found from selector', function () { + let id_selector = 'input#email'; + + let documentStub = sandbox.stub(document, 'querySelector'); + documentStub.withArgs(id_selector).returns({ + innerHTML: '' + }); + + let identity = { + type: 'email', + selector: id_selector + } + + let apiParams = { + 'type': identity.type, + }; + + apiParams = dapUtils.addIdentifier(identity, apiParams); + expect(apiParams.identity).is.undefined; }); }); }); From 02eaabad4f5ea6917b0630d5261a6c60d46ec55d Mon Sep 17 00:00:00 2001 From: Manan Date: Wed, 14 Aug 2024 22:18:40 +0530 Subject: [PATCH 04/10] Added user identity hashing. Removed Publisher Page scrapping Removed onBidWonListener logic Changed documentation Updated related tests --- modules/symitriDapRtdProvider.js | 149 +++++++++++------- modules/symitriDapRtdProvider.md | 10 +- .../modules/symitriDapRtdProvider_spec.js | 95 +---------- 3 files changed, 103 insertions(+), 151 deletions(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 5d272489af3..1040275df6e 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -129,31 +129,6 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { setTimeout(dapUtils.callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent); } - /** - * Listen for bidWon Events to record SAID receipt - * @param {string} authToken - */ - function onBidWonListener (authToken) { - window.pbjs.onEvent('bidWon', function(bid) { - const url = 'https://ProdSymPrebidEventhub1.servicebus.windows.net/prebid-said-1/messages'; - const bidjson = JSON.stringify(bid); - const cb = { - success: (response, request) => { - logInfo('DEBUG(###Winning Bid SAVED###)'); - }, - error: (request, error) => { - logInfo(error); - logInfo('DEBUG(###Save Winning Bid FAILED###)'); - } - }; - - ajax(url, cb, bidjson, { - method: 'POST', - customHeaders: {'Content-Type': 'application/atom+xml;type=entry;charset=utf-8', 'Authorization': authToken} - }); - }); - } - /** * Module init * @param {Object} config @@ -164,20 +139,13 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { if (dapUtils.checkConsent(userConsent) === false) { return false; } - - if (window.pbjs && window.pbjs.onEvent && config && config.params && config.params.apiAuthToken) { - const { apiAuthToken } = config.params; - this.onBidWonListener(apiAuthToken); - } - return true; } const rtdSubmodule = { name: SUBMODULE_NAME, getBidRequestData: getRealTimeData, - init: init, - onBidWonListener: onBidWonListener + init: init }; submodule(MODULE_NAME, rtdSubmodule); @@ -190,7 +158,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { api_version: rtdConfig.params.apiVersion, domain: rtdConfig.params.domain, segtax: rtdConfig.params.segtax, - identity: {type: rtdConfig.params.identityType, identity: rtdConfig.params.identityValue, selector: rtdConfig.params.identitySelector}, + identity: {type: rtdConfig.params.identityType, value: rtdConfig.params.identityValue}, }; let refreshMembership = true; let token = dapUtils.dapGetTokenFromLocalStorage(); @@ -558,25 +526,102 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { }, addIdentifier: function(identity, apiParams) { - if (typeof (identity.identity) != typeof (undefined)) { - apiParams.identity = identity.identity; - } else if (identity.type == 'email' && typeof (identity.selector) != typeof (undefined) && identity.selector.trim() !== '') { - let identityElement = document.querySelector(identity.selector); - let identityEmail = identityElement.innerHTML; - if (identityElement.tagName == 'INPUT') { - identityEmail = identityElement.value; - } - - if (identityEmail) { - const EMAIL_VALIDATION_REGEX = /((([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,}))/i; - const regex = new RegExp(EMAIL_VALIDATION_REGEX); - if (regex.test(identityEmail)) { - apiParams.identity = identityEmail; - } - } + if (typeof (identity.value) != typeof (undefined) && identity.value.trim() !== '') { + let hid = this.generateHash(identity.value); + apiParams.identity = hid; } + return apiParams; + }, - return apiParams + generateHash: function(message) { + const data = new TextEncoder().encode(message); + let h0 = 0x6a09e667; let h1 = 0xbb67ae85; let h2 = 0x3c6ef372; let h3 = 0xa54ff53a; + let h4 = 0x510e527f; let h5 = 0x9b05688c; let h6 = 0x1f83d9ab; let h7 = 0x5be0cd19; + let tsz = 0; let bp = 0; + const k = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]; + const rrot = (x, n) => (x >>> n) | (x << (32 - n)); + const w = new Uint32Array(64); + const buf = new Uint8Array(64); + const process = () => { + for (let j = 0, r = 0; j < 16; j++, r += 4) { + w[j] = (buf[r] << 24) | (buf[r + 1] << 16) | (buf[r + 2] << 8) | buf[r + 3]; + } + for (let j = 16; j < 64; j++) { + let s0 = rrot(w[j - 15], 7) ^ rrot(w[j - 15], 18) ^ (w[j - 15] >>> 3); + let s1 = rrot(w[j - 2], 17) ^ rrot(w[j - 2], 19) ^ (w[j - 2] >>> 10); + w[j] = (w[j - 16] + s0 + w[j - 7] + s1) | 0; + } + let a = h0; let b = h1; let c = h2; let d = h3; let e = h4; let f = h5; let g = h6; let h = h7; + for (let j = 0; j < 64; j++) { + let S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25); + let ch = (e & f) ^ ((~e) & g); + let t1 = (h + S1 + ch + k[j] + w[j]) | 0; + let S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22); + let maj = (a & b) ^ (a & c) ^ (b & c); + let t2 = (S0 + maj) | 0; + h = g; g = f; f = e; e = (d + t1) | 0; d = c; c = b; b = a; a = (t1 + t2) | 0; + } + h0 = (h0 + a) | 0; h1 = (h1 + b) | 0; h2 = (h2 + c) | 0; h3 = (h3 + d) | 0; + h4 = (h4 + e) | 0; h5 = (h5 + f) | 0; h6 = (h6 + g) | 0; h7 = (h7 + h) | 0; + bp = 0; + }; + const add = data => { + if (typeof data === 'string') { + data = typeof TextEncoder === 'undefined' ? Buffer.from(data) : (new TextEncoder()).encode(data); + } + for (let i = 0; i < data.length; i++) { + buf[bp++] = data[i]; + if (bp === 64) process(); + } + tsz += data.length; + }; + const digest = () => { + buf[bp++] = 0x80; if (bp == 64) process(); + if (bp + 8 > 64) { + while (bp < 64) buf[bp++] = 0x00; + process(); + } + while (bp < 58) buf[bp++] = 0x00; + // Max number of bytes is 35,184,372,088,831 + let L = tsz * 8; + buf[bp++] = (L / 1099511627776.0) & 255; + buf[bp++] = (L / 4294967296.0) & 255; + buf[bp++] = L >>> 24; + buf[bp++] = (L >>> 16) & 255; + buf[bp++] = (L >>> 8) & 255; + buf[bp++] = L & 255; + process(); + let reply = new Uint8Array(32); + reply[0] = h0 >>> 24; reply[1] = (h0 >>> 16) & 255; reply[2] = (h0 >>> 8) & 255; reply[3] = h0 & 255; + reply[4] = h1 >>> 24; reply[5] = (h1 >>> 16) & 255; reply[6] = (h1 >>> 8) & 255; reply[7] = h1 & 255; + reply[8] = h2 >>> 24; reply[9] = (h2 >>> 16) & 255; reply[10] = (h2 >>> 8) & 255; reply[11] = h2 & 255; + reply[12] = h3 >>> 24; reply[13] = (h3 >>> 16) & 255; reply[14] = (h3 >>> 8) & 255; reply[15] = h3 & 255; + reply[16] = h4 >>> 24; reply[17] = (h4 >>> 16) & 255; reply[18] = (h4 >>> 8) & 255; reply[19] = h4 & 255; + reply[20] = h5 >>> 24; reply[21] = (h5 >>> 16) & 255; reply[22] = (h5 >>> 8) & 255; reply[23] = h5 & 255; + reply[24] = h6 >>> 24; reply[25] = (h6 >>> 16) & 255; reply[26] = (h6 >>> 8) & 255; reply[27] = h6 & 255; + reply[28] = h7 >>> 24; reply[29] = (h7 >>> 16) & 255; reply[30] = (h7 >>> 8) & 255; reply[31] = h7 & 255; + reply.hex = () => { + let res = ''; + reply.forEach(x => res += ('0' + x.toString(16)).slice(-2)); + return res; + }; + return reply; + }; + if (data === undefined) return {add, digest}; + add(data); + const hashBuffer = digest(); + // convert ArrayBuffer to Array + const hashArray = Array.from(new Uint8Array(hashBuffer)); + // convert bytes to hex string + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + return hashHex; }, /** @@ -834,7 +879,6 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { addRealTimeData, getRealTimeData, generateRealTimeData, - onBidWonListener, rtdSubmodule, storage, dapUtils, @@ -852,7 +896,6 @@ export const { addRealTimeData, getRealTimeData, generateRealTimeData, - onBidWonListener, rtdSubmodule: symitriDapRtdSubmodule, storage, dapUtils, diff --git a/modules/symitriDapRtdProvider.md b/modules/symitriDapRtdProvider.md index da3d82f774c..f44c83f9dfb 100644 --- a/modules/symitriDapRtdProvider.md +++ b/modules/symitriDapRtdProvider.md @@ -45,9 +45,8 @@ pbjs.setConfig({ apiVersion: "x1", apiAuthToken: '', domain: 'your-domain.com', - identityType: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', - identityValue: '' - identitySelector: '#user-profile-email', + identityType: 'hid'| ... | 'dap-signature:1.0.0', + identityValue: '', segtax: 501, dapEntropyUrl: 'https://sym-dist.symitri.net/dapentropy.js', dapEntropyTimeout: 1500 @@ -71,9 +70,8 @@ Please reach out to your Symitri account representative() to | apiVersion | String | This holds the API version | It should be "x1" always | | apiAuthToken | String | Symitri API AuthToken | Please reach out to your Symitri account representative() for this value | | domain | String | The domain name of your webpage | | -| identityType | String | Something like this 'email', 'mobile', ... 'dap-signature:1.0.0' | | -| identityValue | String | This is optional field to pass user email/mobile. Will be used only if identityType is email or mobile | | -| identitySelector | String | This is optional field to pass javascript query selector to extract user email/mobile from DOM tree. Will be used only if identityType is email or mobile and identityValue is not set. | | +| identityType | String | Something like this 'hid', ... 'dap-signature:1.0.0' | | +| identityValue | String | This is optional field to pass user hid. Will be used only if identityType is hid | | | segtax | Integer | The taxonomy for Symitri | The value should be 501 | | dapEntropyUrl | String | URL to dap entropy script | Optional if the script is directly included on the webpage. Contact your Symitri account rep for more details | | dapEntropyTimeout | Integer | Maximum time allotted for the entropy calculation to happen | | diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index 558d2c80a96..c038cba671a 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -603,30 +603,6 @@ describe('symitriDapRtdProvider', function() { }); }); - describe('Test onEvent BidWon binding', function () { - let sandbox; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(window.pbjs, 'onEvent').callsFake(() => {}); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('onBidWonListener called when authToken is present', function () { - let listernerStub = sandbox.stub(symitriDapRtdSubmodule, 'onBidWonListener'); - symitriDapRtdSubmodule.init(cmoduleConfig); - sinon.assert.callCount(listernerStub, 1); - }); - - it('onBidWonListener called authToken is NOT present', function () { - let listernerStub = sandbox.stub(symitriDapRtdSubmodule, 'onBidWonListener'); - symitriDapRtdSubmodule.init(); - sinon.assert.callCount(listernerStub, 0); - }); - }); - describe('Test identifier is added properly to apiParams', function() { let sandbox; @@ -639,15 +615,15 @@ describe('symitriDapRtdProvider', function() { }); it('passed identifier is handled', function () { - const test_identity = 'test@example.valid'; + const test_identity = 'test_identity_1234'; let identity = { - identity: test_identity + value: test_identity }; let apiParams = { 'type': identity.type, }; apiParams = dapUtils.addIdentifier(identity, apiParams); - expect(apiParams.identity).is.equal(test_identity); + expect(apiParams.identity).is.equal(dapUtils.generateHash(test_identity)); }); it('passed undefined identifier is handled', function () { @@ -661,70 +637,5 @@ describe('symitriDapRtdProvider', function() { apiParams = dapUtils.addIdentifier(identity, apiParams); expect(apiParams.identity).is.undefined; }); - - it('valid email is queried from selector', function () { - let id_selector = 'input#email'; - const test_identity = 'test@example.valid'; - - let documentStub = sandbox.stub(document, 'querySelector'); - documentStub.withArgs(id_selector).returns({ - innerHTML: test_identity - }); - - let identity = { - type: 'email', - selector: id_selector - } - - let apiParams = { - 'type': identity.type, - }; - - apiParams = dapUtils.addIdentifier(identity, apiParams); - expect(apiParams.identity).is.equal(test_identity); - }); - - it('invalid email is queried from selector', function () { - let id_selector = 'input#email'; - const test_identity = '@testexample.invalid@'; - - let documentStub = sandbox.stub(document, 'querySelector'); - documentStub.withArgs(id_selector).returns({ - innerHTML: test_identity - }); - - let identity = { - type: 'email', - selector: id_selector - } - - let apiParams = { - 'type': identity.type, - }; - - apiParams = dapUtils.addIdentifier(identity, apiParams); - expect(apiParams.identity).is.undefined; - }); - - it('email is not found from selector', function () { - let id_selector = 'input#email'; - - let documentStub = sandbox.stub(document, 'querySelector'); - documentStub.withArgs(id_selector).returns({ - innerHTML: '' - }); - - let identity = { - type: 'email', - selector: id_selector - } - - let apiParams = { - 'type': identity.type, - }; - - apiParams = dapUtils.addIdentifier(identity, apiParams); - expect(apiParams.identity).is.undefined; - }); }); }); From f4b3ee7d71f196de1fce96f082d062f30f9be82b Mon Sep 17 00:00:00 2001 From: Manan Date: Thu, 15 Aug 2024 01:04:54 +0530 Subject: [PATCH 05/10] Added comments --- modules/symitriDapRtdProvider.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 1040275df6e..e6210f883fa 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -527,12 +527,15 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { addIdentifier: function(identity, apiParams) { if (typeof (identity.value) != typeof (undefined) && identity.value.trim() !== '') { - let hid = this.generateHash(identity.value); + let hid = this.generateHash(identity.value); //Hash the identifier passed by Publisher through config apiParams.identity = hid; } return apiParams; }, + //Using custom SHA256 algorithm to synchronously generate hash for the identifier as we can't use asynchronous crypto.subtle.digest. + //Copyright 2022 Andrea Griffini + //https://github.com/6502/sha256/ generateHash: function(message) { const data = new TextEncoder().encode(message); let h0 = 0x6a09e667; let h1 = 0xbb67ae85; let h2 = 0x3c6ef372; let h3 = 0xa54ff53a; From 302aafa7676876d6f43d022e8ae1f21f37df89ed Mon Sep 17 00:00:00 2001 From: Manan Date: Thu, 15 Aug 2024 01:35:50 +0530 Subject: [PATCH 06/10] Updated comment --- modules/symitriDapRtdProvider.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index e6210f883fa..db7144e63f1 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -527,15 +527,32 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { addIdentifier: function(identity, apiParams) { if (typeof (identity.value) != typeof (undefined) && identity.value.trim() !== '') { - let hid = this.generateHash(identity.value); //Hash the identifier passed by Publisher through config + let hid = this.generateHash(identity.value); // Hash the identifier passed by Publisher through config apiParams.identity = hid; } return apiParams; }, - //Using custom SHA256 algorithm to synchronously generate hash for the identifier as we can't use asynchronous crypto.subtle.digest. - //Copyright 2022 Andrea Griffini - //https://github.com/6502/sha256/ + /** + * SYNOPSIS + * + * generateHash(message); + * + * DESCRIPTION + * + * SHA256 algorithm to synchronously generate hash for the passed in value. + * Asynchronous implementation provided by crypto.subtle.digest can only be triggered from async function. + * This synchronous alternative is useful while generating hash from synchronous code. + * + * Copyright 2022 Andrea Griffini + * https://github.com/6502/sha256/ + * + * PARAMETERS + * + * message: any string that needs tobe hashed. In this case it will be user identifier + * + */ + generateHash: function(message) { const data = new TextEncoder().encode(message); let h0 = 0x6a09e667; let h1 = 0xbb67ae85; let h2 = 0x3c6ef372; let h3 = 0xa54ff53a; From 8dbb732e237369557d65306ce2adc1751eb55fab Mon Sep 17 00:00:00 2001 From: Jeff Palladino Date: Mon, 26 Aug 2024 09:07:45 -0600 Subject: [PATCH 07/10] Dont rely on entropy script to execute code, use async sha256 hash function --- modules/symitriDapRtdProvider.js | 170 +++++++------------------------ 1 file changed, 36 insertions(+), 134 deletions(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index db7144e63f1..387f3a4802d 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -70,32 +70,42 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { */ function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); - let loadScriptPromise = new Promise((resolve, reject) => { - if (rtdConfig && rtdConfig.params && rtdConfig.params.dapEntropyTimeout && Number.isInteger(rtdConfig.params.dapEntropyTimeout)) { - setTimeout(reject, rtdConfig.params.dapEntropyTimeout, Error('DapEntropy script could not be loaded')); - } - if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) { - logMessage('Using cached entropy'); - resolve(); - } else { - if (typeof window.dapCalculateEntropy === 'function') { - window.dapCalculateEntropy(resolve, reject); + + // Attempt to load entroy script if no entropy object exist and entropy config settings are present. + // Else + if (!entropyDict && rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { + + let loadScriptPromise = new Promise((resolve, reject) => { + if (rtdConfig && rtdConfig.params && rtdConfig.params.dapEntropyTimeout && Number.isInteger(rtdConfig.params.dapEntropyTimeout)) { + setTimeout(reject, rtdConfig.params.dapEntropyTimeout, Error('DapEntropy script could not be loaded')); + } + if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) { + logMessage('Using cached entropy'); + resolve(); } else { - if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { - loadExternalScript(rtdConfig.params.dapEntropyUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) }); + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); } else { - reject(Error('Please check if dapEntropyUrl is specified and is valid under config.params')); + if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { + loadExternalScript(rtdConfig.params.dapEntropyUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) }); + } else { + reject(Error('Please check if dapEntropyUrl is specified and is valid under config.params')); + } } } - } - }); - loadScriptPromise - .catch((error) => { - logError('Entropy could not be calculated due to: ', error.message); - }) - .finally(() => { - generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); }); + + loadScriptPromise + .catch((error) => { + logError('Entropy could not be calculated due to: ', error.message); + }) + .finally(() => { + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); + }); + } else { + logMessage('No dapEntropyUrl is specified.'); + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); + } } function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { @@ -525,125 +535,17 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return [ config, false ]; }, - addIdentifier: function(identity, apiParams) { + addIdentifier: async function(identity, apiParams) { if (typeof (identity.value) != typeof (undefined) && identity.value.trim() !== '') { - let hid = this.generateHash(identity.value); // Hash the identifier passed by Publisher through config + const hid = await window.crypto.subtle.digest( + 'SHA-256', + new TextEncoder().encode(identity.value) + ); apiParams.identity = hid; } return apiParams; }, - /** - * SYNOPSIS - * - * generateHash(message); - * - * DESCRIPTION - * - * SHA256 algorithm to synchronously generate hash for the passed in value. - * Asynchronous implementation provided by crypto.subtle.digest can only be triggered from async function. - * This synchronous alternative is useful while generating hash from synchronous code. - * - * Copyright 2022 Andrea Griffini - * https://github.com/6502/sha256/ - * - * PARAMETERS - * - * message: any string that needs tobe hashed. In this case it will be user identifier - * - */ - - generateHash: function(message) { - const data = new TextEncoder().encode(message); - let h0 = 0x6a09e667; let h1 = 0xbb67ae85; let h2 = 0x3c6ef372; let h3 = 0xa54ff53a; - let h4 = 0x510e527f; let h5 = 0x9b05688c; let h6 = 0x1f83d9ab; let h7 = 0x5be0cd19; - let tsz = 0; let bp = 0; - const k = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]; - const rrot = (x, n) => (x >>> n) | (x << (32 - n)); - const w = new Uint32Array(64); - const buf = new Uint8Array(64); - const process = () => { - for (let j = 0, r = 0; j < 16; j++, r += 4) { - w[j] = (buf[r] << 24) | (buf[r + 1] << 16) | (buf[r + 2] << 8) | buf[r + 3]; - } - for (let j = 16; j < 64; j++) { - let s0 = rrot(w[j - 15], 7) ^ rrot(w[j - 15], 18) ^ (w[j - 15] >>> 3); - let s1 = rrot(w[j - 2], 17) ^ rrot(w[j - 2], 19) ^ (w[j - 2] >>> 10); - w[j] = (w[j - 16] + s0 + w[j - 7] + s1) | 0; - } - let a = h0; let b = h1; let c = h2; let d = h3; let e = h4; let f = h5; let g = h6; let h = h7; - for (let j = 0; j < 64; j++) { - let S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25); - let ch = (e & f) ^ ((~e) & g); - let t1 = (h + S1 + ch + k[j] + w[j]) | 0; - let S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22); - let maj = (a & b) ^ (a & c) ^ (b & c); - let t2 = (S0 + maj) | 0; - h = g; g = f; f = e; e = (d + t1) | 0; d = c; c = b; b = a; a = (t1 + t2) | 0; - } - h0 = (h0 + a) | 0; h1 = (h1 + b) | 0; h2 = (h2 + c) | 0; h3 = (h3 + d) | 0; - h4 = (h4 + e) | 0; h5 = (h5 + f) | 0; h6 = (h6 + g) | 0; h7 = (h7 + h) | 0; - bp = 0; - }; - const add = data => { - if (typeof data === 'string') { - data = typeof TextEncoder === 'undefined' ? Buffer.from(data) : (new TextEncoder()).encode(data); - } - for (let i = 0; i < data.length; i++) { - buf[bp++] = data[i]; - if (bp === 64) process(); - } - tsz += data.length; - }; - const digest = () => { - buf[bp++] = 0x80; if (bp == 64) process(); - if (bp + 8 > 64) { - while (bp < 64) buf[bp++] = 0x00; - process(); - } - while (bp < 58) buf[bp++] = 0x00; - // Max number of bytes is 35,184,372,088,831 - let L = tsz * 8; - buf[bp++] = (L / 1099511627776.0) & 255; - buf[bp++] = (L / 4294967296.0) & 255; - buf[bp++] = L >>> 24; - buf[bp++] = (L >>> 16) & 255; - buf[bp++] = (L >>> 8) & 255; - buf[bp++] = L & 255; - process(); - let reply = new Uint8Array(32); - reply[0] = h0 >>> 24; reply[1] = (h0 >>> 16) & 255; reply[2] = (h0 >>> 8) & 255; reply[3] = h0 & 255; - reply[4] = h1 >>> 24; reply[5] = (h1 >>> 16) & 255; reply[6] = (h1 >>> 8) & 255; reply[7] = h1 & 255; - reply[8] = h2 >>> 24; reply[9] = (h2 >>> 16) & 255; reply[10] = (h2 >>> 8) & 255; reply[11] = h2 & 255; - reply[12] = h3 >>> 24; reply[13] = (h3 >>> 16) & 255; reply[14] = (h3 >>> 8) & 255; reply[15] = h3 & 255; - reply[16] = h4 >>> 24; reply[17] = (h4 >>> 16) & 255; reply[18] = (h4 >>> 8) & 255; reply[19] = h4 & 255; - reply[20] = h5 >>> 24; reply[21] = (h5 >>> 16) & 255; reply[22] = (h5 >>> 8) & 255; reply[23] = h5 & 255; - reply[24] = h6 >>> 24; reply[25] = (h6 >>> 16) & 255; reply[26] = (h6 >>> 8) & 255; reply[27] = h6 & 255; - reply[28] = h7 >>> 24; reply[29] = (h7 >>> 16) & 255; reply[30] = (h7 >>> 8) & 255; reply[31] = h7 & 255; - reply.hex = () => { - let res = ''; - reply.forEach(x => res += ('0' + x.toString(16)).slice(-2)); - return res; - }; - return reply; - }; - if (data === undefined) return {add, digest}; - add(data); - const hashBuffer = digest(); - // convert ArrayBuffer to Array - const hashArray = Array.from(new Uint8Array(hashBuffer)); - // convert bytes to hex string - const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); - return hashHex; - }, - /** * SYNOPSIS * From 3674b47610bc810bd2aa05ca2040c17e72ceabd3 Mon Sep 17 00:00:00 2001 From: Jeff Palladino Date: Mon, 26 Aug 2024 12:29:31 -0600 Subject: [PATCH 08/10] updating tests --- modules/symitriDapRtdProvider.js | 12 +++++------- .../spec/modules/symitriDapRtdProvider_spec.js | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 387f3a4802d..884ab85729e 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -74,7 +74,6 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { // Attempt to load entroy script if no entropy object exist and entropy config settings are present. // Else if (!entropyDict && rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { - let loadScriptPromise = new Promise((resolve, reject) => { if (rtdConfig && rtdConfig.params && rtdConfig.params.dapEntropyTimeout && Number.isInteger(rtdConfig.params.dapEntropyTimeout)) { setTimeout(reject, rtdConfig.params.dapEntropyTimeout, Error('DapEntropy script could not be loaded')); @@ -537,13 +536,12 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { addIdentifier: async function(identity, apiParams) { if (typeof (identity.value) != typeof (undefined) && identity.value.trim() !== '') { - const hid = await window.crypto.subtle.digest( - 'SHA-256', - new TextEncoder().encode(identity.value) - ); - apiParams.identity = hid; + const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(identity.value)); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + apiParams.identity = hashHex; } - return apiParams; + return apiParams }, /** diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index c038cba671a..8abb394bf9d 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -614,7 +614,7 @@ describe('symitriDapRtdProvider', function() { sandbox.restore(); }); - it('passed identifier is handled', function () { + it('passed identifier is handled', async function () { const test_identity = 'test_identity_1234'; let identity = { value: test_identity @@ -622,11 +622,16 @@ describe('symitriDapRtdProvider', function() { let apiParams = { 'type': identity.type, }; - apiParams = dapUtils.addIdentifier(identity, apiParams); - expect(apiParams.identity).is.equal(dapUtils.generateHash(test_identity)); + + const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(identity.value)); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + + let hid = await dapUtils.addIdentifier(identity, apiParams); + expect(hid['identity']).is.equal(hashHex); }); - it('passed undefined identifier is handled', function () { + it('passed undefined identifier is handled', async function () { const test_identity = undefined; let identity = { identity: test_identity @@ -634,8 +639,9 @@ describe('symitriDapRtdProvider', function() { let apiParams = { 'type': identity.type, }; - apiParams = dapUtils.addIdentifier(identity, apiParams); - expect(apiParams.identity).is.undefined; + + let hid = await dapUtils.addIdentifier(identity, apiParams); + expect(hid.identity).is.undefined; }); }); }); From 436d4330710d14324f94ea1d3fbc9196b4cc646c Mon Sep 17 00:00:00 2001 From: Jeff Palladino Date: Mon, 26 Aug 2024 13:31:39 -0600 Subject: [PATCH 09/10] be more definsive around window.crypto use and test more reliable --- modules/symitriDapRtdProvider.js | 6 +++--- test/spec/modules/symitriDapRtdProvider_spec.js | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 884ab85729e..e22fadd10a2 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -535,11 +535,11 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { }, addIdentifier: async function(identity, apiParams) { - if (typeof (identity.value) != typeof (undefined) && identity.value.trim() !== '') { - const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(identity.value)); + if (window.crypto && window.crypto.subtle && typeof (identity.value) != typeof (undefined) && identity.value.trim() !== '') { + const hashBuffer = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(identity.value)); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); - apiParams.identity = hashHex; + apiParams.identity = hashHex.toUpperCase(); } return apiParams }, diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index 8abb394bf9d..3056ad7d52f 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -623,12 +623,8 @@ describe('symitriDapRtdProvider', function() { 'type': identity.type, }; - const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(identity.value)); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); - let hid = await dapUtils.addIdentifier(identity, apiParams); - expect(hid['identity']).is.equal(hashHex); + expect(hid['identity']).is.equal('843BE0FB20AAE699F27E5BC88C554B716F3DD366F58C1BDE0ACFB7EA0DD90CE7'); }); it('passed undefined identifier is handled', async function () { From 23d7b71d6b9543fbe09ba97939bbb6915f417f38 Mon Sep 17 00:00:00 2001 From: Jeff Palladino Date: Mon, 26 Aug 2024 14:54:28 -0600 Subject: [PATCH 10/10] check for crypto.subtle in test --- .../gpt/symitridap_segments_example.html | 132 ++++++++++++++++++ .../creative-renderer-display/renderer.js | 2 +- .../creative-renderer-native/renderer.js | 2 +- modules/symitriDapRtdProvider.js | 12 +- .../modules/symitriDapRtdProvider_spec.js | 8 +- 5 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 integrationExamples/gpt/symitridap_segments_example.html diff --git a/integrationExamples/gpt/symitridap_segments_example.html b/integrationExamples/gpt/symitridap_segments_example.html new file mode 100644 index 00000000000..8ec7958dd0d --- /dev/null +++ b/integrationExamples/gpt/symitridap_segments_example.html @@ -0,0 +1,132 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+
Segments Sent to Bidding Adapter
+
+ + diff --git a/libraries/creative-renderer-display/renderer.js b/libraries/creative-renderer-display/renderer.js index 72f3658fe79..146afab46ae 100644 --- a/libraries/creative-renderer-display/renderer.js +++ b/libraries/creative-renderer-display/renderer.js @@ -1,2 +1,2 @@ // this file is autogenerated, see creative/README.md -export const RENDERER = "!function(){\"use strict\";window.render=function({ad:d,adUrl:i,width:n,height:e},{mkFrame:o},r){if(!d&&!i)throw{reason:\"noAd\",message:\"Missing ad markup or URL\"};{const t=r.document,s={width:n,height:e};i&&!d?s.src=i:s.srcdoc=d,t.body.appendChild(o(t,s))}}}();" \ No newline at end of file +export const RENDERER = "(()=>{\"use strict\";window.render=function({ad:d,adUrl:e,width:i,height:r},{mkFrame:n},o){if(!d&&!e)throw{reason:\"noAd\",message:\"Missing ad markup or URL\"};{const s=o.document,t={width:i,height:r};e&&!d?t.src=e:t.srcdoc=d,s.body.appendChild(n(s,t))}}})();" \ No newline at end of file diff --git a/libraries/creative-renderer-native/renderer.js b/libraries/creative-renderer-native/renderer.js index 57d86fc8ce3..d7d85cdd7ba 100644 --- a/libraries/creative-renderer-native/renderer.js +++ b/libraries/creative-renderer-native/renderer.js @@ -1,2 +1,2 @@ // this file is autogenerated, see creative/README.md -export const RENDERER = "!function(){\"use strict\";const e=\"Prebid Native\",t={title:\"text\",data:\"value\",img:\"url\",video:\"vasttag\"};function n(e,t){return new Promise(((n,r)=>{const i=t.createElement(\"script\");i.onload=n,i.onerror=r,i.src=e,t.body.appendChild(i)}))}function r(e,t,r,i,o=n){const{rendererUrl:s,assets:a,ortb:d,adTemplate:c}=t,l=i.document;return s?o(s,l).then((()=>{if(\"function\"!=typeof i.renderAd)throw new Error(`Renderer from '${s}' does not define renderAd()`);const e=a||[];return e.ortb=d,i.renderAd(e)})):Promise.resolve(r(c??l.body.innerHTML))}window.render=function({adId:n,native:i},{sendMessage:o},s,a=r){const{head:d,body:c}=s.document,l=()=>o(e,{action:\"resizeNativeHeight\",height:c.offsetHeight,width:c.offsetWidth}),u=function(e,{assets:n=[],ortb:r,nativeKeys:i={}}){const o=Object.fromEntries(n.map((({key:e,value:t})=>[e,t])));let s=Object.fromEntries(Object.entries(i).flatMap((([t,n])=>{const r=o.hasOwnProperty(t)?o[t]:void 0;return[[`##${n}##`,r],[`${n}:${e}`,r]]})));return r&&Object.assign(s,{\"##hb_native_linkurl##\":r.link?.url,\"##hb_native_privacy##\":r.privacy},Object.fromEntries((r.assets||[]).flatMap((e=>{const n=Object.keys(t).find((t=>e[t]));return[n&&[`##hb_native_asset_id_${e.id}##`,e[n][t[n]]],e.link?.url&&[`##hb_native_asset_link_id_${e.id}##`,e.link.url]].filter((e=>e))})))),s=Object.entries(s).concat([[/##hb_native_asset_(link_)?id_\\d+##/g]]),function(e){return s.reduce(((e,[t,n])=>e.replaceAll(t,n||\"\")),e)}}(n,i);return d&&(d.innerHTML=u(d.innerHTML)),a(n,i,u,s).then((t=>{c.innerHTML=t,\"function\"==typeof s.postRenderAd&&s.postRenderAd({adId:n,...i}),s.document.querySelectorAll(\".pb-click\").forEach((t=>{const n=t.getAttribute(\"hb_native_asset_id\");t.addEventListener(\"click\",(()=>o(e,{action:\"click\",assetId:n})))})),o(e,{action:\"fireNativeImpressionTrackers\"}),\"complete\"===s.document.readyState?l():s.onload=l}))}}();" \ No newline at end of file +export const RENDERER = "(()=>{\"use strict\";const e=\"Prebid Native\",t={title:\"text\",data:\"value\",img:\"url\",video:\"vasttag\"};function n(e,t){return new Promise(((n,r)=>{const i=t.createElement(\"script\");i.onload=n,i.onerror=r,i.src=e,t.body.appendChild(i)}))}function r(e,t,r,i,o=n){const{rendererUrl:s,assets:a,ortb:d,adTemplate:c}=t,l=i.document;return s?o(s,l).then((()=>{if(\"function\"!=typeof i.renderAd)throw new Error(`Renderer from '${s}' does not define renderAd()`);const e=a||[];return e.ortb=d,i.renderAd(e)})):Promise.resolve(r(c??l.body.innerHTML))}window.render=function({adId:n,native:i},{sendMessage:o},s,a=r){const{head:d,body:c}=s.document,l=()=>o(e,{action:\"resizeNativeHeight\",height:c.offsetHeight,width:c.offsetWidth}),u=function(e,{assets:n=[],ortb:r,nativeKeys:i={}}){const o=Object.fromEntries(n.map((({key:e,value:t})=>[e,t])));let s=Object.fromEntries(Object.entries(i).flatMap((([t,n])=>{const r=o.hasOwnProperty(t)?o[t]:void 0;return[[`##${n}##`,r],[`${n}:${e}`,r]]})));return r&&Object.assign(s,{\"##hb_native_linkurl##\":r.link?.url,\"##hb_native_privacy##\":r.privacy},Object.fromEntries((r.assets||[]).flatMap((e=>{const n=Object.keys(t).find((t=>e[t]));return[n&&[`##hb_native_asset_id_${e.id}##`,e[n][t[n]]],e.link?.url&&[`##hb_native_asset_link_id_${e.id}##`,e.link.url]].filter((e=>e))})))),s=Object.entries(s).concat([[/##hb_native_asset_(link_)?id_\\d+##/g]]),function(e){return s.reduce(((e,[t,n])=>e.replaceAll(t,n||\"\")),e)}}(n,i);return d&&(d.innerHTML=u(d.innerHTML)),a(n,i,u,s).then((t=>{c.innerHTML=t,\"function\"==typeof s.postRenderAd&&s.postRenderAd({adId:n,...i}),s.document.querySelectorAll(\".pb-click\").forEach((t=>{const n=t.getAttribute(\"hb_native_asset_id\");t.addEventListener(\"click\",(()=>o(e,{action:\"click\",assetId:n})))})),o(e,{action:\"fireNativeImpressionTrackers\"}),\"complete\"===s.document.readyState?l():s.onload=l}))}})();" \ No newline at end of file diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index e22fadd10a2..aed583cbdfa 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -612,8 +612,16 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { 'type': identity.type, }; - apiParams = this.addIdentifier(identity, apiParams); + if (identity.type === 'hid') { + this.addIdentifier(identity, apiParams).then((apiParams) => { + this.callTokenize(config, identity, apiParams, onDone, onSuccess, onError); + }); + } else { + this.callTokenize(config, identity, apiParams, onDone, onSuccess, onError); + } + }, + callTokenize(config, identity, apiParams, onDone, onSuccess, onError) { if (typeof (identity.attributes) != typeof (undefined)) { apiParams.attributes = identity.attributes; } @@ -638,7 +646,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return; } - let customHeaders = {'Content-Type': 'application/json'}; + let customHeaders = { 'Content-Type': 'application/json' }; let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID)); if (dapSSID) { customHeaders[headerPrefix + '-DAP-SS-ID'] = dapSSID; diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index 3056ad7d52f..793d8deafbe 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -623,8 +623,12 @@ describe('symitriDapRtdProvider', function() { 'type': identity.type, }; - let hid = await dapUtils.addIdentifier(identity, apiParams); - expect(hid['identity']).is.equal('843BE0FB20AAE699F27E5BC88C554B716F3DD366F58C1BDE0ACFB7EA0DD90CE7'); + if (window.crypto && window.crypto.subtle) { + let hid = await dapUtils.addIdentifier(identity, apiParams).then(); + expect(hid['identity']).is.equal('843BE0FB20AAE699F27E5BC88C554B716F3DD366F58C1BDE0ACFB7EA0DD90CE7'); + } else { + expect(window.crypto.subtle).is.undefined + } }); it('passed undefined identifier is handled', async function () {