@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.cjs.js CHANGED
@@ -444,6 +444,7 @@ const DIRECTORY_CREATE_METHOD = 'directory.create';
444
444
  const DIRECTORY_REMOVE_METHOD = 'directory.remove';
445
445
  const DIRECTORY_EXISTS_METHOD = 'directory.exists';
446
446
  const DIRECTORY_LIST_METHOD = 'directory.list';
447
+ const MEDIA_PICK_METHOD = 'media.pick';
447
448
  const UI_SHOW_TOAST_METHOD = 'ui.showToast';
448
449
  const UI_SHOW_LOADING_METHOD = 'ui.showLoading';
449
450
  const UI_HIDE_LOADING_METHOD = 'ui.hideLoading';
@@ -841,6 +842,184 @@ function isMiniProgramDirectoryRef(value) {
841
842
  }
842
843
  return (isMiniProgramPathRootRef(value.root) && (value.relativePath === '.' || isMiniProgramRelativePath(value.relativePath)) && hasOnlyKeys(value, ['kind', 'root', 'relativePath']));
843
844
  }
845
+ /**
846
+ * 判断未知值是否具有文件 descriptor 的最低 wire 结构。
847
+ *
848
+ * @param value - 来自 Host、bridge response、目录列表或其他不可信边界的候选 descriptor。
849
+ * @returns 顶层字段及嵌套文件引用都满足协议结构时返回 `true`,并将 TypeScript 类型收窄为
850
+ * {@link MiniProgramFileDescriptor};否则返回 `false`。
851
+ *
852
+ * @remarks
853
+ * guard 会读取并校验以下四个属性;对通常的对象字面量,它们也是唯一允许的可枚举自有字符串字段:
854
+ *
855
+ * - `kind`:固定为 `'file'`。
856
+ * - `name`:非空字符串。该 guard 不检查空白、`.`、`..`、`/`、反斜杠、控制字符、平台保留名或
857
+ * `name` 是否等于 `ref.relativePath` 的 basename;SDK 创建 `FileHandle` 时还会执行更严格的名称校验。
858
+ * - `mode`:只能是 `'read'` 或 `'readwrite'`。这是 descriptor 声明值,不证明 Host 实际授予了读写权限。
859
+ * - `ref`:必须通过 {@link isMiniProgramFileRef}。
860
+ *
861
+ * `ref` 可以是 `{ kind: 'file', handleId }` 形式的 direct file ref,其中 `handleId` 只要求非空;也可以是
862
+ * `{ kind: 'file', root, relativePath }` 形式的 derived ref。derived root 只能是精确的 sandbox root 或
863
+ * 带非空 `handleId` 的 direct directory root,不能是 file ref 或再次派生的 directory ref;`relativePath`
864
+ * 必须是非空、非绝对、无尾随 `/`、无空段、`.` / `..` 点段、反斜杠或控制字符的相对路径。与目录 ref
865
+ * 不同,文件 ref 不接受 `relativePath: '.'`,因为它必须指向 root 下的具体文件。
866
+ *
867
+ * 顶层 descriptor、`ref` 与 `root` 的额外可枚举自有字符串键都会导致失败,包括值为 `undefined` 的额外键;
868
+ * 非枚举属性、symbol 键和继承属性不在 `Object.keys()` 检查范围内,必需属性本身也不要求是自有或可枚举
869
+ * 属性。该函数不会复制、冻结或规范化输入。
870
+ *
871
+ * 返回 `true` 只建立结构类型收窄,不证明 direct `handleId` 或 root handle 由当前 Runtime 签发、属于当前
872
+ * session/origin、仍然有效且确为预期类型,也不证明文件存在、名称真实、`filesystem` 权限已启用、mode
873
+ * 允许当前操作或 Host 支持对应 capability。实际文件操作仍须由 Runtime 查询 handle registry、执行权限、
874
+ * provenance、目标类型和存在性校验。
875
+ *
876
+ * {@link isMiniProgramFileSystemEntityDescriptor} 使用本 guard 与目录 descriptor guard 识别目录列表项。
877
+ * `files.pickFiles()` 与 `files.saveFile()` 的成功结果要求更窄的 direct file ref,而 sandbox/path API 返回的
878
+ * descriptor 可以使用 derived ref;本 guard 本身不会区分 descriptor 的来源。
879
+ *
880
+ * @example
881
+ * ```ts
882
+ * import { isMiniProgramFileDescriptor } from '@heybox/hb-sdk/protocol';
883
+ *
884
+ * const candidate = {
885
+ * kind: 'file',
886
+ * name: 'report.json',
887
+ * mode: 'readwrite',
888
+ * ref: {
889
+ * kind: 'file',
890
+ * root: { kind: 'sandbox' },
891
+ * relativePath: 'exports/report.json',
892
+ * },
893
+ * };
894
+ * const untrusted: unknown = candidate;
895
+ *
896
+ * if (isMiniProgramFileDescriptor(untrusted)) {
897
+ * // 这里只能安全读取 wire 字段;文件 I/O 仍交给 files API/Runtime。
898
+ * console.log(untrusted.name, untrusted.mode, untrusted.ref);
899
+ * }
900
+ *
901
+ * isMiniProgramFileDescriptor({ ...candidate, size: 1024 }); // false:顶层存在额外键
902
+ * ```
903
+ *
904
+ * @see [MiniProgramFileDescriptor](/reference/symbols/protocol/interfaces/MiniProgramFileDescriptor) descriptor 字段类型。
905
+ * @see [isMiniProgramFileRef](/reference/symbols/protocol/functions/isMiniProgramFileRef) 嵌套 direct/derived 文件引用校验。
906
+ * @see [isMiniProgramFileSystemEntityDescriptor](/reference/symbols/protocol/functions/isMiniProgramFileSystemEntityDescriptor) 文件与目录 descriptor 联合 guard。
907
+ * @see [FileHandle](/reference/symbols/root/interfaces/FileHandle) 校验后由 SDK 建立的文件代理。
908
+ * @see [PickFilesResult](/reference/symbols/protocol/types/PickFilesResult) picker 的 direct-ref 文件结果。
909
+ * @see [SaveFileResult](/reference/symbols/protocol/types/SaveFileResult) save picker 的 direct-ref 文件结果。
910
+ * @since 0.8.0
911
+ */
912
+ function isMiniProgramFileDescriptor(value) {
913
+ return (isRecord$3(value) &&
914
+ value.kind === 'file' &&
915
+ isNonEmptyString$1(value.name) &&
916
+ isFileMode(value.mode) &&
917
+ isMiniProgramFileRef(value.ref) &&
918
+ hasOnlyKeys(value, ['kind', 'name', 'mode', 'ref']));
919
+ }
920
+ /** `media.pick` 结果数组允许的最大项数,与协议 `maxCount` 上限一致。 */
921
+ const MAX_MEDIA_PICK_COUNT = 20;
922
+ /** `media.pick` 单项结果的公共 canonical key 集合。 */
923
+ const MEDIA_PICK_ITEM_KEYS = ['type', 'mimeType', 'size', 'width', 'height', 'previewUrl', 'file'];
924
+ /**
925
+ * 判断未知值是否为单个 `media.pick` 媒体项。
926
+ *
927
+ * @param value - 来自 Host response、bridge result 或其他不可信边界的候选媒体项。
928
+ * @returns 字段组合满足 {@link MediaPickItem} 判别联合的最低结构时返回 `true`,并将 TypeScript 类型
929
+ * 收窄为图片或视频项;否则返回 `false`。
930
+ *
931
+ * @remarks
932
+ * 以 `type` 为判别字段,两个分支都要求 `mimeType` 非空字符串、`size` 为非负安全整数、`width` /
933
+ * `height` 为正安全整数、`previewUrl` 非空字符串,且 `file` 是通过
934
+ * {@link isMiniProgramFileDescriptor} 的 direct file ref(`ref.kind === 'file'` 且带非空 `handleId`),
935
+ * `mode` 必须固定为 `'read'`。图片分支只允许公共 key;视频分支额外要求非负安全整数的 `durationMs`,
936
+ * 并拒绝图片携带该字段。
937
+ *
938
+ * `size` 单位为字节,`width` / `height` 是考虑方向后的显示像素尺寸,`durationMs` 单位为毫秒。
939
+ * 该 guard 只校验结构与数值范围,不证明元数据与 `file` 实际内容一致、`previewUrl` 属于当前会话、
940
+ * `file` 句柄由当前 Runtime 签发或仍在有效期内,也不检查 `media` 权限与 Host 能力。
941
+ *
942
+ * @example
943
+ * ```ts
944
+ * import { isMiniProgramMediaPickItem } from '@heybox/hb-sdk/protocol';
945
+ *
946
+ * const video = {
947
+ * type: 'video',
948
+ * mimeType: 'video/mp4',
949
+ * size: 5_242_880,
950
+ * width: 1280,
951
+ * height: 720,
952
+ * durationMs: 12_000,
953
+ * previewUrl: 'hb-media-preview://session/resource_2',
954
+ * file: { kind: 'file', name: 'clip.mp4', mode: 'read', ref: { kind: 'file', handleId: 'media_handle_2' } },
955
+ * };
956
+ *
957
+ * isMiniProgramMediaPickItem(video); // true
958
+ * isMiniProgramMediaPickItem({ ...video, type: 'image' }); // false:图片不允许 durationMs
959
+ * ```
960
+ *
961
+ * @see [MediaPickItem](/reference/symbols/protocol/types/MediaPickItem) 判别联合定义。
962
+ * @see [isMiniProgramFileDescriptor](/reference/symbols/protocol/functions/isMiniProgramFileDescriptor) 嵌套文件句柄校验。
963
+ * @since 0.9.3
964
+ */
965
+ function isMiniProgramMediaPickItem(value) {
966
+ if (!isRecord$3(value) || (value.type !== 'image' && value.type !== 'video'))
967
+ return false;
968
+ const isVideo = value.type === 'video';
969
+ if (!hasOnlyKeys(value, isVideo ? [...MEDIA_PICK_ITEM_KEYS, 'durationMs'] : MEDIA_PICK_ITEM_KEYS))
970
+ return false;
971
+ if (!isNonEmptyString$1(value.mimeType) ||
972
+ !isNonNegativeSafeInteger(value.size) ||
973
+ !isPositiveSafeInteger$1(value.width) ||
974
+ !isPositiveSafeInteger$1(value.height) ||
975
+ !isNonEmptyString$1(value.previewUrl) ||
976
+ !isDirectFileDescriptor(value.file)) {
977
+ return false;
978
+ }
979
+ return isVideo ? isNonNegativeSafeInteger(value.durationMs) : true;
980
+ }
981
+ /**
982
+ * 判断未知值是否为整批 `media.pick` 结果。
983
+ *
984
+ * @param value - 来自 Host response、bridge result 或其他不可信边界的候选结果。
985
+ * @returns 值为 1–20 项且每项都通过 {@link isMiniProgramMediaPickItem} 的稠密数组时返回 `true`,
986
+ * 并将 TypeScript 类型收窄为 {@link MediaPickResult};否则返回 `false`。
987
+ *
988
+ * @remarks
989
+ * 成功结果必须是非空数组:用户取消或任一项准备失败时走整批错误,不得返回空数组或部分结果。
990
+ * 本 guard 只校验全局上限 20,不校验调用方本次请求的 `maxCount`、相机必须恰好 1 项、项之间是否
991
+ * 重复或顺序,也不确认结果来自当前 request 或会话仍然有效。这些约束由 Runtime 的状态机、可信手势、
992
+ * 权限与句柄注册表继续执行。
993
+ *
994
+ * @example
995
+ * ```ts
996
+ * import { isMiniProgramMediaPickResult } from '@heybox/hb-sdk/protocol';
997
+ *
998
+ * isMiniProgramMediaPickResult([]); // false:取消不是空数组成功
999
+ * isMiniProgramMediaPickResult([imageItem]); // true
1000
+ * ```
1001
+ *
1002
+ * @see [MediaPickResult](/reference/symbols/protocol/types/MediaPickResult) wire 返回类型。
1003
+ * @see [isMiniProgramMediaPickItem](/reference/symbols/protocol/functions/isMiniProgramMediaPickItem) 单项结构与数值校验。
1004
+ * @since 0.9.3
1005
+ */
1006
+ function isMiniProgramMediaPickResult(value) {
1007
+ if (!Array.isArray(value) || value.length < 1 || value.length > MAX_MEDIA_PICK_COUNT)
1008
+ return false;
1009
+ for (let index = 0; index < value.length; index += 1) {
1010
+ if (!Object.prototype.hasOwnProperty.call(value, index) || !isMiniProgramMediaPickItem(value[index]))
1011
+ return false;
1012
+ }
1013
+ return true;
1014
+ }
1015
+ function isDirectFileDescriptor(value) {
1016
+ if (!isMiniProgramFileDescriptor(value))
1017
+ return false;
1018
+ // 媒体结果只允许只读 direct 句柄;derived ref、readwrite 与目录引用都不符合媒体契约。
1019
+ if (value.mode !== 'read')
1020
+ return false;
1021
+ return value.ref.kind === 'file' && 'handleId' in value.ref && isNonEmptyString$1(value.ref.handleId);
1022
+ }
844
1023
  /**
845
1024
  * 判断未知值是否为使用普通 `ArrayBuffer` 存储的 `Uint8Array` 字节视图。
846
1025
  *
@@ -1050,9 +1229,15 @@ function isSDKHandshakePayload(value) {
1050
1229
  function isBridgeError(value) {
1051
1230
  return isRecord$3(value) && isNonEmptyString$1(value.code) && typeof value.message === 'string';
1052
1231
  }
1232
+ function isFileMode(value) {
1233
+ return value === 'read' || value === 'readwrite';
1234
+ }
1053
1235
  function isNonNegativeSafeInteger(value) {
1054
1236
  return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
1055
1237
  }
1238
+ function isPositiveSafeInteger$1(value) {
1239
+ return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
1240
+ }
1056
1241
  function isNonNegativeFiniteNumber(value) {
1057
1242
  return typeof value === 'number' && Number.isFinite(value) && value >= 0;
1058
1243
  }
@@ -1074,6 +1259,7 @@ const MINI_PROGRAM_PERMISSION_KEYS = [
1074
1259
  'storage',
1075
1260
  'cloud_storage',
1076
1261
  'filesystem',
1262
+ 'media',
1077
1263
  'clipboard',
1078
1264
  'leaderboard',
1079
1265
  'network',
@@ -7299,7 +7485,7 @@ function createMessageId() {
7299
7485
  /** 构建时替换为当前发布包的实际版本。 */
