@yeying-community/web3-bs 1.0.16 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/profile.d.ts +34 -0
- package/dist/web3-bs.esm.js +106 -1
- package/dist/web3-bs.esm.js.map +1 -1
- package/dist/web3-bs.umd.js +111 -0
- package/dist/web3-bs.umd.js.map +1 -1
- package/docs/SDK/350/203/275/345/212/233.md +272 -0
- package/docs/UCAN/346/234/211/346/225/210/346/234/237/344/270/216/347/273/255/346/234/237/346/234/272/345/210/266.md +305 -0
- package/docs//345/212/240/350/247/243/345/257/206/346/234/215/345/212/241.md +146 -0
- package/docs//345/274/200/346/224/276/346/216/245/345/217/243/350/247/204/350/214/203.yaml +668 -0
- package/docs//345/277/253/351/200/237/344/270/212/346/211/213.md +476 -0
- package/docs//346/226/207/346/241/243/345/257/274/350/210/252.md +54 -0
- package/docs//347/247/273/345/212/250/347/253/257/350/256/244/350/257/201/344/270/216/346/216/210/346/235/203/351/200/211/345/236/213/346/214/207/345/215/227.md +151 -0
- package/docs//351/222/261/345/214/205/350/265/204/346/226/231/346/216/210/346/235/203.md +154 -0
- package/package.json +3 -2
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# SDK能力
|
|
2
|
+
|
|
3
|
+
本文档合并原有:
|
|
4
|
+
- `SDK能力与账户管理设计.md`
|
|
5
|
+
- `能力矩阵.md`
|
|
6
|
+
- `库的定位与能力边界.md`
|
|
7
|
+
|
|
8
|
+
目标:用一个文档同时回答“这是什么、怎么选路线、怎么落地接入”。
|
|
9
|
+
|
|
10
|
+
## 1. 定位与边界
|
|
11
|
+
|
|
12
|
+
`@yeying-community/web3-bs` 的定位是:
|
|
13
|
+
|
|
14
|
+
> 浏览器端 DApp 连接、会话、认证、授权与存储接入 SDK
|
|
15
|
+
|
|
16
|
+
更具体地说:
|
|
17
|
+
- 以 EIP-1193 钱包为优先入口
|
|
18
|
+
- 统一封装 Provider 发现、连接会话、SIWE 登录、UCAN 授权、中心化认证与 WebDAV 存储接入
|
|
19
|
+
- 面向 DApp 前端集成,不承担后端鉴权逻辑
|
|
20
|
+
|
|
21
|
+
它是什么:
|
|
22
|
+
- 浏览器端前端集成 SDK
|
|
23
|
+
- DApp 访问编排层(钱包 / App 钱包 / 中心化服务 / WebDAV)
|
|
24
|
+
- 会话管理层(连接会话、认证会话、授权会话)
|
|
25
|
+
|
|
26
|
+
它不是什么:
|
|
27
|
+
- 不是链上交易构造与合约调用 SDK
|
|
28
|
+
- 不是后端 UCAN/JWT 校验库
|
|
29
|
+
- 不是 Node.js 服务端 SDK
|
|
30
|
+
- 不是 WalletConnect / deep link / app bridge 本身
|
|
31
|
+
|
|
32
|
+
实现边界:
|
|
33
|
+
- 钱包协议语义由钱包仓库定义
|
|
34
|
+
- audience / capability / app scope 由服务端定义
|
|
35
|
+
- `web3-bs` 负责前端如何调用这些能力
|
|
36
|
+
|
|
37
|
+
## 2. 路线与能力矩阵
|
|
38
|
+
|
|
39
|
+
路线定义:
|
|
40
|
+
- 钱包插件:浏览器内已安装插件钱包
|
|
41
|
+
- App 钱包:钱包 App 或桥接层向前端暴露 Provider
|
|
42
|
+
- 中心化服务:不依赖链上钱包,直接接中心化认证/授权
|
|
43
|
+
|
|
44
|
+
| 能力 | 钱包插件 | App 钱包 | 中心化服务 | 说明 |
|
|
45
|
+
| --- | --- | --- | --- | --- |
|
|
46
|
+
| Provider 发现 | ✅ | ⚠️ | ❌ | `getProvider` 适合插件;App 钱包通常需要外部先注入/适配 |
|
|
47
|
+
| 请求账户 | ✅ | ✅ | ❌ | 依赖 EIP-1193 `eth_requestAccounts` |
|
|
48
|
+
| 标准消息签名 | ✅ | ✅ | ❌ | `signMessage` |
|
|
49
|
+
| challenge / SIWE 登录 | ✅ | ✅ | ❌ | `loginWithChallenge` |
|
|
50
|
+
| access token 缓存 / refresh | ✅ | ✅ | ✅ | `setAccessToken` / `authFetch` / `refreshAccessToken` |
|
|
51
|
+
| 钱包用户名 / 邮箱授权 | ✅ | ❌ | ❌ | `connectAndGetWalletProfile` / `getWalletProfile` |
|
|
52
|
+
| UCAN Session 创建 | ✅ | ⚠️ | ❌ | 优先 `yeying_ucan_session`;不可用时使用本地 session |
|
|
53
|
+
| UCAN Root / Invocation | ✅ | ⚠️ | ❌ | Root 走 SIWE;Invocation 走 UCAN 签名 |
|
|
54
|
+
| 多后端 UCAN 授权 | ✅ | ⚠️ | ❌ | 通过 delegation/invocation 按 audience/capability 下发 |
|
|
55
|
+
| 中心化 session token | ✅ | ✅ | ✅ | `createCentralSession` |
|
|
56
|
+
| 中心化 UCAN 签发 | ✅ | ✅ | ✅ | `issueCentralUcan` |
|
|
57
|
+
| WebDAV 直接访问(JWT/UCAN) | ✅ | ✅ | ✅ | `createWebDavClient` |
|
|
58
|
+
| WebDAV + UCAN 自动初始化 | ✅ | ⚠️ | ❌ | `initWebDavStorage` |
|
|
59
|
+
| DApp 会话编排 | ✅ | ⚠️ | ❌ | `initDappSession` |
|
|
60
|
+
| 加解密(站点授权后静默) | ✅ | ⚠️ | ❌ | `encrypt` / `decrypt` / `getSupportedCipherSuites`(钱包插件路线) |
|
|
61
|
+
|
|
62
|
+
说明:
|
|
63
|
+
- `✅`:天然支持
|
|
64
|
+
- `⚠️`:可支持,但需要额外前提(尤其是 provider 适配或钱包能力)
|
|
65
|
+
- `❌`:该路线下不成立或不是典型用法
|
|
66
|
+
|
|
67
|
+
选型建议:
|
|
68
|
+
- 已有浏览器插件:优先钱包插件路线
|
|
69
|
+
- 移动端 Web 且可接 App 钱包:走 App 钱包路线
|
|
70
|
+
- 更关注接入效率或无钱包条件:走中心化服务路线
|
|
71
|
+
- 需要多后端统一授权:优先 UCAN 路线
|
|
72
|
+
|
|
73
|
+
## 3. 账户与会话管理设计
|
|
74
|
+
|
|
75
|
+
### 3.1 账户使用策略
|
|
76
|
+
|
|
77
|
+
- 页面初始化先读取 `eth_accounts`,避免无意义弹窗
|
|
78
|
+
- 用户主动点击连接时再调用 `eth_requestAccounts`
|
|
79
|
+
- 优先复用“上次选择账户”,不存在时回退 `accounts[0]`
|
|
80
|
+
|
|
81
|
+
示例:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { getProvider, getPreferredAccount, watchProvider } from '@yeying-community/web3-bs';
|
|
85
|
+
|
|
86
|
+
const stopProviderWatch = watchProvider(({ present }) => {
|
|
87
|
+
console.log('wallet provider present:', present);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const provider = await getProvider({ timeoutMs: 3000 });
|
|
91
|
+
const { account } = await getPreferredAccount({
|
|
92
|
+
provider,
|
|
93
|
+
autoConnect: false,
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`requestAccounts` 默认会对同一 provider 的并发连接请求做去重。业务按钮仍建议在提交中展示 loading/disabled 状态,但 SDK 会避免重复点击直接触发多个 `eth_requestAccounts`。
|
|
98
|
+
|
|
99
|
+
如果钱包确认窗口已经打开、但被其他页面遮挡,前端不应该继续盲目发起新的连接或签名请求,而应该优先把已有确认窗口拉回前台。为此 SDK 提供了 `focusPendingApproval`:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { focusPendingApproval, requestAccounts } from '@yeying-community/web3-bs';
|
|
103
|
+
|
|
104
|
+
const pending = await focusPendingApproval().catch(() => ({ focused: false }));
|
|
105
|
+
if (!pending.focused) {
|
|
106
|
+
await requestAccounts();
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
适用范围:
|
|
111
|
+
- 钱包连接弹窗
|
|
112
|
+
- 消息签名弹窗
|
|
113
|
+
- 钱包解锁弹窗
|
|
114
|
+
|
|
115
|
+
产品侧建议:
|
|
116
|
+
- 用户重复点击“连接钱包 / 登录 / 签名确认”时,优先调用 `focusPendingApproval`
|
|
117
|
+
- 只有在不存在待确认窗口时,才真正发起新的钱包 RPC
|
|
118
|
+
- 这类逻辑应由 SDK 统一提供,业务应用只负责按钮状态与提示文案
|
|
119
|
+
|
|
120
|
+
### 3.2 账户变更处理
|
|
121
|
+
|
|
122
|
+
账户变化后建议清理认证与授权缓存,重新发起登录授权流程:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { watchAccounts, clearAccessToken, clearUcanSession } from '@yeying-community/web3-bs';
|
|
126
|
+
|
|
127
|
+
const unsubscribe = watchAccounts(provider, () => {
|
|
128
|
+
clearAccessToken({ storeToken: false });
|
|
129
|
+
clearUcanSession();
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 3.3 三层会话模型
|
|
134
|
+
|
|
135
|
+
- 连接会话:钱包连接状态与当前账户
|
|
136
|
+
- 认证会话:SIWE/JWT 或中心化 token
|
|
137
|
+
- 授权会话:UCAN Session、Root、Delegation、Invocation
|
|
138
|
+
|
|
139
|
+
## 4. 登录与授权模型
|
|
140
|
+
|
|
141
|
+
### 4.1 SIWE + JWT(单后端优先)
|
|
142
|
+
|
|
143
|
+
典型流程:
|
|
144
|
+
1. `POST /api/v1/public/auth/challenge`
|
|
145
|
+
2. 钱包签名 challenge
|
|
146
|
+
3. `POST /api/v1/public/auth/verify`
|
|
147
|
+
4. 使用 access token 调用业务接口
|
|
148
|
+
5. access 过期后 `POST /api/v1/public/auth/refresh`
|
|
149
|
+
6. 退出时 `POST /api/v1/public/auth/logout`
|
|
150
|
+
|
|
151
|
+
推荐 API 组合:
|
|
152
|
+
- `loginWithChallenge`
|
|
153
|
+
- `authFetch`
|
|
154
|
+
- `refreshAccessToken`
|
|
155
|
+
- `logout`
|
|
156
|
+
|
|
157
|
+
### 4.2 UCAN(多后端授权)
|
|
158
|
+
|
|
159
|
+
典型流程:
|
|
160
|
+
1. `createUcanSession`
|
|
161
|
+
2. `getOrCreateUcanRoot`(SIWE bridge)
|
|
162
|
+
3. 可选 `createDelegationUcan`
|
|
163
|
+
4. `createInvocationUcan`(按 audience + capability)
|
|
164
|
+
5. `authUcanFetch` 访问目标后端
|
|
165
|
+
|
|
166
|
+
说明:
|
|
167
|
+
- Root 建议作为跨后端“统一授权根”
|
|
168
|
+
- Invocation 作为短期令牌按后端和能力发放
|
|
169
|
+
- Root 或 Invocation 过期后按链路重建
|
|
170
|
+
- 发起长流式请求前,不只判断 token 未过期,还要判断剩余有效期是否足够支撑本次请求
|
|
171
|
+
|
|
172
|
+
### 4.3 SIWE 与 UCAN 对比
|
|
173
|
+
|
|
174
|
+
| 维度 | SIWE/JWT | UCAN |
|
|
175
|
+
| --- | --- | --- |
|
|
176
|
+
| Token 来源 | 后端签发 | 前端组装、后端校验 |
|
|
177
|
+
| 主要场景 | 单后端登录 | 多后端能力委任 |
|
|
178
|
+
| 续期方式 | refresh 接口 | 重建 invocation(必要时重建 root) |
|
|
179
|
+
| 权限表达 | 后端角色/策略 | `with/can` capability |
|
|
180
|
+
|
|
181
|
+
## 5. API 能力分组
|
|
182
|
+
|
|
183
|
+
### 5.1 Provider 与钱包接入
|
|
184
|
+
|
|
185
|
+
- `getProvider` / `requireProvider`
|
|
186
|
+
- `requestAccounts` / `getAccounts` / `watchAccounts`
|
|
187
|
+
- `getPreferredAccount`
|
|
188
|
+
- `getChainId` / `getBalance`
|
|
189
|
+
- `signMessage`
|
|
190
|
+
|
|
191
|
+
### 5.2 SIWE 与 JWT
|
|
192
|
+
|
|
193
|
+
- `loginWithChallenge`
|
|
194
|
+
- `authFetch`
|
|
195
|
+
- `refreshAccessToken`
|
|
196
|
+
- `logout`
|
|
197
|
+
- `getAccessToken` / `setAccessToken` / `clearAccessToken`
|
|
198
|
+
|
|
199
|
+
### 5.3 UCAN 授权
|
|
200
|
+
|
|
201
|
+
- `createUcanSession` / `getUcanSession`
|
|
202
|
+
- `createRootUcan` / `getOrCreateUcanRoot`
|
|
203
|
+
- `createDelegationUcan`
|
|
204
|
+
- `createInvocationUcan` / `getOrCreateInvocationUcan`
|
|
205
|
+
- `authUcanFetch`
|
|
206
|
+
- `normalizeUcanCapabilities`
|
|
207
|
+
- `getUcanTokenTiming` / `isUcanTokenFresh`
|
|
208
|
+
- `classifyUcanAuthError`
|
|
209
|
+
|
|
210
|
+
### 5.4 中心化 UCAN
|
|
211
|
+
|
|
212
|
+
- `getCentralIssuerInfo`
|
|
213
|
+
- `createCentralSession`
|
|
214
|
+
- `issueCentralUcan`
|
|
215
|
+
- `createAndIssueCentralUcan`
|
|
216
|
+
- `authCentralUcanFetch`
|
|
217
|
+
|
|
218
|
+
### 5.5 WebDAV 与会话编排
|
|
219
|
+
|
|
220
|
+
- `createWebDavClient`
|
|
221
|
+
- `createShareLink` / `listShareLinks` / `revokeShareLink`
|
|
222
|
+
- `initWebDavStorage`
|
|
223
|
+
- `initDappSession`
|
|
224
|
+
- `deriveAppIdFromLocation` / `deriveAppIdFromHost`
|
|
225
|
+
|
|
226
|
+
### 5.6 加解密服务
|
|
227
|
+
|
|
228
|
+
- `encrypt` — 用命名安全套件加密(默认 `aes-256-gcm`)
|
|
229
|
+
- `decrypt` — 解密 v1 格式密文
|
|
230
|
+
- `getSupportedCipherSuites` — 列出可用套件
|
|
231
|
+
- `CipherError` / `isUserRejectedWalletAction` — 错误分类
|
|
232
|
+
|
|
233
|
+
详细用法、安全模型、限制见 [`加解密服务.md`](./加解密服务.md)。
|
|
234
|
+
|
|
235
|
+
## 6. 推荐接入组合
|
|
236
|
+
|
|
237
|
+
| 场景 | 推荐组合 |
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| 插件钱包 + 单后端登录 | `getProvider` + `requestAccounts` + `loginWithChallenge` + `authFetch` |
|
|
240
|
+
| 插件钱包 + UCAN 多后端 | `createUcanSession` + `getOrCreateUcanRoot` + `createInvocationUcan` |
|
|
241
|
+
| App 钱包 + 登录 | `requestAccounts` + `signMessage` + `loginWithChallenge` + `authFetch` |
|
|
242
|
+
| 中心化 JWT | `setAccessToken` + `authFetch` + `createWebDavClient` |
|
|
243
|
+
| 中心化 UCAN | `createCentralSession` + `issueCentralUcan` + `authCentralUcanFetch` |
|
|
244
|
+
| WebDAV 直接访问 | `createWebDavClient` |
|
|
245
|
+
| WebDAV + UCAN 自动初始化 | `initWebDavStorage` |
|
|
246
|
+
|
|
247
|
+
长请求建议:
|
|
248
|
+
- 普通短请求使用默认 token skew 即可,skew 只用于判断是否自动换新 Invocation。
|
|
249
|
+
- token 已过期或进入 skew 窗口时,应自动创建新的 Invocation,不应提前向用户报错。
|
|
250
|
+
- 如果服务端仍返回 `UCAN expired` / 401,`authUcanFetch` 会在可重放请求体场景下自动刷新 Invocation 并重试一次。
|
|
251
|
+
- 详见 [UCAN有效期与续期机制](./UCAN有效期与续期机制.md)。
|
|
252
|
+
|
|
253
|
+
## 7. 工程与安全注意事项
|
|
254
|
+
|
|
255
|
+
- 仅支持浏览器环境(依赖 `window` / `fetch` / `localStorage` / `IndexedDB`)
|
|
256
|
+
- `baseUrl` 只填服务根地址,子路径放 `prefix`
|
|
257
|
+
- capability 资源格式必须与目标服务定义一致(建议统一 `with/can`)
|
|
258
|
+
- 多钱包支持不代表高级能力跨钱包完全等价,取决于钱包能力实现
|
|
259
|
+
- 建议统一 `appId` 生成规则,避免 `localhost` 与 `127.0.0.1` 产生两套目录
|
|
260
|
+
|
|
261
|
+
### 7.1 warehouse 分享链接授权边界
|
|
262
|
+
|
|
263
|
+
- 在 UCAN 链路下,当 capability 使用 `app` scope(例如 `app:all:<appId>`)时,分享链接创建与查询仅允许落在该授权目录内。
|
|
264
|
+
- 在 JWT 或未携带 UCAN `app` scope 的链路下,分享链接范围由后端鉴权策略决定,不自动继承 UCAN 的目录隔离语义。
|
|
265
|
+
- 需要“只能创建/查询授权目录文件分享链接”的场景,建议统一走 UCAN `app` scope 授权。
|
|
266
|
+
|
|
267
|
+
## 8. 相关阅读
|
|
268
|
+
|
|
269
|
+
- [快速上手](./快速上手.md)
|
|
270
|
+
- [UCAN有效期与续期机制](./UCAN有效期与续期机制.md)
|
|
271
|
+
- [移动端认证与授权选型指南](./移动端认证与授权选型指南.md)
|
|
272
|
+
- [开放接口规范](./开放接口规范.yaml)
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# UCAN有效期与续期机制
|
|
2
|
+
|
|
3
|
+
本文档说明 `@yeying-community/web3-bs` 中 UCAN token 的有效期配置、过期判断和推荐续期策略。目标是让所有 DApp 使用同一套规则,而不是在各应用里各自硬编码。
|
|
4
|
+
|
|
5
|
+
## 1. 核心原则
|
|
6
|
+
|
|
7
|
+
`skew` 不是用来提前给用户报错的,而是用来判断“当前 Invocation Token 是否还值得复用”。
|
|
8
|
+
|
|
9
|
+
只要 Root / Session 还有效,SDK 就应该自动生成新的 Invocation Token,用户不应该感知 Invocation 过期。
|
|
10
|
+
|
|
11
|
+
用户真正需要重新操作的钱包授权,只应该发生在 Root / Session 过期、账户变化、能力不足或服务端拒绝继续授权时。
|
|
12
|
+
|
|
13
|
+
## 2. 为什么要自动刷新重试
|
|
14
|
+
|
|
15
|
+
只判断 token “当前还没过期”是不够的,但正确处理方式不是让业务自己预估请求耗时,而是让 SDK 统一处理过期后的刷新重试。
|
|
16
|
+
|
|
17
|
+
典型问题:
|
|
18
|
+
- 用户发起请求时,本地判断 token 仍有效。
|
|
19
|
+
- 服务端因为时钟差、校验策略或处理过程中 token 过期,最终返回 `UCAN expired`。
|
|
20
|
+
- 用户已经等待、上游可能已经消耗 token,但前端拿不到有效结果。
|
|
21
|
+
|
|
22
|
+
正确策略分两层:
|
|
23
|
+
- 请求前优化:用 `skew` 做很小的时钟安全提前量。如果 token 即将过期,就在请求发出前静默生成新的 Invocation Token。
|
|
24
|
+
- 请求后兜底:如果服务端仍返回 `UCAN expired`,SDK 自动重新生成 Invocation Token 并重试一次。
|
|
25
|
+
|
|
26
|
+
## 3. 有效期层次
|
|
27
|
+
|
|
28
|
+
### 3.1 Root UCAN
|
|
29
|
+
|
|
30
|
+
Root UCAN 是用户通过钱包签名建立的授权根。
|
|
31
|
+
|
|
32
|
+
默认有效期:
|
|
33
|
+
- `DEFAULT_UCAN_SESSION_TTL_MS`
|
|
34
|
+
- 当前值:24 小时
|
|
35
|
+
|
|
36
|
+
特点:
|
|
37
|
+
- 通常需要用户钱包签名。
|
|
38
|
+
- 过期后需要重新创建 Root。
|
|
39
|
+
- 如果钱包签名窗口被遮挡,应用应通过 SDK 的钱包弹窗聚焦能力唤回,而不是重复发起多个签名请求。
|
|
40
|
+
|
|
41
|
+
### 3.2 Local / Wallet UCAN Session
|
|
42
|
+
|
|
43
|
+
UCAN Session 是用于签发 Delegation / Invocation 的会话密钥。
|
|
44
|
+
|
|
45
|
+
默认有效期:
|
|
46
|
+
- `DEFAULT_UCAN_SESSION_TTL_MS`
|
|
47
|
+
- 当前值:24 小时
|
|
48
|
+
|
|
49
|
+
来源:
|
|
50
|
+
- 钱包支持 `yeying_ucan_session` 时,优先使用钱包托管 session。
|
|
51
|
+
- 钱包不支持时,SDK 回退到本地 Ed25519 session,并保存在 IndexedDB。
|
|
52
|
+
|
|
53
|
+
过期处理:
|
|
54
|
+
- session 过期后 SDK 会重新创建。
|
|
55
|
+
- 如果需要钱包参与,可能触发钱包交互。
|
|
56
|
+
|
|
57
|
+
### 3.3 Delegation UCAN
|
|
58
|
+
|
|
59
|
+
Delegation UCAN 用于把能力委托给另一个 audience。
|
|
60
|
+
|
|
61
|
+
默认有效期:
|
|
62
|
+
- `DEFAULT_UCAN_TOKEN_TTL_MS`
|
|
63
|
+
- 当前值:40 分钟
|
|
64
|
+
|
|
65
|
+
特点:
|
|
66
|
+
- 适合短期委托。
|
|
67
|
+
- 过期后可在 Root 和 Session 仍有效时重新签发。
|
|
68
|
+
|
|
69
|
+
### 3.4 Invocation UCAN
|
|
70
|
+
|
|
71
|
+
Invocation UCAN 是真正发给目标服务的短期调用 token。
|
|
72
|
+
|
|
73
|
+
默认有效期:
|
|
74
|
+
- `DEFAULT_UCAN_TOKEN_TTL_MS`
|
|
75
|
+
- 当前值:40 分钟
|
|
76
|
+
|
|
77
|
+
默认时钟偏移:
|
|
78
|
+
- `DEFAULT_UCAN_TOKEN_SKEW_MS`
|
|
79
|
+
- 当前值:1 分钟
|
|
80
|
+
|
|
81
|
+
特点:
|
|
82
|
+
- 应按 audience + capability 生成。
|
|
83
|
+
- 应短期有效,降低泄漏风险。
|
|
84
|
+
- 请求前如果进入 `skew` 窗口,SDK 应自动重新生成 Invocation Token。
|
|
85
|
+
- 正常情况下,Invocation Token 过期不应该打断用户正在发起的新请求。
|
|
86
|
+
|
|
87
|
+
### 3.5 中心化 UCAN / 中心化 Session
|
|
88
|
+
|
|
89
|
+
中心化 UCAN 由中心化服务签发。
|
|
90
|
+
|
|
91
|
+
特点:
|
|
92
|
+
- 有效期由中心化服务返回值决定。
|
|
93
|
+
- 前端仍应使用 SDK 的 token timing 工具判断是否足够支撑请求。
|
|
94
|
+
- 如果中心化 session 过期,需要走中心化 session 续期或重新登录。
|
|
95
|
+
|
|
96
|
+
## 4. SDK 默认值
|
|
97
|
+
|
|
98
|
+
| 常量 | 默认值 | 用途 |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `DEFAULT_UCAN_SESSION_TTL_MS` | 24 小时 | Root / Session 默认有效期 |
|
|
101
|
+
| `DEFAULT_UCAN_TOKEN_TTL_MS` | 40 分钟 | Delegation / Invocation 默认有效期 |
|
|
102
|
+
| `DEFAULT_UCAN_TOKEN_SKEW_MS` | 1 分钟 | 判断 Invocation 是否需要换新的安全提前量 |
|
|
103
|
+
|
|
104
|
+
## 5. SDK 提供的通用能力
|
|
105
|
+
|
|
106
|
+
### 5.1 解析 token timing
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { getUcanTokenTiming } from '@yeying-community/web3-bs';
|
|
110
|
+
|
|
111
|
+
const timing = getUcanTokenTiming(ucan);
|
|
112
|
+
|
|
113
|
+
console.log(timing.valid);
|
|
114
|
+
console.log(timing.remainingMs);
|
|
115
|
+
console.log(timing.exp);
|
|
116
|
+
console.log(timing.nbf);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
适用场景:
|
|
120
|
+
- UI 展示授权剩余时间。
|
|
121
|
+
- 请求前判断 token 是否仍可用。
|
|
122
|
+
- 调试 UCAN 过期问题。
|
|
123
|
+
|
|
124
|
+
### 5.2 判断 token 是否新鲜
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { isUcanTokenFresh } from '@yeying-community/web3-bs';
|
|
128
|
+
|
|
129
|
+
const ok = isUcanTokenFresh(ucan, {
|
|
130
|
+
skewMs: 5_000,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (!ok) {
|
|
134
|
+
// 自动重新签发 invocation token 后再请求
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
通常不需要手动传 `skewMs`,使用 SDK 默认值即可。
|
|
139
|
+
|
|
140
|
+
### 5.3 自动获取可用 Invocation Token
|
|
141
|
+
|
|
142
|
+
业务应用优先使用 `getOrCreateInvocationUcan`,而不是自己判断后报错:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { getOrCreateInvocationUcan } from '@yeying-community/web3-bs';
|
|
146
|
+
|
|
147
|
+
const ucan = await getOrCreateInvocationUcan({
|
|
148
|
+
ucan: cachedInvocationToken,
|
|
149
|
+
issuer: session,
|
|
150
|
+
audience: 'did:web:router.example.com',
|
|
151
|
+
capabilities: [{ with: 'app:all:chat', can: 'invoke' }],
|
|
152
|
+
proofs: [root],
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
行为:
|
|
157
|
+
- 如果 `cachedInvocationToken` 仍足够新鲜,直接复用。
|
|
158
|
+
- 如果剩余有效期不足,自动创建新的 Invocation Token。
|
|
159
|
+
- 如果 Session / Root 仍有效,这个过程不需要用户重新签名。
|
|
160
|
+
- 如果 Session / Root 已失效,才进入重新授权流程。
|
|
161
|
+
|
|
162
|
+
### 5.4 过期后自动刷新重试
|
|
163
|
+
|
|
164
|
+
`authUcanFetch` 会在可安全重试时自动处理 Invocation 过期:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { authUcanFetch } from '@yeying-community/web3-bs';
|
|
168
|
+
|
|
169
|
+
const response = await authUcanFetch(
|
|
170
|
+
'https://api.example.com/protected',
|
|
171
|
+
{
|
|
172
|
+
method: 'POST',
|
|
173
|
+
headers: { 'content-type': 'application/json' },
|
|
174
|
+
body: JSON.stringify({ message: 'hello' }),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
issuer: session,
|
|
178
|
+
audience: 'did:web:api.example.com',
|
|
179
|
+
capabilities: [{ with: 'app:all:chat', can: 'invoke' }],
|
|
180
|
+
proofs: [root],
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
行为:
|
|
186
|
+
- 第一次请求如果成功,直接返回。
|
|
187
|
+
- 如果服务端返回可识别的 `UCAN expired` / 401,SDK 强制创建新的 Invocation Token。
|
|
188
|
+
- SDK 使用新的 Invocation Token 自动重试一次。
|
|
189
|
+
- 如果 Root / Session 仍有效,整个过程用户无感。
|
|
190
|
+
- 如果请求体是不可重放的流式 body,SDK 不会自动重试,避免重复消费 body。
|
|
191
|
+
|
|
192
|
+
这才是过期体验的主要兜底机制。
|
|
193
|
+
|
|
194
|
+
### 5.5 分类 UCAN 认证错误
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { classifyUcanAuthError } from '@yeying-community/web3-bs';
|
|
198
|
+
|
|
199
|
+
const info = classifyUcanAuthError(error);
|
|
200
|
+
|
|
201
|
+
if (info.shouldRefresh) {
|
|
202
|
+
// 刷新或重建 token
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
可识别类型:
|
|
207
|
+
- `expired`
|
|
208
|
+
- `not-before`
|
|
209
|
+
- `unauthorized`
|
|
210
|
+
- `forbidden`
|
|
211
|
+
- `invalid-token`
|
|
212
|
+
- `unknown`
|
|
213
|
+
|
|
214
|
+
`UCAN expired (trace id: xxx)` 会被识别为:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
{
|
|
218
|
+
type: 'expired',
|
|
219
|
+
retryable: true,
|
|
220
|
+
shouldRefresh: true
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## 6. WebDAV 自动初始化中的有效期配置
|
|
225
|
+
|
|
226
|
+
`initWebDavStorage` 支持为 invocation token 配置有效期和 skew:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { initWebDavStorage } from '@yeying-community/web3-bs';
|
|
230
|
+
|
|
231
|
+
const storage = await initWebDavStorage({
|
|
232
|
+
baseUrl: 'https://webdav.example.com',
|
|
233
|
+
prefix: '/dav',
|
|
234
|
+
audience: 'did:web:webdav.example.com',
|
|
235
|
+
appId,
|
|
236
|
+
capabilities: [{ with: `app:all:${appId}`, can: 'write' }],
|
|
237
|
+
invocationExpiresInMs: 40 * 60 * 1000,
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
行为:
|
|
242
|
+
- 如果缓存 token 已过期或进入 skew 窗口,SDK 会重新签发 invocation token。
|
|
243
|
+
- 如果 Root / Session 仍有效,重新签发不需要用户重新签名。
|
|
244
|
+
- 如果 Root / Session 已过期,才需要走更上层的授权重建。
|
|
245
|
+
|
|
246
|
+
## 7. 推荐配置
|
|
247
|
+
|
|
248
|
+
| 场景 | Invocation TTL | 请求前判断 | 说明 |
|
|
249
|
+
| --- | --- | --- | --- |
|
|
250
|
+
| 普通接口请求 | 默认 40 分钟 | 默认 skew 即可 | 正常情况下无感复用或换新 |
|
|
251
|
+
| WebDAV 小文件读写 | 默认 40 分钟 | 默认 skew 即可 | 过期后由 SDK 自动刷新重试 |
|
|
252
|
+
| AI 流式对话 | 默认 40 分钟 | 默认 skew 即可 | 过期后由 SDK 自动刷新重试;极长任务应使用任务 ID 恢复 |
|
|
253
|
+
| 长任务 / 批量任务 | 默认 40 分钟或按需更长 | 默认 skew 即可 | 如果任务极长,应改为服务端任务票据 |
|
|
254
|
+
|
|
255
|
+
## 8. DApp 集成建议
|
|
256
|
+
|
|
257
|
+
请求前:
|
|
258
|
+
- 使用 SDK 默认 skew 判断是否复用旧 Invocation。
|
|
259
|
+
- 如果 token 已过期或即将过期,SDK 应静默刷新或重建 Invocation。
|
|
260
|
+
- 不要只用 `exp > now` 判断,更不要把“剩余有效期不足”直接暴露成用户错误。
|
|
261
|
+
|
|
262
|
+
请求中:
|
|
263
|
+
- 流式请求开始后不要频繁重试同一请求,避免重复消耗。
|
|
264
|
+
- 如果服务端支持任务 ID,应优先恢复任务结果,而不是重新执行。
|
|
265
|
+
|
|
266
|
+
请求失败后:
|
|
267
|
+
- 使用 `classifyUcanAuthError` 判断错误。
|
|
268
|
+
- `expired` / `invalid-token` / `unauthorized` 可以刷新 Invocation 后重试一次。
|
|
269
|
+
- `forbidden` 通常是能力不足,不应盲目重试。
|
|
270
|
+
|
|
271
|
+
账户变化后:
|
|
272
|
+
- 清理 access token。
|
|
273
|
+
- 清理 UCAN session。
|
|
274
|
+
- 重新建立 Root / Session / Invocation。
|
|
275
|
+
|
|
276
|
+
## 9. 产品体验建议
|
|
277
|
+
|
|
278
|
+
用户不应该看到“token 过期导致本次请求失败但已经消耗资源”的结果。
|
|
279
|
+
|
|
280
|
+
推荐体验:
|
|
281
|
+
- 请求前静默检查 token 是否过期或即将过期。
|
|
282
|
+
- token 已过期或即将过期时静默刷新 Invocation。
|
|
283
|
+
- 需要钱包签名时,明确提示用户“需要重新授权”。
|
|
284
|
+
- 钱包弹窗被遮挡时,重复点击只聚焦已有弹窗,不重复发起签名。
|
|
285
|
+
- 长流式请求失败后,如果能通过任务 ID 恢复结果,优先恢复;否则再提示用户重试。
|
|
286
|
+
|
|
287
|
+
## 10. 服务端配合要求
|
|
288
|
+
|
|
289
|
+
服务端应返回可分类的错误信息:
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{
|
|
293
|
+
"error": {
|
|
294
|
+
"message": "UCAN expired",
|
|
295
|
+
"type": "auth_error",
|
|
296
|
+
"code": "UCAN_EXPIRED"
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
建议:
|
|
302
|
+
- 401:认证缺失、token 无效、token 过期。
|
|
303
|
+
- 403:认证有效但 capability 不足。
|
|
304
|
+
- 错误 message 或 code 中明确包含 `UCAN_EXPIRED` / `UCAN_INVALID` / `UCAN_FORBIDDEN`。
|
|
305
|
+
- 长任务接口尽量返回任务 ID,避免前端因网络或 token 过期重复消耗。
|