共享存储空间和私有聚合实现快速入门

本文档是使用 Shared Storage 和 Private Aggregation 的快速入门指南。您需要了解这两个 API,因为 Shared Storage 会存储值,而 Private Aggregation 会创建可汇总的报告。

目标受众群体:广告技术和效果衡量服务提供商。

Shared Storage API

为了防止跨网站跟踪,浏览器已开始对所有形式的存储空间(包括本地存储空间、Cookie 等)进行分区。不过,在某些应用场景中,需要使用未分区的存储空间。Shared Storage API 允许您在不同的顶级网站上拥有无限写入权限,同时提供可保护隐私的读取权限。

共享存储空间仅限于上下文来源(sharedStorage 的调用方)。

共享存储空间对每个来源的容量有限制,每个条目的字符数也有限制。如果达到此限制,系统将不再存储任何输入内容。数据存储限制在共享存储空间说明中进行了概述。

调用 Shared Storage

广告技术平台可以使用 JavaScript 或响应标头写入共享存储空间。从共享存储空间读取数据仅发生在称为“工作程序”的隔离 JavaScript 环境中。

  • 使用 JavaScript:广告技术平台可以执行特定的共享存储空间函数,例如在 JavaScript 工作程序之外设置、附加和删除值。不过,读取共享存储空间和执行 Private Aggregation 等函数必须通过 JavaScript worklet 完成。可在 JavaScript worklet 之外使用的方法可在建议的 API Surface - worklet 之外中找到。

    在工作单元中运行期间使用的方法可在建议的 API Surface - 在工作单元中找到。

  • 使用响应标头

    与 JavaScript 类似,只能使用响应标头执行特定函数,例如在共享存储空间中设置、附加和删除值。如需在响应标头中使用共享存储空间,必须在请求标头中包含 Shared-Storage-Writable: ?1。

    如需从客户端发起请求,请根据您选择的方法运行以下代码:

    • 使用 fetch()

      fetch("https://a.example/path/for/updates", {sharedStorageWritable: true});
      
    • 使用 iframe 或 img 标记

      <iframe src="https://a.example/path/for/updates" sharedstoragewritable></iframe>
      
    • 将 IDL 属性与 iframe 或 img 标记搭配使用

      let iframe = document.getElementById("my-iframe");
      iframe.sharedStorageWritable = true;
      iframe.src = "https://a.example/path/for/updates";
      

如需了解详情,请参阅共享存储空间:响应标头。

写入共享存储空间

如需写入共享存储空间,请从 JavaScript worklet 内部或外部调用 sharedStorage.set()。如果从工作程序之外调用,数据会写入调用来源的浏览上下文的来源。如果从工作程序内调用,数据会写入加载工作程序的浏览上下文的来源。设置的密钥的有效期为自上次更新起 30 天。

ignoreIfPresent 字段为可选字段。如果存在且设置为 true,则如果键已存在,系统不会更新该键。即使密钥未更新,密钥到期时间也会从 set() 调用时起延长 30 天。

如果在同一网页加载过程中多次使用同一键访问共享存储空间,则该键的值会被覆盖。如果键需要保持之前的值,最好使用 sharedStorage.append()。

  • 使用 JavaScript

    在工作单元之外:

    window.sharedStorage.set('myKey', 'myValue1', { ignoreIfPresent: true });
    // Shared Storage: {'myKey': 'myValue1'}
    window.sharedStorage.set('myKey', 'myValue2', { ignoreIfPresent: true });
    // Shared Storage: {'myKey': 'myValue1'}
    window.sharedStorage.set('myKey', 'myValue2', { ignoreIfPresent: false });
    // Shared Storage: {'myKey': 'myValue2'}
    

    同样,在 worklet 内:

    sharedStorage.set('myKey', 'myValue1', { ignoreIfPresent: true });
    
  • 使用响应标头

    您还可以使用响应标头写入共享存储空间。为此,请在响应标头中使用 Shared-Storage-Write,并搭配以下命令:

    Shared-Storage-Write : set;key="myKey";value="myValue";ignore_if_present
    
    Shared-Storage-Write : set;key="myKey";value="myValue";ignore_if_present=?0
    

    多个项可以以英文逗号分隔,并且可以组合使用 set、append、delete 和 clear。

    Shared-Storage-Write :
    set;key="hello";value="world";ignore_if_present, set;key="good";value="bye"
    

