@heybox/hb-sdk-protocol 0.8.3-alpha.3 → 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 +7 -0
- package/dist/index.cjs.js +277 -163
- package/dist/index.esm.js +274 -164
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/local-dev-grant.ts +141 -0
- package/types/index.d.ts +1 -0
- package/types/local-dev-grant.d.ts +63 -0
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,6 +51,12 @@ 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` 模块读取。
|
package/dist/index.cjs.js
CHANGED
|
@@ -447,7 +447,7 @@ const MINI_PROGRAM_PROTOCOL_CAPABILITIES = [
|
|
|
447
447
|
* @see [parseMiniProgramRuntimePermissions](/reference/symbols/protocol/functions/parseMiniProgramRuntimePermissions) Runtime 权限快照解析器。
|
|
448
448
|
*/
|
|
449
449
|
function isMiniProgramBridgeMessage(value) {
|
|
450
|
-
if (!isRecord$
|
|
450
|
+
if (!isRecord$3(value))
|
|
451
451
|
return false;
|
|
452
452
|
if (value.namespace !== MINI_PROGRAM_MESSAGE_NAMESPACE || (value.version !== 1 && value.version !== 2) || !isNonEmptyString$1(value.nonce)) {
|
|
453
453
|
return false;
|
|
@@ -597,7 +597,7 @@ function isMiniProgramRelativePath(value) {
|
|
|
597
597
|
* @see [DirectoryHandle](/reference/symbols/root/interfaces/DirectoryHandle) 可作为 derived path root 的目录代理。
|
|
598
598
|
*/
|
|
599
599
|
function isMiniProgramPathRootRef(value) {
|
|
600
|
-
if (!isRecord$
|
|
600
|
+
if (!isRecord$3(value))
|
|
601
601
|
return false;
|
|
602
602
|
if (value.kind === 'sandbox')
|
|
603
603
|
return hasOnlyKeys(value, ['kind']);
|
|
@@ -663,7 +663,7 @@ function isMiniProgramPathRootRef(value) {
|
|
|
663
663
|
* @see [FileHandle](/reference/symbols/root/interfaces/FileHandle) 使用文件引用的公开 I/O 代理。
|
|
664
664
|
*/
|
|
665
665
|
function isMiniProgramFileRef(value) {
|
|
666
|
-
if (!isRecord$
|
|
666
|
+
if (!isRecord$3(value) || value.kind !== 'file')
|
|
667
667
|
return false;
|
|
668
668
|
if (isNonEmptyString$1(value.handleId)) {
|
|
669
669
|
return hasOnlyKeys(value, ['kind', 'handleId']);
|
|
@@ -730,7 +730,7 @@ function isMiniProgramFileRef(value) {
|
|
|
730
730
|
* @see [DirectoryHandle](/reference/symbols/root/interfaces/DirectoryHandle) 使用目录引用的公开路径操作代理。
|
|
731
731
|
*/
|
|
732
732
|
function isMiniProgramDirectoryRef(value) {
|
|
733
|
-
if (!isRecord$
|
|
733
|
+
if (!isRecord$3(value) || value.kind !== 'directory')
|
|
734
734
|
return false;
|
|
735
735
|
if (isNonEmptyString$1(value.handleId)) {
|
|
736
736
|
return hasOnlyKeys(value, ['kind', 'handleId']);
|
|
@@ -804,7 +804,7 @@ function isMiniProgramDirectoryRef(value) {
|
|
|
804
804
|
* @see [SaveFileResult](/reference/symbols/protocol/types/SaveFileResult) save picker 的 direct-ref 文件结果。
|
|
805
805
|
*/
|
|
806
806
|
function isMiniProgramFileDescriptor(value) {
|
|
807
|
-
return (isRecord$
|
|
807
|
+
return (isRecord$3(value) &&
|
|
808
808
|
value.kind === 'file' &&
|
|
809
809
|
isNonEmptyString$1(value.name) &&
|
|
810
810
|
isFileMode(value.mode) &&
|
|
@@ -874,7 +874,7 @@ function isMiniProgramFileDescriptor(value) {
|
|
|
874
874
|
* @see [PickDirectoryResult](/reference/symbols/protocol/types/PickDirectoryResult) picker 的 direct-ref 返回契约。
|
|
875
875
|
*/
|
|
876
876
|
function isMiniProgramDirectoryDescriptor(value) {
|
|
877
|
-
return (isRecord$
|
|
877
|
+
return (isRecord$3(value) &&
|
|
878
878
|
value.kind === 'directory' &&
|
|
879
879
|
isNonEmptyString$1(value.name) &&
|
|
880
880
|
isFileMode(value.mode) &&
|
|
@@ -1053,7 +1053,7 @@ function isMiniProgramByteArray(value) {
|
|
|
1053
1053
|
* @see [FileHandle](/reference/symbols/root/interfaces/FileHandle) 提供 `stat()` 的当前 session 文件代理。
|
|
1054
1054
|
*/
|
|
1055
1055
|
function isMiniProgramFileStat(value) {
|
|
1056
|
-
return (isRecord$
|
|
1056
|
+
return (isRecord$3(value) &&
|
|
1057
1057
|
value.kind === 'file' &&
|
|
1058
1058
|
isNonNegativeSafeInteger(value.size) &&
|
|
1059
1059
|
(value.modifiedAt === undefined || isNonNegativeFiniteNumber(value.modifiedAt)) &&
|
|
@@ -1113,7 +1113,7 @@ function isMiniProgramFileStat(value) {
|
|
|
1113
1113
|
* @see [isMiniProgramBridgeMessage](/reference/symbols/protocol/functions/isMiniProgramBridgeMessage) operation progress envelope 的最低校验。
|
|
1114
1114
|
*/
|
|
1115
1115
|
function isMiniProgramDownloadProgressPayload(value) {
|
|
1116
|
-
if (!isRecord$
|
|
1116
|
+
if (!isRecord$3(value) ||
|
|
1117
1117
|
!isNonNegativeSafeInteger(value.loaded) ||
|
|
1118
1118
|
typeof value.lengthComputable !== 'boolean' ||
|
|
1119
1119
|
!hasOnlyKeys(value, ['loaded', 'total', 'lengthComputable'])) {
|
|
@@ -1124,7 +1124,7 @@ function isMiniProgramDownloadProgressPayload(value) {
|
|
|
1124
1124
|
return isNonNegativeSafeInteger(value.total) && value.loaded <= value.total;
|
|
1125
1125
|
}
|
|
1126
1126
|
function isMiniProgramCompanionPrepareProgress(value) {
|
|
1127
|
-
if (!isRecord$
|
|
1127
|
+
if (!isRecord$3(value))
|
|
1128
1128
|
return false;
|
|
1129
1129
|
if (value.phase === 'downloading') {
|
|
1130
1130
|
return (isNonNegativeSafeInteger(value.loaded) &&
|
|
@@ -1142,10 +1142,10 @@ function isMiniProgramOperationProgressPayload(value) {
|
|
|
1142
1142
|
return isMiniProgramDownloadProgressPayload(value) || isMiniProgramCompanionPrepareProgress(value);
|
|
1143
1143
|
}
|
|
1144
1144
|
function isSDKHandshakePayload(value) {
|
|
1145
|
-
return isRecord$
|
|
1145
|
+
return isRecord$3(value) && isNonEmptyString$1(value.href) && typeof value.userAgent === 'string' && isNonEmptyString$1(value.sdkVersion);
|
|
1146
1146
|
}
|
|
1147
1147
|
function isBridgeError(value) {
|
|
1148
|
-
return isRecord$
|
|
1148
|
+
return isRecord$3(value) && isNonEmptyString$1(value.code) && typeof value.message === 'string';
|
|
1149
1149
|
}
|
|
1150
1150
|
function isFileMode(value) {
|
|
1151
1151
|
return value === 'read' || value === 'readwrite';
|
|
@@ -1163,156 +1163,10 @@ function hasOnlyKeys(value, allowedKeys) {
|
|
|
1163
1163
|
const allowed = new Set(allowedKeys);
|
|
1164
1164
|
return Object.keys(value).every((key) => allowed.has(key));
|
|
1165
1165
|
}
|
|
1166
|
-
function isRecord$
|
|
1166
|
+
function isRecord$3(value) {
|
|
1167
1167
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1168
1168
|
}
|
|
1169
1169
|
|
|
1170
|
-
/** 由 scripts/generate-native-host-contract.mjs 生成,请勿手改。 */
|
|
1171
|
-
const MINI_PROGRAM_NATIVE_HOST_PROTOCOL_VERSION = 1;
|
|
1172
|
-
const MINI_PROGRAM_NATIVE_HOST_CONTRACT_REVISION = 1;
|
|
1173
|
-
const MINI_PROGRAM_NATIVE_CHUNK_MAX_BYTES = 65536;
|
|
1174
|
-
const MINI_PROGRAM_NATIVE_OPERATIONS = {
|
|
1175
|
-
"system": [
|
|
1176
|
-
"describe"
|
|
1177
|
-
],
|
|
1178
|
-
"network": [
|
|
1179
|
-
"open",
|
|
1180
|
-
"read",
|
|
1181
|
-
"release",
|
|
1182
|
-
"transfer"
|
|
1183
|
-
],
|
|
1184
|
-
"files": [
|
|
1185
|
-
"create",
|
|
1186
|
-
"remove",
|
|
1187
|
-
"exists",
|
|
1188
|
-
"openRead",
|
|
1189
|
-
"openWrite",
|
|
1190
|
-
"read",
|
|
1191
|
-
"release",
|
|
1192
|
-
"write",
|
|
1193
|
-
"stat",
|
|
1194
|
-
"list",
|
|
1195
|
-
"pickFiles",
|
|
1196
|
-
"pickDirectory",
|
|
1197
|
-
"saveFile"
|
|
1198
|
-
]
|
|
1199
|
-
};
|
|
1200
|
-
const MINI_PROGRAM_NATIVE_HOST_DESCRIPTION = {
|
|
1201
|
-
"protocolVersion": 1,
|
|
1202
|
-
"contractRevision": 1,
|
|
1203
|
-
"operations": {
|
|
1204
|
-
"system": [
|
|
1205
|
-
"describe"
|
|
1206
|
-
],
|
|
1207
|
-
"network": [
|
|
1208
|
-
"open",
|
|
1209
|
-
"read",
|
|
1210
|
-
"release",
|
|
1211
|
-
"transfer"
|
|
1212
|
-
],
|
|
1213
|
-
"files": [
|
|
1214
|
-
"create",
|
|
1215
|
-
"remove",
|
|
1216
|
-
"exists",
|
|
1217
|
-
"openRead",
|
|
1218
|
-
"openWrite",
|
|
1219
|
-
"read",
|
|
1220
|
-
"release",
|
|
1221
|
-
"write",
|
|
1222
|
-
"stat",
|
|
1223
|
-
"list",
|
|
1224
|
-
"pickFiles",
|
|
1225
|
-
"pickDirectory",
|
|
1226
|
-
"saveFile"
|
|
1227
|
-
]
|
|
1228
|
-
}
|
|
1229
|
-
};
|
|
1230
|
-
|
|
1231
|
-
/** Android WebMessageListener object 与 iOS WKScriptMessageHandler 共用名称。 */
|
|
1232
|
-
const MINI_PROGRAM_NATIVE_HOST_OBJECT = 'hbMiniProgramHost';
|
|
1233
|
-
/** Native 通过主文档 CustomEvent 回送 progress/result/error。 */
|
|
1234
|
-
const MINI_PROGRAM_NATIVE_HOST_MESSAGE_EVENT = 'heybox:miniprogram-native-host-message';
|
|
1235
|
-
function isMiniProgramNativeHostMessage(value) {
|
|
1236
|
-
if (!isRecord$1(value) || !isNonEmptyString(value.requestId))
|
|
1237
|
-
return false;
|
|
1238
|
-
if (value.type === 'progress')
|
|
1239
|
-
return Object.prototype.hasOwnProperty.call(value, 'payload');
|
|
1240
|
-
if (value.type === 'result')
|
|
1241
|
-
return !Object.prototype.hasOwnProperty.call(value, 'error');
|
|
1242
|
-
if (value.type !== 'error' || !isRecord$1(value.error))
|
|
1243
|
-
return false;
|
|
1244
|
-
return isNonEmptyString(value.error.code) && isNonEmptyString(value.error.message);
|
|
1245
|
-
}
|
|
1246
|
-
function isMiniProgramNativeHostDescription(value) {
|
|
1247
|
-
if (!isRecord$1(value) ||
|
|
1248
|
-
!isPositiveSafeInteger(value.protocolVersion) ||
|
|
1249
|
-
!isPositiveSafeInteger(value.contractRevision) ||
|
|
1250
|
-
!isRecord$1(value.operations))
|
|
1251
|
-
return false;
|
|
1252
|
-
const operations = value.operations;
|
|
1253
|
-
if (!isOperationList(operations.system) ||
|
|
1254
|
-
!isOperationList(operations.network) ||
|
|
1255
|
-
!isOperationList(operations.files))
|
|
1256
|
-
return false;
|
|
1257
|
-
return operations.system.includes('describe');
|
|
1258
|
-
}
|
|
1259
|
-
function isMiniProgramNativeBinaryChunk(value) {
|
|
1260
|
-
return (isRecord$1(value) &&
|
|
1261
|
-
isNonEmptyString(value.resourceId) &&
|
|
1262
|
-
typeof value.data === 'string' &&
|
|
1263
|
-
Number.isSafeInteger(value.byteLength) &&
|
|
1264
|
-
Number(value.byteLength) >= 0 &&
|
|
1265
|
-
Number(value.byteLength) <= MINI_PROGRAM_NATIVE_CHUNK_MAX_BYTES &&
|
|
1266
|
-
typeof value.eof === 'boolean' &&
|
|
1267
|
-
(Number(value.byteLength) > 0 || value.eof));
|
|
1268
|
-
}
|
|
1269
|
-
function isRecord$1(value) {
|
|
1270
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
1271
|
-
}
|
|
1272
|
-
function isNonEmptyString(value) {
|
|
1273
|
-
return typeof value === 'string' && value.length > 0;
|
|
1274
|
-
}
|
|
1275
|
-
function isPositiveSafeInteger(value) {
|
|
1276
|
-
return Number.isSafeInteger(value) && Number(value) > 0;
|
|
1277
|
-
}
|
|
1278
|
-
function isOperationList(value) {
|
|
1279
|
-
return (Array.isArray(value) &&
|
|
1280
|
-
value.every(operation => isNonEmptyString(operation) && operation === operation.trim()) &&
|
|
1281
|
-
new Set(value).size === value.length);
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
/** 当前用户信息与登录能力支持的完整 scope 目录(canonical 顺序)。 */
|
|
1285
|
-
const USER_INFO_AUTHORIZATION_SCOPES = ['identity', 'profile', 'steam_account'];
|
|
1286
|
-
/** Companion alias 的跨构建、Runtime 与 Host 规范。 */
|
|
1287
|
-
const COMPANION_ALIAS_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
|
|
1288
|
-
/** Companion Host 可跨 bridge 保留的稳定领域错误码。 */
|
|
1289
|
-
const COMPANION_ERROR_CODES = [
|
|
1290
|
-
'COMPANION_UNSUPPORTED',
|
|
1291
|
-
'COMPANION_PREVIEW_UNSUPPORTED',
|
|
1292
|
-
'COMPANION_NOT_DECLARED',
|
|
1293
|
-
'COMPANION_TARGET_UNSUPPORTED',
|
|
1294
|
-
'COMPANION_NOT_PREPARED',
|
|
1295
|
-
'COMPANION_PREPARATION_CANCELLED',
|
|
1296
|
-
'COMPANION_DOWNLOAD_FAILED',
|
|
1297
|
-
'COMPANION_DESCRIPTOR_UNAVAILABLE',
|
|
1298
|
-
'COMPANION_ARCHIVE_INVALID',
|
|
1299
|
-
'COMPANION_INTEGRITY_FAILED',
|
|
1300
|
-
'COMPANION_ENTRYPOINT_INVALID',
|
|
1301
|
-
'COMPANION_USER_CANCELLED',
|
|
1302
|
-
'COMPANION_ELEVATION_DENIED',
|
|
1303
|
-
'COMPANION_OS_BLOCKED',
|
|
1304
|
-
'COMPANION_LAUNCH_FAILED',
|
|
1305
|
-
'COMPANION_SESSION_VERSION_MISMATCH',
|
|
1306
|
-
'COMPANION_SESSION_OWNERSHIP_LOST',
|
|
1307
|
-
'COMPANION_SESSION_EXITED',
|
|
1308
|
-
'COMPANION_PIPE_CLOSED',
|
|
1309
|
-
'COMPANION_OUTPUT_LIMIT',
|
|
1310
|
-
'COMPANION_PROCESS_CRASHED',
|
|
1311
|
-
'COMPANION_ATTACHMENT_NOT_FOUND',
|
|
1312
|
-
'COMPANION_NOTIFICATION_UNAVAILABLE',
|
|
1313
|
-
'COMPANION_TERMINATE_FAILED',
|
|
1314
|
-
];
|
|
1315
|
-
|
|
1316
1170
|
const MINI_PROGRAM_PERMISSION_KEYS = [
|
|
1317
1171
|
'userInfo',
|
|
1318
1172
|
'steamLibrary',
|
|
@@ -1607,7 +1461,7 @@ function isManagedMiniProgramRuntimePermissionKey(key) {
|
|
|
1607
1461
|
* @see {@link https://docs.xiaoheihe.cn/hb_sdk/guide/permissions | 权限声明}
|
|
1608
1462
|
*/
|
|
1609
1463
|
function parseMiniProgramRuntimePermissions(snapshot) {
|
|
1610
|
-
if (!isRecord(snapshot) || snapshot.schema_version !== 1 || !Array.isArray(snapshot.entries)) {
|
|
1464
|
+
if (!isRecord$2(snapshot) || snapshot.schema_version !== 1 || !Array.isArray(snapshot.entries)) {
|
|
1611
1465
|
return { valid: false, permissions: {} };
|
|
1612
1466
|
}
|
|
1613
1467
|
if (snapshot.revision !== undefined && (typeof snapshot.revision !== 'number' || !Number.isInteger(snapshot.revision) || snapshot.revision < 0)) {
|
|
@@ -1616,7 +1470,7 @@ function parseMiniProgramRuntimePermissions(snapshot) {
|
|
|
1616
1470
|
const seenKeys = new Set();
|
|
1617
1471
|
const permissions = {};
|
|
1618
1472
|
for (const rawEntry of snapshot.entries) {
|
|
1619
|
-
if (!isRecord(rawEntry) || typeof rawEntry.key !== 'string' || !rawEntry.key.trim()) {
|
|
1473
|
+
if (!isRecord$2(rawEntry) || typeof rawEntry.key !== 'string' || !rawEntry.key.trim()) {
|
|
1620
1474
|
return { valid: false, permissions: {} };
|
|
1621
1475
|
}
|
|
1622
1476
|
const inputKey = rawEntry.key.trim();
|
|
@@ -1632,7 +1486,7 @@ function parseMiniProgramRuntimePermissions(snapshot) {
|
|
|
1632
1486
|
}
|
|
1633
1487
|
if (key === 'network') {
|
|
1634
1488
|
const config = rawEntry.config;
|
|
1635
|
-
if (!isRecord(config) || typeof config.useOfficialDomain !== 'boolean') {
|
|
1489
|
+
if (!isRecord$2(config) || typeof config.useOfficialDomain !== 'boolean') {
|
|
1636
1490
|
return { valid: false, permissions: {} };
|
|
1637
1491
|
}
|
|
1638
1492
|
permissions[key] = {
|
|
@@ -1642,7 +1496,7 @@ function parseMiniProgramRuntimePermissions(snapshot) {
|
|
|
1642
1496
|
};
|
|
1643
1497
|
continue;
|
|
1644
1498
|
}
|
|
1645
|
-
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)) {
|
|
1646
1500
|
return { valid: false, permissions: {} };
|
|
1647
1501
|
}
|
|
1648
1502
|
permissions[key] = {
|
|
@@ -1653,9 +1507,265 @@ function parseMiniProgramRuntimePermissions(snapshot) {
|
|
|
1653
1507
|
}
|
|
1654
1508
|
return { valid: true, permissions };
|
|
1655
1509
|
}
|
|
1656
|
-
function isRecord(value) {
|
|
1510
|
+
function isRecord$2(value) {
|
|
1511
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1512
|
+
}
|
|
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) {
|
|
1657
1617
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1658
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
|
+
];
|
|
1659
1769
|
|
|
1660
1770
|
exports.AUTH_LOGIN_METHOD = AUTH_LOGIN_METHOD;
|
|
1661
1771
|
exports.CLOUD_LEADERBOARD_DELETE_CURRENT_USER_ENTRY_METHOD = CLOUD_LEADERBOARD_DELETE_CURRENT_USER_ENTRY_METHOD;
|
|
@@ -1697,6 +1807,7 @@ exports.FILE_REMOVE_METHOD = FILE_REMOVE_METHOD;
|
|
|
1697
1807
|
exports.FILE_STAT_METHOD = FILE_STAT_METHOD;
|
|
1698
1808
|
exports.FILE_WRITE_BYTES_METHOD = FILE_WRITE_BYTES_METHOD;
|
|
1699
1809
|
exports.FILE_WRITE_TEXT_METHOD = FILE_WRITE_TEXT_METHOD;
|
|
1810
|
+
exports.LOCAL_DEV_GRANT_PERMISSION_KEYS = LOCAL_DEV_GRANT_PERMISSION_KEYS;
|
|
1700
1811
|
exports.MINI_PROGRAM_BRIDGE_NONCE_PARAM = MINI_PROGRAM_BRIDGE_NONCE_PARAM;
|
|
1701
1812
|
exports.MINI_PROGRAM_MESSAGE_NAMESPACE = MINI_PROGRAM_MESSAGE_NAMESPACE;
|
|
1702
1813
|
exports.MINI_PROGRAM_MESSAGE_VERSION = MINI_PROGRAM_MESSAGE_VERSION;
|
|
@@ -1736,6 +1847,8 @@ exports.USER_REVOKE_AUTHORIZATION_METHOD = USER_REVOKE_AUTHORIZATION_METHOD;
|
|
|
1736
1847
|
exports.VIEWPORT_GET_WINDOW_INFO_METHOD = VIEWPORT_GET_WINDOW_INFO_METHOD;
|
|
1737
1848
|
exports.VIEWPORT_SET_DISPLAY_METHOD = VIEWPORT_SET_DISPLAY_METHOD;
|
|
1738
1849
|
exports.VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD = VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD;
|
|
1850
|
+
exports.composeLocalDevRuntimePermissions = composeLocalDevRuntimePermissions;
|
|
1851
|
+
exports.createMiniProgramLocalDevGrant = createMiniProgramLocalDevGrant;
|
|
1739
1852
|
exports.isManagedMiniProgramRuntimePermissionKey = isManagedMiniProgramRuntimePermissionKey;
|
|
1740
1853
|
exports.isMiniProgramBridgeMessage = isMiniProgramBridgeMessage;
|
|
1741
1854
|
exports.isMiniProgramByteArray = isMiniProgramByteArray;
|
|
@@ -1754,4 +1867,5 @@ exports.isMiniProgramOperationProgressPayload = isMiniProgramOperationProgressPa
|
|
|
1754
1867
|
exports.isMiniProgramPathRootRef = isMiniProgramPathRootRef;
|
|
1755
1868
|
exports.isMiniProgramPermissionKey = isMiniProgramPermissionKey;
|
|
1756
1869
|
exports.isMiniProgramRelativePath = isMiniProgramRelativePath;
|
|
1870
|
+
exports.parseMiniProgramLocalDevGrant = parseMiniProgramLocalDevGrant;
|
|
1757
1871
|
exports.parseMiniProgramRuntimePermissions = parseMiniProgramRuntimePermissions;
|