@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
@@ -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 向 Runtime 报告 CSP 违规的内部方法名。 */
@@ -59,29 +59,97 @@ function isRecord$3(value) {
59
59
  return typeof value === 'object' && value !== null && !Array.isArray(value);
60
60
  }
61
61
 
62
+ const OFFICIAL_MINI_PROGRAM_NETWORK_ROOT_DOMAINS = [
63
+ 'xiaoheihe.cn',
64
+ 'max-c.com',
65
+ 'debugmode.cn',
66
+ 'maxjia.com',
67
+ ];
68
+ /** Runtime / Host 在 `network.request` 分发前拒绝的小程序服务端私有路径前缀。 */
69
+ const MINI_PROGRAM_NETWORK_PROTECTED_PATHS = [
70
+ '/user_miniprogram/runtime/auth',
71
+ '/user_miniprogram/openapi/v1',
72
+ ];
73
+ const NETWORK_REQUEST_PATHNAME_MAX_DECODE_ROUNDS = 4;
74
+ /** 使用 URL 解析后的 hostname 判断黑盒官方根域及其子域。 */
75
+ function isOfficialMiniProgramNetworkUrl(value) {
76
+ try {
77
+ const url = new URL(value);
78
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
79
+ return false;
80
+ }
81
+ const hostname = url.hostname.toLowerCase().replace(/\.$/, '');
82
+ return OFFICIAL_MINI_PROGRAM_NETWORK_ROOT_DOMAINS.some(rootDomain => hostname === rootDomain || hostname.endsWith(`.${rootDomain}`));
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
62
88
  /**
63
- * 登录授权能力方法名。
64
- *
65
- * @remarks
66
- * 供 SDK 与父容器 runtime 共享同一 bridge method 标识。
89
+ * 规范化并判断 pathname 是否命中 Runtime Auth / OpenAPI 保护前缀。
90
+ * 解码失败或仍可继续解码时视为不安全路径。
67
91
  */
68
- const AUTH_LOGIN_METHOD = 'auth.login';
92
+ function isProtectedMiniProgramNetworkPath(pathname) {
93
+ const normalized = normalizeMiniProgramNetworkPathname(pathname);
94
+ if (normalized === null) {
95
+ return true;
96
+ }
97
+ return MINI_PROGRAM_NETWORK_PROTECTED_PATHS.some(prefix => normalized === prefix || normalized.startsWith(`${prefix}/`));
98
+ }
99
+ function normalizeMiniProgramNetworkPathname(pathname) {
100
+ let decoded = pathname;
101
+ for (let round = 0; round < NETWORK_REQUEST_PATHNAME_MAX_DECODE_ROUNDS; round += 1) {
102
+ let next;
103
+ try {
104
+ next = decodeURIComponent(decoded);
105
+ }
106
+ catch {
107
+ return null;
108
+ }
109
+ if (next === decoded) {
110
+ return normalizeDecodedNetworkPathname(decoded);
111
+ }
112
+ decoded = next;
113
+ }
114
+ try {
115
+ if (decodeURIComponent(decoded) !== decoded) {
116
+ return null;
117
+ }
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ return normalizeDecodedNetworkPathname(decoded);
123
+ }
124
+ function normalizeDecodedNetworkPathname(decoded) {
125
+ const segments = [];
126
+ for (const segment of decoded.replace(/\\/g, '/').split('/')) {
127
+ if (!segment || segment === '.') {
128
+ continue;
129
+ }
130
+ if (segment === '..') {
131
+ segments.pop();
132
+ continue;
133
+ }
134
+ segments.push(segment);
135
+ }
136
+ return `/${segments.join('/')}`;
137
+ }
138
+
69
139
  /**
70
- * 用户信息能力方法名。
140
+ * 登录授权能力方法名。
71
141
  *
72
142
  * @remarks
73
143
  * 供 SDK 与父容器 runtime 共享同一 bridge method 标识。
74
144
  */
145
+ const AUTH_LOGIN_METHOD = 'auth.login';
146
+ /** 读取当前登录态、授权状态和已获准披露资料的 bridge method 标识。 */
75
147
  const USER_GET_INFO_METHOD = 'user.getInfo';
76
- /** 当前用户展示详情能力方法名。 */
77
- const USER_GET_CURRENT_USER_DETAIL_METHOD = 'user.getCurrentUserDetail';
78
- /** 当前用户敏感资料详情能力方法名。 */
79
- const USER_GET_CURRENT_USER_PROFILE_METHOD = 'user.getCurrentUserProfile';
80
- /** 当前用户平台账号概览能力方法名。 */
81
- const USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD = 'user.getPlatformAccountOverview';
82
- /** 当前用户指定平台账号详情能力方法名。 */
83
- const USER_GET_PLATFORM_ACCOUNT_INFO_METHOD = 'user.getPlatformAccountInfo';
84
- /** 当前用户 Steam 游戏库能力方法名。 */
148
+ /** 读取当前用户小程序隔离身份的 bridge method 标识。 */
149
+ const USER_GET_LOCAL_IDENTITY_METHOD = 'user.getLocalIdentity';
150
+ /** 撤销当前用户授予本小程序授权的 bridge method 标识。 */
151
+ const USER_REVOKE_AUTHORIZATION_METHOD = 'user.revokeAuthorization';
152
+ /** 读取当前已登录用户 Steam 游戏库的 bridge method 标识。 */
85
153
  const USER_GET_STEAM_GAME_LIST_METHOD = 'user.getSteamGameList';
86
154
  /**
87
155
  * 展示分享面板能力方法名。
@@ -180,38 +248,24 @@ const MINI_PROGRAM_PROTOCOL_CAPABILITIES = [
180
248
  risk: 'medium',
181
249
  },
182
250
  {
183
- method: USER_GET_CURRENT_USER_DETAIL_METHOD,
184
- module: 'user',
185
- capability: USER_GET_CURRENT_USER_DETAIL_METHOD,
186
- permission: 'user.currentUserDetail',
187
- risk: 'high',
188
- },
189
- {
190
- method: USER_GET_CURRENT_USER_PROFILE_METHOD,
191
- module: 'user',
192
- capability: USER_GET_CURRENT_USER_PROFILE_METHOD,
193
- permission: 'user.currentUserProfile',
194
- risk: 'high',
195
- },
196
- {
197
- method: USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD,
251
+ method: USER_GET_LOCAL_IDENTITY_METHOD,
198
252
  module: 'user',
199
- capability: USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD,
200
- permission: 'user.platformAccount.overview',
253
+ capability: USER_GET_LOCAL_IDENTITY_METHOD,
254
+ permission: 'user.getLocalIdentity',
201
255
  risk: 'medium',
202
256
  },
203
257
  {
204
- method: USER_GET_PLATFORM_ACCOUNT_INFO_METHOD,
258
+ method: USER_REVOKE_AUTHORIZATION_METHOD,
205
259
  module: 'user',
206
- capability: USER_GET_PLATFORM_ACCOUNT_INFO_METHOD,
207
- permission: 'user.platformAccount.info',
260
+ capability: USER_REVOKE_AUTHORIZATION_METHOD,
261
+ permission: 'user.revokeAuthorization',
208
262
  risk: 'high',
209
263
  },
210
264
  {
211
265
  method: USER_GET_STEAM_GAME_LIST_METHOD,
212
266
  module: 'user',
213
267
  capability: USER_GET_STEAM_GAME_LIST_METHOD,
214
- permission: 'user.steamGameList',
268
+ permission: 'user.platformAccount.steam',
215
269
  risk: 'high',
216
270
  },
217
271
  {
@@ -3587,35 +3641,6 @@ function cloneDefaultPermissionState() {
3587
3641
  };
3588
3642
  }
3589
3643
 
3590
- async function getMiniProgramRuntimeUserInfo(platformAdapter) {
3591
- const userId = await resolveCurrentUserId(platformAdapter);
3592
- if (!userId) {
3593
- return {
3594
- isLogin: false,
3595
- userInfo: null,
3596
- };
3597
- }
3598
- const result = await platformAdapter.user.getUserBasicInfo(userId);
3599
- return {
3600
- isLogin: true,
3601
- userInfo: {
3602
- heybox_id: userId,
3603
- nickname: result?.nickname || '',
3604
- avatar: result?.avatar || '',
3605
- },
3606
- };
3607
- }
3608
- function resolveCurrentUserId(platformAdapter) {
3609
- return platformAdapter.auth.getCurrentUserId()
3610
- .then(userId => (userId ? String(userId) : ''))
3611
- .catch(() => '');
3612
- }
3613
-
3614
- async function loginAndGetMiniProgramRuntimeUserInfo(platformAdapter) {
3615
- await platformAdapter.auth.login();
3616
- return getMiniProgramRuntimeUserInfo(platformAdapter);
3617
- }
3618
-
3619
3644
  const DEFAULT_STORAGE_KEY_MAX_LENGTH = 128;
3620
3645
  const STORAGE_KEY_CHAR_RE = /^[A-Za-z0-9_-]+$/;
3621
3646
  function createMiniProgramRuntimeBridgeError(code, message, data) {
@@ -3929,7 +3954,7 @@ function createLeaderboardReadStore() {
3929
3954
  });
3930
3955
  }
3931
3956
  function isBucketEmpty(bucketKey, bucket) {
3932
- return !hasInflight(bucketKey) && Object.values(bucket.caches).every(cache => cache.size === 0);
3957
+ return !hasInflight(bucketKey) && Object.values(bucket.caches).every((cache) => cache.size === 0);
3933
3958
  }
3934
3959
  function hasInflight(bucketKey) {
3935
3960
  for (const entry of inflight.values()) {
@@ -3983,7 +4008,7 @@ function readLeaderboardPayload(payload, methodName, shape) {
3983
4008
  }
3984
4009
  function assertLeaderboardEntry(value, methodName) {
3985
4010
  assertMiniProgramRuntimeRecord(value, `${methodName} 返回的 entry 必须是对象`);
3986
- const { rank, ranked, userId, score, extra, createdAt, updatedAt } = value;
4011
+ const { rank, ranked, appUserId, score, extra, createdAt, updatedAt } = value;
3987
4012
  if (typeof ranked !== 'boolean') {
3988
4013
  throw createMiniProgramRuntimeBridgeError('INVALID_RESPONSE', `${methodName} 返回的 ranked 必须是布尔值`);
3989
4014
  }
@@ -3991,8 +4016,8 @@ function assertLeaderboardEntry(value, methodName) {
3991
4016
  if (!validRank) {
3992
4017
  throw createMiniProgramRuntimeBridgeError('INVALID_RESPONSE', `${methodName} 返回的 rank/ranked 不合法`);
3993
4018
  }
3994
- if (typeof userId !== 'string' || !userId) {
3995
- throw createMiniProgramRuntimeBridgeError('INVALID_RESPONSE', `${methodName} 返回的 userId 必须是非空字符串`);
4019
+ if (typeof appUserId !== 'string' || !appUserId) {
4020
+ throw createMiniProgramRuntimeBridgeError('INVALID_RESPONSE', `${methodName} 返回的 appUserId 必须是非空字符串`);
3996
4021
  }
3997
4022
  if (typeof score !== 'number' || !Number.isFinite(score)) {
3998
4023
  throw createMiniProgramRuntimeBridgeError('INVALID_RESPONSE', `${methodName} 返回的 score 必须是有限数字`);
@@ -4058,6 +4083,57 @@ function readLeaderboardCursor(value) {
4058
4083
  return value;
4059
4084
  }
4060
4085
 
4086
+ const VALID_LOGIN_SCOPES = new Set(['identity', 'profile']);
4087
+ async function loginMiniProgramRuntime(payload, platformAdapter, identity) {
4088
+ const scopes = readLoginScopes(payload);
4089
+ const miniProgramId = identity?.miniProgramId?.trim();
4090
+ if (!miniProgramId) {
4091
+ throw createMiniProgramRuntimeBridgeError('INVALID_STATE', 'auth.login 缺少可信小程序身份');
4092
+ }
4093
+ const result = await platformAdapter.auth.login({
4094
+ miniProgramId,
4095
+ scopes,
4096
+ trustedUserGesture: Boolean(await platformAdapter.auth.hasTrustedUserGesture?.()),
4097
+ });
4098
+ assertAuthLoginResult(result);
4099
+ return { code: result.code, expiresIn: result.expiresIn, scopes: [...result.scopes] };
4100
+ }
4101
+ function readLoginScopes(payload) {
4102
+ if (payload === undefined) {
4103
+ return ['identity'];
4104
+ }
4105
+ if (!isMiniProgramRuntimeRecord(payload) || Object.keys(payload).some((key) => key !== 'scopes')) {
4106
+ throw createInvalidLoginPayloadError();
4107
+ }
4108
+ if (payload.scopes === undefined) {
4109
+ return ['identity'];
4110
+ }
4111
+ if (!Array.isArray(payload.scopes) ||
4112
+ payload.scopes.some((scope) => typeof scope !== 'string' || !VALID_LOGIN_SCOPES.has(scope)) ||
4113
+ new Set(payload.scopes).size !== payload.scopes.length) {
4114
+ throw createInvalidLoginPayloadError();
4115
+ }
4116
+ return [...payload.scopes];
4117
+ }
4118
+ function assertAuthLoginResult(result) {
4119
+ if (!isMiniProgramRuntimeRecord(result) ||
4120
+ typeof result.code !== 'string' ||
4121
+ !result.code ||
4122
+ typeof result.expiresIn !== 'number' ||
4123
+ !Number.isInteger(result.expiresIn) ||
4124
+ result.expiresIn <= 0 ||
4125
+ !Array.isArray(result.scopes) ||
4126
+ !result.scopes.includes('identity') ||
4127
+ result.scopes.some((scope) => typeof scope !== 'string') ||
4128
+ result.scopes.some((scope) => !VALID_LOGIN_SCOPES.has(scope)) ||
4129
+ new Set(result.scopes).size !== result.scopes.length) {
4130
+ throw createMiniProgramRuntimeBridgeError('INVALID_RESPONSE', 'Host auth.login 返回无效');
4131
+ }
4132
+ }
4133
+ function createInvalidLoginPayloadError() {
4134
+ return createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'auth.login 参数不合法');
4135
+ }
4136
+
4061
4137
  const GAME_DETAIL_GAME_TYPES = ['pc', 'console', 'mobile'];
4062
4138
  const GAME_DETAIL_PAGES = ['game', 'wiki'];
4063
4139
  function toOpenAppPagePlatformOptions(payload) {
@@ -4619,6 +4695,9 @@ function assertNetworkRequestUrl(requestUrl) {
4619
4695
  if (url.username || url.password) {
4620
4696
  throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'network.request url 不允许包含用户信息');
4621
4697
  }
4698
+ if (isProtectedMiniProgramNetworkPath(url.pathname)) {
4699
+ throw createMiniProgramRuntimeBridgeError('PERMISSION_DENIED', 'network.request 不允许访问 Runtime 后端接口');
4700
+ }
4622
4701
  }
4623
4702
  function readNetworkRequestHeaders(value) {
4624
4703
  const headers = readStringRecord(value, 'network.request headers 必须是 Record<string, string>');
@@ -5091,31 +5170,45 @@ function hashString(value) {
5091
5170
  return (hash >>> 0).toString(36);
5092
5171
  }
5093
5172
 
5094
- const SUPPORTED_PLATFORM_ACCOUNT_TYPES = [
5095
- 'steam',
5096
- 'epic',
5097
- 'xbox',
5098
- 'psn',
5099
- 'switch',
5100
- 'pc_hardware',
5101
- 'mobile',
5102
- ];
5103
- const SUPPORTED_PLATFORM_ACCOUNT_TYPE_SET = new Set(SUPPORTED_PLATFORM_ACCOUNT_TYPES);
5104
- function isPlatformAccountType(value) {
5105
- return typeof value === 'string' && SUPPORTED_PLATFORM_ACCOUNT_TYPE_SET.has(value);
5173
+ async function getMiniProgramRuntimeUserInfo(platformAdapter, identity) {
5174
+ const miniProgramId = identity?.miniProgramId?.trim();
5175
+ return miniProgramId
5176
+ ? platformAdapter.user.getUserInfo({ miniProgramId })
5177
+ : platformAdapter.user.getUserInfo();
5106
5178
  }
5107
- function readPlatformAccountType(payload) {
5108
- if (!isMiniProgramRuntimeRecord(payload)) {
5109
- throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'user.getPlatformAccountInfo 参数必须是对象');
5179
+
5180
+ async function getMiniProgramRuntimeLocalIdentity(payload, platformAdapter, identity) {
5181
+ if (payload !== undefined) {
5182
+ throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'user.getLocalIdentity 不接收入参');
5110
5183
  }
5111
- const invalidKeys = Object.keys(payload).filter(key => key !== 'platform');
5112
- if (invalidKeys.length > 0) {
5113
- throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', `user.getPlatformAccountInfo 不支持字段: ${invalidKeys.join(', ')}`);
5184
+ const miniProgramId = identity?.miniProgramId?.trim();
5185
+ if (!miniProgramId) {
5186
+ throw createMiniProgramRuntimeBridgeError('INVALID_STATE', 'user.getLocalIdentity 缺少可信小程序身份');
5187
+ }
5188
+ const result = await platformAdapter.user.getLocalIdentity({
5189
+ miniProgramId,
5190
+ trustedUserGesture: Boolean(await platformAdapter.auth.hasTrustedUserGesture?.()),
5191
+ });
5192
+ if (!isMiniProgramRuntimeRecord(result)
5193
+ || typeof result.appUserId !== 'string'
5194
+ || !result.appUserId.trim()) {
5195
+ throw createMiniProgramRuntimeBridgeError('INVALID_RESPONSE', 'Host user.getLocalIdentity 返回无效');
5196
+ }
5197
+ return { appUserId: result.appUserId };
5198
+ }
5199
+
5200
+ async function revokeMiniProgramRuntimeAuthorization(payload, platformAdapter, identity) {
5201
+ if (payload !== undefined) {
5202
+ throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'user.revokeAuthorization 不接收入参');
5114
5203
  }
5115
- if (!isPlatformAccountType(payload.platform)) {
5116
- throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'user.getPlatformAccountInfo platform 不支持');
5204
+ const miniProgramId = identity?.miniProgramId?.trim();
5205
+ if (!miniProgramId) {
5206
+ throw createMiniProgramRuntimeBridgeError('INVALID_STATE', 'user.revokeAuthorization 缺少可信小程序身份');
5117
5207
  }
5118
- return payload.platform;
5208
+ await platformAdapter.user.revokeAuthorization({
5209
+ miniProgramId,
5210
+ trustedUserGesture: Boolean(await platformAdapter.auth.hasTrustedUserGesture?.()),
5211
+ });
5119
5212
  }
5120
5213
 
5121
5214
  const DEFAULT_LIMIT = 20;
@@ -5136,14 +5229,7 @@ function readSteamGameListRequest(payload) {
5136
5229
  if (!isMiniProgramRuntimeRecord(payload)) {
5137
5230
  throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'user.getSteamGameList 参数必须是对象');
5138
5231
  }
5139
- const invalidKeys = Object.keys(payload).filter(key => ![
5140
- 'steamId',
5141
- 'limit',
5142
- 'offset',
5143
- 'sort',
5144
- 'q',
5145
- 'includeHidden',
5146
- ].includes(key));
5232
+ const invalidKeys = Object.keys(payload).filter((key) => !['steamId', 'limit', 'offset', 'sort', 'q', 'includeHidden'].includes(key));
5147
5233
  if (invalidKeys.length > 0) {
5148
5234
  throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', `user.getSteamGameList 不支持字段: ${invalidKeys.join(', ')}`);
5149
5235
  }
@@ -5153,7 +5239,7 @@ function readSteamGameListRequest(payload) {
5153
5239
  offset: readOffset(payload.offset),
5154
5240
  sort: readSort(payload.sort),
5155
5241
  ...readOptionalQuery(payload.q),
5156
- includeHidden: readBoolean(payload.includeHidden, false),
5242
+ includeHidden: readIncludeHidden(payload.includeHidden),
5157
5243
  };
5158
5244
  }
5159
5245
  async function getMiniProgramRuntimeSteamGameList(payload, platformAdapter, home) {
@@ -5165,7 +5251,7 @@ async function getMiniProgramRuntimeSteamGameList(payload, platformAdapter, home
5165
5251
  data: null,
5166
5252
  };
5167
5253
  }
5168
- const steamInfo = readRecord$1(home.steam_id_info);
5254
+ const steamInfo = readRecord(home.steam_id_info);
5169
5255
  if (Object.keys(steamInfo).length === 0) {
5170
5256
  return {
5171
5257
  isLogin: true,
@@ -5187,8 +5273,7 @@ function requestSteamGameListEnvelope(platformAdapter, request) {
5187
5273
  if (!platformAdapter.user.getCurrentUserSteamGameListEnvelope) {
5188
5274
  throw createMiniProgramRuntimeBridgeError('UNSUPPORTED', '当前平台不支持读取 Steam 游戏库');
5189
5275
  }
5190
- return platformAdapter.user.getCurrentUserSteamGameListEnvelope(request)
5191
- .catch((error) => {
5276
+ return platformAdapter.user.getCurrentUserSteamGameListEnvelope(request).catch((error) => {
5192
5277
  throw createMiniProgramRuntimeBridgeError('REQUEST_FAILED', error instanceof Error ? error.message : 'Steam 游戏库请求失败', error);
5193
5278
  });
5194
5279
  }
@@ -5198,13 +5283,13 @@ function mapSteamGameListData(envelope, request) {
5198
5283
  }
5199
5284
  const data = envelope;
5200
5285
  if (data.status !== 'ok') {
5201
- throw createMiniProgramRuntimeBridgeError('REQUEST_FAILED', readString$1(data.msg) || 'Steam 游戏库请求失败', data);
5286
+ throw createMiniProgramRuntimeBridgeError('REQUEST_FAILED', readString(data.msg) || 'Steam 游戏库请求失败', data);
5202
5287
  }
5203
5288
  if (!isMiniProgramRuntimeRecord(data.result)) {
5204
5289
  throw createMiniProgramRuntimeBridgeError('INVALID_RESPONSE', 'Steam 游戏库 result 必须是对象');
5205
5290
  }
5206
5291
  const result = data.result;
5207
- const gameList = readArray$1(result.game_list)
5292
+ const gameList = readArray(result.game_list)
5208
5293
  .map(mapSteamGameListItem)
5209
5294
  .filter((item) => Boolean(item));
5210
5295
  const total = readOptionalNumber(result.total);
@@ -5225,10 +5310,10 @@ function mapSteamGameListData(envelope, request) {
5225
5310
  return response;
5226
5311
  }
5227
5312
  function mapSteamGameListItem(value) {
5228
- const source = readRecord$1(value);
5313
+ const source = readRecord(value);
5229
5314
  const appid = readOptionalNumber(source.appid);
5230
5315
  const steamAppid = readOptionalNumber(source.steam_appid) ?? appid;
5231
- const name = readString$1(source.name);
5316
+ const name = readString(source.name);
5232
5317
  if (appid === undefined || steamAppid === undefined || !name) {
5233
5318
  return undefined;
5234
5319
  }
@@ -5237,21 +5322,21 @@ function mapSteamGameListItem(value) {
5237
5322
  steamAppid,
5238
5323
  name,
5239
5324
  };
5240
- copyStringField$1(result, 'image', source.image);
5241
- copyStringField$1(result, 'icon', source.appicon ?? source.icon);
5242
- copyStringField$1(result, 'background', source.background);
5243
- copyStringField$1(result, 'verticalImage', source.vertical_head_image);
5325
+ copyStringField(result, 'image', source.image);
5326
+ copyStringField(result, 'icon', source.appicon ?? source.icon);
5327
+ copyStringField(result, 'background', source.background);
5328
+ copyStringField(result, 'verticalImage', source.vertical_head_image);
5244
5329
  copyNumberField(result, 'playtimeForever', source.playtime_forever);
5245
5330
  copyNumberField(result, 'playtime2Weeks', source.playtime_2weeks);
5246
5331
  copyNumberField(result, 'playtimePercent', source.playtime_percent);
5247
5332
  copyNumberField(result, 'achievedCount', source.achieved_count);
5248
5333
  copyNumberField(result, 'achievementCount', source.achivement_count ?? source.achievement_count);
5249
5334
  copyBooleanField(result, 'isCleared', source.is_cleared);
5250
- copyStringField$1(result, 'score', source.score);
5335
+ copyStringField(result, 'score', source.score);
5251
5336
  copyBooleanField(result, 'isFree', source.is_free);
5252
5337
  copyBooleanField(result, 'notOwned', source.not_owned);
5253
5338
  copyBooleanField(result, 'hidden', source.hidden ?? source.is_hidden);
5254
- copyStringField$1(result, 'onlinePlayer', source.online_player);
5339
+ copyStringField(result, 'onlinePlayer', source.online_player);
5255
5340
  copyStringArray(result, 'tags', source.external_tags ?? source.tags);
5256
5341
  copyStringArray(result, 'genres', source.genres);
5257
5342
  const price = mapSteamGamePrice(source.price);
@@ -5261,22 +5346,27 @@ function mapSteamGameListItem(value) {
5261
5346
  return result;
5262
5347
  }
5263
5348
  function mapSteamGamePrice(value) {
5264
- const source = readRecord$1(value);
5349
+ const source = readRecord(value);
5265
5350
  const result = {};
5266
- copyStringField$1(result, 'current', source.current);
5267
- copyStringField$1(result, 'initial', source.initial);
5351
+ copyStringField(result, 'current', source.current);
5352
+ copyStringField(result, 'initial', source.initial);
5268
5353
  copyNumberField(result, 'discount', source.discount);
5269
5354
  copyNumberField(result, 'lowestPrice', source.lowest_price);
5270
- copyStringField$1(result, 'deadlineDate', source.deadline_date);
5355
+ copyStringField(result, 'deadlineDate', source.deadline_date);
5271
5356
  copyNumberField(result, 'deadlineTimestamp', source.deadline_timestamp);
5272
5357
  return Object.keys(result).length > 0 ? result : undefined;
5273
5358
  }
5274
5359
  function readOptionalSteamId(value) {
5275
- const steamId = readString$1(value);
5276
- return steamId ? { steamId } : {};
5360
+ if (value === undefined) {
5361
+ return {};
5362
+ }
5363
+ if (typeof value !== 'string' || !value.trim()) {
5364
+ throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'user.getSteamGameList steamId 必须是非空字符串');
5365
+ }
5366
+ return { steamId: value.trim() };
5277
5367
  }
5278
5368
  function readOptionalQuery(value) {
5279
- const q = readString$1(value);
5369
+ const q = readString(value);
5280
5370
  return q ? { q: q.slice(0, MAX_QUERY_LENGTH) } : {};
5281
5371
  }
5282
5372
  function readLimit(value) {
@@ -5314,8 +5404,8 @@ function readInteger(value) {
5314
5404
  }
5315
5405
  return undefined;
5316
5406
  }
5317
- function copyStringField$1(target, key, value) {
5318
- const stringValue = readString$1(value);
5407
+ function copyStringField(target, key, value) {
5408
+ const stringValue = readString(value);
5319
5409
  if (stringValue) {
5320
5410
  Object.assign(target, { [key]: stringValue });
5321
5411
  }
@@ -5333,20 +5423,18 @@ function copyBooleanField(target, key, value) {
5333
5423
  }
5334
5424
  }
5335
5425
  function copyStringArray(target, key, value) {
5336
- const items = readArray$1(value)
5337
- .map(readString$1)
5338
- .filter(Boolean);
5426
+ const items = readArray(value).map(readString).filter(Boolean);
5339
5427
  if (items.length > 0) {
5340
5428
  Object.assign(target, { [key]: items });
5341
5429
  }
5342
5430
  }
5343
- function readRecord$1(value) {
5431
+ function readRecord(value) {
5344
5432
  return isMiniProgramRuntimeRecord(value) ? value : {};
5345
5433
  }
5346
- function readArray$1(value) {
5434
+ function readArray(value) {
5347
5435
  return Array.isArray(value) ? value : [];
5348
5436
  }
5349
- function readString$1(value) {
5437
+ function readString(value) {
5350
5438
  if (typeof value === 'string') {
5351
5439
  return value.trim() ? value.trim() : '';
5352
5440
  }
@@ -5365,376 +5453,16 @@ function readOptionalNumber(value) {
5365
5453
  }
5366
5454
  return undefined;
5367
5455
  }
5368
- function readBoolean(value, fallback) {
5369
- return readOptionalBoolean(value) ?? fallback;
5370
- }
5371
- function readOptionalBoolean(value) {
5372
- if (value === undefined || value === null || value === '') {
5373
- return undefined;
5374
- }
5375
- if (value === true || value === 1 || value === '1') {
5376
- return true;
5377
- }
5378
- if (value === false || value === 0 || value === '0') {
5456
+ function readIncludeHidden(value) {
5457
+ if (value === undefined) {
5379
5458
  return false;
5380
5459
  }
5381
- return undefined;
5382
- }
5383
-
5384
- const PLATFORM_SOURCE_KEY_BY_TYPE = {
5385
- steam: 'steam_id_info',
5386
- epic: 'epic_account_info',
5387
- xbox: 'xbox_account_info',
5388
- psn: 'psn_account_info',
5389
- switch: 'switch_account_info',
5390
- pc_hardware: 'hardware_info',
5391
- mobile: 'mobile_account_info',
5392
- };
5393
- const PLATFORM_HIDDEN_KEY_BY_TYPE = {
5394
- steam: ['steam_is_hidden', 'is_steam_hidden'],
5395
- epic: ['epic_is_hidden', 'is_epic_hidden'],
5396
- xbox: ['xbox_is_hidden', 'is_xbox_hidden'],
5397
- psn: ['psn_is_hidden', 'is_psn_hidden'],
5398
- switch: ['switch_is_hidden', 'is_switch_hidden'],
5399
- pc_hardware: ['hardware_is_hidden', 'pc_hardware_is_hidden', 'is_hardware_hidden'],
5400
- mobile: ['mobile_is_hidden', 'is_mobile_hidden'],
5401
- };
5402
- function mapCurrentUserDetail(home) {
5403
- const accountDetail = readRecord(home.account_detail);
5404
- const result = {
5405
- heybox_id: readString(accountDetail.userid) || '',
5406
- nickname: readString(accountDetail.username) || '',
5407
- avatar: readString(accountDetail.avartar) || readString(accountDetail.avatar) || '',
5408
- };
5409
- copyStringFields(result, accountDetail, [
5410
- 'sex',
5411
- 'signature',
5412
- 'ip_location',
5413
- 'friends_count',
5414
- ]);
5415
- copyRecord(result, 'level_info', mapCurrentUserLevelInfo(accountDetail.level_info));
5416
- copyArray(result, 'medals', mapCurrentUserMedals(accountDetail.bbs_medal ?? accountDetail.medal ?? accountDetail.medals));
5417
- copyRecord(result, 'avatar_decoration', mapCurrentUserAvatarDecoration(accountDetail.avatar_decoration));
5418
- copyRecord(result, 'avatar_config', mapCurrentUserAvatarConfig(accountDetail.avatar_config));
5419
- copyRecord(result, 'bbs_info', mapCurrentUserBbsInfo(accountDetail.bbs_info));
5420
- return result;
5421
- }
5422
- function mapCurrentUserProfile(home) {
5423
- const profile = readRecord(home.profile);
5424
- const result = {
5425
- heybox_id: readString(profile.heybox_id) || readString(profile.userid) || '',
5426
- };
5427
- copyStringFields(result, profile, [
5428
- 'nickname',
5429
- 'avatar',
5430
- 'gender',
5431
- 'birthday',
5432
- 'email',
5433
- 'education',
5434
- 'career',
5435
- 'signature',
5436
- ]);
5437
- return result;
5438
- }
5439
- function mapPlatformAccountOverview(home) {
5440
- return {
5441
- platforms: SUPPORTED_PLATFORM_ACCOUNT_TYPES.map(platform => ({
5442
- platform,
5443
- is_bound: isPlatformBound(home, platform),
5444
- is_hidden: readPlatformHidden(home, platform),
5445
- })),
5446
- display_order: readPlatformDisplayOrder(home.platform_list),
5447
- };
5448
- }
5449
- function mapPlatformAccountResult(home, platform) {
5450
- const isLogin = true;
5451
- const isHidden = readPlatformHidden(home, platform);
5452
- const accountInfo = mapPlatformAccountInfo(home, platform);
5453
- const isBound = isPlatformInfoBound(accountInfo);
5454
- return {
5455
- isLogin,
5456
- platform,
5457
- is_bound: isBound,
5458
- is_hidden: isHidden,
5459
- account_info: isBound ? accountInfo : null,
5460
- };
5461
- }
5462
- function createUnauthenticatedPlatformAccountResult(platform) {
5463
- return {
5464
- isLogin: false,
5465
- platform,
5466
- is_bound: false,
5467
- is_hidden: false,
5468
- account_info: null,
5469
- };
5470
- }
5471
- function mapPlatformAccountInfo(home, platform) {
5472
- const source = readRecord(home[PLATFORM_SOURCE_KEY_BY_TYPE[platform]]);
5473
- switch (platform) {
5474
- case 'steam':
5475
- return mapSteamPlatformAccount(source);
5476
- case 'epic':
5477
- return mapEpicPlatformAccount(source);
5478
- case 'xbox':
5479
- return mapXboxPlatformAccount(source);
5480
- case 'psn':
5481
- return mapPsnPlatformAccount(source);
5482
- case 'switch':
5483
- return mapSwitchPlatformAccount(source);
5484
- case 'pc_hardware':
5485
- return mapPcHardwareAccount(source);
5486
- case 'mobile':
5487
- return mapMobilePlatformAccount(source);
5488
- default:
5489
- return {};
5490
- }
5491
- }
5492
- function mapSteamPlatformAccount(source) {
5493
- const result = mapBasePlatformAccountInfo(source);
5494
- const steamid = readString(source.steamid);
5495
- if (steamid) {
5496
- result.steamid = steamid;
5497
- }
5498
- copyStringFields(result, source, [
5499
- 'short_id',
5500
- 'level',
5501
- 'city',
5502
- 'province',
5503
- 'country',
5504
- 'country_code',
5505
- 'country_flag',
5506
- 'avatar_frame',
5507
- 'head_image',
5508
- 'head_video',
5509
- 'update_time',
5510
- 'account_year',
5511
- 'individuality_signature',
5512
- 'level_icon',
5513
- 'total_badge_count',
5514
- 'total_dlcs_count',
5515
- 'total_game_count',
5516
- 'total_game_price',
5517
- 'total_player_time',
5518
- ]);
5519
- copyOptionalBoolean(result, 'friend_code_show', source.friend_code_show);
5520
- copyOptionalBoolean(result, 'inventory_open', source.inventory_open);
5521
- copyOptionalBoolean(result, 'personal_information_open', source.personal_infomation_open ?? source.personal_information_open);
5522
- copyOptionalBoolean(result, 'has_history', source.has_history);
5523
- if (result.friend_code_show === true) {
5524
- copyStringField(result, 'friend_code', source.friend_code);
5525
- }
5526
- return result;
5527
- }
5528
- function mapEpicPlatformAccount(source) {
5529
- const result = mapBasePlatformAccountInfo(source);
5530
- copyStringFields(result, source, ['xuid', 'head_color', 'desc']);
5531
- return result;
5532
- }
5533
- function mapXboxPlatformAccount(source) {
5534
- const result = mapBasePlatformAccountInfo(source);
5535
- copyStringFields(result, source, ['xuid', 'desc']);
5536
- return result;
5537
- }
5538
- function mapPsnPlatformAccount(source) {
5539
- const result = mapBasePlatformAccountInfo(source);
5540
- copyStringField(result, 'background_url', source.bg_img ?? source.background_url);
5541
- copyStringFields(result, source, ['name', 'level', 'description']);
5542
- copyOptionalBoolean(result, 'is_open', source.is_open);
5543
- const pairStats = mapPsnPairStats(source);
5544
- if (pairStats.length > 0) {
5545
- result.stats = [...readArray(result.stats), ...pairStats];
5546
- }
5547
- return result;
5548
- }
5549
- function mapSwitchPlatformAccount(source) {
5550
- const result = mapBasePlatformAccountInfo(source);
5551
- copyStringFields(result, source, [
5552
- 'heybox_id',
5553
- 'friend_code',
5554
- 'friends_total',
5555
- 'games_total',
5556
- 'server_name',
5557
- 'type',
5558
- 'desc',
5559
- ]);
5560
- return result;
5561
- }
5562
- function mapPcHardwareAccount(source) {
5563
- const result = {};
5564
- copyStringFields(result, source, ['cpu', 'gpu', 'board', 'perf_level']);
5565
- return result;
5566
- }
5567
- function mapMobilePlatformAccount(source) {
5568
- const currentDevice = mapMobileCurrentDevice(readRecord(source.current_device));
5569
- return Object.keys(currentDevice).length > 0
5570
- ? {
5571
- current_device: currentDevice,
5572
- }
5573
- : {};
5574
- }
5575
- function mapBasePlatformAccountInfo(source) {
5576
- const result = {};
5577
- copyStringFields(result, source, ['nickname', 'avatar']);
5578
- copyStringField(result, 'background_url', source.background_url ?? source.bg_img);
5579
- const stats = mapPlatformStats(source.stats ?? source.stat ?? source.cards ?? source.card_list);
5580
- if (stats.length > 0) {
5581
- result.stats = stats;
5582
- }
5583
- return result;
5584
- }
5585
- function mapMobileCurrentDevice(source) {
5586
- const result = {};
5587
- copyStringFields(result, source, ['id', 'name', 'desc', 'model', 'img_url', 'update_time']);
5588
- const stats = mapPlatformStats(source.stats ?? source.stat ?? source.cards ?? source.card_list);
5589
- if (stats.length > 0) {
5590
- result.stats = stats;
5591
- }
5592
- return result;
5593
- }
5594
- function mapCurrentUserLevelInfo(value) {
5595
- const source = readRecord(value);
5596
- const result = {};
5597
- copyStringFields(result, source, ['level', 'level_name', 'exp', 'next_exp', 'icon']);
5598
- return result;
5599
- }
5600
- function mapCurrentUserMedals(value) {
5601
- return readArray(value)
5602
- .map(item => {
5603
- const source = readRecord(item);
5604
- const result = {};
5605
- copyStringFields(result, source, ['id', 'name', 'desc', 'icon', 'image']);
5606
- copyOptionalBoolean(result, 'is_wearing', source.is_wearing);
5607
- return result;
5608
- })
5609
- .filter(item => Object.keys(item).length > 0);
5610
- }
5611
- function mapCurrentUserAvatarDecoration(value) {
5612
- const source = readRecord(value);
5613
- const result = {};
5614
- copyStringFields(result, source, ['id', 'name', 'image', 'frame_url']);
5615
- copyOptionalBoolean(result, 'is_using', source.is_using);
5616
- return result;
5617
- }
5618
- function mapCurrentUserAvatarConfig(value) {
5619
- const source = readRecord(value);
5620
- const result = {};
5621
- copyStringFields(result, source, ['avatar', 'frame_url', 'pendant_url']);
5622
- return result;
5623
- }
5624
- function mapCurrentUserBbsInfo(value) {
5625
- const source = readRecord(value);
5626
- const result = {};
5627
- copyStringFields(result, source, ['uid', 'nickname', 'avatar', 'level']);
5628
- return result;
5629
- }
5630
- function mapPlatformStats(value) {
5631
- return readArray(value)
5632
- .map(item => {
5633
- const source = readRecord(item);
5634
- const result = {};
5635
- copyStringField(result, 'key', source.key ?? source.k);
5636
- copyStringField(result, 'name', source.name);
5637
- copyStringField(result, 'desc', source.desc ?? source.description);
5638
- copyStringField(result, 'value', source.value ?? source.timestamp);
5639
- copyStringField(result, 'num', source.num);
5640
- copyStringField(result, 'icon', source.icon);
5641
- copyStringField(result, 'img', source.img ?? source.img_url ?? source.right_img);
5642
- copyStringField(result, 'color', source.color);
5643
- return result;
5644
- })
5645
- .filter(item => Object.keys(item).length > 0);
5646
- }
5647
- function mapPsnPairStats(source) {
5648
- const stats = [];
5649
- for (let index = 1; index <= 4; index += 1) {
5650
- const name = readString(source[`key${index}`]);
5651
- const value = readString(source[`value${index}`]);
5652
- if (name || value) {
5653
- stats.push({
5654
- ...(name ? { name } : {}),
5655
- ...(value ? { value } : {}),
5656
- });
5657
- }
5658
- }
5659
- return stats;
5660
- }
5661
- function isPlatformBound(home, platform) {
5662
- return isPlatformInfoBound(mapPlatformAccountInfo(home, platform));
5663
- }
5664
- function isPlatformInfoBound(accountInfo) {
5665
- return Object.keys(accountInfo).length > 0;
5666
- }
5667
- function readPlatformHidden(home, platform) {
5668
- for (const key of PLATFORM_HIDDEN_KEY_BY_TYPE[platform]) {
5669
- const value = readOptionalRuntimeBoolean(home[key]);
5670
- if (value !== undefined) {
5671
- return value;
5672
- }
5673
- }
5674
- const hidden = readRecord(home.hidden);
5675
- const hiddenAccounts = readRecord(home.hidden_accounts);
5676
- return readOptionalRuntimeBoolean(hidden[platform]) ?? readOptionalRuntimeBoolean(hiddenAccounts[platform]) ?? false;
5677
- }
5678
- function readPlatformDisplayOrder(value) {
5679
- return readArray(value)
5680
- .map(item => {
5681
- if (isPlatformAccountType(item)) {
5682
- return item;
5683
- }
5684
- if (!isMiniProgramRuntimeRecord(item)) {
5685
- return undefined;
5686
- }
5687
- return readPlatformFromRecord(item);
5688
- })
5689
- .filter((platform) => Boolean(platform));
5690
- }
5691
- function readPlatformFromRecord(value) {
5692
- const platform = value.platform ?? value.type ?? value.key ?? value.name;
5693
- return isPlatformAccountType(platform) ? platform : undefined;
5694
- }
5695
- function readRecord(value) {
5696
- return isMiniProgramRuntimeRecord(value) ? value : {};
5697
- }
5698
- function readArray(value) {
5699
- return Array.isArray(value) ? value : [];
5700
- }
5701
- function copyRecord(target, key, value) {
5702
- if (Object.keys(value).length > 0) {
5703
- target[key] = value;
5704
- }
5705
- }
5706
- function copyArray(target, key, value) {
5707
- if (value.length > 0) {
5708
- target[key] = value;
5709
- }
5710
- }
5711
- function copyStringFields(target, source, keys) {
5712
- for (const key of keys) {
5713
- copyStringField(target, key, source[key]);
5714
- }
5715
- }
5716
- function copyStringField(target, key, value) {
5717
- const stringValue = readString(value);
5718
- if (stringValue) {
5719
- target[key] = stringValue;
5720
- }
5721
- }
5722
- function copyOptionalBoolean(target, key, value) {
5723
- const booleanValue = readOptionalRuntimeBoolean(value);
5724
- if (booleanValue !== undefined) {
5725
- target[key] = booleanValue;
5726
- }
5727
- }
5728
- function readString(value) {
5729
- if (typeof value === 'string') {
5730
- return value.trim() ? value : '';
5731
- }
5732
- if (typeof value === 'number' || typeof value === 'boolean') {
5733
- return String(value);
5460
+ if (typeof value !== 'boolean') {
5461
+ throw createMiniProgramRuntimeBridgeError('INVALID_PARAMS', 'user.getSteamGameList includeHidden 必须是 boolean');
5734
5462
  }
5735
- return '';
5463
+ return value;
5736
5464
  }
5737
- function readOptionalRuntimeBoolean(value) {
5465
+ function readOptionalBoolean(value) {
5738
5466
  if (value === undefined || value === null || value === '') {
5739
5467
  return undefined;
5740
5468
  }
@@ -5765,27 +5493,14 @@ function isNavigationBarForegroundStyle(value) {
5765
5493
  return value === 'light' || value === 'dark';
5766
5494
  }
5767
5495
 
5768
- const OFFICIAL_MINI_PROGRAM_NETWORK_ROOT_DOMAINS = [
5769
- 'xiaoheihe.cn',
5770
- 'max-c.com',
5771
- 'debugmode.cn',
5772
- 'maxjia.com',
5773
- ];
5774
- /** 使用 URL 解析后的 hostname 判断黑盒官方根域及其子域。 */
5775
- function isOfficialMiniProgramNetworkUrl(value) {
5776
- try {
5777
- const url = new URL(value);
5778
- if (url.protocol !== 'http:' && url.protocol !== 'https:') {
5779
- return false;
5780
- }
5781
- const hostname = url.hostname.toLowerCase().replace(/\.$/, '');
5782
- return OFFICIAL_MINI_PROGRAM_NETWORK_ROOT_DOMAINS.some(rootDomain => hostname === rootDomain || hostname.endsWith(`.${rootDomain}`));
5783
- }
5784
- catch {
5785
- return false;
5786
- }
5496
+ /**
5497
+ * 当前小程序是否开通了 `network.request`。
5498
+ * 公开详情关网时只下发合法空 `entries`,没有 enabled 条目视为未开通。
5499
+ */
5500
+ function hasMiniProgramNetworkRequestPermission(snapshot) {
5501
+ const parsed = parseMiniProgramRuntimePermissions(snapshot);
5502
+ return parsed.valid && parsed.permissions['network.request']?.status === 'enabled';
5787
5503
  }
5788
-
5789
5504
  /** 创建只依赖启动快照的纯权限 evaluator,供正式 Runtime 与 Mock Host 共用。 */
5790
5505
  function createMiniProgramRuntimePermissionEvaluator(snapshot) {
5791
5506
  const parsed = parseMiniProgramRuntimePermissions(snapshot);
@@ -5823,13 +5538,11 @@ const MOCK_DEFAULT_LEADERBOARD_KEY = 'default';
5823
5538
  const MOCK_LEADERBOARD_CACHE_MAX_ENTRIES = 500;
5824
5539
  const MOCK_LEADERBOARD_CURRENT_ENTRY_RANK_COUNT_LIMIT = 5000;
5825
5540
  const MINI_PROGRAM_MOCK_RUNTIME_METHODS = MINI_PROGRAM_PROTOCOL_CAPABILITIES.map((capability) => capability.method);
5826
- const MINI_PROGRAM_MOCK_RUNTIME_METHOD_HANDLERS = {
5827
- [AUTH_LOGIN_METHOD]: (runtime) => runtime.login(),
5541
+ const MINI_PROGRAM_MOCK_RUNTIME_METHOD_HANDLER_MAP = {
5542
+ [AUTH_LOGIN_METHOD]: (runtime, payload) => runtime.login(payload),
5828
5543
  [USER_GET_INFO_METHOD]: (runtime) => runtime.getUserInfo(),
5829
- [USER_GET_CURRENT_USER_DETAIL_METHOD]: (runtime) => runtime.getCurrentUserDetail(),
5830
- [USER_GET_CURRENT_USER_PROFILE_METHOD]: (runtime) => runtime.getCurrentUserProfile(),
5831
- [USER_GET_PLATFORM_ACCOUNT_OVERVIEW_METHOD]: (runtime) => runtime.getPlatformAccountOverview(),
5832
- [USER_GET_PLATFORM_ACCOUNT_INFO_METHOD]: (runtime, payload) => runtime.getPlatformAccountInfo(payload),
5544
+ [USER_GET_LOCAL_IDENTITY_METHOD]: (runtime, payload) => runtime.getLocalIdentity(payload),
5545
+ [USER_REVOKE_AUTHORIZATION_METHOD]: (runtime, payload) => runtime.revokeAuthorization(payload),
5833
5546
  [USER_GET_STEAM_GAME_LIST_METHOD]: (runtime, payload) => runtime.getSteamGameList(payload),
5834
5547
  [SHARE_SHOW_SHARE_MENU_METHOD]: (runtime, payload) => runtime.showShareMenu(payload),
5835
5548
  [SHARE_SCREENSHOT_METHOD]: (runtime, payload) => runtime.shareScreenshot(payload),
@@ -5852,6 +5565,7 @@ const MINI_PROGRAM_MOCK_RUNTIME_METHOD_HANDLERS = {
5852
5565
  [NAVIGATION_RELOAD_METHOD]: (runtime, payload) => runtime.reload(payload),
5853
5566
  [NAVIGATION_OPEN_APP_PAGE_METHOD]: (runtime, payload) => runtime.openAppPage(payload),
5854
5567
  };
5568
+ const MINI_PROGRAM_MOCK_RUNTIME_METHOD_HANDLERS = Object.fromEntries(MINI_PROGRAM_MOCK_RUNTIME_METHODS.map((method) => [method, MINI_PROGRAM_MOCK_RUNTIME_METHOD_HANDLER_MAP[method]]));
5855
5569
  function createBrowserMockRuntimePlatformAdapter(options) {
5856
5570
  const leaderboards = createMockLeaderboardBoards(options.leaderboards);
5857
5571
  return {
@@ -5870,16 +5584,56 @@ function createBrowserMockRuntimePlatformAdapter(options) {
5870
5584
  },
5871
5585
  auth: {
5872
5586
  async getCurrentUserId() {
5873
- return options.getCurrentUser()?.heybox_id;
5587
+ const user = options.getCurrentUser();
5588
+ return user?.app_user_id;
5874
5589
  },
5875
- async login() {
5876
- await options.login();
5590
+ hasTrustedUserGesture: options.hasTrustedUserGesture,
5591
+ async login(request) {
5592
+ if (!options.getCurrentUser()) {
5593
+ if (!request.trustedUserGesture) {
5594
+ throw createMockBridgeError('USER_GESTURE_REQUIRED', 'Mock 登录需要可信用户手势');
5595
+ }
5596
+ await options.login();
5597
+ }
5598
+ return {
5599
+ code: options.authorizationCode || `mock-code-${request.miniProgramId || 'local'}`,
5600
+ expiresIn: 300,
5601
+ scopes: Array.from(new Set(['identity', ...request.scopes])),
5602
+ };
5877
5603
  },
5878
5604
  },
5879
5605
  user: {
5606
+ async getUserInfo() {
5607
+ const currentUser = options.getCurrentUser();
5608
+ const appUserId = currentUser?.app_user_id;
5609
+ return appUserId
5610
+ ? {
5611
+ isHeyboxAppLoggedIn: true,
5612
+ authorization: { identity: 'granted', profile: 'not_requested' },
5613
+ userInfo: {
5614
+ app_user_id: appUserId,
5615
+ profile: { nickname: currentUser?.nickname || '', avatar: currentUser?.avatar || '' },
5616
+ },
5617
+ }
5618
+ : { isHeyboxAppLoggedIn: false, authorization: null, userInfo: null };
5619
+ },
5620
+ async getLocalIdentity() {
5621
+ const appUserId = options.getCurrentUser()?.app_user_id;
5622
+ if (!appUserId) {
5623
+ throw createMockBridgeError('NOT_LOGGED_IN', '当前 Mock 用户未登录');
5624
+ }
5625
+ return { appUserId };
5626
+ },
5627
+ async revokeAuthorization(request) {
5628
+ if (!request.trustedUserGesture) {
5629
+ throw createMockBridgeError('USER_GESTURE_REQUIRED', 'Mock 撤销授权需要可信用户手势');
5630
+ }
5631
+ await options.revokeAuthorization?.();
5632
+ },
5880
5633
  async getUserBasicInfo(userId) {
5881
5634
  const currentUser = options.getCurrentUser();
5882
- if (!currentUser || currentUser.heybox_id !== userId) {
5635
+ const appUserId = currentUser?.app_user_id || currentUser?.heybox_id;
5636
+ if (!currentUser || appUserId !== userId) {
5883
5637
  return undefined;
5884
5638
  }
5885
5639
  return {
@@ -5887,9 +5641,6 @@ function createBrowserMockRuntimePlatformAdapter(options) {
5887
5641
  avatar: currentUser.avatar,
5888
5642
  };
5889
5643
  },
5890
- async getCurrentUserSteamGameListEnvelope(request) {
5891
- return createMockSteamGameListEnvelope(request.limit, request.offset);
5892
- },
5893
5644
  },
5894
5645
  share: {
5895
5646
  async showShareMenu(params) {
@@ -6013,7 +5764,11 @@ class MiniProgramMockRuntime {
6013
5764
  this.toasts = [];
6014
5765
  this.vibrates = [];
6015
5766
  this.openAppPages = [];
6016
- this.evaluateRuntimePermission = createMiniProgramRuntimePermissionEvaluator(options.runtimePermissions);
5767
+ const runtimePermissions = resolveMockRuntimePermissions(options);
5768
+ this.evaluateRuntimePermission = createMiniProgramRuntimePermissionEvaluator(runtimePermissions);
5769
+ this.authLoginEnabled = isMockAuthLoginEnabled(runtimePermissions);
5770
+ this.hostUserApiEnabled = isMockHostUserApiEnabled(runtimePermissions);
5771
+ this.authorizationMutationEnabled = isMockAuthorizationMutationEnabled(runtimePermissions);
6017
5772
  this.leaderboardHandlers = createMiniProgramRuntimeLeaderboardHandlers({
6018
5773
  identity: options.identity,
6019
5774
  platformAdapter: adapter,
@@ -6026,7 +5781,7 @@ class MiniProgramMockRuntime {
6026
5781
  if (this.closed && method !== NAVIGATION_CLOSE_METHOD) {
6027
5782
  throw createMockBridgeError('INVALID_STATE', '当前小程序容器已关闭');
6028
5783
  }
6029
- const capability = MINI_PROGRAM_PROTOCOL_CAPABILITIES.find(item => item.method === method);
5784
+ const capability = MINI_PROGRAM_PROTOCOL_CAPABILITIES.find((item) => item.method === method);
6030
5785
  if (capability) {
6031
5786
  const policyResult = this.evaluateRuntimePermission(capability.permission, payload);
6032
5787
  if (!policyResult.allowed) {
@@ -6047,72 +5802,38 @@ class MiniProgramMockRuntime {
6047
5802
  vibrates: [...this.vibrates],
6048
5803
  };
6049
5804
  }
6050
- async login() {
6051
- const result = await loginAndGetMiniProgramRuntimeUserInfo(this.adapter);
6052
- this.options.onAuthChange?.(result);
6053
- return result;
6054
- }
6055
- async getUserInfo() {
6056
- return getMiniProgramRuntimeUserInfo(this.adapter);
6057
- }
6058
- async getCurrentUserDetail() {
6059
- const home = await this.getMockUserHomeV2Result();
6060
- return home
6061
- ? {
6062
- isLogin: true,
6063
- data: mapCurrentUserDetail(home),
6064
- }
6065
- : {
6066
- isLogin: false,
6067
- data: null,
6068
- };
5805
+ login(payload) {
5806
+ if (!this.authLoginEnabled) {
5807
+ throw createMockBridgeError('SERVER_API_REQUIRED', '当前模式要求由小程序服务端完成 auth.login');
5808
+ }
5809
+ return loginMiniProgramRuntime(payload, this.adapter, this.options.identity);
6069
5810
  }
6070
- async getCurrentUserProfile() {
6071
- const home = await this.getMockUserHomeV2Result();
6072
- return home
6073
- ? {
6074
- isLogin: true,
6075
- data: mapCurrentUserProfile(home),
6076
- }
6077
- : {
6078
- isLogin: false,
6079
- data: null,
6080
- };
5811
+ getUserInfo() {
5812
+ this.assertMockHostUserApiEnabled();
5813
+ return getMiniProgramRuntimeUserInfo(this.adapter, this.options.identity);
6081
5814
  }
6082
- async getPlatformAccountOverview() {
6083
- const home = await this.getMockUserHomeV2Result();
6084
- return home
6085
- ? {
6086
- isLogin: true,
6087
- data: mapPlatformAccountOverview(home),
6088
- }
6089
- : {
6090
- isLogin: false,
6091
- data: null,
6092
- };
5815
+ getLocalIdentity(payload) {
5816
+ this.assertMockHostUserApiEnabled();
5817
+ return getMiniProgramRuntimeLocalIdentity(payload, this.adapter, this.options.identity);
6093
5818
  }
6094
- async getPlatformAccountInfo(payload) {
6095
- const platform = readPlatformAccountType(payload);
6096
- const home = await this.getMockUserHomeV2Result();
6097
- return home
6098
- ? mapPlatformAccountResult(home, platform)
6099
- : createUnauthenticatedPlatformAccountResult(platform);
5819
+ revokeAuthorization(payload) {
5820
+ if (!this.authorizationMutationEnabled) {
5821
+ throw createMockBridgeError('SERVER_API_REQUIRED', '当前模式缺少有效的小程序权限快照');
5822
+ }
5823
+ return revokeMiniProgramRuntimeAuthorization(payload, this.adapter, this.options.identity);
6100
5824
  }
6101
5825
  async getSteamGameList(payload) {
6102
- const home = await this.getMockUserHomeV2Result();
6103
- return getMiniProgramRuntimeSteamGameList(payload, {
6104
- ...this.adapter,
6105
- user: {
6106
- ...this.adapter.user,
6107
- getCurrentUserSteamGameListEnvelope: request => Promise.resolve(createMockSteamGameListEnvelope(request.limit, request.offset)),
6108
- },
6109
- }, home);
5826
+ this.assertMockHostUserApiEnabled();
5827
+ const info = await getMiniProgramRuntimeUserInfo(this.adapter, this.options.identity);
5828
+ return getMiniProgramRuntimeSteamGameList(payload, this.adapter, info.isHeyboxAppLoggedIn ? { steam_id_info: { steamid: '76561198000000000' } } : undefined);
5829
+ }
5830
+ assertMockHostUserApiEnabled() {
5831
+ if (!this.hostUserApiEnabled) {
5832
+ throw createMockBridgeError('SERVER_API_REQUIRED', '当前模式要求由小程序服务端调用用户 API');
5833
+ }
6110
5834
  }
6111
- async getMockUserHomeV2Result() {
6112
- const result = await this.getUserInfo();
6113
- return result.isLogin && result.userInfo
6114
- ? createMockRuntimeUserHomeV2Result(result.userInfo)
6115
- : undefined;
5835
+ invoke(method, payload) {
5836
+ return this.runMethod(method, payload);
6116
5837
  }
6117
5838
  showShareMenu(payload) {
6118
5839
  return showMiniProgramRuntimeShareMenu(payload, this.adapter, this.getShareContext());
@@ -6252,6 +5973,31 @@ class MiniProgramMockRuntime {
6252
5973
  this.records.splice(30);
6253
5974
  }
6254
5975
  }
5976
+ function resolveMockRuntimePermissions(options) {
5977
+ if (options.networkMode === 'disabled') {
5978
+ return {
5979
+ schema_version: 1,
5980
+ entries: [{ key: 'network.request', status: 'disabled', config: { useOfficialDomain: false } }],
5981
+ };
5982
+ }
5983
+ if (options.networkMode === 'enabled') {
5984
+ return {
5985
+ schema_version: 1,
5986
+ entries: [{ key: 'network.request', status: 'enabled', config: { useOfficialDomain: true } }],
5987
+ };
5988
+ }
5989
+ return options.runtimePermissions;
5990
+ }
5991
+ function isMockHostUserApiEnabled(runtimePermissions) {
5992
+ const parsed = parseMiniProgramRuntimePermissions(runtimePermissions);
5993
+ return parsed.valid && !hasMiniProgramNetworkRequestPermission(runtimePermissions);
5994
+ }
5995
+ function isMockAuthLoginEnabled(runtimePermissions) {
5996
+ return hasMiniProgramNetworkRequestPermission(runtimePermissions);
5997
+ }
5998
+ function isMockAuthorizationMutationEnabled(runtimePermissions) {
5999
+ return parseMiniProgramRuntimePermissions(runtimePermissions).valid;
6000
+ }
6255
6001
  /**
6256
6002
  * 读取 mock host 自身 URL 上的启动参数作为可信分享上下文。
6257
6003
  * 与生产 runtime 不同:生产只信任启动详情回填的 canonical ID;
@@ -6270,58 +6016,6 @@ function readMockHostShareContext(href) {
6270
6016
  return {};
6271
6017
  }
6272
6018
  }
6273
- function createMockSteamGameListEnvelope(limit, offset) {
6274
- const gameList = [
6275
- {
6276
- appid: 814380,
6277
- steam_appid: 814380,
6278
- name: 'Sekiro: Shadows Die Twice',
6279
- image: 'https://imgheybox.max-c.com/heybox/game/header/814380_lliLB.jpg',
6280
- appicon: 'https://imgheybox.max-c.com/web/2024/02/14/f072e95b0026fa2efba35f737dbc2aab.webp',
6281
- background: 'https://cdn.max-c.com/heybox/game/background/814380_cHOHF.jpg',
6282
- playtime_forever: 2940,
6283
- playtime_2weeks: 120,
6284
- playtime_percent: '0.12',
6285
- achieved_count: 3,
6286
- achivement_count: 34,
6287
- score: '9.8',
6288
- is_free: false,
6289
- not_owned: false,
6290
- online_player: '4314',
6291
- external_tags: ['Action', 'Adventure'],
6292
- genres: ['Action'],
6293
- price: {
6294
- initial: '268',
6295
- current: '134',
6296
- discount: 50,
6297
- lowest_price: 134,
6298
- },
6299
- },
6300
- {
6301
- appid: 230410,
6302
- steam_appid: 230410,
6303
- name: 'Warframe',
6304
- image: 'https://imgheybox.max-c.com/heybox/game/header/230410_tamuE.jpg',
6305
- playtime_forever: 1024140,
6306
- playtime_2weeks: 1500,
6307
- achieved_count: 142,
6308
- achivement_count: 193,
6309
- is_free: true,
6310
- not_owned: false,
6311
- online_player: '5万',
6312
- external_tags: ['MMO', 'Sci-Fi'],
6313
- genres: ['Action', 'Free To Play'],
6314
- },
6315
- ].slice(offset, offset + limit);
6316
- return {
6317
- status: 'ok',
6318
- result: {
6319
- game_list: gameList,
6320
- total: 2,
6321
- is_me: true,
6322
- },
6323
- };
6324
- }
6325
6019
  function isMiniProgramMockRuntimeMethod(method) {
6326
6020
  return MINI_PROGRAM_MOCK_RUNTIME_METHODS.includes(method);
6327
6021
  }
@@ -6489,10 +6183,10 @@ class MockLeaderboardBoard {
6489
6183
  this.key = config.key;
6490
6184
  this.order = readMockLeaderboardOrder(config.order);
6491
6185
  this.rankLimit = readMockLeaderboardRankLimit(config.rankLimit);
6492
- config.entries?.forEach(entry => {
6186
+ config.entries?.forEach((entry) => {
6493
6187
  const now = Date.now();
6494
- this.entries.set(entry.userId, {
6495
- userId: readMockLeaderboardUserId(entry.userId),
6188
+ this.entries.set(entry.appUserId, {
6189
+ appUserId: readMockLeaderboardAppUserId(entry.appUserId),
6496
6190
  score: readMockLeaderboardScore(entry.score),
6497
6191
  extra: cloneMockRecord(entry.extra || {}),
6498
6192
  createdAt: entry.createdAt ?? now,
@@ -6507,7 +6201,7 @@ class MockLeaderboardBoard {
6507
6201
  return this.getCurrentUserEntry(userId);
6508
6202
  }
6509
6203
  this.entries.set(userId, {
6510
- userId,
6204
+ appUserId: userId,
6511
6205
  score,
6512
6206
  extra: extra === undefined ? cloneMockRecord(existing?.extra || {}) : cloneMockRecord(extra),
6513
6207
  createdAt: existing?.createdAt ?? now,
@@ -6536,15 +6230,14 @@ class MockLeaderboardBoard {
6536
6230
  return freshCachedResponse;
6537
6231
  }
6538
6232
  }
6539
- const entriesAfterCursor = this.getListEntries()
6540
- .filter(entry => listCursor ? this.isAfterCursor(entry, listCursor) : true);
6233
+ const entriesAfterCursor = this.getListEntries().filter((entry) => (listCursor ? this.isAfterCursor(entry, listCursor) : true));
6541
6234
  const entries = entriesAfterCursor.slice(0, limit);
6542
6235
  const hasMore = entries.length < entriesAfterCursor.length;
6543
6236
  return this.createListResponse(entries, startRank, hasMore);
6544
6237
  }
6545
6238
  getCurrentUserEntry(userId) {
6546
6239
  const orderedEntries = this.getOrderedEntries();
6547
- const index = orderedEntries.findIndex(entry => entry.userId === userId);
6240
+ const index = orderedEntries.findIndex((entry) => entry.appUserId === userId);
6548
6241
  if (index < 0) {
6549
6242
  return undefined;
6550
6243
  }
@@ -6574,9 +6267,7 @@ class MockLeaderboardBoard {
6574
6267
  let start = 0;
6575
6268
  let startRank = 1;
6576
6269
  if (cursor) {
6577
- const cursorIndex = this.cachedList.entries.findIndex(entry => (entry.score === cursor.score &&
6578
- entry.updatedAt === cursor.update_time &&
6579
- entry.userId === cursor.user_id));
6270
+ const cursorIndex = this.cachedList.entries.findIndex((entry) => entry.score === cursor.score && entry.updatedAt === cursor.update_time && entry.appUserId === cursor.user_id);
6580
6271
  if (cursorIndex < 0) {
6581
6272
  return undefined;
6582
6273
  }
@@ -6596,12 +6287,10 @@ class MockLeaderboardBoard {
6596
6287
  return this.createListResponse(this.cachedList.entries.slice(start, end), startRank, hasMore);
6597
6288
  }
6598
6289
  refreshListCache() {
6599
- const cacheLimit = this.rankLimit > 0 && this.rankLimit < MOCK_LEADERBOARD_CACHE_MAX_ENTRIES
6600
- ? this.rankLimit
6601
- : MOCK_LEADERBOARD_CACHE_MAX_ENTRIES;
6290
+ const cacheLimit = this.rankLimit > 0 && this.rankLimit < MOCK_LEADERBOARD_CACHE_MAX_ENTRIES ? this.rankLimit : MOCK_LEADERBOARD_CACHE_MAX_ENTRIES;
6602
6291
  const rankedEntries = this.getListEntries();
6603
6292
  this.cachedList = {
6604
- entries: rankedEntries.slice(0, cacheLimit).map(entry => ({
6293
+ entries: rankedEntries.slice(0, cacheLimit).map((entry) => ({
6605
6294
  ...entry,
6606
6295
  extra: cloneMockRecord(entry.extra),
6607
6296
  })),
@@ -6633,9 +6322,9 @@ class MockLeaderboardBoard {
6633
6322
  if (left.updatedAt !== right.updatedAt) {
6634
6323
  return left.updatedAt - right.updatedAt;
6635
6324
  }
6636
- return left.userId.localeCompare(right.userId);
6325
+ return left.appUserId.localeCompare(right.appUserId);
6637
6326
  })
6638
- .map(entry => ({
6327
+ .map((entry) => ({
6639
6328
  ...entry,
6640
6329
  extra: cloneMockRecord(entry.extra),
6641
6330
  }));
@@ -6682,7 +6371,7 @@ class MockLeaderboardBoard {
6682
6371
  rank: entry.rank,
6683
6372
  score: entry.score,
6684
6373
  update_time: entry.updatedAt,
6685
- user_id: entry.userId,
6374
+ user_id: entry.appUserId,
6686
6375
  }));
6687
6376
  }
6688
6377
  isAfterCursor(entry, cursor) {
@@ -6692,7 +6381,7 @@ class MockLeaderboardBoard {
6692
6381
  if (entry.updatedAt !== cursor.update_time) {
6693
6382
  return entry.updatedAt > cursor.update_time;
6694
6383
  }
6695
- return entry.userId.localeCompare(cursor.user_id) > 0;
6384
+ return entry.appUserId.localeCompare(cursor.user_id) > 0;
6696
6385
  }
6697
6386
  isWithinRankLimit(rank) {
6698
6387
  return this.rankLimit <= 0 || rank <= this.rankLimit;
@@ -6724,9 +6413,9 @@ function readMockLeaderboardRankLimit(value) {
6724
6413
  }
6725
6414
  return value;
6726
6415
  }
6727
- function readMockLeaderboardUserId(value) {
6416
+ function readMockLeaderboardAppUserId(value) {
6728
6417
  if (typeof value !== 'string' || !value) {
6729
- throw createMockBridgeError('INVALID_PARAMS', 'mock leaderboard entry userId 必须是非空字符串');
6418
+ throw createMockBridgeError('INVALID_PARAMS', 'mock leaderboard entry appUserId 必须是非空字符串');
6730
6419
  }
6731
6420
  return value;
6732
6421
  }
@@ -6763,125 +6452,9 @@ function decodeMockLeaderboardCursor(value) {
6763
6452
  throw createMockBridgeError('INVALID_PARAMS', 'cloud.leaderboard.getList cursor 不合法');
6764
6453
  }
6765
6454
  const binary = atob(padded);
6766
- const bytes = Uint8Array.from(binary, char => char.charCodeAt(0));
6455
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
6767
6456
  return new TextDecoder().decode(bytes);
6768
6457
  }
6769
- function createMockRuntimeUserHomeV2Result(userInfo) {
6770
- const heyboxId = userInfo.heybox_id || 'debug_user';
6771
- const nickname = userInfo.nickname || 'PC Debug User';
6772
- const avatar = userInfo.avatar || '';
6773
- return {
6774
- account_detail: {
6775
- userid: heyboxId,
6776
- username: nickname,
6777
- avartar: avatar,
6778
- signature: '来自 hb-sdk mock runtime 的用户资料',
6779
- ip_location: 'Mock',
6780
- friends_count: '128',
6781
- level_info: {
6782
- level: '12',
6783
- level_name: 'Mock Lv.12',
6784
- exp: '2400',
6785
- next_exp: '3000',
6786
- },
6787
- bbs_medal: [
6788
- {
6789
- id: 'mock-medal',
6790
- name: 'Mock Medal',
6791
- desc: '本地 mock 勋章',
6792
- is_wearing: true,
6793
- },
6794
- ],
6795
- },
6796
- profile: {
6797
- heybox_id: heyboxId,
6798
- nickname,
6799
- avatar,
6800
- gender: 'unknown',
6801
- signature: '来自 hb-sdk mock runtime 的个人档案',
6802
- email: 'mock-user@example.com',
6803
- },
6804
- platform_list: ['steam', 'epic', 'xbox', 'psn', 'switch', 'pc_hardware', 'mobile'],
6805
- steam_is_hidden: false,
6806
- steam_id_info: {
6807
- steamid: '76561198000000000',
6808
- short_id: 'mock-steam',
6809
- nickname: `${nickname} Steam`,
6810
- avatar,
6811
- level: '12',
6812
- friend_code_show: true,
6813
- friend_code: '123456789',
6814
- inventory_open: true,
6815
- personal_information_open: true,
6816
- has_history: true,
6817
- stats: [
6818
- {
6819
- key: 'total_game_count',
6820
- name: '游戏数',
6821
- value: '42',
6822
- },
6823
- {
6824
- key: 'total_player_time',
6825
- name: '总时长',
6826
- value: '128h',
6827
- },
6828
- ],
6829
- },
6830
- epic_account_info: {
6831
- nickname: `${nickname} Epic`,
6832
- avatar,
6833
- xuid: 'mock-epic-xuid',
6834
- desc: 'Mock Epic account',
6835
- },
6836
- xbox_account_info: {
6837
- nickname: `${nickname} Xbox`,
6838
- avatar,
6839
- xuid: 'mock-xbox-xuid',
6840
- desc: 'Mock Xbox account',
6841
- },
6842
- psn_account_info: {
6843
- name: `${nickname} PSN`,
6844
- avatar,
6845
- level: '8',
6846
- description: 'Mock PSN account',
6847
- is_open: true,
6848
- key1: '白金',
6849
- value1: '3',
6850
- },
6851
- switch_account_info: {
6852
- heybox_id: heyboxId,
6853
- nickname: `${nickname} Switch`,
6854
- avatar,
6855
- friend_code: 'SW-1234-5678-9012',
6856
- friends_total: '16',
6857
- games_total: '9',
6858
- server_name: 'Mock Server',
6859
- type: 'switch',
6860
- },
6861
- hardware_info: {
6862
- cpu: 'Mock CPU',
6863
- gpu: 'Mock GPU',
6864
- board: 'Mock Board',
6865
- perf_level: 'A',
6866
- },
6867
- mobile_account_info: {
6868
- current_device: {
6869
- id: 'mock-device',
6870
- name: 'Mock Phone',
6871
- desc: 'hb-sdk mock runtime device',
6872
- model: 'MockPhone 1',
6873
- update_time: '2026-06-04',
6874
- stats: [
6875
- {
6876
- key: 'score',
6877
- name: '跑分',
6878
- value: '999999',
6879
- },
6880
- ],
6881
- },
6882
- },
6883
- };
6884
- }
6885
6458
  function renderMockToast(toast) {
6886
6459
  const document = readDocument();
6887
6460
  if (!document) {
@@ -7014,12 +6587,7 @@ function readWindow() {
7014
6587
  return typeof window === 'undefined' ? undefined : window;
7015
6588
  }
7016
6589
  function escapeMockHtml(value) {
7017
- return String(value)
7018
- .replace(/&/g, '&amp;')
7019
- .replace(/</g, '&lt;')
7020
- .replace(/>/g, '&gt;')
7021
- .replace(/"/g, '&quot;')
7022
- .replace(/'/g, '&#039;');
6590
+ return String(value).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
7023
6591
  }
7024
6592
  function isPlainRecord(value) {
7025
6593
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -7244,6 +6812,9 @@ const NETWORK_PROXY_PATH = '/__hb_sdk_mock_network__';
7244
6812
  const MOCK_HOST_BOOTSTRAP_PATH = '/__hb_sdk_bootstrap__';
7245
6813
  const DEV_PERMISSIONS_PATH = '/__hb_sdk__/dev-permissions';
7246
6814
  const RUNTIME_GATE_PROBE_METHOD = 'runtime.gate.probe';
6815
+ /** 长 heybox:// 协议需要更大展示面,便于测试端扫码。 */
6816
+ const MOBILE_QR_IMAGE_SIZE = 320;
6817
+ const NATIVE_LAUNCH_CONFIG_REFRESH_MS = 1000;
7247
6818
  const params = new URL(location.href).searchParams;
7248
6819
  const miniUrl = params.get('mini_url');
7249
6820
  let lanAddresses = [];
@@ -7259,7 +6830,7 @@ let mobileQrGeneration = 0;
7259
6830
  let mobileQrRefreshTimer;
7260
6831
  let mobileDevSessionCreation = Promise.resolve();
7261
6832
  let currentUser = {
7262
- heybox_id: 'debug_user',
6833
+ app_user_id: 'au_debug_user',
7263
6834
  nickname: 'PC Debug User',
7264
6835
  avatar: '',
7265
6836
  };
@@ -7293,6 +6864,8 @@ if (!miniUrl) {
7293
6864
  }
7294
6865
  const miniProgramUrl = miniUrl;
7295
6866
  const bootstrap = await loadMockHostBootstrap();
6867
+ let nativeLaunchConfigSignature = readNativeLaunchConfigSignature(bootstrap);
6868
+ let nativeLaunchConfigRefreshInFlight = false;
7296
6869
  let devContext = bootstrap.devContext;
7297
6870
  let remotePermissionState = createMiniProgramMockPermissionState(devContext);
7298
6871
  let permissionState = clonePermissionState(remotePermissionState);
@@ -7342,7 +6915,7 @@ queryElement('#login-button').addEventListener('click', () => {
7342
6915
  queryElement('#logout-button').addEventListener('click', () => {
7343
6916
  currentUser = null;
7344
6917
  updateUserStatus();
7345
- postEvent('authChange', createUserInfoResult());
6918
+ postEvent('heybox_app_login_change', { isHeyboxAppLoggedIn: false });
7346
6919
  });
7347
6920
  queryElement('#show-button').addEventListener('click', () => {
7348
6921
  postEvent('show', { timestamp: Date.now(), source: 'mock-host' });
@@ -7366,9 +6939,15 @@ elements.resetPermissionsButton.addEventListener('click', () => {
7366
6939
  void resetPermissionOverride();
7367
6940
  });
7368
6941
  const mobileAppQrReady = setupMobileAppQr();
6942
+ if (bootstrap.devContext.source === 'remote') {
6943
+ void mobileAppQrReady.then(() => {
6944
+ window.setInterval(() => void refreshNativeLaunchConfig(), NATIVE_LAUNCH_CONFIG_REFRESH_MS);
6945
+ });
6946
+ }
7369
6947
  function createRuntime() {
7370
6948
  return new MiniProgramMockRuntime(platformAdapter, {
7371
- onAuthChange: (result) => postEvent('authChange', result),
6949
+ onHeyboxAppLoginChange: payload => postEvent('heybox_app_login_change', payload),
6950
+ onUserInfoAuthorizationChange: authorization => postEvent('user_info_authorization_change', authorization),
7372
6951
  runtimePermissions: createMiniProgramMockRuntimePermissions(permissionState),
7373
6952
  });
7374
6953
  }
@@ -7723,15 +7302,9 @@ function isBridgeMessage(message) {
7723
7302
  message.version === MINI_PROGRAM_MESSAGE_VERSION &&
7724
7303
  message.nonce === nonce);
7725
7304
  }
7726
- function createUserInfoResult() {
7727
- return {
7728
- isLogin: Boolean(currentUser),
7729
- userInfo: currentUser ? { ...currentUser } : null,
7730
- };
7731
- }
7732
7305
  function createUserFromForm() {
7733
7306
  return {
7734
- heybox_id: 'debug_user',
7307
+ app_user_id: 'au_debug_user',
7735
7308
  nickname: elements.nicknameInput.value.trim() || 'PC Debug User',
7736
7309
  avatar: '',
7737
7310
  };
@@ -7765,6 +7338,34 @@ async function setupMobileAppQr() {
7765
7338
  setupMobileLanSelect();
7766
7339
  await updateMobileQrCode();
7767
7340
  }
7341
+ async function refreshNativeLaunchConfig() {
7342
+ if (nativeLaunchConfigRefreshInFlight)
7343
+ return;
7344
+ nativeLaunchConfigRefreshInFlight = true;
7345
+ try {
7346
+ const latest = await loadMockHostBootstrap();
7347
+ const signature = readNativeLaunchConfigSignature(latest);
7348
+ if (signature === nativeLaunchConfigSignature)
7349
+ return;
7350
+ nativeLaunchConfigSignature = signature;
7351
+ bootstrap.defaultLanAddressId = latest.defaultLanAddressId;
7352
+ bootstrap.lanAddresses = latest.lanAddresses;
7353
+ bootstrap.macAppProtocol = latest.macAppProtocol;
7354
+ bootstrap.nativeAppLaunchUnavailableReason = latest.nativeAppLaunchUnavailableReason;
7355
+ await setupMobileAppQr();
7356
+ }
7357
+ finally {
7358
+ nativeLaunchConfigRefreshInFlight = false;
7359
+ }
7360
+ }
7361
+ function readNativeLaunchConfigSignature(value) {
7362
+ return JSON.stringify({
7363
+ defaultLanAddressId: value.defaultLanAddressId,
7364
+ lanAddresses: value.lanAddresses,
7365
+ macAppProtocol: value.macAppProtocol,
7366
+ nativeAppLaunchUnavailableReason: value.nativeAppLaunchUnavailableReason,
7367
+ });
7368
+ }
7768
7369
  async function openMacApp() {
7769
7370
  await mobileAppQrReady.catch(() => undefined);
7770
7371
  if (nativeAppLaunchUnavailableReason) {
@@ -7774,7 +7375,6 @@ async function openMacApp() {
7774
7375
  if (!protocol) {
7775
7376
  return;
7776
7377
  }
7777
- console.log('[hb-sdk] Mac App launch protocol:', protocol);
7778
7378
  showMacAppLaunchHint();
7779
7379
  window.location.href = protocol;
7780
7380
  }
@@ -7875,27 +7475,29 @@ async function updateMobileQrCode() {
7875
7475
  if (generation !== mobileQrGeneration) {
7876
7476
  return;
7877
7477
  }
7478
+ // 测试端扫一扫暂不支持打开网页,二维码必须直接编码 heybox:// 协议(不能走 HTTP 短链)。
7878
7479
  const payload = applyDevContextToMobileQrPayload(basePayload, devSession.url);
7879
7480
  const imageUrl = await browserExports.toDataURL(payload, {
7880
- errorCorrectionLevel: 'M',
7881
- margin: 1,
7882
- width: 176,
7481
+ // 内容很长时用 L 可降低模块密度,配合更大尺寸更容易扫上。
7482
+ errorCorrectionLevel: 'L',
7483
+ margin: 2,
7484
+ width: MOBILE_QR_IMAGE_SIZE,
7883
7485
  });
7884
7486
  if (generation !== mobileQrGeneration) {
7885
7487
  return;
7886
7488
  }
7887
7489
  elements.mobileQrImage.src = imageUrl;
7888
7490
  elements.mobileQrImage.hidden = false;
7889
- elements.mobileQrStatus.textContent = '';
7491
+ elements.mobileQrStatus.textContent = `协议约 ${payload.length} 字符 · 请用测试端扫一扫`;
7890
7492
  scheduleMobileQrRefresh(devSession.expiresAt, generation);
7891
7493
  }
7892
- catch {
7494
+ catch (error) {
7893
7495
  if (generation !== mobileQrGeneration) {
7894
7496
  return;
7895
7497
  }
7896
7498
  elements.mobileQrImage.removeAttribute('src');
7897
7499
  elements.mobileQrImage.hidden = true;
7898
- elements.mobileQrStatus.textContent = '二维码生成失败';
7500
+ elements.mobileQrStatus.textContent = `二维码生成失败:${readErrorMessage(error)}`;
7899
7501
  }
7900
7502
  }
7901
7503
  async function createMobileDevSession(selected) {
@@ -7957,7 +7559,7 @@ function clearMobileQrRefreshTimer() {
7957
7559
  mobileQrRefreshTimer = undefined;
7958
7560
  }
7959
7561
  function applyDevContextToMobileQrPayload(payload, devContextUrl) {
7960
- const protocolMarker = '#heybox://';
7562
+ const protocolMarker = payload.includes('#heybox://') ? '#heybox://' : 'heybox://';
7961
7563
  const protocolIndex = payload.indexOf(protocolMarker);
7962
7564
  if (protocolIndex < 0) {
7963
7565
  throw new Error('Invalid mobile App QR payload');
@@ -7974,9 +7576,31 @@ function applyDevContextToMobileQrPayload(payload, devContextUrl) {
7974
7576
  const devShellUrl = new URL(devShellUrlValue);
7975
7577
  devShellUrl.searchParams.delete('dev_network_request');
7976
7578
  devShellUrl.searchParams.set(MINI_PROGRAM_DEV_CONTEXT_QUERY_PARAM, devContextUrl);
7579
+ // 与 mini_url 相同:客户端打开 runtime_url 时往往不会合并 sandbox query,
7580
+ // 需要把 dev_context_url 写进 runtime_url 本身。
7581
+ const runtimeUrl = devShellUrl.searchParams.get('runtime_url');
7582
+ if (runtimeUrl) {
7583
+ try {
7584
+ const hostUrl = new URL(runtimeUrl);
7585
+ if (hostUrl.protocol === 'http:' || hostUrl.protocol === 'https:') {
7586
+ hostUrl.searchParams.set(MINI_PROGRAM_DEV_CONTEXT_QUERY_PARAM, devContextUrl);
7587
+ const miniUrl = devShellUrl.searchParams.get('mini_url');
7588
+ if (miniUrl && !hostUrl.searchParams.get('mini_url')) {
7589
+ hostUrl.searchParams.set('mini_url', miniUrl);
7590
+ }
7591
+ devShellUrl.searchParams.set('runtime_url', hostUrl.toString());
7592
+ }
7593
+ }
7594
+ catch {
7595
+ // runtime_url 非法时保留原值,避免阻断二维码生成。
7596
+ }
7597
+ }
7977
7598
  openWindowPayload.webview.url = devShellUrl.toString();
7978
7599
  return `${payload.slice(0, protocolIndex)}${protocolMarker}${encodeURIComponent(JSON.stringify(openWindowPayload))}`;
7979
7600
  }
7601
+ function readErrorMessage(error) {
7602
+ return error instanceof Error && error.message ? error.message : '未知错误';
7603
+ }
7980
7604
  function updateNativeAppLaunchAvailability() {
7981
7605
  const isUnavailable = Boolean(nativeAppLaunchUnavailableReason);
7982
7606
  elements.macAppButton.disabled = isUnavailable;