From 733c02d1ee23abb79c9f09d67f24ea54db0c6f7b Mon Sep 17 00:00:00 2001 From: Alexander Velichkin Date: Fri, 7 Oct 2022 19:19:33 +0300 Subject: [PATCH 1/8] Mgid RTD provider --- .../gpt/mgidRtdProvider_example.html | 144 ++++++++ modules/mgidRtdProvider.js | 184 ++++++++++ test/spec/modules/mgidRtdProvider_spec.js | 326 ++++++++++++++++++ 3 files changed, 654 insertions(+) create mode 100644 integrationExamples/gpt/mgidRtdProvider_example.html create mode 100644 modules/mgidRtdProvider.js create mode 100644 test/spec/modules/mgidRtdProvider_spec.js diff --git a/integrationExamples/gpt/mgidRtdProvider_example.html b/integrationExamples/gpt/mgidRtdProvider_example.html new file mode 100644 index 00000000000..d4c5dffda79 --- /dev/null +++ b/integrationExamples/gpt/mgidRtdProvider_example.html @@ -0,0 +1,144 @@ + + + + + + + + + + + +JS Bin + + + +

Basic Prebid.js Example

+
Div-1
+ +
+ +
+ + + diff --git a/modules/mgidRtdProvider.js b/modules/mgidRtdProvider.js new file mode 100644 index 00000000000..abf2c379c79 --- /dev/null +++ b/modules/mgidRtdProvider.js @@ -0,0 +1,184 @@ +import { submodule } from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; +import {deepAccess, logError, logInfo, mergeDeep} from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; + +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'mgid'; +const MGID_RTD_API_URL = 'https://servicer-dfw.mgid.com/sda'; +const MGUID_LOCAL_STORAGE_KEY = 'mguid'; +const ORTB2_NAME = 'www.mgid.com' + +const GVLID = 358; +/** @type {?Object} */ +export const storage = getStorageManager({ + gvlid: GVLID, + moduleName: SUBMODULE_NAME +}); + +function init(moduleConfig) { + if (!moduleConfig?.params?.clientSiteId) { + logError('Mgid clientSiteId is not set!'); + return false; + } + return true; +} + +function getBidRequestData(reqBidsConfigObj, onDone, moduleConfig, userConsent) { + let mguid; + try { + mguid = storage.getDataFromLocalStorage(MGUID_LOCAL_STORAGE_KEY); + } catch (e) { + logInfo(`Can't get mguid from localstorage`); + } + + const params = [ + { + name: 'gdprApplies', + data: userConsent?.gdpr?.gdprApplies, + }, + { + name: 'consentData', + data: userConsent?.gdpr?.consentString, + }, + { + name: 'uspString', + data: userConsent?.usp, + }, + { + name: 'cxurl', + data: encodeURIComponent(getContextUrl()), + }, + { + name: 'muid', + data: mguid, + }, + { + name: 'clientSiteId', + data: moduleConfig?.params?.clientSiteId, + }, + { + name: 'cxlang', + data: deepAccess(reqBidsConfigObj.ortb2Fragments.global, 'site.content.language'), + }, + ]; + + const url = MGID_RTD_API_URL + '?' + params.filter((p) => p.data).map((p) => p.name + '=' + p.data).join('&'); + + let isDone = false; + + ajax(url, { + success: (response, req) => { + if (req.status === 200) { + try { + const data = JSON.parse(response); + const ortb2 = reqBidsConfigObj?.ortb2Fragments?.global || {}; + + mergeDeep(ortb2, getDataForMerge(data)); + + if (data?.muid) { + try { + mguid = storage.setDataInLocalStorage(MGUID_LOCAL_STORAGE_KEY, data.muid); + } catch (e) { + logInfo(`Can't set mguid to localstorage`); + } + } + + onDone(); + isDone = true; + } catch (e) { + onDone(); + isDone = true; + + logError('Unable to parse Mgid RTD data', e); + } + } else { + onDone(); + isDone = true; + + logError('Mgid RTD wrong response status'); + } + }, + error: () => { + onDone(); + isDone = true; + + logError('Unable to get Mgid RTD data'); + } + }, + null, { + method: 'GET', + withCredentials: false, + }); + + setTimeout(function () { + if (!isDone) { + onDone(); + logInfo('Mgid RTD timeout'); + isDone = true; + } + }, moduleConfig.params.timeout || 1000); +} + +function getContextUrl() { + const refererInfo = getRefererInfo(); + + let resultUrl = refererInfo.canonicalUrl || refererInfo.topmostLocation; + + const metaElements = document.getElementsByTagName('meta'); + for (let i = 0; i < metaElements.length; i++) { + if (metaElements[i].getAttribute('property') === 'og:url') { + resultUrl = metaElements[i].content; + } + } + + return resultUrl; +} + +function getDataForMerge(responseData) { + let siteData = { + name: ORTB2_NAME + }; + let userData = { + name: ORTB2_NAME + }; + + if (responseData.siteSegments) { + siteData.segment = responseData.siteSegments.map((segmentId) => ({ id: segmentId })); + } + if (responseData.siteSegtax) { + siteData.ext = { + segtax: responseData.siteSegtax + } + } + + if (responseData.userSegments) { + userData.segment = responseData.userSegments.map((segmentId) => ({ id: segmentId })); + } + if (responseData.userSegtax) { + userData.ext = { + segtax: responseData.userSegtax + } + } + + return { + site: { + content: { + data: [siteData], + } + }, + user: { + data: [userData], + } + }; +} + +/** @type {RtdSubmodule} */ +export const mgidSubmodule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: getBidRequestData, +}; + +submodule(MODULE_NAME, mgidSubmodule); diff --git a/test/spec/modules/mgidRtdProvider_spec.js b/test/spec/modules/mgidRtdProvider_spec.js new file mode 100644 index 00000000000..7427c8c8be2 --- /dev/null +++ b/test/spec/modules/mgidRtdProvider_spec.js @@ -0,0 +1,326 @@ +import { mgidSubmodule, storage } from '../../../modules/mgidRtdProvider.js'; +import {expect} from 'chai'; +import * as refererDetection from '../../../src/refererDetection'; + +describe('Mgid RTD submodule', () => { + let server; + let clock; + let getRefererInfoStub; + let getDataFromLocalStorageStub; + + beforeEach(() => { + server = sinon.fakeServer.create(); + + clock = sinon.useFakeTimers(); + + getRefererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); + getRefererInfoStub.returns({ + canonicalUrl: 'https://www.test.com/abc' + }); + + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').returns('qwerty654321'); + }); + + afterEach(() => { + server.restore(); + clock.restore(); + getRefererInfoStub.restore(); + getDataFromLocalStorageStub.restore(); + }); + + it('init is successfull, when clientSiteId is defined', () => { + expect(mgidSubmodule.init({params: {clientSiteId: 123}})).to.be.true; + }); + + it('init is unsuccessfull, when clientSiteId is not defined', () => { + expect(mgidSubmodule.init({})).to.be.false; + }); + + it('getBidRequestData send all params to our endpoint and succesfully modifies ortb2', () => { + const responseObj = { + userSegments: ['100', '200'], + userSegtax: 5, + siteSegments: ['300', '400'], + siteSegtax: 7, + muid: 'qwerty654321', + }; + + let reqBidsConfigObj = { + ortb2Fragments: { + global: { + site: { + content: { + language: 'en', + } + } + }, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + { + gdpr: { + gdprApplies: true, + consentString: 'testConsent', + }, + usp: '1YYY', + } + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(responseObj) + ); + + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq('servicer-dfw.mgid.com'); + expect(requestUrl.searchParams.get('gdprApplies')).to.be.eq('true'); + expect(requestUrl.searchParams.get('consentData')).to.be.eq('testConsent'); + expect(requestUrl.searchParams.get('uspString')).to.be.eq('1YYY'); + expect(requestUrl.searchParams.get('muid')).to.be.eq('qwerty654321'); + expect(requestUrl.searchParams.get('clientSiteId')).to.be.eq('123'); + expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/abc'); + expect(requestUrl.searchParams.get('cxlang')).to.be.eq('en'); + + assert.deepInclude( + reqBidsConfigObj.ortb2Fragments.global, + { + site: { + content: { + language: 'en', + data: [ + { + name: 'www.mgid.com', + ext: { + segtax: 7 + }, + segment: [ + { id: '300' }, + { id: '400' }, + ] + } + ], + } + }, + user: { + data: [ + { + name: 'www.mgid.com', + ext: { + segtax: 5 + }, + segment: [ + { id: '100' }, + { id: '200' }, + ] + } + ], + }, + }); + }); + + it('getBidRequestData doesn\'t send params (consent and cxlang), if we haven\'t received them', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({}) + ); + + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq('servicer-dfw.mgid.com'); + expect(requestUrl.searchParams.get('gdprApplies')).to.be.null; + expect(requestUrl.searchParams.get('consentData')).to.be.null; + expect(requestUrl.searchParams.get('uspString')).to.be.null; + expect(requestUrl.searchParams.get('muid')).to.be.eq('qwerty654321'); + expect(requestUrl.searchParams.get('clientSiteId')).to.be.eq('123'); + expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/abc'); + expect(requestUrl.searchParams.get('cxlang')).to.be.null; + expect(onDone.calledOnce).to.be.true; + }); + + it('getBidRequestData use og:url for cxurl, if it is available', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + let metaStub = sinon.stub(document, 'getElementsByTagName').returns([ + { getAttribute: () => 'og:test', content: 'fake' }, + { getAttribute: () => 'og:url', content: 'https://realOgUrl.com/' } + ]); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({}) + ); + + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://realOgUrl.com/'); + expect(onDone.calledOnce).to.be.true; + + metaStub.restore(); + }); + + it('getBidRequestData use topMostLocation for cxurl, if nothing else left', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + getRefererInfoStub.returns({ + topmostLocation: 'https://www.test.com/topMost' + }); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({}) + ); + + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/topMost'); + expect(onDone.calledOnce).to.be.true; + }); + + it('getBidRequestData won\'t modify ortb2 if response is broken', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + '{' + ); + + assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {}); + expect(onDone.calledOnce).to.be.true; + }); + + it('getBidRequestData won\'t modify ortb2 if response status is not 200', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 204, + {'Content-Type': 'application/json'}, + '{}' + ); + + assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {}); + expect(onDone.calledOnce).to.be.true; + }); + + it('getBidRequestData won\'t modify ortb2 if response results in error', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 500, + {'Content-Type': 'application/json'}, + '{}' + ); + + assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {}); + expect(onDone.calledOnce).to.be.true; + }); + + it('getBidRequestData won\'t modify ortb2 if response time hits timeout', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123, timeout: 500}}, + {} + ); + + clock.tick(510); + + assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {}); + expect(onDone.calledOnce).to.be.true; + }); +}); From c8ecd487821651165ec9ef8626c2ce00d1a6a354 Mon Sep 17 00:00:00 2001 From: Alexander Velichkin Date: Mon, 10 Oct 2022 12:38:06 +0300 Subject: [PATCH 2/8] Mgid RTD provider --- modules/mgidRtdProvider.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 modules/mgidRtdProvider.md diff --git a/modules/mgidRtdProvider.md b/modules/mgidRtdProvider.md new file mode 100644 index 00000000000..7a2f5908d00 --- /dev/null +++ b/modules/mgidRtdProvider.md @@ -0,0 +1,9 @@ +# Overview + +Module Name: Mgid RTD Provider +Module Type: RTD Provider +Maintainer: prebid@mgid.com + +# Description + +RTD provider for mgid.com. From 7efcc3fe030b0329fb406493dc5bacb65d7d528c Mon Sep 17 00:00:00 2001 From: Alexander Velichkin Date: Tue, 11 Oct 2022 12:45:35 +0300 Subject: [PATCH 3/8] Mgid RTD provider. Fix endpoint URL --- modules/mgidRtdProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mgidRtdProvider.js b/modules/mgidRtdProvider.js index abf2c379c79..3b52b71e31a 100644 --- a/modules/mgidRtdProvider.js +++ b/modules/mgidRtdProvider.js @@ -6,7 +6,7 @@ import {getRefererInfo} from '../src/refererDetection.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'mgid'; -const MGID_RTD_API_URL = 'https://servicer-dfw.mgid.com/sda'; +const MGID_RTD_API_URL = 'https://servicer.mgid.com/sda'; const MGUID_LOCAL_STORAGE_KEY = 'mguid'; const ORTB2_NAME = 'www.mgid.com' From 17464f96f3c3447e20c144d069f5dce43d1ed86f Mon Sep 17 00:00:00 2001 From: Alexander Velichkin Date: Wed, 12 Oct 2022 16:11:12 +0300 Subject: [PATCH 4/8] Mgid RTD provider. Fix gdprApplies --- modules/mgidRtdProvider.js | 2 +- test/spec/modules/mgidRtdProvider_spec.js | 44 +++++++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/modules/mgidRtdProvider.js b/modules/mgidRtdProvider.js index 3b52b71e31a..3574bfe357c 100644 --- a/modules/mgidRtdProvider.js +++ b/modules/mgidRtdProvider.js @@ -36,7 +36,7 @@ function getBidRequestData(reqBidsConfigObj, onDone, moduleConfig, userConsent) const params = [ { name: 'gdprApplies', - data: userConsent?.gdpr?.gdprApplies, + data: typeof userConsent?.gdpr?.gdprApplies !== 'undefined' ? userConsent?.gdpr?.gdprApplies + '' : undefined, }, { name: 'consentData', diff --git a/test/spec/modules/mgidRtdProvider_spec.js b/test/spec/modules/mgidRtdProvider_spec.js index 7427c8c8be2..4f70b4d8b7c 100644 --- a/test/spec/modules/mgidRtdProvider_spec.js +++ b/test/spec/modules/mgidRtdProvider_spec.js @@ -79,7 +79,7 @@ describe('Mgid RTD submodule', () => { ); const requestUrl = new URL(server.requests[0].url); - expect(requestUrl.host).to.be.eq('servicer-dfw.mgid.com'); + expect(requestUrl.host).to.be.eq('servicer.mgid.com'); expect(requestUrl.searchParams.get('gdprApplies')).to.be.eq('true'); expect(requestUrl.searchParams.get('consentData')).to.be.eq('testConsent'); expect(requestUrl.searchParams.get('uspString')).to.be.eq('1YYY'); @@ -148,7 +148,7 @@ describe('Mgid RTD submodule', () => { ); const requestUrl = new URL(server.requests[0].url); - expect(requestUrl.host).to.be.eq('servicer-dfw.mgid.com'); + expect(requestUrl.host).to.be.eq('servicer.mgid.com'); expect(requestUrl.searchParams.get('gdprApplies')).to.be.null; expect(requestUrl.searchParams.get('consentData')).to.be.null; expect(requestUrl.searchParams.get('uspString')).to.be.null; @@ -159,6 +159,46 @@ describe('Mgid RTD submodule', () => { expect(onDone.calledOnce).to.be.true; }); + it('getBidRequestData send gdprApplies event if it is false', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + { + gdpr: { + gdprApplies: false, + consentString: 'testConsent', + }, + usp: '1YYY', + } + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({}) + ); + + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq('servicer.mgid.com'); + expect(requestUrl.searchParams.get('gdprApplies')).to.be.eq('false'); + expect(requestUrl.searchParams.get('consentData')).to.be.eq('testConsent'); + expect(requestUrl.searchParams.get('uspString')).to.be.eq('1YYY'); + expect(requestUrl.searchParams.get('muid')).to.be.eq('qwerty654321'); + expect(requestUrl.searchParams.get('clientSiteId')).to.be.eq('123'); + expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/abc'); + expect(requestUrl.searchParams.get('cxlang')).to.be.null; + expect(onDone.calledOnce).to.be.true; + }); + it('getBidRequestData use og:url for cxurl, if it is available', () => { let reqBidsConfigObj = { ortb2Fragments: { From 8ab2a5e35bde77c6e1b6ad5da64c400bb0939b61 Mon Sep 17 00:00:00 2001 From: Alexander Velichkin Date: Wed, 12 Oct 2022 17:20:38 +0300 Subject: [PATCH 5/8] Mgid RTD provider. Fix warnings --- modules/mgidRtdProvider.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/modules/mgidRtdProvider.js b/modules/mgidRtdProvider.js index 3574bfe357c..f30f14ea528 100644 --- a/modules/mgidRtdProvider.js +++ b/modules/mgidRtdProvider.js @@ -162,16 +162,22 @@ function getDataForMerge(responseData) { } } - return { - site: { + let result = {}; + if (siteData.segment || siteData.ext) { + result.site = { content: { data: [siteData], } - }, - user: { + } + } + + if (userData.segment || userData.ext) { + result.user = { data: [userData], } - }; + } + + return result; } /** @type {RtdSubmodule} */ From 707afb26cf7a0e27f9f1edfad1dfe8c716f01760 Mon Sep 17 00:00:00 2001 From: Alexander Velichkin Date: Sun, 23 Oct 2022 14:16:20 +0300 Subject: [PATCH 6/8] Mgid RTD provider. Remove unnecessary script --- integrationExamples/gpt/mgidRtdProvider_example.html | 1 - 1 file changed, 1 deletion(-) diff --git a/integrationExamples/gpt/mgidRtdProvider_example.html b/integrationExamples/gpt/mgidRtdProvider_example.html index d4c5dffda79..e3e4f720586 100644 --- a/integrationExamples/gpt/mgidRtdProvider_example.html +++ b/integrationExamples/gpt/mgidRtdProvider_example.html @@ -11,7 +11,6 @@ }); -