@heybox/hb-sdk 0.6.9 → 0.7.0-alpha.1

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 (57) hide show
  1. package/CHANGELOG.md +41 -7
  2. package/README.md +83 -20
  3. package/dist/cli-chunks/{build-BtGxhrcV.cjs → build-CWBksuZX.cjs} +2 -2
  4. package/dist/cli-chunks/{context-DX1KG-8A.cjs → context-B3_TJIaj.cjs} +1 -1
  5. package/dist/cli-chunks/{create-DAD0OF1r.cjs → create-BdDwp1Qx.cjs} +1 -1
  6. package/dist/cli-chunks/{dev-Dh4HY0I8.cjs → dev-PCY_9q8y.cjs} +40 -8
  7. package/dist/cli-chunks/{doctor-BfKdiD3H.cjs → doctor-DzEMXA58.cjs} +1 -1
  8. package/dist/cli-chunks/{index-tCURDEx8.cjs → index-CjT2V5RD.cjs} +15 -14
  9. package/dist/cli-chunks/{index-DB6Q8HaC.cjs → index-DmgF1ROG.cjs} +1 -1
  10. package/dist/cli-chunks/{login-D60EJLgs.cjs → login-D775i7Ub.cjs} +2 -2
  11. package/dist/cli-chunks/{project-vite-DAUaP0_3.cjs → project-vite-ty-N0y8F.cjs} +1 -1
  12. package/dist/cli-chunks/{remote-nQDniAUY.cjs → remote-3Zmtshjq.cjs} +4 -4
  13. package/dist/cli-chunks/{session-D6jOKXeu.cjs → session-piLyMOuV.cjs} +1 -1
  14. package/dist/cli.cjs +1 -1
  15. package/dist/devtools/mock-host/index.html +9 -2
  16. package/dist/devtools/mock-host/main.js +390 -801
  17. package/dist/index.cjs.js +46 -90
  18. package/dist/index.esm.js +46 -90
  19. package/dist/protocol.cjs.js +98 -42
  20. package/dist/protocol.esm.js +93 -39
  21. package/dist/templates/vue3-vite-ts/README.md.ejs +1 -1
  22. package/dist/templates/vue3-vite-ts/src/App.vue +22 -17
  23. package/dist/templates/vue3-vite-ts/src/__tests__/App.spec.ts +37 -29
  24. package/dist/templates/vue3-vite-ts/src/auth-handoff.ts +46 -0
  25. package/dist/vite.cjs.js +2 -2
  26. package/dist/vite.esm.js +2 -2
  27. package/package.json +2 -2
  28. package/skill/SKILL.md +16 -12
  29. package/skill/references/api-protocol.md +22 -28
  30. package/skill/references/api-root.md +127 -112
  31. package/skill/references/cli.md +15 -20
  32. package/skill/references/examples.md +29 -17
  33. package/skill/references/recipes.md +180 -134
  34. package/skill/references/safety-boundaries.md +9 -3
  35. package/skill/scripts/sync-references.mjs +235 -139
  36. package/skill/scripts/validate-skill.mjs +18 -14
  37. package/skill/skill.json +4 -4
  38. package/types/index.d.ts +4 -4
  39. package/types/modules/auth/index.d.ts +16 -28
  40. package/types/modules/cloud/index.d.ts +2 -2
  41. package/types/modules/network/index.d.ts +1 -2
  42. package/types/modules/user/get-info.d.ts +4 -4
  43. package/types/modules/user/get-local-identity.d.ts +9 -0
  44. package/types/modules/user/index.d.ts +13 -41
  45. package/types/modules/user/revoke-authorization.d.ts +7 -0
  46. package/types/modules/user/steam-game-list.d.ts +72 -0
  47. package/types/modules/user/types.d.ts +37 -302
  48. package/types/protocol/capabilities.d.ts +24 -49
  49. package/types/protocol/constants.d.ts +1 -1
  50. package/types/protocol/network-policy.d.ts +10 -0
  51. package/types/protocol/types.d.ts +10 -6
  52. package/types/protocol.d.ts +4 -3
  53. package/types/modules/user/get-current-user-detail.d.ts +0 -9
  54. package/types/modules/user/get-current-user-profile.d.ts +0 -9
  55. package/types/modules/user/get-platform-account-info.d.ts +0 -12
  56. package/types/modules/user/get-platform-account-overview.d.ts +0 -9
  57. package/types/modules/user/get-steam-game-list.d.ts +0 -12
