@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0-alpha.2
4
+
5
+ ### CLI
6
+
7
+ - `hb-sdk dev` 绑定远端小程序后签发并轮换 local_dev 启动票,把独立的 `preview_token` 写入扫码 URL,供 Host 兑换 unpublished 公开详情。
8
+
9
+ ### Runtime
10
+
11
+ - 允许的 `mini_url` 调试入口会绑定 Host 上的 `mini_program_id`;公开详情对 unpublished 失败时也不丢这个身份。
12
+
13
+ ## 0.7.0-alpha.1
14
+
15
+ ### Docs / Agent Skill
16
+
17
+ - 开发者服务端改为调用 `GET /user_miniprogram/openapi/v1/user` 读取当前用户,不再使用已下线的 `/user/identity` 与 `/user/profile`。
18
+
19
+ ### Runtime
20
+
21
+ - 公开详情关网时的合法空权限快照视为未开通 `network.request`,允许本地身份读取与撤销授权。
22
+
23
+ ## 0.7.0-alpha.0
24
+
25
+ ### Release
26
+
27
+ - 硬切小程序隐私用户身份 v2 契约,并保留 v1 bridge 握手兼容。
28
+ - 排行榜记录的公开用户标识从 `userId` 硬切为按小程序隔离的 `appUserId`。
29
+
30
+ ### SDK
31
+
32
+ - 已开通网络权限的小程序统一通过 `auth.login({ scopes? })` 获取 5 分钟短期授权码;identity 隐式强制包含,返回值不再携带用户、资料或 `appUserId`。
33
+ - 未开通网络权限的小程序通过 `user.getLocalIdentity()` 读取当前小程序内隔离的 `appUserId`,不能获取授权码或调用 OpenAPI。
34
+ - 新增 `user.revokeAuthorization()`,在两种网络模式下由可信用户操作撤销当前账号对当前小程序的授权。
35
+ - 已开通网络权限时,Host current-user 资料与平台账号读取统一提示 `SERVER_API_REQUIRED`,由开发者服务端通过 OpenAPI 获取。
36
+
37
+ ### Security
38
+
39
+ - 授权码只能交给开发者服务端换取 token;小程序页面不得持有服务端应用凭据,`network.request` 也不能访问平台保留的 runtime auth 与 OpenAPI 内部路径。
40
+ - 需要授权 UI 且缺少可信用户手势时返回 `USER_GESTURE_REQUIRED`;用户取消或关闭时返回 `AUTHORIZATION_CANCELLED`,已授权场景可静默签发新 code。
41
+
42
+ ### Docs / Agent Skill
43
+
44
+ - README、用户身份 Guide、快速开始、登录门禁、错误处理、生命周期、CLI 与排行榜文档同步新身份边界,并移除旧用户授权 API 指引。
45
+
3
46
  ## 0.6.9
4
47
 
5
48
  ### SDK
@@ -45,16 +88,17 @@
45
88
 
46
89
  ### SDK
47
90
 
48
- - `network.request` 失败时输出脱敏诊断日志,覆盖 HTTP 非 2xx、无效响应与 bridge/runtime 错误;`HbMiniProgramNetworkError` 文案补充 statusText/method/url(message 为诊断字符串,业务请用 `instanceof` + `status`/`data` 等字段,勿精确匹配全文) (@heybox/hb-sdk).
49
- - 诊断日志与错误文案统一脱敏:敏感 header/query/body 字段、URL userinfo;Mock Host / mock network proxy 复用同一套脱敏原语 (@heybox/hb-sdk).
50
- - 日志前缀:`[hb-sdk][network.request]` / `[hb-sdk][mock-host][network.request]` / `[hb-sdk][mock-network]` (@heybox/hb-sdk).
51
- - `network.request` 提前拒绝 `multipart/form-data`(含手写 boundary body),以 `INVALID_PARAMS` 给出可操作中文说明;Mock 网络代理同步拒绝,避免本地 fetch 能通、App Host heybox 代发失败 (@heybox/hb-sdk).
52
- - `HbMiniProgramNetworkError` 识别宿主伪装的 `network_error` / `unsupported-request-shape` 失败,在 message 中标明“非目标站 HTTP 状态”并补充 form/json 用法提示 (@heybox/hb-sdk).
91
+ - 用户身份 API 收敛为 `auth.requireHeyboxAppLogin()`、`user.getInfo()` `user.authorize()`;登录不再返回资料,身份使用按小程序隔离的 `app_user_id`,资料按授权 scope 披露。
92
+ - Vue 3 模板、调试示例与公开文档同步使用 v2 登录和资料授权流程。
53
93
 
54
94
  ### Runtime
55
95
 
56
- - Runtime 侧记录 `network.request` 参数校验失败、adapter 失败与非 2xx 诊断日志(前缀 `[hb-sdk-runtime][network.request]`;非 2xx 仍保持 bridge success) (@heybox/hb-sdk-runtime).
57
- - Runtime 参数校验同步拒绝 multipart,避免落到 heybox transport 后变成含糊的 500 `network_error` (@heybox/hb-sdk-runtime).
96
+ - Host 集成改用稳定的用户身份隔离接口,并由 Runtime 构造授权结果,避免转发后端额外字段。
97
+ - `user.authorize` 对畸形 payload 稳定返回 `INVALID_PARAMS`;浏览器 Mock 使用 `heybox_app_login_change` 与 `user_info_authorization_change` 事件,并保持授权事件不携带身份资料。
98
+
99
+ ### Docs / Agent Skill
100
+
101
+ - 同步 README、Guide、Recipe、API Reference 与 Agent Skill,移除已删除的用户 API 指引。
58
102
 