7300
7486
  const HB_SDK_VERSION = typeof undefined === 'string'
7301
7487
  ? undefined
7302
- : '0.9.3-alpha.6';
7488
+ : '0.9.3';
7303
7489
 
7304
7490
  const RUNTIME_MODES = new Set(['production', 'preview', 'development', 'unknown']);
7305
7491
  const OPERATING_SYSTEM_NAMES = new Set(['android', 'ios', 'ohos', 'windows', 'macos', 'linux', 'unknown']);
@@ -7920,7 +8106,7 @@ function createCloudModule(requester) {
7920
8106
  const registries = new WeakMap();
7921
8107
  const brandedFileSystemValues = new WeakSet();
7922
8108
  const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/;
7923
- function invalidParams$1(message, data) {
8109
+ function invalidParams$2(message, data) {
7924
8110
  return createSDKError('INVALID_PARAMS', message, data);
7925
8111
  }
7926
8112
  function clonePathRootRef(ref) {
@@ -7995,13 +8181,13 @@ function assertSandboxRef(ref) {
7995
8181
  }
7996
8182
  function normalizeCreateOptions(options) {
7997
8183
  if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) {
7998
- throw invalidParams$1('create options 必须是对象');
8184
+ throw invalidParams$2('create options 必须是对象');
7999
8185
  }
8000
8186
  if (options?.recursive !== undefined && typeof options.recursive !== 'boolean') {
8001
- throw invalidParams$1('recursive 必须是 boolean');
8187
+ throw invalidParams$2('recursive 必须是 boolean');
8002
8188
  }
8003
8189
  if (options?.exclusive !== undefined && typeof options.exclusive !== 'boolean') {
8004
- throw invalidParams$1('exclusive 必须是 boolean');
8190
+ throw invalidParams$2('exclusive 必须是 boolean');
8005
8191
  }
8006
8192
  return {
8007
8193
  recursive: options?.recursive ?? false,
@@ -8010,28 +8196,28 @@ function normalizeCreateOptions(options) {
8010
8196
  }
8011
8197
  function normalizeListOptions(options) {
8012
8198
  if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) {
8013
- throw invalidParams$1('list options 必须是对象');
8199
+ throw invalidParams$2('list options 必须是对象');
8014
8200
  }
8015
8201
  if (options?.limit !== undefined && (!Number.isSafeInteger(options.limit) || options.limit <= 0)) {
8016
- throw invalidParams$1('limit 必须是正的 safe integer');
8202
+ throw invalidParams$2('limit 必须是正的 safe integer');
8017
8203
  }
8018
8204
  return options?.limit;
8019
8205
  }
8020
8206
  function normalizeRemoveOptions(options) {
8021
8207
  if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) {
8022
- throw invalidParams$1('remove options 必须是对象');
8208
+ throw invalidParams$2('remove options 必须是对象');
8023
8209
  }
8024
8210
  if (options?.recursive !== undefined && typeof options.recursive !== 'boolean') {
8025
- throw invalidParams$1('recursive 必须是 boolean');
8211
+ throw invalidParams$2('recursive 必须是 boolean');
8026
8212
  }
8027
8213
  return { recursive: options?.recursive ?? false };
8028
8214
  }
8029
8215
  function cloneBytes$1(content) {
8030
8216
  if (!(content instanceof Uint8Array)) {
8031
- throw invalidParams$1('content 必须是 Uint8Array');
8217
+ throw invalidParams$2('content 必须是 Uint8Array');
8032
8218
  }
8033
8219
  if (typeof SharedArrayBuffer !== 'undefined' && content.buffer instanceof SharedArrayBuffer) {
8034
- throw invalidParams$1('content 不能由 SharedArrayBuffer 承载');
8220
+ throw invalidParams$2('content 不能由 SharedArrayBuffer 承载');
8035
8221
  }
8036
8222
  return new Uint8Array(content);
8037
8223
  }
@@ -8049,7 +8235,7 @@ function normalizeStat(stat) {
8049
8235
  /** 校验并返回平台无关的 `/` 分隔逻辑相对路径。 */
8050
8236
  function normalizeRelativePath(value) {
8051
8237
  if (!isMiniProgramRelativePath(value)) {
8052
- throw invalidParams$1('relativePath 必须是规范的 `/` 分隔逻辑相对路径');
8238
+ throw invalidParams$2('relativePath 必须是规范的 `/` 分隔逻辑相对路径');
8053
8239
  }
8054
8240
  return value;
8055
8241
  }
@@ -8116,7 +8302,7 @@ class MiniProgramFileHandleRegistry {
8116
8302
  defineReadonlyValue(handle, 'writeText', async (content) => {
8117
8303
  assertWritable(state.mode);
8118
8304
  if (typeof content !== 'string') {
8119
- throw invalidParams$1('content 必须是 string');
8305
+ throw invalidParams$2('content 必须是 string');
8120
8306
  }
8121
8307
  await this.requester.request(FILE_WRITE_TEXT_METHOD, { target: state.ref, content });
8122
8308
  }, false);
@@ -8218,14 +8404,14 @@ class MiniProgramFileHandleRegistry {
8218
8404
  requireFile(handle) {
8219
8405
  const state = handle && typeof handle === 'object' ? this.fileStates.get(handle) : undefined;
8220
8406
  if (!state) {
8221
- throw invalidParams$1('to 必须是当前 SDK session 创建的 FileHandle');
8407
+ throw invalidParams$2('to 必须是当前 SDK session 创建的 FileHandle');
8222
8408
  }
8223
8409
  return state;
8224
8410
  }
8225
8411
  requireDirectory(handle) {
8226
8412
  const state = handle && typeof handle === 'object' ? this.directoryStates.get(handle) : undefined;
8227
8413
  if (!state) {
8228
- throw invalidParams$1('to 必须是当前 SDK session 创建的 DirectoryHandle');
8414
+ throw invalidParams$2('to 必须是当前 SDK session 创建的 DirectoryHandle');
8229
8415
  }
8230
8416
  return state;
8231
8417
  }
@@ -10863,7 +11049,7 @@ async function upload(requester, options) {
10863
11049
  return response;
10864
11050
  }
10865
11051
 
10866
- function invalidParams(message) {
11052
+ function invalidParams$1(message) {
10867
11053
  return createSDKError('INVALID_PARAMS', message);
10868
11054
  }
10869
11055
  function normalizeMode(mode) {
@@ -10871,13 +11057,13 @@ function normalizeMode(mode) {
10871
11057
  return undefined;
10872
11058
  }
10873
11059
  if (mode !== 'read' && mode !== 'readwrite') {
10874
- throw invalidParams('mode 必须是 read 或 readwrite');
11060
+ throw invalidParams$1('mode 必须是 read 或 readwrite');
10875
11061
  }
10876
11062
  return mode;
10877
11063
  }
10878
- function assertOptionsObject(value, name) {
11064
+ function assertOptionsObject$1(value, name) {
10879
11065
  if (value !== undefined && (!value || typeof value !== 'object' || Array.isArray(value))) {
10880
- throw invalidParams(`${name} 必须是对象`);
11066
+ throw invalidParams$1(`${name} 必须是对象`);
10881
11067
  }
10882
11068
  }
10883
11069
  function normalizeAccept(accept) {
@@ -10885,20 +11071,20 @@ function normalizeAccept(accept) {
10885
11071
  return undefined;
10886
11072
  }
10887
11073
  if (!Array.isArray(accept)) {
10888
- throw invalidParams('accept 必须是扩展名数组');
11074
+ throw invalidParams$1('accept 必须是扩展名数组');
10889
11075
  }
10890
11076
  return accept.map((extension) => {
10891
11077
  if (typeof extension !== 'string' ||
10892
11078
  !/^\.[a-z0-9][a-z0-9._-]*$/i.test(extension)) {
10893
- throw invalidParams('accept 只能包含规范扩展名,例如 .json');
11079
+ throw invalidParams$1('accept 只能包含规范扩展名,例如 .json');
10894
11080
  }
10895
11081
  return extension;
10896
11082
  });
10897
11083
  }
10898
11084
  function normalizePickFilesOptions(options) {
10899
- assertOptionsObject(options, 'pickFiles options');
11085
+ assertOptionsObject$1(options, 'pickFiles options');
10900
11086
  if (options?.multiple !== undefined && typeof options.multiple !== 'boolean') {
10901
- throw invalidParams('multiple 必须是 boolean');
11087
+ throw invalidParams$1('multiple 必须是 boolean');
10902
11088
  }
10903
11089
  const mode = normalizeMode(options?.mode);
10904
11090
  const accept = normalizeAccept(options?.accept);
@@ -10912,21 +11098,21 @@ function normalizePickFilesOptions(options) {
10912
11098
  };
10913
11099
  }
10914
11100
  function normalizePickDirectoryOptions(options) {
10915
- assertOptionsObject(options, 'pickDirectory options');
11101
+ assertOptionsObject$1(options, 'pickDirectory options');
10916
11102
  const mode = normalizeMode(options?.mode);
10917
11103
  return options === undefined ? undefined : mode === undefined ? {} : { mode };
10918
11104
  }
10919
11105
  function normalizeSaveFileOptions(options) {
10920
- assertOptionsObject(options, 'saveFile options');
11106
+ assertOptionsObject$1(options, 'saveFile options');
10921
11107
  if (!options || typeof options.suggestedName !== 'string') {
10922
- throw invalidParams('suggestedName 必须是非空字符串');
11108
+ throw invalidParams$1('suggestedName 必须是非空字符串');
10923
11109
  }
10924
11110
  const suggestedName = normalizeRelativePath(options.suggestedName);
10925
11111
  if (suggestedName.includes('/')) {
10926
- throw invalidParams('suggestedName 必须是 basename');
11112
+ throw invalidParams$1('suggestedName 必须是 basename');
10927
11113
  }
10928
11114
  if ('accept' in options) {
10929
- throw invalidParams('saveFile V1 不支持 accept');
11115
+ throw invalidParams$1('saveFile V1 不支持 accept');
10930
11116
  }
10931
11117
  return { suggestedName };
10932
11118
  }
@@ -10968,6 +11154,97 @@ function createFilesModule(requester) {
10968
11154
  });
10969
11155
  }
10970
11156
 
11157
+ const MEDIA_SOURCES = ['album', 'camera'];
11158
+ const MEDIA_SELECTION_TYPES = ['image', 'video', 'all'];
11159
+ const MEDIA_PICK_MAX_COUNT = 20;
11160
+ const MEDIA_PICK_OPTION_KEYS = ['source', 'mediaType', 'maxCount'];
11161
+ function invalidParams(message) {
11162
+ return createSDKError('INVALID_PARAMS', message);
11163
+ }
11164
+ function assertOptionsObject(value) {
11165
+ if (value !== undefined && (!value || typeof value !== 'object' || Array.isArray(value))) {
11166
+ throw invalidParams('pick options 必须是对象');
11167
+ }
11168
+ }
11169
+ function assertKnownKeys(options) {
11170
+ for (const key of Object.keys(options)) {
11171
+ if (!MEDIA_PICK_OPTION_KEYS.includes(key)) {
11172
+ throw invalidParams(`pick options 包含未知字段 ${key}`);
11173
+ }
11174
+ }
11175
+ }
11176
+ function normalizeSource(value) {
11177
+ if (value === undefined) {
11178
+ return 'album';
11179
+ }
11180
+ if (typeof value !== 'string' || !MEDIA_SOURCES.includes(value)) {
11181
+ throw invalidParams('source 必须是 album 或 camera');
11182
+ }
11183
+ return value;
11184
+ }
11185
+ function normalizeSelectionType(value) {
11186
+ if (value === undefined) {
11187
+ return 'all';
11188
+ }
11189
+ if (typeof value !== 'string' || !MEDIA_SELECTION_TYPES.includes(value)) {
11190
+ throw invalidParams('mediaType 必须是 image、video 或 all');
11191
+ }
11192
+ return value;
11193
+ }
11194
+ function normalizeMaxCount(value, source) {
11195
+ if (value === undefined) {
11196
+ return 1;
11197
+ }
11198
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 1 || value > MEDIA_PICK_MAX_COUNT) {
11199
+ throw invalidParams(`maxCount 必须是 1 到 ${MEDIA_PICK_MAX_COUNT} 的安全整数`);
11200
+ }
11201
+ if (source === 'camera' && value > 1) {
11202
+ throw invalidParams('camera 的 maxCount 只能是 1');
11203
+ }
11204
+ return value;
11205
+ }
11206
+ /** 把公开选项收窄为 wire payload,缺省值显式补齐且不携带 undefined 键。 */
11207
+ function normalizePickOptions(options) {
11208
+ assertOptionsObject(options);
11209
+ if (options === undefined) {
11210
+ return { source: 'album', mediaType: 'all', maxCount: 1 };
11211
+ }
11212
+ const raw = options;
11213
+ assertKnownKeys(raw);
11214
+ const source = normalizeSource(raw.source);
11215
+ const mediaType = normalizeSelectionType(raw.mediaType);
11216
+ const maxCount = normalizeMaxCount(raw.maxCount, source);
11217
+ return { source, mediaType, maxCount };
11218
+ }
11219
+ /** 创建当前 SDK session 的 media 模块。 */
11220
+ function createMediaModule(requester) {
11221
+ const registry = getFileHandleRegistry(requester);
11222
+ return Object.freeze({
11223
+ async pick(options) {
11224
+ const payload = normalizePickOptions(options);
11225
+ // 图库选择与拍摄等待用户确认,不使用普通请求的固定超时;销毁仍沿用 bridge cancel。
11226
+ const result = await requester.requestOperation(MEDIA_PICK_METHOD, payload);
11227
+ if (!isMiniProgramMediaPickResult(result)) {
11228
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的媒体 picker 结果');
11229
+ }
11230
+ if (result.length > payload.maxCount) {
11231
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的媒体项数量超过本次请求上限');
11232
+ }
11233
+ if (payload.source === 'camera' && result.length !== 1) {
11234
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的相机结果必须恰好一项');
11235
+ }
11236
+ const items = result.map((item) => {
11237
+ const handle = registry.createDirectFile(item.file);
11238
+ if (handle.mode !== 'read') {
11239
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的媒体 File mode 不是 read');
11240
+ }
11241
+ return Object.freeze({ ...item, file: handle });
11242
+ });
11243
+ return Object.freeze(items);
11244
+ },
11245
+ });
11246
+ }
11247
+
10971
11248
  /**
10972
11249
  * 获取当前小程序窗口信息。
10973
11250
  *
@@ -11353,6 +11630,8 @@ class MiniProgramSDK {
11353
11630
  network;
11354
11631
  /** 受控文件与目录能力。 */
11355
11632
  files;
11633
+ /** 受控媒体选择能力。 */
11634
+ media;
11356
11635
  /** UI 交互相关开放能力。 */
11357
11636
  ui;
11358
11637
  /** 设备相关开放能力。 */
@@ -11377,6 +11656,7 @@ class MiniProgramSDK {
11377
11656
  this.viewport = createViewportModule(this.client);
11378
11657
  this.storage = createStorageModule(this.client);
11379
11658
  this.files = createFilesModule(this.client);
11659
+ this.media = createMediaModule(this.client);
11380
11660
  this.network = createNetworkModule(this.client);
11381
11661
  this.ui = createUiModule(this.client);
11382
11662
  this.device = createDeviceModule(this.client);
@@ -11921,6 +12201,10 @@ const files = {
11921
12201
  pickDirectory: (options) => getDefaultSDK().files.pickDirectory(options),
11922
12202
  saveFile: (options) => getDefaultSDK().files.saveFile(options),
11923
12203
  };
12204
+ /** 默认 SDK 实例的 media 模块。 */
12205
+ const media = {
12206
+ pick: (options) => getDefaultSDK().media.pick(options),
12207
+ };
11924
12208
  /** 默认 SDK 实例的 UI 模块。 */
11925
12209
  const ui = {
11926
12210
  showToast: (options) => getDefaultSDK().ui.showToast(options),
@@ -11987,6 +12271,7 @@ const hbSDK = {
11987
12271
  viewport,
11988
12272
  storage,
11989
12273
  files,
12274
+ media,
11990
12275
  network,
11991
12276
  ui,
11992
12277
  device,
@@ -12008,6 +12293,7 @@ exports.device = device;
12008
12293
  exports.environment = environment;
12009
12294
  exports.files = files;
12010
12295
  exports.getHandshakeState = getHandshakeState;
12296
+ exports.media = media;
12011
12297
  exports.navigation = navigation;
12012
12298
  exports.network = network;
12013
12299
  exports.off = off;