@heybox/hb-sdk 0.8.0-alpha → 0.8.0-alpha.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 (64) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/README.md +87 -14
  3. package/THIRD_PARTY_NOTICES.md +1755 -0
  4. package/dist/cli-chunks/{build-DJWFSM1B.cjs → build-qWzAbpS7.cjs} +2 -2
  5. package/dist/cli-chunks/{context-DV2UK1Nz.cjs → context-CtS2Thp0.cjs} +1 -26
  6. package/dist/cli-chunks/{create-2HfoB48V.cjs → create-PV5ua977.cjs} +1 -1
  7. package/dist/cli-chunks/{dev-Dgt2zS9k.cjs → dev-CwKbAm_H.cjs} +337 -522
  8. package/dist/cli-chunks/doctor-tJUGOYrm.cjs +65 -0
  9. package/dist/cli-chunks/{index-BjoSXl8C.cjs → index-DVuD75Hr.cjs} +1 -1
  10. package/dist/cli-chunks/{index-D62ANeBv.cjs → index-De687C6-.cjs} +33 -24
  11. package/dist/cli-chunks/{index.esm-CigcxJ2B.cjs → index.esm-B-4yrLNm.cjs} +6 -5
  12. package/dist/cli-chunks/{login-Cumknwdx.cjs → login-DolpqD8K.cjs} +2 -2
  13. package/dist/cli-chunks/{project-vite-CcE-HMmd.cjs → project-vite-1rvkK-M8.cjs} +1 -1
  14. package/dist/cli-chunks/{remote-DDdP3xcE.cjs → remote-rIAQE_G2.cjs} +4 -4
  15. package/dist/cli-chunks/{session-BDi_AZSv.cjs → session-CzaM2Cq3.cjs} +1 -1
  16. package/dist/cli-chunks/skill-CM40_9WH.cjs +83 -0
  17. package/dist/cli-chunks/version-Bz-AfXQU.cjs +8 -0
  18. package/dist/cli.cjs +1 -1
  19. package/dist/devtools/browser-dev-host/assets/browser-dev-host-uq-Wac6k.js +97 -0
  20. package/dist/devtools/browser-dev-host/assets/heybox-logo-CogNENsk.svg +6 -0
  21. package/dist/devtools/browser-dev-host/assets/index-CeP6SLB3.js +567 -0
  22. package/dist/devtools/browser-dev-host/assets/index-KD2f3Jdz.css +1 -0
  23. package/dist/devtools/browser-dev-host/assets/workbench-state-D-1U0JRq.js +5 -0
  24. package/dist/devtools/browser-dev-host/index.html +6 -435
  25. package/dist/index.cjs.js +924 -28
  26. package/dist/index.esm.js +924 -29
  27. package/dist/protocol.cjs.js +208 -6
  28. package/dist/protocol.esm.js +182 -7
  29. package/dist/vite.cjs.js +1 -1
  30. package/dist/vite.esm.js +1 -1
  31. package/package.json +29 -10
  32. package/skill/SKILL.md +16 -13
  33. package/skill/references/api-protocol.md +100 -4
  34. package/skill/references/api-root.md +142 -15
  35. package/skill/references/cli.md +27 -22
  36. package/skill/references/examples.md +30 -1
  37. package/skill/references/llms-index.md +1 -1
  38. package/skill/references/recipes.md +106 -2
  39. package/skill/references/safety-boundaries.md +12 -1
  40. package/skill/skill.json +10 -5
  41. package/types/core/client.d.ts +17 -1
  42. package/types/core/sdk.d.ts +3 -0
  43. package/types/core/singleton.d.ts +3 -0
  44. package/types/index.d.ts +4 -2
  45. package/types/modules/files/index.d.ts +5 -0
  46. package/types/modules/files/registry.d.ts +35 -0
  47. package/types/modules/files/types.d.ts +159 -0
  48. package/types/modules/network/index.d.ts +50 -3
  49. package/types/protocol/capabilities.d.ts +2 -2
  50. package/types/protocol/constants.d.ts +1 -1
  51. package/types/protocol/guards.d.ts +1 -1
  52. package/types/protocol/types.d.ts +1 -1
  53. package/types/protocol.d.ts +4 -3
  54. package/types/skill-metadata.d.ts +0 -4
  55. package/dist/cli-chunks/doctor-C95gIao_.cjs +0 -204
  56. package/dist/devtools/browser-dev-host/main.js +0 -12263
  57. package/skill/scripts/check-references.mjs +0 -14
  58. package/skill/scripts/markdown-sections.mjs +0 -36
  59. package/skill/scripts/package-skill.mjs +0 -60
  60. package/skill/scripts/package-skill.sh +0 -6
  61. package/skill/scripts/skill-metadata.mjs +0 -77
  62. package/skill/scripts/sync-agent-skills-payload.mjs +0 -359
  63. package/skill/scripts/sync-references.mjs +0 -794
  64. package/skill/scripts/validate-skill.mjs +0 -263
package/dist/index.esm.js CHANGED
@@ -572,6 +572,22 @@ const VIEWPORT_SET_NAVIGATION_BAR_STYLE_METHOD = 'viewport.setNavigationBarStyle
572
572
  const STORAGE_GET_STORAGE_METHOD = 'storage.getStorage';
573
573
  const STORAGE_SET_STORAGE_METHOD = 'storage.setStorage';
574
574
  const NETWORK_REQUEST_METHOD = 'network.request';
575
+ const NETWORK_DOWNLOAD_METHOD = 'network.download';
576
+ const FILES_PICK_FILES_METHOD = 'files.pickFiles';
577
+ const FILES_PICK_DIRECTORY_METHOD = 'files.pickDirectory';
578
+ const FILES_SAVE_FILE_METHOD = 'files.saveFile';
579
+ const FILE_CREATE_METHOD = 'file.create';
580
+ const FILE_REMOVE_METHOD = 'file.remove';
581
+ const FILE_EXISTS_METHOD = 'file.exists';
582
+ const FILE_READ_TEXT_METHOD = 'file.readText';
583
+ const FILE_WRITE_TEXT_METHOD = 'file.writeText';
584
+ const FILE_READ_BYTES_METHOD = 'file.readBytes';
585
+ const FILE_WRITE_BYTES_METHOD = 'file.writeBytes';
586
+ const FILE_STAT_METHOD = 'file.stat';
587
+ const DIRECTORY_CREATE_METHOD = 'directory.create';
588
+ const DIRECTORY_REMOVE_METHOD = 'directory.remove';
589
+ const DIRECTORY_EXISTS_METHOD = 'directory.exists';
590
+ const DIRECTORY_LIST_METHOD = 'directory.list';
575
591
  const UI_SHOW_TOAST_METHOD = 'ui.showToast';
576
592
  const UI_SHOW_LOADING_METHOD = 'ui.showLoading';
577
593
  const UI_HIDE_LOADING_METHOD = 'ui.hideLoading';
@@ -593,15 +609,112 @@ const SDK_HANDSHAKE_METHOD = 'sdk.handshake';
593
609
  const RUNTIME_LOCATION_PROBE_METHOD = 'runtime.location.probe';
594
610
  const SDK_LOCATION_REPORT_METHOD = 'sdk.location.report';
595
611
  const SDK_CSP_VIOLATION_METHOD = 'sdk.csp.violation';
612
+ const MINI_PROGRAM_OPERATION_PROGRESS_METHOD = 'operation.progress';
596
613
 
