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

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 (62) hide show
  1. package/CHANGELOG.md +51 -7
  2. package/README.md +83 -20
  3. package/dist/cli-chunks/{build-BtGxhrcV.cjs → build-DLIhlBQA.cjs} +4 -4
  4. package/dist/cli-chunks/{context-DX1KG-8A.cjs → context-Bl_Q4RKq.cjs} +15 -2
  5. package/dist/cli-chunks/{create-DAD0OF1r.cjs → create-C--pwY0l.cjs} +1 -1
  6. package/dist/cli-chunks/{dev-Dh4HY0I8.cjs → dev-B1tyT5ql.cjs} +197 -13
  7. package/dist/cli-chunks/{doctor-BfKdiD3H.cjs → doctor-D9M2scaJ.cjs} +1 -1
  8. package/dist/cli-chunks/{index-tCURDEx8.cjs → index-BnBeTd0B.cjs} +15 -14
  9. package/dist/cli-chunks/{index-DB6Q8HaC.cjs → index-HNB8i51C.cjs} +2 -2
  10. package/dist/cli-chunks/{login-D60EJLgs.cjs → login-DnX81kof.cjs} +2 -2
  11. package/dist/cli-chunks/{project-vite-DAUaP0_3.cjs → project-vite-CgnCpNDZ.cjs} +1 -1
  12. package/dist/cli-chunks/{remote-nQDniAUY.cjs → remote-CETqg77E.cjs} +6 -6
  13. package/dist/cli-chunks/{runtime-gate-BWlU-R4h.cjs → runtime-gate-CtV7rWyX.cjs} +1 -1
  14. package/dist/cli-chunks/{runtime-permission-env-DKrhgVM3.cjs → runtime-permission-env-D-8_jPG3.cjs} +2 -0
  15. package/dist/cli-chunks/{session-D6jOKXeu.cjs → session-Davw2CMd.cjs} +1 -1
  16. package/dist/cli.cjs +1 -1
  17. package/dist/devtools/mock-host/index.html +9 -2
  18. package/dist/devtools/mock-host/main.js +426 -802
  19. package/dist/index.cjs.js +46 -90
  20. package/dist/index.esm.js +46 -90
  21. package/dist/miniapp-publish.cjs.js +2 -0
  22. package/dist/miniapp-publish.esm.js +2 -1
  23. package/dist/protocol.cjs.js +98 -42
  24. package/dist/protocol.esm.js +93 -39
  25. package/dist/templates/vue3-vite-ts/README.md.ejs +1 -1
  26. package/dist/templates/vue3-vite-ts/src/App.vue +22 -17
  27. package/dist/templates/vue3-vite-ts/src/__tests__/App.spec.ts +37 -29
  28. package/dist/templates/vue3-vite-ts/src/auth-handoff.ts +46 -0
  29. package/dist/vite.cjs.js +2 -2
  30. package/dist/vite.esm.js +2 -2
  31. package/package.json +2 -2
  32. package/skill/SKILL.md +16 -12
  33. package/skill/references/api-protocol.md +22 -28
  34. package/skill/references/api-root.md +127 -112
  35. package/skill/references/cli.md +15 -20
  36. package/skill/references/examples.md +29 -17
  37. package/skill/references/recipes.md +180 -134
  38. package/skill/references/safety-boundaries.md +9 -3
  39. package/skill/scripts/sync-references.mjs +235 -139
  40. package/skill/scripts/validate-skill.mjs +18 -14
  41. package/skill/skill.json +4 -4
  42. package/types/index.d.ts +4 -4
  43. package/types/miniapp-publish/index.d.ts +1 -0
  44. package/types/modules/auth/index.d.ts +16 -28
  45. package/types/modules/cloud/index.d.ts +2 -2
  46. package/types/modules/network/index.d.ts +1 -2
  47. package/types/modules/user/get-info.d.ts +4 -4
  48. package/types/modules/user/get-local-identity.d.ts +9 -0
  49. package/types/modules/user/index.d.ts +13 -41
  50. package/types/modules/user/revoke-authorization.d.ts +7 -0
  51. package/types/modules/user/steam-game-list.d.ts +72 -0
  52. package/types/modules/user/types.d.ts +37 -302
  53. package/types/protocol/capabilities.d.ts +24 -49
  54. package/types/protocol/constants.d.ts +1 -1
  55. package/types/protocol/network-policy.d.ts +10 -0
  56. package/types/protocol/types.d.ts +10 -6
  57. package/types/protocol.d.ts +4 -3
  58. package/types/modules/user/get-current-user-detail.d.ts +0 -9
  59. package/types/modules/user/get-current-user-profile.d.ts +0 -9
  60. package/types/modules/user/get-platform-account-info.d.ts +0 -12
  61. package/types/modules/user/get-platform-account-overview.d.ts +0 -9
  62. package/types/modules/user/get-steam-game-list.d.ts +0 -12
@@ -3,7 +3,7 @@
3
3
  /** 小程序沙盒通信命名空间,父容器与 iframe 内 SDK 必须保持一致。 */
4
4
  const MINI_PROGRAM_MESSAGE_NAMESPACE = 'heybox:miniprogram';
5
5
  /** 小程序沙盒通信协议版本。 */
6
- const MINI_PROGRAM_MESSAGE_VERSION = 1;
6
+ const MINI_PROGRAM_MESSAGE_VERSION = 2;
7
7
  /** 父容器注入到小程序 URL 上的通信 nonce 参数名。 */
