diff --git a/services/ad-server/src/public/js/ad-server-lib.js b/services/ad-server/src/public/js/ad-server-lib.js index 2ca534fb..c6a396af 100644 --- a/services/ad-server/src/public/js/ad-server-lib.js +++ b/services/ad-server/src/public/js/ad-server-lib.js @@ -192,7 +192,7 @@ class AdServerLib { paragraphEl.className = 'font-mono text-sm'; document.getElementById(divId).appendChild(paragraphEl); - if (type === 'image') { + if (type === 'image' || type === 'multi-piece') { [containerFrameEl.width, containerFrameEl.height] = size; } else if (type === 'video') { // The video creative does not actually render anything, and it only contains diff --git a/services/ad-server/src/public/js/decision-logic.js b/services/ad-server/src/public/js/decision-logic.js index 061080ae..d6f3f995 100644 --- a/services/ad-server/src/public/js/decision-logic.js +++ b/services/ad-server/src/public/js/decision-logic.js @@ -26,7 +26,10 @@ function scoreAd( // If it's a video ad, there is no contextual video auction in this demo to compare // the bid against, so we just return the desirability score as the bid itself, // and the highest bid will win the auction - if (auctionConfig.sellerSignals.adType === 'video') { + if ( + auctionConfig.sellerSignals.adType === 'video' || + auctionConfig.sellerSignals.adType === 'multi-piece' + ) { desirability = bid; } else { // For an image ad, we compare the PA auction bid against the bid floor set by the diff --git a/services/dsp-a/src/index.ts b/services/dsp-a/src/index.ts index b48deb94..d7f9cfc6 100644 --- a/services/dsp-a/src/index.ts +++ b/services/dsp-a/src/index.ts @@ -80,6 +80,14 @@ app.use((req, res, next) => { next(); }); +app.use((req, res, next) => { + // opt-in fencedframe + if (req.get('sec-fetch-dest') === 'fencedframe') { + res.setHeader('Supports-Loading-Mode', 'fenced-frame'); + } + next(); +}); + app.use( express.static('src/public', { setHeaders: (res: Response, path, stat) => { @@ -94,14 +102,6 @@ app.use( }), ); -app.use((req, res, next) => { - // opt-in fencedframe - if (req.get('sec-fetch-dest') === 'fencedframe') { - res.setHeader('Supports-Loading-Mode', 'fenced-frame'); - } - next(); -}); - app.set('view engine', 'ejs'); app.set('views', 'src/views'); @@ -144,6 +144,25 @@ app.get('/interest-group.json', async (req: Request, res: Response) => { imageCreative.searchParams.append('advertiser', advertiser as string); imageCreative.searchParams.append('id', id as string); + const multiPieceCreativeContainer = new URL( + `https://${DSP_A_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/container.html`, + ); + const multiPieceCreativeComponentA = new URL( + `https://${DSP_A_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/component-a.html`, + ); + const multiPieceCreativeComponentB = new URL( + `https://${DSP_A_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/component-b.html`, + ); + const multiPieceCreativeComponentC = new URL( + `https://${DSP_A_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/component-c.html`, + ); + const multiPieceCreativeComponentD = new URL( + `https://${DSP_A_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/component-d.html`, + ); + const multiPieceCreativeComponentE = new URL( + `https://${DSP_A_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/component-e.html`, + ); + const videoCreativeForSspA = new URL( `https://${DSP_A_HOST}:${EXTERNAL_PORT}/html/video-ad-creative.html?sspVastUrl=${SSP_A_VAST_URL}`, ); @@ -207,6 +226,19 @@ app.get('/interest-group.json', async (req: Request, res: Response) => { seller: SSP_B, }, }, + { + renderUrl: multiPieceCreativeContainer, + metadata: { + adType: 'multi-piece', + }, + }, + ], + adComponents: [ + {renderUrl: multiPieceCreativeComponentA}, + {renderUrl: multiPieceCreativeComponentB}, + {renderUrl: multiPieceCreativeComponentC}, + {renderUrl: multiPieceCreativeComponentD}, + {renderUrl: multiPieceCreativeComponentE}, ], }); }); diff --git a/services/dsp-a/src/public/html/multi-piece-ad/component-a.html b/services/dsp-a/src/public/html/multi-piece-ad/component-a.html new file mode 100644 index 00000000..1843987c --- /dev/null +++ b/services/dsp-a/src/public/html/multi-piece-ad/component-a.html @@ -0,0 +1,21 @@ + + + + + + DSP-A component ad A + + + + + A + + diff --git a/services/dsp-a/src/public/html/multi-piece-ad/component-b.html b/services/dsp-a/src/public/html/multi-piece-ad/component-b.html new file mode 100644 index 00000000..0eeb8559 --- /dev/null +++ b/services/dsp-a/src/public/html/multi-piece-ad/component-b.html @@ -0,0 +1,21 @@ + + + + + + DSP-A component ad B + + + + + B + + diff --git a/services/dsp-a/src/public/html/multi-piece-ad/component-c.html b/services/dsp-a/src/public/html/multi-piece-ad/component-c.html new file mode 100644 index 00000000..1b1b21c7 --- /dev/null +++ b/services/dsp-a/src/public/html/multi-piece-ad/component-c.html @@ -0,0 +1,21 @@ + + + + + + DSP-A component ad C + + + + + C + + diff --git a/services/dsp-a/src/public/html/multi-piece-ad/component-d.html b/services/dsp-a/src/public/html/multi-piece-ad/component-d.html new file mode 100644 index 00000000..d67fbc0a --- /dev/null +++ b/services/dsp-a/src/public/html/multi-piece-ad/component-d.html @@ -0,0 +1,21 @@ + + + + + + DSP-A component ad D + + + + + D + + diff --git a/services/dsp-a/src/public/html/multi-piece-ad/component-e.html b/services/dsp-a/src/public/html/multi-piece-ad/component-e.html new file mode 100644 index 00000000..2e26ff53 --- /dev/null +++ b/services/dsp-a/src/public/html/multi-piece-ad/component-e.html @@ -0,0 +1,21 @@ + + + + + + DSP-A component ad E + + + + + E + + diff --git a/services/dsp-a/src/public/html/multi-piece-ad/container.html b/services/dsp-a/src/public/html/multi-piece-ad/container.html new file mode 100644 index 00000000..42fc3933 --- /dev/null +++ b/services/dsp-a/src/public/html/multi-piece-ad/container.html @@ -0,0 +1,89 @@ + + + + + + DSP-A ad container + + + + +
+
DSP-A multi-piece ad
+
+

Fenced frame components

+
+

Iframe components

+
+ + + diff --git a/services/dsp-a/src/public/js/bidding-logic.js b/services/dsp-a/src/public/js/bidding-logic.js index 1a67b6a8..43dfead0 100644 --- a/services/dsp-a/src/public/js/bidding-logic.js +++ b/services/dsp-a/src/public/js/bidding-logic.js @@ -14,6 +14,10 @@ limitations under the License. */ +const IMAGE_AD_TYPE = 'image'; +const VIDEO_AD_TYPE = 'video'; +const MULTI_PIECE_AD_TYPE = 'multi-piece'; + function generateBid( interestGroup, auctionSignals, @@ -21,31 +25,40 @@ function generateBid( trustedBiddingSignals, browserSignals, ) { - const {ads} = interestGroup; + const {ads, adComponents} = interestGroup; const {adType} = auctionSignals; const {seller, topLevelSeller} = browserSignals; let render; - if (adType === 'image') { - render = ads.find((ad) => ad.metadata.adType === 'image')?.renderUrl; - } else if (adType === 'video') { - // We look through the video ads passed in from the interest group and - // select the ad that matches the component seller's origin - render = ads.find( - (ad) => - ad.metadata.adType === 'video' && ad.metadata.seller.includes(seller), - ).renderUrl; + switch (adType) { + case IMAGE_AD_TYPE: + render = ads.find( + ({metadata}) => metadata.adType === IMAGE_AD_TYPE, + )?.renderUrl; + break; + case VIDEO_AD_TYPE: + // We look through the video ads passed in from the interest group and + // select the ad that matches the component seller's origin + render = ads.find( + ({metadata}) => + metadata.adType === VIDEO_AD_TYPE && metadata.seller.includes(seller), + ).renderUrl; + break; + case MULTI_PIECE_AD_TYPE: + render = ads.find( + ({metadata}) => metadata.adType === MULTI_PIECE_AD_TYPE, + ).renderUrl; + break; } - const response = { + return { // We return a random bid of 0 to 100 bid: Math.floor(Math.random() * 100, 10), render, + adComponents: adComponents.map(({renderUrl}) => ({url: renderUrl})), allowComponentAuction: !!topLevelSeller, }; - - return response; } function reportWin( diff --git a/services/dsp-b/src/index.ts b/services/dsp-b/src/index.ts index c5bc829e..395f8d6c 100644 --- a/services/dsp-b/src/index.ts +++ b/services/dsp-b/src/index.ts @@ -80,6 +80,14 @@ app.use((req, res, next) => { next(); }); +app.use((req, res, next) => { + // opt-in fencedframe + if (req.get('sec-fetch-dest') === 'fencedframe') { + res.setHeader('Supports-Loading-Mode', 'fenced-frame'); + } + next(); +}); + app.use( express.static('src/public', { setHeaders: (res: Response, path, stat) => { @@ -94,14 +102,6 @@ app.use( }), ); -app.use((req, res, next) => { - // opt-in fencedframe - if (req.get('sec-fetch-dest') === 'fencedframe') { - res.setHeader('Supports-Loading-Mode', 'fenced-frame'); - } - next(); -}); - app.set('view engine', 'ejs'); app.set('views', 'src/views'); @@ -144,6 +144,25 @@ app.get('/interest-group.json', async (req: Request, res: Response) => { imageCreative.searchParams.append('advertiser', advertiser as string); imageCreative.searchParams.append('id', id as string); + const multiPieceCreativeContainer = new URL( + `https://${DSP_B_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/container.html`, + ); + const multiPieceCreativeComponentA = new URL( + `https://${DSP_B_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/component-1.html`, + ); + const multiPieceCreativeComponentB = new URL( + `https://${DSP_B_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/component-2.html`, + ); + const multiPieceCreativeComponentC = new URL( + `https://${DSP_B_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/component-3.html`, + ); + const multiPieceCreativeComponentD = new URL( + `https://${DSP_B_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/component-4.html`, + ); + const multiPieceCreativeComponentE = new URL( + `https://${DSP_B_HOST}:${EXTERNAL_PORT}/html/multi-piece-ad/component-5.html`, + ); + const videoCreativeForSspA = new URL( `https://${DSP_B_HOST}:${EXTERNAL_PORT}/html/video-ad-creative.html?sspVastUrl=${SSP_A_VAST_URL}`, ); @@ -207,6 +226,19 @@ app.get('/interest-group.json', async (req: Request, res: Response) => { seller: SSP_B, }, }, + { + renderUrl: multiPieceCreativeContainer, + metadata: { + adType: 'multi-piece', + }, + }, + ], + adComponents: [ + {renderUrl: multiPieceCreativeComponentA}, + {renderUrl: multiPieceCreativeComponentB}, + {renderUrl: multiPieceCreativeComponentC}, + {renderUrl: multiPieceCreativeComponentD}, + {renderUrl: multiPieceCreativeComponentE}, ], }); }); diff --git a/services/dsp-b/src/public/html/multi-piece-ad/component-1.html b/services/dsp-b/src/public/html/multi-piece-ad/component-1.html new file mode 100644 index 00000000..471bca71 --- /dev/null +++ b/services/dsp-b/src/public/html/multi-piece-ad/component-1.html @@ -0,0 +1,21 @@ + + + + + + DSP-B component ad 1 + + + + + 1 + + diff --git a/services/dsp-b/src/public/html/multi-piece-ad/component-2.html b/services/dsp-b/src/public/html/multi-piece-ad/component-2.html new file mode 100644 index 00000000..3d8ba0e6 --- /dev/null +++ b/services/dsp-b/src/public/html/multi-piece-ad/component-2.html @@ -0,0 +1,21 @@ + + + + + + DSP-B component ad 2 + + + + + 2 + + diff --git a/services/dsp-b/src/public/html/multi-piece-ad/component-3.html b/services/dsp-b/src/public/html/multi-piece-ad/component-3.html new file mode 100644 index 00000000..cf077b93 --- /dev/null +++ b/services/dsp-b/src/public/html/multi-piece-ad/component-3.html @@ -0,0 +1,21 @@ + + + + + + DSP-B component ad 3 + + + + + 3 + + diff --git a/services/dsp-b/src/public/html/multi-piece-ad/component-4.html b/services/dsp-b/src/public/html/multi-piece-ad/component-4.html new file mode 100644 index 00000000..9a9f81d1 --- /dev/null +++ b/services/dsp-b/src/public/html/multi-piece-ad/component-4.html @@ -0,0 +1,21 @@ + + + + + + DSP-B component ad 4 + + + + + 4 + + diff --git a/services/dsp-b/src/public/html/multi-piece-ad/component-5.html b/services/dsp-b/src/public/html/multi-piece-ad/component-5.html new file mode 100644 index 00000000..85f8d4c0 --- /dev/null +++ b/services/dsp-b/src/public/html/multi-piece-ad/component-5.html @@ -0,0 +1,21 @@ + + + + + + DSP-B component ad 5 + + + + + 5 + + diff --git a/services/dsp-b/src/public/html/multi-piece-ad/container.html b/services/dsp-b/src/public/html/multi-piece-ad/container.html new file mode 100644 index 00000000..4a8cee56 --- /dev/null +++ b/services/dsp-b/src/public/html/multi-piece-ad/container.html @@ -0,0 +1,92 @@ + + + + + + DSP-B ad container + + + + +
+
DSP-B multi-piece ad
+
+

Fenced frame components

+
+
+

Iframe components

+
+
+ + + diff --git a/services/dsp-b/src/public/js/bidding-logic.js b/services/dsp-b/src/public/js/bidding-logic.js index 1a67b6a8..43dfead0 100644 --- a/services/dsp-b/src/public/js/bidding-logic.js +++ b/services/dsp-b/src/public/js/bidding-logic.js @@ -14,6 +14,10 @@ limitations under the License. */ +const IMAGE_AD_TYPE = 'image'; +const VIDEO_AD_TYPE = 'video'; +const MULTI_PIECE_AD_TYPE = 'multi-piece'; + function generateBid( interestGroup, auctionSignals, @@ -21,31 +25,40 @@ function generateBid( trustedBiddingSignals, browserSignals, ) { - const {ads} = interestGroup; + const {ads, adComponents} = interestGroup; const {adType} = auctionSignals; const {seller, topLevelSeller} = browserSignals; let render; - if (adType === 'image') { - render = ads.find((ad) => ad.metadata.adType === 'image')?.renderUrl; - } else if (adType === 'video') { - // We look through the video ads passed in from the interest group and - // select the ad that matches the component seller's origin - render = ads.find( - (ad) => - ad.metadata.adType === 'video' && ad.metadata.seller.includes(seller), - ).renderUrl; + switch (adType) { + case IMAGE_AD_TYPE: + render = ads.find( + ({metadata}) => metadata.adType === IMAGE_AD_TYPE, + )?.renderUrl; + break; + case VIDEO_AD_TYPE: + // We look through the video ads passed in from the interest group and + // select the ad that matches the component seller's origin + render = ads.find( + ({metadata}) => + metadata.adType === VIDEO_AD_TYPE && metadata.seller.includes(seller), + ).renderUrl; + break; + case MULTI_PIECE_AD_TYPE: + render = ads.find( + ({metadata}) => metadata.adType === MULTI_PIECE_AD_TYPE, + ).renderUrl; + break; } - const response = { + return { // We return a random bid of 0 to 100 bid: Math.floor(Math.random() * 100, 10), render, + adComponents: adComponents.map(({renderUrl}) => ({url: renderUrl})), allowComponentAuction: !!topLevelSeller, }; - - return response; } function reportWin( diff --git a/services/news/src/views/index.ejs b/services/news/src/views/index.ejs index 1499a141..c790fd82 100644 --- a/services/news/src/views/index.ejs +++ b/services/news/src/views/index.ejs @@ -56,6 +56,10 @@

<%= lorem %>

+
+
+
+

<%= lorem %>

@@ -165,6 +169,7 @@ size: [300, 250], isFencedFrame: true } + const videoIframeAdUnit = { divId: 'video-ad--iframe', type: 'video', @@ -172,10 +177,17 @@ isFencedFrame: false } + const multiPieceFencedFrameAdUnit = { + divId: 'image-ad__multi-piece--fenced-frame', + type: 'multi-piece', + size: [300, 250], + isFencedFrame: true + } // Start the auction for each ad unit startSequentialAuction(imageIframeAdUnit, sellers) startSequentialAuction(imageFencedFrameAdUnit, sellers) startSequentialAuction(videoIframeAdUnit, sellers) + startSequentialAuction(multiPieceFencedFrameAdUnit, sellers) } async function setupDemo () {