@heybox/hb-sdk 0.8.2-alpha.5 → 0.8.2-alpha.6

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +5 -2
  3. package/dist/cli-chunks/{build-D2T3ky_9.cjs → build-TOcahJhW.cjs} +5 -5
  4. package/dist/cli-chunks/{context-DEgmdpSL.cjs → context-Bk0adtVQ.cjs} +2 -2
  5. package/dist/cli-chunks/{create-DKqQhrG5.cjs → create-BxIgQ4SF.cjs} +1 -1
  6. package/dist/cli-chunks/{dev-BEXM7cmq.cjs → dev-CyhKTGGJ.cjs} +9 -9
  7. package/dist/cli-chunks/{doctor-B7UixGrd.cjs → doctor-ItlNn-K2.cjs} +25 -13
  8. package/dist/cli-chunks/{index-BtwfuoWI.cjs → index-CP14FXez.cjs} +16 -16
  9. package/dist/cli-chunks/{index-C69OtRug.cjs → index-CjWRMwuI.cjs} +2 -2
  10. package/dist/cli-chunks/{index.esm-C86KlVdK.cjs → index.esm-deP-KEyE.cjs} +7 -7
  11. package/dist/cli-chunks/{login-Bp_HMx-i.cjs → login-4B-2nqgd.cjs} +2 -2
  12. package/dist/cli-chunks/{project-vite-DZxipd13.cjs → project-vite-HKW4tSBX.cjs} +1 -1
  13. package/dist/cli-chunks/{remote-B_sVsbQP.cjs → remote-BLdGzViG.cjs} +7 -7
  14. package/dist/cli-chunks/{runtime-gate-GR9jZd37.cjs → runtime-gate-B_fHNic6.cjs} +1 -1
  15. package/dist/cli-chunks/{runtime-permission-env-COWketgE.cjs → runtime-permission-env-CAhESVDA.cjs} +1 -1
  16. package/dist/cli-chunks/{session-6-AApcTZ.cjs → session-COr9TQ6O.cjs} +1 -1
  17. package/dist/cli-chunks/{skill-CPDLk7t7.cjs → skill-DiZSczbw.cjs} +2 -2
  18. package/dist/cli-chunks/{version-hHrSPRwW.cjs → version-tRrcrp9F.cjs} +1 -1
  19. package/dist/cli.cjs +1 -1
  20. package/dist/devtools/browser-dev-host/assets/{browser-dev-host-CpwQQhA1.js → browser-dev-host-CF7IQARw.js} +1 -1
  21. package/dist/devtools/browser-dev-host/assets/{desktop-app-launch-BdWBOIvY.js → desktop-app-launch-CQmXIMyL.js} +1 -1
  22. package/dist/devtools/browser-dev-host/assets/{index-BQSEynx6.js → index-DdKYO0um.js} +3 -3
  23. package/dist/devtools/browser-dev-host/index.html +2 -2
  24. package/dist/index.cjs.js +1 -1
  25. package/dist/index.esm.js +1 -1
  26. package/dist/templates/vanilla-vite-js/README.md.ejs +3 -0
  27. package/dist/vite.cjs.js +1 -1
  28. package/dist/vite.esm.js +1 -1
  29. package/package.json +7 -7
  30. package/skill/SKILL.md +22 -128
  31. package/skill/skill.json +5 -5
  32. package/types/skill-metadata.d.ts +2 -2
  33. package/skill/references/api-protocol.md +0 -317
  34. package/skill/references/api-root.md +0 -1244
  35. package/skill/references/cli.md +0 -401
  36. package/skill/references/examples.md +0 -204
  37. package/skill/references/llms-index.md +0 -24
  38. package/skill/references/recipes.md +0 -855
  39. package/skill/references/safety-boundaries.md +0 -63
  40. package/skill/references/smoke-evaluation.md +0 -26
