Skip to content

Commit 8e5202c

Browse files
committed
Add Measure-Conversion HTTP header
1 parent af8ba6b commit 8e5202c

File tree

1 file changed

+229
-7
lines changed

1 file changed

+229
-7
lines changed

api.bs

Lines changed: 229 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,14 @@ The <dfn method for=Attribution>measureConversion(|options|)</dfn> method steps
15271527

15281528
1. Let |implicitInputs| be the result of [=obtaining the implicit API inputs=] from [=this=].
15291529
1. [=Assert=]: |implicitInputs| is not null.
1530+
1. Return the result of running [=measure a conversion=] with |options| and |implicitInputs|.
1531+
1532+
</div>
1533+
1534+
<div algorithm>
1535+
To <dfn>measure a conversion</dfn> given {{AttributionConversionOptions}} |options|
1536+
and [=implicit API inputs=] |implicitInputs|:
1537+
15301538
1. Let |document| be |implicitInputs|'s [=implicit API inputs/associated document=].
15311539
1. Let |realm| be |document|'s [=relevant realm=].
15321540
1. If |document| is not [=allowed to use=] the [=policy-controlled feature=] named
@@ -1917,6 +1925,8 @@ if the user has opted out of collection of diagnostic data.
19171925

19181926
# HTTP API # {#http-api}
19191927