8
8
  const MINI_PROGRAM_BRIDGE_NONCE_PARAM = 'hb_mini_bridge_nonce';
9
9
  /** SDK 初始化时发给父容器的握手方法名。 */
@@ -27,7 +27,7 @@ function isMiniProgramBridgeMessage(value) {
27
27
  }
28
28
  const message = value;
29
29
  return (message.namespace === MINI_PROGRAM_MESSAGE_NAMESPACE &&
30
- message.version === MINI_PROGRAM_MESSAGE_VERSION &&
30
+ (message.version === 1 || message.version === 2) &&
31
31
  typeof message.nonce === 'string' &&
32
32
  typeof message.type === 'string');
33
33
  }
@@ -84,29 +84,97 @@ function isRecord(value) {
84
84
  return typeof value === 'object' && value !== null && !Array.isArray(value);
85
85
  }
86
86
 
87
+ const OFFICIAL_MINI_PROGRAM_NETWORK_ROOT_DOMAINS = [
88
+ 'xiaoheihe.cn',
89
+ 'max-c.com',
90
+ 'debugmode.cn',
91
+ 'maxjia.com',
92
+ ];
93
+ /** Runtime / Host 在 `network.request` 分发前拒绝的小程序服务端私有路径前缀。 */
94
+ const MINI_PROGRAM_NETWORK_PROTECTED_PATHS = [
95
+ '/user_miniprogram/runtime/auth',
96
+ '/user_miniprogram/openapi/v1',
97
+ ];
98
+ const NETWORK_REQUEST_PATHNAME_MAX_DECODE_ROUNDS = 4;
99
+ /** 使用 URL 解析后的 hostname 判断黑盒官方根域及其子域。 */
100
+ function isOfficialMiniProgramNetworkUrl(value) {
101
+ try {
102
+ const url = new URL(value);
103
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
104
+ return false;
105
+ }
106
+ const hostname = url.hostname.toLowerCase().replace(/\.$/, '');
107
+ return OFFICIAL_MINI_PROGRAM_NETWORK_ROOT_DOMAINS.some(rootDomain => hostname === rootDomain || hostname.endsWith(`.${rootDomain}`));
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ }
87
113
  /**
88
- * 登录授权能力方法名。
89
- *
90
- * @remarks
91
- * 供 SDK 与父容器 runtime 共享同一 bridge method 标识。
114
+ * 规范化并判断 pathname 是否命中 Runtime Auth / OpenAPI 保护前缀。
115
+ * 解码失败或仍可继续解码时视为不安全路径。
92
116
  */
93
- const AUTH_LOGIN_METHOD = 'auth.login';
117
+ function isProtectedMiniProgramNetworkPath(pathname) {
118
+ const normalized = normalizeMiniProgramNetworkPathname(pathname);
119
+ if (normalized === null) {
120
+ return true;
121
+ }
122
+ return MINI_PROGRAM_NETWORK_PROTECTED_PATHS.some(prefix => normalized === prefix || normalized.startsWith(`${prefix}/`));
123
+ }
124
+ function normalizeMiniProgramNetworkPathname(pathname) {
125
+ let decoded = pathname;
126
+ for (let round = 0; round < NETWORK_REQUEST_PATHNAME_MAX_DECODE_ROUNDS; round += 1) {
127
+ let next;
128
+ try {
129
+ next = decodeURIComponent(decoded);
130
+ }
131
+ catch {
132
+ return null;
133
+ }
134
+ if (next === decoded) {
135
+ return normalizeDecodedNetworkPathname(decoded);
136
+ }
137
+ decoded = next;
138
+ }
139
+ try {
140
+ if (decodeURIComponent(decoded) !== decoded) {
141
+ return null;
142
+ }
143
+ }
144
+ catch {
145
+ return null;
146
+ }
147
+ return normalizeDecodedNetworkPathname(decoded);
148
+ }
149
+ function normalizeDecodedNetworkPathname(decoded) {
150
+ const segments = [];
151
+ for (const segment of decoded.replace(/\\/g, '/').split('/')) {
152
+ if (!segment || segment === '.') {
153
+ continue;
154
+ }
155
+ if (segment === '..') {
156
+ segments.pop();
157
+ continue;
158
+ }
159
+ segments.push(segment);
160
+ }
161
+ return `/${segments.join('/')}`;
162
+ }
163
+
94
164
  /**
95
- * 用户信息能力方法名。
165
+ * 登录授权能力方法名。
96
166
  *
97
167
  * @remarks
98
168
  * 供 SDK 与父容器 runtime 共享同一 bridge method 标识。
99
169
  */
170
+ const AUTH_LOGIN_METHOD = 'auth.login';
171
+ /** 读取当前登录态、授权状态和已获准披露资料的 bridge method 标识。 */
100
172
  const USER_GET_INFO_METHOD = 'user.getInfo';
