@heybox/hb-sdk 0.9.3-alpha.6 → 0.9.3

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 (40) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -0
  3. package/dist/cli-chunks/{build-B1YssPNl.cjs → build-DrFbb0V-.cjs} +5 -5
  4. package/dist/cli-chunks/{context-Deob2IZ5.cjs → context-CTlwLbwn.cjs} +2 -2
  5. package/dist/cli-chunks/{create-BWzY1CVT.cjs → create-Ccse2SW7.cjs} +1 -1
  6. package/dist/cli-chunks/{deploy-lock-ClcPcaSW.cjs → deploy-lock-DaGpgsU7.cjs} +1 -1
  7. package/dist/cli-chunks/{dev-wVCyeUOb.cjs → dev-BHsCFm4O.cjs} +8 -8
  8. package/dist/cli-chunks/{doctor-DHd5eYBr.cjs → doctor-BhTTMupn.cjs} +1 -1
  9. package/dist/cli-chunks/{games-DNhP1Om4.cjs → games-BOP_ZDp6.cjs} +2 -2
  10. package/dist/cli-chunks/{index-DfYr8g3I.cjs → index-CF6qh9ll.cjs} +17 -17
  11. package/dist/cli-chunks/{index-BOd8ouDY.cjs → index-DMall69X.cjs} +1 -1
  12. package/dist/cli-chunks/{index.esm-PlHUr780.cjs → index.esm-Cau47g22.cjs} +6 -6
  13. package/dist/cli-chunks/{login-D-iG6Um2.cjs → login-BGDovscU.cjs} +1 -1
  14. package/dist/cli-chunks/{project-vite-DyKHpc6h.cjs → project-vite-Dnev8Z34.cjs} +1 -1
  15. package/dist/cli-chunks/{remote-CyynHOcj.cjs → remote--MDcbz4m.cjs} +7 -7
  16. package/dist/cli-chunks/{runtime-gate-DPXnKdQ4.cjs → runtime-gate-BgWB2U5f.cjs} +2 -2
  17. package/dist/cli-chunks/{runtime-permission-env-BjO2KzGl.cjs → runtime-permission-env-DmJ1zEpa.cjs} +13 -2
  18. package/dist/cli-chunks/{skill-BOLOEDrj.cjs → skill-Bm1MmMN5.cjs} +2 -2
  19. package/dist/cli-chunks/{version-DX2glIk7.cjs → version-C6b3tkax.cjs} +1 -1
  20. package/dist/cli.cjs +1 -1
  21. package/dist/devtools/browser-dev-host/assets/browser-dev-host-BHtf4WWG.js +102 -0
  22. package/dist/devtools/browser-dev-host/assets/desktop-app-launch-Dl4RfM9z.js +6 -0
  23. package/dist/devtools/browser-dev-host/assets/{index-Br7UpHGt.js → index-DFLYLAXj.js} +4 -4
  24. package/dist/devtools/browser-dev-host/assets/{index-7Cq8iNcD.css → index-h-fYisyX.css} +1 -1
  25. package/dist/devtools/browser-dev-host/index.html +3 -3
  26. package/dist/index.cjs.js +314 -28
  27. package/dist/index.esm.js +314 -29
  28. package/dist/miniapp-publish.cjs.js +11 -0
  29. package/dist/miniapp-publish.esm.js +11 -0
  30. package/dist/protocol.cjs.js +172 -0
  31. package/dist/protocol.esm.js +169 -1
  32. package/dist/vite.cjs.js +17 -3
  33. package/dist/vite.esm.js +17 -3
  34. package/package.json +7 -7
  35. package/skill/skill.json +4 -4
  36. package/types/index.d.ts +80 -2
  37. package/types/miniapp-publish/index.d.ts +1 -1
  38. package/types/protocol.d.ts +77 -2
  39. package/dist/devtools/browser-dev-host/assets/browser-dev-host-CD6Ado2B.js +0 -102
  40. package/dist/devtools/browser-dev-host/assets/desktop-app-launch-CFl6m8k-.js +0 -6
package/dist/index.esm.js CHANGED
@@ -440,6 +440,7 @@ const DIRECTORY_CREATE_METHOD = 'directory.create';
440
440
  const DIRECTORY_REMOVE_METHOD = 'directory.remove';
441
441
  const DIRECTORY_EXISTS_METHOD = 'directory.exists';
442
442
  const DIRECTORY_LIST_METHOD = 'directory.list';
443
+ const MEDIA_PICK_METHOD = 'media.pick';
443
444
  const UI_SHOW_TOAST_METHOD = 'ui.showToast';
444
445
  const UI_SHOW_LOADING_METHOD = 'ui.showLoading';
445
446
  const UI_HIDE_LOADING_METHOD = 'ui.hideLoading';
@@ -837,6 +838,184 @@ function isMiniProgramDirectoryRef(value) {
837
838
  }