附加值

您可以使用 append 方法将值附加到现有键。如果该键不存在,则调用 append() 会创建该键并设置相应的值。您可以使用 JavaScript 或响应标头来实现此目的。

  • 使用 JavaScript

    如需更新现有键的值,请在工作程序内或工作程序外使用 sharedStorage.append()。

    window.sharedStorage.append('myKey', 'myValue1');
    // Shared Storage: {'myKey': 'myValue1'}
    window.sharedStorage.append('myKey', 'myValue2');
    // Shared Storage: {'myKey': 'myValue1myValue2'}
    window.sharedStorage.append('anotherKey', 'hello');
    // Shared Storage: {'myKey': 'myValue1myValue2', 'anotherKey': 'hello'}
    

    在 worklet 中附加:

    sharedStorage.append('myKey', 'myValue1');
    
  • 使用响应标头

    与在共享存储空间中设置值类似,您可以使用响应标头中的 Shared-Storage-Write 传入键值对。

    Shared-Storage-Write : append;key="myKey";value="myValue2"
    

批量更新值

您可以在 JavaScript worklet 内或外部调用 sharedStorage.batchUpdate(),并传入一个指定所选操作的有序方法数组。每个方法构造函数接受的参数与用于设置、附加、删除和清除的相应单个方法相同。

您可以从 JavaScript 调用 batchUpdate(),也可以使用响应标头:

  • 使用 JavaScript

    可与 batchUpdate() 搭配使用的 JavaScript 方法包括:

    • SharedStorageSetMethod():将键值对写入共享存储空间。
    • SharedStorageAppendMethod():将值附加到共享存储空间中的现有键,或者在键尚不存在时写入键值对。
    • SharedStorageDeleteMethod():从 Shared Storage 中删除键值对。
    • SharedStorageClearMethod():清除共享存储空间中的所有键。
    sharedStorage.batchUpdate([
    new SharedStorageSetMethod('keyOne', 'valueOne'),
    new SharedStorageAppendMethod('keyTwo', 'valueTwo'),
    new SharedStorageDeleteMethod('keyThree'),
    new SharedStorageClearMethod()
    ]);
    
  • 使用响应标头

    Shared-Storage-Write : set;key=keyOne;value=valueOne, append;key=keyTwo;value=valueTwo,delete;key=keyThree,clear
    

使用响应标头会针对标头中的所有方法执行 batchUpdate()。

从共享存储空间读取

您只能从工作程序中读取共享存储空间。

await sharedStorage.get('mykey');

加载 worklet 模块的浏览上下文的来源决定了读取哪个共享存储空间。

从 Shared Storage 中删除

您可以使用工作程序内或工作程序外的 JavaScript 从共享存储空间中执行删除操作,也可以使用带有 delete() 的响应标头执行删除操作。如需一次性删除所有键,请使用任一来源中的 clear()。

  • 使用 JavaScript

    从工作区之外的共享存储空间中删除:

    window.sharedStorage.delete('myKey');
    

    从 worklet 内部删除共享存储空间中的数据:

    sharedStorage.delete('myKey');
    

    从工作程序之外一次性删除所有键:

    window.sharedStorage.clear();
    

    如需从工作程序中一次性删除所有密钥,请执行以下操作:

    sharedStorage.clear();
    
  • 使用响应标头

    如需使用响应标头删除值,您还可以在响应标头中使用 Shared-Storage-Write 传递要删除的键。

    delete;key="myKey"
    

    使用响应标头删除所有密钥:

    clear;
    

从共享存储空间读取 Protected Audience 兴趣群体

您可以从 Shared Storage 的 worklet 中读取 Protected Audience 的兴趣群体。interestGroups() 方法会返回一个 StorageInterestGroup 对象数组,其中包括 AuctionInterestGroup 和 GenerateBidInterestGroup 属性。

以下示例展示了如何读取浏览上下文兴趣组,以及可以对检索到的兴趣组执行的一些可能的操作。可能使用的两种操作是查找兴趣组的数量和查找出价次数最多的兴趣组。

async function analyzeInterestGroups() {
  const interestGroups = await interestGroups();
  numIGs = interestGroups.length;
  maxBidCountIG = interestGroups.reduce((max, cur) => { return cur.bidCount > max.bidCount ? cur : max; }, interestGroups[0]);
  console.log("The IG that bid the most has name " + maxBidCountIG.name);
}