101
- /** 当前用户展示详情能力方法名。 */
102
- const USER_GET_CURRENT_USER_DETAIL_METHOD = 'user.getCurrentUserDetail';
103
- /** 当前用户敏感资料详情能力方法名。 */
104
- const USER_GET_CURRENT_USER_PROFILE_METHOD = 'user.getCurrentUserProfile';
105
- /** 当前用户平台账号概览能力方法名。 */
106
- const USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD = 'user.getPlatformAccountOverview';
107
- /** 当前用户指定平台账号详情能力方法名。 */
108
- const USER_GET_PLATFORM_ACCOUNT_INFO_METHOD = 'user.getPlatformAccountInfo';
109
- /** 当前用户 Steam 游戏库能力方法名。 */
173
+ /** 读取当前用户小程序隔离身份的 bridge method 标识。 */
174
+ const USER_GET_LOCAL_IDENTITY_METHOD = 'user.getLocalIdentity';
175
+ /** 撤销当前用户授予本小程序授权的 bridge method 标识。 */
176
+ const USER_REVOKE_AUTHORIZATION_METHOD = 'user.revokeAuthorization';
177
+ /** 读取当前已登录用户 Steam 游戏库的 bridge method 标识。 */
110
178
  const USER_GET_STEAM_GAME_LIST_METHOD = 'user.getSteamGameList';
111
179
  /**
112
180
  * 展示分享面板能力方法名。
@@ -205,38 +273,24 @@ const MINI_PROGRAM_PROTOCOL_CAPABILITIES = [
205
273
  risk: 'medium',
206
274
  },
207
275
  {
208
- method: USER_GET_CURRENT_USER_DETAIL_METHOD,
209
- module: 'user',
210
- capability: USER_GET_CURRENT_USER_DETAIL_METHOD,
211
- permission: 'user.currentUserDetail',
212
- risk: 'high',
213
- },
214
- {
215
- method: USER_GET_CURRENT_USER_PROFILE_METHOD,
216
- module: 'user',
217
- capability: USER_GET_CURRENT_USER_PROFILE_METHOD,
218
- permission: 'user.currentUserProfile',
219
- risk: 'high',
220
- },
221
- {
222
- method: USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD,
276
+ method: USER_GET_LOCAL_IDENTITY_METHOD,
223
277
  module: 'user',
224
- capability: USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD,
225
- permission: 'user.platformAccount.overview',
278
+ capability: USER_GET_LOCAL_IDENTITY_METHOD,
279
+ permission: 'user.getLocalIdentity',
226
280
  risk: 'medium',
227
281
  },
228
282
  {
229
- method: USER_GET_PLATFORM_ACCOUNT_INFO_METHOD,
283
+ method: USER_REVOKE_AUTHORIZATION_METHOD,
230
284
  module: 'user',
231
- capability: USER_GET_PLATFORM_ACCOUNT_INFO_METHOD,
232
- permission: 'user.platformAccount.info',
285
+ capability: USER_REVOKE_AUTHORIZATION_METHOD,
286
+ permission: 'user.revokeAuthorization',
233
287
  risk: 'high',
234
288
  },
235
289
  {
236
290
  method: USER_GET_STEAM_GAME_LIST_METHOD,
237
291
  module: 'user',
238
292
  capability: USER_GET_STEAM_GAME_LIST_METHOD,
239
- permission: 'user.steamGameList',
293
+ permission: 'user.platformAccount.steam',
240
294
  risk: 'high',
241
295
  },
242
296
  {
@@ -392,6 +446,7 @@ exports.DEVICE_VIBRATE_METHOD = DEVICE_VIBRATE_METHOD;
392
446
  exports.MINI_PROGRAM_BRIDGE_NONCE_PARAM = MINI_PROGRAM_BRIDGE_NONCE_PARAM;
393
447
  exports.MINI_PROGRAM_MESSAGE_NAMESPACE = MINI_PROGRAM_MESSAGE_NAMESPACE;
394
448
  exports.MINI_PROGRAM_MESSAGE_VERSION = MINI_PROGRAM_MESSAGE_VERSION;
449
+ exports.MINI_PROGRAM_NETWORK_PROTECTED_PATHS = MINI_PROGRAM_NETWORK_PROTECTED_PATHS;
395
450
  exports.MINI_PROGRAM_PROTOCOL_CAPABILITIES = MINI_PROGRAM_PROTOCOL_CAPABILITIES;
396
451
  exports.NAVIGATION_CLOSE_METHOD = NAVIGATION_CLOSE_METHOD;
397
452
  exports.NAVIGATION_OPEN_APP_PAGE_METHOD = NAVIGATION_OPEN_APP_PAGE_METHOD;
@@ -408,14 +463,15 @@ exports.STORAGE_SET_STORAGE_METHOD = STORAGE_SET_STORAGE_METHOD;
408
463
  exports.UI_HIDE_LOADING_METHOD = UI_HIDE_LOADING_METHOD;
409
464
  exports.UI_SHOW_LOADING_METHOD = UI_SHOW_LOADING_METHOD;
410
465
  exports.UI_SHOW_TOAST_METHOD = UI_SHOW_TOAST_METHOD;
411
- exports.USER_GET_CURRENT_USER_DETAIL_METHOD = USER_GET_CURRENT_USER_DETAIL_METHOD;
412
- exports.USER_GET_CURRENT_USER_PROFILE_METHOD = USER_GET_CURRENT_USER_PROFILE_METHOD;
413
466
  exports.USER_GET_INFO_METHOD = USER_GET_INFO_METHOD;
414
- exports.USER_GET_PLATFORM_ACCOUNT_INFO_METHOD = USER_GET_PLATFORM_ACCOUNT_INFO_METHOD;
415
- exports.USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD = USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD;
467
+ exports.USER_GET_LOCAL_IDENTITY_METHOD = USER_GET_LOCAL_IDENTITY_METHOD;
416
468
  exports.USER_GET_STEAM_GAME_LIST_METHOD = USER_GET_STEAM_GAME_LIST_METHOD;
469
+ exports.USER_REVOKE_AUTHORIZATION_METHOD = USER_REVOKE_AUTHORIZATION_METHOD;
417
470
  exports.VIEWPORT_GET_WINDOW_INFO_METHOD = VIEWPORT_GET_WINDOW_INFO_METHOD;
418
471
  exports.VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD = VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD;
419
472
  exports.isManagedMiniProgramRuntimePermissionKey = isManagedMiniProgramRuntimePermissionKey;
420
473
  exports.isMiniProgramBridgeMessage = isMiniProgramBridgeMessage;
474
+ exports.isOfficialMiniProgramNetworkUrl = isOfficialMiniProgramNetworkUrl;
475
+ exports.isProtectedMiniProgramNetworkPath = isProtectedMiniProgramNetworkPath;
476
+ exports.normalizeMiniProgramNetworkPathname = normalizeMiniProgramNetworkPathname;
421
477
  exports.parseMiniProgramRuntimePermissions = parseMiniProgramRuntimePermissions;
@@ -1,7 +1,7 @@
1
1
  /** 小程序沙盒通信命名空间,父容器与 iframe 内 SDK 必须保持一致。 */
