From aab0156214cfae023b42d51e00c5c663b8e98a2a Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Thu, 11 Jul 2024 14:58:23 +0200 Subject: [PATCH 01/25] raveltech bid adapter: imports the AppNexus adapter and encrypts the UIDs found in the bid requests --- modules/raveltechBidAdapter.js | 88 ++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 modules/raveltechBidAdapter.js diff --git a/modules/raveltechBidAdapter.js b/modules/raveltechBidAdapter.js new file mode 100644 index 00000000000..01e93a1a0dc --- /dev/null +++ b/modules/raveltechBidAdapter.js @@ -0,0 +1,88 @@ +// Import the base adapter +import { spec as baseAdapter } from './appnexusBidAdapter.js'; // eslint-disable-line prebid/validate-imports +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { logInfo } from '../src/utils.js'; + +const BIDDER_CODE = 'raveltech'; +const URL = 'https://pb1.rvlproxy.net/bid/bid'; +// const URL_SIMPLE = 'https://pb1.rvlproxy.net/bid/simplebid'; + +export const spec = { + code: BIDDER_CODE, + gvlid: baseAdapter.GVLID, // use base adapter gvlid + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest[] Info describing the requests to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + if (!baseAdapter.buildRequests) { return []; } + + let anonymizedBidRequests = baseAdapter.buildRequests(bidRequests, bidderRequest); // call the bid requests from the Appnexus adapter + if (!anonymizedBidRequests) { return []; } // if no bid request, return empty Array + + if (!Array.isArray(anonymizedBidRequests)) { anonymizedBidRequests = [anonymizedBidRequests]; } // if only 1 bid request, anonymizedBidRequest will be an Object instead of an Array. Build Array with 1 bid request. + + // Load ZKAD runtime, used to anonymize the uids + const ZKAD = window.ZKAD || { anonymizeID(v, p) { return []; } }; + logInfo('ZKAD.ready=', ZKAD.ready); + + anonymizedBidRequests.forEach(bid => { + bid.url = URL; + bid.data = JSON.parse(bid.data); + + let eids = bid.data.eids; + if (!eids) { return; } + + eids.forEach(eid => { + if (!eid || !eid.id) { return; } + logInfo('eid.source=', eid.source); + eid.id = ZKAD.anonymizeID(eid.id, eid.source); + logInfo('Anonymized uid.id=', eid.id, 'as byte array of length=', eid.id.length); + }); + }); + + logInfo(anonymizedBidRequests); + return anonymizedBidRequests; + }, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!baseAdapter.isBidRequestValid) { return false; } + return baseAdapter.isBidRequestValid(bid); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, params) { + if (!baseAdapter.interpretResponse) { return []; } + return baseAdapter.interpretResponse(serverResponse, params); + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (!baseAdapter.getUserSyncs) { return []; } + return baseAdapter.getUserSyncs(syncOptions, responses, gdprConsent); + }, + + /** + * Add element selector to javascript tracker to improve native viewability + * @param {Bid} bid + */ + onBidWon: function (bid) { + if (!baseAdapter.onBidWon) { return; } + baseAdapter.onBidWon(bid); + } +}; + +registerBidder(spec); From 3faf9ea7c757ceda7d310fbd5e7ec1e438bab0cf Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Thu, 11 Jul 2024 16:31:26 +0200 Subject: [PATCH 02/25] 1st version of the key information for the raveltech bid adapter --- modules/raveltechBidAdapter.md | 150 +++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 modules/raveltechBidAdapter.md diff --git a/modules/raveltechBidAdapter.md b/modules/raveltechBidAdapter.md new file mode 100644 index 00000000000..e3919f24489 --- /dev/null +++ b/modules/raveltechBidAdapter.md @@ -0,0 +1,150 @@ +# Overview + +``` +Module Name: Ravel Bid Adapter +Module Type: Bidder Adapter +Maintainer: maintainers@raveltech.io +``` + +# Description + +Connects to Appnexus exchange through encrypted UIDs called Ravel Identifier (RIDs). + + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'raveltech', + params: { + placementId: 13144370 + } + }] + }, + // Native adUnit + { + code: 'native-div', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + }, + icon: { + required: false + } + } + }, + bids: [{ + bidder: 'raveltech', + params: { + placementId: 13232354, + allowSmallerSizes: true + } + }] + }, + // Video instream adUnit + { + code: 'video-instream', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + }, + }, + bids: [{ + bidder: 'raveltech', + params: { + placementId: 13232361, + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'] + } + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[300, 250]], + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream', + // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. + // To note - appnexus supports additional values for our system that are not part of the ORTB spec. If you want + // to use these values, they will have to be declared in the bids[].params.video object instead using the appnexus syntax. + // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will + // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. + minduration: 1, + maxduration: 60, + skip: 0, // 1 - true, 0 - false + skipafter: 5, + playbackmethod: [2], // note - we only support options 1-4 at this time + api: [1,2,3] // note - option 6 is not supported at this time + } + }, + bids: [ + { + bidder: 'raveltech', + params: { + placementId: 13232385, + video: { + skippable: true, + playback_method: 'auto_play_sound_off' + } + } + } + ] + }, + // Banner adUnit in a App Webview + // Only use this for situations where prebid.js is in a webview of an App + // See Prebid Mobile for displaying ads via an SDK + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + } + bids: [{ + bidder: 'raveltech', + params: { + placementId: 13144370, + app: { + id: "B1O2W3M4AN.com.prebid.webview", + geo: { + lat: 40.0964439, + lng: -75.3009142 + }, + device_id: { + idfa: "4D12078D-3246-4DA4-AD5E-7610481E7AE", // Apple advertising identifier + aaid: "38400000-8cf0-11bd-b23e-10b96e40000d", // Android advertising identifier + md5udid: "5756ae9022b2ea1e47d84fead75220c8", // MD5 hash of the ANDROID_ID + sha1udid: "4DFAA92388699AC6539885AEF1719293879985BF", // SHA1 hash of the ANDROID_ID + windowsadid: "750c6be243f1c4b5c9912b95a5742fc5" // Windows advertising identifier + } + } + } + }] + } +]; +``` From ad754b8a557658246ec881fbc59a5e8a716e4b46 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Thu, 11 Jul 2024 16:31:43 +0200 Subject: [PATCH 03/25] 1st version of the unit test for the raveltech bid adapter --- test/spec/modules/raveltechBidAdapter_spec.js | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 test/spec/modules/raveltechBidAdapter_spec.js diff --git a/test/spec/modules/raveltechBidAdapter_spec.js b/test/spec/modules/raveltechBidAdapter_spec.js new file mode 100644 index 00000000000..2af683c0174 --- /dev/null +++ b/test/spec/modules/raveltechBidAdapter_spec.js @@ -0,0 +1,77 @@ +import { expect } from 'chai'; +import { spec } from 'modules/raveltechBidAdapter.js'; + +const ENDPOINT = 'https://pb1.rvlproxy.net/bid/bid'; +const RID_LENGTH = 10000; + +describe('RavelTechAdapter', function () { + const bidRequests = [{ + 'bidder': 'raveltech', + 'params': { + 'placement_id': 234234 + }, + 'userIdAsEids': [{ + 'source': 'not-eligible-source', + 'uids': [{ + 'id': '12345678' + }] + }, + { + 'source': 'adnxs.com', + 'uids': [{ + 'id': '5435546' + }, + { + 'id': '2398645' + }] + }] + }]; + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('anonymizeBidRequests', function () { + let anonymizedBidRequests; + + beforeEach(function() { + anonymizedBidRequests = spec.buildRequests(bidRequests); + if (!Array.isArray(anonymizedBidRequests)) { + anonymizedBidRequests = [anonymizedBidRequests]; + } + }); + + it('should anonymize every id if the source is eligible for anonymization', function() { + anonymizedBidRequests.forEach(bid => { + bid.data = JSON.parse(bid.data); + const eids = bid.data.eids; + + eids.forEach(eid => { + if (eid.source === 'not-eligible-source') { return; } + expect(typeof eid.id).to.equal('string'); + expect(eid.id.length).to.be.at.least(RID_LENGTH); + }) + }) + }); + + it('should empty the id if the source is not eligible for anonymization', function() { + anonymizedBidRequests.forEach(bid => { + bid.data = JSON.parse(bid.data); + const eids = bid.data.eids; + + eids.forEach(eid => { + if (eid.source !== 'not-eligible-source') { return; } + expect(eid.id).to.satisfy(id => id === '' || (Array.isArray(id) && id.length === 0)); + }) + }) + }); + + it('should update the URL of every bid request', function() { + anonymizedBidRequests.forEach(bid => { + expect(bid.url).to.equal(ENDPOINT); + }); + }); + }); +}); From 962e519279779d410d22c2d81758121581761bea Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Mon, 15 Jul 2024 16:49:03 +0200 Subject: [PATCH 04/25] remove all functions that doesn't change compared to the Appnexus adapter --- modules/raveltechBidAdapter.js | 36 ---------------------------------- 1 file changed, 36 deletions(-) diff --git a/modules/raveltechBidAdapter.js b/modules/raveltechBidAdapter.js index 01e93a1a0dc..93bd2536994 100644 --- a/modules/raveltechBidAdapter.js +++ b/modules/raveltechBidAdapter.js @@ -46,42 +46,6 @@ export const spec = { logInfo(anonymizedBidRequests); return anonymizedBidRequests; - }, - - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function (bid) { - if (!baseAdapter.isBidRequestValid) { return false; } - return baseAdapter.isBidRequestValid(bid); - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, params) { - if (!baseAdapter.interpretResponse) { return []; } - return baseAdapter.interpretResponse(serverResponse, params); - }, - - getUserSyncs: function (syncOptions, responses, gdprConsent) { - if (!baseAdapter.getUserSyncs) { return []; } - return baseAdapter.getUserSyncs(syncOptions, responses, gdprConsent); - }, - - /** - * Add element selector to javascript tracker to improve native viewability - * @param {Bid} bid - */ - onBidWon: function (bid) { - if (!baseAdapter.onBidWon) { return; } - baseAdapter.onBidWon(bid); } }; From ac960879c399331d1aedaa3c0e6e2e5bf78d8869 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Mon, 15 Jul 2024 17:15:51 +0200 Subject: [PATCH 05/25] add logs --- test/spec/modules/raveltechBidAdapter_spec.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/spec/modules/raveltechBidAdapter_spec.js b/test/spec/modules/raveltechBidAdapter_spec.js index 2af683c0174..7a5454b286d 100644 --- a/test/spec/modules/raveltechBidAdapter_spec.js +++ b/test/spec/modules/raveltechBidAdapter_spec.js @@ -27,12 +27,6 @@ describe('RavelTechAdapter', function () { }] }]; - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - describe('anonymizeBidRequests', function () { let anonymizedBidRequests; @@ -45,6 +39,7 @@ describe('RavelTechAdapter', function () { it('should anonymize every id if the source is eligible for anonymization', function() { anonymizedBidRequests.forEach(bid => { + console.log(bid); bid.data = JSON.parse(bid.data); const eids = bid.data.eids; @@ -58,6 +53,7 @@ describe('RavelTechAdapter', function () { it('should empty the id if the source is not eligible for anonymization', function() { anonymizedBidRequests.forEach(bid => { + console.log(bid); bid.data = JSON.parse(bid.data); const eids = bid.data.eids; From 0009fd8398f4457bd3aefa0ec51b0c8b61698a9e Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Mon, 15 Jul 2024 17:34:44 +0200 Subject: [PATCH 06/25] Revert "remove all functions that doesn't change compared to the Appnexus adapter" This reverts commit 962e519279779d410d22c2d81758121581761bea. --- modules/raveltechBidAdapter.js | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/modules/raveltechBidAdapter.js b/modules/raveltechBidAdapter.js index 93bd2536994..01e93a1a0dc 100644 --- a/modules/raveltechBidAdapter.js +++ b/modules/raveltechBidAdapter.js @@ -46,6 +46,42 @@ export const spec = { logInfo(anonymizedBidRequests); return anonymizedBidRequests; + }, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!baseAdapter.isBidRequestValid) { return false; } + return baseAdapter.isBidRequestValid(bid); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, params) { + if (!baseAdapter.interpretResponse) { return []; } + return baseAdapter.interpretResponse(serverResponse, params); + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (!baseAdapter.getUserSyncs) { return []; } + return baseAdapter.getUserSyncs(syncOptions, responses, gdprConsent); + }, + + /** + * Add element selector to javascript tracker to improve native viewability + * @param {Bid} bid + */ + onBidWon: function (bid) { + if (!baseAdapter.onBidWon) { return; } + baseAdapter.onBidWon(bid); } }; From bab3139498eb0067933d7dc8dff7cbc6446b0183 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Mon, 15 Jul 2024 17:36:20 +0200 Subject: [PATCH 07/25] remove unnecessary functions, preventing duplicate --- modules/raveltechBidAdapter.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/modules/raveltechBidAdapter.js b/modules/raveltechBidAdapter.js index 01e93a1a0dc..13ec13eb01f 100644 --- a/modules/raveltechBidAdapter.js +++ b/modules/raveltechBidAdapter.js @@ -68,20 +68,6 @@ export const spec = { interpretResponse: function (serverResponse, params) { if (!baseAdapter.interpretResponse) { return []; } return baseAdapter.interpretResponse(serverResponse, params); - }, - - getUserSyncs: function (syncOptions, responses, gdprConsent) { - if (!baseAdapter.getUserSyncs) { return []; } - return baseAdapter.getUserSyncs(syncOptions, responses, gdprConsent); - }, - - /** - * Add element selector to javascript tracker to improve native viewability - * @param {Bid} bid - */ - onBidWon: function (bid) { - if (!baseAdapter.onBidWon) { return; } - baseAdapter.onBidWon(bid); } }; From a9af496a5e2d0d1cfccaddfd7fd3cc0dec08f0c8 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Tue, 16 Jul 2024 16:16:28 +0200 Subject: [PATCH 08/25] clean logs --- modules/raveltechBidAdapter.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/raveltechBidAdapter.js b/modules/raveltechBidAdapter.js index 13ec13eb01f..b0e442bd650 100644 --- a/modules/raveltechBidAdapter.js +++ b/modules/raveltechBidAdapter.js @@ -22,7 +22,6 @@ export const spec = { let anonymizedBidRequests = baseAdapter.buildRequests(bidRequests, bidderRequest); // call the bid requests from the Appnexus adapter if (!anonymizedBidRequests) { return []; } // if no bid request, return empty Array - if (!Array.isArray(anonymizedBidRequests)) { anonymizedBidRequests = [anonymizedBidRequests]; } // if only 1 bid request, anonymizedBidRequest will be an Object instead of an Array. Build Array with 1 bid request. // Load ZKAD runtime, used to anonymize the uids @@ -40,11 +39,10 @@ export const spec = { if (!eid || !eid.id) { return; } logInfo('eid.source=', eid.source); eid.id = ZKAD.anonymizeID(eid.id, eid.source); - logInfo('Anonymized uid.id=', eid.id, 'as byte array of length=', eid.id.length); + logInfo('Anonymized as byte array of length=', eid.id.length); }); }); - logInfo(anonymizedBidRequests); return anonymizedBidRequests; }, From ba6be2331c4ab95bc34403c3f1a8932e4d0d376b Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Tue, 16 Jul 2024 17:07:35 +0200 Subject: [PATCH 09/25] update of bidRequests to include UIDs + logs --- test/spec/modules/raveltechBidAdapter_spec.js | 120 +++++++++++++++--- 1 file changed, 102 insertions(+), 18 deletions(-) diff --git a/test/spec/modules/raveltechBidAdapter_spec.js b/test/spec/modules/raveltechBidAdapter_spec.js index 7a5454b286d..7463733ca7b 100644 --- a/test/spec/modules/raveltechBidAdapter_spec.js +++ b/test/spec/modules/raveltechBidAdapter_spec.js @@ -1,3 +1,5 @@ +/* eslint-disable no-console */ + import { expect } from 'chai'; import { spec } from 'modules/raveltechBidAdapter.js'; @@ -5,32 +7,115 @@ const ENDPOINT = 'https://pb1.rvlproxy.net/bid/bid'; const RID_LENGTH = 10000; describe('RavelTechAdapter', function () { - const bidRequests = [{ + let bidRequests = [{ 'bidder': 'raveltech', 'params': { - 'placement_id': 234234 + 'placementId': 234234 }, - 'userIdAsEids': [{ - 'source': 'not-eligible-source', - 'uids': [{ - 'id': '12345678' - }] + 'userId': { + 'id5id': { + 'uid': '0', + 'ext': { + 'linkType': 0, + 'pba': 'K2ogG7aaimJB4/PSEuwADQ==' + } + }, + 'pubProvidedId': [ + { + 'source': 'adnxs.com', + 'uids': [ + { + 'id': 'webo-id-1', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + } + ] + }, + { + 'source': 'adnxs.com', + 'uids': [ + { + 'id': 'webo-id-2', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + } + ] + }, + null + ] }, - { - 'source': 'adnxs.com', - 'uids': [{ - 'id': '5435546' + 'userIdAsEids': [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': '0', + 'atype': 1, + 'ext': { + 'linkType': 0, + 'pba': 'K2ogG7aaimJB4/PSEuwADQ==' + } + } + ] }, { - 'id': '2398645' - }] - }] + 'source': 'adnxs.com', + 'uids': [ + { + 'id': 'webo-id-1', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + }, + { + 'id': 'webo-id-2', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + } + ] + } + ], + 'ortb2Imp': { + 'ext': {} + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600], + [728, 90] + ] + } + }, + 'adUnitCode': 'test-div', + 'transactionId': null, + 'adUnitId': 'd6f8ff69-1336-41c3-a639-20b76c6e0be1', + 'sizes': [ + [300, 250], + [300, 600], + [728, 90] + ], + 'bidId': '3da7e5be5cc74', + 'bidderRequestId': '22077672ce0e1f', + 'auctionId': null, + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 }]; describe('anonymizeBidRequests', function () { let anonymizedBidRequests; beforeEach(function() { + console.log('ravel: beforeEach() eids ', JSON.stringify(bidRequests[0].userIdAsEids)); anonymizedBidRequests = spec.buildRequests(bidRequests); if (!Array.isArray(anonymizedBidRequests)) { anonymizedBidRequests = [anonymizedBidRequests]; @@ -38,9 +123,8 @@ describe('RavelTechAdapter', function () { }); it('should anonymize every id if the source is eligible for anonymization', function() { + console.log('ravel: anonymize eids:', anonymizedBidRequests[0].data.eids); anonymizedBidRequests.forEach(bid => { - console.log(bid); - bid.data = JSON.parse(bid.data); const eids = bid.data.eids; eids.forEach(eid => { @@ -53,8 +137,6 @@ describe('RavelTechAdapter', function () { it('should empty the id if the source is not eligible for anonymization', function() { anonymizedBidRequests.forEach(bid => { - console.log(bid); - bid.data = JSON.parse(bid.data); const eids = bid.data.eids; eids.forEach(eid => { @@ -71,3 +153,5 @@ describe('RavelTechAdapter', function () { }); }); }); + +/* eslint-disable no-console */ From 8815826a79cd912b5f65105a9f0d7d82ac15d82e Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Wed, 17 Jul 2024 11:50:04 +0200 Subject: [PATCH 10/25] update assertion in anonymization unit test --- test/spec/modules/raveltechBidAdapter_spec.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/spec/modules/raveltechBidAdapter_spec.js b/test/spec/modules/raveltechBidAdapter_spec.js index 7463733ca7b..6ae3276da5d 100644 --- a/test/spec/modules/raveltechBidAdapter_spec.js +++ b/test/spec/modules/raveltechBidAdapter_spec.js @@ -123,14 +123,12 @@ describe('RavelTechAdapter', function () { }); it('should anonymize every id if the source is eligible for anonymization', function() { - console.log('ravel: anonymize eids:', anonymizedBidRequests[0].data.eids); anonymizedBidRequests.forEach(bid => { const eids = bid.data.eids; eids.forEach(eid => { - if (eid.source === 'not-eligible-source') { return; } - expect(typeof eid.id).to.equal('string'); - expect(eid.id.length).to.be.at.least(RID_LENGTH); + if (eid.source === 'not-eligible-source') { return; } // if the source is not eligible, then go to the next eid + expect(eid.id).to.be.an('array').that.is.empty; }) }) }); @@ -140,7 +138,7 @@ describe('RavelTechAdapter', function () { const eids = bid.data.eids; eids.forEach(eid => { - if (eid.source !== 'not-eligible-source') { return; } + if (eid.source !== 'not-eligible-source') { return; } // if the source is eligible, then go to the next eid expect(eid.id).to.satisfy(id => id === '' || (Array.isArray(id) && id.length === 0)); }) }) From 4de13aaf96e9484e99a60b11ec971911cb7592e6 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Mon, 22 Jul 2024 15:00:00 +0200 Subject: [PATCH 11/25] add import to fix warnings about BidRequest and Bid --- modules/raveltechBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/raveltechBidAdapter.js b/modules/raveltechBidAdapter.js index b0e442bd650..0f6ac31c39f 100644 --- a/modules/raveltechBidAdapter.js +++ b/modules/raveltechBidAdapter.js @@ -2,10 +2,10 @@ import { spec as baseAdapter } from './appnexusBidAdapter.js'; // eslint-disable-line prebid/validate-imports import { registerBidder } from '../src/adapters/bidderFactory.js'; import { logInfo } from '../src/utils.js'; +import { BidRequest, Bid } from '../src/bidfactory.js'; const BIDDER_CODE = 'raveltech'; const URL = 'https://pb1.rvlproxy.net/bid/bid'; -// const URL_SIMPLE = 'https://pb1.rvlproxy.net/bid/simplebid'; export const spec = { code: BIDDER_CODE, From 29e817d288a078dbd4609f0882109e670070fc36 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Wed, 5 Mar 2025 15:05:14 +0100 Subject: [PATCH 12/25] 1st push of raveltechRtdProvider.md (doc for the raveltechRtdProvider module) --- modules/raveltechRtdProvider.md | 57 +++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 modules/raveltechRtdProvider.md diff --git a/modules/raveltechRtdProvider.md b/modules/raveltechRtdProvider.md new file mode 100644 index 00000000000..55302b36021 --- /dev/null +++ b/modules/raveltechRtdProvider.md @@ -0,0 +1,57 @@ +# Raveltech RTD Module for Prebid.js + +## Overview + +``` +Module Name: Raveltech RTD Provider +Module Type: RTD Provider +Maintainer: maintainers@raveltech.io +``` + +The RavelTech RTD (Real-Time Data) module for Prebid.js enables publishers to integrate seamlessly with Ravel Technologies' privacy-focused solution, ensuring bidder requests are anonymized before reaching SSPs and DSPs. By leveraging the Ravel Privacy Bus, this module prevents the transmission of personally identifiable information (PII) in bid requests, strengthening privacy compliance and security. + +## How It Works + +The module operates in two modes: +1. **Bid URL Replacement:** The module modifies the bid request URL of the configured bidders to pass through the Ravel proxy, ensuring that all IDs are anonymized. +2. **Bid Duplication (if `preserveOriginalBid` is enabled):** The module duplicates the original bid request, sending one request as-is and another through the Ravel proxy with anonymized IDs. + +## Configuration + +To enable the Raveltech RTD module, you need to configure it with a list of bidders and specify whether to preserve the original bid request. + +### Build +``` +gulp build --modules="rtdModule,raveltechRtdProvider,appnexusBidAdapter,..." +``` + +> Note that the global RTD module, `rtdModule`, is a prerequisite of the raveltech RTD module. + +### Parameters + +| Parameter | Type | Description | +|--------------------|--------|-------------| +| `bidders` | Array | A list of bidder codes (or their alias if an alias is used) that should have their bid requests anonymized via Ravel. | +| `preserveOriginalBid` | Boolean | If `true`, the original bid request is preserved, and an additional bid request is sent through the Ravel proxy. If `false`, the original bid request is replaced with the Ravel-protected request. | + +### Example Configuration + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'raveltech', + params: { + bidders: ['appnexus', 'rubicon'], + preserveOriginalBid: true + } + }] + } +}); +``` + +## Privacy Features + +The RavelTech RTD module allows publishers to implement the following privacy protections: +- Personally Identifiable Information (PII) is either removed or converted into Anonymized IDs (RIDs). +- Bid requests are routed through an anonymized proxy before reaching the SSP, ensuring IP address anonymization. From a8b4cd971fc8a1ee908c9230276688dbe140d3b6 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Wed, 5 Mar 2025 15:10:05 +0100 Subject: [PATCH 13/25] remove the raveltechBidAdapter as we're now going with the RTD approach as advised in the last pull request --- modules/raveltechBidAdapter.js | 72 -------- modules/raveltechBidAdapter.md | 150 --------------- test/spec/modules/rakutenBidAdapter_spec.js | 171 ------------------ test/spec/modules/raveltechBidAdapter_spec.js | 155 ---------------- 4 files changed, 548 deletions(-) delete mode 100644 modules/raveltechBidAdapter.js delete mode 100644 modules/raveltechBidAdapter.md delete mode 100644 test/spec/modules/rakutenBidAdapter_spec.js delete mode 100644 test/spec/modules/raveltechBidAdapter_spec.js diff --git a/modules/raveltechBidAdapter.js b/modules/raveltechBidAdapter.js deleted file mode 100644 index 0f6ac31c39f..00000000000 --- a/modules/raveltechBidAdapter.js +++ /dev/null @@ -1,72 +0,0 @@ -// Import the base adapter -import { spec as baseAdapter } from './appnexusBidAdapter.js'; // eslint-disable-line prebid/validate-imports -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { logInfo } from '../src/utils.js'; -import { BidRequest, Bid } from '../src/bidfactory.js'; - -const BIDDER_CODE = 'raveltech'; -const URL = 'https://pb1.rvlproxy.net/bid/bid'; - -export const spec = { - code: BIDDER_CODE, - gvlid: baseAdapter.GVLID, // use base adapter gvlid - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @return ServerRequest[] Info describing the requests to the server. - */ - buildRequests: function(bidRequests, bidderRequest) { - if (!baseAdapter.buildRequests) { return []; } - - let anonymizedBidRequests = baseAdapter.buildRequests(bidRequests, bidderRequest); // call the bid requests from the Appnexus adapter - if (!anonymizedBidRequests) { return []; } // if no bid request, return empty Array - if (!Array.isArray(anonymizedBidRequests)) { anonymizedBidRequests = [anonymizedBidRequests]; } // if only 1 bid request, anonymizedBidRequest will be an Object instead of an Array. Build Array with 1 bid request. - - // Load ZKAD runtime, used to anonymize the uids - const ZKAD = window.ZKAD || { anonymizeID(v, p) { return []; } }; - logInfo('ZKAD.ready=', ZKAD.ready); - - anonymizedBidRequests.forEach(bid => { - bid.url = URL; - bid.data = JSON.parse(bid.data); - - let eids = bid.data.eids; - if (!eids) { return; } - - eids.forEach(eid => { - if (!eid || !eid.id) { return; } - logInfo('eid.source=', eid.source); - eid.id = ZKAD.anonymizeID(eid.id, eid.source); - logInfo('Anonymized as byte array of length=', eid.id.length); - }); - }); - - return anonymizedBidRequests; - }, - - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function (bid) { - if (!baseAdapter.isBidRequestValid) { return false; } - return baseAdapter.isBidRequestValid(bid); - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, params) { - if (!baseAdapter.interpretResponse) { return []; } - return baseAdapter.interpretResponse(serverResponse, params); - } -}; - -registerBidder(spec); diff --git a/modules/raveltechBidAdapter.md b/modules/raveltechBidAdapter.md deleted file mode 100644 index e3919f24489..00000000000 --- a/modules/raveltechBidAdapter.md +++ /dev/null @@ -1,150 +0,0 @@ -# Overview - -``` -Module Name: Ravel Bid Adapter -Module Type: Bidder Adapter -Maintainer: maintainers@raveltech.io -``` - -# Description - -Connects to Appnexus exchange through encrypted UIDs called Ravel Identifier (RIDs). - - -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [{ - bidder: 'raveltech', - params: { - placementId: 13144370 - } - }] - }, - // Native adUnit - { - code: 'native-div', - sizes: [[1, 1]], - mediaTypes: { - native: { - title: { - required: true - }, - body: { - required: true - }, - image: { - required: true - }, - sponsoredBy: { - required: true - }, - icon: { - required: false - } - } - }, - bids: [{ - bidder: 'raveltech', - params: { - placementId: 13232354, - allowSmallerSizes: true - } - }] - }, - // Video instream adUnit - { - code: 'video-instream', - sizes: [[640, 480]], - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'instream' - }, - }, - bids: [{ - bidder: 'raveltech', - params: { - placementId: 13232361, - video: { - skippable: true, - playback_methods: ['auto_play_sound_off'] - } - } - }] - }, - // Video outstream adUnit - { - code: 'video-outstream', - sizes: [[300, 250]], - mediaTypes: { - video: { - playerSize: [[300, 250]], - context: 'outstream', - // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. - // To note - appnexus supports additional values for our system that are not part of the ORTB spec. If you want - // to use these values, they will have to be declared in the bids[].params.video object instead using the appnexus syntax. - // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will - // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. - minduration: 1, - maxduration: 60, - skip: 0, // 1 - true, 0 - false - skipafter: 5, - playbackmethod: [2], // note - we only support options 1-4 at this time - api: [1,2,3] // note - option 6 is not supported at this time - } - }, - bids: [ - { - bidder: 'raveltech', - params: { - placementId: 13232385, - video: { - skippable: true, - playback_method: 'auto_play_sound_off' - } - } - } - ] - }, - // Banner adUnit in a App Webview - // Only use this for situations where prebid.js is in a webview of an App - // See Prebid Mobile for displaying ads via an SDK - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - } - bids: [{ - bidder: 'raveltech', - params: { - placementId: 13144370, - app: { - id: "B1O2W3M4AN.com.prebid.webview", - geo: { - lat: 40.0964439, - lng: -75.3009142 - }, - device_id: { - idfa: "4D12078D-3246-4DA4-AD5E-7610481E7AE", // Apple advertising identifier - aaid: "38400000-8cf0-11bd-b23e-10b96e40000d", // Android advertising identifier - md5udid: "5756ae9022b2ea1e47d84fead75220c8", // MD5 hash of the ANDROID_ID - sha1udid: "4DFAA92388699AC6539885AEF1719293879985BF", // SHA1 hash of the ANDROID_ID - windowsadid: "750c6be243f1c4b5c9912b95a5742fc5" // Windows advertising identifier - } - } - } - }] - } -]; -``` diff --git a/test/spec/modules/rakutenBidAdapter_spec.js b/test/spec/modules/rakutenBidAdapter_spec.js deleted file mode 100644 index 2a9fcb9f83b..00000000000 --- a/test/spec/modules/rakutenBidAdapter_spec.js +++ /dev/null @@ -1,171 +0,0 @@ -import { expect } from 'chai' -import { spec } from 'modules/rakutenBidAdapter/index.js' -import { newBidder } from 'src/adapters/bidderFactory.js' -import {config} from '../../../src/config.js'; - -describe('rakutenBidAdapter', function() { - const adapter = newBidder(spec); - const ENDPOINT = 'https://s-bid.rmp.rakuten.com/h'; - let sandbox; - - beforeEach(function() { - config.resetConfig(); - }); - - afterEach(function () { - config.resetConfig(); - }); - - describe('inherited functions', () => { - it('exists and is a function', () => { - expect(adapter.callBids).to.exist.and.to.be.a('function') - }) - }); - - describe('isBidRequestValid', () => { - let bid = { - bidder: 'rakuten', - params: { - adSpotId: '56789' - } - }; - - it('should return true when required params found', () => { - expect(spec.isBidRequestValid(bid)).to.equal(true) - }); - - it('should return false when required params are not passed', () => { - bid.params.adSpotId = ''; - expect(spec.isBidRequestValid(bid)).to.equal(false) - }); - - it('should return false when required params are not passed', () => { - let invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = {}; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false) - }) - }); - - describe('buildRequests', () => { - const bidRequests = [ - { - // banner - params: { - adSpotId: '58278' - } - } - ]; - - const bidderRequest = { - bids: bidRequests, - refererInfo: { - referer: 'http://test.com', - stack: ['http://test.com'] - }, - gdprConsent: { - consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', - vendorData: {}, - gdprApplies: true - }, - uspConsent: '1YN-' - }; - - it('sends bid request to ENDPOINT via GET', () => { - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('GET') - expect(request.data.gdpr).to.equal(1); - expect(request.data.cd).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - expect(request.data.ccpa).to.equal('1YN-'); - }); - - it('allows url override', () => { - config.setConfig({ - rakuten: { - endpoint: '//test.rakuten.com' - } - }); - const request = spec.buildRequests(bidRequests, bidderRequest)[0]; - expect(request.url).to.equal('//test.rakuten.com'); - }) - }); - - describe('interpretResponse', () => { - const bidRequests = { - banner: { - method: 'GET', - url: '', - data: { - t: '56789', - s: 'https', - ua: - 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36', - l: 'ja', - d: 'examples.com', - tp: 'https://examples.com/foo/fuga', - pp: 'https://examples.com/hoge/muga' - } - } - }; - - const serverResponse = { - noAd: [], - noAd2: { - requestId: 'biequa9oaph4we' - }, - banner: { - requestId: 'biequa9oaph4we', - cpm: 37.66, - width: 300, - height: 250, - creativeId: 140281, - dealId: 'phoh3pad-ai4ah-xoh7x-ahk7cheasae3oh', - currency: 'USD', - netRevenue: 300, - ttl: 3000, - ad: '' - } - }; - - it('handles nobid responses', () => { - const result = spec.interpretResponse( - { body: serverResponse.noAd }, - bidRequests.banner - ); - expect(result.length).to.equal(0); - - const result2 = spec.interpretResponse( - { body: serverResponse.noAd2 }, - bidRequests.banner - ); - expect(result2.length).to.equal(0); - }) - }); - describe('spec.getUserSyncs', function () { - const syncResponse = [{ - body: { - request_id: 'biequa9oaph4we', - sync_urls: ['https://rdn1.test/sync?uid=9876543210', 'https://rdn2.test/sync?uid=9876543210'] - } - }]; - const nosyncResponse = [{ - body: { - request_id: 'biequa9oaph4we', - sync_urls: [] - } - }]; - let syncOptions - beforeEach(function () { - syncOptions = { - pixelEnabled: true - } - }); - it('sucess usersync url', function () { - const result = []; - result.push({type: 'image', url: 'https://rdn1.test/sync?uid=9876543210'}); - result.push({type: 'image', url: 'https://rdn2.test/sync?uid=9876543210'}); - expect(spec.getUserSyncs(syncOptions, syncResponse)).to.deep.equal(result); - }); - }); -}); diff --git a/test/spec/modules/raveltechBidAdapter_spec.js b/test/spec/modules/raveltechBidAdapter_spec.js deleted file mode 100644 index 6ae3276da5d..00000000000 --- a/test/spec/modules/raveltechBidAdapter_spec.js +++ /dev/null @@ -1,155 +0,0 @@ -/* eslint-disable no-console */ - -import { expect } from 'chai'; -import { spec } from 'modules/raveltechBidAdapter.js'; - -const ENDPOINT = 'https://pb1.rvlproxy.net/bid/bid'; -const RID_LENGTH = 10000; - -describe('RavelTechAdapter', function () { - let bidRequests = [{ - 'bidder': 'raveltech', - 'params': { - 'placementId': 234234 - }, - 'userId': { - 'id5id': { - 'uid': '0', - 'ext': { - 'linkType': 0, - 'pba': 'K2ogG7aaimJB4/PSEuwADQ==' - } - }, - 'pubProvidedId': [ - { - 'source': 'adnxs.com', - 'uids': [ - { - 'id': 'webo-id-1', - 'atype': 1, - 'ext': { - 'stype': 'ppuid' - } - } - ] - }, - { - 'source': 'adnxs.com', - 'uids': [ - { - 'id': 'webo-id-2', - 'atype': 1, - 'ext': { - 'stype': 'ppuid' - } - } - ] - }, - null - ] - }, - 'userIdAsEids': [ - { - 'source': 'id5-sync.com', - 'uids': [ - { - 'id': '0', - 'atype': 1, - 'ext': { - 'linkType': 0, - 'pba': 'K2ogG7aaimJB4/PSEuwADQ==' - } - } - ] - }, - { - 'source': 'adnxs.com', - 'uids': [ - { - 'id': 'webo-id-1', - 'atype': 1, - 'ext': { - 'stype': 'ppuid' - } - }, - { - 'id': 'webo-id-2', - 'atype': 1, - 'ext': { - 'stype': 'ppuid' - } - } - ] - } - ], - 'ortb2Imp': { - 'ext': {} - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [300, 250], - [300, 600], - [728, 90] - ] - } - }, - 'adUnitCode': 'test-div', - 'transactionId': null, - 'adUnitId': 'd6f8ff69-1336-41c3-a639-20b76c6e0be1', - 'sizes': [ - [300, 250], - [300, 600], - [728, 90] - ], - 'bidId': '3da7e5be5cc74', - 'bidderRequestId': '22077672ce0e1f', - 'auctionId': null, - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }]; - - describe('anonymizeBidRequests', function () { - let anonymizedBidRequests; - - beforeEach(function() { - console.log('ravel: beforeEach() eids ', JSON.stringify(bidRequests[0].userIdAsEids)); - anonymizedBidRequests = spec.buildRequests(bidRequests); - if (!Array.isArray(anonymizedBidRequests)) { - anonymizedBidRequests = [anonymizedBidRequests]; - } - }); - - it('should anonymize every id if the source is eligible for anonymization', function() { - anonymizedBidRequests.forEach(bid => { - const eids = bid.data.eids; - - eids.forEach(eid => { - if (eid.source === 'not-eligible-source') { return; } // if the source is not eligible, then go to the next eid - expect(eid.id).to.be.an('array').that.is.empty; - }) - }) - }); - - it('should empty the id if the source is not eligible for anonymization', function() { - anonymizedBidRequests.forEach(bid => { - const eids = bid.data.eids; - - eids.forEach(eid => { - if (eid.source !== 'not-eligible-source') { return; } // if the source is eligible, then go to the next eid - expect(eid.id).to.satisfy(id => id === '' || (Array.isArray(id) && id.length === 0)); - }) - }) - }); - - it('should update the URL of every bid request', function() { - anonymizedBidRequests.forEach(bid => { - expect(bid.url).to.equal(ENDPOINT); - }); - }); - }); -}); - -/* eslint-disable no-console */ From 89b366a5e5d91bc1b949e08ec9a2f44819b1dfe2 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Wed, 5 Mar 2025 15:10:39 +0100 Subject: [PATCH 14/25] 1st push of raveltechRtdProvider.js --- modules/raveltechRtdProvider.js | 123 ++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 modules/raveltechRtdProvider.js diff --git a/modules/raveltechRtdProvider.js b/modules/raveltechRtdProvider.js new file mode 100644 index 00000000000..706fc027a68 --- /dev/null +++ b/modules/raveltechRtdProvider.js @@ -0,0 +1,123 @@ +import {submodule, getHook} from '../src/hook.js'; +import adapterManager from '../src/adapterManager.js'; +import {logInfo, deepClone, isArray, isStr, isPlainObject, logError} from '../src/utils.js'; + +// Constants +const MODULE_NAME = 'raveltech'; +const RAVEL_ENDPOINT = 'https://pb1.rvlproxy.net/bid/bid'; + +const getAdapterNameForAlias = (aliasName) => adapterManager.aliasRegistry[aliasName] || aliasName; + +const getAnonymizedEids = (eids) => { + const ZKAD = window.ZKAD || { anonymizeID(v, p) { return undefined; } }; + logInfo('ZKAD.ready=', ZKAD.ready); + if (!eids) { return eids; } + + eids.forEach(eid => { + if (!eid || !eid.uids || eid.uids.length === 0) { return eid } + logInfo('eid.source=', eid.source); + eid.uids = eid.uids.flatMap(uid => { + if (!uid || !uid.id) { return []; } + const id = ZKAD.anonymizeID(uid.id, eid.source); + if (!id) { return []; } + logInfo('Anonymized as byte array of length=', id.length); + return [ { + ...uid, + id + } ]; + }) + }) + + return eids; +}; + +const addRavelDataToRequest = (request, adapterName) => { + if (isStr(request.data)) { + try { + const data = JSON.parse(request.data); + data.ravel = { pbjsAdapter: adapterName }; + request.data = JSON.stringify(data); + } catch (_e) {} + } else if (!request.data) { + request.data = { ravel: { pbjsAdapter: adapterName } }; + } else if (isPlainObject(request.data)) { + request.data.ravel = { pbjsAdapter: adapterName }; + } +}; + +const wrapBuildRequests = (aliasName, preserveOriginalBid, buildRequests) => { + const adapterName = getAdapterNameForAlias(aliasName) + + return (validBidRequests, ...rest) => { + let requests = preserveOriginalBid ? buildRequests(validBidRequests, ...rest) : []; + if (!isArray(requests)) { + requests = [ requests ]; + } + + try { + const ravelBidRequests = deepClone(validBidRequests); + + // Anonymize eids for ravel proxified requests + const anonymizedEids = getAnonymizedEids(ravelBidRequests[0]?.userIdAsEids); + + ravelBidRequests.forEach(bidRequest => { + // Replace original eids with anonymized eids + bidRequest.userIdAsEids = anonymizedEids; + }); + + let ravelRequests = buildRequests(ravelBidRequests, ...rest); + if (!isArray(ravelRequests) && ravelRequests) { + ravelRequests = [ ravelRequests ]; + } + ravelRequests?.forEach(request => { + // Proxyfy request + request.url = RAVEL_ENDPOINT; + request.method = 'POST'; + addRavelDataToRequest(request, adapterName); + }) + + return [ ...requests ?? [], ...ravelRequests ?? [] ]; + } catch (e) { + logError('Error while generating ravel requests :', e); + return requests; + } + } +}; + +const getBidderRequestsHook = (config) => { + const allowedBidders = config.params.bidders || []; + const preserveOriginalBid = config.params.preserveOriginalBid ?? false; + const wrappedBidders = []; + + return (next, spec, ...rest) => { + if (allowedBidders.includes(spec.code) && !wrappedBidders.includes(spec.code)) { + spec.buildRequests = wrapBuildRequests(spec.code, preserveOriginalBid, spec.buildRequests); + + wrappedBidders.push(spec.code); + } + next(spec, ...rest); + } +}; + +/** + * Init + * @param {Object} config Module configuration + * @param {boolean} _userConsent + * @returns true + */ +const init = (config, _userConsent) => { + const allowedBidders = config.params.bidders || []; + const preserveOriginalBid = config.params.preserveOriginalBid ?? false; + + getHook('processBidderRequests').before(getBidderRequestsHook(config)); + logInfo(`Raveltech RTD ready - ${preserveOriginalBid ? 'will' : `won't`} duplicate bid requests - Allowed bidders : `, allowedBidders); + return true; +}; + +export const raveltechSubmodule = { + name: MODULE_NAME, + init +}; + +// Register raveltechSubmodule as submodule of realTimeData +submodule('realTimeData', raveltechSubmodule); From c4cfb596b6ce1eead859a5d92cfb0390e451d565 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Wed, 5 Mar 2025 15:10:57 +0100 Subject: [PATCH 15/25] 1st push of raveltechRtdProvider_spec.js --- .../spec/modules/raveltechRtdProvider_spec.js | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 test/spec/modules/raveltechRtdProvider_spec.js diff --git a/test/spec/modules/raveltechRtdProvider_spec.js b/test/spec/modules/raveltechRtdProvider_spec.js new file mode 100644 index 00000000000..fe96a76d6c5 --- /dev/null +++ b/test/spec/modules/raveltechRtdProvider_spec.js @@ -0,0 +1,111 @@ +import {hook} from '../../../src/hook'; +import {BANNER} from '../../../src/mediaTypes.js'; +import {raveltechSubmodule} from 'modules/raveltechRtdProvider'; +import adapterManager from '../../../src/adapterManager.js'; +import {registerBidder} from 'src/adapters/bidderFactory.js'; + +describe('raveltechRtdProvider', () => { + const fakeBuildRequests = sinon.spy((valibBidRequests) => { + return { method: 'POST', data: { count: valibBidRequests.length, uids: valibBidRequests[0]?.userIdAsEids }, url: 'https://www.fakebidder.com' } + }); + + const fakeZkad = sinon.spy((id) => id.substr(0, 3)); + const fakeAjax = sinon.spy(); + + const fakeBidReq = { + adUnitCode: 'adunit', + adUnitId: '123', + auctionId: 'abc', + bidId: 'abc123', + userIdAsEids: [ + { source: 'usersource.com', uids: [ { id: 'testid123', atype: 1 } ] } + ] + }; + + before(() => { + hook.ready(); + // Setup fake bidder + const stubBidder = { + code: 'test', + supportedMediaTypes: [BANNER], + buildRequests: fakeBuildRequests, + interpretResponse: () => [], + isBidRequestValid: () => true + }; + registerBidder(stubBidder); + adapterManager.aliasBidAdapter('test', 'alias1'); + adapterManager.aliasBidAdapter('test', 'alias2'); + + // Init module + raveltechSubmodule.init({ params: { bidders: [ 'alias1', 'test' ], preserveOriginalBid: true } }); + }) + + afterEach(() => { + fakeBuildRequests.resetHistory(); + fakeZkad.resetHistory(); + fakeAjax.resetHistory(); + }) + + it('do not wrap bidder not in bidders params', () => { + adapterManager.getBidAdapter('alias2').callBids({ + auctionId: '123', + bidderCode: 'alias2', + bidderRequestId: 'abc', + bids: [ { ...fakeBidReq, bidder: 'alias2' } ] + }, sinon.stub(), sinon.stub(), fakeAjax, sinon.stub(), sinon.stub()); + expect(fakeAjax.calledOnce).to.be.true; + expect(fakeZkad.called).to.be.false; + expect(fakeBuildRequests.calledOnce).to.be.true; + expect(fakeAjax.getCall(0).args[2]).to.contain('"id":"testid123"'); + expect(fakeAjax.getCall(0).args[2]).not.to.contain('"pbjsAdapter":"test"'); + }) + + it('wrap bidder only by alias', () => { + adapterManager.getBidAdapter('alias2').callBids({ + auctionId: '123', + bidderCode: 'test', + bidderRequestId: 'abc', + bids: [ { ...fakeBidReq, bidder: 'test' } ] + }, sinon.stub(), sinon.stub(), fakeAjax, sinon.stub(), sinon.stub()); + expect(fakeAjax.calledOnce).to.be.true; + expect(fakeZkad.called).to.be.false; + expect(fakeBuildRequests.calledOnce).to.be.true; + expect(fakeAjax.getCall(0).args[2]).to.contain('"id":"testid123"'); + expect(fakeAjax.getCall(0).args[2]).not.to.contain('"pbjsAdapter":"test"'); + }) + + it('remove uids when ZKAD unavailable', () => { + adapterManager.getBidAdapter('alias1').callBids({ + auctionId: '123', + bidderCode: 'test', + bidderRequestId: 'abc', + bids: [ { ...fakeBidReq, bidder: 'test' } ] + }, sinon.stub(), sinon.stub(), fakeAjax, sinon.stub(), sinon.stub()); + expect(fakeAjax.calledTwice).to.be.true; + expect(fakeZkad.called).to.be.false; + expect(fakeBuildRequests.calledTwice).to.be.true; + expect(fakeAjax.getCall(0).args[2]).to.contain('"id":"testid123"'); + expect(fakeAjax.getCall(1).args[2]).not.to.contain('"id":"testid123"'); + expect(fakeAjax.getCall(1).args[2]).not.to.contain('"id":'); + expect(fakeAjax.getCall(0).args[2]).not.to.contain('"pbjsAdapter":"test"'); + expect(fakeAjax.getCall(1).args[2]).to.contain('"pbjsAdapter":"test"'); + }) + + it('successfully replace uids with ZKAD', () => { + window.ZKAD = { anonymizeID: fakeZkad }; + adapterManager.getBidAdapter('alias1').callBids({ + auctionId: '123', + bidderCode: 'test', + bidderRequestId: 'abc', + bids: [ { ...fakeBidReq, bidder: 'test' } ] + }, sinon.stub(), sinon.stub(), fakeAjax, sinon.stub(), sinon.stub()); + expect(fakeAjax.calledTwice).to.be.true; + expect(fakeZkad.calledOnce).to.be.true; + expect(fakeBuildRequests.calledTwice).to.be.true; + expect(fakeAjax.getCall(0).args[2]).to.contain('"id":"testid123"'); + expect(fakeAjax.getCall(1).args[2]).not.to.contain('"id":"testid123"'); + expect(fakeAjax.getCall(1).args[2]).to.contain('"id":"tes"'); + expect(fakeAjax.getCall(0).args[2]).not.to.contain('"pbjsAdapter":"test"'); + expect(fakeAjax.getCall(1).args[2]).to.contain('"pbjsAdapter":"test"'); + }) +}) From 26d9822645d7b3dde0422dbb9f27c292002d0b0e Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Wed, 5 Mar 2025 17:22:32 +0100 Subject: [PATCH 16/25] Revert "remove the raveltechBidAdapter as we're now going with the RTD approach as advised in the last pull request" This reverts commit a8b4cd971fc8a1ee908c9230276688dbe140d3b6. --- modules/raveltechBidAdapter.js | 72 ++++++++ modules/raveltechBidAdapter.md | 150 +++++++++++++++ test/spec/modules/rakutenBidAdapter_spec.js | 171 ++++++++++++++++++ test/spec/modules/raveltechBidAdapter_spec.js | 155 ++++++++++++++++ 4 files changed, 548 insertions(+) create mode 100644 modules/raveltechBidAdapter.js create mode 100644 modules/raveltechBidAdapter.md create mode 100644 test/spec/modules/rakutenBidAdapter_spec.js create mode 100644 test/spec/modules/raveltechBidAdapter_spec.js diff --git a/modules/raveltechBidAdapter.js b/modules/raveltechBidAdapter.js new file mode 100644 index 00000000000..0f6ac31c39f --- /dev/null +++ b/modules/raveltechBidAdapter.js @@ -0,0 +1,72 @@ +// Import the base adapter +import { spec as baseAdapter } from './appnexusBidAdapter.js'; // eslint-disable-line prebid/validate-imports +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { logInfo } from '../src/utils.js'; +import { BidRequest, Bid } from '../src/bidfactory.js'; + +const BIDDER_CODE = 'raveltech'; +const URL = 'https://pb1.rvlproxy.net/bid/bid'; + +export const spec = { + code: BIDDER_CODE, + gvlid: baseAdapter.GVLID, // use base adapter gvlid + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest[] Info describing the requests to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + if (!baseAdapter.buildRequests) { return []; } + + let anonymizedBidRequests = baseAdapter.buildRequests(bidRequests, bidderRequest); // call the bid requests from the Appnexus adapter + if (!anonymizedBidRequests) { return []; } // if no bid request, return empty Array + if (!Array.isArray(anonymizedBidRequests)) { anonymizedBidRequests = [anonymizedBidRequests]; } // if only 1 bid request, anonymizedBidRequest will be an Object instead of an Array. Build Array with 1 bid request. + + // Load ZKAD runtime, used to anonymize the uids + const ZKAD = window.ZKAD || { anonymizeID(v, p) { return []; } }; + logInfo('ZKAD.ready=', ZKAD.ready); + + anonymizedBidRequests.forEach(bid => { + bid.url = URL; + bid.data = JSON.parse(bid.data); + + let eids = bid.data.eids; + if (!eids) { return; } + + eids.forEach(eid => { + if (!eid || !eid.id) { return; } + logInfo('eid.source=', eid.source); + eid.id = ZKAD.anonymizeID(eid.id, eid.source); + logInfo('Anonymized as byte array of length=', eid.id.length); + }); + }); + + return anonymizedBidRequests; + }, + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (!baseAdapter.isBidRequestValid) { return false; } + return baseAdapter.isBidRequestValid(bid); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, params) { + if (!baseAdapter.interpretResponse) { return []; } + return baseAdapter.interpretResponse(serverResponse, params); + } +}; + +registerBidder(spec); diff --git a/modules/raveltechBidAdapter.md b/modules/raveltechBidAdapter.md new file mode 100644 index 00000000000..e3919f24489 --- /dev/null +++ b/modules/raveltechBidAdapter.md @@ -0,0 +1,150 @@ +# Overview + +``` +Module Name: Ravel Bid Adapter +Module Type: Bidder Adapter +Maintainer: maintainers@raveltech.io +``` + +# Description + +Connects to Appnexus exchange through encrypted UIDs called Ravel Identifier (RIDs). + + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'raveltech', + params: { + placementId: 13144370 + } + }] + }, + // Native adUnit + { + code: 'native-div', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + }, + icon: { + required: false + } + } + }, + bids: [{ + bidder: 'raveltech', + params: { + placementId: 13232354, + allowSmallerSizes: true + } + }] + }, + // Video instream adUnit + { + code: 'video-instream', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + }, + }, + bids: [{ + bidder: 'raveltech', + params: { + placementId: 13232361, + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'] + } + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[300, 250]], + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream', + // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. + // To note - appnexus supports additional values for our system that are not part of the ORTB spec. If you want + // to use these values, they will have to be declared in the bids[].params.video object instead using the appnexus syntax. + // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will + // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. + minduration: 1, + maxduration: 60, + skip: 0, // 1 - true, 0 - false + skipafter: 5, + playbackmethod: [2], // note - we only support options 1-4 at this time + api: [1,2,3] // note - option 6 is not supported at this time + } + }, + bids: [ + { + bidder: 'raveltech', + params: { + placementId: 13232385, + video: { + skippable: true, + playback_method: 'auto_play_sound_off' + } + } + } + ] + }, + // Banner adUnit in a App Webview + // Only use this for situations where prebid.js is in a webview of an App + // See Prebid Mobile for displaying ads via an SDK + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + } + bids: [{ + bidder: 'raveltech', + params: { + placementId: 13144370, + app: { + id: "B1O2W3M4AN.com.prebid.webview", + geo: { + lat: 40.0964439, + lng: -75.3009142 + }, + device_id: { + idfa: "4D12078D-3246-4DA4-AD5E-7610481E7AE", // Apple advertising identifier + aaid: "38400000-8cf0-11bd-b23e-10b96e40000d", // Android advertising identifier + md5udid: "5756ae9022b2ea1e47d84fead75220c8", // MD5 hash of the ANDROID_ID + sha1udid: "4DFAA92388699AC6539885AEF1719293879985BF", // SHA1 hash of the ANDROID_ID + windowsadid: "750c6be243f1c4b5c9912b95a5742fc5" // Windows advertising identifier + } + } + } + }] + } +]; +``` diff --git a/test/spec/modules/rakutenBidAdapter_spec.js b/test/spec/modules/rakutenBidAdapter_spec.js new file mode 100644 index 00000000000..2a9fcb9f83b --- /dev/null +++ b/test/spec/modules/rakutenBidAdapter_spec.js @@ -0,0 +1,171 @@ +import { expect } from 'chai' +import { spec } from 'modules/rakutenBidAdapter/index.js' +import { newBidder } from 'src/adapters/bidderFactory.js' +import {config} from '../../../src/config.js'; + +describe('rakutenBidAdapter', function() { + const adapter = newBidder(spec); + const ENDPOINT = 'https://s-bid.rmp.rakuten.com/h'; + let sandbox; + + beforeEach(function() { + config.resetConfig(); + }); + + afterEach(function () { + config.resetConfig(); + }); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }); + + describe('isBidRequestValid', () => { + let bid = { + bidder: 'rakuten', + params: { + adSpotId: '56789' + } + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }); + + it('should return false when required params are not passed', () => { + bid.params.adSpotId = ''; + expect(spec.isBidRequestValid(bid)).to.equal(false) + }); + + it('should return false when required params are not passed', () => { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false) + }) + }); + + describe('buildRequests', () => { + const bidRequests = [ + { + // banner + params: { + adSpotId: '58278' + } + } + ]; + + const bidderRequest = { + bids: bidRequests, + refererInfo: { + referer: 'http://test.com', + stack: ['http://test.com'] + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {}, + gdprApplies: true + }, + uspConsent: '1YN-' + }; + + it('sends bid request to ENDPOINT via GET', () => { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET') + expect(request.data.gdpr).to.equal(1); + expect(request.data.cd).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(request.data.ccpa).to.equal('1YN-'); + }); + + it('allows url override', () => { + config.setConfig({ + rakuten: { + endpoint: '//test.rakuten.com' + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.url).to.equal('//test.rakuten.com'); + }) + }); + + describe('interpretResponse', () => { + const bidRequests = { + banner: { + method: 'GET', + url: '', + data: { + t: '56789', + s: 'https', + ua: + 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Mobile Safari/537.36', + l: 'ja', + d: 'examples.com', + tp: 'https://examples.com/foo/fuga', + pp: 'https://examples.com/hoge/muga' + } + } + }; + + const serverResponse = { + noAd: [], + noAd2: { + requestId: 'biequa9oaph4we' + }, + banner: { + requestId: 'biequa9oaph4we', + cpm: 37.66, + width: 300, + height: 250, + creativeId: 140281, + dealId: 'phoh3pad-ai4ah-xoh7x-ahk7cheasae3oh', + currency: 'USD', + netRevenue: 300, + ttl: 3000, + ad: '' + } + }; + + it('handles nobid responses', () => { + const result = spec.interpretResponse( + { body: serverResponse.noAd }, + bidRequests.banner + ); + expect(result.length).to.equal(0); + + const result2 = spec.interpretResponse( + { body: serverResponse.noAd2 }, + bidRequests.banner + ); + expect(result2.length).to.equal(0); + }) + }); + describe('spec.getUserSyncs', function () { + const syncResponse = [{ + body: { + request_id: 'biequa9oaph4we', + sync_urls: ['https://rdn1.test/sync?uid=9876543210', 'https://rdn2.test/sync?uid=9876543210'] + } + }]; + const nosyncResponse = [{ + body: { + request_id: 'biequa9oaph4we', + sync_urls: [] + } + }]; + let syncOptions + beforeEach(function () { + syncOptions = { + pixelEnabled: true + } + }); + it('sucess usersync url', function () { + const result = []; + result.push({type: 'image', url: 'https://rdn1.test/sync?uid=9876543210'}); + result.push({type: 'image', url: 'https://rdn2.test/sync?uid=9876543210'}); + expect(spec.getUserSyncs(syncOptions, syncResponse)).to.deep.equal(result); + }); + }); +}); diff --git a/test/spec/modules/raveltechBidAdapter_spec.js b/test/spec/modules/raveltechBidAdapter_spec.js new file mode 100644 index 00000000000..6ae3276da5d --- /dev/null +++ b/test/spec/modules/raveltechBidAdapter_spec.js @@ -0,0 +1,155 @@ +/* eslint-disable no-console */ + +import { expect } from 'chai'; +import { spec } from 'modules/raveltechBidAdapter.js'; + +const ENDPOINT = 'https://pb1.rvlproxy.net/bid/bid'; +const RID_LENGTH = 10000; + +describe('RavelTechAdapter', function () { + let bidRequests = [{ + 'bidder': 'raveltech', + 'params': { + 'placementId': 234234 + }, + 'userId': { + 'id5id': { + 'uid': '0', + 'ext': { + 'linkType': 0, + 'pba': 'K2ogG7aaimJB4/PSEuwADQ==' + } + }, + 'pubProvidedId': [ + { + 'source': 'adnxs.com', + 'uids': [ + { + 'id': 'webo-id-1', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + } + ] + }, + { + 'source': 'adnxs.com', + 'uids': [ + { + 'id': 'webo-id-2', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + } + ] + }, + null + ] + }, + 'userIdAsEids': [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': '0', + 'atype': 1, + 'ext': { + 'linkType': 0, + 'pba': 'K2ogG7aaimJB4/PSEuwADQ==' + } + } + ] + }, + { + 'source': 'adnxs.com', + 'uids': [ + { + 'id': 'webo-id-1', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + }, + { + 'id': 'webo-id-2', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + } + ] + } + ], + 'ortb2Imp': { + 'ext': {} + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], + [300, 600], + [728, 90] + ] + } + }, + 'adUnitCode': 'test-div', + 'transactionId': null, + 'adUnitId': 'd6f8ff69-1336-41c3-a639-20b76c6e0be1', + 'sizes': [ + [300, 250], + [300, 600], + [728, 90] + ], + 'bidId': '3da7e5be5cc74', + 'bidderRequestId': '22077672ce0e1f', + 'auctionId': null, + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }]; + + describe('anonymizeBidRequests', function () { + let anonymizedBidRequests; + + beforeEach(function() { + console.log('ravel: beforeEach() eids ', JSON.stringify(bidRequests[0].userIdAsEids)); + anonymizedBidRequests = spec.buildRequests(bidRequests); + if (!Array.isArray(anonymizedBidRequests)) { + anonymizedBidRequests = [anonymizedBidRequests]; + } + }); + + it('should anonymize every id if the source is eligible for anonymization', function() { + anonymizedBidRequests.forEach(bid => { + const eids = bid.data.eids; + + eids.forEach(eid => { + if (eid.source === 'not-eligible-source') { return; } // if the source is not eligible, then go to the next eid + expect(eid.id).to.be.an('array').that.is.empty; + }) + }) + }); + + it('should empty the id if the source is not eligible for anonymization', function() { + anonymizedBidRequests.forEach(bid => { + const eids = bid.data.eids; + + eids.forEach(eid => { + if (eid.source !== 'not-eligible-source') { return; } // if the source is eligible, then go to the next eid + expect(eid.id).to.satisfy(id => id === '' || (Array.isArray(id) && id.length === 0)); + }) + }) + }); + + it('should update the URL of every bid request', function() { + anonymizedBidRequests.forEach(bid => { + expect(bid.url).to.equal(ENDPOINT); + }); + }); + }); +}); + +/* eslint-disable no-console */ From cfacf9ee0ec04cd250bbee21df18960fbbedf4d2 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Wed, 5 Mar 2025 17:25:15 +0100 Subject: [PATCH 17/25] remove the raveltechBidAdapter as we're now going with the RTD approach as advised in the last pull request --- modules/raveltechBidAdapter.js | 72 -------- modules/raveltechBidAdapter.md | 150 ----------------- test/spec/modules/raveltechBidAdapter_spec.js | 155 ------------------ 3 files changed, 377 deletions(-) delete mode 100644 modules/raveltechBidAdapter.js delete mode 100644 modules/raveltechBidAdapter.md delete mode 100644 test/spec/modules/raveltechBidAdapter_spec.js diff --git a/modules/raveltechBidAdapter.js b/modules/raveltechBidAdapter.js deleted file mode 100644 index 0f6ac31c39f..00000000000 --- a/modules/raveltechBidAdapter.js +++ /dev/null @@ -1,72 +0,0 @@ -// Import the base adapter -import { spec as baseAdapter } from './appnexusBidAdapter.js'; // eslint-disable-line prebid/validate-imports -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { logInfo } from '../src/utils.js'; -import { BidRequest, Bid } from '../src/bidfactory.js'; - -const BIDDER_CODE = 'raveltech'; -const URL = 'https://pb1.rvlproxy.net/bid/bid'; - -export const spec = { - code: BIDDER_CODE, - gvlid: baseAdapter.GVLID, // use base adapter gvlid - - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @return ServerRequest[] Info describing the requests to the server. - */ - buildRequests: function(bidRequests, bidderRequest) { - if (!baseAdapter.buildRequests) { return []; } - - let anonymizedBidRequests = baseAdapter.buildRequests(bidRequests, bidderRequest); // call the bid requests from the Appnexus adapter - if (!anonymizedBidRequests) { return []; } // if no bid request, return empty Array - if (!Array.isArray(anonymizedBidRequests)) { anonymizedBidRequests = [anonymizedBidRequests]; } // if only 1 bid request, anonymizedBidRequest will be an Object instead of an Array. Build Array with 1 bid request. - - // Load ZKAD runtime, used to anonymize the uids - const ZKAD = window.ZKAD || { anonymizeID(v, p) { return []; } }; - logInfo('ZKAD.ready=', ZKAD.ready); - - anonymizedBidRequests.forEach(bid => { - bid.url = URL; - bid.data = JSON.parse(bid.data); - - let eids = bid.data.eids; - if (!eids) { return; } - - eids.forEach(eid => { - if (!eid || !eid.id) { return; } - logInfo('eid.source=', eid.source); - eid.id = ZKAD.anonymizeID(eid.id, eid.source); - logInfo('Anonymized as byte array of length=', eid.id.length); - }); - }); - - return anonymizedBidRequests; - }, - - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function (bid) { - if (!baseAdapter.isBidRequestValid) { return false; } - return baseAdapter.isBidRequestValid(bid); - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, params) { - if (!baseAdapter.interpretResponse) { return []; } - return baseAdapter.interpretResponse(serverResponse, params); - } -}; - -registerBidder(spec); diff --git a/modules/raveltechBidAdapter.md b/modules/raveltechBidAdapter.md deleted file mode 100644 index e3919f24489..00000000000 --- a/modules/raveltechBidAdapter.md +++ /dev/null @@ -1,150 +0,0 @@ -# Overview - -``` -Module Name: Ravel Bid Adapter -Module Type: Bidder Adapter -Maintainer: maintainers@raveltech.io -``` - -# Description - -Connects to Appnexus exchange through encrypted UIDs called Ravel Identifier (RIDs). - - -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [{ - bidder: 'raveltech', - params: { - placementId: 13144370 - } - }] - }, - // Native adUnit - { - code: 'native-div', - sizes: [[1, 1]], - mediaTypes: { - native: { - title: { - required: true - }, - body: { - required: true - }, - image: { - required: true - }, - sponsoredBy: { - required: true - }, - icon: { - required: false - } - } - }, - bids: [{ - bidder: 'raveltech', - params: { - placementId: 13232354, - allowSmallerSizes: true - } - }] - }, - // Video instream adUnit - { - code: 'video-instream', - sizes: [[640, 480]], - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'instream' - }, - }, - bids: [{ - bidder: 'raveltech', - params: { - placementId: 13232361, - video: { - skippable: true, - playback_methods: ['auto_play_sound_off'] - } - } - }] - }, - // Video outstream adUnit - { - code: 'video-outstream', - sizes: [[300, 250]], - mediaTypes: { - video: { - playerSize: [[300, 250]], - context: 'outstream', - // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. - // To note - appnexus supports additional values for our system that are not part of the ORTB spec. If you want - // to use these values, they will have to be declared in the bids[].params.video object instead using the appnexus syntax. - // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will - // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. - minduration: 1, - maxduration: 60, - skip: 0, // 1 - true, 0 - false - skipafter: 5, - playbackmethod: [2], // note - we only support options 1-4 at this time - api: [1,2,3] // note - option 6 is not supported at this time - } - }, - bids: [ - { - bidder: 'raveltech', - params: { - placementId: 13232385, - video: { - skippable: true, - playback_method: 'auto_play_sound_off' - } - } - } - ] - }, - // Banner adUnit in a App Webview - // Only use this for situations where prebid.js is in a webview of an App - // See Prebid Mobile for displaying ads via an SDK - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - } - bids: [{ - bidder: 'raveltech', - params: { - placementId: 13144370, - app: { - id: "B1O2W3M4AN.com.prebid.webview", - geo: { - lat: 40.0964439, - lng: -75.3009142 - }, - device_id: { - idfa: "4D12078D-3246-4DA4-AD5E-7610481E7AE", // Apple advertising identifier - aaid: "38400000-8cf0-11bd-b23e-10b96e40000d", // Android advertising identifier - md5udid: "5756ae9022b2ea1e47d84fead75220c8", // MD5 hash of the ANDROID_ID - sha1udid: "4DFAA92388699AC6539885AEF1719293879985BF", // SHA1 hash of the ANDROID_ID - windowsadid: "750c6be243f1c4b5c9912b95a5742fc5" // Windows advertising identifier - } - } - } - }] - } -]; -``` diff --git a/test/spec/modules/raveltechBidAdapter_spec.js b/test/spec/modules/raveltechBidAdapter_spec.js deleted file mode 100644 index 6ae3276da5d..00000000000 --- a/test/spec/modules/raveltechBidAdapter_spec.js +++ /dev/null @@ -1,155 +0,0 @@ -/* eslint-disable no-console */ - -import { expect } from 'chai'; -import { spec } from 'modules/raveltechBidAdapter.js'; - -const ENDPOINT = 'https://pb1.rvlproxy.net/bid/bid'; -const RID_LENGTH = 10000; - -describe('RavelTechAdapter', function () { - let bidRequests = [{ - 'bidder': 'raveltech', - 'params': { - 'placementId': 234234 - }, - 'userId': { - 'id5id': { - 'uid': '0', - 'ext': { - 'linkType': 0, - 'pba': 'K2ogG7aaimJB4/PSEuwADQ==' - } - }, - 'pubProvidedId': [ - { - 'source': 'adnxs.com', - 'uids': [ - { - 'id': 'webo-id-1', - 'atype': 1, - 'ext': { - 'stype': 'ppuid' - } - } - ] - }, - { - 'source': 'adnxs.com', - 'uids': [ - { - 'id': 'webo-id-2', - 'atype': 1, - 'ext': { - 'stype': 'ppuid' - } - } - ] - }, - null - ] - }, - 'userIdAsEids': [ - { - 'source': 'id5-sync.com', - 'uids': [ - { - 'id': '0', - 'atype': 1, - 'ext': { - 'linkType': 0, - 'pba': 'K2ogG7aaimJB4/PSEuwADQ==' - } - } - ] - }, - { - 'source': 'adnxs.com', - 'uids': [ - { - 'id': 'webo-id-1', - 'atype': 1, - 'ext': { - 'stype': 'ppuid' - } - }, - { - 'id': 'webo-id-2', - 'atype': 1, - 'ext': { - 'stype': 'ppuid' - } - } - ] - } - ], - 'ortb2Imp': { - 'ext': {} - }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [300, 250], - [300, 600], - [728, 90] - ] - } - }, - 'adUnitCode': 'test-div', - 'transactionId': null, - 'adUnitId': 'd6f8ff69-1336-41c3-a639-20b76c6e0be1', - 'sizes': [ - [300, 250], - [300, 600], - [728, 90] - ], - 'bidId': '3da7e5be5cc74', - 'bidderRequestId': '22077672ce0e1f', - 'auctionId': null, - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }]; - - describe('anonymizeBidRequests', function () { - let anonymizedBidRequests; - - beforeEach(function() { - console.log('ravel: beforeEach() eids ', JSON.stringify(bidRequests[0].userIdAsEids)); - anonymizedBidRequests = spec.buildRequests(bidRequests); - if (!Array.isArray(anonymizedBidRequests)) { - anonymizedBidRequests = [anonymizedBidRequests]; - } - }); - - it('should anonymize every id if the source is eligible for anonymization', function() { - anonymizedBidRequests.forEach(bid => { - const eids = bid.data.eids; - - eids.forEach(eid => { - if (eid.source === 'not-eligible-source') { return; } // if the source is not eligible, then go to the next eid - expect(eid.id).to.be.an('array').that.is.empty; - }) - }) - }); - - it('should empty the id if the source is not eligible for anonymization', function() { - anonymizedBidRequests.forEach(bid => { - const eids = bid.data.eids; - - eids.forEach(eid => { - if (eid.source !== 'not-eligible-source') { return; } // if the source is eligible, then go to the next eid - expect(eid.id).to.satisfy(id => id === '' || (Array.isArray(id) && id.length === 0)); - }) - }) - }); - - it('should update the URL of every bid request', function() { - anonymizedBidRequests.forEach(bid => { - expect(bid.url).to.equal(ENDPOINT); - }); - }); - }); -}); - -/* eslint-disable no-console */ From aeace1e16f8597ca266658bb8744259f7ab4ea0d Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Thu, 6 Mar 2025 08:44:46 +0100 Subject: [PATCH 18/25] fix for "72:7 error Expected an assignment or function call and instead saw an expression no-unused-expressions" found during ci/cd pipeline tests --- modules/raveltechRtdProvider.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/raveltechRtdProvider.js b/modules/raveltechRtdProvider.js index 706fc027a68..5ece495aaf8 100644 --- a/modules/raveltechRtdProvider.js +++ b/modules/raveltechRtdProvider.js @@ -69,12 +69,14 @@ const wrapBuildRequests = (aliasName, preserveOriginalBid, buildRequests) => { if (!isArray(ravelRequests) && ravelRequests) { ravelRequests = [ ravelRequests ]; } - ravelRequests?.forEach(request => { - // Proxyfy request - request.url = RAVEL_ENDPOINT; - request.method = 'POST'; - addRavelDataToRequest(request, adapterName); - }) + if (ravelRequests) { + ravelRequests.forEach(request => { + // Proxyfy request + request.url = RAVEL_ENDPOINT; + request.method = 'POST'; + addRavelDataToRequest(request, adapterName); + }) + } return [ ...requests ?? [], ...ravelRequests ?? [] ]; } catch (e) { @@ -120,4 +122,4 @@ export const raveltechSubmodule = { }; // Register raveltechSubmodule as submodule of realTimeData -submodule('realTimeData', raveltechSubmodule); +submodule('realTimeData', raveltechSubmodule); \ No newline at end of file From f43534e1a7cd243d21c0d1accdb696f7f6768945 Mon Sep 17 00:00:00 2001 From: mnguyen-raveltech Date: Thu, 6 Mar 2025 08:57:54 +0100 Subject: [PATCH 19/25] add missing blank line at the end of the file --- modules/raveltechRtdProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/raveltechRtdProvider.js b/modules/raveltechRtdProvider.js index 5ece495aaf8..614fc7729c8 100644 --- a/modules/raveltechRtdProvider.js +++ b/modules/raveltechRtdProvider.js @@ -122,4 +122,4 @@ export const raveltechSubmodule = { }; // Register raveltechSubmodule as submodule of realTimeData -submodule('realTimeData', raveltechSubmodule); \ No newline at end of file +submodule('realTimeData', raveltechSubmodule); From 6589a94bf14389b889ee58bcab6450eceb290358 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Thu, 13 Mar 2025 15:17:43 +0100 Subject: [PATCH 20/25] prevents bid requests to be sent whenever zkad.js is not loaded --- modules/raveltechRtdProvider.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/raveltechRtdProvider.js b/modules/raveltechRtdProvider.js index 614fc7729c8..f8079ac59e9 100644 --- a/modules/raveltechRtdProvider.js +++ b/modules/raveltechRtdProvider.js @@ -49,6 +49,9 @@ const wrapBuildRequests = (aliasName, preserveOriginalBid, buildRequests) => { const adapterName = getAdapterNameForAlias(aliasName) return (validBidRequests, ...rest) => { + if (!window.ZKAD || !window.ZKAD.ready) { + return buildRequests(validBidRequests, ...rest); + } let requests = preserveOriginalBid ? buildRequests(validBidRequests, ...rest) : []; if (!isArray(requests)) { requests = [ requests ]; From 2d4443fc7fdb25385ad79dfcbc7f52ae61b7405c Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Thu, 13 Mar 2025 15:21:13 +0100 Subject: [PATCH 21/25] add log whenever there's an error in uid anonymization --- modules/raveltechRtdProvider.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/raveltechRtdProvider.js b/modules/raveltechRtdProvider.js index f8079ac59e9..1d0b8ea42ab 100644 --- a/modules/raveltechRtdProvider.js +++ b/modules/raveltechRtdProvider.js @@ -19,7 +19,10 @@ const getAnonymizedEids = (eids) => { eid.uids = eid.uids.flatMap(uid => { if (!uid || !uid.id) { return []; } const id = ZKAD.anonymizeID(uid.id, eid.source); - if (!id) { return []; } + if (!id) { + logError('Error while anonymizing uid :', eid, uid); + return []; + } logInfo('Anonymized as byte array of length=', id.length); return [ { ...uid, From 66ec3f8612fdb88bc5e442481a3429e27a7c1129 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Thu, 13 Mar 2025 15:45:28 +0100 Subject: [PATCH 22/25] update markdown with instructions on how to load zkad.js --- modules/raveltechRtdProvider.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/raveltechRtdProvider.md b/modules/raveltechRtdProvider.md index 55302b36021..60aa4273047 100644 --- a/modules/raveltechRtdProvider.md +++ b/modules/raveltechRtdProvider.md @@ -19,6 +19,10 @@ The module operates in two modes: ## Configuration To enable the Raveltech RTD module, you need to configure it with a list of bidders and specify whether to preserve the original bid request. +For the anonymization feature to work, you also need to load a javascript in the header of your HTML page: +```html + +``` ### Build ``` From 899b2587f51b7237cec5dca647e8d6c7641660d6 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Thu, 13 Mar 2025 15:52:22 +0100 Subject: [PATCH 23/25] fix lint error with trailing space --- modules/raveltechRtdProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/raveltechRtdProvider.js b/modules/raveltechRtdProvider.js index 1d0b8ea42ab..adac49c3258 100644 --- a/modules/raveltechRtdProvider.js +++ b/modules/raveltechRtdProvider.js @@ -19,7 +19,7 @@ const getAnonymizedEids = (eids) => { eid.uids = eid.uids.flatMap(uid => { if (!uid || !uid.id) { return []; } const id = ZKAD.anonymizeID(uid.id, eid.source); - if (!id) { + if (!id) { logError('Error while anonymizing uid :', eid, uid); return []; } From 32646c4ada04369b7f6e449339ec4d4a1eec05f2 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Wed, 16 Apr 2025 18:22:07 +0200 Subject: [PATCH 24/25] fix unit tests --- test/spec/modules/raveltechRtdProvider_spec.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test/spec/modules/raveltechRtdProvider_spec.js b/test/spec/modules/raveltechRtdProvider_spec.js index fe96a76d6c5..5adaf287bed 100644 --- a/test/spec/modules/raveltechRtdProvider_spec.js +++ b/test/spec/modules/raveltechRtdProvider_spec.js @@ -74,25 +74,22 @@ describe('raveltechRtdProvider', () => { expect(fakeAjax.getCall(0).args[2]).not.to.contain('"pbjsAdapter":"test"'); }) - it('remove uids when ZKAD unavailable', () => { + it('do not call ravel when ZKAD unavailable', () => { adapterManager.getBidAdapter('alias1').callBids({ auctionId: '123', bidderCode: 'test', bidderRequestId: 'abc', bids: [ { ...fakeBidReq, bidder: 'test' } ] }, sinon.stub(), sinon.stub(), fakeAjax, sinon.stub(), sinon.stub()); - expect(fakeAjax.calledTwice).to.be.true; + expect(fakeAjax.calledOnce).to.be.true; expect(fakeZkad.called).to.be.false; - expect(fakeBuildRequests.calledTwice).to.be.true; + expect(fakeBuildRequests.calledOnce).to.be.true; expect(fakeAjax.getCall(0).args[2]).to.contain('"id":"testid123"'); - expect(fakeAjax.getCall(1).args[2]).not.to.contain('"id":"testid123"'); - expect(fakeAjax.getCall(1).args[2]).not.to.contain('"id":'); expect(fakeAjax.getCall(0).args[2]).not.to.contain('"pbjsAdapter":"test"'); - expect(fakeAjax.getCall(1).args[2]).to.contain('"pbjsAdapter":"test"'); }) it('successfully replace uids with ZKAD', () => { - window.ZKAD = { anonymizeID: fakeZkad }; + window.ZKAD = { anonymizeID: fakeZkad, ready: true }; adapterManager.getBidAdapter('alias1').callBids({ auctionId: '123', bidderCode: 'test', From 40dc7c470249a095efc733e82267eb5488d57439 Mon Sep 17 00:00:00 2001 From: Michel Nguyen Date: Thu, 17 Apr 2025 15:56:13 +0200 Subject: [PATCH 25/25] usage example for the raveltechRtdProvider module --- .../gpt/raveltechRtdProvider_example.html | 379 ++++++++++++++++++ 1 file changed, 379 insertions(+) create mode 100644 integrationExamples/gpt/raveltechRtdProvider_example.html diff --git a/integrationExamples/gpt/raveltechRtdProvider_example.html b/integrationExamples/gpt/raveltechRtdProvider_example.html new file mode 100644 index 00000000000..97ff28d0ea6 --- /dev/null +++ b/integrationExamples/gpt/raveltechRtdProvider_example.html @@ -0,0 +1,379 @@ + + + + + + + + + + + + + + User ID Modules Example + + + + + + + + + + + + + +

User ID Modules Example

+ +

Generated EIDs

+ +

+
+  

Ad Slot

+
+ +
+ + +