597
614
  function isMiniProgramBridgeMessage(value) {
598
- if (!value || typeof value !== 'object')
615
+ if (!isRecord$1(value))
599
616
  return false;
600
- const message = value;
601
- return (message.namespace === MINI_PROGRAM_MESSAGE_NAMESPACE &&
602
- (message.version === 1 || message.version === 2) &&
603
- typeof message.nonce === 'string' &&
604
- typeof message.type === 'string');
617
+ if (value.namespace !== MINI_PROGRAM_MESSAGE_NAMESPACE || (value.version !== 1 && value.version !== 2) || !isNonEmptyString(value.nonce)) {
618
+ return false;
619
+ }
620
+ if (value.type === 'handshake') {
621
+ return isNonEmptyString(value.id) && value.method === SDK_HANDSHAKE_METHOD && value.error === undefined && isSDKHandshakePayload(value.payload);
622
+ }
623
+ if (value.type === 'request') {
624
+ return isNonEmptyString(value.id) && isNonEmptyString(value.method) && value.error === undefined;
625
+ }
626
+ if (value.type === 'response') {
627
+ return (isNonEmptyString(value.id) &&
628
+ (value.method === undefined || isNonEmptyString(value.method)) &&
629
+ (value.error === undefined || isBridgeError(value.error)));
630
+ }
631
+ if (value.type === 'event') {
632
+ if (!isNonEmptyString(value.method) || value.error !== undefined)
633
+ return false;
634
+ if (value.method === MINI_PROGRAM_OPERATION_PROGRESS_METHOD) {
635
+ return value.version === 2 && isNonEmptyString(value.id) && isMiniProgramDownloadProgressPayload(value.payload);
636
+ }
637
+ return value.id === undefined || isNonEmptyString(value.id);
638
+ }
639
+ if (value.type === 'cancel') {
640
+ return (value.version === 2 && isNonEmptyString(value.id) && value.method === undefined && value.payload === undefined && value.error === undefined);
641
+ }
642
+ return false;
643
+ }
644
+ function isMiniProgramRelativePath(value) {
645
+ if (typeof value !== 'string' || !value || value.startsWith('/') || value.endsWith('/')) {
646
+ return false;
647
+ }
648
+ if (/[\\\u0000-\u001f\u007f]/.test(value))
649
+ return false;
650
+ return value.split('/').every((segment) => Boolean(segment) && segment !== '.' && segment !== '..');
651
+ }
652
+ function isMiniProgramPathRootRef(value) {
653
+ if (!isRecord$1(value))
654
+ return false;
655
+ if (value.kind === 'sandbox')
656
+ return hasOnlyKeys(value, ['kind']);
657
+ return value.kind === 'directory' && isNonEmptyString(value.handleId) && hasOnlyKeys(value, ['kind', 'handleId']);
658
+ }
659
+ function isMiniProgramFileRef(value) {
660
+ if (!isRecord$1(value) || value.kind !== 'file')
661
+ return false;
662
+ if (isNonEmptyString(value.handleId)) {
663
+ return hasOnlyKeys(value, ['kind', 'handleId']);
664
+ }
665
+ return (isMiniProgramPathRootRef(value.root) && isMiniProgramRelativePath(value.relativePath) && hasOnlyKeys(value, ['kind', 'root', 'relativePath']));
666
+ }
667
+ function isMiniProgramDirectoryRef(value) {
668
+ if (!isRecord$1(value) || value.kind !== 'directory')
669
+ return false;
670
+ if (isNonEmptyString(value.handleId)) {
671
+ return hasOnlyKeys(value, ['kind', 'handleId']);
672
+ }
673
+ return (isMiniProgramPathRootRef(value.root) && isMiniProgramRelativePath(value.relativePath) && hasOnlyKeys(value, ['kind', 'root', 'relativePath']));
674
+ }
675
+ function isMiniProgramByteArray(value) {
676
+ return value instanceof Uint8Array && value.buffer instanceof ArrayBuffer;
677
+ }
678
+ function isMiniProgramFileStat(value) {
679
+ return (isRecord$1(value) &&
680
+ value.kind === 'file' &&
681
+ isNonNegativeSafeInteger(value.size) &&
682
+ (value.modifiedAt === undefined || isNonNegativeFiniteNumber(value.modifiedAt)) &&
683
+ (value.createdAt === undefined || isNonNegativeFiniteNumber(value.createdAt)) &&
684
+ hasOnlyKeys(value, ['kind', 'size', 'modifiedAt', 'createdAt']));
685
+ }
686
+ function isMiniProgramDownloadProgressPayload(value) {
687
+ if (!isRecord$1(value) ||
688
+ !isNonNegativeSafeInteger(value.loaded) ||
689
+ typeof value.lengthComputable !== 'boolean' ||
690
+ !hasOnlyKeys(value, ['loaded', 'total', 'lengthComputable'])) {
691
+ return false;
692
+ }
693
+ if (!value.lengthComputable)
694
+ return value.total === undefined;
695
+ return isNonNegativeSafeInteger(value.total) && value.loaded <= value.total;
696
+ }
697
+ function isSDKHandshakePayload(value) {
698
+ return isRecord$1(value) && isNonEmptyString(value.href) && typeof value.userAgent === 'string' && isNonEmptyString(value.sdkVersion);
699
+ }
700
+ function isBridgeError(value) {
701
+ return isRecord$1(value) && isNonEmptyString(value.code) && typeof value.message === 'string';
702
+ }
703
+ function isNonNegativeSafeInteger(value) {
704
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
705
+ }
706
+ function isNonNegativeFiniteNumber(value) {
707
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
708
+ }
709
+ function isNonEmptyString(value) {
710
+ return typeof value === 'string' && value.length > 0;
711
+ }
712
+ function hasOnlyKeys(value, allowedKeys) {
713
+ const allowed = new Set(allowedKeys);
714
+ return Object.keys(value).every((key) => allowed.has(key));
715
+ }
716
+ function isRecord$1(value) {
717
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
605
718
  }
606
719
 
607
720
  /** 当前用户信息与登录能力支持的完整 scope 目录。 */