2
2
  const MINI_PROGRAM_MESSAGE_NAMESPACE = 'heybox:miniprogram';
3
3
  /** 小程序沙盒通信协议版本。 */
4
- const MINI_PROGRAM_MESSAGE_VERSION = 1;
4
+ const MINI_PROGRAM_MESSAGE_VERSION = 2;
5
5
  /** 父容器注入到小程序 URL 上的通信 nonce 参数名。 */
6
6
  const MINI_PROGRAM_BRIDGE_NONCE_PARAM = 'hb_mini_bridge_nonce';
7
7
  /** SDK 初始化时发给父容器的握手方法名。 */
@@ -25,7 +25,7 @@ function isMiniProgramBridgeMessage(value) {
25
25
  }
26
26
  const message = value;
27
27
  return (message.namespace === MINI_PROGRAM_MESSAGE_NAMESPACE &&
28
- message.version === MINI_PROGRAM_MESSAGE_VERSION &&
28
+ (message.version === 1 || message.version === 2) &&
29
29
  typeof message.nonce === 'string' &&
30
30
  typeof message.type === 'string');
31
31
  }
@@ -82,29 +82,97 @@ function isRecord(value) {
82
82
  return typeof value === 'object' && value !== null && !Array.isArray(value);
83
83
  }
84
84
 