工作程序模块的加载来源决定了默认读取的兴趣组的来源。如需详细了解默认工作程序源以及如何更改它,请参阅 Shared Storage API 演练中的“执行 Shared Storage 和 Private Aggregation”部分。

选项

所有共享存储空间修饰符方法都支持将可选的 options 对象作为最后一个实参。

withLock

withLock 选项是可选的。如果指定了此选项,该方法会在继续操作之前,指示使用 Web Locks API 为定义的资源获取锁。请求锁定时,会传递锁定名称。该名称表示一种资源,其使用情况在来源内的多个标签页、工作器或代码之间进行协调。

withLock 选项可与以下共享存储空间修饰符方法搭配使用:

  • set
  • append
  • 删除
  • 清除
  • 批量更新

您可以使用 JavaScript 或响应标头来设置锁定:

  • 使用 JavaScript

    sharedStorage.set('myKey', 'myValue', { withLock: 'myResource' });
    
  • 使用响应标头

    Shared-Storage-Write : set;key="myKey";value="myValue",options;with_lock="myResource"
    

共享存储锁按数据来源进行分区。这些锁独立于使用 LockManager request() 方法获得的任何锁,无论它们是否位于 window 或 worker 上下文中。不过,它们与在 SharedStorageWorklet 上下文中通过 request() 获取的锁具有相同的范围。

虽然 request() 方法支持各种配置选项,但在共享存储空间内获取的锁始终遵循以下默认设置:

  • mode: "exclusive":不能同时持有任何其他同名锁。
  • steal: false:不会释放同名的现有锁来满足其他请求。
  • ifAvailable: false:请求无限期等待,直到锁可用。
何时使用 withLock

在可能同时运行多个 worklet(例如,网页上的多个 worklet 或不同标签页中的多个 worklet)且每个 worklet 都查看相同数据的情况下,锁非常有用。在这种情况下,最好使用锁封装相关的工作单元代码,以确保一次只有一个工作单元在处理报告。

锁的另一个用途是,如果工作程序中需要一起读取多个键,并且这些键的状态应保持同步,则可以使用锁。在这种情况下,应使用锁封装 get 调用,并确保在写入这些键时获取相同的锁。

锁的顺序

由于 Web 锁的特性,修饰符方法可能不会按您定义的顺序执行。如果第一个操作需要锁定并且被延迟,则第二个操作可能会在第一个操作完成之前开始。

例如:

// This line might pause until the lock is available.
sharedStorage.set('keyOne', 'valueOne', { withLock: 'resource-lock' });

// This line will run right away, even if the first one is still waiting.
sharedStorage.set('keyOne', 'valueTwo');
修改多个键的示例

使用 batchUpdate() 的 withLock 选项可确保与其他获取同一锁的并发操作实现互斥。您只能将 withLock 选项应用于整个批次的 batchUpdate()。将 withLock 应用于批处理中的任何单个方法对象都会抛出异常。

此示例使用锁来确保工作单元中的读取和删除操作同时发生,从而防止工作单元外部的干扰。

以下 modify-multiple-keys.js 示例使用 modify-lock 为 keyOne 和 keyTwo 设置新值,然后从工作单元执行 modify-multiple-keys 操作:

// modify-multiple-keys.js
sharedStorage.batchUpdate([
    new SharedStorageSetMethod('keyOne', calculateValueFor('keyOne')),
    new SharedStorageSetMethod('keyTwo', calculateValueFor('keyTwo'))
], { withLock: 'modify-lock' });

const modifyWorklet = await sharedStorage.createWorklet('modify-multiple-keys-worklet.js');
await modifyWorklet.run('modify-multiple-keys');

然后,在 modify-multiple-keys-worklet.js 中,您可以使用 navigator.locks.request() 请求锁定,以便根据需要读取和修改密钥

// modify-multiple-keys-worklet.js
class ModifyMultipleKeysOperation {
  async run(data) {
    await navigator.locks.request('modify-lock', async (lock) => {
      const value1 = await sharedStorage.get('keyOne');
      const value2 = await sharedStorage.get('keyTwo');

      // Do something with `value1` and `value2` here.

      await sharedStorage.delete('keyOne');
      await sharedStorage.delete('keyTwo');
    });
  }
}
register('modify-multiple-keys', ModifyMultipleKeysOperation);