838
839
  return (isMiniProgramPathRootRef(value.root) && (value.relativePath === '.' || isMiniProgramRelativePath(value.relativePath)) && hasOnlyKeys(value, ['kind', 'root', 'relativePath']));
839
840
  }
841
+ /**
842
+ * 判断未知值是否具有文件 descriptor 的最低 wire 结构。
843
+ *
844
+ * @param value - 来自 Host、bridge response、目录列表或其他不可信边界的候选 descriptor。
845
+ * @returns 顶层字段及嵌套文件引用都满足协议结构时返回 `true`,并将 TypeScript 类型收窄为
846
+ * {@link MiniProgramFileDescriptor};否则返回 `false`。
847
+ *
848
+ * @remarks
849
+ * guard 会读取并校验以下四个属性;对通常的对象字面量,它们也是唯一允许的可枚举自有字符串字段:
850
+ *
851
+ * - `kind`:固定为 `'file'`。
852
+ * - `name`:非空字符串。该 guard 不检查空白、`.`、`..`、`/`、反斜杠、控制字符、平台保留名或
853
+ * `name` 是否等于 `ref.relativePath` 的 basename;SDK 创建 `FileHandle` 时还会执行更严格的名称校验。
854
+ * - `mode`:只能是 `'read'` 或 `'readwrite'`。这是 descriptor 声明值,不证明 Host 实际授予了读写权限。
855
+ * - `ref`:必须通过 {@link isMiniProgramFileRef}。
856
+ *
857
+ * `ref` 可以是 `{ kind: 'file', handleId }` 形式的 direct file ref,其中 `handleId` 只要求非空;也可以是
858
+ * `{ kind: 'file', root, relativePath }` 形式的 derived ref。derived root 只能是精确的 sandbox root 或
859
+ * 带非空 `handleId` 的 direct directory root,不能是 file ref 或再次派生的 directory ref;`relativePath`
860
+ * 必须是非空、非绝对、无尾随 `/`、无空段、`.` / `..` 点段、反斜杠或控制字符的相对路径。与目录 ref
861
+ * 不同,文件 ref 不接受 `relativePath: '.'`,因为它必须指向 root 下的具体文件。
862
+ *
863
+ * 顶层 descriptor、`ref` 与 `root` 的额外可枚举自有字符串键都会导致失败,包括值为 `undefined` 的额外键;
864
+ * 非枚举属性、symbol 键和继承属性不在 `Object.keys()` 检查范围内,必需属性本身也不要求是自有或可枚举
865
+ * 属性。该函数不会复制、冻结或规范化输入。
866
+ *
867
+ * 返回 `true` 只建立结构类型收窄,不证明 direct `handleId` 或 root handle 由当前 Runtime 签发、属于当前
868
+ * session/origin、仍然有效且确为预期类型,也不证明文件存在、名称真实、`filesystem` 权限已启用、mode
869
+ * 允许当前操作或 Host 支持对应 capability。实际文件操作仍须由 Runtime 查询 handle registry、执行权限、
870
+ * provenance、目标类型和存在性校验。
871
+ *
872
+ * {@link isMiniProgramFileSystemEntityDescriptor} 使用本 guard 与目录 descriptor guard 识别目录列表项。
873
+ * `files.pickFiles()` 与 `files.saveFile()` 的成功结果要求更窄的 direct file ref,而 sandbox/path API 返回的
874
+ * descriptor 可以使用 derived ref;本 guard 本身不会区分 descriptor 的来源。
875
+ *
876
+ * @example
877
+ * ```ts
878
+ * import { isMiniProgramFileDescriptor } from '@heybox/hb-sdk/protocol';
879
+ *
880
+ * const candidate = {
881
+ * kind: 'file',
882
+ * name: 'report.json',
883
+ * mode: 'readwrite',
884
+ * ref: {
885
+ * kind: 'file',
886
+ * root: { kind: 'sandbox' },
887
+ * relativePath: 'exports/report.json',
888
+ * },
889
+ * };
890
+ * const untrusted: unknown = candidate;
891
+ *
892
+ * if (isMiniProgramFileDescriptor(untrusted)) {
893
+ * // 这里只能安全读取 wire 字段;文件 I/O 仍交给 files API/Runtime。
894
+ * console.log(untrusted.name, untrusted.mode, untrusted.ref);
895
+ * }
896
+ *
897
+ * isMiniProgramFileDescriptor({ ...candidate, size: 1024 }); // false:顶层存在额外键
898
+ * ```
899
+ *
900
+ * @see [MiniProgramFileDescriptor](/reference/symbols/protocol/interfaces/MiniProgramFileDescriptor) descriptor 字段类型。
901
+ * @see [isMiniProgramFileRef](/reference/symbols/protocol/functions/isMiniProgramFileRef) 嵌套 direct/derived 文件引用校验。
902
+ * @see [isMiniProgramFileSystemEntityDescriptor](/reference/symbols/protocol/functions/isMiniProgramFileSystemEntityDescriptor) 文件与目录 descriptor 联合 guard。
903
+ * @see [FileHandle](/reference/symbols/root/interfaces/FileHandle) 校验后由 SDK 建立的文件代理。
904
+ * @see [PickFilesResult](/reference/symbols/protocol/types/PickFilesResult) picker 的 direct-ref 文件结果。
905
+ * @see [SaveFileResult](/reference/symbols/protocol/types/SaveFileResult) save picker 的 direct-ref 文件结果。
906
+ * @since 0.8.0
907
+ */
908
+ function isMiniProgramFileDescriptor(value) {
909
+ return (isRecord$3(value) &&
910
+ value.kind === 'file' &&
911
+ isNonEmptyString$1(value.name) &&
912
+ isFileMode(value.mode) &&
913
+ isMiniProgramFileRef(value.ref) &&
914
+ hasOnlyKeys(value, ['kind', 'name', 'mode', 'ref']));
915
+ }
916
+ /** `media.pick` 结果数组允许的最大项数,与协议 `maxCount` 上限一致。 */
917
+ const MAX_MEDIA_PICK_COUNT = 20;
918
+ /** `media.pick` 单项结果的公共 canonical key 集合。 */
919
+ const MEDIA_PICK_ITEM_KEYS = ['type', 'mimeType', 'size', 'width', 'height', 'previewUrl', 'file'];
920
+ /**
921
+ * 判断未知值是否为单个 `media.pick` 媒体项。
922
+ *
923
+ * @param value - 来自 Host response、bridge result 或其他不可信边界的候选媒体项。
924
+ * @returns 字段组合满足 {@link MediaPickItem} 判别联合的最低结构时返回 `true`,并将 TypeScript 类型
925
+ * 收窄为图片或视频项;否则返回 `false`。
926
+ *
927
+ * @remarks
928
+ * 以 `type` 为判别字段,两个分支都要求 `mimeType` 非空字符串、`size` 为非负安全整数、`width` /
929
+ * `height` 为正安全整数、`previewUrl` 非空字符串,且 `file` 是通过
930
+ * {@link isMiniProgramFileDescriptor} 的 direct file ref(`ref.kind === 'file'` 且带非空 `handleId`),
931
+ * `mode` 必须固定为 `'read'`。图片分支只允许公共 key;视频分支额外要求非负安全整数的 `durationMs`,
932
+ * 并拒绝图片携带该字段。
933
+ *
934
+ * `size` 单位为字节,`width` / `height` 是考虑方向后的显示像素尺寸,`durationMs` 单位为毫秒。
935
+ * 该 guard 只校验结构与数值范围,不证明元数据与 `file` 实际内容一致、`previewUrl` 属于当前会话、
936
+ * `file` 句柄由当前 Runtime 签发或仍在有效期内,也不检查 `media` 权限与 Host 能力。
937
+ *
938
+ * @example
939
+ * ```ts
940
+ * import { isMiniProgramMediaPickItem } from '@heybox/hb-sdk/protocol';
941
+ *
942
+ * const video = {
943
+ * type: 'video',
944
+ * mimeType: 'video/mp4',
945
+ * size: 5_242_880,
946
+ * width: 1280,
947
+ * height: 720,
948
+ * durationMs: 12_000,
949
+ * previewUrl: 'hb-media-preview://session/resource_2',
950
+ * file: { kind: 'file', name: 'clip.mp4', mode: 'read', ref: { kind: 'file', handleId: 'media_handle_2' } },
951
+ * };
952
+ *
953
+ * isMiniProgramMediaPickItem(video); // true
954
+ * isMiniProgramMediaPickItem({ ...video, type: 'image' }); // false:图片不允许 durationMs
955
+ * ```
956
+ *
957
+ * @see [MediaPickItem](/reference/symbols/protocol/types/MediaPickItem) 判别联合定义。
958
+ * @see [isMiniProgramFileDescriptor](/reference/symbols/protocol/functions/isMiniProgramFileDescriptor) 嵌套文件句柄校验。
959
+ * @since 0.9.3
960
+ */
961
+ function isMiniProgramMediaPickItem(value) {
962
+ if (!isRecord$3(value) || (value.type !== 'image' && value.type !== 'video'))
963
+ return false;
964
+ const isVideo = value.type === 'video';
965
+ if (!hasOnlyKeys(value, isVideo ? [...MEDIA_PICK_ITEM_KEYS, 'durationMs'] : MEDIA_PICK_ITEM_KEYS))
966
+ return false;
967
+ if (!isNonEmptyString$1(value.mimeType) ||
968
+ !isNonNegativeSafeInteger(value.size) ||
969
+ !isPositiveSafeInteger$1(value.width) ||
970
+ !isPositiveSafeInteger$1(value.height) ||
971
+ !isNonEmptyString$1(value.previewUrl) ||
972
+ !isDirectFileDescriptor(value.file)) {
973
+ return false;
974
+ }
975
+ return isVideo ? isNonNegativeSafeInteger(value.durationMs) : true;
976
+ }
977
+ /**
978
+ * 判断未知值是否为整批 `media.pick` 结果。
979
+ *
980
+ * @param value - 来自 Host response、bridge result 或其他不可信边界的候选结果。
981
+ * @returns 值为 1–20 项且每项都通过 {@link isMiniProgramMediaPickItem} 的稠密数组时返回 `true`,
982
+ * 并将 TypeScript 类型收窄为 {@link MediaPickResult};否则返回 `false`。
983
+ *
984
+ * @remarks
985
+ * 成功结果必须是非空数组:用户取消或任一项准备失败时走整批错误,不得返回空数组或部分结果。
986
+ * 本 guard 只校验全局上限 20,不校验调用方本次请求的 `maxCount`、相机必须恰好 1 项、项之间是否
987
+ * 重复或顺序,也不确认结果来自当前 request 或会话仍然有效。这些约束由 Runtime 的状态机、可信手势、
988
+ * 权限与句柄注册表继续执行。
989
+ *
990
+ * @example
991
+ * ```ts
992
+ * import { isMiniProgramMediaPickResult } from '@heybox/hb-sdk/protocol';
993
+ *
994
+ * isMiniProgramMediaPickResult([]); // false:取消不是空数组成功
995
+ * isMiniProgramMediaPickResult([imageItem]); // true
996
+ * ```
997
+ *
998
+ * @see [MediaPickResult](/reference/symbols/protocol/types/MediaPickResult) wire 返回类型。
999
+ * @see [isMiniProgramMediaPickItem](/reference/symbols/protocol/functions/isMiniProgramMediaPickItem) 单项结构与数值校验。
1000
+ * @since 0.9.3
1001
+ */
1002
+ function isMiniProgramMediaPickResult(value) {
1003
+ if (!Array.isArray(value) || value.length < 1 || value.length > MAX_MEDIA_PICK_COUNT)
1004
+ return false;
1005
+ for (let index = 0; index < value.length; index += 1) {
1006
+ if (!Object.prototype.hasOwnProperty.call(value, index) || !isMiniProgramMediaPickItem(value[index]))
1007
+ return false;
1008
+ }
1009
+ return true;
1010
+ }
1011
+ function isDirectFileDescriptor(value) {
1012
+ if (!isMiniProgramFileDescriptor(value))
1013
+ return false;
1014
+ // 媒体结果只允许只读 direct 句柄;derived ref、readwrite 与目录引用都不符合媒体契约。
1015
+ if (value.mode !== 'read')
1016
+ return false;
1017
+ return value.ref.kind === 'file' && 'handleId' in value.ref && isNonEmptyString$1(value.ref.handleId);
1018
+ }
840
1019
  /**
841
1020
  * 判断未知值是否为使用普通 `ArrayBuffer` 存储的 `Uint8Array` 字节视图。
842
1021
  *
@@ -1046,9 +1225,15 @@ function isSDKHandshakePayload(value) {
1046
1225
  function isBridgeError(value) {
1047
1226
  return isRecord$3(value) && isNonEmptyString$1(value.code) && typeof value.message === 'string';
1048
1227
  }
1228
+ function isFileMode(value) {
1229
+ return value === 'read' || value === 'readwrite';
1230
+ }
1049
1231
  function isNonNegativeSafeInteger(value) {
1050
1232
  return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
1051
1233
  }
1234
+ function isPositiveSafeInteger$1(value) {
1235
+ return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
1236
+ }
1052
1237
  function isNonNegativeFiniteNumber(value) {
1053
1238
  return typeof value === 'number' && Number.isFinite(value) && value >= 0;
1054
1239
  }
@@ -1070,6 +1255,7 @@ const MINI_PROGRAM_PERMISSION_KEYS = [
1070
1255
  'storage',
1071
1256
  'cloud_storage',
1072
1257
  'filesystem',
1258
+ 'media',
1073
1259
  'clipboard',
1074
1260
  'leaderboard',
1075
1261
  'network',
@@ -7295,7 +7481,7 @@ function createMessageId() {
7295
7481
  /** 构建时替换为当前发布包的实际版本。 */