85
+ const OFFICIAL_MINI_PROGRAM_NETWORK_ROOT_DOMAINS = [
86
+ 'xiaoheihe.cn',
87
+ 'max-c.com',
88
+ 'debugmode.cn',
89
+ 'maxjia.com',
90
+ ];
91
+ /** Runtime / Host 在 `network.request` 分发前拒绝的小程序服务端私有路径前缀。 */
92
+ const MINI_PROGRAM_NETWORK_PROTECTED_PATHS = [
93
+ '/user_miniprogram/runtime/auth',
94
+ '/user_miniprogram/openapi/v1',
95
+ ];
96
+ const NETWORK_REQUEST_PATHNAME_MAX_DECODE_ROUNDS = 4;
97
+ /** 使用 URL 解析后的 hostname 判断黑盒官方根域及其子域。 */
98
+ function isOfficialMiniProgramNetworkUrl(value) {
99
+ try {
100
+ const url = new URL(value);
101
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
102
+ return false;
103
+ }
104
+ const hostname = url.hostname.toLowerCase().replace(/\.$/, '');
105
+ return OFFICIAL_MINI_PROGRAM_NETWORK_ROOT_DOMAINS.some(rootDomain => hostname === rootDomain || hostname.endsWith(`.${rootDomain}`));
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
85
111
  /**
86
- * 登录授权能力方法名。
87
- *
88
- * @remarks
89
- * 供 SDK 与父容器 runtime 共享同一 bridge method 标识。
112
+ * 规范化并判断 pathname 是否命中 Runtime Auth / OpenAPI 保护前缀。
113
+ * 解码失败或仍可继续解码时视为不安全路径。
90
114
  */
91
- const AUTH_LOGIN_METHOD = 'auth.login';
115
+ function isProtectedMiniProgramNetworkPath(pathname) {
116
+ const normalized = normalizeMiniProgramNetworkPathname(pathname);
117
+ if (normalized === null) {
118
+ return true;
119
+ }
120
+ return MINI_PROGRAM_NETWORK_PROTECTED_PATHS.some(prefix => normalized === prefix || normalized.startsWith(`${prefix}/`));
121
+ }
122
+ function normalizeMiniProgramNetworkPathname(pathname) {
123
+ let decoded = pathname;
124
+ for (let round = 0; round < NETWORK_REQUEST_PATHNAME_MAX_DECODE_ROUNDS; round += 1) {
125
+ let next;
126
+ try {
127
+ next = decodeURIComponent(decoded);
128
+ }
129
+ catch {
130
+ return null;
131
+ }
132
+ if (next === decoded) {
133
+ return normalizeDecodedNetworkPathname(decoded);
134
+ }
135
+ decoded = next;
136
+ }
137
+ try {
138
+ if (decodeURIComponent(decoded) !== decoded) {
139
+ return null;
140
+ }
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ return normalizeDecodedNetworkPathname(decoded);
146
+ }
147
+ function normalizeDecodedNetworkPathname(decoded) {
148
+ const segments = [];
149
+ for (const segment of decoded.replace(/\\/g, '/').split('/')) {
150
+ if (!segment || segment === '.') {
151
+ continue;
152
+ }
153
+ if (segment === '..') {
154
+ segments.pop();
155
+ continue;
156
+ }
157
+ segments.push(segment);
158
+ }
159
+ return `/${segments.join('/')}`;
160
+ }
161
+
92
162
  /**
93
- * 用户信息能力方法名。
163
+ * 登录授权能力方法名。
94
164
  *
95
165
  * @remarks
96
166
  * 供 SDK 与父容器 runtime 共享同一 bridge method 标识。
97
167
  */
168
+ const AUTH_LOGIN_METHOD = 'auth.login';
169
+ /** 读取当前登录态、授权状态和已获准披露资料的 bridge method 标识。 */
98
170
  const USER_GET_INFO_METHOD = 'user.getInfo';
99
- /** 当前用户展示详情能力方法名。 */
100
- const USER_GET_CURRENT_USER_DETAIL_METHOD = 'user.getCurrentUserDetail';
101
- /** 当前用户敏感资料详情能力方法名。 */
102
- const USER_GET_CURRENT_USER_PROFILE_METHOD = 'user.getCurrentUserProfile';
103
- /** 当前用户平台账号概览能力方法名。 */
104
- const USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD = 'user.getPlatformAccountOverview';
105
- /** 当前用户指定平台账号详情能力方法名。 */
106
- const USER_GET_PLATFORM_ACCOUNT_INFO_METHOD = 'user.getPlatformAccountInfo';
107
- /** 当前用户 Steam 游戏库能力方法名。 */
171
+ /** 读取当前用户小程序隔离身份的 bridge method 标识。 */
172
+ const USER_GET_LOCAL_IDENTITY_METHOD = 'user.getLocalIdentity';
173
+ /** 撤销当前用户授予本小程序授权的 bridge method 标识。 */
174
+ const USER_REVOKE_AUTHORIZATION_METHOD = 'user.revokeAuthorization';
175
+ /** 读取当前已登录用户 Steam 游戏库的 bridge method 标识。 */
108
176
  const USER_GET_STEAM_GAME_LIST_METHOD = 'user.getSteamGameList';
109
177
  /**
110
178
  * 展示分享面板能力方法名。
@@ -203,38 +271,24 @@ const MINI_PROGRAM_PROTOCOL_CAPABILITIES = [
203
271
  risk: 'medium',
204
272
  },
205
273
  {
206
- method: USER_GET_CURRENT_USER_DETAIL_METHOD,
207
- module: 'user',
208
- capability: USER_GET_CURRENT_USER_DETAIL_METHOD,
209
- permission: 'user.currentUserDetail',
210
- risk: 'high',
211
- },
212
- {
213
- method: USER_GET_CURRENT_USER_PROFILE_METHOD,
214
- module: 'user',
215
- capability: USER_GET_CURRENT_USER_PROFILE_METHOD,
216
- permission: 'user.currentUserProfile',
217
- risk: 'high',
218
- },
219
- {
220
- method: USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD,
274
+ method: USER_GET_LOCAL_IDENTITY_METHOD,
221
275
  module: 'user',
222
- capability: USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD,
223
- permission: 'user.platformAccount.overview',
276
+ capability: USER_GET_LOCAL_IDENTITY_METHOD,
277
+ permission: 'user.getLocalIdentity',
224
278
  risk: 'medium',
225
279
  },
226
280
  {
227
- method: USER_GET_PLATFORM_ACCOUNT_INFO_METHOD,
281
+ method: USER_REVOKE_AUTHORIZATION_METHOD,
228
282
  module: 'user',
229
- capability: USER_GET_PLATFORM_ACCOUNT_INFO_METHOD,
230
- permission: 'user.platformAccount.info',
283
+ capability: USER_REVOKE_AUTHORIZATION_METHOD,
284
+ permission: 'user.revokeAuthorization',
231
285
  risk: 'high',
232
286
  },
233
287
  {
234
288
  method: USER_GET_STEAM_GAME_LIST_METHOD,
235
289
  module: 'user',
236
290
  capability: USER_GET_STEAM_GAME_LIST_METHOD,
237
- permission: 'user.steamGameList',
291
+ permission: 'user.platformAccount.steam',
238
292
  risk: 'high',
239
293
  },
240
294
  {
@@ -379,4 +433,4 @@ const MINI_PROGRAM_PROTOCOL_CAPABILITIES = [
379
433
  },
380
434
  ];
381
435
 
382
- export { AUTH_LOGIN_METHOD, CLOUD_LEADERBOARD_DELETE_CURRENT_USER_ENTRY_METHOD, CLOUD_LEADERBOARD_GET_CURRENT_USER_ENTRY_METHOD, CLOUD_LEADERBOARD_GET_INFO_METHOD, CLOUD_LEADERBOARD_GET_LIST_METHOD, CLOUD_LEADERBOARD_SUBMIT_METHOD, DEVICE_SET_CLIPBOARD_METHOD, DEVICE_VIBRATE_METHOD, MINI_PROGRAM_BRIDGE_NONCE_PARAM, MINI_PROGRAM_MESSAGE_NAMESPACE, MINI_PROGRAM_MESSAGE_VERSION, MINI_PROGRAM_PROTOCOL_CAPABILITIES, NAVIGATION_CLOSE_METHOD, NAVIGATION_OPEN_APP_PAGE_METHOD, NAVIGATION_RELOAD_METHOD, NETWORK_REQUEST_METHOD, RUNTIME_LOCATION_PROBE_METHOD, SDK_CSP_VIOLATION_METHOD, SDK_HANDSHAKE_METHOD, SDK_LOCATION_REPORT_METHOD, SHARE_SCREENSHOT_METHOD, SHARE_SHOW_SHARE_MENU_METHOD, STORAGE_GET_STORAGE_METHOD, STORAGE_SET_STORAGE_METHOD, UI_HIDE_LOADING_METHOD, UI_SHOW_LOADING_METHOD, UI_SHOW_TOAST_METHOD, USER_GET_CURRENT_USER_DETAIL_METHOD, USER_GET_CURRENT_USER_PROFILE_METHOD, USER_GET_INFO_METHOD, USER_GET_PLATFORM_ACCOUNT_INFO_METHOD, USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD, USER_GET_STEAM_GAME_LIST_METHOD, VIEWPORT_GET_WINDOW_INFO_METHOD, VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD, isManagedMiniProgramRuntimePermissionKey, isMiniProgramBridgeMessage, parseMiniProgramRuntimePermissions };
436
+ export { AUTH_LOGIN_METHOD, CLOUD_LEADERBOARD_DELETE_CURRENT_USER_ENTRY_METHOD, CLOUD_LEADERBOARD_GET_CURRENT_USER_ENTRY_METHOD, CLOUD_LEADERBOARD_GET_INFO_METHOD, CLOUD_LEADERBOARD_GET_LIST_METHOD, CLOUD_LEADERBOARD_SUBMIT_METHOD, DEVICE_SET_CLIPBOARD_METHOD, DEVICE_VIBRATE_METHOD, MINI_PROGRAM_BRIDGE_NONCE_PARAM, MINI_PROGRAM_MESSAGE_NAMESPACE, MINI_PROGRAM_MESSAGE_VERSION, MINI_PROGRAM_NETWORK_PROTECTED_PATHS, MINI_PROGRAM_PROTOCOL_CAPABILITIES, NAVIGATION_CLOSE_METHOD, NAVIGATION_OPEN_APP_PAGE_METHOD, NAVIGATION_RELOAD_METHOD, NETWORK_REQUEST_METHOD, RUNTIME_LOCATION_PROBE_METHOD, SDK_CSP_VIOLATION_METHOD, SDK_HANDSHAKE_METHOD, SDK_LOCATION_REPORT_METHOD, SHARE_SCREENSHOT_METHOD, SHARE_SHOW_SHARE_MENU_METHOD, STORAGE_GET_STORAGE_METHOD, STORAGE_SET_STORAGE_METHOD, UI_HIDE_LOADING_METHOD, UI_SHOW_LOADING_METHOD, UI_SHOW_TOAST_METHOD, USER_GET_INFO_METHOD, USER_GET_LOCAL_IDENTITY_METHOD, USER_GET_STEAM_GAME_LIST_METHOD, USER_REVOKE_AUTHORIZATION_METHOD, VIEWPORT_GET_WINDOW_INFO_METHOD, VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD, isManagedMiniProgramRuntimePermissionKey, isMiniProgramBridgeMessage, isOfficialMiniProgramNetworkUrl, isProtectedMiniProgramNetworkPath, normalizeMiniProgramNetworkPathname, parseMiniProgramRuntimePermissions };
@@ -42,4 +42,4 @@ await hbSDK.storage.setStorage({
42
42
 
43
43
  ```
44
44
 
45
- 工坊小程序默认不能进行网络请求,网络权限暂未开放申请。
45
+ 工坊小程序默认不能进行网络请求。开通网络权限后,把 `VITE_HEYBOX_AUTH_EXCHANGE_URL` 配成开发者服务端的 HTTPS 交换地址(本地可用 localhost)。授权契约见文档站「用户身份与登录」。
@@ -4,13 +4,13 @@
4
4
  <p class="eyebrow">Heybox Mini Program</p>
5
5
  <h1><%= projectName %></h1>
6
6
  <p class="intro">
7
- 这个模板只保留最小接入示例:等待 SDK ready、读取用户信息,并在需要时唤起登录。
7
+ 这个模板只保留最小接入示例:等待 SDK ready,并由用户主动选择受支持的操作。
8
8
  </p>
9
9
 
10
10
  <p class="status" data-testid="status">{{ status }}</p>
11
11
 
12
12
  <button type="button" :disabled="loading" data-testid="login" @click="login">
13
- 登录并刷新用户信息
13
+ 获取授权码
14
14
  </button>
15
15
 
16
16
  <pre data-testid="output">{{ output }}</pre>
@@ -21,36 +21,41 @@
21
21
  <script setup lang="ts">
22
22
  import hbSDK from '@heybox/hb-sdk';
23
23
  import { onMounted, ref } from 'vue';
24
+ import { loginAndExchangeAuthorizationCode } from './auth-handoff';
24
25
 
25
26
  const loading = ref(false);
26
27
  const status = ref('正在连接 SDK...');
27
- const output = ref('等待 user.getInfo 返回结果。');
28
+ const output = ref('连接后请选择操作。');
28
29
 
29
30
  onMounted(() => {
30
- void loadUserInfo();
31
+ void connectSDK();
31
32
  });
32
33
 
33
- async function loadUserInfo() {
34
- await runSdkAction('user.getInfo', async () => {
34
+ async function connectSDK() {
35
+ try {
35
36
  await hbSDK.ready();
36
- return hbSDK.user.getInfo();
37
- });
37
+ status.value = 'SDK 已连接';
38
+ output.value = '请选择操作。';
39
+ } catch (error) {
40
+ status.value = 'SDK 连接失败';
41
+ output.value = readErrorMessage(error);
42
+ }
38
43
  }
39
44
 
40
45
  async function login() {
41
- await runSdkAction('auth.login', () => hbSDK.auth.login());
42
- }
43
-
44
- async function runSdkAction(label: string, action: () => Promise<unknown>) {
45
46
  loading.value = true;
46
- status.value = `${label} 调用中...`;
47
+ status.value = 'auth.login 调用中...';
47
48
 
48
49
  try {
49
- const result = await action();
50
- status.value = `${label} 调用成功`;
51
- output.value = JSON.stringify(result, null, 2);
50
+ const { expiresIn, scopes } = await loginAndExchangeAuthorizationCode({
51
+ exchangeUrl: import.meta.env.VITE_HEYBOX_AUTH_EXCHANGE_URL,
52
+ login: () => hbSDK.auth.login(),
53
+ request: (config) => hbSDK.network.request(config),
54
+ });
55
+ status.value = 'auth.login 调用成功';
56
+ output.value = JSON.stringify({ expiresIn, scopes }, null, 2);
52
57
  } catch (error) {
53
- status.value = `${label} 调用失败`;
58
+ status.value = 'auth.login 调用失败';
54
59
  output.value = readErrorMessage(error);
55
60
  } finally {
56
61
  loading.value = false;
@@ -3,65 +3,73 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import App from '../App.vue';
4
4
 
5
5
  const sdkMock = vi.hoisted(() => ({
6
- authLogin: vi.fn(),
6
+ login: vi.fn(),
7
+ networkRequest: vi.fn(),
7
8
  ready: vi.fn(),
8
- userGetInfo: vi.fn(),
9
9
  }));
10
10
 
11
11
  vi.mock('@heybox/hb-sdk', () => ({
12
12
  default: {
13
13
  auth: {
14
- login: sdkMock.authLogin,
14
+ login: sdkMock.login,
15
15
  },
16
- ready: sdkMock.ready,
17
- user: {
18
- getInfo: sdkMock.userGetInfo,
16
+ network: {
17
+ request: sdkMock.networkRequest,
19
18
  },
19
+ ready: sdkMock.ready,
20
20
  },
21
21
  }));
22
22
 
23
23
  describe('App', () => {
24
24
  beforeEach(() => {
25
25
  vi.resetAllMocks();
26
+ vi.unstubAllEnvs();
26
27
 
27
28
  sdkMock.ready.mockResolvedValue(undefined);
28
- sdkMock.userGetInfo.mockResolvedValue({
29
- isLogin: true,
30
- userInfo: {
31
- avatar: '',
32
- heybox_id: '10001',
33
- nickname: '模板用户',
34
- },
35
- });
36
- sdkMock.authLogin.mockResolvedValue({
37
- isLogin: true,
38
- userInfo: {
39
- avatar: '',
40
- heybox_id: '10002',
41
- nickname: '登录用户',
42
- },
43
- });
29
+ sdkMock.login.mockResolvedValue({ code: 'authorization_code', expiresIn: 300, scopes: ['identity'] });
30
+ sdkMock.networkRequest.mockResolvedValue({ status: 200 });
44
31
  });
45
32
 
46
- it('connects to hb-sdk and renders user info', async () => {
33
+ it('only waits for SDK readiness during startup', async () => {
47
34
  const wrapper = mount(App);
48
35
  await flushPromises();
49
36
 
50
37
  expect(sdkMock.ready).toHaveBeenCalledTimes(1);
51
- expect(sdkMock.userGetInfo).toHaveBeenCalledTimes(1);
52
- expect(wrapper.get('[data-testid="status"]').text()).toBe('user.getInfo 调用成功');
53
- expect(wrapper.get('[data-testid="output"]').text()).toContain('模板用户');
38
+ expect(sdkMock.login).not.toHaveBeenCalled();
39
+ expect(wrapper.get('[data-testid="status"]').text()).toBe('SDK 已连接');
40
+ expect(wrapper.get('[data-testid="output"]').text()).toContain('请选择操作');
54
41
  });
55
42
 
56
- it('logs in and refreshes the output', async () => {
43
+ it('hands the authorization code to the developer server without rendering it', async () => {
44
+ vi.stubEnv('VITE_HEYBOX_AUTH_EXCHANGE_URL', 'https://developer.example.com/api/heybox/exchange');
57
45
  const wrapper = mount(App);
58
46
  await flushPromises();
59
47
 
60
48
  await wrapper.get('[data-testid="login"]').trigger('click');
61
49
  await flushPromises();
62
50
 
63
- expect(sdkMock.authLogin).toHaveBeenCalledTimes(1);
51
+ expect(sdkMock.login).toHaveBeenCalledTimes(1);
52
+ expect(sdkMock.networkRequest).toHaveBeenCalledWith({
53
+ method: 'POST',
54
+ url: 'https://developer.example.com/api/heybox/exchange',
55
+ data: { code: 'authorization_code' },
56
+ });
64
57
  expect(wrapper.get('[data-testid="status"]').text()).toBe('auth.login 调用成功');
65
- expect(wrapper.get('[data-testid="output"]').text()).toContain('登录用户');
58
+ expect(wrapper.get('[data-testid="output"]').text()).toContain('300');
59
+ expect(wrapper.get('[data-testid="output"]').text()).toContain('identity');
60
+ expect(wrapper.text()).not.toContain('authorization_code');
61
+ });
62
+
63
+ it('does not request a code when the developer server is not configured', async () => {
64
+ const wrapper = mount(App);
65
+ await flushPromises();
66
+
67
+ await wrapper.get('[data-testid="login"]').trigger('click');
68
+ await flushPromises();
69
+
70
+ expect(sdkMock.login).not.toHaveBeenCalled();
71
+ expect(sdkMock.networkRequest).not.toHaveBeenCalled();
72
+ expect(wrapper.get('[data-testid="status"]').text()).toBe('auth.login 调用失败');
73
+ expect(wrapper.get('[data-testid="output"]').text()).toContain('VITE_HEYBOX_AUTH_EXCHANGE_URL');
66
74
  });
67
75
  });
@@ -0,0 +1,46 @@
1
+ export interface AuthorizationCodeLoginResult {
2
+ code: string;
3
+ expiresIn: number;
4
+ scopes: string[];
5
+ }
6
+
7
+ export interface LoginAndExchangeAuthorizationCodeOptions {
8
+ exchangeUrl?: string;
9
+ login: () => Promise<AuthorizationCodeLoginResult>;
10
+ request: (config: {
11
+ method: 'POST';
12
+ url: string;
13
+ data: { code: string };
14
+ }) => Promise<unknown>;
15
+ }
16
+
17
+ export async function loginAndExchangeAuthorizationCode(options: LoginAndExchangeAuthorizationCodeOptions) {
18
+ const exchangeUrl = options.exchangeUrl?.trim();
19
+ if (!exchangeUrl) {
20
+ throw new Error('请先配置 VITE_HEYBOX_AUTH_EXCHANGE_URL');
21
+ }
22
+ if (!isAllowedExchangeUrl(exchangeUrl)) {
23
+ throw new Error('VITE_HEYBOX_AUTH_EXCHANGE_URL 必须使用 HTTPS,本地调试仅允许 localhost');
24
+ }
25
+
26
+ const { code, expiresIn, scopes } = await options.login();
27
+ await options.request({
28
+ method: 'POST',
29
+ url: exchangeUrl,
30
+ data: { code },
31
+ });
32
+
33
+ return { expiresIn, scopes };
34
+ }
35
+
36
+ function isAllowedExchangeUrl(value: string) {
37
+ try {
38
+ const url = new URL(value);
39
+ if (url.protocol === 'https:') {
40
+ return true;
41
+ }
42
+ return url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
package/dist/vite.cjs.js CHANGED
@@ -8,7 +8,7 @@ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentS
8
8
  /** 构建时替换为当前发布包的实际版本。 */
9
9
  const HB_SDK_VERSION = typeof undefined === 'string'
10
10
  ? undefined
11
- : '0.6.9';
11
+ : '0.7.0-alpha.2';
12
12
 
13
13
  var re = {exports: {}};
14
14
 
@@ -11304,7 +11304,7 @@ function parse(html, options) {
11304
11304
  /** 小程序沙盒通信命名空间,父容器与 iframe 内 SDK 必须保持一致。 */
11305
11305
  const MINI_PROGRAM_MESSAGE_NAMESPACE = 'heybox:miniprogram';
11306
11306
  /** 小程序沙盒通信协议版本。 */
11307
- const MINI_PROGRAM_MESSAGE_VERSION = 1;
11307
+ const MINI_PROGRAM_MESSAGE_VERSION = 2;
11308
11308
  /** 父容器注入到小程序 URL 上的通信 nonce 参数名。 */
11309
11309
  const MINI_PROGRAM_BRIDGE_NONCE_PARAM = 'hb_mini_bridge_nonce';
11310
11310
 
package/dist/vite.esm.js CHANGED
@@ -5,7 +5,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
5
5
  /** 构建时替换为当前发布包的实际版本。 */
6
6
  const HB_SDK_VERSION = typeof undefined === 'string'
7
7
  ? undefined
8
- : '0.6.9';
8
+ : '0.7.0-alpha.2';
9
9
 
10
10
  var re = {exports: {}};
11
11
 
@@ -11301,7 +11301,7 @@ function parse(html, options) {
11301
11301
  /** 小程序沙盒通信命名空间,父容器与 iframe 内 SDK 必须保持一致。 */
11302
11302
  const MINI_PROGRAM_MESSAGE_NAMESPACE = 'heybox:miniprogram';
11303
11303
  /** 小程序沙盒通信协议版本。 */
11304
- const MINI_PROGRAM_MESSAGE_VERSION = 1;
11304
+ const MINI_PROGRAM_MESSAGE_VERSION = 2;
11305
11305
  /** 父容器注入到小程序 URL 上的通信 nonce 参数名。 */
11306
11306
  const MINI_PROGRAM_BRIDGE_NONCE_PARAM = 'hb_mini_bridge_nonce';
11307
11307
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heybox/hb-sdk",
3
- "version": "0.6.9",
3
+ "version": "0.7.0-alpha.2",
4
4
  "sideEffects": [
5
5
  "./src/index.ts",
6
6
  "./src/core/singleton.ts",
@@ -95,7 +95,7 @@
95
95
  "vue": "^2.7.16",
96
96
  "vite": "^8.0.12",
97
97
  "vitest": "^3.2.4",
98
- "@heybox/hb-api": "~1.25.11"
98
+ "@heybox/hb-api": "~1.25.16"
99
99
  },
100
100
  "publishConfig": {
101
101
  "registry": "https://registry.npmjs.org/",