# SDK Extended Preferences

> 配置浏览器实例，读写应用数据，并了解 Extended Preferences 的 License 与签名规则。

Source: https://console.getchrovia.com/zh/docs/sdk-extended-preferences

**Extended Preferences** 是位于 `--user-data-dir` 指定目录中的 JSON 文件，文件名必须为 `Extended Preferences`，不带 `.json`。它与 Chromium 自身的 `Preferences` 文件、`Chrovia License` 都是不同的文件。

如果还没有准备浏览器实例和 Provision 扩展，请先阅读 [SDK 开发入门](https://console.getchrovia.com/zh/docs/sdk-getting-started)。

## 文档结构

浏览器配置放在顶层 `internal` 对象中，应用自有数据可以放在它旁边。需要签名时，`signature` 是单独的顶层对象，不是 `internal` 的子项。

下面是完整的未签名示例，适用于已授予 `extendedPrefs` 和 `instanceAppearance`、但**没有**授予 `signedExtendedPrefs` 的 License：

```json
{
  "internal": {
    "instance_appearance": {
      "label": "SDK Example",
      "background_color": "#059669",
      "foreground_color": "#FFFFFF"
    }
  },
  "sdk_example": {
    "message": "Hello from Chrovia SDK",
    "counter": 0
  }
}
```

在启动前写入文件。各项内核功能仍需对应的 License 能力：添加 `instance_appearance` 不会授予 `instanceAppearance`。除非某项功能明确支持运行时更新，否则应将内核配置视为启动配置；通过 API 写入 JSON 并不等于所有功能都会热更新。

## Provision 默认文件与实例数据

可以在 Provision 根目录中放置初始 `Extended Preferences`，与根目录的 `manifest.json` 和 `Extensions` 文件夹并列。

首次启动时，如果用户数据目录没有 `Extended Preferences`，浏览器会将 Provision 中的文件复制过去，再加载实例副本。已有实例文件不会被覆盖，因此修改 Provision 中的初始文件不会更新现有实例。复制失败时会记录警告，并按文件缺失继续处理。

复制后的文件也必须满足 License 对已签名配置的要求。

## JavaScript 读取、写入与删除

需要 `extendedPrefs` 能力，以及允许访问 API 的上下文，通常是 Provision Service Worker。

| 方法 | 返回值 |
| --- | --- |
| `chrovia.prefs.get(key)` | Promise，解析为保存的值；不可用或键不存在时为 `undefined` |
| `chrovia.prefs.set(key, value)` | Promise，解析为 `undefined` |
| `chrovia.prefs.remove(key)` | Promise，解析为 `undefined` |

键名是从**整个文档根节点**开始的点分路径。读取内核配置时需要带上 `internal.`：

```js
async function readAndWritePrefs() {
  const prefs = globalThis.chrovia?.prefs;
  if (!prefs) {
    throw new Error('extendedPrefs is unavailable');
  }

  const label = await prefs.get('internal.instance_appearance.label');
  await prefs.set('sdk_example.message', 'Updated by the extension');
  const message = await prefs.get('sdk_example.message');
  await prefs.remove('sdk_example.counter');
  console.log({ label, message });
}

readAndWritePrefs().catch(console.error);
```

使用兼容 JSON 的值。值无法转换为可保存的数据时，`set()` 会以 `DataCloneError` 拒绝 Promise；应捕获错误并修正输入。这些方法不是事务，先读后写也不是原子递增。写入 Promise 成功解析，不代表数据已保存到磁盘或授权一定有效：偏好不可用、验证失败等情况可能使写入不执行任何操作。

## 配置签名

License 授予 `signedExtendedPrefs` 时，必须提供有效签名的文件。没有该能力时，浏览器不要求 Extended Preferences 签名，但仍然验证 License。

从配置提供方获取与 License 匹配的完整已签名文件。不要自行构造或编辑 `signature` 对象。修改受保护的设置前，应安排获取替换文件；浏览器没有用于签名的命令或 API。

将需要修改的应用数据放在 `internal` 外部，例如 `sdk_example`。对于已签名文件，通过编辑器或 `prefs.set()` 修改 `internal` 可能使文件在下次启动时不可用；应获取替换的已签名文件。

如果必需的验证失败，浏览器会阻止**所有** Extended Preferences 读写，包括未签名的应用字段。`get()` 可能返回 `undefined`，`set()` 和 `remove()` 则可能正常解析却没有修改数据。不要将这些返回值当成签名验证 API。

## 配置下载目录

`internal.profile_prefs` 接受少量白名单内的 Chromium Profile 偏好，需要 `extendedPrefs` 能力。它不是通用 Chrome 策略或 Local State API。

目录设置支持的键是 `download.default_directory` 和 `savefile.default_directory`。下面的 Windows 配置片段分别设置这两个值：

```json
{
  "internal": {
    "profile_prefs": {
      "download.default_directory": "C:\\SDK\\downloads\\instance-01",
      "savefile.default_directory": "C:\\SDK\\downloads\\instance-01"
    }
  }
}
```

如果需要签名，应先合并完整配置，再获取已签名文件。使用符合运行机器操作系统的绝对路径；相对路径和包含 `..` 的路径会被拒绝。如果下载前就需要目录存在，请由启动器创建。

建议像示例一样使用专用子目录。首次设置时，如果选择的路径恰好是桌面目录，或在 Linux 上恰好是用户主目录，浏览器可能改用默认下载目录。它们的子目录不受这一精确路径例外影响。请通过一次测试下载核对实际位置。

这些目录设置在启动时生效，并保存在档案中。它们不会锁定设置界面，不会关闭下载询问，也不会在删除 Extended Preferences 配置项后自动恢复原值。不支持的键会被跳过并记录日志。两个目录值不会自动互相同步。

## 配置启动页面

`internal.profile_prefs` 也支持整数键 `session.restore_on_startup`：`1` 表示继续浏览上次打开的网页，`5` 表示打开新标签页。其他值和类型会被拒绝。

```json
{
  "internal": {
    "profile_prefs": {
      "session.restore_on_startup": 1
    }
  }
}
```

此设置在启动时生效，并保存在档案中。它不会锁定设置界面，也不会在删除该配置项后自动恢复原值。显式传入的启动网址和启动参数仍遵循 Chromium 的启动逻辑。

签名验证能力的独立说明见 [Extended Preferences 签名验证](https://console.getchrovia.com/zh/docs/sdk-signed-extended-prefs)。其他内核配置项见[完整能力索引](https://console.getchrovia.com/zh/docs/sdk-capabilities)。
