@heybox/hb-sdk-protocol 0.8.3-alpha.2 → 0.8.3-alpha.4

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.
package/README.md CHANGED
@@ -13,6 +13,7 @@
13
13
  - method payload/result 与错误的 wire 类型;
14
14
  - Mobile Mini-program Native Host 的无版本 envelope、operation vocabulary 与跨语言 fixture;
15
15
  - `runtime_permissions` schema 解析器和相关类型。
16
+ - CLI / Host 专用的 `local_dev_grant` 严格合同与纯数据合成,不向小程序业务 API 暴露权限来源。
16
17
 
17
18
  它不拥有:
18
19
 
@@ -50,12 +51,25 @@ import { MINI_PROGRAM_PROTOCOL_CAPABILITIES, type MiniProgramBridgeMessage } fro
50
51
 
51
52
  协议包不决定业务授权。`auth.login()` 的 UI、账号会话、授权码签发和 OpenAPI 都由 Host/服务端负责;协议层只传递经过约束的 request/response 数据。
52
53
 
54
+ ## 本地调试临时权限
55
+
56
+ `parseMiniProgramLocalDevGrant()` 严格接受 schema 1、非负安全整数 revision,以及 `network` / `companion` 清单;未知字段、未知权限、重复项、非 enabled 状态或非法配置都会拒绝整份 grant。网络配置必须是 `useOfficialDomain: false`。解析成功只表示数据合法,Host 仍须验证现有启动票、canonical identity、摘要、期限、CLI lease 并取得每次启动确认。
57
+
58
+ `createMiniProgramLocalDevGrant()` 仅生成已声明而线上未批准的缺口;Companion 由 CLI 完成本地产物检查后显式列入 eligible keys。`composeLocalDevRuntimePermissions()` 将线上基线与本地声明求交后补齐合法 grant,保留已批准配置的声明子集,输出独立的普通 Runtime 快照和 Host 专用提示清单。生产路径不得调用此合成函数,grant 也不得进入 Manifest、发布资料或 iframe 握手。
59
+
53
60
  ## 握手环境快照
54
61
 
55
62
  Runtime 可以在握手 response 中附带 `MiniProgramRuntimeEnvironmentInfo`。该快照只包含运行模式、Host App 版本、canonical 小程序 ID/版本和标准化操作系统信息,不包含账号、设备唯一标识、UA 或凭据。字段缺失和旧 Host 通过固定的 `null` / `'unknown'` 语义兼容;客户端 SDK 会在对外派发 `ready` 事件前缓存快照,业务代码通过根包的 `environment` 模块读取。
56
63
 
57
64
  该 response 是 v2 envelope 上的向后兼容扩展,不新增 capability method 或权限,也不提升 `MINI_PROGRAM_MESSAGE_VERSION`。旧 SDK 没有对应 pending request,会忽略该 response;新 SDK 遇到旧 Host 时生成未知值快照。
58
65
 
66
+ ## 窗口协议
67
+
68
+ `viewport.setDisplay` 接受 `{ orientation?: 'portrait' | 'landscape', fullscreen?: boolean }`,返回 `{ supported: boolean }`。`true` 表示原生方向与胶囊设置已完成;`false` 表示原生能力不可用,保留原生状态,宿主仍可尽力处理 H5 顶部栏。能力缺失的兼容错误由 SDK 统一转换为 `false`,其它执行错误继续保留。
69
+ 至少传一个字段,省略字段保持当前目标值;初始为竖屏、非全屏。全屏同时收起胶囊和宿主额外顶部栏。SDK 复用 request-scoped cancel 协议;
70
+ Runtime 持有截止时间与意图替换语义,Host 负责原子应用与终止恢复。
71
+ `viewport_change` 事件载荷复用 `MiniProgramWindowInfoResult`,描述当前窗口与四边安全区。
72
+
59
73
  ## 公开入口
60
74
 
61
75
  - `@heybox/hb-sdk-protocol`:本包唯一入口,适合 Host、Runtime 和协议维护代码。
@@ -85,3 +99,9 @@ pnpm --filter @heybox/hb-sdk-protocol run check:boundary
85
99
  pnpm --filter @heybox/hb-sdk-protocol run test:unit
86
100
  pnpm --filter @heybox/hb-sdk-protocol run build:package
