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/mgidBidAdapter.js b/modules/mgidBidAdapter.js
index 2653f157196..036effecd88 100644
--- a/modules/mgidBidAdapter.js
+++ b/modules/mgidBidAdapter.js
@@ -66,6 +66,7 @@ _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] =
export const spec = {
VERSION: '1.5',
code: BIDDER_CODE,
+ gvlid: GVLID,
supportedMediaTypes: [BANNER, NATIVE],
reId: /^[1-9][0-9]*$/,
NATIVE_ASSET_ID_TO_KEY_MAP: _NATIVE_ASSET_ID_TO_KEY_MAP,
@@ -177,6 +178,8 @@ export const spec = {
return;
}
+ const ortb2Data = bidderRequest?.ortb2 || {};
+
let request = {
id: deepAccess(bidderRequest, 'bidderRequestId'),
site: {domain, page},
@@ -190,7 +193,11 @@ export const spec = {
w: screen.width,
language: getLanguage()
},
- ext: {mgid_ver: spec.VERSION, prebid_ver: '$prebid.version$'},
+ ext: {
+ mgid_ver: spec.VERSION,
+ prebid_ver: '$prebid.version$',
+ ...ortb2Data
+ },
imp
};
if (bidderRequest && bidderRequest.gdprConsent) {
diff --git a/modules/mgidRtdProvider.js b/modules/mgidRtdProvider.js
new file mode 100644
index 00000000000..f30f14ea528
--- /dev/null
+++ b/modules/mgidRtdProvider.js
@@ -0,0 +1,190 @@
+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.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: typeof userConsent?.gdpr?.gdprApplies !== 'undefined' ? userConsent?.gdpr?.gdprApplies + '' : undefined,
+ },
+ {
+ 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
+ }
+ }
+
+ let result = {};
+ if (siteData.segment || siteData.ext) {
+ result.site = {
+ content: {
+ data: [siteData],
+ }
+ }
+ }
+
+ if (userData.segment || userData.ext) {
+ result.user = {
+ data: [userData],
+ }
+ }
+
+ return result;
+}
+
+/** @type {RtdSubmodule} */
+export const mgidSubmodule = {
+ name: SUBMODULE_NAME,
+ init: init,
+ getBidRequestData: getBidRequestData,
+};
+
+submodule(MODULE_NAME, mgidSubmodule);
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.
diff --git a/test/spec/modules/mgidRtdProvider_spec.js b/test/spec/modules/mgidRtdProvider_spec.js
new file mode 100644
index 00000000000..4f70b4d8b7c
--- /dev/null
+++ b/test/spec/modules/mgidRtdProvider_spec.js
@@ -0,0 +1,366 @@
+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.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.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 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: {
+ 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;
+ });
+});