59
103
  ## 0.6.8-alpha.2
60
104
 
package/README.md CHANGED
@@ -23,20 +23,49 @@ npm run dev
23
23
  npm install @heybox/hb-sdk
24
24
  ```
25
25
 
26
- 页面启动时等待 SDK 就绪,再读取当前用户状态:
26
+ 页面启动时先等待 SDK 就绪。未开通网络权限的小程序可以读取当前小程序内的隔离身份:
27
27
 
28
28
  ```ts
29
29
  import hbSDK from '@heybox/hb-sdk';
30
30
 
31
- await hbSDK.ready();
31
+ async function getLocalIdentityFromUserAction() {
32
+ await hbSDK.ready();
33
+ const { appUserId } = await hbSDK.user.getLocalIdentity();
34
+ return appUserId;
35
+ }
36
+ ```
37
+
38
+ 把 `getLocalIdentityFromUserAction` 绑定到按钮点击等可信用户操作。首次登录或 identity consent 需要 UI 而缺少可信手势时返回 `USER_GESTURE_REQUIRED`;用户取消或关闭时返回 `AUTHORIZATION_CANCELLED`,不会确认或改变授权状态。
39
+
40
+ 已开通网络权限的小程序在按钮点击等明确的用户操作中调用 `auth.login()`:
41
+
42
+ ```ts
43
+ async function loginFromUserAction() {
44
+ const { code, expiresIn, scopes } = await hbSDK.auth.login({ scopes: ['profile'] });
45
+ await hbSDK.network.request({
46
+ url: 'https://api.example.com/session/heybox',
47
+ method: 'POST',
48
+ data: { code },
49
+ });
50
+ console.log(expiresIn, scopes);
51
+ }
52
+ ```
53
+
54
+ `auth.login()` 返回 `{ code, expiresIn: 300, scopes }`,其中 `identity` 隐式强制包含;它不返回 user、profile、`appUserId` 或 token。页面只能把 code 交给开发者服务端,再由服务端按后端 OpenAPI 文档换取 token。未开通网络权限的小程序使用 `user.getLocalIdentity()`,无法获取 code 或调用 OpenAPI。完整边界见[用户身份与登录](https://docs.xiaoheihe.cn/hb_sdk/guide/auth)。
55
+
56
+ 用户可以在明确的按钮点击和二次确认后撤销当前小程序的授权:
57
+
58
+ ```ts
59
+ async function revokeAuthorizationFromUserAction() {
60
+ const confirmed = window.confirm('确定撤销当前小程序的用户授权吗?');
61
+ if (!confirmed) return;
32
62
 
33
- const result = await hbSDK.user.getInfo();
34
- if (result.isLogin && result.userInfo) {
35
- console.log(result.userInfo.nickname);
63
+ await hbSDK.user.revokeAuthorization();
64
+ // 同步清理业务保存的 appUserId、用户资料和服务端会话。
36
65
  }
37
66
  ```
38
67
 
39
- `ready()` 只表示 SDK 可以调用,不代表用户已经登录。需要登录时,请在按钮点击等明确的用户操作中调用 `hbSDK.auth.login()`。
68
+ `user.revokeAuthorization()` 在已开通和未开通网络权限的小程序中都可用,不接收小程序 ID、黑盒用户 ID 或手势参数。调用必须来自可信用户操作,否则返回 `USER_GESTURE_REQUIRED`。撤销成功后,后续身份或授权读取会重新进入授权流程;SDK 不会替业务清理已经持久化的数据。
40
69
 
41
70
  已有 Vite 项目还需要启用构建插件:
42
71
 
@@ -76,31 +105,65 @@ try {
76
105
 
77
106
  除 `ready()` 外,SDK 还提供 `on()` 和 `off()` 处理前后台、登录态等生命周期事件。
78
107
 
79
- | 模块 | 用途 |
80
- | ------------ | -------------------------------- |
81
- | `auth` | 由用户操作触发登录 |
82
- | `user` | 读取当前用户、账号资料和游戏数据 |
83
- | `share` | 打开分享或截图分享流程 |
84
- | `ui` | 展示 Toast 和 Loading |
85
- | `device` | 调用振动和剪贴板能力 |
86
- | `navigation` | 关闭、刷新页面或打开小黑盒页面 |
87
- | `viewport` | 读取窗口信息和设置导航栏样式 |
88
- | `storage` | 读写当前小程序的隔离存储 |
89
- | `cloud` | 使用小程序云端排行榜 |
90
- | `network` | 发起经过平台授权的网络请求 |
108
+ | 模块 | 用途 |
109
+ | ------------ | ------------------------------------------------------------ |
110
+ | `auth` | 获取交给开发者服务端交换的短期授权码 |
111
+ | `user` | 使用 Host current-user APIs 或读取无网络小程序的本地隔离身份 |
112
+ | `share` | 打开分享或截图分享流程 |
113
+ | `ui` | 展示 Toast 和 Loading |
114
+ | `device` | 调用振动和剪贴板能力 |
115
+ | `navigation` | 关闭、刷新页面或打开小黑盒页面 |
116
+ | `viewport` | 读取窗口信息和设置导航栏样式 |
117
+ | `storage` | 读写当前小程序的隔离存储 |
118
+ | `cloud` | 使用小程序云端排行榜 |
119
+ | `network` | 发起经过平台授权的网络请求 |
91
120
 
92
121
  具体方法、参数和返回值以 [API Reference](https://docs.xiaoheihe.cn/hb_sdk/reference/) 为准,常见组合写法见 [Recipes](https://docs.xiaoheihe.cn/hb_sdk/recipes/)。
93
122
 
94
123
  ## 关键约束
95
124
 
96
- - `ready()` 不代表用户已登录;使用 `user.getInfo()` 判断登录状态。
97
- - `auth.login()` 只能由明确的用户操作触发,不要在页面初始化时自动登录。
125
+ - `ready()` 只表示 SDK 可调用,不表示已取得用户授权。
126
+ - `auth.login({ scopes? })` 只返回 `{ code, expiresIn: 300, scopes }`;identity 隐式强制包含,code 只能提交给开发者服务端。
127
+ - 需要授权 UI 时,`auth.login()` 必须来自可信用户手势,否则返回 `USER_GESTURE_REQUIRED`;用户取消或关闭时返回 `AUTHORIZATION_CANCELLED`。已授权时可以静默返回新 code。
128
+ - `user.getSteamGameList()` 仅供未开通网络权限的小程序通过 Host 读取;已开通网络权限时返回 `SERVER_API_REQUIRED`,且授权码与 OpenAPI 不提供 Steam 游戏库 scope 或资源接口。
129
+ - 已开通网络权限时,公开的 `user.getInfo()` 和 `user.getLocalIdentity()` 返回 `SERVER_API_REQUIRED`;对应身份和资料数据应由开发者服务端通过 OpenAPI 获取。
130
+ - 未开通网络权限时,只用 `user.getLocalIdentity()` 获取 `{ appUserId }`,不能获取 code 或调用 OpenAPI。
131
+ - `user.revokeAuthorization()` 在两种网络模式下都要求可信用户手势;成功后必须停止使用并清理业务缓存的旧身份、资料和服务端会话。
98
132
  - `on()` 返回取消监听函数,组件卸载或页面销毁时需要调用。
99
133
  - 业务网络请求使用 `network.request()`,不要依赖浏览器原生网络出口。
134
+ - `network.request()` 不能访问平台保留的 runtime auth 与 OpenAPI 内部路径;页面不得持有或交换服务端应用凭据。
100
135
  - `network.request` 不支持 `multipart/form-data`;App Host 仅支持 form(`application/x-www-form-urlencoded`)与 JSON。表单请用 `URLSearchParams#toString()` 作为 `data`,文件上传请走专用上传能力。
101
136
  - 仅当远端已启用 `network.request` 且 `useOfficialDomain=true` 时,`hb-sdk dev` 与 `hb-sdk remote deploy` 构建才会跳过平台 CSP;其他构建继续注入平台 CSP。
102
137
  - 构建必须启用 `miniappManifest()`,推荐统一使用 `hb-sdk build`。
103
138
 
139
+ ## 云端排行榜
140
+
141
+ 删除当前用户的排行榜成绩属于破坏性操作,只能在明确的用户操作和二次确认后执行:
142
+
143
+ ```ts
144
+ async function deleteMyLeaderboardEntryFromUserAction() {
145
+ const confirmed = window.confirm('确定删除自己的榜单成绩吗?');
146
+ if (!confirmed) return;
147
+
148
+ try {
149
+ await hbSDK.cloud.leaderboard.deleteCurrentUserEntry({ key: 'cube_run_total' });
150
+ } catch (error) {
151
+ if (error instanceof HbMiniProgramSDKError) {
152
+ console.error(error.code, error.message);
153
+ }
154
+ throw error;
155
+ }
156
+ }
157
+ ```
158
+
159
+ 排行榜记录的 `extra` 会展示给榜单中的其他读取者,只能包含公开展示必需的数据;不得写入个人身份信息(PII)、token、设备标识或非必要行为数据。
160
+
161
+ 管理员删除完整榜单及其排名记录时,必须显式传入匹配的榜单 key 进行确认:
162
+
163
+ ```bash
164
+ hb-sdk remote cloud leaderboard delete <key> --confirm <key>
165
+ ```
166
+
104
167
  ## CLI 主流程
105
168
 
106
169
  ```bash
@@ -3,10 +3,10 @@
3
3
  var fs$1 = require('node:fs');
4
4
  var fs = require('node:fs/promises');
5
5
  var path = require('node:path');
6
- var runtimePermissionEnv = require('./runtime-permission-env-DKrhgVM3.cjs');
7
- var runtimeGate = require('./runtime-gate-BWlU-R4h.cjs');
8
- var index = require('./index-tCURDEx8.cjs');
9
- var projectVite = require('./project-vite-DAUaP0_3.cjs');
6
+ var runtimePermissionEnv = require('./runtime-permission-env-D-8_jPG3.cjs');
7
+ var runtimeGate = require('./runtime-gate-CtV7rWyX.cjs');
8
+ var index = require('./index-BnBeTd0B.cjs');
9
+ var projectVite = require('./project-vite-CgnCpNDZ.cjs');
10
10
  require('node:async_hooks');
11
11
  require('node:module');
12
12
  require('path');
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var session = require('./session-D6jOKXeu.cjs');
4
- var runtimePermissionEnv = require('./runtime-permission-env-DKrhgVM3.cjs');
3
+ var session = require('./session-Davw2CMd.cjs');
4
+ var runtimePermissionEnv = require('./runtime-permission-env-D-8_jPG3.cjs');
5
5
  var fs$1 = require('node:fs');
6
6
  var fs = require('node:fs/promises');
7
7
  var path = require('node:path');
@@ -109,6 +109,7 @@ function createRemoteApiClient(options) {
109
109
  listUserMiniprograms: (requestOptions = {}) => listUserMiniprograms(requestOptions, options),
110
110
  createUserMiniprogram: () => createUserMiniprogram(options),
111
111
  getUserMiniprogramDetail: (requestOptions) => getUserMiniprogramDetail(requestOptions, options),
112
+ createLocalDevLaunchToken: (requestOptions) => createLocalDevLaunchToken$1(requestOptions, options),
112
113
  getPreviewAllowlist: (requestOptions) => getPreviewAllowlist(requestOptions, options),
113
114
  updatePreviewAllowlist: (requestOptions) => updatePreviewAllowlist(requestOptions, options),
114
115
  createUserMiniprogramLeaderboard: (requestOptions) => createUserMiniprogramLeaderboard(requestOptions, options),
@@ -144,6 +145,9 @@ async function createUserMiniprogram(runtime) {
144
145
  async function getUserMiniprogramDetail(options, runtime) {
145
146
  return remoteGet(runtimePermissionEnv.DETAIL_USER_MINIPROGRAM_API_PATH, options, runtime);
146
147
  }
148
+ async function createLocalDevLaunchToken$1(options, runtime) {
149
+ return remotePostForm(runtimePermissionEnv.CREATE_USER_MINIPROGRAM_LOCAL_DEV_LAUNCH_TOKEN_API_PATH, options, runtime);
150
+ }
147
151
  async function getPreviewAllowlist(options, runtime) {
148
152
  return remoteGet(runtimePermissionEnv.USER_MINIPROGRAM_PREVIEW_ALLOWLIST_API_PATH, options, runtime);
149
153
  }
@@ -384,6 +388,14 @@ async function getBoundMiniProgram(options = {}, runtime = {}) {
384
388
  detail,
385
389
  };
386
390
  }
391
+ async function createLocalDevLaunchToken(options, runtime = {}) {
392
+ const miniProgramId = options.miniProgramId.trim();
393
+ if (!miniProgramId) {
394
+ throw new Error('mini-program-id 不能为空');
395
+ }
396
+ const api = await resolveRemoteApiClient(options, runtime);
397
+ return api.createLocalDevLaunchToken({ mini_program_id: miniProgramId });
398
+ }
387
399
  async function resolveRemoteApiClient(options, runtime) {
388
400
  if (runtime.apiClient) {
389
401
  return runtime.apiClient;
@@ -397,6 +409,7 @@ function isTruthyEnvFlag(value) {
397
409
  }
398
410
 
399
411
  exports.MiniProgramProjectBindingError = MiniProgramProjectBindingError;
412
+ exports.createLocalDevLaunchToken = createLocalDevLaunchToken;
400
413
  exports.createMiniProgramMockPermissionState = createMiniProgramMockPermissionState;
401
414
  exports.createMiniProgramMockRuntimePermissions = createMiniProgramMockRuntimePermissions;
402
415
  exports.createRemoteApiClientForEnvironment = createRemoteApiClientForEnvironment;
@@ -5,7 +5,7 @@ var fs = require('node:fs/promises');
5
5
  var path = require('node:path');
6
6
  var require$$0 = require('fs');
7
7
  var require$$1 = require('path');
8
- var index = require('./index-tCURDEx8.cjs');
8
+ var index = require('./index-BnBeTd0B.cjs');
9
9
  require('node:module');
10
10
  require('os');
11
11
  require('readline');
@@ -6,15 +6,15 @@ var path = require('node:path');
6
6
  var net = require('node:net');
7
7
  var promises = require('node:dns/promises');
8
8
  var node_http = require('node:http');
9
- var context = require('./context-DX1KG-8A.cjs');
9
+ var context = require('./context-Bl_Q4RKq.cjs');
10
10
  var node_crypto = require('node:crypto');
11
11
  var undici = require('undici');
12
12
  var browser = require('./browser-RAy8e8cV.cjs');
13
- var index = require('./index-tCURDEx8.cjs');
14
- var projectVite = require('./project-vite-DAUaP0_3.cjs');
13
+ var index = require('./index-BnBeTd0B.cjs');
14
+ var projectVite = require('./project-vite-CgnCpNDZ.cjs');
15
15
  var fs$1 = require('node:fs/promises');
16
- var runtimePermissionEnv = require('./runtime-permission-env-DKrhgVM3.cjs');
17
- require('./session-D6jOKXeu.cjs');
16
+ var runtimePermissionEnv = require('./runtime-permission-env-D-8_jPG3.cjs');
17
+ require('./session-Davw2CMd.cjs');
18
18
  require('fs');
19
19
  require('constants');
20
20
  require('stream');
@@ -896,18 +896,19 @@ const MOCK_HOST_ROOT_CANDIDATES = [
896
896
  async function startMiniProgramMockHostServer(options) {
897
897
  const root = options.root ?? resolveMiniProgramMockHostRoot(options.rootCandidates);
898
898
  assertCompleteMiniProgramMockHostRoot(root);
899
- const bootstrapJson = serializeMockHostBootstrap({
899
+ const bootstrap = {
900
900
  defaultLanAddressId: options.defaultLanAddressId,
901
901
  devContext: options.devContext,
902
902
  lanAddresses: options.lanAddresses ?? [],
903
903
  macAppProtocol: options.macAppProtocol,
904
904
  nativeAppLaunchUnavailableReason: options.nativeAppLaunchUnavailableReason,
905
905
  runtimePermissions: options.runtimePermissions,
906
- });
906
+ };
907
+ let bootstrapJson = serializeMockHostBootstrap(bootstrap);
907
908
  const server = createMiniProgramMockHostServer(root, {
908
909
  appUrl: options.appUrl,
909
910
  allowedMiniUrlOrigins: new Set((options.lanAddresses ?? []).map(({ appUrl }) => new URL(appUrl).origin)),
910
- bootstrapJson,
911
+ readBootstrapJson: () => bootstrapJson,
911
912
  fetchImpl: options.fetchImpl ?? fetch,
912
913
  isLoopbackPeer: options.isLoopbackPeer ?? isLoopbackAddress,
913
914
  lookupHostname: options.lookupHostname ?? (async (hostname) => promises.lookup(hostname, { all: true, verbatim: true })),
@@ -921,6 +922,17 @@ async function startMiniProgramMockHostServer(options) {
921
922
  close: () => new Promise((resolve) => {
922
923
  server.close(() => resolve());
923
924
  }),
925
+ updateBootstrap: (next) => {
926
+ if (next.devContext !== undefined)
927
+ bootstrap.devContext = next.devContext;
928
+ if (next.lanAddresses !== undefined)
929
+ bootstrap.lanAddresses = next.lanAddresses;
930
+ if (next.macAppProtocol === null)
931
+ delete bootstrap.macAppProtocol;
932
+ else if (next.macAppProtocol !== undefined)
933
+ bootstrap.macAppProtocol = next.macAppProtocol;
934
+ bootstrapJson = serializeMockHostBootstrap(bootstrap);
935
+ },
924
936
  networkUrls: createMiniProgramMockHostNetworkUrls({
925
937
  lanAddresses: options.lanAddresses,
926
938
  port,
@@ -988,6 +1000,7 @@ function createMiniProgramMockHostServer(root, runtime) {
988
1000
  const pathname = decodeURIComponent(requestUrl.pathname);
989
1001
  const devSessionRoute = devSessionRoutes.match(pathname);
990
1002
  const loopbackPeer = runtime.isLoopbackPeer(request.socket.remoteAddress);
1003
+ // Dev Session 需要允许手机局域网访问;其余 mock 页仍限制本机。
991
1004
  if (!loopbackPeer && !devSessionRoute) {
992
1005
  writeForbidden(response);
993
1006
  return;
@@ -1012,7 +1025,7 @@ function createMiniProgramMockHostServer(root, runtime) {
1012
1025
  writeMethodNotAllowed(response, 'GET');
1013
1026
  return;
1014
1027
  }
1015
- writeSerializedJsonResponse(response, 200, runtime.bootstrapJson);
1028
+ writeSerializedJsonResponse(response, 200, runtime.readBootstrapJson());
1016
1029
  return;
1017
1030
  }
1018
1031
  if (pathname === DEV_PERMISSIONS_PATH && runtime.permissionController) {
@@ -1224,13 +1237,15 @@ async function listenHttpServer(server, options, getPortImpl) {
1224
1237
  const MINI_PROGRAM_URL_QUERY_PARAM = 'mini_url';
1225
1238
  const MINI_PROGRAM_RUNTIME_URL_QUERY_PARAM = 'runtime_url';
1226
1239
  const MINI_PROGRAM_SDK_VERSION_QUERY_PARAM = 'sdk_version';
1240
+ const MINI_PROGRAM_LOCAL_DEV_LAUNCH_TOKEN_QUERY_PARAM = 'local_dev_launch_token';
1227
1241
  const MINI_PROGRAM_DEV_SHELL_URL = 'heybox-mini-dev://sandbox';
1228
1242
  const OPEN_IN_APP_URL = 'https://api.xiaoheihe.cn/open_inapp/';
1229
1243
  function createMacAppProtocol(appUrl, options = {}) {
1230
1244
  return createHeyboxProtocol(createMiniProgramDevShellOpenWindowPayload(appUrl, options));
1231
1245
  }
1232
1246
  function createMobileAppQrPayload(appUrl, options = {}) {
1233
- return `${OPEN_IN_APP_URL}#${createHeyboxProtocol(createMiniProgramDevShellOpenWindowPayload(appUrl, { ...options, encodeMiniUrl: false }))}`;
1247
+ const protocol = createHeyboxProtocol(createMiniProgramDevShellOpenWindowPayload(appUrl, { ...options, encodeMiniUrl: false }));
1248
+ return options.bareProtocol ? protocol : `${OPEN_IN_APP_URL}#${protocol}`;
1234
1249
  }