@@ -9,40 +9,52 @@
9
9
  - apps/docs/hb-sdk/recipes/**
10
10
  ## Positive examples
11
11
 
12
- ### Minimal user info
12
+ ### Local identity for a network-disabled mini-program
13
13
 
14
14
  ```ts
15
- import { ready, user } from '@heybox/hb-sdk';
15
+ import { ready, user, HbMiniProgramSDKError } from '@heybox/hb-sdk';
16
16
 
17
- await ready();
17
+ export async function getLocalIdentityFromUserAction() {
18
+ await ready();
18
19
 
19
- const result = await user.getInfo();
20
- if (result.isLogin && result.userInfo) {
21
- console.log(result.userInfo.nickname);
20
+ try {
21
+ return await user.getLocalIdentity();
22
+ } catch (error) {
23
+ if (error instanceof HbMiniProgramSDKError && error.code === 'AUTHORIZATION_CANCELLED') {
24
+ return undefined;
25
+ }
26
+ throw error;
27
+ }
22
28
  }
23
29
  ```
24
30
 
25
- ### Login gate
31
+ ### Developer server session for a network-enabled mini-program
26
32
 
27
33
  ```ts
28
- import { auth, ready, user, HbMiniProgramSDKError } from '@heybox/hb-sdk';
34
+ import { auth, network, ready, HbMiniProgramSDKError } from '@heybox/hb-sdk';
29
35
 
30
- export async function ensureLogin() {
36
+ export async function createServerSessionFromUserAction() {
31
37
  await ready();
32
38
 
33
- const current = await user.getInfo();
34
- if (current.isLogin && current.userInfo) return current.userInfo;
35
-
36
39
  try {
37
- const loginResult = await auth.login();
38
- return loginResult.userInfo;
40
+ const result = await auth.login({ scopes: ['profile'] });
41
+ await exchangeCodeWithDeveloperServer(result.code);
42
+ return { status: 'ready' as const };
39
43
  } catch (error) {
40
- if (error instanceof HbMiniProgramSDKError) {
41
- console.warn(error.code, error.message);
44
+ if (error instanceof HbMiniProgramSDKError && error.code === 'AUTHORIZATION_CANCELLED') {
45
+ return { status: 'cancelled' as const };
42
46
  }
43
- return null;
47
+ throw error;
44
48
  }
45
49
  }
50
+
51
+ async function exchangeCodeWithDeveloperServer(code: string) {
52
+ await network.request({
53
+ url: 'https://api.example.com/session/heybox',
54
+ method: 'POST',
55
+ data: { code },
56
+ });
57
+ }
46
58
  ```
47
59
 
48
60
  ### Network request
@@ -26,157 +26,196 @@
26
26
 
27
27
  # 快速开始
28
28
 
29
- 如果页面不需要小黑盒开放能力,可以不接入 SDK。这是最短接入路径:等待 SDK 就绪,然后读取当前用户登录态。
29
+ 如果页面不需要小黑盒开放能力,可以不接入 SDK。需要开放能力时,先等待 `ready()`,再根据小程序是否开通网络权限选择身份流程。
30
+
31
+ ## 未开通网络权限
32
+
33
+ 无网络小程序可以在本地能力中使用按小程序隔离的 `appUserId`:
30
34
 
31
35
  ```ts
32
- import hbSDK from '@heybox/hb-sdk'
36
+ import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
33
37
 
34
- async function bootstrap() {
38
+ async function getLocalIdentityFromUserAction() {
35
39
  await hbSDK.ready()
36
40
 
37
- const result = await hbSDK.user.getInfo()
38
- if (result.isLogin && result.userInfo) {
39
- console.log(result.userInfo.nickname)
40
- return
41
+ try {
42
+ const { appUserId } = await hbSDK.user.getLocalIdentity()
43
+ return appUserId
44
+ } catch (error) {
45
+ if (error instanceof HbMiniProgramSDKError && error.code === 'AUTHORIZATION_CANCELLED') {
46
+ return undefined
47
+ }
48
+ throw error
41
49
  }
42
-
43
- console.log('当前用户未登录')
44
50
  }
45
-
46
- bootstrap()
47
51
  ```
48
52
 
49
- ## 推荐业务写法
53
+ `getLocalIdentityFromUserAction` 绑定到按钮点击等可信用户操作。首次登录或 identity consent 需要 UI 而缺少可信手势时返回 `USER_GESTURE_REQUIRED`;取消或关闭时返回 `AUTHORIZATION_CANCELLED`,不会确认或改变授权状态。这条路径不能获取授权码或调用 OpenAPI,也不要把 `appUserId` 发送到外部服务。
50
54
 
51
- 业务页通常还需要监听登录态变化。下面以 Vue 3 为例:初始化只读取状态,登录必须由按钮等明确的用户操作触发,监听在组件卸载时清理。
55
+ ## 已开通网络权限
52
56
 
53
- ```ts
54
- import { onUnmounted } from 'vue'
55
- import hbSDK from '@heybox/hb-sdk'
57
+ 有网络小程序通过 `auth.login()` 获取短期授权码。登录可能展示 UI,因此应绑定到按钮点击等明确的用户操作:
56
58
 
57
- const stopAuthChange = hbSDK.on('authChange', result => {
58
- if (result.isLogin) {
59
- console.log('用户已登录', result.userInfo?.heybox_id)
60
- }
61
- })
59
+ ```ts
60
+ import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
62
61
 
63
62
  await hbSDK.ready()
64
63
 
65
- const initialUser = await hbSDK.user.getInfo()
66
-
67
64
  async function loginFromUserAction() {
68
- const result = await hbSDK.auth.login()
69
- if (!result.isLogin || !result.userInfo) {
70
- return
71
- }
65
+ try {
66
+ const result = await hbSDK.auth.login({ scopes: ['profile'] })
72
67
 
73
- console.log('用户已登录', result.userInfo.heybox_id)
68
+ await hbSDK.network.request({
69
+ url: 'https://api.example.com/session/heybox',
70
+ method: 'POST',
71
+ data: { code: result.code },
72
+ })
73
+ } catch (error) {
74
+ if (error instanceof HbMiniProgramSDKError && error.code === 'AUTHORIZATION_CANCELLED') {
75
+ return
76
+ }
77
+ throw error
78
+ }
74
79
  }
75
-
76
- onUnmounted(stopAuthChange)
77
80
  ```
78
81
 
79
- `loginFromUserAction` 绑定到登录按钮;不要在页面 bootstrap 阶段自动调用 `auth.login()`。
82
+ 返回值与边界以[用户身份与登录](/guide/auth)为准。页面只把 `code` 提交给自己的服务端。不要在页面初始化阶段自动调用 `auth.login()`。
80
83
 
81
84
  ## ready 的含义
82
85
 
83
- `ready()` 表示 SDK 已就绪,可以安全调用开放能力。如果当前不在可用的小程序运行环境中,它会抛出公开错误。`ready()` 不等价于“用户已登录”,用户状态需要通过 `user.getInfo()` 或 `authChange` 判断。
86
+ `ready()` 只表示 SDK 已就绪,可以安全调用开放能力。它不表示已经取得用户授权,也不返回用户身份。
84
87
 
85
- ## 默认单例适合什么场景
88
+ ## 默认单例
86
89
 
87
90
  大多数小程序页面都应该使用默认实例。0.6 起不再对业务代码提供独立实例工厂。
88
91
 
89
92
  ## User and login
90
93
 
91
94
 
92
- # 用户与登录
95
+ # 用户身份与登录
93
96
 
94
- 用户与授权模块当前分工:
97
+ 用户身份接入取决于小程序是否已开通 `network.request`。两类小程序使用不同的身份入口:
95
98
 
96
- - `user.getInfo()`:静默读取当前登录态与公开基础资料。
97
- - `auth.login()`:唤起黑盒登录流程,并返回登录后的最新公开用户资料。
98
- - `user.getCurrentUserDetail()`:读取当前用户展示详情。
99
- - `user.getCurrentUserProfile()`:读取当前用户敏感资料,需要对应平台权限。
100
- - `user.getPlatformAccountOverview()` / `getPlatformAccountInfo()`:读取平台账号概览或指定平台详情。
101
- - `user.getSteamGameList()`:读取当前用户 Steam 游戏库。
99
+ | 小程序类型 | 身份入口 | 结果用途 |
100
+ | -------------- | ------------------------- | ------------------------------------------------------ |
101
+ | 已开通网络权限 | `auth.login({ scopes? })` | 获取短期授权码,由开发者服务端换取 OpenAPI token |
102
+ | 未开通网络权限 | `user.getLocalIdentity()` | 在当前小程序页面和平台代管能力中使用隔离的 `appUserId` |
102
103
 
103
- ## 静默读取用户信息
104
+ 不要混用两条路径。SDK 不向小程序页面提供 token、cookie、黑盒用户 ID 或平台私有凭据。
104
105
 
105
- `getInfo` 不会触发登录流程。用户未登录时,返回 `isLogin: false` 和 `userInfo: null`。
106
+ ## 已开通网络权限
107
+
108
+ 在登录、绑定账号或读取资料等明确的用户操作中调用 `auth.login()`:
106
109
 
107
110
  ```ts
108
- import { ready, user } from '@heybox/hb-sdk'
111
+ import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
109
112
 
110
- await ready()
113
+ async function loginFromUserAction() {
114
+ await hbSDK.ready()
111
115
 
112
- const result = await user.getInfo()
113
- if (result.isLogin) {
114
- console.log(result.userInfo?.nickname)
116
+ try {
117
+ const { code, expiresIn, scopes } = await hbSDK.auth.login({ scopes: ['profile'] })
118
+ // expiresIn 以服务端返回为准,单位秒;当前后端签发 300 秒一次性 code
119
+
120
+ await hbSDK.network.request({
121
+ url: 'https://api.example.com/session/heybox',
122
+ method: 'POST',
123
+ data: { code },
124
+ })
125
+
126
+ console.log(expiresIn, scopes)
127
+ } catch (error) {
128
+ if (error instanceof HbMiniProgramSDKError && error.code === 'AUTHORIZATION_CANCELLED') {
129
+ return
130
+ }
131
+ throw error
132
+ }
115
133
  }
116
134
  ```
117
135
 
118
- ## 主动唤起登录
136
+ 返回值固定为 `{ code, expiresIn: 300, scopes }`:
119
137
 
120
- 当业务动作必须登录才能继续时,再调用 `login`。
138
+ - `code` 是一次性短期授权码,只能提交给当前小程序的开发者服务端。
139
+ - `expiresIn` 固定为 `300` 秒,服务端应尽快使用,失败或过期后重新获取。
140
+ - `scopes` 是本次授权码实际包含的 scope。
121
141
 
122
- ```ts
123
- import hbSDK from '@heybox/hb-sdk'
142
+ `identity` 始终隐式包含,不需要也不能通过省略它来申请匿名授权。`scopes` 只填写业务确实需要的可选资料,例如 `profile`;不需要额外资料时直接调用 `auth.login()`。
124
143
 
125
- async function ensureLogin() {
126
- await hbSDK.ready()
144
+ 小程序页面只把 `code` 发送到自己的服务端。开发者服务端再按后端 OpenAPI 文档,用服务端保存的应用凭据交换 token;页面不得自行交换,也不得持有应用私密凭据。`auth.login()` 不返回 user、profile、`appUserId` 或 token。
127
145
 
128
- const current = await hbSDK.user.getInfo()
129
- if (current.isLogin) {
130
- return current.userInfo
131
- }
146
+ 开发者服务端用 access token 调用 `GET /user_miniprogram/openapi/v1/user` 读取当前用户。响应始终包含 `app_user_id`;token 含 `profile` 时还会返回 `nickname` 和 `avatar`。不要再请求已下线的 `/user/identity` 或 `/user/profile`。
147
+
148
+ ## 用户操作和错误
149
+
150
+ 需要展示登录或授权 UI 时,调用必须来自按钮点击等可信用户手势,否则返回 `USER_GESTURE_REQUIRED`。用户取消、拒绝或关闭授权页面时返回 `AUTHORIZATION_CANCELLED`,业务应停留在当前页面并允许稍后重试。
151
+
152
+ 用户已授权所需 scope 时,`auth.login()` 可以不展示 UI,静默返回新的短期授权码。业务仍应把可能出现的授权 UI 设计在明确的用户操作之后,不要在页面初始化阶段自动调用。
153
+
154
+ ## 撤销当前小程序授权
155
+
156
+ 已开通和未开通网络权限的小程序都可以在明确的用户操作中撤销授权:
132
157
 
133
- const next = await hbSDK.auth.login()
134
- return next.userInfo
158
+ ```ts
159
+ async function revokeAuthorizationFromUserAction() {
160
+ const confirmed = window.confirm('确定撤销当前小程序的用户授权吗?')
161
+ if (!confirmed) return
162
+
163
+ await hbSDK.user.revokeAuthorization()
164
+ clearLocalUserState()
135
165
  }
136
166
  ```
137
167
 
138
- ## 基础资料与扩展资料
168
+ `user.revokeAuthorization()` 不接收参数。小程序页面不能提交小程序 ID、黑盒用户 ID 或手势标记;Runtime 从 Host 启动上下文和用户操作状态注入可信值。调用缺少可信用户手势时返回 `USER_GESTURE_REQUIRED`。
169
+
170
+ 撤销成功后,后续 `auth.login()` 或 `user.getLocalIdentity()` 会重新进入授权流程。业务必须立即停止使用并清理此前保存的 `appUserId`、用户资料和开发者服务端会话;SDK 不会删除业务自己的 localStorage、数据库或 cookie。
171
+
172
+ ## 当前用户资料由服务端读取
173
+
174
+ 已开通网络权限后,页面不再直接读取 Host 当前用户资料。`@heybox/hb-sdk` 根包实际导出的以下调用统一返回 `SERVER_API_REQUIRED`:
139
175
 
140
- `user.getInfo()` 和 `auth.login()` 的 `userInfo` 只包含三项公开基础字段:
176
+ - `user.getInfo()`
177
+ - `user.getSteamGameList(options)`
178
+ - `user.getLocalIdentity()`
141
179
 
142
- | 字段 | 类型 | 说明 |
143
- | ----------- | -------- | ------------ |
144
- | `heybox_id` | `string` | 黑盒用户 ID |
145
- | `nickname` | `string` | 用户昵称 |
146
- | `avatar` | `string` | 用户头像 URL |
180
+ 其他 Host current-user capabilities 属于 Host/runtime 集成边界,不是小程序可从 SDK 根包调用的方法。需要对应数据时,先取得授权码,再由开发者服务端按后端 OpenAPI 文档换取 token 并调用对应接口。不要把 `SERVER_API_REQUIRED` 当作未登录或权限弹窗失败。
147
181
 
148
- 不会暴露 token、cookie、手机号或任何可用于调用主站私有接口的凭据。
182
+ `user.revokeAuthorization()` 是例外:它是授权变更操作,在两种网络模式下都可用,但始终要求可信用户手势。
149
183
 
150
- current-user scoped API 返回独立的 `UserScopedResult<T>`。未登录时是 `{ isLogin: false, data: null }`;已登录时才有 `data`。其中 `getCurrentUserProfile()` 可能返回生日、邮箱、教育和职业等敏感资料,必须按最小必要原则使用,并以平台权限结果为准;不要把这些字段理解为 `getInfo()` 的默认返回值。
184
+ ## 未开通网络权限
185
+
186
+ 未开通网络权限的小程序不走授权码流程。需要当前用户在本小程序内的稳定隔离身份时调用:
151
187
 
152
188
  ```ts
153
- const profile = await user.getCurrentUserProfile()
154
- if (profile.isLogin) {
155
- console.log(profile.data.email)
156
- }
189
+ import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
190
+
191
+ async function getLocalIdentityFromUserAction() {
192
+ await hbSDK.ready()
157
193
 
158
- const platforms = await user.getPlatformAccountOverview()
159
- if (platforms.isLogin) {
160
- console.log(platforms.data)
194
+ try {
195
+ const { appUserId } = await hbSDK.user.getLocalIdentity()
196
+ return appUserId
197
+ } catch (error) {
198
+ if (error instanceof HbMiniProgramSDKError && error.code === 'AUTHORIZATION_CANCELLED') {
199
+ return undefined
200
+ }
201
+ throw error
202
+ }
161
203
  }
162
204
  ```
163
205
 
164
- ## 监听登录态变化
206
+ `user.getLocalIdentity()` 返回 `{ appUserId }`。同一用户在同一小程序中保持稳定,在不同小程序中取值不同;应把它当作不透明字符串,不要解析、跨小程序关联或当作黑盒用户 ID。
165
207
 
166
- 如果页面上同时存在登录按钮、权限态 UI 和业务数据,建议监听 `authChange`。
208
+ 首次登录或尚未授予 identity 时,该调用可能展示 UI,因此 `getLocalIdentityFromUserAction` 必须绑定到按钮点击等可信用户操作;缺少可信手势时返回 `USER_GESTURE_REQUIRED`。用户取消、拒绝或关闭时返回 `AUTHORIZATION_CANCELLED`,不会确认或改变授权状态;业务应正常结束当前操作并允许稍后重试。
167
209
 
168
- ```ts
169
- import { onUnmounted } from 'vue'
170
- import { on } from '@heybox/hb-sdk'
210
+ 这类小程序无法获取授权码,也无法调用 OpenAPI;在未开通网络权限时调用 `auth.login()` 同样返回 `SERVER_API_REQUIRED`。Host 提供的 current-user APIs 与平台代管能力继续按各自权限工作,但页面不能把其中的身份或资料发送到外部服务。
171
211
 
172
- const stopAuthChange = on('authChange', result => {
173
- if (result.isLogin) {
174
- console.log('登录态更新', result.userInfo?.heybox_id)
175
- }
176
- })
212
+ ## 网络请求边界
177
213
 
178
- onUnmounted(stopAuthChange)
179
- ```
214
+ `network.request()` 只用于访问开发者自己的业务服务。平台保留的 runtime auth 与 OpenAPI 内部路径不能通过该能力访问;身份交换必须由开发者服务端按公开后端文档完成。
215
+
216
+ ## CLI 登录与用户登录
217
+
218
+ `hb-sdk login` 只给本地开发和远端管理命令建立 CLI 登录态,与小程序页面的 `auth.login()`、`user.getLocalIdentity()` 和用户授权完全无关。
180
219
 
181
220
  ## Lifecycle events
182
221
 
@@ -210,15 +249,15 @@ onUnmounted(stopLifecycleEvents)
210
249
 
211
250
  ## 事件列表
212
251
 
213
- | 事件 | 触发时机 | 典型用途 |
214
- | ------------ | ------------------------ | ------------------ |
215
- | `launch` | 小程序首次启动 | 初始化一次性数据 |
216
- | `ready` | SDK 可安全调用开放能力 | 标记 SDK 可用 |
217
- | `show` | 小程序页面展示 | 刷新可见态数据 |
218
- | `hide` | 小程序页面隐藏 | 暂停轮询、暂停播放 |
219
- | `unload` | 当前小程序运行环境终止 | 清理资源并停止请求 |
220
- | `error` | 小程序或开放能力运行异常 | 统一错误上报 |
221
- | `authChange` | 登录状态变化 | 刷新用户信息和权限 |
252
+ | 事件 | 触发时机 | 典型用途 |
253
+ | ------------------------- | ------------------------ | -------------------------------- |
254
+ | `launch` | 小程序首次启动 | 初始化一次性数据 |
255
+ | `ready` | SDK 可安全调用开放能力 | 标记 SDK 可用 |
256
+ | `show` | 小程序页面展示 | 刷新可见态数据 |
257
+ | `hide` | 小程序页面隐藏 | 暂停轮询、暂停播放 |
258
+ | `unload` | 当前小程序运行环境终止 | 清理资源并停止请求 |
259
+ | `error` | 小程序或开放能力运行异常 | 统一错误上报 |
260
+ | `heybox_app_login_change` | 黑盒 APP 登录状态变化 | 刷新依赖 Host 当前用户的本地能力 |
222
261
 
223
262
  完整载荷与事件名见:
224
263
 
@@ -229,7 +268,7 @@ onUnmounted(stopLifecycleEvents)
229
268
 
230
269
  - 初始化开放能力前先 `await ready()`。
231
270
  - UI 可见性相关逻辑放在 `show`、`hide`。
232
- - 用户状态不要只在页面加载时读一次,登录入口附近要监听 `authChange`。
271
+ - 使用 Host current-user APIs 的无网络小程序,可以在 `heybox_app_login_change` 后刷新本地状态。已开通网络权限的小程序使用 `auth.login()` 获取 code,并由开发者服务端维护业务会话。
233
272
  - 组件或页面销毁时清理 `on` 注册的监听,避免重复响应。
234
273
  - 事件只派发给注册当时存在的监听器,不会重放;一次性 `launch`/`ready` 状态应以 `ready()` Promise 为准。
235
274
  - 收到 `unload` 后,当前 SDK 上下文不可恢复,未完成请求会失败;不要在同一页面上下文继续重试能力调用。
@@ -245,11 +284,7 @@ SDK 公开两类标准错误:
245
284
  - `HbMiniProgramNetworkError`:网络请求已返回,但 HTTP 状态未通过 `validateStatus`。
246
285
 
247
286
  ```ts
248
- import {
249
- HbMiniProgramNetworkError,
250
- HbMiniProgramSDKError,
251
- network,
252
- } from '@heybox/hb-sdk'
287
+ import { HbMiniProgramNetworkError, HbMiniProgramSDKError, network } from '@heybox/hb-sdk'
253
288
 
254
289
  try {
255
290
  await network.request({ url: 'https://api.example.com/data' })
@@ -272,6 +307,9 @@ try {
272
307
 
273
308
  - 根据 `error.code` 区分运行环境、权限、超时和业务失败,不要只比对错误文案。
274
309
  - 权限失败时给出可理解的提示,不要将它当成未登录。
310
+ - `USER_GESTURE_REQUIRED` 表示潜在授权 UI 缺少可信用户手势,应让用户点击按钮后重试。
311
+ - `AUTHORIZATION_CANCELLED` 表示用户取消、拒绝或关闭授权页面,应正常结束当前操作。
312
+ - `SERVER_API_REQUIRED` 表示当前用户数据必须经开发者服务端 OpenAPI 获取,不应在页面重试对应 Host API。
275
313
  - 超时或运行环境不可用时,允许用户重试或退出当前流程。
276
314
  - 上报 `code`、`message` 和必要的业务上下文,不要上报用户凭据或敏感数据。
277
315
 
@@ -282,16 +320,22 @@ try {
282
320
  ```ts
283
321
  import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
284
322
 
285
- type UserViewState =
286
- | { status: 'ready'; user: Awaited<ReturnType<typeof hbSDK.user.getInfo>> }
323
+ type LoginViewState =
324
+ | { status: 'ready' }
325
+ | { status: 'cancelled' }
287
326
  | { status: 'failed'; error: HbMiniProgramSDKError }
288
327
 
289
- async function loadUser(): Promise<UserViewState> {
328
+ async function loginFromUserAction(): Promise<LoginViewState> {
290
329
  try {
291
330
  await hbSDK.ready()
292
- return { status: 'ready', user: await hbSDK.user.getInfo() }
331
+ const result = await hbSDK.auth.login()
332
+ await exchangeCodeWithDeveloperServer(result.code)
333
+ return { status: 'ready' }
293
334
  } catch (error) {
294
335
  if (error instanceof HbMiniProgramSDKError) {
336
+ if (error.code === 'AUTHORIZATION_CANCELLED') {
337
+ return { status: 'cancelled' }
338
+ }
295
339
  reportSDKError(error.code, error.message, error.data)
296
340
  return { status: 'failed', error }
297
341
  }
@@ -300,39 +344,45 @@ async function loadUser(): Promise<UserViewState> {
300
344
  }
301
345
  }
302
346
 
303
- function reportSDKError(code: string, message: string, data?: unknown) {
304
- console.log('[hb-sdk]', code, message, data)
347
+ async function exchangeCodeWithDeveloperServer(code: string) {
348
+ await hbSDK.network.request({
349
+ url: 'https://api.example.com/session/heybox',
350
+ method: 'POST',
351
+ data: { code },
352
+ })
353
+ }
354
+
355
+ function reportSDKError(errorCode: string, message: string, data?: unknown) {
356
+ console.log('[hb-sdk]', errorCode, message, data)
305
357
  }
306
358
  ```
307
359
 
360
+ 授权码在取得后立即交给开发者服务端交换,不要写入页面状态、DOM、日志或持久化存储。UI 只处理服务端会话是否建立成功。
361
+
308
362
  ## Login gate recipe
309
363
 
310
364
 
311
- # 登录门禁
365
+ # 服务端登录门禁
312
366
 
313
- 当业务动作必须登录后才能继续时,可以把登录态判断收敛成 `ensureLogin`。
367
+ 已开通网络权限的小程序可以把“取得授权码并交给开发者服务端”收敛成一个用户操作函数:
314
368
 
315
369
  ```ts
316
370
  import hbSDK, { HbMiniProgramSDKError } from '@heybox/hb-sdk'
317
- import type { MiniProgramUserInfo } from '@heybox/hb-sdk'
318
371
 
319
- export async function ensureLogin(): Promise<MiniProgramUserInfo | null> {
320
- try {
321
- await hbSDK.ready()
322
-
323
- const current = await hbSDK.user.getInfo()
324
- if (current.isLogin) {
325
- return current.userInfo
326
- }
372
+ export async function createServerSessionFromUserAction() {
373
+ await hbSDK.ready()
327
374
 
328
- const next = await hbSDK.auth.login()
329
- return next.userInfo
375
+ try {
376
+ const { code } = await hbSDK.auth.login({ scopes: ['profile'] })
377
+ return await hbSDK.network.request({
378
+ url: 'https://api.example.com/session/heybox',
379
+ method: 'POST',
380
+ data: { code },
381
+ })
330
382
  } catch (error) {
331
- if (error instanceof HbMiniProgramSDKError) {
332
- console.log('[hb-sdk]', error.code, error.message)
333
- return null
383
+ if (error instanceof HbMiniProgramSDKError && error.code === 'AUTHORIZATION_CANCELLED') {
384
+ return undefined
334
385
  }
335
-
336
386
  throw error
337
387
  }
338
388
  }
@@ -342,20 +392,16 @@ export async function ensureLogin(): Promise<MiniProgramUserInfo | null> {
342
392
 
343
393
  ```ts
344
394
  async function handleSubmit() {
345
- const userInfo = await ensureLogin()
346
- if (!userInfo) {
347
- return
348
- }
395
+ const session = await createServerSessionFromUserAction()
396
+ if (!session) return
349
397
 
350
- // 继续执行需要登录的业务动作
398
+ // 继续执行需要开发者服务端身份的业务动作
351
399
  }
352
400
  ```
353
401
 
354
402
  ## 注意事项
355
403
 
356
- - 进入页面时可以用 `getInfo` 静默初始化 UI。
357
- - 用户主动点击登录、提交、收藏等动作时,再调用 `login`。
358
- - 登录态变化后,父容器应派发 `authChange`,页面可据此刷新用户相关 UI。
404
+ 完整授权契约见[用户身份与登录](/guide/auth)。本页只保留调用骨架。
359
405
 
360
406
  ## Community share recipe
361
407
 
@@ -10,10 +10,16 @@
10
10
 
11
11
  ## 关键约束
12
12
 
13
- - `ready()` 不代表用户已登录;使用 `user.getInfo()` 判断登录状态。
14
- - `auth.login()` 只能由明确的用户操作触发,不要在页面初始化时自动登录。
13
+ - `ready()` 只表示 SDK 可调用,不表示已取得用户授权。
14
+ - `auth.login({ scopes? })` 只返回 `{ code, expiresIn: 300, scopes }`;identity 隐式强制包含,code 只能提交给开发者服务端。
15
+ - 需要授权 UI 时,`auth.login()` 必须来自可信用户手势,否则返回 `USER_GESTURE_REQUIRED`;用户取消或关闭时返回 `AUTHORIZATION_CANCELLED`。已授权时可以静默返回新 code。
16
+ - `user.getSteamGameList()` 仅供未开通网络权限的小程序通过 Host 读取;已开通网络权限时返回 `SERVER_API_REQUIRED`,且授权码与 OpenAPI 不提供 Steam 游戏库 scope 或资源接口。
17
+ - 已开通网络权限时,公开的 `user.getInfo()` 和 `user.getLocalIdentity()` 返回 `SERVER_API_REQUIRED`;对应身份和资料数据应由开发者服务端通过 OpenAPI 获取。
18
+ - 未开通网络权限时,只用 `user.getLocalIdentity()` 获取 `{ appUserId }`,不能获取 code 或调用 OpenAPI。
19
+ - `user.revokeAuthorization()` 在两种网络模式下都要求可信用户手势;成功后必须停止使用并清理业务缓存的旧身份、资料和服务端会话。
15
20
  - `on()` 返回取消监听函数,组件卸载或页面销毁时需要调用。
16
21
  - 业务网络请求使用 `network.request()`,不要依赖浏览器原生网络出口。
22
+ - `network.request()` 不能访问平台保留的 runtime auth 与 OpenAPI 内部路径;页面不得持有或交换服务端应用凭据。
17
23
  - `network.request` 不支持 `multipart/form-data`;App Host 仅支持 form(`application/x-www-form-urlencoded`)与 JSON。表单请用 `URLSearchParams#toString()` 作为 `data`,文件上传请走专用上传能力。
18
24
  - 仅当远端已启用 `network.request` 且 `useOfficialDomain=true` 时,`hb-sdk dev` 与 `hb-sdk remote deploy` 构建才会跳过平台 CSP;其他构建继续注入平台 CSP。
19
25
  - 构建必须启用 `miniappManifest()`,推荐统一使用 `hb-sdk build`。
@@ -23,7 +29,7 @@
23
29
  - Do not instruct mini-program code to read, extract, forward, store, or depend on token, cookie, phone number, or private credentials.
24
30
  - Negative safety statements that explain the SDK does not expose token/cookie/private credentials are correct and should be preserved.
25
31
  - Do not use raw share protocol fields, JS callbacks, activity reporting, direct post publishing, custom buttons, or upload-only flows. Public `share.*({ post })` options only preset an editable client post flow.
26
- - Do not use storage delete, clear, info listing, V2, or global Heybox client storage access.
32
+ - Do not use storage delete, clear, info listing, or global Heybox client storage access.
27
33
  - Do not pass host-only protocol fields through `network.request`.
28
34
  - Do not build raw `postMessage` bridge flows in iframe business code.
29
35
  - Do not fetch deployed `dist/manifest.json` from business code; it is a build artifact for the release pipeline, not a CDN runtime asset.