上下文切换

共享存储空间数据会写入调用来源的浏览上下文的来源(例如,https://example.adtech.com)。

当您使用 <script> 代码加载第三方代码时,该代码会在嵌入者的浏览环境中执行。因此,当第三方代码调用 sharedStorage.set() 时,数据会写入嵌入者的共享存储空间。当您在 iframe 中加载第三方代码时,该代码会收到新的浏览上下文,并且其来源是 iframe 的来源。因此,从 iframe 发出的 sharedStorage.set() 调用会将数据存储到 iframe 源的共享存储空间中。

第一方环境

如果第一方网页嵌入了调用 sharedStorage.set() 或 sharedStorage.delete() 的第三方 JavaScript 代码,则键值对会存储在第一方上下文中。

存储在嵌入了第三方 JavaScript 的第一方网页中的数据。
此图显示了存储在嵌入了第三方 JavaScript 的第一方网页中的数据。

第三方情境

通过创建 iframe 并从 iframe 内的 JavaScript 代码中调用 set() 或 delete(),可以将键值对存储在广告技术平台或第三方上下文中。

存储在广告技术平台或第三方环境中的数据。
此图显示了存储在广告技术或第三方环境中的数据。

Private Aggregation API

如需衡量存储在 Shared Storage 中的可汇总数据,您可以使用 Private Aggregation API。

如需创建报告,请在包含桶和值的小程序中调用 contributeToHistogram()。该分桶由一个无符号 128 位整数表示,必须作为 BigInt 传递给函数。该值为正整数。

为保护隐私,报告的载荷(包含分桶和值)在传输过程中会进行加密,并且只能使用汇总服务进行解密和汇总。

浏览器还会限制网站对输出查询的贡献。具体来说,贡献预算会限制给定浏览器在给定时间窗口内通过所有分桶从单个网站获得的所有报告的总数。如果超出当前预算,系统将不会生成报告。

privateAggregation.contributeToHistogram({
  bucket: BigInt(myBucket),
  value: parseInt(myBucketValue)
});

执行 Shared Storage 和 Private Aggregation

默认情况下,将共享存储与 createWorklet() 搭配使用时,数据分区来源将是调用浏览上下文的来源,而不是工作程序脚本本身的来源。

如需更改默认行为,请在调用 createWorklet 时设置 dataOrigin 属性。

  • dataOrigin: "context-origin":(默认)数据存储在调用浏览上下文的源的共享存储空间中。
  • dataOrigin: "script-origin":数据存储在工作程序脚本来源的共享存储空间中。您必须选择启用此模式。
  • dataOrigin: "https://custom-data-origin.example":数据存储在自定义数据源的共享存储空间中。启用此模式需要选择启用,并且需要自定义数据源所有者的同意,详情请参阅自定义数据源。
sharedStorage.createWorklet(scriptUrl, {dataOrigin: "script-origin"});

如需选择启用,在使用 "script-origin" 或自定义来源时,脚本端点必须使用标头 Shared-Storage-Cross-Origin-Worklet-Allowed 进行响应。对于非同源请求,还应启用 CORS。

Shared-Storage-Cross-Origin-Worklet-Allowed : ?1
Access-Control-Allow-Origin: *

您还可以使用第三方 iframe 运行跨源脚本,在这种情况下,共享存储区操作将位于第三方浏览上下文中。

使用跨源 iframe

需要使用 iframe 来调用共享存储空间 worklet。

在广告的 iframe 中,通过调用 addModule() 加载 worklet 模块。如需运行在 sharedStorageWorklet.js worklet 文件中注册的方法,请在同一广告 iframe JavaScript 中调用 sharedStorage.run()。

const sharedStorageWorklet = await window.sharedStorage.createWorklet(
  'https://any-origin.example/modules/sharedStorageWorklet.js'
);
await sharedStorageWorklet.run('shared-storage-report', {
  data: { campaignId: '1234' },
});

在 worklet 脚本中,您需要创建一个具有异步 run 方法的类,并对其进行 register,以便在广告的 iframe 中运行。在 sharedStorageWorklet.js 内:

class SharedStorageReportOperation {
  async run(data) {
    // Other code goes here.
    bucket = getBucket(...);
    value = getValue(...);
    privateAggregation.contributeToHistogram({
      bucket,
      value
    });
  }
}
register('shared-storage-report', SharedStorageReportOperation);

使用非同源请求

借助 Shared Storage 和 Private Aggregation,您可以创建跨源 worklet,而无需使用跨源 iframe。

第一方网页还可以调用 createWorklet() 来调用跨源 JavaScript 端点。创建 worklet 时,您需要将 worklet 的数据分区来源设置为脚本来源。

async function crossOriginCall() {
  const privateAggregationWorklet = await sharedStorage.createWorklet(
    'https://cross-origin.example/js/worklet.js',
    { dataOrigin: 'script-origin' }
  );
  await privateAggregationWorklet.run('pa-worklet');
}
crossOriginCall();

跨源 JavaScript 端点必须使用标头 Shared-Storage-Cross-Origin-Worklet-Allowed 进行响应,并注意已为请求启用 CORS。

Shared-Storage-Cross-Origin-Worklet-Allowed : ?1

使用 createWorklet() 创建的微程序将具有 selectURL 和 run()。 addModule() 不适用于此功能。

class CrossOriginWorklet {
  async run(data){
    // Other code goes here.
    bucket = getBucket(...);
    value = getValue(...);
    privateAggregation.contributeToHistogram({
      bucket,
      value
    });
  }
}

自定义数据来源

当 dataOrigin 设置为有效来源时,dataOrigin 的所有者必须通过在路径 /.well-known/shared-storage/trusted-origins 中托管列出工作区脚本来源的 JSON 文件,同意处理该 dataOrigin 的共享存储空间。该文件应为包含键 scriptOrigin 和 contextOrigin 的对象数组。这些键的值可以是字符串,也可以是字符串数组。

使用以下信息构建 trusted-origins 文件:

  • 来电者背景信息
  • 工作程序脚本来源和网址
  • 数据来源和所有者

下表显示了如何根据此信息构建 trusted-origins 文件:

来电者背景信息 工作程序脚本网址 数据来源 数据所有者 数据源所有者的 trusted-origins JSON 文件
https://publisher.example https://publisher.example/script.js context-origin https://publisher.example 不需要 JSON
https://publisher.example https://ad.example/script.js script-origin https://ad.example 不需要 JSON
https://publisher.example https://cdn-ad.example/script.js https://ad.example https://ad.example
[{
  "scriptOrigin": "https://cdn-ad.example",
  "contextOrigin": "https://publisher.example"
}]
      
任何来电者 https://cdn-ad.example/script.js https://ad.example https://ad.example
[{
  "scriptOrigin": "https://cdn-ad.example",
  "contextOrigin": "*"
}]
      
https://publisher-a.example,或 https://publisher-b.example https://cdn-ad.example/script.js https://ad.example https://ad.example
[{
  "scriptOrigin": "https://cdn-ad.example",
  "contextOrigin": [
      "https://publisher-a.example",
      "https://publisher-b.example"
  ]
}]
      
https://publisher.example https://cdn-a-ad.example/script.js,或 https://cdn-b-ad.example/script.js https://ad.example https://ad.example
[{
  "scriptOrigin": [
    "https://cdn-a-ad.example",
    "https://cdn-b-ad.example"
  ],
  "contextOrigin": "https://publisher.example"
}]
      

例如,以下 JSON 可托管在 https://custom-data-origin.example/.well-known/shared-storage/trusted-origins,并合并 https://custom-data-origin.example 源的所有允许的共享存储空间数据处理器。

[
  {
    "scriptOrigin": "https://script-origin.a.example",
    "contextOrigin": "https://context-origin.a.example"
  },
  {
    "scriptOrigin": "https://script-origin.b.example",
    "contextOrigin": [
      "https://context-origin.a.example",
      "https://context-origin.b.example"
    ]
}]

后续步骤

以下页面介绍了 Shared Storage API 和 Private Aggregation API 的重要方面。

熟悉这些 API 后,您就可以开始收集报告了。这些报告会以 POST 请求的形式发送到以下端点,并以 JSON 格式包含在请求正文中。

  • 调试报告 - context-origin/.well-known/private-aggregation/debug/report-shared-storage
  • 报告 - context-origin/.well-known/private-aggregation/report-shared-storage

收集报告后,您可以使用本地测试工具进行测试,也可以设置用于汇总服务的可信执行环境来获取汇总报告。

分享您的反馈

您可以在 GitHub 上分享对 API 和文档的反馈。