7296
7482
  const HB_SDK_VERSION = typeof undefined === 'string'
7297
7483
  ? undefined
7298
- : '0.9.3-alpha.6';
7484
+ : '0.9.3';
7299
7485
 
7300
7486
  const RUNTIME_MODES = new Set(['production', 'preview', 'development', 'unknown']);
7301
7487
  const OPERATING_SYSTEM_NAMES = new Set(['android', 'ios', 'ohos', 'windows', 'macos', 'linux', 'unknown']);
@@ -7916,7 +8102,7 @@ function createCloudModule(requester) {
7916
8102
  const registries = new WeakMap();
7917
8103
  const brandedFileSystemValues = new WeakSet();
7918
8104
  const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/;
7919
- function invalidParams$1(message, data) {
8105
+ function invalidParams$2(message, data) {
7920
8106
  return createSDKError('INVALID_PARAMS', message, data);
7921
8107
  }
7922
8108
  function clonePathRootRef(ref) {
@@ -7991,13 +8177,13 @@ function assertSandboxRef(ref) {
7991
8177
  }
7992
8178
  function normalizeCreateOptions(options) {
7993
8179
  if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) {
7994
- throw invalidParams$1('create options 必须是对象');
8180
+ throw invalidParams$2('create options 必须是对象');
7995
8181
  }
7996
8182
  if (options?.recursive !== undefined && typeof options.recursive !== 'boolean') {
7997
- throw invalidParams$1('recursive 必须是 boolean');
8183
+ throw invalidParams$2('recursive 必须是 boolean');
7998
8184
  }
7999
8185
  if (options?.exclusive !== undefined && typeof options.exclusive !== 'boolean') {
8000
- throw invalidParams$1('exclusive 必须是 boolean');
8186
+ throw invalidParams$2('exclusive 必须是 boolean');
8001
8187
  }
8002
8188
  return {
8003
8189
  recursive: options?.recursive ?? false,
@@ -8006,28 +8192,28 @@ function normalizeCreateOptions(options) {
8006
8192
  }
8007
8193
  function normalizeListOptions(options) {
8008
8194
  if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) {
8009
- throw invalidParams$1('list options 必须是对象');
8195
+ throw invalidParams$2('list options 必须是对象');
8010
8196
  }
8011
8197
  if (options?.limit !== undefined && (!Number.isSafeInteger(options.limit) || options.limit <= 0)) {
8012
- throw invalidParams$1('limit 必须是正的 safe integer');
8198
+ throw invalidParams$2('limit 必须是正的 safe integer');
8013
8199
  }
8014
8200
  return options?.limit;
8015
8201
  }
8016
8202
  function normalizeRemoveOptions(options) {
8017
8203
  if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) {
8018
- throw invalidParams$1('remove options 必须是对象');
8204
+ throw invalidParams$2('remove options 必须是对象');
8019
8205
  }
8020
8206
  if (options?.recursive !== undefined && typeof options.recursive !== 'boolean') {
8021
- throw invalidParams$1('recursive 必须是 boolean');
8207
+ throw invalidParams$2('recursive 必须是 boolean');
8022
8208
  }
8023
8209
  return { recursive: options?.recursive ?? false };
8024
8210
  }
8025
8211
  function cloneBytes$1(content) {
8026
8212
  if (!(content instanceof Uint8Array)) {
8027
- throw invalidParams$1('content 必须是 Uint8Array');
8213
+ throw invalidParams$2('content 必须是 Uint8Array');
8028
8214
  }
8029
8215
  if (typeof SharedArrayBuffer !== 'undefined' && content.buffer instanceof SharedArrayBuffer) {
8030
- throw invalidParams$1('content 不能由 SharedArrayBuffer 承载');
8216
+ throw invalidParams$2('content 不能由 SharedArrayBuffer 承载');
8031
8217
  }
8032
8218
  return new Uint8Array(content);
8033
8219
  }
@@ -8045,7 +8231,7 @@ function normalizeStat(stat) {
8045
8231
  /** 校验并返回平台无关的 `/` 分隔逻辑相对路径。 */
8046
8232
  function normalizeRelativePath(value) {
8047
8233
  if (!isMiniProgramRelativePath(value)) {
8048
- throw invalidParams$1('relativePath 必须是规范的 `/` 分隔逻辑相对路径');
8234
+ throw invalidParams$2('relativePath 必须是规范的 `/` 分隔逻辑相对路径');
8049
8235
  }
8050
8236
  return value;
8051
8237
  }
@@ -8112,7 +8298,7 @@ class MiniProgramFileHandleRegistry {
8112
8298
  defineReadonlyValue(handle, 'writeText', async (content) => {
8113
8299
  assertWritable(state.mode);
8114
8300
  if (typeof content !== 'string') {
8115
- throw invalidParams$1('content 必须是 string');
8301
+ throw invalidParams$2('content 必须是 string');
8116
8302
  }
8117
8303
  await this.requester.request(FILE_WRITE_TEXT_METHOD, { target: state.ref, content });
8118
8304
  }, false);
@@ -8214,14 +8400,14 @@ class MiniProgramFileHandleRegistry {
8214
8400
  requireFile(handle) {
8215
8401
  const state = handle && typeof handle === 'object' ? this.fileStates.get(handle) : undefined;
8216
8402
  if (!state) {
8217
- throw invalidParams$1('to 必须是当前 SDK session 创建的 FileHandle');
8403
+ throw invalidParams$2('to 必须是当前 SDK session 创建的 FileHandle');
8218
8404
  }
8219
8405
  return state;
8220
8406
  }
8221
8407
  requireDirectory(handle) {
8222
8408
  const state = handle && typeof handle === 'object' ? this.directoryStates.get(handle) : undefined;
8223
8409
  if (!state) {
8224
- throw invalidParams$1('to 必须是当前 SDK session 创建的 DirectoryHandle');
8410
+ throw invalidParams$2('to 必须是当前 SDK session 创建的 DirectoryHandle');
8225
8411
  }
8226
8412
  return state;
8227
8413
  }
@@ -10859,7 +11045,7 @@ async function upload(requester, options) {
10859
11045
  return response;
10860
11046
  }
10861
11047
 
10862
- function invalidParams(message) {
11048
+ function invalidParams$1(message) {
10863
11049
  return createSDKError('INVALID_PARAMS', message);
10864
11050
  }
10865
11051
  function normalizeMode(mode) {
@@ -10867,13 +11053,13 @@ function normalizeMode(mode) {
10867
11053
  return undefined;
10868
11054
  }
10869
11055
  if (mode !== 'read' && mode !== 'readwrite') {
10870
- throw invalidParams('mode 必须是 read 或 readwrite');
11056
+ throw invalidParams$1('mode 必须是 read 或 readwrite');
10871
11057
  }
10872
11058
  return mode;
10873
11059
  }
10874
- function assertOptionsObject(value, name) {
11060
+ function assertOptionsObject$1(value, name) {
10875
11061
  if (value !== undefined && (!value || typeof value !== 'object' || Array.isArray(value))) {
10876
- throw invalidParams(`${name} 必须是对象`);
11062
+ throw invalidParams$1(`${name} 必须是对象`);
10877
11063
  }
10878
11064
  }
10879
11065
  function normalizeAccept(accept) {
@@ -10881,20 +11067,20 @@ function normalizeAccept(accept) {
10881
11067
  return undefined;
10882
11068
  }
10883
11069
  if (!Array.isArray(accept)) {
10884
- throw invalidParams('accept 必须是扩展名数组');
11070
+ throw invalidParams$1('accept 必须是扩展名数组');
10885
11071
  }
10886
11072
  return accept.map((extension) => {
10887
11073
  if (typeof extension !== 'string' ||
10888
11074
  !/^\.[a-z0-9][a-z0-9._-]*$/i.test(extension)) {
10889
- throw invalidParams('accept 只能包含规范扩展名,例如 .json');
11075
+ throw invalidParams$1('accept 只能包含规范扩展名,例如 .json');
10890
11076
  }
10891
11077
  return extension;
10892
11078
  });
10893
11079
  }
10894
11080
  function normalizePickFilesOptions(options) {
10895
- assertOptionsObject(options, 'pickFiles options');
11081
+ assertOptionsObject$1(options, 'pickFiles options');
10896
11082
  if (options?.multiple !== undefined && typeof options.multiple !== 'boolean') {
10897
- throw invalidParams('multiple 必须是 boolean');
11083
+ throw invalidParams$1('multiple 必须是 boolean');
10898
11084
  }
10899
11085
  const mode = normalizeMode(options?.mode);
10900
11086
  const accept = normalizeAccept(options?.accept);
@@ -10908,21 +11094,21 @@ function normalizePickFilesOptions(options) {
10908
11094
  };
10909
11095
  }
10910
11096
  function normalizePickDirectoryOptions(options) {
10911
- assertOptionsObject(options, 'pickDirectory options');
11097
+ assertOptionsObject$1(options, 'pickDirectory options');
10912
11098
  const mode = normalizeMode(options?.mode);
10913
11099
  return options === undefined ? undefined : mode === undefined ? {} : { mode };
10914
11100
  }
10915
11101
  function normalizeSaveFileOptions(options) {
10916
- assertOptionsObject(options, 'saveFile options');
11102
+ assertOptionsObject$1(options, 'saveFile options');
10917
11103
  if (!options || typeof options.suggestedName !== 'string') {
10918
- throw invalidParams('suggestedName 必须是非空字符串');
11104
+ throw invalidParams$1('suggestedName 必须是非空字符串');
10919
11105
  }
10920
11106
  const suggestedName = normalizeRelativePath(options.suggestedName);
10921
11107
  if (suggestedName.includes('/')) {
10922
- throw invalidParams('suggestedName 必须是 basename');
11108
+ throw invalidParams$1('suggestedName 必须是 basename');
10923
11109
  }
10924
11110
  if ('accept' in options) {
10925
- throw invalidParams('saveFile V1 不支持 accept');
11111
+ throw invalidParams$1('saveFile V1 不支持 accept');
10926
11112
  }
10927
11113
  return { suggestedName };
10928
11114
  }
@@ -10964,6 +11150,97 @@ function createFilesModule(requester) {
10964
11150
  });
10965
11151
  }
10966
11152
 
11153
+ const MEDIA_SOURCES = ['album', 'camera'];
11154
+ const MEDIA_SELECTION_TYPES = ['image', 'video', 'all'];
11155
+ const MEDIA_PICK_MAX_COUNT = 20;
11156
+ const MEDIA_PICK_OPTION_KEYS = ['source', 'mediaType', 'maxCount'];
11157
+ function invalidParams(message) {
11158
+ return createSDKError('INVALID_PARAMS', message);
11159
+ }
11160
+ function assertOptionsObject(value) {
11161
+ if (value !== undefined && (!value || typeof value !== 'object' || Array.isArray(value))) {
11162
+ throw invalidParams('pick options 必须是对象');
11163
+ }
11164
+ }
11165
+ function assertKnownKeys(options) {
11166
+ for (const key of Object.keys(options)) {
11167
+ if (!MEDIA_PICK_OPTION_KEYS.includes(key)) {
11168
+ throw invalidParams(`pick options 包含未知字段 ${key}`);
11169
+ }
11170
+ }
11171
+ }
11172
+ function normalizeSource(value) {
11173
+ if (value === undefined) {
11174
+ return 'album';
11175
+ }
11176
+ if (typeof value !== 'string' || !MEDIA_SOURCES.includes(value)) {
11177
+ throw invalidParams('source 必须是 album 或 camera');
11178
+ }
11179
+ return value;
11180
+ }
11181
+ function normalizeSelectionType(value) {
11182
+ if (value === undefined) {
11183
+ return 'all';
11184
+ }
11185
+ if (typeof value !== 'string' || !MEDIA_SELECTION_TYPES.includes(value)) {
11186
+ throw invalidParams('mediaType 必须是 image、video 或 all');
11187
+ }
11188
+ return value;
11189
+ }
11190
+ function normalizeMaxCount(value, source) {
11191
+ if (value === undefined) {
11192
+ return 1;
11193
+ }
11194
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 1 || value > MEDIA_PICK_MAX_COUNT) {
11195
+ throw invalidParams(`maxCount 必须是 1 到 ${MEDIA_PICK_MAX_COUNT} 的安全整数`);
11196
+ }
11197
+ if (source === 'camera' && value > 1) {
11198
+ throw invalidParams('camera 的 maxCount 只能是 1');
11199
+ }
11200
+ return value;
11201
+ }
11202
+ /** 把公开选项收窄为 wire payload,缺省值显式补齐且不携带 undefined 键。 */
11203
+ function normalizePickOptions(options) {
11204
+ assertOptionsObject(options);
11205
+ if (options === undefined) {
11206
+ return { source: 'album', mediaType: 'all', maxCount: 1 };
11207
+ }
11208
+ const raw = options;
11209
+ assertKnownKeys(raw);
11210
+ const source = normalizeSource(raw.source);
11211
+ const mediaType = normalizeSelectionType(raw.mediaType);
11212
+ const maxCount = normalizeMaxCount(raw.maxCount, source);
11213
+ return { source, mediaType, maxCount };
11214
+ }
11215
+ /** 创建当前 SDK session 的 media 模块。 */
11216
+ function createMediaModule(requester) {
11217
+ const registry = getFileHandleRegistry(requester);
11218
+ return Object.freeze({
11219
+ async pick(options) {
11220
+ const payload = normalizePickOptions(options);
11221
+ // 图库选择与拍摄等待用户确认,不使用普通请求的固定超时;销毁仍沿用 bridge cancel。
11222
+ const result = await requester.requestOperation(MEDIA_PICK_METHOD, payload);
11223
+ if (!isMiniProgramMediaPickResult(result)) {
11224
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的媒体 picker 结果');
11225
+ }
11226
+ if (result.length > payload.maxCount) {
11227
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的媒体项数量超过本次请求上限');
11228
+ }
11229
+ if (payload.source === 'camera' && result.length !== 1) {
11230
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的相机结果必须恰好一项');
11231
+ }
11232
+ const items = result.map((item) => {
11233
+ const handle = registry.createDirectFile(item.file);
11234
+ if (handle.mode !== 'read') {
11235
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的媒体 File mode 不是 read');
11236
+ }
11237
+ return Object.freeze({ ...item, file: handle });
11238
+ });
11239
+ return Object.freeze(items);
11240
+ },
11241
+ });
11242
+ }
11243
+
10967
11244
  /**
10968
11245
  * 获取当前小程序窗口信息。
10969
11246
  *
@@ -11349,6 +11626,8 @@ class MiniProgramSDK {
11349
11626
  network;
11350
11627
  /** 受控文件与目录能力。 */
11351
11628
  files;
11629
+ /** 受控媒体选择能力。 */
11630
+ media;
11352
11631
  /** UI 交互相关开放能力。 */
11353
11632
  ui;
11354
11633
  /** 设备相关开放能力。 */
@@ -11373,6 +11652,7 @@ class MiniProgramSDK {
11373
11652
  this.viewport = createViewportModule(this.client);
11374
11653
  this.storage = createStorageModule(this.client);
11375
11654
  this.files = createFilesModule(this.client);
11655
+ this.media = createMediaModule(this.client);
11376
11656
  this.network = createNetworkModule(this.client);
11377
11657
  this.ui = createUiModule(this.client);
11378
11658
  this.device = createDeviceModule(this.client);
@@ -11917,6 +12197,10 @@ const files = {
11917
12197
  pickDirectory: (options) => getDefaultSDK().files.pickDirectory(options),
11918
12198
  saveFile: (options) => getDefaultSDK().files.saveFile(options),
11919
12199
  };
12200
+ /** 默认 SDK 实例的 media 模块。 */
12201
+ const media = {
12202
+ pick: (options) => getDefaultSDK().media.pick(options),
12203
+ };
11920
12204
  /** 默认 SDK 实例的 UI 模块。 */
11921
12205
  const ui = {
11922
12206
  showToast: (options) => getDefaultSDK().ui.showToast(options),
@@ -11983,6 +12267,7 @@ const hbSDK = {
11983
12267
  viewport,
11984
12268
  storage,
11985
12269
  files,
12270
+ media,
11986
12271
  network,
11987
12272
  ui,
11988
12273
  device,
@@ -11993,4 +12278,4 @@ const hbSDK = {
11993
12278
  workspaceCards,
11994
12279
  };
11995
12280
 
11996
- export { HbMiniProgramNetworkError, HbMiniProgramSDKError, USER_INFO_AUTHORIZATION_SCOPES, auth, cloud, companion, hbSDK as default, device, environment, files, getHandshakeState, navigation, network, off, on, onHandshakeStateChange, share, storage, ui, user, viewport, workspaceCards };
12281
+ export { HbMiniProgramNetworkError, HbMiniProgramSDKError, USER_INFO_AUTHORIZATION_SCOPES, auth, cloud, companion, hbSDK as default, device, environment, files, getHandshakeState, media, navigation, network, off, on, onHandshakeStateChange, share, storage, ui, user, viewport, workspaceCards };
@@ -14,6 +14,7 @@ const MINI_PROGRAM_PERMISSION_KEYS = [
14
14
  'storage',
15
15
  'cloud_storage',
16
16
  'filesystem',
17
+ 'media',
17
18
  'clipboard',
18
19
  'leaderboard',
19
20
  'network',
@@ -100,6 +101,16 @@ const MINI_PROGRAM_PERMISSION_CATALOG = [
100
101
  ],
101
102
  conditions: ['外部文件继续要求有效的用户选择、会话句柄和匹配的读写模式。'],
102
103
  },
104
+ {
105
+ key: 'media',
106
+ name: '媒体',
107
+ description: '通过系统图库或相机选择图片、视频。',
108
+ access: 'declaration',
109
+ risk: 'high',
110
+ config: {},
111
+ methods: ['media.pick'],
112
+ conditions: ['图库授权范围仅限本次用户选择的媒体,不包含任意文件或沙盒访问权;继续执行可信手势、Host 能力与会话资源规则。'],
113
+ },
103
114
  {
104
115
  key: 'clipboard',
105
116
  name: '剪贴板',
@@ -12,6 +12,7 @@ const MINI_PROGRAM_PERMISSION_KEYS = [
12
12
  'storage',
13
13
  'cloud_storage',
14
14
  'filesystem',
15
+ 'media',
15
16
  'clipboard',
16
17
  'leaderboard',
17
18
  'network',
@@ -98,6 +99,16 @@ const MINI_PROGRAM_PERMISSION_CATALOG = [
98
99
  ],
99
100
  conditions: ['外部文件继续要求有效的用户选择、会话句柄和匹配的读写模式。'],
100
101
  },
102
+ {
103
+ key: 'media',
104
+ name: '媒体',
105
+ description: '通过系统图库或相机选择图片、视频。',
106
+ access: 'declaration',
107
+ risk: 'high',
108
+ config: {},
109
+ methods: ['media.pick'],
110
+ conditions: ['图库授权范围仅限本次用户选择的媒体,不包含任意文件或沙盒访问权;继续执行可信手势、Host 能力与会话资源规则。'],
111
+ },
101
112
  {
102
113
  key: 'clipboard',
103
114
  name: '剪贴板',