87
101
  ```
102
+
103
+ ### 展示状态事件
104
+
105
+ `display_change` 在宿主确认方向或全屏状态发生变化后发送 `{ orientation, fullscreen, source }`,包括开发者调用 `setDisplay()` 和用户点击宿主退出全屏按钮。两个字段独立;退出全屏不改变方向。重复状态、不支持(`supported: false`)、失败、超时、取消及被后续请求替换的设置不发送事件。事件不回放初始状态,新容器默认竖屏、非全屏;它不监听手机旋转传感器,窗口尺寸与安全区仍通过 `viewport_change` 获取。请在调用设置前注册监听,并在页面销毁时取消。
106
+
107
+ `display_change.source` 区分变化来源:`api` 为开发者调用,`user` 为用户点击宿主退出全屏按钮。同目标并发合并时保留首次发起操作的来源;重复状态不额外发事件。旧宿主可能不含 source,不能将缺失来源推断为用户操作。仅用户退出时跳页,应判断 `source === 'user' && !fullscreen`,再按业务需要调用竖屏设置并跳转。
package/dist/index.cjs.js CHANGED
@@ -41,6 +41,7 @@ const USER_GET_STEAM_GAME_LIST_METHOD = 'user.getSteamGameList';
41
41
  const SHARE_COPY_LINK_METHOD = 'share.copyLink';
42
42
  const SHARE_SHOW_SHARE_MENU_METHOD = 'share.showShareMenu';
43
43
  const SHARE_SCREENSHOT_METHOD = 'share.screenshot';
44
+ const VIEWPORT_SET_DISPLAY_METHOD = 'viewport.setDisplay';
44
45
  const VIEWPORT_GET_WINDOW_INFO_METHOD = 'viewport.getWindowInfo';
45
46
  const VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD = 'viewport.setNavigationBarStyle';
46
47
  const STORAGE_GET_STORAGE_METHOD = 'storage.getStorage';
@@ -164,6 +165,13 @@ const MINI_PROGRAM_PROTOCOL_CAPABILITIES = [
164
165
  requirement: { kind: 'none' },
165
166
  risk: 'low',
166
167
  },
168
+ {
169
+ method: VIEWPORT_SET_DISPLAY_METHOD,
170
+ module: 'viewport',
171
+ capability: VIEWPORT_SET_DISPLAY_METHOD,
172
+ requirement: { kind: 'none' },
173
+ risk: 'low',
174
+ },
167
175
  {
168
176
  method: STORAGE_GET_STORAGE_METHOD,
169
177
  module: 'storage',
@@ -439,7 +447,7 @@ const MINI_PROGRAM_PROTOCOL_CAPABILITIES = [
439
447
  * @see [parseMiniProgramRuntimePermissions](/reference/symbols/protocol/functions/parseMiniProgramRuntimePermissions) Runtime 权限快照解析器。
440
448
  */
441
449
  function isMiniProgramBridgeMessage(value) {
442
- if (!isRecord$2(value))
450
+ if (!isRecord$3(value))
443
451
  return false;
444
452
  if (value.namespace !== MINI_PROGRAM_MESSAGE_NAMESPACE || (value.version !== 1 && value.version !== 2) || !isNonEmptyString$1(value.nonce)) {
445
453
  return false;
@@ -589,7 +597,7 @@ function isMiniProgramRelativePath(value) {
589
597
  * @see [DirectoryHandle](/reference/symbols/root/interfaces/DirectoryHandle) 可作为 derived path root 的目录代理。
590
598
  */
591
599
  function isMiniProgramPathRootRef(value) {
592
- if (!isRecord$2(value))
600
+ if (!isRecord$3(value))
593
601
  return false;
594
602
  if (value.kind === 'sandbox')
595
603
  return hasOnlyKeys(value, ['kind']);
@@ -655,7 +663,7 @@ function isMiniProgramPathRootRef(value) {
655
663
  * @see [FileHandle](/reference/symbols/root/interfaces/FileHandle) 使用文件引用的公开 I/O 代理。
656
664
  */
657
665
  function isMiniProgramFileRef(value) {
658
- if (!isRecord$2(value) || value.kind !== 'file')
666
+ if (!isRecord$3(value) || value.kind !== 'file')
659
667
  return false;
660
668
  if (isNonEmptyString$1(value.handleId)) {
661
669
  return hasOnlyKeys(value, ['kind', 'handleId']);
@@ -722,7 +730,7 @@ function isMiniProgramFileRef(value) {
722
730
  * @see [DirectoryHandle](/reference/symbols/root/interfaces/DirectoryHandle) 使用目录引用的公开路径操作代理。
723
731
  */
724
732
  function isMiniProgramDirectoryRef(value) {
725
- if (!isRecord$2(value) || value.kind !== 'directory')
733
+ if (!isRecord$3(value) || value.kind !== 'directory')
726
734
  return false;
727
735
  if (isNonEmptyString$1(value.handleId)) {
728
736
  return hasOnlyKeys(value, ['kind', 'handleId']);
@@ -796,7 +804,7 @@ function isMiniProgramDirectoryRef(value) {
796
804
  * @see [SaveFileResult](/reference/symbols/protocol/types/SaveFileResult) save picker 的 direct-ref 文件结果。
797
805
  */
798
806
  function isMiniProgramFileDescriptor(value) {
799
- return (isRecord$2(value) &&
807
+ return (isRecord$3(value) &&
800
808
  value.kind === 'file' &&
801
809
  isNonEmptyString$1(value.name) &&
802
810
  isFileMode(value.mode) &&
@@ -866,7 +874,7 @@ function isMiniProgramFileDescriptor(value) {
866
874
  * @see [PickDirectoryResult](/reference/symbols/protocol/types/PickDirectoryResult) picker 的 direct-ref 返回契约。
867
875
  */
868
876
  function isMiniProgramDirectoryDescriptor(value) {
869
- return (isRecord$2(value) &&
877
+ return (isRecord$3(value) &&
870
878
  value.kind === 'directory' &&
871
879
  isNonEmptyString$1(value.name) &&
872
880
  isFileMode(value.mode) &&
@@ -1045,7 +1053,7 @@ function isMiniProgramByteArray(value) {
1045
1053
  * @see [FileHandle](/reference/symbols/root/interfaces/FileHandle) 提供 `stat()` 的当前 session 文件代理。
1046
1054
  */
1047
1055
  function isMiniProgramFileStat(value) {
1048
- return (isRecord$2(value) &&
1056
+ return (isRecord$3(value) &&
1049
1057
  value.kind === 'file' &&
1050
1058
  isNonNegativeSafeInteger(value.size) &&
1051
1059
  (value.modifiedAt === undefined || isNonNegativeFiniteNumber(value.modifiedAt)) &&
@@ -1105,7 +1113,7 @@ function isMiniProgramFileStat(value) {
1105
1113
  * @see [isMiniProgramBridgeMessage](/reference/symbols/protocol/functions/isMiniProgramBridgeMessage) operation progress envelope 的最低校验。
1106
1114
  */
1107
1115
  function isMiniProgramDownloadProgressPayload(value) {
1108
- if (!isRecord$2(value) ||
1116
+ if (!isRecord$3(value) ||
1109
1117
  !isNonNegativeSafeInteger(value.loaded) ||
1110
1118
  typeof value.lengthComputable !== 'boolean' ||
1111
1119
  !hasOnlyKeys(value, ['loaded', 'total', 'lengthComputable'])) {
@@ -1116,7 +1124,7 @@ function isMiniProgramDownloadProgressPayload(value) {
1116
1124
  return isNonNegativeSafeInteger(value.total) && value.loaded <= value.total;
1117
1125
  }
1118
1126
  function isMiniProgramCompanionPrepareProgress(value) {
1119
- if (!isRecord$2(value))
1127
+ if (!isRecord$3(value))
1120
1128
  return false;
1121
1129
  if (value.phase === 'downloading') {
1122
1130
  return (isNonNegativeSafeInteger(value.loaded) &&
@@ -1134,10 +1142,10 @@ function isMiniProgramOperationProgressPayload(value) {
1134
1142
  return isMiniProgramDownloadProgressPayload(value) || isMiniProgramCompanionPrepareProgress(value);
1135
1143
  }
1136
1144
  function isSDKHandshakePayload(value) {
1137
- return isRecord$2(value) && isNonEmptyString$1(value.href) && typeof value.userAgent === 'string' && isNonEmptyString$1(value.sdkVersion);
1145
+ return isRecord$3(value) && isNonEmptyString$1(value.href) && typeof value.userAgent === 'string' && isNonEmptyString$1(value.sdkVersion);
1138
1146
  }
1139
1147
  function isBridgeError(value) {
1140
- return isRecord$2(value) && isNonEmptyString$1(value.code) && typeof value.message === 'string';
1148
+ return isRecord$3(value) && isNonEmptyString$1(value.code) && typeof value.message === 'string';
1141
1149
  }
1142
1150
  function isFileMode(value) {
1143
1151
  return value === 'read' || value === 'readwrite';
@@ -1155,156 +1163,10 @@ function hasOnlyKeys(value, allowedKeys) {
1155
1163
  const allowed = new Set(allowedKeys);
1156
1164
  return Object.keys(value).every((key) => allowed.has(key));
1157
1165
  }
1158
- function isRecord$2(value) {
1166
+ function isRecord$3(value) {
1159
1167
  return typeof value === 'object' && value !== null && !Array.isArray(value);
1160
1168
  }
1161
1169
 
1162
- /** 由 scripts/generate-native-host-contract.mjs 生成,请勿手改。 */
1163
- const MINI_PROGRAM_NATIVE_HOST_PROTOCOL_VERSION = 1;
1164
- const MINI_PROGRAM_NATIVE_HOST_CONTRACT_REVISION = 1;
1165
- const MINI_PROGRAM_NATIVE_CHUNK_MAX_BYTES = 65536;
1166
- const MINI_PROGRAM_NATIVE_OPERATIONS = {
1167
- "system": [
1168
- "describe"
1169
- ],
1170
- "network": [
1171
- "open",
1172
- "read",
1173
- "release",
1174
- "transfer"
1175
- ],
1176
- "files": [
1177
- "create",
1178
- "remove",
1179
- "exists",
1180
- "openRead",
1181
- "openWrite",
1182
- "read",
1183
- "release",
1184
- "write",
1185
- "stat",
1186
- "list",
1187
- "pickFiles",
1188
- "pickDirectory",
1189
- "saveFile"
1190
- ]
1191
- };
1192
- const MINI_PROGRAM_NATIVE_HOST_DESCRIPTION = {
1193
- "protocolVersion": 1,
1194
- "contractRevision": 1,
1195
- "operations": {
1196
- "system": [
1197
- "describe"
1198
- ],
1199
- "network": [
1200
- "open",
1201
- "read",
1202
- "release",
1203
- "transfer"
1204
- ],
1205
- "files": [
1206
- "create",
1207
- "remove",
1208
- "exists",
1209
- "openRead",
1210
- "openWrite",
1211
- "read",
1212
- "release",
1213
- "write",
1214
- "stat",
1215
- "list",
1216
- "pickFiles",
1217
- "pickDirectory",
1218
- "saveFile"
1219
- ]
1220
- }
1221
- };
1222
-
1223
- /** Android WebMessageListener object 与 iOS WKScriptMessageHandler 共用名称。 */
1224
- const MINI_PROGRAM_NATIVE_HOST_OBJECT = 'hbMiniProgramHost';
1225
- /** Native 通过主文档 CustomEvent 回送 progress/result/error。 */
1226
- const MINI_PROGRAM_NATIVE_HOST_MESSAGE_EVENT = 'heybox:miniprogram-native-host-message';
1227
- function isMiniProgramNativeHostMessage(value) {
1228
- if (!isRecord$1(value) || !isNonEmptyString(value.requestId))
1229
- return false;
1230
- if (value.type === 'progress')
1231
- return Object.prototype.hasOwnProperty.call(value, 'payload');
1232
- if (value.type === 'result')
1233
- return !Object.prototype.hasOwnProperty.call(value, 'error');
1234
- if (value.type !== 'error' || !isRecord$1(value.error))
1235
- return false;
1236
- return isNonEmptyString(value.error.code) && isNonEmptyString(value.error.message);
1237
- }
1238
- function isMiniProgramNativeHostDescription(value) {
1239
- if (!isRecord$1(value) ||
1240
- !isPositiveSafeInteger(value.protocolVersion) ||
1241
- !isPositiveSafeInteger(value.contractRevision) ||
1242
- !isRecord$1(value.operations))
1243
- return false;
1244
- const operations = value.operations;
1245
- if (!isOperationList(operations.system) ||
1246
- !isOperationList(operations.network) ||
1247
- !isOperationList(operations.files))
1248
- return false;
1249
- return operations.system.includes('describe');
1250
- }
1251
- function isMiniProgramNativeBinaryChunk(value) {
1252
- return (isRecord$1(value) &&
1253
- isNonEmptyString(value.resourceId) &&
1254
- typeof value.data === 'string' &&
1255
- Number.isSafeInteger(value.byteLength) &&
1256
- Number(value.byteLength) >= 0 &&
1257
- Number(value.byteLength) <= MINI_PROGRAM_NATIVE_CHUNK_MAX_BYTES &&
1258
- typeof value.eof === 'boolean' &&
1259
- (Number(value.byteLength) > 0 || value.eof));
1260
- }
1261
- function isRecord$1(value) {
1262
- return value !== null && typeof value === 'object' && !Array.isArray(value);
1263
- }
1264
- function isNonEmptyString(value) {
1265
- return typeof value === 'string' && value.length > 0;
1266
- }
1267
- function isPositiveSafeInteger(value) {
1268
- return Number.isSafeInteger(value) && Number(value) > 0;
1269
- }
1270
- function isOperationList(value) {
1271
- return (Array.isArray(value) &&
1272
- value.every(operation => isNonEmptyString(operation) && operation === operation.trim()) &&
1273
- new Set(value).size === value.length);
1274
- }
1275
-
1276
- /** 当前用户信息与登录能力支持的完整 scope 目录(canonical 顺序)。 */
1277
- const USER_INFO_AUTHORIZATION_SCOPES = ['identity', 'profile', 'steam_account'];
1278
- /** Companion alias 的跨构建、Runtime 与 Host 规范。 */
1279
- const COMPANION_ALIAS_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
1280
- /** Companion Host 可跨 bridge 保留的稳定领域错误码。 */
1281
- const COMPANION_ERROR_CODES = [
1282
- 'COMPANION_UNSUPPORTED',
1283
- 'COMPANION_PREVIEW_UNSUPPORTED',
1284
- 'COMPANION_NOT_DECLARED',
1285
- 'COMPANION_TARGET_UNSUPPORTED',
1286
- 'COMPANION_NOT_PREPARED',
1287
- 'COMPANION_PREPARATION_CANCELLED',
1288
- 'COMPANION_DOWNLOAD_FAILED',
1289
- 'COMPANION_DESCRIPTOR_UNAVAILABLE',
1290
- 'COMPANION_ARCHIVE_INVALID',
1291
- 'COMPANION_INTEGRITY_FAILED',
1292
- 'COMPANION_ENTRYPOINT_INVALID',
1293
- 'COMPANION_USER_CANCELLED',
1294
- 'COMPANION_ELEVATION_DENIED',
1295
- 'COMPANION_OS_BLOCKED',
1296
- 'COMPANION_LAUNCH_FAILED',
1297
- 'COMPANION_SESSION_VERSION_MISMATCH',
1298
- 'COMPANION_SESSION_OWNERSHIP_LOST',
1299
- 'COMPANION_SESSION_EXITED',
1300
- 'COMPANION_PIPE_CLOSED',
1301
- 'COMPANION_OUTPUT_LIMIT',
1302
- 'COMPANION_PROCESS_CRASHED',
1303
- 'COMPANION_ATTACHMENT_NOT_FOUND',
1304
- 'COMPANION_NOTIFICATION_UNAVAILABLE',
1305
- 'COMPANION_TERMINATE_FAILED',
1306
- ];
1307
-
1308
1170
  const MINI_PROGRAM_PERMISSION_KEYS = [
1309
1171
  'userInfo',
1310
1172
  'steamLibrary',
@@ -1458,6 +1320,7 @@ const MINI_PROGRAM_NO_DECLARATION_METHODS = [
1458
1320
  'ui.hideLoading',
1459
1321
  'viewport.getWindowInfo',
1460
1322
  'viewport.setNavigationBarStyle',
1323
+ 'viewport.setDisplay',
1461
1324
  'device.vibrate',
1462
1325
  'navigation.close',
1463
1326
  'navigation.reload',
@@ -1598,7 +1461,7 @@ function isManagedMiniProgramRuntimePermissionKey(key) {
1598
1461
  * @see {@link https://docs.xiaoheihe.cn/hb_sdk/guide/permissions | 权限声明}
1599
1462
  */
1600
1463
  function parseMiniProgramRuntimePermissions(snapshot) {
1601
- if (!isRecord(snapshot) || snapshot.schema_version !== 1 || !Array.isArray(snapshot.entries)) {
1464
+ if (!isRecord$2(snapshot) || snapshot.schema_version !== 1 || !Array.isArray(snapshot.entries)) {
1602
1465
  return { valid: false, permissions: {} };
1603
1466
  }
1604
1467
  if (snapshot.revision !== undefined && (typeof snapshot.revision !== 'number' || !Number.isInteger(snapshot.revision) || snapshot.revision < 0)) {
@@ -1607,7 +1470,7 @@ function parseMiniProgramRuntimePermissions(snapshot) {
1607
1470
  const seenKeys = new Set();
1608
1471
  const permissions = {};
1609
1472
  for (const rawEntry of snapshot.entries) {
1610
- if (!isRecord(rawEntry) || typeof rawEntry.key !== 'string' || !rawEntry.key.trim()) {
1473
+ if (!isRecord$2(rawEntry) || typeof rawEntry.key !== 'string' || !rawEntry.key.trim()) {
1611
1474
  return { valid: false, permissions: {} };
1612
1475
  }
1613
1476
  const inputKey = rawEntry.key.trim();
@@ -1623,7 +1486,7 @@ function parseMiniProgramRuntimePermissions(snapshot) {
1623
1486
  }
1624
1487
  if (key === 'network') {
1625
1488
  const config = rawEntry.config;
1626
- if (!isRecord(config) || typeof config.useOfficialDomain !== 'boolean') {
1489
+ if (!isRecord$2(config) || typeof config.useOfficialDomain !== 'boolean') {
1627
1490
  return { valid: false, permissions: {} };
1628
1491
  }
1629
1492
  permissions[key] = {
@@ -1633,7 +1496,7 @@ function parseMiniProgramRuntimePermissions(snapshot) {
1633
1496
  };
1634
1497
  continue;
1635
1498
  }
1636
- if (rawEntry.config !== null && rawEntry.config !== undefined && (!isRecord(rawEntry.config) || Object.keys(rawEntry.config).length > 0)) {
1499
+ if (rawEntry.config !== null && rawEntry.config !== undefined && (!isRecord$2(rawEntry.config) || Object.keys(rawEntry.config).length > 0)) {
1637
1500
  return { valid: false, permissions: {} };
1638
1501
  }
1639
1502
  permissions[key] = {
@@ -1644,10 +1507,266 @@ function parseMiniProgramRuntimePermissions(snapshot) {
1644
1507
  }
1645
1508
  return { valid: true, permissions };
1646
1509
  }
1647
- function isRecord(value) {
1510
+ function isRecord$2(value) {
1648
1511
  return typeof value === 'object' && value !== null && !Array.isArray(value);
1649
1512
  }
1650
1513
 
1514
+ /** 仅供开发工具和 Host 使用;新增权限必须单独评估,不能从平台审批目录自动推导。 */
1515
+ const LOCAL_DEV_GRANT_PERMISSION_KEYS = ['network', 'companion'];
1516
+ /**
1517
+ * 严格解析本地 grant。任何未知字段、重复权限或非法配置都拒绝整份输入。
1518
+ * 只校验数据合同;票据、身份、摘要、过期时间和 CLI lease 必须由 Host 另行验证。
1519
+ */
1520
+ function parseMiniProgramLocalDevGrant(value) {
1521
+ if (!hasExactFields(value, ['schema_version', 'revision', 'entries']) ||
1522
+ value.schema_version !== 1 ||
1523
+ typeof value.revision !== 'number' ||
1524
+ !Number.isSafeInteger(value.revision) ||
1525
+ value.revision < 0 ||
1526
+ !Array.isArray(value.entries) ||
1527
+ value.entries.length > LOCAL_DEV_GRANT_PERMISSION_KEYS.length)
1528
+ return { valid: false };
1529
+ const entries = [];
1530
+ const seen = new Set();
1531
+ for (const entry of value.entries) {
1532
+ if (!hasExactFields(entry, ['key', 'status', 'config']) || entry.status !== 'enabled')
1533
+ return { valid: false };
1534
+ if (typeof entry.key !== 'string' || seen.has(entry.key))
1535
+ return { valid: false };
1536
+ seen.add(entry.key);
1537
+ if (entry.key === 'network') {
1538
+ if (!hasExactFields(entry.config, ['useOfficialDomain']) || entry.config.useOfficialDomain !== false)
1539
+ return { valid: false };
1540
+ entries.push({ key: 'network', status: 'enabled', config: { useOfficialDomain: false } });
1541
+ }
1542
+ else if (entry.key === 'companion') {
1543
+ if (!hasExactFields(entry.config, []))
1544
+ return { valid: false };
1545
+ entries.push({ key: 'companion', status: 'enabled', config: {} });
1546
+ }
1547
+ else {
1548
+ return { valid: false };
1549
+ }
1550
+ }
1551
+ entries.sort((a, b) => LOCAL_DEV_GRANT_PERMISSION_KEYS.indexOf(a.key) - LOCAL_DEV_GRANT_PERMISSION_KEYS.indexOf(b.key));
1552
+ return { valid: true, grant: { schema_version: 1, revision: value.revision, entries } };
1553
+ }
1554
+ /**
1555
+ * 从已校验声明生成批准缺口。Companion 默认不符合资格,由 CLI 确认平台、产物与摘要后显式加入。
1556
+ * 空 entries 可保留 revision,不能作为存在临时权限的判断依据。
1557
+ */
1558
+ function createMiniProgramLocalDevGrant(options) {
1559
+ if (!Number.isSafeInteger(options.revision) || options.revision < 0)
1560
+ throw new Error('本地调试权限 revision 必须是非负安全整数');
1561
+ const approvals = parseMiniProgramRuntimePermissions(options.runtimePermissions);
1562
+ const eligible = new Set(options.eligiblePermissionKeys ?? ['network']);
1563
+ const entries = [];
1564
+ for (const key of LOCAL_DEV_GRANT_PERMISSION_KEYS) {
1565
+ if (!eligible.has(key) || options.declaredPermissions[key]?.enabled !== true)
1566
+ continue;
1567
+ if (approvals.valid && approvals.permissions[key]?.status === 'enabled')
1568
+ continue;
1569
+ entries.push(key === 'network'
1570
+ ? { key, status: 'enabled', config: { useOfficialDomain: false } }
1571
+ : { key, status: 'enabled', config: {} });
1572
+ }
1573
+ return { schema_version: 1, revision: options.revision, entries };
1574
+ }
1575
+ /**
1576
+ * 线上基线先与本地声明求交,再补齐临时权限,返回与输入脱离的普通 Runtime 快照。
1577
+ * 调用方只可在当前 Dev Session 已验证且用户接受后传入 grant;本函数不证明其可信来源。
1578
+ * 非法 grant 整体降权到基线,已批准配置不会被临时配置覆盖。
1579
+ */
1580
+ function composeLocalDevRuntimePermissions(options) {
1581
+ const baseline = parseMiniProgramRuntimePermissions(options.runtimePermissions);
1582
+ const parsed = parseMiniProgramLocalDevGrant(options.localDevGrant);
1583
+ const permissions = baseline.valid ? baseline.permissions : {};
1584
+ for (const key of MINI_PROGRAM_PERMISSION_KEYS) {
1585
+ if (options.declaredPermissions[key]?.enabled !== true) {
1586
+ delete permissions[key];
1587
+ }
1588
+ else if (key === 'network' && permissions.network) {
1589
+ permissions.network.config.useOfficialDomain = permissions.network.status === 'enabled'
1590
+ && permissions.network.config.useOfficialDomain === true
1591
+ && options.declaredPermissions.network?.useOfficialDomain === true;
1592
+ }
1593
+ }
1594
+ const temporaryPermissionKeys = [];
1595
+ if (parsed.valid) {
1596
+ for (const entry of parsed.grant.entries) {
1597
+ if (options.declaredPermissions[entry.key]?.enabled !== true || permissions[entry.key]?.status === 'enabled')
1598
+ continue;
1599
+ permissions[entry.key] = entry;
1600
+ temporaryPermissionKeys.push(entry.key);
1601
+ }
1602
+ }
1603
+ const revision = isRecord$1(options.runtimePermissions) && typeof options.runtimePermissions.revision === 'number'
1604
+ && Number.isSafeInteger(options.runtimePermissions.revision) && options.runtimePermissions.revision >= 0
1605
+ ? options.runtimePermissions.revision : undefined;
1606
+ return {
1607
+ runtimePermissions: {
1608
+ schema_version: 1,
1609
+ ...(revision === undefined ? {} : { revision }),
1610
+ entries: MINI_PROGRAM_PERMISSION_KEYS.flatMap((key) => permissions[key] ? [permissions[key]] : []),
1611
+ },
1612
+ temporaryPermissionKeys,
1613
+ grantValid: parsed.valid,
1614
+ };
1615
+ }
1616
+ function isRecord$1(value) {
1617
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
1618
+ }
1619
+ function hasExactFields(value, fields) {
1620
+ return isRecord$1(value) && Object.keys(value).length === fields.length
1621
+ && fields.every((field) => Object.prototype.hasOwnProperty.call(value, field));
1622
+ }
1623
+
1624
+ /** 由 scripts/generate-native-host-contract.mjs 生成,请勿手改。 */
1625
+ const MINI_PROGRAM_NATIVE_HOST_PROTOCOL_VERSION = 1;
1626
+ const MINI_PROGRAM_NATIVE_HOST_CONTRACT_REVISION = 1;
1627
+ const MINI_PROGRAM_NATIVE_CHUNK_MAX_BYTES = 65536;
1628
+ const MINI_PROGRAM_NATIVE_OPERATIONS = {
1629
+ "system": [
1630
+ "describe"
1631
+ ],
1632
+ "network": [
1633
+ "open",
1634
+ "read",
1635
+ "release",
1636
+ "transfer"
1637
+ ],
1638
+ "files": [
1639
+ "create",
1640
+ "remove",
1641
+ "exists",
1642
+ "openRead",
1643
+ "openWrite",
1644
+ "read",
1645
+ "release",
1646
+ "write",
1647
+ "stat",
1648
+ "list",
1649
+ "pickFiles",
1650
+ "pickDirectory",
1651
+ "saveFile"
1652
+ ]
1653
+ };
1654
+ const MINI_PROGRAM_NATIVE_HOST_DESCRIPTION = {
1655
+ "protocolVersion": 1,
1656
+ "contractRevision": 1,
1657
+ "operations": {
1658
+ "system": [
1659
+ "describe"
1660
+ ],
1661
+ "network": [
1662
+ "open",
1663
+ "read",
1664
+ "release",
1665
+ "transfer"
1666
+ ],
1667
+ "files": [
1668
+ "create",
1669
+ "remove",
1670
+ "exists",
1671
+ "openRead",
1672
+ "openWrite",
1673
+ "read",
1674
+ "release",
1675
+ "write",
1676
+ "stat",
1677
+ "list",
1678
+ "pickFiles",
1679
+ "pickDirectory",
1680
+ "saveFile"
1681
+ ]
1682
+ }
1683
+ };
1684
+
1685
+ /** Android WebMessageListener object 与 iOS WKScriptMessageHandler 共用名称。 */
1686
+ const MINI_PROGRAM_NATIVE_HOST_OBJECT = 'hbMiniProgramHost';
1687
+ /** Native 通过主文档 CustomEvent 回送 progress/result/error。 */
1688
+ const MINI_PROGRAM_NATIVE_HOST_MESSAGE_EVENT = 'heybox:miniprogram-native-host-message';
1689
+ function isMiniProgramNativeHostMessage(value) {
1690
+ if (!isRecord(value) || !isNonEmptyString(value.requestId))
1691
+ return false;
1692
+ if (value.type === 'progress')
1693
+ return Object.prototype.hasOwnProperty.call(value, 'payload');
1694
+ if (value.type === 'result')
1695
+ return !Object.prototype.hasOwnProperty.call(value, 'error');
1696
+ if (value.type !== 'error' || !isRecord(value.error))
1697
+ return false;
1698
+ return isNonEmptyString(value.error.code) && isNonEmptyString(value.error.message);
1699
+ }
1700
+ function isMiniProgramNativeHostDescription(value) {
1701
+ if (!isRecord(value) ||
1702
+ !isPositiveSafeInteger(value.protocolVersion) ||
1703
+ !isPositiveSafeInteger(value.contractRevision) ||
1704
+ !isRecord(value.operations))
1705
+ return false;
1706
+ const operations = value.operations;
1707
+ if (!isOperationList(operations.system) ||
1708
+ !isOperationList(operations.network) ||
1709
+ !isOperationList(operations.files))
1710
+ return false;
1711
+ return operations.system.includes('describe');
1712
+ }
1713
+ function isMiniProgramNativeBinaryChunk(value) {
1714
+ return (isRecord(value) &&
1715
+ isNonEmptyString(value.resourceId) &&
1716
+ typeof value.data === 'string' &&
1717
+ Number.isSafeInteger(value.byteLength) &&
1718
+ Number(value.byteLength) >= 0 &&
1719
+ Number(value.byteLength) <= MINI_PROGRAM_NATIVE_CHUNK_MAX_BYTES &&
1720
+ typeof value.eof === 'boolean' &&
1721
+ (Number(value.byteLength) > 0 || value.eof));
1722
+ }
1723
+ function isRecord(value) {
1724
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
1725
+ }
1726
+ function isNonEmptyString(value) {
1727
+ return typeof value === 'string' && value.length > 0;
1728
+ }
1729
+ function isPositiveSafeInteger(value) {
1730
+ return Number.isSafeInteger(value) && Number(value) > 0;
1731
+ }
1732
+ function isOperationList(value) {
1733
+ return (Array.isArray(value) &&
1734
+ value.every(operation => isNonEmptyString(operation) && operation === operation.trim()) &&
1735
+ new Set(value).size === value.length);
1736
+ }
1737
+
1738
+ /** 当前用户信息与登录能力支持的完整 scope 目录(canonical 顺序)。 */
1739
+ const USER_INFO_AUTHORIZATION_SCOPES = ['identity', 'profile', 'steam_account'];
1740
+ /** Companion alias 的跨构建、Runtime 与 Host 规范。 */
1741
+ const COMPANION_ALIAS_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
1742
+ /** Companion Host 可跨 bridge 保留的稳定领域错误码。 */
1743
+ const COMPANION_ERROR_CODES = [
1744
+ 'COMPANION_UNSUPPORTED',
1745
+ 'COMPANION_PREVIEW_UNSUPPORTED',
1746
+ 'COMPANION_NOT_DECLARED',
1747
+ 'COMPANION_TARGET_UNSUPPORTED',
1748
+ 'COMPANION_NOT_PREPARED',
1749
+ 'COMPANION_PREPARATION_CANCELLED',
1750
+ 'COMPANION_DOWNLOAD_FAILED',
1751
+ 'COMPANION_DESCRIPTOR_UNAVAILABLE',
1752
+ 'COMPANION_ARCHIVE_INVALID',
1753
+ 'COMPANION_INTEGRITY_FAILED',
1754
+ 'COMPANION_ENTRYPOINT_INVALID',
1755
+ 'COMPANION_USER_CANCELLED',
1756
+ 'COMPANION_ELEVATION_DENIED',
1757
+ 'COMPANION_OS_BLOCKED',
1758
+ 'COMPANION_LAUNCH_FAILED',
1759
+ 'COMPANION_SESSION_VERSION_MISMATCH',
1760
+ 'COMPANION_SESSION_OWNERSHIP_LOST',
1761
+ 'COMPANION_SESSION_EXITED',
1762
+ 'COMPANION_PIPE_CLOSED',
1763
+ 'COMPANION_OUTPUT_LIMIT',
1764
+ 'COMPANION_PROCESS_CRASHED',
1765
+ 'COMPANION_ATTACHMENT_NOT_FOUND',
1766
+ 'COMPANION_NOTIFICATION_UNAVAILABLE',
1767
+ 'COMPANION_TERMINATE_FAILED',
1768
+ ];
1769
+
1651
1770
  exports.AUTH_LOGIN_METHOD = AUTH_LOGIN_METHOD;
1652
1771
  exports.CLOUD_LEADERBOARD_DELETE_CURRENT_USER_ENTRY_METHOD = CLOUD_LEADERBOARD_DELETE_CURRENT_USER_ENTRY_METHOD;
1653
1772
  exports.CLOUD_LEADERBOARD_GET_CURRENT_USER_ENTRY_METHOD = CLOUD_LEADERBOARD_GET_CURRENT_USER_ENTRY_METHOD;
@@ -1688,6 +1807,7 @@ exports.FILE_REMOVE_METHOD = FILE_REMOVE_METHOD;
1688
1807
  exports.FILE_STAT_METHOD = FILE_STAT_METHOD;
1689
1808
  exports.FILE_WRITE_BYTES_METHOD = FILE_WRITE_BYTES_METHOD;
1690
1809
  exports.FILE_WRITE_TEXT_METHOD = FILE_WRITE_TEXT_METHOD;
1810
+ exports.LOCAL_DEV_GRANT_PERMISSION_KEYS = LOCAL_DEV_GRANT_PERMISSION_KEYS;
1691
1811
  exports.MINI_PROGRAM_BRIDGE_NONCE_PARAM = MINI_PROGRAM_BRIDGE_NONCE_PARAM;
1692
1812
  exports.MINI_PROGRAM_MESSAGE_NAMESPACE = MINI_PROGRAM_MESSAGE_NAMESPACE;
1693
1813
  exports.MINI_PROGRAM_MESSAGE_VERSION = MINI_PROGRAM_MESSAGE_VERSION;
@@ -1725,7 +1845,10 @@ exports.USER_GET_STEAM_GAME_LIST_METHOD = USER_GET_STEAM_GAME_LIST_METHOD;
1725
1845
  exports.USER_INFO_AUTHORIZATION_SCOPES = USER_INFO_AUTHORIZATION_SCOPES;
1726
1846
  exports.USER_REVOKE_AUTHORIZATION_METHOD = USER_REVOKE_AUTHORIZATION_METHOD;
1727
1847
  exports.VIEWPORT_GET_WINDOW_INFO_METHOD = VIEWPORT_GET_WINDOW_INFO_METHOD;
1848
+ exports.VIEWPORT_SET_DISPLAY_METHOD = VIEWPORT_SET_DISPLAY_METHOD;
1728
1849
  exports.VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD = VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD;
1850
+ exports.composeLocalDevRuntimePermissions = composeLocalDevRuntimePermissions;
1851
+ exports.createMiniProgramLocalDevGrant = createMiniProgramLocalDevGrant;
1729
1852
  exports.isManagedMiniProgramRuntimePermissionKey = isManagedMiniProgramRuntimePermissionKey;
1730
1853
  exports.isMiniProgramBridgeMessage = isMiniProgramBridgeMessage;
1731
1854
  exports.isMiniProgramByteArray = isMiniProgramByteArray;
@@ -1744,4 +1867,5 @@ exports.isMiniProgramOperationProgressPayload = isMiniProgramOperationProgressPa
1744
1867
  exports.isMiniProgramPathRootRef = isMiniProgramPathRootRef;
1745
1868
  exports.isMiniProgramPermissionKey = isMiniProgramPermissionKey;
1746
1869
  exports.isMiniProgramRelativePath = isMiniProgramRelativePath;
1870
+ exports.parseMiniProgramLocalDevGrant = parseMiniProgramLocalDevGrant;
1747
1871
  exports.parseMiniProgramRuntimePermissions = parseMiniProgramRuntimePermissions;