protected-audience-helper-module.js (8295B)
1 // This file is adapted from /fledge/tentative/resources/fledge-util.js, 2 // removing unnecessary logic and modifying to allow it to be run in the 3 // private-aggregation directory. 4 5 "use strict;" 6 7 const FULL_URL = window.location.href; 8 let BASE_URL = FULL_URL.substring(0, FULL_URL.lastIndexOf('/') + 1) 9 const BASE_PATH = (new URL(BASE_URL)).pathname; 10 const DEFAULT_INTEREST_GROUP_NAME = 'default name'; 11 12 // Use python script files under fledge directory 13 const FLEDGE_DIR = '/fledge/tentative/'; 14 const FLEDGE_BASE_URL = BASE_URL.replace(BASE_PATH, FLEDGE_DIR); 15 16 // Sleep method that waits for prescribed number of milliseconds. 17 const sleep = ms => new Promise(resolve => step_timeout(resolve, ms)); 18 19 // Generates a UUID by token. 20 function generateUuid() { 21 let uuid = token(); 22 return uuid; 23 } 24 25 // Creates a URL that will be sent to the handler. 26 // `uuid` is used to identify the stash shard to use. 27 // `operate` is used to set action as write or read. 28 // `report` is used to carry the message for write requests. 29 function createReportingURL(uuid, operation, report = 'default-report') { 30 let url = new URL(`${window.location.origin}${BASE_PATH}resources/protected_audience_event_level_report_handler.py`); 31 url.searchParams.append('uuid', uuid); 32 url.searchParams.append('operation', operation); 33 34 if (report) 35 url.searchParams.append('report', report); 36 37 return url.toString(); 38 } 39 40 function createWritingURL(uuid, report) { 41 return createReportingURL(uuid, 'write'); 42 } 43 44 function createReadingURL(uuid) { 45 return createReportingURL(uuid, 'read'); 46 } 47 48 async function waitForObservedReports(uuid, expectedNumReports, timeout = 5000 /*ms*/) { 49 expectedReports = Array(expectedNumReports).fill('default-report'); 50 const reportURL = createReadingURL(uuid); 51 let startTime = performance.now(); 52 53 while (performance.now() - startTime < timeout) { 54 let response = await fetch(reportURL, { credentials: 'omit', mode: 'cors' }); 55 let actualReports = await response.json(); 56 57 // If expected number of reports have been observed, compare with list of 58 // all expected reports and exit. 59 if (actualReports.length == expectedReports.length) { 60 assert_array_equals(actualReports.sort(), expectedReports); 61 return; 62 } 63 64 await sleep(/*ms=*/ 100); 65 } 66 assert_unreached("Report fetching timed out: " + uuid); 67 } 68 69 // Creates a bidding script with the provided code in the method bodies. The 70 // bidding script's generateBid() method will return a bid of 9 for the first 71 // ad, after the passed in code in the "generateBid" input argument has been 72 // run, unless it returns something or throws. 73 // 74 // The default reportWin() method is empty. 75 function createBiddingScriptURL(params = {}) { 76 let url = new URL(`${FLEDGE_BASE_URL}resources/bidding-logic.sub.py`); 77 if (params.generateBid) 78 url.searchParams.append('generateBid', params.generateBid); 79 if (params.reportWin) 80 url.searchParams.append('reportWin', params.reportWin); 81 if (params.error) 82 url.searchParams.append('error', params.error); 83 if (params.bid) 84 url.searchParams.append('bid', params.bid); 85 return url.toString(); 86 } 87 88 // Creates a decision script with the provided code in the method bodies. The 89 // decision script's scoreAd() method will reject ads with renderURLs that 90 // don't ends with "uuid", and will return a score equal to the bid, after the 91 // passed in code in the "scoreAd" input argument has been run, unless it 92 // returns something or throws. 93 // 94 // The default reportResult() method is empty. 95 function createDecisionScriptURL(uuid, params = {}) { 96 let url = new URL(`${FLEDGE_BASE_URL}resources/decision-logic.sub.py`); 97 url.searchParams.append('uuid', uuid); 98 if (params.scoreAd) 99 url.searchParams.append('scoreAd', params.scoreAd); 100 if (params.reportResult) 101 url.searchParams.append('reportResult', params.reportResult); 102 if (params.error) 103 url.searchParams.append('error', params.error); 104 return url.toString(); 105 } 106 107 // Creates a renderURL for an ad that runs the passed in "script". "uuid" has 108 // no effect, beyond making the URL distinct between tests, and being verified 109 // by the decision logic script before accepting a bid. "uuid" is expected to 110 // be last. 111 function createRenderURL(uuid, script) { 112 let url = new URL(`${FLEDGE_BASE_URL}resources/fenced-frame.sub.py`); 113 if (script) 114 url.searchParams.append('script', script); 115 url.searchParams.append('uuid', uuid); 116 return url.toString(); 117 } 118 119 // Joins an interest group that, by default, is owned by the current frame's 120 // origin, is named DEFAULT_INTEREST_GROUP_NAME, has a bidding script that 121 // issues a bid of 9 with a renderURL of "https://not.checked.test/${uuid}". 122 // `interestGroupOverrides` is required to override fields in the joined 123 // interest group. 124 async function joinInterestGroup(test, uuid, interestGroupOverrides) { 125 const INTEREST_GROUP_LIFETIME_SECS = 60; 126 127 let interestGroup = { 128 owner: window.location.origin, 129 name: DEFAULT_INTEREST_GROUP_NAME, 130 ads: [{renderURL: createRenderURL(uuid)}], 131 ...interestGroupOverrides 132 }; 133 134 await navigator.joinAdInterestGroup(interestGroup, 135 INTEREST_GROUP_LIFETIME_SECS); 136 test.add_cleanup( 137 async () => {await navigator.leaveAdInterestGroup(interestGroup)}); 138 } 139 140 // Runs a FLEDGE auction and returns the result. `auctionConfigOverrides` is 141 // required to override fields in the auction configuration. 142 async function runBasicFledgeAuction(test, uuid, auctionConfigOverrides) { 143 let auctionConfig = { 144 seller: window.location.origin, 145 interestGroupBuyers: [window.location.origin], 146 resolveToConfig: true, 147 ...auctionConfigOverrides 148 }; 149 return await navigator.runAdAuction(auctionConfig); 150 } 151 152 // Calls runBasicFledgeAuction(), expecting the auction to have a winner. 153 // Creates a fenced frame that will be destroyed on completion of "test", and 154 // navigates it to the URN URL returned by the auction. Does not wait for the 155 // fenced frame to finish loading, since there's no API that can do that. 156 async function runBasicFledgeAuctionAndNavigate(test, uuid, 157 auctionConfigOverrides) { 158 let config = await runBasicFledgeAuction(test, uuid, auctionConfigOverrides); 159 assert_true(config instanceof FencedFrameConfig, 160 `Wrong value type returned from auction: ${config.constructor.type}`); 161 162 let fencedFrame = document.createElement('fencedframe'); 163 fencedFrame.mode = 'opaque-ads'; 164 fencedFrame.config = config; 165 document.body.appendChild(fencedFrame); 166 test.add_cleanup(() => { document.body.removeChild(fencedFrame); }); 167 } 168 169 // Joins an interest group and runs an auction, expecting no winner to be 170 // returned. "testConfig" can optionally modify the interest group or 171 // auctionConfig. 172 async function runBasicFledgeTestExpectingNoWinner(test, testConfig) { 173 const uuid = generateUuid(test); 174 await joinInterestGroup(test, uuid, testConfig.interestGroupOverrides); 175 let result = await runBasicFledgeAuction( 176 test, uuid, testConfig.auctionConfigOverrides); 177 assert_true(result === null, 'Auction unexpectedly had a winner'); 178 } 179 180 // Test helper for report phase of auctions that lets the caller specify the 181 // body of scoreAd(), reportResult(), generateBid() and reportWin(), as well as 182 // additional arguments to be passed to joinAdInterestGroup() and runAdAuction() 183 async function runReportTest(test, uuid, codeToInsert, 184 expectedNumReports = 0, overrides = {}) { 185 let generateBid = codeToInsert.generateBid; 186 let scoreAd = codeToInsert.scoreAd; 187 let reportWin = codeToInsert.reportWin; 188 let reportResult = codeToInsert.reportResult; 189 190 let extraInterestGroupOverrides = overrides.joinAdInterestGroup || {} 191 let extraAuctionConfigOverrides = overrides.runAdAuction || {} 192 193 let interestGroupOverrides = { 194 biddingLogicURL: createBiddingScriptURL({ generateBid, reportWin }), 195 ...extraInterestGroupOverrides 196 }; 197 let auctionConfigOverrides = { 198 decisionLogicURL: createDecisionScriptURL( 199 uuid, { scoreAd, reportResult }), 200 ...extraAuctionConfigOverrides 201 } 202 203 await joinInterestGroup(test, uuid, interestGroupOverrides); 204 await runBasicFledgeAuctionAndNavigate(test, uuid, auctionConfigOverrides); 205 206 if (expectedNumReports) { 207 await waitForObservedReports(uuid, expectedNumReports); 208 } 209 }