# 已保存的密码存储

> 从 Provision Service Worker 读取、添加、清空和观察 Chromium Profile 密码。

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

**能力：**`passwords`。**API：**`chrovia.passwords`。即使拥有 `unrestrictedApi`，也只对 Provision 扩展 Service Worker 开放。订阅变更消息还需要 `messaging`。

## 添加与列出测试凭据

使用可丢弃的 Profile。此操作添加真实密码存储记录，不是临时填充表单：

```js
async function addTestLogin() {
  const passwords = globalThis.chrovia?.passwords;
  if (!passwords) {
    throw new Error('passwords is required in a Provision Service Worker');
  }
  await passwords.add([{
    origin: 'https://example.com/login',
    signon_realm: 'https://example.com/',
    username: 'sdk-test-user',
    password: 'replace-with-a-test-password',
  }]);
  const rows = await passwords.list();
  console.log('Saved credential count:', Array.isArray(rows) ? rows.length : 0);
}
```

由明确测试命令调用，不要每次 Worker 唤醒都添加。`add()` 直接接受数组，不是 `{credentials: [...]}`。每项需要有效 `origin` URL，以及非空字符串 `username`、`password`。`signon_realm` 可选；缺失或为空时从 origin 计算。使用真实 HTTP(S) 登录 URL 和匹配的登录域。

## 方法

| 方法 | 行为 |
| --- | --- |
| `add(credentials)` | 添加已保存密码，Promise 解析为 `undefined` |
| `list()` | 返回包含 `origin`、`signon_realm`、`username`、`password` 的凭据对象 Promise |
| `empty()` | 请求清空 Profile 密码存储，Promise 解析为 `undefined` |
| `watch()` | 观察调用者当前密码存储的变更，Promise 解析为 `undefined` |

`list()` 排除阻止项、联合登录项、空用户名和空密码。结果包含明文秘密，不要完整输出日志或通过不受限制的 popup/页面命令暴露。空结果也可能来自读取失败或存储不可用，不是健康检查结论。

`add()` 不会使已保存密码列表等于传入数组，也不保证仅按站点和用户名更新已有登录。无效条目会跳过。同一输入数组中重复的登录域/用户名组合只添加一次。已有登录的 origin 或表单信息不同时，可能保留为独立条目。依赖替换行为前，应先在测试档案中核对结果列表。

## 观察变更

每次 Worker 启动时先注册监听，再调用 `watch()`：

```js
const sdk = globalThis.chrovia;
if (sdk?.passwords && sdk.messaging) {
  sdk.messaging.on('chrovia.passwords.changed', () => {
    console.log('Password store changed; schedule a controlled refresh');
  });
  sdk.passwords.watch().catch(console.error);
}
```

事件是表示发生变更的空对象，不携带密码。需要调用 `watch()` 才能启用通知；`list()`、`add()` 和单纯订阅通道都不会启用。每次 Provision Service Worker 启动都调用 `watch()`，以便档案重新打开后恢复通知。单纯 Worker 休眠不会停止已有观察。

插件自己的写入也会产生变更。避免每次变更触发写入、写入又触发变更的回声循环。合并应用侧刷新请求，并将秘密保持在受控存储范围内。

## 清空与启动导入

`empty()` 是破坏性操作，与 `add()` 独立。其 Promise 不报告清空是否成功，正常解析不证明所有密码已删除。导入替换列表或向受控凭据库上传本地密码前，应核对结果；之后调用 `add()` 不会删除清空失败留下的记录。同时注意，`list()` 读取失败时也可能返回空结果。

导入后，已打开的登录页可能继续提供旧密码。应在打开登录页前完成计划中的启动导入，或刷新相关页面后再测试自动填充和保存提示。

使用 `add()` 导入已保存密码；只向 EP 添加密码不会自动导入。需要通过配置自动填充时，参见[注入凭据](https://console.getchrovia.com/zh/docs/sdk-injected-credentials)。此 API 不管理 TOTP 或 WebAuthn 通行密钥。
