Skip to content
248 changes: 239 additions & 9 deletions api.bs
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,14 @@ The <dfn method for=Attribution>measureConversion(|options|)</dfn> method steps

1. Let |implicitInputs| be the result of [=obtaining the implicit API inputs=] from [=this=].
1. [=Assert=]: |implicitInputs| is not null.
1. Return the result of running [=measure a conversion=] with |options| and |implicitInputs|.

</div>

<div algorithm>
To <dfn>measure a conversion</dfn> given {{AttributionConversionOptions}} |options|
and [=implicit API inputs=] |implicitInputs|:

1. Let |document| be |implicitInputs|'s [=implicit API inputs/associated document=].
1. Let |realm| be |document|'s [=relevant realm=].
1. If |document| is not [=allowed to use=] the [=policy-controlled feature=] named
Expand Down Expand Up @@ -1917,6 +1925,8 @@ if the user has opted out of collection of diagnostic data.

# HTTP API # {#http-api}

## Saving impressions ## {#http-api-impressions}

\`<dfn http-header><code>Save-Impression</code></dfn>\` is a
[=structured header/dictionary|Dictionary Structured Header=]
set on a response requesting that the user agent invoke the
Expand All @@ -1933,8 +1943,8 @@ The following keys are defined, corresponding to the members of
the {{AttributionImpressionOptions}} dictionary passed to
<a method for=Attribution>saveImpression()</a>. Default values for omitted
optional keys are treated the same way as the corresponding
{{AttributionImpressionOptions}} field.

{{AttributionImpressionOptions}} field. Unknown dictionary keys are ignored,
as are unknown parameters.

<dl dfn-for=save-impression>
<dt><dfn noexport><code>conversion-sites</code></dfn></dt>
Expand Down Expand Up @@ -2016,6 +2026,207 @@ To <dfn noexport>parse a `Save-Impression` header</dfn> given a [=header value=]

</div>

## Measuring Conversions ## {#http-api-conversions}

\`<dfn http-header><code>Measure-Conversion</code></dfn>\` is a
[=structured header/dictionary|Dictionary Structured Header=]
set on a response requesting that the user agent invoke the
<a method for=Attribution>measureConversion()</a> API.

<div class=example id=ex-measure-conversion-header>
This is the HTTP equivalent of <a href=#ex-measure-conversion>the JavaScript `measureConversion` example</a>,
with the addition of a `report-url` to which the resulting report will be `POST`ed:
<xmp highlight=http>
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"
</xmp>
</div>

The following keys are defined, corresponding to the members of
the {{AttributionConversionOptions}} dictionary passed to
<a method for=Attribution>measureConversion()</a>. Default values for omitted
optional keys are treated the same way as the corresponding
{{AttributionConversionOptions}} field. Unknown dictionary keys are ignored, as
are unknown parameters.

<dl dfn-for=measure-conversion>
<dt><dfn noexport><code>aggregation-service</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionConversionOptions>aggregationService</a>,
a [=structured header/string=]. This key is required.
</dd>
<dt><dfn noexport><code>epsilon</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionConversionOptions>epsilon</a>,
a positive [=structured header/decimal=] or [=structured header/integer=]. This key is optional.
</dd>
<dt><dfn noexport><code>histogram-size</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionConversionOptions>histogramSize</a>,
a positive [=structured header/integer=]. This key is required.
</dd>
<dt><dfn noexport><code>lookback-days</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionConversionOptions>lookbackDays</a>,
a positive [=structured header/integer=]. This key is optional.
</dd>
<dt><dfn noexport><code>match-values</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionConversionOptions>matchValues</a>,
an [=structured header/inner list=] containing non-negative [=structured header/integer|integers=].
This key is optional.
</dd>
<dt><dfn noexport><code>impression-sites</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionConversionOptions>impressionSites</a>,
an [=structured header/inner list=] containing [=structured header/string|strings=].
Each string value includes a domain name using A-labels only;
[[RFC5890|Internationalized Domain Names]] therefore need to use [[RFC3492|punycode]].
This key is optional.
</dd>
<dt><dfn noexport><code>impression-callers</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionConversionOptions>impressionCallers</a>,
an [=structured header/inner list=] containing [=structured header/string|strings=].
Each string value includes a domain name using A-labels only;
[[RFC5890|Internationalized Domain Names]] therefore need to use [[RFC3492|punycode]].
This key is optional.
</dd>
<dt><dfn noexport><code>credit</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionConversionOptions>credit</a>,
an [=structured header/inner list=] containing positive [=structured header/decimal|decimals=]
or positive [=structured header/integer|integers=]. This key is optional.
</dd>
<dt><dfn noexport><code>value</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionConversionOptions>value</a>,
a positive [=structured header/integer=]. This key is optional.
</dd>
<dt><dfn noexport><code>max-value</code></dfn></dt>
<dd>
Value of <a dict-member for=AttributionConversionOptions>maxValue</a>,
a positive [=structured header/integer=]. This key is optional.
</dd>
<dt><dfn noexport><code>report-url</code></dfn></dt>
<dd>
A [=structured header/string=] containing the [=potentially trustworthy URL=]
to which the resulting report, if any, will be `POST`ed. The URL may be
relative to the response URL. Its [=url/scheme=] must be "`https`".
This key is required.
</dd>
</dl>

<div algorithm>
To <dfn noexport>parse a `Measure-Conversion` header</dfn> given a [=header value=]
|input| and [=URL=] |baseUrl|, run these steps:

1. Let |dict| be the result of [=structured header/parsing structured fields=]
with <var ignore>input_bytes</var> set to |input| and
<var ignore>field_type</var> set to "`dictionary`".
1. If parsing failed, return an error.
1. Let |aggregationService| be |dict|["<code>[=measure-conversion/aggregation-service=]</code>"] [=map/with default=] `undefined`.
1. If |aggregationService| is not a [=structured header/string=], return an error.
1. Let |histogramSize| be |dict|["<code>[=measure-conversion/histogram-size=]</code>"] [=map/with default=] `undefined`.
1. If |histogramSize| is not a positive [=structured header/integer=] in the [=32-bit unsigned integer=] range, return an error.
1. Let |reportUrlString| be |dict|["<code>[=measure-conversion/report-url=]</code>"] [=map/with default=] `undefined`.
1. If |reportUrlString| is not a [=structured header/string=], return an error.
1. Let |reportUrl| be the result of applying the [=URL parser=] to |reportUrlString|, with |baseUrl|.
1. If |reportUrl| is failure, return an error.
1. If |reportUrl| is not a [=potentially trustworthy URL=], return an error.
1. If |reportUrl|'s [=url/scheme=] is not "`https`", return an error.
1. Let |opts| be a new {{AttributionConversionOptions}} with the following items:
: {{AttributionConversionOptions/aggregationService}}
:: |aggregationService|
: {{AttributionConversionOptions/histogramSize}}
:: |histogramSize|
1. If |dict|["<code>[=measure-conversion/epsilon=]</code>"] [=map/exists=]:
1. Let |epsilon| be its [=map/value=].
1. If |epsilon| is not a [=structured header/decimal=] or [=structured header/integer=], return an error.
1. Set |opts|.{{AttributionConversionOptions/epsilon}} to |epsilon|.
1. If |dict|["<code>[=measure-conversion/lookback-days=]</code>"] [=map/exists=]:
1. Let |lookbackDays| be its [=map/value=].
1. If |lookbackDays| is not a positive [=structured header/integer=], return an error.
1. Set |opts|.{{AttributionConversionOptions/lookbackDays}} to |lookbackDays|.
1. If |dict|["<code>[=measure-conversion/match-values=]</code>"] [=map/exists=]:
1. Let |matchValues| be its [=map/value=].
1. If |matchValues| is not an [=structured header/inner list=], or if any of
|matchValues|' [=list/items=] is not an [=structured header/integer=] in
the [=32-bit unsigned integer=] range, return an error.
1. Set |opts|.{{AttributionConversionOptions/matchValues}} to |matchValues|.
1. If |dict|["<code>[=measure-conversion/impression-sites=]</code>"] [=map/exists=]:
1. Let |impressionSites| be its [=map/value=].
1. If |impressionSites| is not an [=structured header/inner list=], or if any of
|impressionSites|' [=list/items=] is not a [=structured header/string=],
return an error.
1. Set |opts|.{{AttributionConversionOptions/impressionSites}} to |impressionSites|.
1. If |dict|["<code>[=measure-conversion/impression-callers=]</code>"] [=map/exists=]:
1. Let |impressionCallers| be its [=map/value=].
1. If |impressionCallers| is not an [=structured header/inner list=], or if any of
|impressionCallers|' [=list/items=] is not a [=structured header/string=],
return an error.
1. Set |opts|.{{AttributionConversionOptions/impressionCallers}} to |impressionCallers|.
1. If |dict|["<code>[=measure-conversion/credit=]</code>"] [=map/exists=]:
1. Let |credit| be its [=map/value=].
1. If |credit| is not an [=structured header/inner list=], or if any of
|credit|'s [=list/items=] is not a [=structured header/decimal=] or
[=structured header/integer=], return an error.
1. Set |opts|.{{AttributionConversionOptions/credit}} to |credit|.
1. If |dict|["<code>[=measure-conversion/value=]</code>"] [=map/exists=]:
1. Let |value| be its [=map/value=].
1. If |value| is not a positive [=structured header/integer=] in the
[=32-bit unsigned integer=] range, return an error.
1. Set |opts|.{{AttributionConversionOptions/value}} to |value|.
1. If |dict|["<code>[=measure-conversion/max-value=]</code>"] [=map/exists=]:
1. Let |maxValue| be its [=map/value=].
1. If |maxValue| is not a positive [=structured header/integer=] in the
[=32-bit unsigned integer=] range, return an error.
1. Set |opts|.{{AttributionConversionOptions/maxValue}} to |maxValue|.
1. Return (|opts|, |reportUrl|).

Issue: Should we allow `http` for |reportUrl|'s [=url/scheme=]?
Related to <a href="https://github.com/w3c/attribution/issues/146">issue 146</a>.

</div>

<div algorithm>
To <dfn noexport>send a report</dfn> given a [=byte sequence=] |report|,
a [=URL=] |url|, and an [=environment settings object=] |settings|:

1. [=Assert=]: |url| is a [=potentially trustworthy URL=].
1. [=Assert=]: |url|'s [=url/scheme=] is "`https`".
1. Let |headers| be a new [=header list=] containing a [=header=] named
`"Content-Type"` whose value is
"<code>[[DAP#name-application-dap-report-medi|application/dap-report]]</code>".

Note: This will need to be updated if {{AttributionAggregationProtocol}}
ever gains a value other than {{AttributionAggregationProtocol/dap-15-histogram}}.
1. Let |request| be a new [=request=] with the following properties:
: [=request/method=]
:: "`POST`"
: [=request/URL=]
:: |url|
: [=request/header list=]
:: |headers|
: [=request/body=]
:: |report|
: [=request/client=]
:: |settings|
: [=request/mode=]
:: "`cors`"
: [=request/cache mode=]
:: "`no-store`"
: [=request/keepalive=]
:: true
: [=request/credentials mode=]
:: "`omit`"
: [=request/referrer=]
:: |url|
1. [=Fetch=] |request|, optionally retrying in the event of an error.

</div>

## Fetch monkey patches ## {#fetch-monkey-patches}

<div algorithm>
To <dfn noexport>handle Attribution headers</dfn> given a [=request=] |request|
and [=response=] |response|, run these steps:
Expand All @@ -2027,7 +2238,7 @@ and [=response=] |response|, run these steps:

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

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

1. Let |implicitInputs| be the result of [=obtaining the implicit API inputs=] from |request|'s [=request/client=]
with |response|'s [=response/URL=]'s [=url/origin=].
Expand All @@ -2036,17 +2247,35 @@ and [=response=] |response|, run these steps:

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

1. If |saveImpressionHeader| is null, return.
1. If |saveImpressionHeader| is not null:

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

1. If |impressionOptions| is an error, return.
1. If |impressionOptions| is not an error, [=save an impression|Save=] |impressionOptions| with |implicitInputs|.

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

</div>
1. If |measureConversionHeader| is not null:

## Fetch monkey patches ## {#fetch-monkey-patches}
1. Let |parseConversionResult| be the result of [=parse a Measure-Conversion header|parsing=] |measureConversionHeader|.

1. If |parseConversionResult| is not an error:

1. Let (|conversionOptions|, |reportUrl|) be |parseConversionResult|.

1. Let |reportPromise| be the result of running [=measure a conversion=] with |conversionOptions| and |implicitInputs|.

1. [=In parallel=]:
1. [=Upon fulfillment=] of |reportPromise|, let |result| be the fulfilled value.
1. [=Send a report=] with |result|.{{AttributionConversionResult/report}}, |reportUrl|, and
|request|'s [=request/client=].

Issue: Confirm the desired semantics of [:Save-Impression:] and [:Measure-Conversion:] in the same response.

Issue: Should we allow `http` for |response|'s [=response/URL=]'s [=url/scheme=]?
Related to <a href="https://github.com/w3c/attribution/issues/146">issue 146</a>.

</div>

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

Expand Down Expand Up @@ -3135,6 +3364,7 @@ urlPrefix:https://tc39.es/ecma262/#;type:dfn;spec:ecma-262
spec:structured header; type:dfn; urlPrefix: https://httpwg.org/specs/rfc9651;
text: structured header; url: #name-introduction
for: structured header
text: decimal; url: #decimal
text: dictionary; url: #dictionary
text: parse structured fields; url: #text-parse
text: string; url: #string
Expand Down
Loading