Skip to content

Commit

Permalink
Merge pull request #458 from privacysandbox/dev
Browse files Browse the repository at this point in the history
Absorb the latest changes from dev
  • Loading branch information
siddharth-sahoo authored Nov 5, 2024
2 parents 9a1f307 + e7e49ea commit 2ef5df9
Show file tree
Hide file tree
Showing 16 changed files with 787 additions and 242 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Privacy Sandbox Demos

A use case demo library for [Privacy Sandbox APIs](https://developer.chrome.com/en/docs/privacy-sandbox/) on the web.
A use case demo library for the [Privacy Sandbox APIs](https://developer.chrome.com/en/docs/privacy-sandbox/) on the web.

## Motivation

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"docker": "docker-compose build && docker-compose up",
"deploy": "./scripts/cloudrun_deploy.sh && ./scripts/firebase_deploy.sh",
"clean": "docker-compose rm -f && docker volume prune -f && docker image prune -f && docker rmi -f $(docker images -q)",
"fmt": "prettier --write ."
"fmt": "pre-commit run --all-files"
},
"devDependencies": {
"firebase-tools": "^11.23.0",
Expand Down
2 changes: 1 addition & 1 deletion services/ad-tech/src/controllers/key-value-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class KeyValueStore {
defaultValues: string[][] = [],
resetIntervalInMins: number = 30,
) {
console.log('Initializing in-memory key value store.');
console.log('Initializing in-memory key value store.', {defaultValues});
this.defaultData = defaultValues;
this.rewriteDefaults();
setInterval(this.rewriteDefaults, 1000 * 60 * resetIntervalInMins);
Expand Down
32 changes: 29 additions & 3 deletions services/ad-tech/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,43 @@ export const CURRENT_ORIGIN = new URL(
).toString();

// ****************************************************************************
// CONTEXTUAL AUCTION METADATA
// BIDDING SIGNALS - CONTEXTUAL AUCTIONS
// ****************************************************************************
// NOTE: These bidding signals are only used on the server-side to respond to a
// contextual bid request from the browser or server-to-server.
/**
* Name of the contextual advertiser.
* This is used in the buyer's bid response to contextual bid requests.
*/
export const ADVERTISER_CONTEXTUAL = 'ContextNext';
/** Max bid CPM for contextual auctions. */
export const MAX_CONTEXTUAL_BID = 1.5;
export const CONTEXTUAL_BID_MAX = 1.5;
/** Min bid CPM for contextual auctions. */
export const MIN_CONTEXTUAL_BID = 0.5;
export const CONTEXTUAL_BID_MIN = 0.5;

// ****************************************************************************
// BIDDING SIGNALS - PROTECTED AUDIENCE AUCTIONS
// ****************************************************************************
// NOTE: These bidding signals are stored in the BYOS Key-Value Server, where
// values are stored as strings.
/** The deafult bidding signals for the BYOS Key-Value Serice. */
export const BIDDING_SIGNALS_DEFAULT = [
// Whether the campaign is active.
['isActive', 'true'],
// Min bid CPM for Protected Audience auctions.
['minBid', '3.5'],
// Max bid CPM for Protected Audience auctions.
['maxBid', '4.5'],
// Default bid multiplier for Protected Audience auctions.
['multiplier', '1.1'],
];
/** Deal specific bid multipliers for Protected Audience auctions. */
export const BIDDING_SIGNALS_DEALS = [
// Template for keys: `multiplier-${dealId}`
['multiplier-deal-1', '0.5'], // Deal ID: deal-1
['multiplier-deal-2', '0.4'], // Deal ID: deal-2
['multiplier-deal-3', '0.45'], // Deal ID: deal-3
];

// ****************************************************************************
// ADVERTISER METADATA
Expand Down
190 changes: 170 additions & 20 deletions services/ad-tech/src/lib/interest-group-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,94 +12,244 @@
*/

import {
CURRENT_ORIGIN,
BIDDING_SIGNALS_DEFAULT,
EXTERNAL_PORT,
HOSTNAME,
MACRO_DISPLAY_RENDER_URL_AD_SIZE,
MACRO_VIDEO_RENDER_URL_SSP_VAST,
BIDDING_SIGNALS_DEALS,
} from './constants.js';

// ****************************************************************************
// ENUMS AND INTERFACES
// ****************************************************************************
/** Supported ad types. */
export enum AdType {
DISPLAY = 'DISPLAY',
VIDEO = 'VIDEO',
}

/** Generalized interface for an interest group ad object. */
/** Interface for an interest group ad object. */
export interface InterestGroupAd {
// REQUIRED FIELDS
/** Main creative URL. */
renderURL: string;
/** Custom ad metadata stored by ad-tech. */
metadata: {
/** Hostname of the advertiser. */
advertiser: string;
/** DSIPLAY or VIDEO */
adType: AdType;
adSizes?: {width: string; height: string}[];
/** List of compatible ad sizes. */
adSizes?: InterestGroupAdSize[];
};
// OPTIONAL FIELDS
/** Associated ad size group label. */
sizeGroup?: string;
// [Optional] Reporting IDs
/** Selectable reporting ID accessible to both buyer and seller. */
selectableBuyerAndSellerReportingIds?: string[];
/** Non-selectable reporting ID accessible to buyer only. */
buyerReportingId?: string;
/** Non-selectable reporting ID accessible to both buyer and seller. */
buyerAndSellerReportingId?: string;
}

/** Generalized interface of an ad size. */
export interface InterestGroupAdSize {
width: string;
height: string;
}

/** Top-level interface of an interest group. */
export interface InterestGroup {
/** Name of the interest group: ${advertiser}-${usecase} */
name: string;
/** Origin for the ad buyer. */
owner: string;
/** URL to script defining generateBid() and reportWin(). */
biddingLogicURL?: string;
/** Endpoint for real-time bidding signals. */
trustedBiddingSignalsURL?: string;
/** Real-time bidding signals to query for. */
trustedBiddingSignalsKeys?: string[];
/** Endpoint to periodically update the interest group. */
updateURL?: string;
/** User-specific bidding signals to consider while bidding. */
userBiddingSignals?: {[key: string]: string};
/** All ads to consider while bidding. */
ads?: InterestGroupAd[];
/** Map of ad sizes indexed by a label for the size. */
adSizes?: {[key: string]: InterestGroupAdSize};
/** Map of ad size labels indexed by the ad size group label. */
sizeGroups?: {[key: string]: string[]};
}

/** Generalized interface of the interest group targeting context. */
export interface TargetingContext {
// REQUIRED FIELDS
/** Hostname of the advertiser. */
advertiser: string;
/** Usecase for targeting, or 'default'. */
usecase: string;
/** Whether this is a fresh request or an update. */
isUpdateRequest: boolean;
// OPTIONAL FIELDS
/** Advertiser prouct item ID. */
itemId?: string;
/** Real-time bidding signal keys to include in the interest group. */
biddingSignalKeys?: string[];
/** All other undocumented custom query parameters. */
additionalContext?: {[key: string]: string[]};
}

// ****************************************************************************
// HELPER FUNCTIONS
// ****************************************************************************
/** Returns video ads for a given advertiser and SSP hosts to integrate. */
const getVideoAdForRequest = (advertiser: string): InterestGroupAd => {
const getVideoAdForRequest = (
targetingContext: TargetingContext,
): InterestGroupAd => {
const {advertiser} = targetingContext;
const renderUrl = new URL(
`https://${HOSTNAME}:${EXTERNAL_PORT}/ads/video-ads?`,
);
renderUrl.searchParams.append('advertiser', advertiser);
return {
const ad: InterestGroupAd = {
renderURL: `${renderUrl.toString()}&${MACRO_VIDEO_RENDER_URL_SSP_VAST}`,
metadata: {
advertiser,
adType: AdType.VIDEO,
},
selectableBuyerAndSellerReportingIds: ['deal1', 'deal2', 'deal3'],
buyerReportingId: 'buyerSpecificInfo1',
buyerAndSellerReportingId: 'seatid-1234',
};
if (targetingContext.isUpdateRequest) {
// Only include deal IDs in update requests.
ad.selectableBuyerAndSellerReportingIds = ['deal-1', 'deal-2', 'deal-3'];
}
return ad;
};

/** Returns the interest group display ad to for the given advertiser. */
const getDisplayAdForRequest = (
advertiser: string,
itemId?: string,
targetingContext: TargetingContext,
): InterestGroupAd => {
const {advertiser, itemId} = targetingContext;
const renderUrl = new URL(
`https://${HOSTNAME}:${EXTERNAL_PORT}/ads/display-ads`,
);
renderUrl.searchParams.append('advertiser', advertiser);
if (itemId) {
renderUrl.searchParams.append('itemId', itemId);
}
return {
const ad: InterestGroupAd = {
renderURL: `${renderUrl.toString()}&${MACRO_DISPLAY_RENDER_URL_AD_SIZE}`,
metadata: {
advertiser,
adType: AdType.DISPLAY,
adSizes: [{width: '300px', height: '250px'}],
},
sizeGroup: 'medium-rectangle',
selectableBuyerAndSellerReportingIds: ['deal1', 'deal2', 'deal3'],
buyerReportingId: 'buyerSpecificInfo1',
buyerAndSellerReportingId: 'seatid-1234',
};
if (targetingContext.isUpdateRequest) {
// Only include deal IDs in update requests.
ad.selectableBuyerAndSellerReportingIds = ['deal-1', 'deal-2', 'deal-3'];
}
return ad;
};

/** Returns the interest group name to use. */
const getInterestGroupName = (targetingContext: TargetingContext): string => {
const {advertiser, usecase} = targetingContext;
return `${advertiser}-${usecase}`;
};

/** Returns the update URL with the targeting context. */
const constructInterestGroupUpdateUrl = (
targetingContext: TargetingContext,
): string => {
const interestGroupName = getInterestGroupName(targetingContext);
const updateURL = new URL(
`https://${HOSTNAME}:${EXTERNAL_PORT}/dsp/interest-group-update.json`,
);
updateURL.searchParams.append('interestGroupName', interestGroupName);
updateURL.searchParams.append('advertiser', targetingContext.advertiser);
updateURL.searchParams.append('usecase', targetingContext.usecase);
if (targetingContext.itemId) {
updateURL.searchParams.append('itemId', targetingContext.itemId);
}
if (targetingContext.biddingSignalKeys?.length) {
updateURL.searchParams.append(
'biddingSignalKeys',
targetingContext.biddingSignalKeys.join(','),
);
}
if (targetingContext.additionalContext) {
for (const [key, value] of Object.entries(
targetingContext.additionalContext,
)) {
updateURL.searchParams.append(key, value.join(','));
}
}
return updateURL.toString();
};

/** Returns the bidding signal keys to include in the interest group. */
const getBiddingSignalKeys = (targetingContext: TargetingContext): string[] => {
const biddingSignalsKeys = [...BIDDING_SIGNALS_DEFAULT.map(([key]) => key)];
if (targetingContext.biddingSignalKeys) {
biddingSignalsKeys.push(...targetingContext.biddingSignalKeys);
}
if (targetingContext.isUpdateRequest) {
// Only include keys related to deals for update requests.
biddingSignalsKeys.push(...BIDDING_SIGNALS_DEALS.map(([key]) => key));
}
return biddingSignalsKeys;
};

// ****************************************************************************
// EXPORTED FUNCTIONS
// ****************************************************************************
/** Returns the interest groups ads for the given advertiser. */
export const getAdsForRequest = (
advertiser: string,
itemId?: string,
): InterestGroupAd[] => {
// Include all types of ads in the interest group and filter based on
// opportunity at bidding time.
return [
getDisplayAdForRequest(advertiser, itemId),
getVideoAdForRequest(advertiser),
];
/** Returns the interest group for the given targeting context. */
export const getInterestGroup = (
targetingContext: TargetingContext,
): InterestGroup => {
const {usecase} = targetingContext;
const userBiddingSignals: {[key: string]: string} = {
'user-signal-key-1': 'user-signal-value-1',
};
if (targetingContext.additionalContext) {
for (const [key, values] of Object.entries(
targetingContext.additionalContext,
)) {
userBiddingSignals[key] = JSON.stringify(values);
}
}
return {
name: getInterestGroupName(targetingContext),
owner: CURRENT_ORIGIN,
biddingLogicURL: new URL(
`https://${HOSTNAME}:${EXTERNAL_PORT}/js/dsp/${usecase}/auction-bidding-logic.js`,
).toString(),
trustedBiddingSignalsURL: new URL(
`https://${HOSTNAME}:${EXTERNAL_PORT}/dsp/realtime-signals/bidding-signal.json`,
).toString(),
trustedBiddingSignalsKeys: getBiddingSignalKeys(targetingContext),
updateURL: constructInterestGroupUpdateUrl(targetingContext),
userBiddingSignals,
ads: [
getDisplayAdForRequest(targetingContext),
getVideoAdForRequest(targetingContext),
],
adSizes: {
'medium-rectangle-default': {'width': '300px', 'height': '250px'},
},
sizeGroups: {
'medium-rectangle': ['medium-rectangle-default'],
},
};
};
Loading

0 comments on commit 2ef5df9

Please sign in to comment.