1235
1250
  function createMiniProgramDevShellOpenWindowPayload(appUrl, options = {}) {
1236
1251
  const devShellUrl = options.encodeMiniUrl === false
@@ -1258,8 +1273,13 @@ function createPartiallyEncodedMiniProgramDevShellUrl(appUrl, options) {
1258
1273
  if (miniProgramId) {
1259
1274
  devShellUrl += `&mini_program_id=${encodeURIComponent(miniProgramId)}`;
1260
1275
  }
1276
+ const localDevLaunchToken = options.localDevLaunchToken?.trim();
1277
+ if (localDevLaunchToken) {
1278
+ devShellUrl += `&${MINI_PROGRAM_LOCAL_DEV_LAUNCH_TOKEN_QUERY_PARAM}=${encodeURIComponent(localDevLaunchToken)}`;
1279
+ }
1261
1280
  if (hasRuntimeUrl(options.runtimeUrl)) {
1262
- devShellUrl += `&${MINI_PROGRAM_RUNTIME_URL_QUERY_PARAM}=${encodeURIComponent(options.runtimeUrl)}`;
1281
+ const runtimeUrl = buildRuntimeUrlWithLaunchParams(options.runtimeUrl, appUrl, options);
1282
+ devShellUrl += `&${MINI_PROGRAM_RUNTIME_URL_QUERY_PARAM}=${encodeURIComponent(runtimeUrl)}`;
1263
1283
  }
1264
1284
  const sdkVersion = options.sdkVersion?.trim();
1265
1285
  if (sdkVersion) {
@@ -1274,8 +1294,12 @@ function createEncodedMiniProgramDevShellUrl(appUrl, options) {
1274
1294
  if (miniProgramId) {
1275
1295
  devShellUrl.searchParams.set('mini_program_id', miniProgramId);
1276
1296
  }
1297
+ const localDevLaunchToken = options.localDevLaunchToken?.trim();
1298
+ if (localDevLaunchToken) {
1299
+ devShellUrl.searchParams.set(MINI_PROGRAM_LOCAL_DEV_LAUNCH_TOKEN_QUERY_PARAM, localDevLaunchToken);
1300
+ }
1277
1301
  if (hasRuntimeUrl(options.runtimeUrl)) {
1278
- devShellUrl.searchParams.set(MINI_PROGRAM_RUNTIME_URL_QUERY_PARAM, options.runtimeUrl);
1302
+ devShellUrl.searchParams.set(MINI_PROGRAM_RUNTIME_URL_QUERY_PARAM, buildRuntimeUrlWithLaunchParams(options.runtimeUrl, appUrl, options));
1279
1303
  }
1280
1304
  const sdkVersion = options.sdkVersion?.trim();
1281
1305
  if (sdkVersion) {
@@ -1286,6 +1310,36 @@ function createEncodedMiniProgramDevShellUrl(appUrl, options) {
1286
1310
  function hasRuntimeUrl(runtimeUrl) {
1287
1311
  return runtimeUrl !== undefined && runtimeUrl !== '';
1288
1312
  }
1313
+ /**
1314
+ * 客户端加载 runtime_url 时,常只打开该 URL 本身,不会把 sandbox 上的 mini_url 再拼进去。
1315
+ * 电脑浏览器手开 detail 能成功,是因为 URL 上已经带了 mini_url。
1316
+ * 因此 HTTP(S) runtime host 需要在 runtime_url 内预置启动参数。
1317
+ */
1318
+ function buildRuntimeUrlWithLaunchParams(runtimeUrl, appUrl, options) {
1319
+ try {
1320
+ const url = new URL(runtimeUrl);
1321
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
1322
+ return runtimeUrl;
1323
+ }
1324
+ url.searchParams.set(MINI_PROGRAM_URL_QUERY_PARAM, appUrl);
1325
+ const miniProgramId = options.miniProgramId?.trim();
1326
+ if (miniProgramId) {
1327
+ url.searchParams.set('mini_program_id', miniProgramId);
1328
+ }
1329
+ const localDevLaunchToken = options.localDevLaunchToken?.trim();
1330
+ if (localDevLaunchToken) {
1331
+ url.searchParams.set(MINI_PROGRAM_LOCAL_DEV_LAUNCH_TOKEN_QUERY_PARAM, localDevLaunchToken);
1332
+ }
1333
+ const sdkVersion = options.sdkVersion?.trim();
1334
+ if (sdkVersion) {
1335
+ url.searchParams.set(MINI_PROGRAM_SDK_VERSION_QUERY_PARAM, sdkVersion);
1336
+ }
1337
+ return url.toString();
1338
+ }
1339
+ catch {
1340
+ return runtimeUrl;
1341
+ }
1342
+ }
1289
1343
  function createHeyboxProtocol(payload) {
1290
1344
  return `heybox://${encodeURIComponent(JSON.stringify(payload))}`;
1291
1345
  }
@@ -1293,7 +1347,7 @@ function createHeyboxProtocol(payload) {
1293
1347
  /** 构建时替换为当前发布包的实际版本。 */
1294
1348
  const HB_SDK_VERSION = typeof undefined === 'string'
1295
1349
  ? undefined
1296
- : '0.6.9';
1350
+ : '0.7.0-alpha.2';
1297
1351
 
1298
1352
  class DevPermissionOverrideStoreError extends Error {
1299
1353
  code;
@@ -1476,6 +1530,8 @@ async function runDevCommand(options, runtime = {}) {
1476
1530
  hmrHost: resolvedOptions.hmrHost,
1477
1531
  interfaces: networkInterfaceSnapshot,
1478
1532
  miniProgramId: devLaunchContext.miniProgramId,
1533
+ localDevLaunchToken: devLaunchContext.localDevLaunchToken,
1534
+ rawProtocol: options.rawProtocol,
1479
1535
  runtimeUrl: options.runtimeUrl,
1480
1536
  sdkVersion: HB_SDK_VERSION,
1481
1537
  viteNetworkUrls: appServer.resolvedUrls?.network ?? [],
@@ -1493,6 +1549,7 @@ async function runDevCommand(options, runtime = {}) {
1493
1549
  lookupHostname: runtime.lookupHostname,
1494
1550
  macAppProtocol: createMacAppProtocol(appUrl, {
1495
1551
  miniProgramId: devLaunchContext.miniProgramId,
1552
+ localDevLaunchToken: devLaunchContext.localDevLaunchToken,
1496
1553
  runtimeUrl: options.runtimeUrl,
1497
1554
  sdkVersion: HB_SDK_VERSION,
1498
1555
  }),
@@ -1508,6 +1565,45 @@ async function runDevCommand(options, runtime = {}) {
1508
1565
  throw error;
1509
1566
  }
1510
1567
  closers.push(() => mockHost.close());
1568
+ if (devLaunchContext.source === 'remote' && devLaunchContext.miniProgramId) {
1569
+ const tokenSession = startLocalDevLaunchTokenSession({
1570
+ env: runtime.env,
1571
+ fetchImpl,
1572
+ logger,
1573
+ miniProgramId: devLaunchContext.miniProgramId,
1574
+ onToken: (localDevLaunchToken) => {
1575
+ const tokenContext = {
1576
+ source: 'remote',
1577
+ miniProgramId: devLaunchContext.miniProgramId,
1578
+ ...(localDevLaunchToken ? { localDevLaunchToken } : {}),
1579
+ runtimePermissions: devLaunchContext.runtimePermissions,
1580
+ };
1581
+ mockHost.updateBootstrap({
1582
+ devContext: tokenContext,
1583
+ lanAddresses: createLanAddressCandidates({
1584
+ appUrl,
1585
+ hmrHost: resolvedOptions.hmrHost,
1586
+ interfaces: networkInterfaceSnapshot,
1587
+ miniProgramId: devLaunchContext.miniProgramId,
1588
+ localDevLaunchToken,
1589
+ rawProtocol: options.rawProtocol,
1590
+ runtimeUrl: options.runtimeUrl,
1591
+ sdkVersion: HB_SDK_VERSION,
1592
+ viteNetworkUrls: appServer.resolvedUrls?.network ?? [],
1593
+ }),
1594
+ macAppProtocol: createMacAppProtocol(appUrl, {
1595
+ miniProgramId: devLaunchContext.miniProgramId,
1596
+ localDevLaunchToken,
1597
+ runtimeUrl: options.runtimeUrl,
1598
+ sdkVersion: HB_SDK_VERSION,
1599
+ }),
1600
+ });
1601
+ },
1602
+ projectRoot,
1603
+ timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS,
1604
+ });
1605
+ closers.push(tokenSession.close);
1606
+ }
1511
1607
  logger.success('Mock runtime host 已就绪');
1512
1608
  logger.info(`Mock runtime host: ${mockHost.url}`);
1513
1609
  logger.info(`Mini program URL: ${appUrl}`);
@@ -1808,6 +1904,89 @@ function installShutdownHandlers(closers, processLike) {
1808
1904
  async function closeAll(closers) {
1809
1905
  await Promise.allSettled(closers.map((close) => close()));
1810
1906
  }
1907
+ function startLocalDevLaunchTokenSession(options) {
1908
+ let closed = false;
1909
+ let expirationTimer;
1910
+ let refreshTimer;
1911
+ const refresh = async () => {
1912
+ try {
1913
+ const issued = await issueLocalDevLaunchToken(options);
1914
+ if (closed)
1915
+ return;
1916
+ if (!issued) {
1917
+ schedule(options.timeoutMs);
1918
+ return;
1919
+ }
1920
+ options.onToken(issued.token);
1921
+ const ttl = Math.max(0, issued.expiresAt - Date.now());
1922
+ scheduleExpiration(ttl);
1923
+ schedule(Math.max(1_000, Math.floor(ttl * 0.8)));
1924
+ }
1925
+ catch (error) {
1926
+ if (closed)
1927
+ return;
1928
+ options.logger.warn(`刷新本地调试启动票失败,将自动重试:${index.readRedactedErrorMessage(error)}`);
1929
+ schedule(options.timeoutMs);
1930
+ }
1931
+ };
1932
+ const schedule = (delayMs) => {
1933
+ refreshTimer = setTimeout(() => {
1934
+ void refresh();
1935
+ }, Math.max(1, delayMs));
1936
+ refreshTimer.unref?.();
1937
+ };
1938
+ const scheduleExpiration = (ttl) => {
1939
+ if (expirationTimer)
1940
+ clearTimeout(expirationTimer);
1941
+ expirationTimer = setTimeout(() => {
1942
+ if (!closed)
1943
+ options.onToken(undefined);
1944
+ }, Math.max(1, ttl));
1945
+ expirationTimer.unref?.();
1946
+ };
1947
+ void refresh();
1948
+ return {
1949
+ close: () => {
1950
+ closed = true;
1951
+ if (expirationTimer)
1952
+ clearTimeout(expirationTimer);
1953
+ if (refreshTimer)
1954
+ clearTimeout(refreshTimer);
1955
+ },
1956
+ };
1957
+ }
1958
+ async function issueLocalDevLaunchToken(options) {
1959
+ try {
1960
+ const issued = await loadRemoteDevContextWithTimeout({
1961
+ fetchImpl: options.fetchImpl,
1962
+ timeoutMs: options.timeoutMs,
1963
+ load: (fetchImpl) => context.createLocalDevLaunchToken({
1964
+ cwd: options.projectRoot,
1965
+ env: options.env,
1966
+ fetchImpl,
1967
+ miniProgramId: options.miniProgramId,
1968
+ }),
1969
+ });
1970
+ const token = typeof issued.token === 'string' ? issued.token.trim() : '';
1971
+ if (!token) {
1972
+ throw new Error('响应缺少 token');
1973
+ }
1974
+ const expiresAt = normalizeExpiresAt(issued.expires_at);
1975
+ if (!expiresAt || expiresAt <= Date.now()) {
1976
+ throw new Error('响应缺少有效 expires_at');
1977
+ }
1978
+ return { expiresAt, token };
1979
+ }
1980
+ catch (error) {
1981
+ options.logger.warn(`未能签发本地调试启动票,扫码后公开详情可能仍拿不到未发布小程序身份:${index.readRedactedErrorMessage(error)}`);
1982
+ return undefined;
1983
+ }
1984
+ }
1985
+ function normalizeExpiresAt(value) {
1986
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
1987
+ return undefined;
1988
+ return value < 1_000_000_000_000 ? value * 1000 : value;
1989
+ }
1811
1990
  function createLanAddressCandidates(options) {
1812
1991
  const viteNetworkUrlByHost = new Map(options.viteNetworkUrls.map((url) => [readUrlHost(url), url]).filter((entry) => Boolean(entry[0])));
1813
1992
  const candidates = createLanInterfaceCandidates(options.interfaces)
@@ -1826,14 +2005,18 @@ function createLanAddressCandidates(options) {
1826
2005
  const runtimeUrl = rewriteLoopbackUrlHost(options.runtimeUrl, address);
1827
2006
  if (runtimeUrl !== undefined) {
1828
2007
  candidate.mobileAppQrPayload = createMobileAppQrPayload(appUrl, {
2008
+ bareProtocol: options.rawProtocol,
1829
2009
  miniProgramId: options.miniProgramId,
2010
+ localDevLaunchToken: options.localDevLaunchToken,
1830
2011
  runtimeUrl,
1831
2012
  sdkVersion: options.sdkVersion,
1832
2013
  });
1833
2014
  }
1834
2015
  else {
1835
2016
  candidate.mobileAppQrPayload = createMobileAppQrPayload(appUrl, {
2017
+ bareProtocol: options.rawProtocol,
1836
2018
  miniProgramId: options.miniProgramId,
2019
+ localDevLaunchToken: options.localDevLaunchToken,
1837
2020
  sdkVersion: options.sdkVersion,
1838
2021
  });
1839
2022
  }
@@ -1923,4 +2106,5 @@ function isPrivateIPv4(address) {
1923
2106
  return parts[0] === 10 || (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) || (parts[0] === 192 && parts[1] === 168);
1924
2107
  }
1925
2108
 
2109
+ exports.normalizeExpiresAt = normalizeExpiresAt;
1926
2110
  exports.runDevCommand = runDevCommand;
@@ -4,7 +4,7 @@ var fs$1 = require('node:fs');
4
4
  var fs = require('node:fs/promises');
5
5
  var os = require('node:os');
6
6
  var path = require('node:path');
7
- var index = require('./index-tCURDEx8.cjs');
7
+ var index = require('./index-BnBeTd0B.cjs');
8
8
  require('node:module');
9
9
  require('path');
10
10
  require('os');