@@ -1,855 +0,0 @@
1
- # Recipes
2
-
3
- > Generated by `node packages/hb-sdk/scripts/skill/sync-references.mjs` from the public hb-sdk source/docs. Do not edit by hand; update sources or this generator instead.
4
-
5
- ## Sources
6
-
7
- - apps/docs/hb-sdk/guide/quick-start.md
8
- - apps/docs/hb-sdk/guide/auth.md
9
- - apps/docs/hb-sdk/guide/lifecycle.md
10
- - apps/docs/hb-sdk/guide/error-handling.md
11
- - apps/docs/hb-sdk/guide/companion.md
12
- - apps/docs/hb-sdk/recipes/login-gate.md
13
- - apps/docs/hb-sdk/recipes/community-share.md
14
- - apps/docs/hb-sdk/recipes/custom-instance.md
15
-
16
- ## Contents
17
-
18
- - [Quick start](#quick-start)
19
- - [User and login](#user-and-login)
20
- - [Lifecycle events](#lifecycle-events)
21
- - [Error handling](#error-handling)
22
- - [Managed Companion](#managed-companion)
23
- - [Login gate recipe](#login-gate-recipe)
24
- - [Community share recipe](#community-share-recipe)
25
- - [Custom instance recipe](#custom-instance-recipe)
26
- ## Quick start
27
-
28
-
29
- # 快速开始
30
-
31
- 新项目可先运行 `hb-sdk create my-miniapp` 获得 Vanilla JavaScript + Vite HelloWorld;模板只演示 Toast,业务代码可以直接替换。
32
-
33
- 如果页面不需要小黑盒开放能力,可以不接入 SDK。SDK 导入时会自动启动握手,能力调用会自动等待;使用受保护能力前需在 `package.json#heybox.permissions` 声明对应权限,详见[权限声明](https://docs.xiaoheihe.cn/hb_sdk/guide/permissions)。
34
-
35
- ## 交互控件与握手状态
36
-
37
- `auth.login()` 等可能展示授权 UI 的能力必须保留原始可信用户手势。不要在点击回调中等待握手;页面加载时读取并订阅持久握手状态,只在 `status === 'ready'` 时启用按钮:
38
-
39
- ```ts
40
- import hbSDK, { type MiniProgramSDKHandshakeState } from '@heybox/hb-sdk'
41
- import { computed, onUnmounted, ref } from 'vue'
42
-
43
- const handshakeState = ref<MiniProgramSDKHandshakeState>(hbSDK.getHandshakeState())
44
- const stopHandshakeState = hbSDK.onHandshakeStateChange(state => {
45
- handshakeState.value = state
46
- })
47
- const sdkReady = computed(() => handshakeState.value.status === 'ready')
48
-
49
- onUnmounted(stopHandshakeState)
50
- ```
51
-
52
- 把 `sdkReady` 用作按钮的 `disabled` 条件。订阅会立即回放当前状态,因此晚挂载组件也能得到 `ready` 或 `failed` 终态。`ready` 生命周期事件只在握手成功瞬间派发且不会重放,不能作为当前状态来源。
53
-
54
- ## 读取当前用户
55
-
56
- 声明 `userInfo` 后,可以读取按小程序隔离的 `app_user_id` 和已授权资料。该能力不依赖公开 `network` 权限:
57
-
58
- ```ts
59
- import hbSDK from '@heybox/hb-sdk'
60
-
61
- async function getCurrentUser() {
62
- const result = await hbSDK.user.getInfo()
63
- return result.userInfo
64
- }
65
- ```
66
-
67
- `user.getInfo()` 不展示授权弹窗,也不要求可信用户手势。未登录时返回未登录状态;已登录时隐式授予当前本地用户 scopes(`identity`、`profile`),静默返回 `userInfo.app_user_id`、`profile.nickname` 和 `profile.avatar`。平台为当前版本启用 Steam 账号能力且小程序未开通网络权限时,还会授予 `steam_account`,已绑定用户返回可选 `userInfo.steam_id`。这条路径不能获取授权码或调用 OpenAPI,也不能把身份或资料发送到外部服务。
68
-
69
- ## 获取服务端授权码
70
-
71
- 声明 `userInfo` 后,可通过 `auth.login()` 获取短期授权码。登录可能展示 UI,因此应绑定到按钮点击等明确的用户操作。平台为当前版本启用 Steam 账号能力后,联网小程序可在 `scopes` 中按需加入 `steam_account`,并由开发者服务端通过 OpenAPI 读取 `steam_id`。该调用内部使用 Host 受信请求,不依赖公开 `network` 权限;示例中的 `network.request()` 另需声明并获得 `network` 平台批准。
72
-
73
- SDK 会自动完成握手。登录按钮应按上面的持久握手状态启用;点击回调直接调用 `auth.login()`,不要先等待其他异步任务,以免首次操作丢失可信手势。
74
-
75
- ```ts
76
- import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
77
-
78
- // 这是你自己的后端接口,不是黑盒 OpenAPI 地址。
79
-
80
- async function loginFromUserAction() {
81
- try {
82
- const result = await hbSDK.auth.login({ scopes: ['profile'] })
83
-
84
- await hbSDK.network.request({
85
- url: 'https://api.example.com/session/heybox',
86
- method: 'POST',
87
- data: { code: result.code },
88
- })
89
- } catch (error) {
90
- if (error instanceof HbMiniProgramSDKError && error.code === 'AUTHORIZATION_CANCELLED') {
91
- return
92
- }
93
- throw error
94
- }
95
- }
96
- ```
97
-
98
- 返回值与边界以[用户身份与登录](https://docs.xiaoheihe.cn/hb_sdk/guide/auth)为准。页面只把 `code` 提交给自己的服务端。不要在页面初始化阶段自动调用 `auth.login()`。
99
-
100
- ## 文件与下载
101
-
102
- 文件能力的完整用法、沙箱模型、路径规则、配额与错误码见[文件能力与沙箱](https://docs.xiaoheihe.cn/hb_sdk/guide/files-and-sandbox);本节只给最短上手路径。
103
-
104
- `files.sandbox` 是当前小程序隔离的逻辑根。`file()` / `directory()` 只创建 lazy proxy,不执行
105
- I/O;第一次真实 I/O 仍由 Runtime 和 Host 校验当前 session、授权、mode、kind 和路径。
106
-
107
- ```ts
108
- import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
109
-
110
- const exportDirectory = hbSDK.files.sandbox.directory('exports')
111
- await exportDirectory.create({ recursive: true })
112
-
113
- const controller = new AbortController()
114
-
115
- try {
116
- const file = await hbSDK.network.download({
117
- url: 'https://cdn.example.com/report.json',
118
- to: exportDirectory,
119
- suggestedName: 'report.json',
120
- overwrite: true,
121
- maxBytes: 8 * 1024 * 1024,
122
- signal: controller.signal,
123
- onProgress({ loaded, total, lengthComputable }) {
124
- console.log(lengthComputable ? `${loaded}/${total}` : loaded)
125
- },
126
- })
127
-
128
- console.log(await file.readText())
129
- } catch (error) {
130
- if (error instanceof HbMiniProgramSDKError && error.code === 'METHOD_FORBIDDEN') {
131
- // 当前 Host 不支持 V1 文件能力。
132
- return
133
- }
134
- throw error
135
- }
136
- ```
137
-
138
- 当前平台状态如下:
139
-
140
- | Host | `files.sandbox` | 外部 picker | `files.saveFile()` | `network.download()` |
141
- | ---------------- | --------------- | ----------- | ------------------ | -------------------- |
142
- | 新 PC | 支持 | 支持 | 支持 | 支持 |
143
- | Mobile | 支持 | 支持 | 仅 iOS | 支持 |
144
- | Web | 不支持 | 不支持 | 不支持 | 不支持 |
145
- | Browser Dev Host | 不支持 | 不支持 | 不支持 | 不支持 |
146
-
147
- 当前 Runtime 认识但 Host 未实现时返回 `METHOD_FORBIDDEN`;旧 Runtime 收到新 method 时返回
148
- `METHOD_NOT_FOUND`。新 PC 注入真实文件与流式下载 primitive;Android/iOS `1.3.396+` 还需在
149
- 启动时通过 `system.describe` 宣告对应 Native operations。缺失 handler、协商超时或 operation
150
- 不完整时对应能力保持关闭,且不提供 throwing stub、Blob 或内存假下载。
151
-
152
- Mobile 的可用性完全由 Host 宣告的 operation 集决定,Runtime/H5 不按操作系统分支判断能力:
153
- Android 端不提供 `files.saveFile`,`pickFiles()` 选中的 Android 精确 SAF 文件仅支持只读
154
- (对这类目标请求 `mode: 'readwrite'` 会返回 `FILE_ACCESS_DENIED`)。
155
-
156
- ### 路径与创建
157
-
158
- - 逻辑相对路径统一使用 `/`。拒绝空字符串、绝对路径、空 segment、`.`、`..`、反斜杠、尾分隔符、NUL 和控制字符。
159
- - `create()` 的 `recursive` 与 `exclusive` 默认均为 `false`。默认行为是不截断内容的 ensure-exists;`exclusive: true` 在同名实体已存在时失败。
160
- - `writeText()` / `writeBytes()` 可以在父目录存在时隐式创建目标,但不会自动创建父目录。
161
- - `exists()` 在祖先/目标缺失或 kind mismatch 时返回 `false`;授权撤销、quota 与 I/O 错误仍抛出。
162
- - `file.remove()` 与 `directory.remove()` 只删除 sandbox 对象;外部 picker/saveFile 授权调用删除时抛出 `FILE_ACCESS_DENIED`。
163
- - 非空目录默认不能删除并抛出 `DIRECTORY_NOT_EMPTY`;确认删除整个目录树后显式调用 `directory.remove({ recursive: true })`。
164
- - `directory.list()` 默认最多返回 1000 项,显式 `limit` 也不能超过 1000;超限抛出 `DIRECTORY_LIST_LIMIT_EXCEEDED`。
165
- - Host 文件错误只暴露稳定 code 与 canonical 安全文案;原始 message、data、native path 和 credential 不会进入小程序。
166
- - `FileStat.size` 是字节数;`modifiedAt` / `createdAt` 若存在,单位为 Unix epoch milliseconds。
167
-
168
- ### 外部授权与保存
169
-
170
- `pickFiles()`、`pickDirectory()` 和 `saveFile()` 必须直接从按钮点击等可信用户操作调用。picker
171
- 取消会抛 `FILE_PICKER_CANCELLED`,不会返回空数组或 `undefined`。
172
-
173
- ```ts
174
- async function saveFromUserAction(text: string) {
175
- const file = await hbSDK.files.saveFile({ suggestedName: 'report.json' })
176
- await file.writeText(text)
177
- }
178
- ```
179
-
180
- `saveFile()` 请求 Host 返回精确 File 授权,不会同时创建或保留 Directory 授权。新 PC 当前先让
181
- 用户选择保存目录,再以 `suggestedName` 独占创建空文件;同名文件抛出 `FILE_ALREADY_EXISTS`,
182
- 不会静默覆盖。V1 不接受 `accept`;扩展名过滤只属于 `pickFiles({ accept: ['.json'] })`。外部授权
183
- 仅在当前 Runtime session 有效。
184
-
185
- `pickFiles()` / `pickDirectory()` 默认返回 `mode: 'read'` 的授权;只有显式传入
186
- `multiple: true` 时,`pickFiles()` 才允许 Host 返回多个文件。需要修改文件、在所选目录中创建
187
- 文件,或把外部授权用作下载目标时,必须在 picker options 中显式请求 `mode: 'readwrite'`。
188
-
189
- 单个 Runtime session 最多保留 1024 个 picker/saveFile direct handle;整批选择超过剩余额度时抛出 `FILE_QUOTA_EXCEEDED`,不会保留部分授权。Sandbox 派生项与目录枚举结果使用逻辑引用,不持续占用该额度。
190
-
191
- ### 下载边界
192
-
193
- - 下载固定使用 GET,不接受 method、body、上传或 Range 字段。
194
- - File target 必须已存在且为 `readwrite`,成功后返回同一个 File proxy;Directory target 必须存在且为 `readwrite`,成功后返回新的子 File proxy。
195
- - Directory 默认不覆盖同名文件;只有显式 `overwrite: true` 才允许原子替换。
196
- - Directory target 的子文件名按安全的 `Content-Disposition filename*` / `filename`、`suggestedName`、URL basename、`download.bin` 依次选择。
197
- - `timeout` 是网络空闲超时,不是整个下载总时长。取消或失败不会改动原目标。
198
- - `AbortSignal` 只发出取消意图并等待 Host 终态;如果 Host 已先完成原子提交,Promise 仍以成功结果 resolve,否则抛出 `DOWNLOAD_CANCELLED`。
199
- - 公开 request 与 download 都不跟随重定向;3xx 或 partial representation 会失败。
200
- - 进度从 `loaded: 0` 开始并单调不减;原子提交后、Promise resolve 前会再发送一次 final progress,因此空文件或 Host 已报告最终值时允许相邻值相同。只有无歧义、identity-encoded 的 `Content-Length` 才使 `lengthComputable=true`;压缩、chunked 或重复长度都视为未知 total,可信长度必须在提交前与实际字节数一致。
201
- - `onProgress` 在本地同步执行;回调异常不会中断下载。成功时最后一次回调发生在文件提交后、Promise resolve 前。
202
-
203
- ## 默认单例
204
-
205
- 大多数小程序页面都应该使用默认实例。0.6 起不再对业务代码提供独立实例工厂。
206
-
207
- ## User and login
208
-
209
-
210
- # 用户身份与登录
211
-
212
- `app_user_id` 是平台为“用户 × 当前小程序”生成的稳定、隔离标识,不是黑盒用户 ID。它是不透明字符串,只能在当前小程序内使用,不能解析或跨小程序关联。
213
-
214
- 身份能力与公开 `network` 权限相互独立。根据业务需要选择入口,并在 `package.json#heybox.permissions` 声明对应权限:
215
-
216
- | 身份入口 | 权限 | 结果用途 |
217
- | ------------------------- | -------------- | ------------------------------------------------ |
218
- | `auth.login({ scopes? })` | `userInfo` | 获取短期授权码,由开发者服务端换取 OpenAPI token |
219
- | `user.getInfo()` | `userInfo` | 读取当前用户的隔离身份及已授权资料 |
220
- | `user.getSteamGameList()` | `steamLibrary` | 读取当前用户的 Steam 游戏库 |
221
-
222
- 不要混用两条路径。SDK 不向小程序页面提供 token、cookie、黑盒用户 ID 或平台私有凭据。
223
-
224
- ## 获取服务端授权码
225
-
226
- 在登录、绑定账号或读取资料等明确的用户操作中调用 `auth.login()`:
227
-
228
- SDK 会在导入时自动握手,能力调用也会自动等待。不要在按钮回调中等待握手;用 `getHandshakeState()` 读取当前状态、用 `onHandshakeStateChange()` 订阅并立即接收状态,只在 `status === 'ready'` 时启用按钮,并在组件销毁时取消订阅。`ready` 生命周期事件不会重放,只是边沿通知,不能作为握手状态。
229
-
230
- ```ts
231
- import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
232
-
233
- // 这是你自己的后端接口,不是黑盒 OpenAPI 地址。
234
-
235
- async function loginFromUserAction() {
236
- try {
237
- const { code, expiresIn, scopes } = await hbSDK.auth.login({ scopes: ['profile'] })
238
- // expiresIn 以服务端返回为准,单位秒;当前后端签发 300 秒一次性 code
239
-
240
- await hbSDK.network.request({
241
- url: 'https://api.example.com/session/heybox',
242
- method: 'POST',
243
- data: { code },
244
- })
245
-
246
- console.log(expiresIn, scopes)
247
- } catch (error) {
248
- if (error instanceof HbMiniProgramSDKError && error.code === 'AUTHORIZATION_CANCELLED') {
249
- return
250
- }
251
- throw error
252
- }
253
- }
254
- ```
255
-
256
- 返回值为 `{ code, expiresIn, scopes }`。当前服务端签发的一次性 code 有效期为 300 秒,实际时长以响应中的 `expiresIn` 为准:
257
-
258
- - `code` 是一次性短期授权码,只能提交给当前小程序的开发者服务端。
259
- - `expiresIn` 是服务端返回的有效期(当前为 `300` 秒);服务端应尽快使用,失败或过期后重新获取,不要在业务代码中硬编码该值。
260
- - `scopes` 是本次授权码实际包含的 scope。
261
-
262
- `identity` 始终隐式包含,不需要也不能通过省略它来申请匿名授权。`scopes` 只填写业务确实需要的可选资料,例如 `profile`。平台为当前版本启用 Steam 账号能力后,需要主绑定 SteamID64 的联网小程序可申请 `steam_account`;不需要额外资料时直接调用 `auth.login()`。
263
-
264
- 小程序页面只把 `code` 发送到自己的服务端。开发者服务端再按后端 OpenAPI 文档,用服务端保存的应用凭据交换 token;页面不得自行交换,也不得持有应用私密凭据。`auth.login()` 不返回 user、profile、`steam_id`、`app_user_id` 或 token。
265
-
266
- ## 后端 OpenAPI
267
-
268
- 开发者服务端按[在线用户身份与登录文档](https://docs.xiaoheihe.cn/hb_sdk/guide/auth)完成授权码交换和用户读取。小程序页面只提交 `auth.login()` 返回的短期 code,不持有服务端密钥、access token 或认证 header;Agent Skill 不分发服务端凭据操作示例。
269
-
270
- ## 用户操作和错误
271
-
272
- 需要展示登录或授权 UI 时,调用必须来自按钮点击等可信用户手势,否则返回 `USER_GESTURE_REQUIRED`。用户取消、拒绝或关闭授权页面时返回 `AUTHORIZATION_CANCELLED`,业务应停留在当前页面并允许稍后重试。
273
-
274
- 首次授权时,Host 授权页会展示当前小程序的名称和图标;申请 `profile` scope 时,还会预览当前小黑盒账号的昵称和头像,帮助用户确认资料披露对象。Runtime 优先复用 Host 启动阶段已经解析的资料,读取补充资料失败或超时时使用中性占位,不影响用户继续授权。账号切换、授权撤销或 Runtime 终止会关闭当前确认界面;这些 Host 展示字段不会加入 `auth.login()` 返回值。
275
-
276
- 用户已授权所需 scope 时,`auth.login()` 可以不展示 UI,静默返回新的短期授权码。业务仍应把可能出现的授权 UI 设计在明确的用户操作之后,不要在页面初始化阶段自动调用。
277
-
278
- `auth.login()` 内部使用 Host 的受信请求,不会调用小程序公开的 `network.request()`,因此不要求声明 `network`。如果页面需要把 code 通过 `network.request()` 发给开发者服务端,则该请求本身仍需声明并获得 `network` 的平台批准。
279
-
280
- OpenAPI 凭据、授权码和 access token 的生命周期也不由 `network` 权限开启或关闭。
281
-
282
- ## 撤销当前小程序授权
283
-
284
- 任何小程序都可以在明确的用户操作中撤销授权;`user.revokeAuthorization()` 位于无需声明白名单:
285
-
286
- ```ts
287
- async function revokeAuthorizationFromUserAction() {
288
- const confirmed = window.confirm('确定撤销当前小程序的用户授权吗?')
289
- if (!confirmed) return
290
-
291
- await hbSDK.user.revokeAuthorization()
292
- clearLocalUserState()
293
- }
294
- ```
295
-
296
- `user.revokeAuthorization()` 不接收参数。小程序页面不能提交小程序 ID、黑盒用户 ID 或手势标记;Runtime 从 Host 启动上下文和用户操作状态注入可信值。调用缺少可信用户手势时返回 `USER_GESTURE_REQUIRED`。
297
-
298
- 撤销成功后,后续 `auth.login()` 会重新进入授权流程;再次调用 `user.getInfo()` 时会按现有用户授权规则读取身份。业务必须立即停止使用并清理此前保存的 `app_user_id`、用户资料和开发者服务端会话;SDK 不会删除业务自己的 localStorage、数据库或 cookie。
299
-
300
- ## 读取当前用户资料
301
-
302
- 需要当前用户在本小程序内的稳定隔离身份和已授权资料时调用:
303
-
304
- ```ts
305
- import hbSDK from '@heybox/hb-sdk'
306
-
307
- async function getCurrentUser() {
308
- const result = await hbSDK.user.getInfo()
309
- return result.userInfo
310
- }
311
- ```
312
-
313
- `user.getInfo()` 必须声明 `userInfo`。它延续现有交互:不展示授权弹窗,也不要求可信用户手势。未登录时返回 `isHeyboxAppLoggedIn: false`;已登录时按授权状态返回当前小程序隔离的 `app_user_id`、`profile.nickname` 和 `profile.avatar`。平台为当前版本启用 Steam 账号能力后,未开通网络权限的小程序会隐式获得 `steam_account`,当前用户有主绑定时返回可选 `steam_id`;已开通网络权限的小程序改由开发者服务端通过 OpenAPI 读取这些资料,页面调用返回 `SERVER_API_REQUIRED`。用户授权与开发者权限声明是两层独立门禁,不能用声明代替用户同意。
314
-
315
- Steam 游戏库使用 `user.getSteamGameList(options)`,要求声明 `steamLibrary`,同样不依赖 `network`。Host current-user capabilities 的既有账号绑定和数据可用性规则保持不变。
316
-
317
- ## 网络请求边界
318
-
319
- `network.request()` 只用于访问开发者自己的业务服务。平台保留的 runtime auth 与 OpenAPI 内部路径不能通过该能力访问;身份交换必须由开发者服务端按公开后端文档完成。
320
-
321
- 完整权限配置与错误区别见[权限声明](https://docs.xiaoheihe.cn/hb_sdk/guide/permissions)。
322
-
323
- ## CLI 登录与用户登录
324
-
325
- `hb-sdk login` 只给本地开发和远端管理命令建立 CLI 登录态,与小程序页面的 `auth.login()`、`user.getInfo()` 和用户授权完全无关。
326
-
327
- ## Lifecycle events
328
-
329
-
330
- # 事件与生命周期
331
-
332
- SDK 通过 `on` 监听小程序生命周期和业务事件。
333
-
334
- ```ts
335
- import { onUnmounted } from 'vue'
336
- import { on } from '@heybox/hb-sdk'
337
-
338
- function handleShow(payload: { timestamp: number; source?: string }) {
339
- console.log('show from', payload.source)
340
- }
341
-
342
- const stopShow = on('show', handleShow)
343
- const stopHide = on('hide', () => {
344
- console.log('小程序页面隐藏')
345
- })
346
-
347
- function stopLifecycleEvents() {
348
- stopShow()
349
- stopHide()
350
- }
351
-
352
- onUnmounted(stopLifecycleEvents)
353
- ```
354
-
355
- 框架外也可以保存 `on()` 返回的取消函数,在页面或业务模块真正销毁时调用。需要按 handler 精确移除时再使用 `off(event, handler)`;不要在注册后的同一执行流里立即取消。
356
-
357
- ## 事件列表
358
-
359
- | 事件 | 触发时机 | 典型用途 |
360
- | ------------------------- | ------------------------ | -------------------------------- |
361
- | `launch` | 小程序首次启动 | 初始化一次性数据 |
362
- | `ready` | SDK 握手成功瞬间 | 不可重放的边沿通知 |
363
- | `show` | 小程序页面展示 | 刷新可见态数据 |
364
- | `hide` | 小程序页面隐藏 | 暂停轮询、暂停播放 |
365
- | `unload` | 当前小程序运行环境终止 | 清理资源并停止请求 |
366
- | `error` | 小程序或开放能力运行异常 | 统一错误上报 |
367
- | `heybox_app_login_change` | 黑盒 APP 登录状态变化 | 刷新依赖 Host 当前用户的本地能力 |
368
-
369
- 完整载荷与事件名见:
370
-
371
- - [MiniProgramEventPayloadMap](api-root.md)
372
- - [MiniProgramEventName](api-root.md)
373
-
374
- ## 生命周期建议
375
-
376
- - SDK 导入时自动启动握手,能力调用会自动等待;不要在用户手势回调中额外等待握手。
377
- - 当前握手状态只通过 `getHandshakeState()` 查询、通过 `onHandshakeStateChange()` 订阅;订阅会立即回放当前值并返回取消函数。
378
- - `ready` 事件只在握手成功瞬间派发,不会向晚订阅者重放,不能用于驱动按钮可用状态或替代握手状态 API。
379
- - UI 可见性相关逻辑放在 `show`、`hide`。
380
- - 使用 `user.getInfo()` 的小程序可以在 `heybox_app_login_change` 后刷新本地状态。需要开发者服务端会话时使用 `auth.login()` 获取 code;两者都不依赖公开 `network` 权限。
381
- - 组件或页面销毁时清理 `on` 注册的监听,避免重复响应。
382
- - 生命周期事件只派发给注册当时存在的监听器,不会重放;一次性 `launch` 信息应在页面启动阶段通过业务能力调用处理。
383
- - 收到 `unload` 后,当前 SDK 上下文不可恢复,未完成请求会失败;不要在同一页面上下文继续重试能力调用。
384
- - Companion preparation 与进程 Session 由 Host 持有,页面 `hide` 或 reload 不会自动取消或终止;新页面分别通过 `getPreparationStatus()` 与 `getActiveSession()` 恢复观察和控制。
385
- - 新 Runtime generation 取得活动 Companion Session 后,旧 controller 会收到 ownership-lost 并失去写权限;stdio 输出可能 at-least-once 重放,业务必须按自身协议去重。
386
-
387
- ## Error handling
388
-
389
-
390
- # 错误处理
391
-
392
- SDK 公开两类标准错误:
393
-
394
- - `HbMiniProgramSDKError`:SDK 初始化或开放能力调用失败。
395
- - `HbMiniProgramNetworkError`:网络请求已返回,但 HTTP 状态未通过 `validateStatus`。
396
-
397
- ```ts
398
- import { HbMiniProgramNetworkError, HbMiniProgramSDKError, network } from '@heybox/hb-sdk'
399
-
400
- try {
401
- await network.request({ url: 'https://api.example.com/data' })
402
- } catch (error) {
403
- if (error instanceof HbMiniProgramNetworkError) {
404
- console.log(error.status, error.data, error.headers)
405
- return
406
- }
407
-
408
- if (error instanceof HbMiniProgramSDKError) {
409
- console.log(error.code, error.message, error.data)
410
- return
411
- }
412
-
413
- throw error
414
- }
415
- ```
416
-
417
- ## 错误边界
418
-
419
- | 结果 | 表示什么 | 常见处理 |
420
- | --------------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------- |
421
- | `HbMiniProgramSDKError` | 握手、参数、权限、Host 能力或模块操作失败 | 按稳定 `code` 分支,`message` 仅用于开发者提示 |
422
- | `HbMiniProgramNetworkError` | `network.request()` 已获得 HTTP 响应,但状态未通过 `validateStatus` | 读取 `status`、`data` 和安全响应头 |
423
- | `undefined` / `deleted: false` 等空结果 | API 明确定义的正常结果,例如排行榜中没有当前用户记录 | 正常渲染空态,不进入错误上报 |
424
- | 其他异常 | 业务回调、页面代码或非 SDK 依赖抛出的错误 | 不要伪装成 SDK 错误;保留原异常继续交给统一错误边界 |
425
-
426
- `HbMiniProgramNetworkError` 不是“所有网络失败”的父类:只有已形成标准 HTTP 响应且状态校验失败时才使用它。
427
- 握手、bridge、Host transport 或权限失败仍属于 `HbMiniProgramSDKError`。业务不应通过 `status === 0` 猜测网络错误。
428
-
429
- `HbMiniProgramSDKError.data` 是按错误码提供的可选诊断信息,不保证所有 Host 版本结构一致。读取前应做类型收窄,
430
- 日志上报也必须先移除 token、授权码、文件路径、请求正文和用户隐私数据。
431
-
432
- ## 处理原则
433
-
434
- - 根据 `error.code` 区分运行环境、权限、超时和业务失败,不要只比对错误文案。
435
- - 对未知 `code` 使用通用失败 UI 并保留诊断信息;不要用穷举 `switch` 的 `default` 自动重试。
436
- - 权限失败时给出可理解的提示,不要将它当成未登录。
437
- - `PERMISSION_NOT_DECLARED` 表示当前版本没有在 `package.json#heybox.permissions` 声明所需权限,应修改项目配置并重新构建、提交版本。
438
- - `PERMISSION_DENIED` 表示权限已声明,但需要的平台批准缺失或批准配置不足。首期只有 `network` 需要平台批准。
439
- - `USER_GESTURE_REQUIRED` 表示潜在授权 UI 缺少可信用户手势,应让用户点击按钮后重试。
440
- - `AUTHORIZATION_CANCELLED` 表示用户取消、拒绝或关闭授权页面,应正常结束当前操作。
441
- - `SERVER_API_REQUIRED` 表示当前用户数据必须经开发者服务端 OpenAPI 获取,不应在页面重试对应 Host API。
442
- - `METHOD_FORBIDDEN` 表示当前 Host 未实现或关闭对应能力。V1 files/download 在 Web、Browser Dev Host 和未实现该能力的旧版 Host 会得到该错误;Android/iOS `1.3.396+` 需先经 `system.describe` 协商到对应 operation 集。
443
- - `ENVIRONMENT_NOT_READY` 表示在 SDK 完成握手前调用了 `environment.getInfoSync()`;一般改用会自动等待的 `environment.getInfo()`。
444
- - `FILE_PICKER_CANCELLED` 表示用户取消了外部文件或目录选择,应正常结束当前操作。
445
- - `DOWNLOAD_CANCELLED` 表示取消意图先于 Host 提交生效;Host 已完成提交时仍返回成功。`DOWNLOAD_TIMEOUT` 表示网络空闲超时,失败不会改动原目标。
446
- - `COMPANION_NOT_PREPARED` 表示启动前尚未显式准备;应由新的用户操作调用 `companion.prepare()`,不要在 `launch()` 中自动重试。
447
- - `COMPANION_PREPARATION_CANCELLED` 表示取消意图已结束当前观察;原子提交可能已完成,应重新查询准备状态再更新 UI。
448
- - `COMPANION_SESSION_OWNERSHIP_LOST` 表示 Session controller 已由新的 Runtime generation 接管;停止旧 controller 写入,需要继续取得控制权时重新取得活动 Session。
449
- - `COMPANION_SESSION_EXITED` / `COMPANION_PIPE_CLOSED` 表示当前 Session 或 stdin 已进入终态;不要对同一 controller 重放写入。
450
- - `COMPANION_PROCESS_CRASHED` / `COMPANION_OUTPUT_LIMIT` 表示进程异常退出或输出越界;先展示退出状态并保存诊断,再由新的用户操作决定是否重启。
451
- - `COMPANION_DESCRIPTOR_UNAVAILABLE` 表示授权或描述符暂时不可用,Runtime 会保留 `retryable: true`。
452
- - `COMPANION_INTEGRITY_FAILED`、`COMPANION_ARCHIVE_INVALID` 或操作系统拦截都不能绕过;停止重试并修正归档后提交新审核版本。
453
- - `DOWNLOAD_HTTP_STATUS` 的 `data` 只包含脱敏后的 `status`、可选 `statusText` 和安全响应头。
454
- - 超时或运行环境不可用时,允许用户重试或退出当前流程。
455
- - 上报 `code`、`message` 和必要的业务上下文,不要上报用户凭据或敏感数据。
456
-
457
- 完整错误码和字段见 [HbMiniProgramSDKError](api-root.md) 与 [HbMiniProgramNetworkError](api-root.md)。
458
-
459
- ## 业务层建议
460
-
461
- ```ts
462
- import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
463
-
464
- type LoginViewState =
465
- | { status: 'ready' }
466
- | { status: 'cancelled' }
467
- | { status: 'failed'; error: HbMiniProgramSDKError }
468
-
469
- async function loginFromUserAction(): Promise<LoginViewState> {
470
- try {
471
- const result = await hbSDK.auth.login()
472
- await exchangeCodeWithDeveloperServer(result.code)
473
- return { status: 'ready' }
474
- } catch (error) {
475
- if (error instanceof HbMiniProgramSDKError) {
476
- if (error.code === 'AUTHORIZATION_CANCELLED') {
477
- return { status: 'cancelled' }
478
- }
479
- reportSDKError(error.code, error.message, error.data)
480
- return { status: 'failed', error }
481
- }
482
-
483
- throw error
484
- }
485
- }
486
-
487
- async function exchangeCodeWithDeveloperServer(code: string) {
488
- await hbSDK.network.request({
489
- url: 'https://api.example.com/session/heybox',
490
- method: 'POST',
491
- data: { code },
492
- })
493
- }
494
-
495
- function reportSDKError(errorCode: string, message: string, data?: unknown) {
496
- console.log('[hb-sdk]', errorCode, message, data)
497
- }
498
- ```
499
-
500
- 授权码在取得后立即交给开发者服务端交换,不要写入页面状态、DOM、日志或持久化存储。UI 只处理服务端会话是否建立成功。
501
-
502
- 文件与下载仍使用 `HbMiniProgramSDKError`,不会新增第三种错误类。业务根据 `error.code` 处理
503
- picker 取消、授权撤销、文件缺失、quota、下载状态或取消,不依赖 `message` 文案,也不要记录
504
- 真实路径、内部 handle 或响应凭据头。
505
-
506
- Companion 失败仍使用 `HbMiniProgramSDKError`。prepare 与 launch 的授权取消属于两次独立用户决定;业务不得在一次点击中自动串联授权,也不得用 Browser Fake 成功替代 Windows/macOS 真机错误处理验证。完整状态机见[受管桌面程序](https://docs.xiaoheihe.cn/hb_sdk/guide/companion)。
507
-
508
- ## Managed Companion
509
-
510
-
511
- # 受管桌面程序
512
-
513
- `companion` 用于准备和启动随当前小程序审核版本发布的桌面辅助应用。V1 仅支持新 PC Host 的
514
- `windows-x64` 与 `macos-arm64`;老 PC、Mobile、Web 和 Linux 不在支持范围内。
515
-
516
- ## 声明与构建
517
-
518
- 项目必须在 `package.json` 显式声明高风险权限:
519
-
520
- ```json
521
- {
522
- "heybox": {
523
- "permissions": {
524
- "companion": { "enabled": true }
525
- },
526
- "companions": {
527
- "runner": {
528
- "displayName": "桌面辅助程序",
529
- "capabilities": ["stdio"],
530
- "targets": {
531
- "windows-x64": {
532
- "source": "companion/runner-windows-x64",
533
- "entrypoint": { "kind": "executable", "path": "runner.exe" },
534
- "elevation": "none",
535
- "args": []
536
- }
537
- }
538
- }
539
- }
540
- }
541
- }
542
- ```
543
-
544
- 归档统一通过 `package.json#heybox.companions` 配置,`source` 相对该 `package.json` 所在目录解析。
545
- 目标平台写在 `package.json#heybox.platforms`;Vite 中只注册无参的 `miniappManifest()`,不要在 Vite 配置中复制 Companion 声明或平台声明。
546
- 构建和本地调试读取同一份声明。构建会检查 ZIP 结构和固定入口,复制到
547
- `dist/companions/<alias>/<target>.zip`,并生成大小与 SHA-256;Web 文件和 Companion 文件进入同一上传
548
- session 与同一审核版本。页面不能读取部署后的 `manifest.json`,也不能取得真实 CDN URL 或本机安装路径。
549
-
550
- `dist/companions/` 下全部归档的**总大小**上限为 500 MiB(`500 × 1024 × 1024` bytes);该上限针对归档总量,
551
- 不是单个文件。超限时 `hb-sdk build` 以“Companion 归档总大小超过 500MiB 上限,请缩减归档后重新 build”
552
- 失败,`hb-sdk remote deploy` 以“Companion 归档总大小超过 500MiB 上限,请缩减归档后重新 deploy”失败,
553
- 需要先缩减桌面辅助应用产物再重新构建或部署。
554
-
555
- `hb-sdk dev` 的真实 PC 联调可以直接使用这里声明的本地 ZIP,不要求先上传版本,但项目必须已绑定且 COA
556
- 批准 `companion`。dev server 只向 loopback 提供固定 Manifest/ZIP 路由,CLI 用短期票据绑定当前 dev
557
- 进程的 canonical snapshot。ZIP 或配置变化后会校验并切换到新快照,已有 Session 保持原有产物;
558
- 新产物必须重新准备并启动。校验失败时调试台显示阻塞原因,不会悄悄使用旧文件或线上产物。
559
- 单次开发服务最多保留 32 份快照、累计 ZIP 大小 1 GiB;达到上限后停止接纳新快照,
560
- 请先结束调试 Session,再重启开发服务,已有快照不会被自动逐出。
561
- 未配置本地 Companion 时使用 Browser Fake 做状态联调,真实 PC 不会回退到线上 candidate。
562
-
563
- 构建与 PC 安装都会检查真实二进制格式:Windows 为 AMD64 PE,macOS 为包含 arm64 的 Mach-O;
564
- 脚本即使改名或有执行权限也会被拒绝。授权弹窗展示产物来源、签名状态、目标平台、人类可读归档
565
- 大小、精确字节数与完整 SHA-256;本地 ZIP 明确标记“本地开发产物,未经过版本审核”。SHA-256
566
- 只用于 Host 可信授权展示,不进入小程序可读取的 `CompanionInfo`。
567
- `.app` 从 XML 或 binary Info.plist 的 `CFBundleExecutable` 精确选择主入口。
568
-
569
- Manifest 只接受固定入口和固定 args。V1 仅支持普通权限启动,`elevation` 只能省略或设为 `none`;
570
- `required` 会在构建时明确失败,管理员权限启动留待后续版本。公开 API 不接受 executable path、动态 args、
571
- cwd、environment、shell 或任意命令;不要把 `files.sandbox` 中的普通文件当作可执行对象。
572
- `displayName` 最多 120 个 Unicode code point;args 最多 64 项,单项最多 1024 UTF-8 bytes、总计最多
573
- 16384 UTF-8 bytes,并拒绝 NUL、CR 和 LF。
574
-
575
- ## 准备与启动
576
-
577
- ```ts
578
- import { companion } from '@heybox/hb-sdk'
579
-
580
- // 分别绑定到「准备」与「启动」按钮,不能在一个点击中串联。
581
- function prepareFromUserAction() {
582
- return companion.prepare('runner', {
583
- onProgress(progress) {
584
- renderProgress(progress)
585
- },
586
- })
587
- }
588
-
589
- function launchFromUserAction() {
590
- return companion.launch('runner')
591
- }
592
- ```
593
-
594
- `prepare()` 和 `launch()` 必须分别由新的可信用户手势触发。每次真正开始新的准备操作前,Runtime 展示一次
595
- Host 可信授权;已 ready 或加入同一在途准备时不会重复授权。每次真正创建新进程前,`launch()` 再展示独立
596
- 授权,不能复用 prepare 的手势或授权。`launch()` 不会隐式调用 `prepare()`,未准备时稳定失败。
597
-
598
- 准备操作属于 Host:页面 reload、最小化或进入后台不会取消。新页面可用
599
- `getPreparationStatus()` 查询状态,并通过相同 alias 加入在途操作;显式取消使用
600
- `cancelPreparation(alias)` 或 `AbortSignal`。页面 reload 只断开观察者,不会向 Host 发送取消;原子提交已经开始时,最终提交结果优先于取消意图。
601
-
602
- `prepare()` 与 `getInfo()` 返回的 `CompanionInfo.source` 标明 Host 确认的产物来源:`local-dev` 是本地开发产物,`preview` 是审核前预览产物,`released` 是已发布版本产物;旧 Host 缺省时 `source` 为 `undefined`,表示来源未知。不要用 `alias`、`displayName` 或文件路径自行推断来源,也不要把 `preview` 产物当作已发布版本;当前 PC Host 不支持审核前预览时以 `COMPANION_PREVIEW_UNSUPPORTED` 失败。
603
-
604
- 准备状态是可恢复的 Host 状态,不等于当前页面 Promise 的状态:
605
-
606
- | `getPreparationStatus()` 状态 | 含义 | 推荐动作 |
607
- | ----------------------------- | ------------------------------------- | ---------------------------------------------------------------- |
608
- | `not-prepared` | 当前版本产物尚未准备 | 等待新的可信用户操作调用 `prepare()` |
609
- | `preparing` | Host 正在下载、校验或解压 | 展示 `progress`;需要继续观察时再次调用 `prepare()` 加入同一操作 |
610
- | `ready` | 产物已原子提交,可独立触发 `launch()` | 展示可启动状态;`preparedAt` 是 Host 提供的时间戳 |
611
- | `failed` | 最近一次准备失败 | 根据 `error.retryable` 和 `error.code` 决定重试或停止 |
612
-
613
- `AbortSignal` 取消的是当前调用并向 Host 发出取消意图,不是页面本地的同步回滚。若原子提交已开始,当前 Promise
614
- 可能因取消而失败,但稍后查询仍可能得到 `ready`;取消后需要用 `getPreparationStatus()` 对账,不要直接把 UI 固定为
615
- `not-prepared`。
616
-
617
- ## Session 与 stdio
618
-
619
- 同一小程序 identity 与 alias 同时最多有一个活动 Session。`getActiveSession(alias)` 会取得当前活动 Session
620
- 的控制权;新 Runtime generation 接管后,旧 controller 的写入、关闭 stdin、终止、附件和通知操作都会失败。
621
- 进程退出后 `getActiveSession()` 返回 `undefined`,业务结果应由小程序自行持久化。
622
-
623
- `CompanionSession.state` 只有 `running` 和 `exited`。`onExit()` 在 Session 已退出后注册时会立即收到保存的退出结果,
624
- `waitForExit()` 对同一 Session 始终等待同一次退出;组件销毁时仍应调用所有 `on*()` 返回的取消订阅函数。
625
- 失去 controller 不等于进程退出,也不会让 `waitForExit()` 提前完成。收到 `onOwnershipLost()` 后应停止写入;确有继续
626
- 控制的需要时重新调用 `getActiveSession(alias)`,不要对旧 controller 操作做无界重试。
627
-
628
- stdio 仅在 Manifest 声明 `stdio` 时存在,输入输出都是原始 `Uint8Array`。SDK 不做 UTF-8、换行、NDJSON、
629
- RPC 或业务状态解析。输出采用 at-least-once 投递,收到 chunk 后 SDK 会确认 sequence;断连与重取控制权可能
630
- 导致重复,因此业务 framing 必须携带可去重标识,并能处理拆包、粘包和重放。
631
-
632
- ```ts
633
- const session = await companion.getActiveSession('runner')
634
- if (session?.stdio) {
635
- const stopOutput = session.stdio.onStdout(bytes => decodeBusinessFrame(bytes))
636
- const stopOwnership = session.onOwnershipLost(() => setControlsEnabled(false))
637
- await session.stdio.write(encodeBusinessFrame({ type: 'status' }))
638
- // 组件销毁时调用 stopOutput() 和 stopOwnership();业务退出协议可先写入,再 closeStdin()。
639
- }
640
- ```
641
-
642
- 平台不提供语义跨平台不稳定的 request-close。业务可先通过自身 stdio 协议请求正常退出,或关闭 stdin;
643
- `terminate()` 是强制结束完整受管进程树。页面隐藏或 reload 不结束 Session,用户真正关闭小程序窗口时由 Host
644
- 处理完整进程树生命周期。
645
-
646
- ## 可选能力
647
-
648
- - `attachments`:`session.attachments.open(id)` 返回只读 `FileHandle`;opaque id 来自桌面程序自身业务协议,页面无法指定物理路径。
649
- - `notifications`:页面解析业务协议后,可请求 Host 展示固定类别通知;页面完全断开时不承诺业务通知。
650
- - `stdio`:仅提供原始字节双向流,不提供文本编码或 RPC。
651
-
652
- 只有 Manifest 声明且 Host 实现的能力才出现在 Session。不要根据操作系统或属性存在性猜测支持情况。
653
-
654
- ## 调试与验收
655
-
656
- 调试台分别显示浏览器、手机与 PC Companion 的就绪状态,以及本地 alias、平台和产物摘要短前缀。
657
- PC 会在页面闲置时继续复核授权;明确撤销权限会结束进程树,临时网络故障只允许有时限的宽限,
658
- 不会让已失去授权的进程无限运行。关闭开发服务前请先停止调试进程。
659
-
660
- Browser Dev Host 只提供确定性的 Fake 场景,用于开发准备进度、Session 状态、stdio framing、错误 UI 与恢复逻辑。
661
- Fake 不选择、不下载、不解压、不启动本机程序,也不经过操作系统安全检查或真实进程组。
662
- 因此 Browser Fake、Mobile 或 Web 的结果不能作为桌面发布证据;发布前必须分别在目标 Windows/macOS 新 PC Host
663
- 完成归档摘要、准备取消、启动、stdio、退出与进程树清理验收。
664
-
665
- ## 失败处理
666
-
667
- - `USER_GESTURE_REQUIRED`:当前 prepare 或 launch 缺少新的可信用户手势。
668
- - `USER_CANCELLED`:用户拒绝或关闭本次 Host 授权,应正常结束流程。
669
- - `COMPANION_NOT_PREPARED`:先由新的用户操作显式调用 `prepare()`。
670
- - `COMPANION_INTEGRITY_FAILED` / `COMPANION_ARCHIVE_INVALID`:停止重试并提交新审核版本,不能绕过摘要或系统安全检查。
671
- - `COMPANION_SESSION_OWNERSHIP_LOST`:当前 Runtime 已失去 controller;需要时重新调用 `getActiveSession()`。
672
- - `COMPANION_DESCRIPTOR_UNAVAILABLE`:授权或描述符暂时不可用,可在有界重试策略下重试。
673
- - `COMPANION_ELEVATION_DENIED`:为未来管理员权限启动保留的兼容错误码;V1 构建不接受 `elevation: 'required'`。
674
- - `METHOD_FORBIDDEN` / `COMPANION_UNSUPPORTED`:当前 Host 或平台未实现,不要循环重试或用任意执行替代。
675
- - `COMPANION_PREVIEW_UNSUPPORTED`:当前 PC Host 不支持审核前预览 Companion,需要升级客户端后重试。
676
-
677
- 完整类型与方法见 [API Reference](https://docs.xiaoheihe.cn/hb_sdk/reference/)。
678
-
679
- ## 完成检查
680
-
681
- - 权限、目标 ZIP、固定入口和固定参数都随同一版本提交审核。
682
- - prepare 与 launch 使用两次独立可信用户操作和 Host 授权。
683
- - stdio 业务协议处理了原始字节 framing、at-least-once 去重和断连恢复。
684
- - 未向页面暴露或传入 URL、物理路径、动态 args、环境变量、shell 或 PID。
685
- - Browser Fake 只用于开发,目标平台均已完成新 PC 真机验收。
686
-
687
- ## Login gate recipe
688
-
689
-
690
- # 服务端登录门禁
691
-
692
- 声明 `userInfo` 后,小程序可以把“取得授权码并交给开发者服务端”收敛成一个用户操作函数。`auth.login()` 本身不依赖 `network`;以下示例使用 `network.request()` 传递 code,因此还需声明并获得 `network` 的平台批准:
693
-
694
- 示例中的 URL 是开发者自己的后端接口,不是黑盒 OpenAPI 地址。
695
-
696
- ```ts
697
- import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
698
-
699
- export async function createServerSessionFromUserAction() {
700
- try {
701
- const { code } = await hbSDK.auth.login({ scopes: ['profile'] })
702
- return await hbSDK.network.request({
703
- url: 'https://api.example.com/session/heybox',
704
- method: 'POST',
705
- data: { code },
706
- })
707
- } catch (error) {
708
- if (error instanceof HbMiniProgramSDKError && error.code === 'AUTHORIZATION_CANCELLED') {
709
- return undefined
710
- }
711
- throw error
712
- }
713
- }
714
- ```
715
-
716
- 业务按钮里使用:
717
-
718
- ```ts
719
- async function handleSubmit() {
720
- const session = await createServerSessionFromUserAction()
721
- if (!session) return
722
-
723
- // 继续执行需要开发者服务端身份的业务动作
724
- }
725
- ```
726
-
727
- 按钮在页面初始化时通过 `getHandshakeState()` 读取握手状态,并通过 `onHandshakeStateChange()` 持续更新;只在 `status === 'ready'` 时启用,组件卸载时调用订阅返回的取消函数。点击回调直接进入 `createServerSessionFromUserAction()`,不要在其中等待握手。完整接线见[快速开始](https://docs.xiaoheihe.cn/hb_sdk/guide/quick-start)。
728
-
729
- ## 注意事项
730
-
731
- 完整授权契约见[用户身份与登录](https://docs.xiaoheihe.cn/hb_sdk/guide/auth)。本页只保留调用骨架。
732
-
733
- ## Community share recipe
734
-
735
-
736
- # 社区分享预置
737
-
738
- `share.showShareMenu()` 和 `share.screenshot()` 可以给客户端的“转发到社区”流程预置分区与话题。预置内容进入发帖页后仍可由用户修改,SDK 不会直接发布内容。
739
-
740
- ## 普通分享
741
-
742
- 普通分享的落地页固定为当前小程序的 `common_share`,不接受自定义 `url`。普通分享只支持一个默认分区,并且配置 `post` 时不要同时指定站外 `channel`。
743
-
744
- ```ts
745
- import { share } from '@heybox/hb-sdk'
746
-
747
- await share.showShareMenu({
748
- title: '本局战绩',
749
- desc: '查看我的完整战绩',
750
- post: {
751
- topicIds: [235709],
752
- topics: ['无畏契约战绩'],
753
- },
754
- })
755
- ```
756
-
757
- ## 截图分享
758
-
759
- 截图分享可以预置多个分区,客户端发帖页负责最终选择与确认。进入社区发帖流程后,客户端会自动把帖子关联到当前小程序,开发者无需配置额外来源字段。
760
-
761
- ```ts
762
- await share.screenshot({
763
- delay: 100,
764
- saveToAlbum: true,
765
- post: {
766
- topicIds: [235709, 66739],
767
- topics: ['无畏契约战绩'],
768
- },
769
- })
770
- ```
771
-
772
- ## 恢复分享页面状态
773
-
774
- 通过固定通用分享链接分享时,可以使用 `extra` 携带由小程序自行定义的页面状态。分享方只负责写入状态,接收方负责决定如何使用:
775
-
776
- ```ts
777
- await share.showShareMenu({
778
- title: '排行榜',
779
- desc: '查看当前排名',
780
- extra: { page: 'ranking', options: [1, 2, 3] },
781
- })
782
- ```
783
-
784
- 需要由业务按钮复制链接时,传入同样的状态即可:
785
-
786
- ```ts
787
- const shareUrl = await share.copyLink({
788
- extra: { page: 'ranking', options: [1, 2, 3] },
789
- })
790
- ```
791
-
792
- `copyLink()` 完成时链接已写入系统剪贴板,并返回同一个完整链接。它不会自动弹出提示,建议在用户点击后调用并自行展示成功反馈。
793
-
794
- 小程序被分享链接打开后,用同步的 `getExtra()` 读取状态,再交给自己的路由:
795
-
796
- ```ts
797
- const sharedState = share.getExtra()
798
- const routeMap = { ranking: '#/ranking', profile: '#/profile' } as const
799
-
800
- if (sharedState && typeof sharedState === 'object' && !Array.isArray(sharedState)) {
801
- const page = sharedState.page
802
- const target = typeof page === 'string' ? routeMap[page as keyof typeof routeMap] : undefined
803
- if (target) window.location.hash = target
804
- }
805
- ```
806
-
807
- `getExtra()` 不依赖异步请求;没有有效数据时返回 `undefined`,业务应回退到默认首页。`extra` 只支持 JSON-compatible 数据,最多 8 层,单个数组或对象最多 64 项,内容不超过 128 字节,也不要存放 token、个人信息等敏感数据。`copyLink()` 不会继承当前启动链接中的 `extra`,只携带本次显式传入的数据。
808
-
809
- ## 参数边界
810
-
811
- - `topicIds` 接受正整数或非空字符串,按首次出现顺序去重。
812
- - `topics` 只传话题内容,不要添加首尾 `#`;字符串会先 trim 再去重。
813
- - `post: null` 等同于不配置社区发帖预置,`post: {}` 是无效配置。
814
- - 本地 Mock、App Dev Shell 和开发 Runtime 会对无效配置返回 `INVALID_PARAMS`。
815
- - 生产 Runtime 会过滤无效项;普通分享只使用第一个有效分区,配置冲突时保留站外 `channel`,不会阻断原分享流程。
816
-
817
- 调用方不能通过该 API 传客户端协议、回调、自定义按钮或发布指令。
818
-
819
- ## Custom instance recipe
820
-
821
-
822
- # 默认 SDK 单例
823
-
824
- 0.6 起 SDK 根入口会 eager 创建唯一默认实例并立即握手,不再公开独立实例工厂。
825
-
826
- ## 业务接入
827
-
828
- ```ts
829
- import hbSDK from '@heybox/hb-sdk'
830
- ```
831
-
832
- ## 测试环境注入 window
833
-
834
- 测试需要替换 bridge 或 window 时,应在应用测试边界 mock `@heybox/hb-sdk` 公开模块,不要从业务代码访问内部构造器。
835
-
836
- ```ts
837
- import type { MiniProgramSDKHandshakeStateHandler } from '@heybox/hb-sdk'
838
-
839
- const sdkMock = vi.hoisted(() => ({
840
- state: { status: 'ready' as const },
841
- unsubscribe: vi.fn(),
842
- }))
843
-
844
- vi.mock('@heybox/hb-sdk', () => ({
845
- default: {
846
- getHandshakeState: vi.fn(() => sdkMock.state),
847
- onHandshakeStateChange: vi.fn((handler: MiniProgramSDKHandshakeStateHandler) => {
848
- handler(sdkMock.state)
849
- return sdkMock.unsubscribe
850
- }),
851
- },
852
- }))
853
- ```
854
-
855
- 默认实例由页面生命周期管理,业务不调用 `destroy()`。