@@ -668,7 +781,7 @@ function createMessageId() {
668
781
  /** 构建时替换为当前发布包的实际版本。 */
669
782
  const HB_SDK_VERSION = typeof undefined === 'string'
670
783
  ? undefined
671
- : '0.8.0-alpha';
784
+ : '0.8.0-alpha.3';
672
785
 
673
786
  const DEFAULT_TIMEOUT = 10000;
674
787
  const HANDSHAKE_RETRY_INTERVAL = 250;
@@ -703,8 +816,7 @@ class MiniProgramBridgeClient {
703
816
  constructor(options = {}) {
704
817
  this.timeout = options.timeout || DEFAULT_TIMEOUT;
705
818
  this.selfWindow = options.selfWindow || getGlobalWindow();
706
- this.targetWindow =
707
- options.targetWindow === undefined ? getParentWindow(this.selfWindow) : options.targetWindow;
819
+ this.targetWindow = options.targetWindow === undefined ? getParentWindow(this.selfWindow) : options.targetWindow;
708
820
  this.targetOrigin = options.targetOrigin || readParentOrigin(this.selfWindow) || '*';
709
821
  this.nonce = options.nonce || readBridgeNonce(this.selfWindow);
710
822
  this.handleMessage = this.onMessage.bind(this);
@@ -737,20 +849,42 @@ class MiniProgramBridgeClient {
737
849
  }
738
850
  /** 调用父容器开放能力。 */
739
851
  async request(method, ...args) {
852
+ return this.performRequest(method, args[0], true);
853
+ }
854
+ /** 发起不使用固定总时长 timer 的长操作。 */
855
+ async requestOperation(method, payload, options = {}) {
856
+ return this.performRequest(method, payload, false, options);
857
+ }
858
+ async performRequest(method, payload, useTimeout, options = {}) {
740
859
  if (this.destroyed) {
741
860
  throw createSDKError('SDK_DESTROYED', '小程序 SDK 已销毁');
742
861
  }
743
862
  if (this.runtimeUnavailable) {
744
863
  throw this.getRuntimeUnavailableError();
745
864
  }
746
- await this.waitForHandshake();
865
+ if (!useTimeout && options.signal?.aborted) {
866
+ throw options.createAbortError?.() ?? createSDKError('REQUEST_CANCELLED', `${method} 已取消`);
867
+ }
868
+ if (!useTimeout && options.signal && !this.handshakeSettled) {
869
+ await waitForHandshakeOrAbort(this.waitForHandshake(), options.signal, () => options.createAbortError?.() ?? createSDKError('REQUEST_CANCELLED', `${method} 已取消`), () => {
870
+ const state = this.handshakeState.getState();
871
+ if (state.status === 'failed')
872
+ return state.error;
873
+ return this.destroyed ? createSDKError('SDK_DESTROYED', '小程序 SDK 已销毁') : undefined;
874
+ });
875
+ }
876
+ else {
877
+ await this.waitForHandshake();
878
+ }
747
879
  if (this.destroyed) {
748
880
  throw createSDKError('SDK_DESTROYED', '小程序 SDK 已销毁');
749
881
  }
750
882
  if (this.runtimeUnavailable) {
751
883
  throw this.getRuntimeUnavailableError();
752
884
  }
753
- const payload = args[0];
885
+ if (options.signal?.aborted) {
886
+ throw options.createAbortError?.() ?? createSDKError('REQUEST_CANCELLED', `${method} 已取消`);
887
+ }
754
888
  const id = createMessageId();
755
889
  const message = {
756
890
  namespace: MINI_PROGRAM_MESSAGE_NAMESPACE,
@@ -762,27 +896,46 @@ class MiniProgramBridgeClient {
762
896
  payload,
763
897
  };
764
898
  return new Promise((resolve, reject) => {
765
- const timer = setTimeout(() => {
766
- this.pendingRequests.delete(id);
767
- reject(createSDKError('REQUEST_TIMEOUT', `${method} 调用超时`));
768
- }, this.timeout);
769
- this.pendingRequests.set(id, {
899
+ const pending = {
770
900
  resolve: resolve,
771
901
  reject,
772
- timer,
773
- });
902
+ isOperation: !useTimeout,
903
+ ...(options.onProgress ? { onProgress: options.onProgress } : {}),
904
+ ...(options.signal ? { signal: options.signal } : {}),
905
+ };
906
+ if (useTimeout) {
907
+ pending.timer = setTimeout(() => {
908
+ const active = this.pendingRequests.get(id);
909
+ if (!active) {
910
+ return;
911
+ }
912
+ this.cleanupPending(active);
913
+ this.pendingRequests.delete(id);
914
+ reject(createSDKError('REQUEST_TIMEOUT', `${method} 调用超时`));
915
+ }, this.timeout);
916
+ }
917
+ this.pendingRequests.set(id, pending);
774
918
  try {
775
919
  this.postMessage(message);
776
920
  }
777
921
  catch (error) {
778
- clearTimeout(timer);
922
+ this.cleanupPending(pending);
779
923
  this.pendingRequests.delete(id);
780
924
  reject(error);
925
+ return;
926
+ }
927
+ if (options.signal) {
928
+ pending.abortHandler = () => this.sendCancel(id);
929
+ options.signal.addEventListener('abort', pending.abortHandler, { once: true });
930
+ if (options.signal.aborted) {
931
+ pending.abortHandler();
932
+ }
781
933
  }
782
934
  });
783
935
  }
784
936
  /** 销毁 SDK 实例,并拒绝尚未完成的请求。 */
785
937
  destroy() {
938
+ this.pendingRequests.forEach((_pending, id) => this.sendCancel(id));
786
939
  this.destroyed = true;
787
940
  this.selfWindow?.removeEventListener('message', this.handleMessage);
788
941
  this.selfWindow?.removeEventListener('popstate', this.handlePopState);
@@ -835,16 +988,12 @@ class MiniProgramBridgeClient {
835
988
  this.postHandshake();
836
989
  }
837
990
  catch (error) {
838
- this.failHandshake(error instanceof HbMiniProgramSDKError
839
- ? error
840
- : createSDKError('HANDSHAKE_FAILED', '小程序沙盒握手发送失败', error));
991
+ this.failHandshake(error instanceof HbMiniProgramSDKError ? error : createSDKError('HANDSHAKE_FAILED', '小程序沙盒握手发送失败', error));
841
992
  }
842
993
  }, HANDSHAKE_RETRY_INTERVAL);
843
994
  }
844
995
  catch (error) {
845
- this.failHandshake(error instanceof HbMiniProgramSDKError
846
- ? error
847
- : createSDKError('HANDSHAKE_FAILED', '小程序沙盒握手发送失败', error));
996
+ this.failHandshake(error instanceof HbMiniProgramSDKError ? error : createSDKError('HANDSHAKE_FAILED', '小程序沙盒握手发送失败', error));
848
997
  }
849
998
  }
850
999
  postHandshake() {
@@ -873,6 +1022,10 @@ class MiniProgramBridgeClient {
873
1022
  return;
874
1023
  }
875
1024
  if (event.data.type === 'event') {
1025
+ if (event.data.id && event.data.method === MINI_PROGRAM_OPERATION_PROGRESS_METHOD) {
1026
+ this.handleOperationProgress(event.data);
1027
+ return;
1028
+ }
876
1029
  this.handleEvent(event.data);
877
1030
  return;
878
1031
  }
@@ -954,7 +1107,7 @@ class MiniProgramBridgeClient {
954
1107
  if (!pending) {
955
1108
  return;
956
1109
  }
957
- clearTimeout(pending.timer);
1110
+ this.cleanupPending(pending);
958
1111
  this.pendingRequests.delete(message.id);
959
1112
  if (message.error) {
960
1113
  pending.reject(new HbMiniProgramSDKError(message.error));
@@ -962,6 +1115,52 @@ class MiniProgramBridgeClient {
962
1115
  }
963
1116
  pending.resolve(message.payload);
964
1117
  }
1118
+ handleOperationProgress(message) {
1119
+ if (!message.id) {
1120
+ return;
1121
+ }
1122
+ const pending = this.pendingRequests.get(message.id);
1123
+ if (!pending?.onProgress) {
1124
+ return;
1125
+ }
1126
+ try {
1127
+ pending.onProgress(message.payload);
1128
+ }
1129
+ catch {
1130
+ // Mini-program 回调失败不能改变 Host 长操作的生命周期。
1131
+ }
1132
+ }
1133
+ sendCancel(id) {
1134
+ const pending = this.pendingRequests.get(id);
1135
+ if (!pending?.isOperation || pending.cancelSent) {
1136
+ return;
1137
+ }
1138
+ pending.cancelSent = true;
1139
+ try {
1140
+ const message = {
1141
+ namespace: MINI_PROGRAM_MESSAGE_NAMESPACE,
1142
+ version: MINI_PROGRAM_MESSAGE_VERSION,
1143
+ nonce: this.nonce,
1144
+ type: 'cancel',
1145
+ id,
1146
+ };
1147
+ this.postMessage(message);
1148
+ }
1149
+ catch {
1150
+ // cancel 只是意图,发送失败后仍等待唯一 terminal response。
1151
+ }
1152
+ }
1153
+ cleanupPending(pending) {
1154
+ if (pending.timer) {
1155
+ clearTimeout(pending.timer);
1156
+ }
1157
+ if (pending.signal && pending.abortHandler) {
1158
+ pending.signal.removeEventListener('abort', pending.abortHandler);
1159
+ }
1160
+ pending.onProgress = undefined;
1161
+ pending.abortHandler = undefined;
1162
+ pending.signal = undefined;
1163
+ }
965
1164
  postMessage(message) {
966
1165
  if (!this.targetWindow) {
967
1166
  throw createSDKError('NOT_IN_IFRAME', '当前页面不在小程序沙盒 iframe 中');
@@ -1013,13 +1212,34 @@ class MiniProgramBridgeClient {
1013
1212
  return this.runtimeUnavailableError;
1014
1213
  }
1015
1214
  rejectAllPending(error) {
1016
- this.pendingRequests.forEach(pending => {
1017
- clearTimeout(pending.timer);
1215
+ this.pendingRequests.forEach((pending) => {
1216
+ this.cleanupPending(pending);
1018
1217
  pending.reject(error);
1019
1218
  });
1020
1219
  this.pendingRequests.clear();
1021
1220
  }
1022
1221
  }
1222
+ function waitForHandshakeOrAbort(handshake, signal, createAbortError, getTerminalError) {
1223
+ if (!signal)
1224
+ return handshake;
1225
+ if (signal.aborted)
1226
+ return Promise.reject(createAbortError());
1227
+ return new Promise((resolve, reject) => {
1228
+ let settled = false;
1229
+ const settle = (callback) => {
1230
+ if (settled)
1231
+ return;
1232
+ settled = true;
1233
+ signal.removeEventListener('abort', onAbort);
1234
+ callback();
1235
+ };
1236
+ const onAbort = () => settle(() => reject(getTerminalError() ?? createAbortError()));
1237
+ signal.addEventListener('abort', onAbort, { once: true });
1238
+ handshake.then(() => settle(() => (signal.aborted ? reject(createAbortError()) : resolve())), (error) => settle(() => reject(error)));
1239
+ if (signal.aborted)
1240
+ onAbort();
1241
+ });
1242
+ }
1023
1243
 
1024
1244
  /** 获取短期授权码。 */
1025
1245
  function login(requester, options) {
@@ -2322,6 +2542,373 @@ function createStorageModule(requester) {
2322
2542
  };
2323
2543
  }
2324
2544
 
2545
+ const registries = new WeakMap();
2546
+ const brandedFileSystemValues = new WeakSet();
2547
+ const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/;
2548
+ function invalidParams$1(message, data) {
2549
+ return createSDKError('INVALID_PARAMS', message, data);
2550
+ }
2551
+ function clonePathRootRef(ref) {
2552
+ return ref.kind === 'sandbox' ? Object.freeze({ kind: 'sandbox' }) : Object.freeze({ kind: 'directory', handleId: ref.handleId });
2553
+ }
2554
+ function cloneFileRef(ref) {
2555
+ return 'handleId' in ref
2556
+ ? Object.freeze({ kind: 'file', handleId: ref.handleId })
2557
+ : Object.freeze({
2558
+ kind: 'file',
2559
+ root: clonePathRootRef(ref.root),
2560
+ relativePath: ref.relativePath,
2561
+ });
2562
+ }
2563
+ function cloneDirectoryRef(ref) {
2564
+ return 'handleId' in ref
2565
+ ? Object.freeze({ kind: 'directory', handleId: ref.handleId })
2566
+ : Object.freeze({
2567
+ kind: 'directory',
2568
+ root: clonePathRootRef(ref.root),
2569
+ relativePath: ref.relativePath,
2570
+ });
2571
+ }
2572
+ function assertMode(mode) {
2573
+ if (mode !== 'read' && mode !== 'readwrite') {
2574
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的文件访问模式');
2575
+ }
2576
+ }
2577
+ function assertName(name) {
2578
+ if (typeof name !== 'string' || name.length === 0 || name.includes('/') || name.includes('\\') || CONTROL_CHARACTER_PATTERN.test(name)) {
2579
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的文件名');
2580
+ }
2581
+ }
2582
+ function assertFileRef(ref) {
2583
+ if (!isMiniProgramFileRef(ref)) {
2584
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的 File handle');
2585
+ }
2586
+ }
2587
+ function assertDirectoryRef(ref) {
2588
+ if (!isMiniProgramDirectoryRef(ref)) {
2589
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的 Directory handle');
2590
+ }
2591
+ }
2592
+ function defineReadonlyValue(target, key, value, enumerable = true) {
2593
+ Object.defineProperty(target, key, {
2594
+ value,
2595
+ enumerable,
2596
+ configurable: false,
2597
+ writable: false,
2598
+ });
2599
+ }
2600
+ function joinRelativePath(prefix, relativePath) {
2601
+ return prefix ? `${prefix}/${relativePath}` : relativePath;
2602
+ }
2603
+ function basename(relativePath) {
2604
+ const segments = relativePath.split('/');
2605
+ return segments[segments.length - 1];
2606
+ }
2607
+ function assertWritable(mode) {
2608
+ if (mode !== 'readwrite') {
2609
+ throw createSDKError('FILE_ACCESS_DENIED', '当前文件句柄不允许写入');
2610
+ }
2611
+ }
2612
+ function assertSandboxRef(ref) {
2613
+ if ('handleId' in ref || ref.root.kind !== 'sandbox') {
2614
+ throw createSDKError('FILE_ACCESS_DENIED', '外部文件授权不允许删除');
2615
+ }
2616
+ }
2617
+ function normalizeCreateOptions(options) {
2618
+ if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) {
2619
+ throw invalidParams$1('create options 必须是对象');
2620
+ }
2621
+ if (options?.recursive !== undefined && typeof options.recursive !== 'boolean') {
2622
+ throw invalidParams$1('recursive 必须是 boolean');
2623
+ }
2624
+ if (options?.exclusive !== undefined && typeof options.exclusive !== 'boolean') {
2625
+ throw invalidParams$1('exclusive 必须是 boolean');
2626
+ }
2627
+ return {
2628
+ recursive: options?.recursive ?? false,
2629
+ exclusive: options?.exclusive ?? false,
2630
+ };
2631
+ }
2632
+ function normalizeListOptions(options) {
2633
+ if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) {
2634
+ throw invalidParams$1('list options 必须是对象');
2635
+ }
2636
+ if (options?.limit !== undefined && (!Number.isSafeInteger(options.limit) || options.limit <= 0)) {
2637
+ throw invalidParams$1('limit 必须是正的 safe integer');
2638
+ }
2639
+ return options?.limit;
2640
+ }
2641
+ function normalizeRemoveOptions(options) {
2642
+ if (options !== undefined && (!options || typeof options !== 'object' || Array.isArray(options))) {
2643
+ throw invalidParams$1('remove options 必须是对象');
2644
+ }
2645
+ if (options?.recursive !== undefined && typeof options.recursive !== 'boolean') {
2646
+ throw invalidParams$1('recursive 必须是 boolean');
2647
+ }
2648
+ return { recursive: options?.recursive ?? false };
2649
+ }
2650
+ function cloneBytes(content) {
2651
+ if (!(content instanceof Uint8Array)) {
2652
+ throw invalidParams$1('content 必须是 Uint8Array');
2653
+ }
2654
+ if (typeof SharedArrayBuffer !== 'undefined' && content.buffer instanceof SharedArrayBuffer) {
2655
+ throw invalidParams$1('content 不能由 SharedArrayBuffer 承载');
2656
+ }
2657
+ return new Uint8Array(content);
2658
+ }
2659
+ function normalizeStat(stat) {
2660
+ if (!isMiniProgramFileStat(stat)) {
2661
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的 FileStat');
2662
+ }
2663
+ return {
2664
+ kind: 'file',
2665
+ size: stat.size,
2666
+ ...(stat.modifiedAt === undefined ? {} : { modifiedAt: stat.modifiedAt }),
2667
+ ...(stat.createdAt === undefined ? {} : { createdAt: stat.createdAt }),
2668
+ };
2669
+ }
2670
+ /** 校验并返回平台无关的 `/` 分隔逻辑相对路径。 */
2671
+ function normalizeRelativePath(value) {
2672
+ if (!isMiniProgramRelativePath(value)) {
2673
+ throw invalidParams$1('relativePath 必须是规范的 `/` 分隔逻辑相对路径');
2674
+ }
2675
+ return value;
2676
+ }
2677
+ class MiniProgramFileHandleRegistry {
2678
+ requester;
2679
+ fileStates = new WeakMap();
2680
+ directoryStates = new WeakMap();
2681
+ pathRootStates = new WeakMap();
2682
+ sandboxRoot;
2683
+ constructor(requester) {
2684
+ this.requester = requester;
2685
+ }
2686
+ getSandbox() {
2687
+ if (this.sandboxRoot) {
2688
+ return this.sandboxRoot;
2689
+ }
2690
+ const sandbox = {};
2691
+ const state = {
2692
+ root: Object.freeze({ kind: 'sandbox' }),
2693
+ prefix: '',
2694
+ mode: 'readwrite',
2695
+ };
2696
+ this.definePathRootMethods(sandbox, state);
2697
+ this.pathRootStates.set(sandbox, state);
2698
+ brandedFileSystemValues.add(sandbox);
2699
+ this.sandboxRoot = Object.freeze(sandbox);
2700
+ return this.sandboxRoot;
2701
+ }
2702
+ createFile(descriptor) {
2703
+ this.assertFileDescriptor(descriptor);
2704
+ const ref = cloneFileRef(descriptor.ref);
2705
+ const state = { ref, mode: descriptor.mode };
2706
+ const handle = {};
2707
+ defineReadonlyValue(handle, 'kind', 'file');
2708
+ defineReadonlyValue(handle, 'name', descriptor.name);
2709
+ defineReadonlyValue(handle, 'mode', descriptor.mode);
2710
+ defineReadonlyValue(handle, 'create', async (options) => {
2711
+ assertWritable(state.mode);
2712
+ await this.requester.request(FILE_CREATE_METHOD, {
2713
+ target: state.ref,
2714
+ ...normalizeCreateOptions(options),
2715
+ });
2716
+ return handle;
2717
+ }, false);
2718
+ defineReadonlyValue(handle, 'remove', async () => {
2719
+ assertWritable(state.mode);
2720
+ assertSandboxRef(state.ref);
2721
+ await this.requester.request(FILE_REMOVE_METHOD, { target: state.ref });
2722
+ }, false);
2723
+ defineReadonlyValue(handle, 'exists', async () => {
2724
+ const result = await this.requester.request(FILE_EXISTS_METHOD, { target: state.ref });
2725
+ if (typeof result !== 'boolean') {
2726
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的 exists 结果');
2727
+ }
2728
+ return result;
2729
+ }, false);
2730
+ defineReadonlyValue(handle, 'readText', async () => {
2731
+ const result = await this.requester.request(FILE_READ_TEXT_METHOD, { target: state.ref });
2732
+ if (typeof result !== 'string') {
2733
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的文本内容');
2734
+ }
2735
+ return result;
2736
+ }, false);
2737
+ defineReadonlyValue(handle, 'writeText', async (content) => {
2738
+ assertWritable(state.mode);
2739
+ if (typeof content !== 'string') {
2740
+ throw invalidParams$1('content 必须是 string');
2741
+ }
2742
+ await this.requester.request(FILE_WRITE_TEXT_METHOD, { target: state.ref, content });
2743
+ }, false);
2744
+ defineReadonlyValue(handle, 'readBytes', async () => {
2745
+ const result = await this.requester.request(FILE_READ_BYTES_METHOD, { target: state.ref });
2746
+ if (!isMiniProgramByteArray(result)) {
2747
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的二进制内容');
2748
+ }
2749
+ return new Uint8Array(result);
2750
+ }, false);
2751
+ defineReadonlyValue(handle, 'writeBytes', async (content) => {
2752
+ assertWritable(state.mode);
2753
+ const snapshot = cloneBytes(content);
2754
+ await this.requester.request(FILE_WRITE_BYTES_METHOD, { target: state.ref, content: snapshot });
2755
+ }, false);
2756
+ defineReadonlyValue(handle, 'stat', async () => {
2757
+ const result = await this.requester.request(FILE_STAT_METHOD, { target: state.ref });
2758
+ return normalizeStat(result);
2759
+ }, false);
2760
+ this.fileStates.set(handle, state);
2761
+ brandedFileSystemValues.add(handle);
2762
+ return Object.freeze(handle);
2763
+ }
2764
+ createDirectFile(descriptor) {
2765
+ this.assertFileDescriptor(descriptor);
2766
+ if (!('handleId' in descriptor.ref)) {
2767
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的 File 不是精确文件授权');
2768
+ }
2769
+ return this.createFile(descriptor);
2770
+ }
2771
+ createDirectory(descriptor) {
2772
+ this.assertDirectoryDescriptor(descriptor);
2773
+ const ref = cloneDirectoryRef(descriptor.ref);
2774
+ const state = { ref, mode: descriptor.mode };
2775
+ const pathRootState = 'handleId' in ref ? { root: ref, prefix: '', mode: descriptor.mode } : { root: ref.root, prefix: ref.relativePath, mode: descriptor.mode };
2776
+ const handle = {};
2777
+ defineReadonlyValue(handle, 'kind', 'directory');
2778
+ defineReadonlyValue(handle, 'name', descriptor.name);
2779
+ defineReadonlyValue(handle, 'mode', descriptor.mode);
2780
+ this.definePathRootMethods(handle, pathRootState);
2781
+ defineReadonlyValue(handle, 'create', async (options) => {
2782
+ assertWritable(state.mode);
2783
+ await this.requester.request(DIRECTORY_CREATE_METHOD, {
2784
+ target: state.ref,
2785
+ ...normalizeCreateOptions(options),
2786
+ });
2787
+ return handle;
2788
+ }, false);
2789
+ defineReadonlyValue(handle, 'remove', async (options) => {
2790
+ assertWritable(state.mode);
2791
+ assertSandboxRef(state.ref);
2792
+ await this.requester.request(DIRECTORY_REMOVE_METHOD, {
2793
+ target: state.ref,
2794
+ ...normalizeRemoveOptions(options),
2795
+ });
2796
+ }, false);
2797
+ defineReadonlyValue(handle, 'exists', async () => {
2798
+ const result = await this.requester.request(DIRECTORY_EXISTS_METHOD, { target: state.ref });
2799
+ if (typeof result !== 'boolean') {
2800
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的 exists 结果');
2801
+ }
2802
+ return result;
2803
+ }, false);
2804
+ defineReadonlyValue(handle, 'list', async (options) => {
2805
+ const limit = normalizeListOptions(options);
2806
+ const result = await this.requester.request(DIRECTORY_LIST_METHOD, {
2807
+ target: state.ref,
2808
+ ...(limit === undefined ? {} : { limit }),
2809
+ });
2810
+ if (!Array.isArray(result)) {
2811
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的目录列表');
2812
+ }
2813
+ if (result.some((entry) => entry?.mode !== state.mode)) {
2814
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的目录项未继承父目录 mode');
2815
+ }
2816
+ return result.map((entry) => this.createEntity(entry));
2817
+ }, false);
2818
+ this.directoryStates.set(handle, state);
2819
+ this.pathRootStates.set(handle, pathRootState);
2820
+ brandedFileSystemValues.add(handle);
2821
+ return Object.freeze(handle);
2822
+ }
2823
+ createDirectDirectory(descriptor) {
2824
+ this.assertDirectoryDescriptor(descriptor);
2825
+ if (!('handleId' in descriptor.ref)) {
2826
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的 Directory 不是精确目录授权');
2827
+ }
2828
+ return this.createDirectory(descriptor);
2829
+ }
2830
+ createEntity(descriptor) {
2831
+ if (descriptor?.kind === 'file') {
2832
+ return this.createFile(descriptor);
2833
+ }
2834
+ if (descriptor?.kind === 'directory') {
2835
+ return this.createDirectory(descriptor);
2836
+ }
2837
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的文件实体');
2838
+ }
2839
+ requireFile(handle) {
2840
+ const state = handle && typeof handle === 'object' ? this.fileStates.get(handle) : undefined;
2841
+ if (!state) {
2842
+ throw invalidParams$1('to 必须是当前 SDK session 创建的 FileHandle');
2843
+ }
2844
+ return state;
2845
+ }
2846
+ requireDirectory(handle) {
2847
+ const state = handle && typeof handle === 'object' ? this.directoryStates.get(handle) : undefined;
2848
+ if (!state) {
2849
+ throw invalidParams$1('to 必须是当前 SDK session 创建的 DirectoryHandle');
2850
+ }
2851
+ return state;
2852
+ }
2853
+ definePathRootMethods(target, state) {
2854
+ defineReadonlyValue(target, 'file', (relativePath) => {
2855
+ const normalized = normalizeRelativePath(relativePath);
2856
+ const fullPath = joinRelativePath(state.prefix, normalized);
2857
+ return this.createFile({
2858
+ kind: 'file',
2859
+ name: basename(normalized),
2860
+ mode: state.mode,
2861
+ ref: {
2862
+ kind: 'file',
2863
+ root: state.root,
2864
+ relativePath: fullPath,
2865
+ },
2866
+ });
2867
+ }, false);
2868
+ defineReadonlyValue(target, 'directory', (relativePath) => {
2869
+ const normalized = normalizeRelativePath(relativePath);
2870
+ const fullPath = joinRelativePath(state.prefix, normalized);
2871
+ return this.createDirectory({
2872
+ kind: 'directory',
2873
+ name: basename(normalized),
2874
+ mode: state.mode,
2875
+ ref: {
2876
+ kind: 'directory',
2877
+ root: state.root,
2878
+ relativePath: fullPath,
2879
+ },
2880
+ });
2881
+ }, false);
2882
+ }
2883
+ assertFileDescriptor(descriptor) {
2884
+ if (!descriptor || descriptor.kind !== 'file') {
2885
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的 File descriptor');
2886
+ }
2887
+ assertName(descriptor.name);
2888
+ assertMode(descriptor.mode);
2889
+ assertFileRef(descriptor.ref);
2890
+ }
2891
+ assertDirectoryDescriptor(descriptor) {
2892
+ if (!descriptor || descriptor.kind !== 'directory') {
2893
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的 Directory descriptor');
2894
+ }
2895
+ assertName(descriptor.name);
2896
+ assertMode(descriptor.mode);
2897
+ assertDirectoryRef(descriptor.ref);
2898
+ }
2899
+ }
2900
+ function getFileHandleRegistry(requester) {
2901
+ let registry = registries.get(requester);
2902
+ if (!registry) {
2903
+ registry = new MiniProgramFileHandleRegistry(requester);
2904
+ registries.set(requester, registry);
2905
+ }
2906
+ return registry;
2907
+ }
2908
+ function isMiniProgramFileSystemValue(value) {
2909
+ return !!value && typeof value === 'object' && brandedFileSystemValues.has(value);
2910
+ }
2911
+
2325
2912
  const LOG_PREFIX = '[hb-sdk][network.request]';
2326
2913
  /**
2327
2914
  * 记录 `network.request` 失败,保证异常路径在开发者控制台可观测。
@@ -2523,6 +3110,194 @@ function assertSupportedPublicNetworkRequest(config) {
2523
3110
  if (isUnsupportedMultipartNetworkRequest(config)) {
2524
3111
  throw createSDKError('INVALID_PARAMS', NETWORK_REQUEST_MULTIPART_UNSUPPORTED_MESSAGE);
2525
3112
  }
3113
+ if (containsUnsupportedNetworkData(config.data)) {
3114
+ throw createSDKError('INVALID_PARAMS', 'network.request data 不支持 FileHandle、FileSystem、Blob、FormData 或二进制数据');
3115
+ }
3116
+ }
3117
+ function containsUnsupportedNetworkData(value, visiting = new WeakSet(), scanned = new WeakSet()) {
3118
+ if (isMiniProgramFileSystemValue(value))
3119
+ return true;
3120
+ if (typeof Blob !== 'undefined' && value instanceof Blob)
3121
+ return true;
3122
+ if (typeof FormData !== 'undefined' && value instanceof FormData)
3123
+ return true;
3124
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value))
3125
+ return true;
3126
+ if (typeof SharedArrayBuffer !== 'undefined' && value instanceof SharedArrayBuffer)
3127
+ return true;
3128
+ if (!value || typeof value !== 'object' || value instanceof URLSearchParams)
3129
+ return false;
3130
+ if (visiting.has(value))
3131
+ return true;
3132
+ if (scanned.has(value))
3133
+ return false;
3134
+ visiting.add(value);
3135
+ let unsupported = false;
3136
+ if (Array.isArray(value))
3137
+ unsupported = value.some((item) => containsUnsupportedNetworkData(item, visiting, scanned));
3138
+ if (isPlainObject(value)) {
3139
+ unsupported ||= Object.values(value).some((item) => containsUnsupportedNetworkData(item, visiting, scanned));
3140
+ }
3141
+ visiting.delete(value);
3142
+ if (!unsupported)
3143
+ scanned.add(value);
3144
+ return unsupported;
3145
+ }
3146
+ function assertFinitePositive(value, name) {
3147
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
3148
+ throw createSDKError('INVALID_PARAMS', `${name} 必须是有限正数`);
3149
+ }
3150
+ }
3151
+ function normalizeDownloadProgress(value) {
3152
+ if (!value || typeof value !== 'object') {
3153
+ throw createSDKError('INVALID_NETWORK_RESPONSE', 'network.download 返回了无效的进度');
3154
+ }
3155
+ if (!isMiniProgramDownloadProgressPayload(value)) {
3156
+ throw createSDKError('INVALID_NETWORK_RESPONSE', 'network.download 返回了无效的进度');
3157
+ }
3158
+ return {
3159
+ loaded: value.loaded,
3160
+ ...(value.total === undefined ? {} : { total: value.total }),
3161
+ lengthComputable: value.lengthComputable,
3162
+ };
3163
+ }
3164
+ function normalizeDownloadPayload(options, to, isFileTarget) {
3165
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
3166
+ throw createSDKError('INVALID_PARAMS', 'network.download options 必须是对象');
3167
+ }
3168
+ if (typeof options.url !== 'string' || options.url.length === 0) {
3169
+ throw createSDKError('INVALID_PARAMS', 'url 必须是非空字符串');
3170
+ }
3171
+ if (options.params !== undefined && !isPlainObject(options.params)) {
3172
+ throw createSDKError('INVALID_PARAMS', 'params 必须是对象');
3173
+ }
3174
+ if (options.headers !== undefined) {
3175
+ if (!isPlainObject(options.headers) || Object.values(options.headers).some((value) => typeof value !== 'string')) {
3176
+ throw createSDKError('INVALID_PARAMS', 'headers 必须是字符串字典');
3177
+ }
3178
+ }
3179
+ if (options.timeout !== undefined) {
3180
+ assertFinitePositive(options.timeout, 'timeout');
3181
+ }
3182
+ if (options.maxBytes !== undefined && (!Number.isSafeInteger(options.maxBytes) || options.maxBytes <= 0)) {
3183
+ throw createSDKError('INVALID_PARAMS', 'maxBytes 必须是正的 safe integer');
3184
+ }
3185
+ if (options.withCredentials !== undefined && typeof options.withCredentials !== 'boolean') {
3186
+ throw createSDKError('INVALID_PARAMS', 'withCredentials 必须是 boolean');
3187
+ }
3188
+ if (options.onProgress !== undefined && typeof options.onProgress !== 'function') {
3189
+ throw createSDKError('INVALID_PARAMS', 'onProgress 必须是函数');
3190
+ }
3191
+ if (options.signal !== undefined &&
3192
+ (typeof options.signal !== 'object' ||
3193
+ typeof options.signal.aborted !== 'boolean' ||
3194
+ typeof options.signal.addEventListener !== 'function' ||
3195
+ typeof options.signal.removeEventListener !== 'function')) {
3196
+ throw createSDKError('INVALID_PARAMS', 'signal 必须是 AbortSignal');
3197
+ }
3198
+ if ('method' in options || 'data' in options || 'body' in options) {
3199
+ throw createSDKError('INVALID_PARAMS', 'network.download 固定使用 GET 且不接受 body');
3200
+ }
3201
+ const base = {
3202
+ url: options.url,
3203
+ to,
3204
+ ...(options.params === undefined ? {} : { params: clonePublicValue(options.params) }),
3205
+ ...(options.headers === undefined ? {} : { headers: clonePublicValue(options.headers) }),
3206
+ ...(options.timeout === undefined ? {} : { timeout: options.timeout }),
3207
+ ...(options.withCredentials === undefined ? {} : { withCredentials: options.withCredentials }),
3208
+ ...(options.maxBytes === undefined ? {} : { maxBytes: options.maxBytes }),
3209
+ };
3210
+ if (isFileTarget) {
3211
+ if (options.suggestedName !== undefined || options.overwrite !== undefined) {
3212
+ throw createSDKError('INVALID_PARAMS', 'File target 不接受 suggestedName 或 overwrite');
3213
+ }
3214
+ return base;
3215
+ }
3216
+ if (options.overwrite !== undefined && typeof options.overwrite !== 'boolean') {
3217
+ throw createSDKError('INVALID_PARAMS', 'overwrite 必须是 boolean');
3218
+ }
3219
+ let suggestedName;
3220
+ if (options.suggestedName !== undefined) {
3221
+ suggestedName = normalizeRelativePath(options.suggestedName);
3222
+ if (suggestedName.includes('/')) {
3223
+ throw createSDKError('INVALID_PARAMS', 'suggestedName 必须是 basename');
3224
+ }
3225
+ }
3226
+ return {
3227
+ ...base,
3228
+ ...(suggestedName === undefined ? {} : { suggestedName }),
3229
+ ...(options.overwrite === undefined ? {} : { overwrite: options.overwrite }),
3230
+ };
3231
+ }
3232
+ function isSamePathRoot(left, right) {
3233
+ if (left.kind !== right.kind)
3234
+ return false;
3235
+ return left.kind === 'sandbox' || (right.kind === 'directory' && left.handleId === right.handleId);
3236
+ }
3237
+ function isSameFileRef(left, right) {
3238
+ if ('handleId' in left || 'handleId' in right) {
3239
+ return 'handleId' in left && 'handleId' in right && left.handleId === right.handleId;
3240
+ }
3241
+ return left.relativePath === right.relativePath && isSamePathRoot(left.root, right.root);
3242
+ }
3243
+ function isDirectChildFileRef(directory, file) {
3244
+ if ('handleId' in file)
3245
+ return false;
3246
+ if ('handleId' in directory) {
3247
+ return file.root.kind === 'directory' && file.root.handleId === directory.handleId && !file.relativePath.includes('/');
3248
+ }
3249
+ if (!isSamePathRoot(directory.root, file.root))
3250
+ return false;
3251
+ const prefix = `${directory.relativePath}/`;
3252
+ if (!file.relativePath.startsWith(prefix))
3253
+ return false;
3254
+ const childName = file.relativePath.slice(prefix.length);
3255
+ return childName.length > 0 && !childName.includes('/');
3256
+ }
3257
+ function invalidDownloadResult(message) {
3258
+ throw createSDKError('INVALID_NETWORK_RESPONSE', message);
3259
+ }
3260
+ /** 发起不使用 bridge 固定总时长 timer 的流式下载。 */
3261
+ async function download(requester, options) {
3262
+ if (options?.signal?.aborted) {
3263
+ throw createSDKError('DOWNLOAD_CANCELLED', '下载已取消');
3264
+ }
3265
+ const registry = getFileHandleRegistry(requester);
3266
+ const isFileTarget = options?.to?.kind === 'file';
3267
+ const target = isFileTarget ? registry.requireFile(options?.to) : registry.requireDirectory(options?.to);
3268
+ if (target.mode !== 'readwrite') {
3269
+ throw createSDKError('FILE_ACCESS_DENIED', '下载 target 必须是 readwrite');
3270
+ }
3271
+ const payload = normalizeDownloadPayload(options, target.ref, isFileTarget);
3272
+ const result = await requester.requestOperation(NETWORK_DOWNLOAD_METHOD, payload, {
3273
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
3274
+ ...(options.onProgress === undefined
3275
+ ? {}
3276
+ : {
3277
+ onProgress: (progress) => {
3278
+ options.onProgress?.(normalizeDownloadProgress(progress));
3279
+ },
3280
+ }),
3281
+ createAbortError: () => createSDKError('DOWNLOAD_CANCELLED', '下载已取消'),
3282
+ });
3283
+ const resultHandle = registry.createFile(result);
3284
+ const resultState = registry.requireFile(resultHandle);
3285
+ if (resultHandle.mode !== 'readwrite') {
3286
+ invalidDownloadResult('network.download 返回的 File 不是 readwrite');
3287
+ }
3288
+ if (isFileTarget) {
3289
+ const original = options.to;
3290
+ if (resultHandle.name !== original.name || !isSameFileRef(target.ref, resultState.ref)) {
3291
+ invalidDownloadResult('network.download 返回的 File 与 target 不一致');
3292
+ }
3293
+ return original;
3294
+ }
3295
+ if (!isDirectChildFileRef(target.ref, resultState.ref) ||
3296
+ 'handleId' in resultState.ref ||
3297
+ resultHandle.name !== resultState.ref.relativePath.split('/').at(-1)) {
3298
+ invalidDownloadResult('network.download 返回的 File 不在 target Directory 内');
3299
+ }
3300
+ return resultHandle;
2526
3301
  }
2527
3302
  /**
2528
3303
  * 创建 network 模块。
@@ -2533,9 +3308,115 @@ function assertSupportedPublicNetworkRequest(config) {
2533
3308
  function createNetworkModule(requester) {
2534
3309
  return {
2535
3310
  request: (config) => request(requester, config),
3311
+ download: (options) => download(requester, options),
2536
3312
  };
2537
3313
  }
2538
3314
 
3315
+ function invalidParams(message) {
3316
+ return createSDKError('INVALID_PARAMS', message);
3317
+ }
3318
+ function normalizeMode(mode) {
3319
+ if (mode === undefined) {
3320
+ return undefined;
3321
+ }
3322
+ if (mode !== 'read' && mode !== 'readwrite') {
3323
+ throw invalidParams('mode 必须是 read 或 readwrite');
3324
+ }
3325
+ return mode;
3326
+ }
3327
+ function assertOptionsObject(value, name) {
3328
+ if (value !== undefined && (!value || typeof value !== 'object' || Array.isArray(value))) {
3329
+ throw invalidParams(`${name} 必须是对象`);
3330
+ }
3331
+ }
3332
+ function normalizeAccept(accept) {
3333
+ if (accept === undefined) {
3334
+ return undefined;
3335
+ }
3336
+ if (!Array.isArray(accept)) {
3337
+ throw invalidParams('accept 必须是扩展名数组');
3338
+ }
3339
+ return accept.map((extension) => {
3340
+ if (typeof extension !== 'string' ||
3341
+ !/^\.[a-z0-9][a-z0-9._-]*$/i.test(extension)) {
3342
+ throw invalidParams('accept 只能包含规范扩展名,例如 .json');
3343
+ }
3344
+ return extension;
3345
+ });
3346
+ }
3347
+ function normalizePickFilesOptions(options) {
3348
+ assertOptionsObject(options, 'pickFiles options');
3349
+ if (options?.multiple !== undefined && typeof options.multiple !== 'boolean') {
3350
+ throw invalidParams('multiple 必须是 boolean');
3351
+ }
3352
+ const mode = normalizeMode(options?.mode);
3353
+ const accept = normalizeAccept(options?.accept);
3354
+ if (options === undefined) {
3355
+ return undefined;
3356
+ }
3357
+ return {
3358
+ ...(mode === undefined ? {} : { mode }),
3359
+ ...(accept === undefined ? {} : { accept }),
3360
+ ...(options.multiple === undefined ? {} : { multiple: options.multiple }),
3361
+ };
3362
+ }
3363
+ function normalizePickDirectoryOptions(options) {
3364
+ assertOptionsObject(options, 'pickDirectory options');
3365
+ const mode = normalizeMode(options?.mode);
3366
+ return options === undefined ? undefined : mode === undefined ? {} : { mode };
3367
+ }
3368
+ function normalizeSaveFileOptions(options) {
3369
+ assertOptionsObject(options, 'saveFile options');
3370
+ if (!options || typeof options.suggestedName !== 'string') {
3371
+ throw invalidParams('suggestedName 必须是非空字符串');
3372
+ }
3373
+ const suggestedName = normalizeRelativePath(options.suggestedName);
3374
+ if (suggestedName.includes('/')) {
3375
+ throw invalidParams('suggestedName 必须是 basename');
3376
+ }
3377
+ if ('accept' in options) {
3378
+ throw invalidParams('saveFile V1 不支持 accept');
3379
+ }
3380
+ return { suggestedName };
3381
+ }
3382
+ /** 创建当前 SDK session 的 files 模块。 */
3383
+ function createFilesModule(requester) {
3384
+ const registry = getFileHandleRegistry(requester);
3385
+ return Object.freeze({
3386
+ sandbox: registry.getSandbox(),
3387
+ async pickFiles(options) {
3388
+ const normalizedOptions = normalizePickFilesOptions(options);
3389
+ const descriptors = await requester.request(FILES_PICK_FILES_METHOD, normalizedOptions);
3390
+ if (!Array.isArray(descriptors) || descriptors.length === 0 || (normalizedOptions?.multiple !== true && descriptors.length > 1)) {
3391
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回了无效的 picker 结果');
3392
+ }
3393
+ const expectedMode = normalizedOptions?.mode ?? 'read';
3394
+ const handles = descriptors.map((descriptor) => registry.createDirectFile(descriptor));
3395
+ if (handles.some((handle) => handle.mode !== expectedMode)) {
3396
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的 File mode 与 picker 请求不一致');
3397
+ }
3398
+ return handles;
3399
+ },
3400
+ async pickDirectory(options) {
3401
+ const normalizedOptions = normalizePickDirectoryOptions(options);
3402
+ const descriptor = await requester.request(FILES_PICK_DIRECTORY_METHOD, normalizedOptions);
3403
+ const handle = registry.createDirectDirectory(descriptor);
3404
+ if (handle.mode !== (normalizedOptions?.mode ?? 'read')) {
3405
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的 Directory mode 与 picker 请求不一致');
3406
+ }
3407
+ return handle;
3408
+ },
3409
+ async saveFile(options) {
3410
+ const descriptor = await requester.request(FILES_SAVE_FILE_METHOD, normalizeSaveFileOptions(options));
3411
+ const handle = registry.createDirectFile(descriptor);
3412
+ if (handle.mode !== 'readwrite') {
3413
+ throw createSDKError('FILE_IO_FAILED', 'Host 返回的 saveFile 不是 readwrite 授权');
3414
+ }
3415
+ return handle;
3416
+ },
3417
+ });
3418
+ }
3419
+
2539
3420
  /**
2540
3421
  * 获取当前小程序窗口信息。
2541
3422
  *
@@ -2782,6 +3663,8 @@ class MiniProgramSDK {
2782
3663
  storage;
2783
3664
  /** 网络请求相关开放能力。 */
2784
3665
  network;
3666
+ /** 受控文件与目录能力。 */
3667
+ files;
2785
3668
  /** UI 交互相关开放能力。 */
2786
3669
  ui;
2787
3670
  /** 设备相关开放能力。 */
@@ -2798,6 +3681,7 @@ class MiniProgramSDK {
2798
3681
  this.share = createShareModule(this.client, launchHref);
2799
3682
  this.viewport = createViewportModule(this.client);
2800
3683
  this.storage = createStorageModule(this.client);
3684
+ this.files = createFilesModule(this.client);
2801
3685
  this.network = createNetworkModule(this.client);
2802
3686
  this.ui = createUiModule(this.client);
2803
3687
  this.device = createDeviceModule(this.client);
@@ -2979,6 +3863,16 @@ const storage = {
2979
3863
  /** 默认 SDK 实例的 network 模块。 */
2980
3864
  const network = {
2981
3865
  request: (config) => getDefaultSDK().network.request(config),
3866
+ download: (options) => getDefaultSDK().network.download(options),
3867
+ };
3868
+ /** 默认 SDK 实例的 files 模块。 */
3869
+ const files = {
3870
+ get sandbox() {
3871
+ return getDefaultSDK().files.sandbox;
3872
+ },
3873
+ pickFiles: (options) => getDefaultSDK().files.pickFiles(options),
3874
+ pickDirectory: (options) => getDefaultSDK().files.pickDirectory(options),
3875
+ saveFile: (options) => getDefaultSDK().files.saveFile(options),
2982
3876
  };
2983
3877
  /** 默认 SDK 实例的 UI 模块。 */
2984
3878
  const ui = {
@@ -3018,6 +3912,7 @@ const hbSDK = {
3018
3912
  share,
3019
3913
  viewport,
3020
3914
  storage,
3915
+ files,
3021
3916
  network,
3022
3917
  ui,
3023
3918
  device,
@@ -3025,4 +3920,4 @@ const hbSDK = {
3025
3920
  cloud,
3026
3921
  };
3027
3922
 
3028
- export { HbMiniProgramNetworkError, HbMiniProgramSDKError, USER_INFO_AUTHORIZATION_SCOPES, auth, cloud, hbSDK as default, device, getHandshakeState, navigation, network, off, on, onHandshakeStateChange, share, storage, ui, user, viewport };
3923
+ export { HbMiniProgramNetworkError, HbMiniProgramSDKError, USER_INFO_AUTHORIZATION_SCOPES, auth, cloud, hbSDK as default, device, files, getHandshakeState, navigation, network, off, on, onHandshakeStateChange, share, storage, ui, user, viewport };