1928+
## Saving impressions ## {#http-api-impressions}
1929+
19201930
\`<dfn http-header><code>Save-Impression</code></dfn>\` is a
19211931
[=structured header/dictionary|Dictionary Structured Header=]
19221932
set on a response requesting that the user agent invoke the
@@ -2016,6 +2026,201 @@ To <dfn noexport>parse a `Save-Impression` header</dfn> given a [=header value=]
20162026

20172027
</div>
20182028

2029+
## Measuring Conversions ## {#http-api-conversions}
2030+
2031+
\`<dfn http-header><code>Measure-Conversion</code></dfn>\` is a
2032+
[=structured header/dictionary|Dictionary Structured Header=]
2033+
set on a response requesting that the user agent invoke the
2034+
<a method for=Attribution>measureConversion()</a> API.
2035+
2036+
<div class=example id=ex-measure-conversion-header>
2037+
This is the HTTP equivalent of <a href=#ex-measure-conversion>the JavaScript `measureConversion` example</a>,
2038+
with the addition of a `report-url` to which the resulting report will be `POST`ed:
2039+
<xmp highlight=http>
2040+
Measure-Conversion: aggregation-service="https://aggregator.example/tee", histogram-size=20, epsilon=1.0, lookback-days=14, impression-sites=("publisher.example" "other.example"), impression-callers=("ad-tech.example"), match-values=(2), credit=(0.25 0.25 0.5), value=3, max-value=7, report-url="https://report-handler.example/foo"
2041+
</xmp>
2042+
</div>
2043+
2044+
The following keys are defined, corresponding to the members of
2045+
the {{AttributionConversionOptions}} dictionary passed to
2046+
<a method for=Attribution>measureConversion()</a>. Default values for omitted
2047+
optional keys are populated the same way as the corresponding
2048+
{{AttributionConversionOptions}} field.
2049+
2050+
<dl dfn-for=measure-conversion>
2051+
<dt><dfn noexport><code>aggregation-service</code></dfn></dt>
2052+
<dd>
2053+
Value of <a dict-member for=AttributionConversionOptions>aggregationService</a>,
2054+
a [=structured header/string=]. This key is required.
2055+
</dd>
2056+
<dt><dfn noexport><code>epsilon</code></dfn></dt>
2057+
<dd>
2058+
Value of <a dict-member for=AttributionConversionOptions>epsilon</a>,
2059+
a positive [=structured header/decimal=]. This key is optional.
2060+
</dd>
2061+
<dt><dfn noexport><code>histogram-size</code></dfn></dt>
2062+
<dd>
2063+
Value of <a dict-member for=AttributionConversionOptions>histogramSize</a>,
2064+
a positive [=structured header/integer=]. This key is required.
2065+
</dd>
2066+
<dt><dfn noexport><code>lookback-days</code></dfn></dt>
2067+
<dd>
2068+
Value of <a dict-member for=AttributionConversionOptions>lookbackDays</a>,
2069+
a positive [=structured header/integer=]. This key is optional.
2070+
</dd>
2071+
<dt><dfn noexport><code>match-values</code></dfn></dt>
2072+
<dd>
2073+
Value of <a dict-member for=AttributionConversionOptions>matchValues</a>,
2074+
an [=structured header/inner list=] containing non-negative [=structured header/integer|integers=].
2075+
This key is optional.
2076+
</dd>
2077+
<dt><dfn noexport><code>impression-sites</code></dfn></dt>
2078+
<dd>
2079+
Value of <a dict-member for=AttributionConversionOptions>impressionSites</a>,
2080+
an [=structured header/inner list=] containing [=structured header/string|strings=].
2081+
Each string value includes a domain name using A-labels only;
2082+
[[RFC5890|Internationalized Domain Names]] therefore need to use [[RFC3492|punycode]].
2083+
This key is optional.
2084+
</dd>
2085+
<dt><dfn noexport><code>impression-callers</code></dfn></dt>
2086+
<dd>
2087+
Value of <a dict-member for=AttributionConversionOptions>impressionCallers</a>,
2088+
an [=structured header/inner list=] containing [=structured header/string|strings=].
2089+
Each string value includes a domain name using A-labels only;
2090+
[[RFC5890|Internationalized Domain Names]] therefore need to use [[RFC3492|punycode]].
2091+
This key is optional.
2092+
</dd>
2093+
<dt><dfn noexport><code>credit</code></dfn></dt>
2094+
<dd>
2095+
Value of <a dict-member for=AttributionConversionOptions>credit</a>,
2096+
an [=structured header/inner list=] containing positive [=structured header/decimal|decimals=]
2097+
or positive [=structured header/integer|integers=]. This key is optional.
2098+
</dd>
2099+
<dt><dfn noexport><code>value</code></dfn></dt>
2100+
<dd>
2101+
Value of <a dict-member for=AttributionConversionOptions>value</a>,
2102+
a positive [=structured header/integer=]. This key is optional.
2103+
</dd>
2104+
<dt><dfn noexport><code>max-value</code></dfn></dt>
2105+
<dd>
2106+
Value of <a dict-member for=AttributionConversionOptions>maxValue</a>,
2107+
a positive [=structured header/integer=]. This key is optional.
2108+
</dd>
2109+
<dt><dfn noexport><code>report-url</code></dfn></dt>
2110+
<dd>
2111+
A [=structured header/string=] containing the [=potentially trustworthy URL=]
2112+
to which the resulting report, if any, will be `POST`ed. The URL may be
2113+
relative to the response URL. Its [=url/scheme=] must be "`https`".
2114+
This key is required.
2115+
</dd>
2116+
</dl>
2117+
2118+
<div algorithm>
2119+
To <dfn noexport>parse a `Measure-Conversion` header</dfn> given a [=header value=]
2120+
|input| and [=URL=] |baseUrl|, run these steps:
2121+
2122+
1. Let |dict| be the result of [=structured header/parsing structured fields=]
2123+
with <var ignore>input_bytes</var> set to |input| and
2124+
<var ignore>field_type</var> set to "`dictionary`".
2125+
1. If parsing failed, return an error.
2126+
1. Let |aggregationService| be |dict|["<code>[=measure-conversion/aggregation-service=]</code>"] [=map/with default=] `undefined`.
2127+
1. If |aggregationService| is not a [=structured header/string=], return an error.
2128+
1. Let |histogramSize| be |dict|["<code>[=measure-conversion/histogram-size=]</code>"] [=map/with default=] `undefined`.
2129+
1. If |histogramSize| is not a positive [=structured header/integer=] in the [=32-bit unsigned integer=] range, return an error.
2130+
1. Let |reportUrlString| be |dict|["<code>[=measure-conversion/report-url=]</code>"] [=map/with default=] `undefined`.
2131+
1. If |reportUrlString| is not a [=structured header/string=], return an error.
2132+
1. Let |reportUrl| be the result of applying the [=URL parser=] to |reportUrlString|, with |baseUrl|.
2133+
1. If |reportUrl| is failure, return an error.
2134+
1. If |reportUrl| is not a [=potentially trustworthy URL=], return an error.
2135+
1. If |reportUrl|'s [=url/scheme=] is not "`https`", return an error.
2136+
1. Let |opts| be a new {{AttributionConversionOptions}} with the following items:
2137+
: {{AttributionConversionOptions/aggregationService}}
2138+
:: |aggregationService|
2139+
: {{AttributionConversionOptions/histogramSize}}
2140+
:: |histogramSize|
2141+
1. If |dict|["<code>[=measure-conversion/epsilon=]</code>"] [=map/exists=]:
2142+
1. Let |epsilon| be its [=map/value=].
2143+
1. If |epsilon| is not a [=structured header/decimal=] or [=structured header/integer=], return an error.
2144+
1. Set |opts|.{{AttributionConversionOptions/epsilon}} to |epsilon|.
2145+
1. If |dict|["<code>[=measure-conversion/lookback-days=]</code>"] [=map/exists=]:
2146+
1. Let |lookbackDays| be its [=map/value=].
2147+
1. If |lookbackDays| is not a positive [=structured header/integer=], return an error.
2148+
1. Set |opts|.{{AttributionConversionOptions/lookbackDays}} to |lookbackDays|.
2149+
1. If |dict|["<code>[=measure-conversion/match-values=]</code>"] [=map/exists=]:
2150+
1. Let |matchValues| be its [=map/value=].
2151+
1. If |matchValues| is not an [=structured header/inner list=], or if any of
2152+
|matchValues|' [=list/items=] is not a [=structured header/integer=] in
2153+
the [=32-bit unsigned integer=] range, return an error.
2154+
1. Set |opts|.{{AttributionConversionOptions/matchValues}} to |matchValues|.
2155+
1. If |dict|["<code>[=measure-conversion/impression-sites=]</code>"] [=map/exists=]:
2156+
1. Let |impressionSites| be its [=map/value=].
2157+
1. If |impressionSites| is not an [=structured header/inner list=], or if any of
2158+
|impressionSites|' [=list/items=] is not a [=structured header/string=],
2159+
return an error.
2160+
1. Set |opts|.{{AttributionConversionOptions/impressionSites}} to |impressionSites|.
2161+
1. If |dict|["<code>[=measure-conversion/impression-callers=]</code>"] [=map/exists=]:
2162+
1. Let |impressionCallers| be its [=map/value=].
2163+
1. If |impressionCallers| is not an [=structured header/inner list=], or if any of
2164+
|impressionCallers|' [=list/items=] is not a [=structured header/string=],
2165+
return an error.
2166+
1. Set |opts|.{{AttributionConversionOptions/impressionCallers}} to |impressionCallers|.
2167+
1. If |dict|["<code>[=measure-conversion/credit=]</code>"] [=map/exists=]:
2168+
1. Let |credit| be its [=map/value=].
2169+
1. If |credit| is not an [=structured header/inner list=], or if any of
2170+
|credit|'s [=list/items=] is not a [=structured header/decimal=] or
2171+
[=structured header/integer=], return an error.
2172+
1. Set |opts|.{{AttributionConversionOptions/credit}} to |credit|.
2173+
1. If |dict|["<code>[=measure-conversion/value=]</code>"] [=map/exists=]:
2174+
1. Let |value| be its [=map/value=].
2175+
1. If |value| is not a positive [=structured header/integer=] in the
2176+
[=32-bit unsigned integer=] range, return an error.
2177+
1. Set |opts|.{{AttributionConversionOptions/value}} to |value|.
2178+
1. If |dict|["<code>[=measure-conversion/max-value=]</code>"] [=map/exists=]:
2179+
1. Let |maxValue| be its [=map/value=].
2180+
1. If |maxValue| is not a positive [=structured header/integer=] in the
2181+
[=32-bit unsigned integer=] range, return an error.
2182+
1. Set |opts|.{{AttributionConversionOptions/maxValue}} to |maxValue|.
2183+
1. Return (|opts|, |reportUrl|).
2184+
2185+
Issue: Should we allow `http` for |reportUrl|'s [=url/scheme=]?
2186+
Related to https://github.com/w3c/attribution/issues/146.
2187+
2188+
</div>
2189+
2190+
<div algorithm>
2191+
To <dfn noexport>send a report</dfn> given a [=byte sequence=] |report|,
2192+
a [=URL=] |url|, and an [=environment settings object=] |settings|:
2193+
2194+
1. [=Assert=]: |url| is a [=potentially trustworthy URL=].
2195+
1. [=Assert=]: |url|'s [=url/scheme=] is "`https`".
2196+
1. Let |headers| be a new [=header list=] containing a [=header=] named `"Content-Type"` whose value is `"application/octet-stream"`.
2197+
1. Let |request| be a new [=request=] with the following properties:
2198+
: [=request/method=]
2199+
:: "`POST`"
2200+
: [=request/URL=]
2201+
:: |url|
2202+
: [=request/header list=]
2203+
:: |headers|
2204+
: [=request/body=]
2205+
:: |report|
2206+
: [=request/client=]
2207+
:: |settings|
2208+
: [=request/mode=]
2209+
:: "`cors`"
2210+
: [=request/cache mode=]
2211+
:: "`no-store`"
2212+
: [=request/keepalive=]
2213+
:: true
2214+
1. [=Fetch=] |request|, optionally retrying in the event of an error.
2215+
2216+
Issue: Should the request use a more specific content-type?
2217+
2218+
Issue: What is the correct [=request/credentials mode=]?
2219+
2220+
</div>
2221+
2222+
## Fetch monkey patches ## {#fetch-monkey-patches}
2223+
20192224
<div algorithm>
20202225
To <dfn noexport>handle Attribution headers</dfn> given a [=request=] |request|
20212226
and [=response=] |response|, run these steps:
@@ -2028,7 +2233,7 @@ and [=response=] |response|, run these steps:
20282233

20292234
1. If |response|'s [=response/URL=] is not a [=potentially trustworthy URL=], return.
20302235

2031-
1. If |response|'s [=response/URL=]'s [=url/scheme=] is not not "`http`" or "`https`", return.
2236+
1. If |response|'s [=response/URL=]'s [=url/scheme=] is not "`https`", return.
20322237

20332238
1. Let |implicitInputs| be the result of [=obtaining the implicit API inputs=] from |request|'s [=request/client=]
20342239
with |response|'s [=response/URL=]'s [=url/origin=].
@@ -2037,17 +2242,33 @@ and [=response=] |response|, run these steps:
20372242

20382243
1. Let |saveImpressionHeader| be the result of [=header list/get|getting=] <code>[:Save-Impression:]</code> from |response|'s [=response/header list=].
20392244

2040-
1. If |saveImpressionHeader| is null, return.
2245+
1. If |saveImpressionHeader| is not null:
20412246

2042-
1. Let |impressionOptions| be the result of [=parse a Save-Impression header|parsing=] |saveImpressionHeader|.
2247+
1. Let |impressionOptions| be the result of [=parse a Save-Impression header|parsing=] |saveImpressionHeader|.
20432248

2044-
1. If |impressionOptions| is an error, return.
2249+
1. If |impressionOptions| is not an error, [=save an impression|Save=] |impressionOptions| with |implicitInputs|.
20452250

2046-
1. [=save an impression|Save=] |impressionOptions| with |implicitInputs|.
2251+
1. Let |measureConversionHeader| be the result of [=header list/get|getting=] <code>[:Measure-Conversion:]</code> from |response|'s [=response/header list=].
20472252

2048-
</div>
2253+
1. If |measureConversionHeader| is not null:
20492254

2050-
## Fetch monkey patches ## {#fetch-monkey-patches}
2255+
1. Let |parseConversionResult| be the result of [=parse a Measure-Conversion header|parsing=] |measureConversionHeader|.
2256+
2257+
1. If |parseConversionResult| is not an error:
2258+
2259+
1. Let (|conversionOptions|, |reportUrl|) be |parseConversionResult|.
2260+
2261+
1. Let |reportPromise| be the result of running [=measure a conversion=] with |conversionOptions| and |implicitInputs|.
2262+
2263+
1. [=In parallel=], [=upon fulfillment=] of |reportPromise|, [=send a report=] with the fulfilled value, |reportUrl|, and
2264+
|request|'s [=request/client=].
2265+
2266+
Issue: Confirm the desired semantics of [:Save-Impression:] and [:Measure-Conversion:] in the same response.
2267+
2268+
Issue: Should we allow `http` for |response|'s [=response/URL=]'s [=url/scheme=]?
2269+
Related to https://github.com/w3c/attribution/issues/146.
2270+
2271+
</div>
20512272

20522273
Modify [=HTTP-network fetch=] as follows:
20532274

@@ -3136,6 +3357,7 @@ urlPrefix:https://tc39.es/ecma262/#;type:dfn;spec:ecma-262
31363357
spec:structured header; type:dfn; urlPrefix: https://httpwg.org/specs/rfc9651;
31373358
text: structured header; url: #name-introduction
31383359
for: structured header
3360+
text: decimal; url: #decimal
31393361
text: dictionary; url: #dictionary
31403362
text: parse structured fields; url: #text-parse
31413363
text: string; url: #string

0 commit comments

Comments
 (0)