@dimina-kit/devtools 0.4.0-dev.20260729062524 → 0.4.0-dev.20260801163345

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 (104) hide show
  1. package/dist/main/app/app.js +7 -0
  2. package/dist/main/app/bootstrap.d.ts +8 -5
  3. package/dist/main/app/bootstrap.js +15 -15
  4. package/dist/main/index.bundle.js +2207 -5152
  5. package/dist/main/ipc/app-lifecycle.d.ts +1 -31
  6. package/dist/main/ipc/app-lifecycle.js +1 -53
  7. package/dist/main/ipc/bridge-router.d.ts +19 -152
  8. package/dist/main/ipc/bridge-router.js +28 -2186
  9. package/dist/main/ipc/page-scroll.d.ts +1 -13
  10. package/dist/main/ipc/page-scroll.js +1 -16
  11. package/dist/main/ipc/session-listener-bag.d.ts +1 -40
  12. package/dist/main/ipc/session-listener-bag.js +1 -39
  13. package/dist/main/services/console-forward/index.d.ts +1 -60
  14. package/dist/main/services/console-forward/index.js +1 -225
  15. package/dist/main/services/console-forward/internal-log.d.ts +1 -10
  16. package/dist/main/services/console-forward/internal-log.js +1 -22
  17. package/dist/main/services/diagnostics/index.d.ts +1 -58
  18. package/dist/main/services/diagnostics/index.js +1 -60
  19. package/dist/main/services/dimina-resource-server.d.ts +1 -6
  20. package/dist/main/services/dimina-resource-server.js +1 -98
  21. package/dist/main/services/mcp/target-manager.js +5 -1
  22. package/dist/main/services/network-forward/index.d.ts +15 -3
  23. package/dist/main/services/network-forward/index.js +30 -4
  24. package/dist/main/services/network-forward/websocket.d.ts +36 -0
  25. package/dist/main/services/network-forward/websocket.js +120 -0
  26. package/dist/main/services/service-host-pool/pool.d.ts +1 -196
  27. package/dist/main/services/service-host-pool/pool.js +1 -363
  28. package/dist/main/services/simulator/custom-apis.d.ts +1 -42
  29. package/dist/main/services/simulator/custom-apis.js +1 -46
  30. package/dist/main/services/simulator/referer.d.ts +1 -9
  31. package/dist/main/services/simulator/referer.js +1 -19
  32. package/dist/main/services/simulator-storage/service-storage-ops.d.ts +1 -39
  33. package/dist/main/services/simulator-storage/service-storage-ops.js +1 -66
  34. package/dist/main/services/simulator-temp-files/disk.d.ts +1 -54
  35. package/dist/main/services/simulator-temp-files/disk.js +1 -237
  36. package/dist/main/services/simulator-temp-files/fs-channels.d.ts +1 -68
  37. package/dist/main/services/simulator-temp-files/fs-channels.js +1 -107
  38. package/dist/main/services/simulator-temp-files/index.d.ts +1 -32
  39. package/dist/main/services/simulator-temp-files/index.js +1 -181
  40. package/dist/main/services/simulator-temp-files/request-handler.d.ts +1 -29
  41. package/dist/main/services/simulator-temp-files/request-handler.js +1 -177
  42. package/dist/main/services/simulator-temp-files/resolver.d.ts +1 -21
  43. package/dist/main/services/simulator-temp-files/resolver.js +1 -21
  44. package/dist/main/services/simulator-temp-files/store.d.ts +1 -13
  45. package/dist/main/services/simulator-temp-files/store.js +1 -23
  46. package/dist/main/services/views/miniapp-partition.d.ts +1 -89
  47. package/dist/main/services/views/miniapp-partition.js +1 -158
  48. package/dist/main/services/views/simulator-session-policy.d.ts +1 -27
  49. package/dist/main/services/views/simulator-session-policy.js +1 -98
  50. package/dist/main/services/workbench-context.d.ts +4 -1
  51. package/dist/main/services/workbench-context.js +17 -0
  52. package/dist/main/utils/ipc-registry.d.ts +21 -3
  53. package/dist/main/utils/ipc-registry.js +26 -23
  54. package/dist/main/utils/paths.d.ts +14 -0
  55. package/dist/main/utils/paths.js +14 -0
  56. package/dist/main/windows/service-host-window/create.d.ts +1 -92
  57. package/dist/main/windows/service-host-window/create.js +1 -154
  58. package/dist/native-host/common/common.js +154 -58
  59. package/dist/native-host/container/pageFrame.css +1 -1
  60. package/dist/native-host/render/render.js +6611 -3312
  61. package/dist/native-host/service/service.js +2 -2
  62. package/dist/preload/index.d.ts +2 -0
  63. package/dist/preload/index.js +1 -0
  64. package/dist/preload/runtime/clipboard.d.ts +6 -0
  65. package/dist/preload/runtime/clipboard.js +12 -0
  66. package/dist/preload/runtime/native-host.d.ts +2 -0
  67. package/dist/preload/runtime/native-host.js +5 -10
  68. package/dist/preload/runtime/node-bindings.d.ts +18 -0
  69. package/dist/preload/runtime/node-bindings.js +44 -0
  70. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  71. package/dist/preload/windows/main.cjs +1195 -897
  72. package/dist/preload/windows/main.cjs.map +4 -4
  73. package/dist/preload/windows/simulator.cjs +149 -60
  74. package/dist/preload/windows/simulator.cjs.map +4 -4
  75. package/dist/preload/windows/simulator.js +149 -60
  76. package/dist/render-host/pageFrame.html +3 -3
  77. package/dist/shared/bridge-channels.d.ts +1 -419
  78. package/dist/shared/bridge-channels.js +1 -89
  79. package/dist/shared/dmb-resource-url.d.ts +2 -0
  80. package/dist/shared/dmb-resource-url.js +2 -0
  81. package/dist/shared/ipc-channels.d.ts +2 -32
  82. package/dist/shared/ipc-channels.js +2 -5
  83. package/dist/shared/node-bindings.d.ts +2 -0
  84. package/dist/shared/node-bindings.js +2 -0
  85. package/dist/shared/request-core.d.ts +1 -83
  86. package/dist/shared/request-core.js +1 -167
  87. package/dist/simulator/assets/device-shell-Cw7c-IkG.js +2 -0
  88. package/dist/simulator/assets/{device-shell-BHz1v3Yo.css → device-shell-DbC5ccs8.css} +1 -1
  89. package/dist/simulator/assets/{jsx-runtime-hBv-rHqQ.js → jsx-runtime-DkKmJyBb.js} +2 -2
  90. package/dist/simulator/assets/simulator-COIE6R49.js +10 -0
  91. package/dist/simulator/assets/simulator-mini-app-BLn7XV8e.js +2 -0
  92. package/dist/simulator/simulator.html +2 -2
  93. package/dist/simulator/temp-files.d.ts +29 -0
  94. package/dist/simulator/temp-files.js +79 -0
  95. package/dist/simulator/types.d.ts +43 -0
  96. package/dist/simulator/types.js +2 -0
  97. package/package.json +6 -5
  98. package/dist/shared/simulator-api-metadata.d.ts +0 -26
  99. package/dist/shared/simulator-api-metadata.js +0 -59
  100. package/dist/shared/vpath.d.ts +0 -42
  101. package/dist/shared/vpath.js +0 -105
  102. package/dist/simulator/assets/device-shell-CH_DMYaz.js +0 -2
  103. package/dist/simulator/assets/simulator-DXraYerh.js +0 -10
  104. package/dist/simulator/assets/simulator-mini-app-w1Bhwkld.js +0 -2
@@ -28,7 +28,7 @@ var main_exports = {};
28
28
  module.exports = __toCommonJS(main_exports);
29
29
  var import_electron = require("electron");
30
30
 
31
- // src/shared/bridge-channels.ts
31
+ // ../dimina-electron-runtime/dist/shared/bridge-channels.js
32
32
  var BRIDGE_CHANNELS = {
33
33
  SPAWN: "dmb:spawn",
34
34
  DISPOSE: "dmb:dispose",
@@ -108,8 +108,8 @@ function makeBaseContext() {
108
108
  }
109
109
  };
110
110
  }
111
- function runApiAsync(handlers, name, params) {
112
- const handler = handlers[name];
111
+ function runApiAsync(handlers2, name, params) {
112
+ const handler = handlers2[name];
113
113
  if (!handler) {
114
114
  return Promise.resolve({ ok: false, errMsg: `${name}:fail no handler` });
115
115
  }
@@ -322,36 +322,45 @@ function vibrateLong({ success, complete } = {}) {
322
322
  onComplete?.();
323
323
  }
324
324
  var scanCode = notSupportedApi("scanCode");
325
+ function getClipboardBridge() {
326
+ return window.__diminaClipboard;
327
+ }
325
328
  function getClipboardData({ success, fail, complete } = {}) {
326
329
  const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
327
- return navigator.clipboard.readText().then(
328
- (data) => {
329
- onSuccess?.({ data, errMsg: "getClipboardData:ok" });
330
- onComplete?.();
331
- },
332
- (error) => {
333
- onFail?.({ errMsg: `getClipboardData:fail ${error?.message ?? error}` });
334
- onComplete?.();
335
- }
336
- );
330
+ const bridge = getClipboardBridge();
331
+ if (!bridge) {
332
+ onFail?.({ errMsg: "getClipboardData:fail clipboard bridge unavailable" });
333
+ onComplete?.();
334
+ return;
335
+ }
336
+ try {
337
+ const data = bridge.readText();
338
+ onSuccess?.({ data, errMsg: "getClipboardData:ok" });
339
+ } catch (error) {
340
+ onFail?.({ errMsg: `getClipboardData:fail ${error?.message ?? error}` });
341
+ }
342
+ onComplete?.();
337
343
  }
338
344
  function setClipboardData({ data, success, fail, complete } = {}) {
339
345
  const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
340
346
  if (typeof data !== "string") {
341
347
  onFail?.({ errMsg: "setClipboardData:fail data is required" });
342
348
  onComplete?.();
343
- return Promise.resolve();
349
+ return;
344
350
  }
345
- return navigator.clipboard.writeText(data).then(
346
- () => {
347
- onSuccess?.({ errMsg: "setClipboardData:ok" });
348
- onComplete?.();
349
- },
350
- (error) => {
351
- onFail?.({ errMsg: `setClipboardData:fail ${error?.message ?? error}` });
352
- onComplete?.();
353
- }
354
- );
351
+ const bridge = getClipboardBridge();
352
+ if (!bridge) {
353
+ onFail?.({ errMsg: "setClipboardData:fail clipboard bridge unavailable" });
354
+ onComplete?.();
355
+ return;
356
+ }
357
+ try {
358
+ bridge.writeText(data);
359
+ onSuccess?.({ errMsg: "setClipboardData:ok" });
360
+ } catch (error) {
361
+ onFail?.({ errMsg: `setClipboardData:fail ${error?.message ?? error}` });
362
+ }
363
+ onComplete?.();
355
364
  }
356
365
  function mapEffectiveType(effectiveType) {
357
366
  switch (effectiveType) {
@@ -403,6 +412,71 @@ function bindDomEvents(target, eventMap, fire, buildPayload) {
403
412
  };
404
413
  }
405
414
 
415
+ // ../dimina-electron-runtime/dist/shared/node-bindings.js
416
+ var NODE_BINDINGS_GLOBAL = "__diminaNodeBindings";
417
+ function getNodeBindings() {
418
+ const bag = globalThis[NODE_BINDINGS_GLOBAL];
419
+ return bag && typeof bag === "object" ? bag : {};
420
+ }
421
+
422
+ // ../dimina-electron-runtime/dist/shared/vpath.js
423
+ var import_node_os = __toESM(require("node:os"), 1);
424
+ var import_node_path = __toESM(require("node:path"), 1);
425
+ var os = typeof import_node_os.default?.homedir === "function" ? import_node_os.default : getNodeBindings().os ?? import_node_os.default;
426
+ var path = typeof import_node_path.default?.join === "function" ? import_node_path.default : getNodeBindings().path ?? import_node_path.default;
427
+ var DIFILE_PREFIX = "difile://";
428
+ function sandboxBase() {
429
+ const env = typeof process !== "undefined" && process.env && process.env.DIMINA_HOME || "";
430
+ if (env) {
431
+ return path.join(env, "files");
432
+ }
433
+ return path.join(os.homedir(), ".dimina", "files");
434
+ }
435
+ function resolveVPath(url) {
436
+ if (typeof url !== "string")
437
+ return null;
438
+ if (!url.startsWith(DIFILE_PREFIX))
439
+ return null;
440
+ const raw = url.slice(DIFILE_PREFIX.length);
441
+ if (raw.length === 0)
442
+ return null;
443
+ let decoded;
444
+ try {
445
+ decoded = decodeURIComponent(raw);
446
+ } catch {
447
+ return null;
448
+ }
449
+ if (decoded.includes("\0"))
450
+ return null;
451
+ decoded = decoded.replace(/^[/\\]+/, "");
452
+ if (decoded.length === 0)
453
+ return null;
454
+ const segments = decoded.split(/[/\\]+/);
455
+ if (segments.some((s) => s === ".." || s === "."))
456
+ return null;
457
+ let kind;
458
+ let writable;
459
+ if (decoded.startsWith("_tmp/")) {
460
+ kind = "tmp";
461
+ writable = false;
462
+ } else if (decoded.startsWith("_store/")) {
463
+ kind = "store";
464
+ writable = false;
465
+ } else {
466
+ kind = "usr";
467
+ writable = true;
468
+ }
469
+ if (kind === "tmp") {
470
+ return { kind, writable };
471
+ }
472
+ const base = sandboxBase();
473
+ const joined = path.join(base, decoded);
474
+ const normalized = path.normalize(joined);
475
+ if (normalized !== base && !normalized.startsWith(base + path.sep))
476
+ return null;
477
+ return { kind, writable, realPath: normalized };
478
+ }
479
+
406
480
  // src/simulator/temp-files.ts
407
481
  var tempFiles = /* @__PURE__ */ new Map();
408
482
  var activeSink = null;
@@ -442,988 +516,1227 @@ function getTempFileName(path2, blob, fallback = "file") {
442
516
  return fallback;
443
517
  }
444
518
 
445
- // src/simulator/simulator-api-media.ts
446
- function captureAttrFor(sourceType, camera) {
447
- if (sourceType.length !== 1 || sourceType[0] !== "camera") return void 0;
448
- return camera === "front" ? "user" : "environment";
519
+ // src/simulator/simulator-api-fs.ts
520
+ async function _tmpBytes(url) {
521
+ try {
522
+ const blob = await resolveTempFilePath(url);
523
+ return await blobToBuffer(blob);
524
+ } catch (err) {
525
+ const msg = err instanceof Error ? err.message : String(err);
526
+ throw new Error(`ENOENT: no such file (${url}): ${msg}`, { cause: err });
527
+ }
449
528
  }
450
- function createFilePicker(apiName, config, cbs, onPick) {
451
- const input = document.createElement("input");
452
- input.type = "file";
453
- input.accept = config.accept;
454
- input.multiple = config.multiple;
455
- if (config.capture) input.setAttribute("capture", config.capture);
456
- input.style.display = "none";
457
- document.body.appendChild(input);
458
- const removeInput = () => input.remove();
459
- input.addEventListener("change", () => {
460
- const files = Array.from(input.files || []);
461
- if (files.length === 0) {
462
- cbs.onFail?.({ errMsg: `${apiName}:fail cancel` });
463
- cbs.onComplete?.();
464
- removeInput();
465
- return;
466
- }
467
- void onPick(files, removeInput);
468
- });
469
- input.click();
529
+ function isArrayBuffer(value) {
530
+ return Object.prototype.toString.call(value) === "[object ArrayBuffer]";
470
531
  }
471
- function chooseImage({ count = 9, sourceType, camera, success, fail, complete }) {
472
- const cbs = bindCallbacks(this, { success, fail, complete });
473
- const { onSuccess, onComplete } = cbs;
474
- const normalizedCount = normalizeChooseMediaCount(count);
475
- const normalizedSourceType = normalizeStringArray(sourceType, ["album", "camera"]);
476
- createFilePicker(
477
- "chooseImage",
478
- { accept: "image/*", multiple: normalizedCount > 1, capture: captureAttrFor(normalizedSourceType, camera) },
479
- cbs,
480
- (rawFiles, removeInput) => {
481
- const files = rawFiles.slice(0, normalizedCount);
482
- const tempFilePaths = files.map((f) => createTempFilePath(f));
483
- const tempFiles2 = files.map((f, i) => ({ path: tempFilePaths[i], size: f.size }));
484
- onSuccess?.({ tempFilePaths, tempFiles: tempFiles2, errMsg: "chooseImage:ok" });
485
- onComplete?.();
486
- removeInput();
487
- }
488
- );
532
+ var BufferImpl = getNodeBindings().buffer?.Buffer ?? (typeof Buffer !== "undefined" ? Buffer : void 0);
533
+ async function blobToBuffer(blob) {
534
+ if (!BufferImpl) {
535
+ throw new Error("Buffer binding unavailable");
536
+ }
537
+ const readable = blob;
538
+ if (typeof readable.arrayBuffer === "function") {
539
+ const ab = await readable.arrayBuffer();
540
+ return BufferImpl.from(new Uint8Array(ab));
541
+ }
542
+ if (typeof FileReader !== "function") {
543
+ throw new Error("Blob cannot be read as ArrayBuffer");
544
+ }
545
+ return new Promise((resolve, reject) => {
546
+ const reader = new FileReader();
547
+ reader.onerror = () => reject(reader.error ?? new Error("Blob read failed"));
548
+ reader.onload = () => {
549
+ const result = reader.result;
550
+ if (!isArrayBuffer(result)) {
551
+ reject(new Error("Blob read did not return an ArrayBuffer"));
552
+ return;
553
+ }
554
+ resolve(BufferImpl.from(new Uint8Array(result)));
555
+ };
556
+ reader.readAsArrayBuffer(blob);
557
+ });
489
558
  }
490
- function previewImage({ urls, current, success, complete }) {
491
- const { onSuccess, onComplete } = bindCallbacks(this, { success, complete });
492
- if (!urls || urls.length === 0) {
493
- onComplete?.();
494
- return;
559
+ function resolveNodeModule(fromBindings, name, probe) {
560
+ if (fromBindings && probe(fromBindings)) return fromBindings;
561
+ if (typeof require !== "undefined") {
562
+ const mod = require(name);
563
+ if (mod && probe(mod)) return mod;
495
564
  }
496
- const overlay = document.createElement("div");
497
- overlay.style.cssText = "position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;cursor:pointer;";
498
- const img = document.createElement("img");
499
- img.src = current || urls[0] || "";
500
- img.style.cssText = "max-width:90%;max-height:90%;object-fit:contain;";
501
- overlay.appendChild(img);
502
- overlay.addEventListener("click", () => overlay.remove());
503
- document.body.appendChild(overlay);
504
- onSuccess?.({ errMsg: "previewImage:ok" });
505
- onComplete?.();
565
+ return null;
506
566
  }
507
- function compressImage({ src, quality = 80, success, fail, complete }) {
508
- const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
509
- const img = new Image();
510
- img.crossOrigin = "anonymous";
511
- img.onload = () => {
567
+ var _fs = resolveNodeModule(getNodeBindings().fs, "fs", (m) => typeof m.readFile === "function");
568
+ var _path = resolveNodeModule(getNodeBindings().path, "path", (m) => typeof m.join === "function");
569
+ var _crypto = resolveNodeModule(getNodeBindings().crypto, "crypto", (m) => typeof m.createHash === "function");
570
+ function guardFsAvailable(apiName, cbs) {
571
+ if (_fs) return false;
572
+ cbs.onFail?.({ errMsg: `${apiName}:fail not available in browser context` });
573
+ cbs.onComplete?.();
574
+ return true;
575
+ }
576
+ function containsSymlink(realPath) {
577
+ const rel = _path.relative(sandboxBase(), realPath);
578
+ if (!rel || rel.startsWith("..")) return false;
579
+ let cur = sandboxBase();
580
+ for (const seg of rel.split(_path.sep)) {
581
+ cur = _path.join(cur, seg);
582
+ let entry;
512
583
  try {
513
- const canvas = document.createElement("canvas");
514
- canvas.width = img.naturalWidth;
515
- canvas.height = img.naturalHeight;
516
- const ctx = canvas.getContext("2d");
517
- ctx.drawImage(img, 0, 0);
518
- canvas.toBlob(
519
- (blob) => {
520
- if (blob) {
521
- const tempFilePath = createTempFilePath(blob);
522
- onSuccess?.({ tempFilePath, errMsg: "compressImage:ok" });
523
- } else {
524
- onFail?.({ errMsg: "compressImage:fail compression error" });
525
- }
526
- onComplete?.();
527
- },
528
- "image/jpeg",
529
- quality / 100
530
- );
531
- } catch (error) {
532
- onFail?.({ errMsg: `compressImage:fail ${error.message}` });
533
- onComplete?.();
584
+ entry = _fs.lstatSync(cur);
585
+ } catch {
586
+ return false;
534
587
  }
535
- };
536
- img.onerror = () => {
537
- onFail?.({ errMsg: "compressImage:fail image load error" });
538
- onComplete?.();
539
- };
540
- img.src = src;
541
- }
542
- function saveImageToPhotosAlbum({ filePath, success, fail, complete }) {
543
- const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
544
- try {
545
- const a = document.createElement("a");
546
- a.href = filePath;
547
- a.download = "image";
548
- a.click();
549
- onSuccess?.({ errMsg: "saveImageToPhotosAlbum:ok" });
550
- } catch (error) {
551
- onFail?.({ errMsg: `saveImageToPhotosAlbum:fail ${error.message}` });
588
+ if (entry.isSymbolicLink()) return true;
552
589
  }
553
- onComplete?.();
590
+ return false;
554
591
  }
555
- function getImageInfo({ src, success, fail, complete }) {
556
- const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
557
- const img = new Image();
558
- img.crossOrigin = "anonymous";
559
- img.onload = () => {
560
- onSuccess?.({
561
- width: img.naturalWidth,
562
- height: img.naturalHeight,
563
- path: src,
564
- orientation: "up",
565
- type: "unknown",
566
- errMsg: "getImageInfo:ok"
567
- });
568
- onComplete?.();
569
- };
570
- img.onerror = () => {
571
- onFail?.({ errMsg: "getImageInfo:fail image load error" });
572
- onComplete?.();
592
+ function resolveOrBail(p, apiName, cbs) {
593
+ const v = resolveVPath(p);
594
+ if (v && (!v.realPath || !_fs || !containsSymlink(v.realPath))) return v;
595
+ cbs.onFail?.({ errMsg: `${apiName}:fail invalid or unsafe path` });
596
+ cbs.onComplete?.();
597
+ return void 0;
598
+ }
599
+ function toWxStats(s) {
600
+ return {
601
+ size: s.size,
602
+ mode: s.mode,
603
+ lastAccessedTime: Math.floor(s.atimeMs / 1e3),
604
+ lastModifiedTime: Math.floor(s.mtimeMs / 1e3),
605
+ isFile: s.isFile(),
606
+ isDirectory: s.isDirectory()
573
607
  };
574
- img.src = src;
575
608
  }
576
- var VIDEO_METADATA_TIMEOUT_MS = 5e3;
577
- var VIDEO_THUMBNAIL_TIMEOUT_MS = 500;
578
- function normalizeStringArray(value, fallback) {
579
- return Array.isArray(value) ? value.filter((item) => typeof item === "string") : fallback;
609
+ function ensureWritable(v, apiName, cbs) {
610
+ if (v.writable && v.realPath) return true;
611
+ cbs.onFail?.({ errMsg: `${apiName}:fail permission denied` });
612
+ cbs.onComplete?.();
613
+ return false;
580
614
  }
581
- function normalizeChooseMediaCount(value) {
582
- const n = Number(value);
583
- if (!Number.isFinite(n)) return 9;
584
- return Math.max(1, Math.min(20, Math.floor(n)));
615
+ function tmpFailHandler(apiName, cbs) {
616
+ return (err) => {
617
+ cbs.onFail?.({ errMsg: `${apiName}:fail ${err.message}` });
618
+ cbs.onComplete?.();
619
+ };
585
620
  }
586
- function getChooseMediaAccept(mediaType) {
587
- const wantsMix = mediaType.includes("mix");
588
- const wantsImage = wantsMix || mediaType.includes("image");
589
- const wantsVideo = wantsMix || mediaType.includes("video");
590
- if (wantsImage && wantsVideo) return "image/*,video/*";
591
- return wantsVideo ? "video/*" : "image/*";
592
- }
593
- function getChooseMediaResultType(files) {
594
- const types = new Set(files.map((file) => file.fileType));
595
- if (types.size > 1) return "mix";
596
- return files[0]?.fileType ?? "image";
621
+ function nodeComplete(apiName, cbs, buildOk) {
622
+ return (err, ...args) => {
623
+ if (err) {
624
+ cbs.onFail?.({ errMsg: `${apiName}:fail ${err.message}` });
625
+ } else {
626
+ cbs.onSuccess?.({ ...buildOk ? buildOk(...args) : void 0, errMsg: `${apiName}:ok` });
627
+ }
628
+ cbs.onComplete?.();
629
+ };
597
630
  }
598
- function readImageMetadata(src) {
599
- return new Promise((resolve) => {
600
- const img = new Image();
601
- img.onload = () => resolve({ width: img.naturalWidth || 0, height: img.naturalHeight || 0 });
602
- img.onerror = () => resolve({ width: 0, height: 0 });
603
- img.src = src;
631
+ function mkdirpThenWrite(destReal, apiName, cbs, writeFn, okExtra) {
632
+ _fs.mkdir(_path.dirname(destReal), { recursive: true }, (mkdirErr) => {
633
+ if (mkdirErr) {
634
+ cbs.onFail?.({ errMsg: `${apiName}:fail ${mkdirErr.message}` });
635
+ cbs.onComplete?.();
636
+ return;
637
+ }
638
+ try {
639
+ writeFn(nodeComplete(apiName, cbs, () => okExtra));
640
+ } catch (err) {
641
+ cbs.onFail?.({ errMsg: `${apiName}:fail ${err instanceof Error ? err.message : String(err)}` });
642
+ cbs.onComplete?.();
643
+ }
604
644
  });
605
645
  }
606
- function readVideoMetadata(src) {
607
- return new Promise((resolve) => {
608
- const video = document.createElement("video");
646
+ function fsAccess({ path: path2, success, fail, complete }) {
647
+ const cbs = bindCallbacks(this, { success, fail, complete });
648
+ const { onSuccess, onFail, onComplete } = cbs;
649
+ if (guardFsAvailable("fsAccess", cbs)) return;
650
+ const v = resolveOrBail(path2, "fsAccess", cbs);
651
+ if (!v) return;
652
+ if (v.kind === "tmp") {
653
+ _tmpBytes(path2).then(
654
+ () => {
655
+ onSuccess?.({ errMsg: "fsAccess:ok" });
656
+ onComplete?.();
657
+ },
658
+ tmpFailHandler("fsAccess", cbs)
659
+ );
660
+ return;
661
+ }
662
+ if (!v.realPath) {
663
+ onFail?.({ errMsg: "fsAccess:fail invalid path" });
664
+ onComplete?.();
665
+ return;
666
+ }
667
+ _fs.access(v.realPath, _fs.constants.F_OK, nodeComplete("fsAccess", cbs));
668
+ }
669
+ function fsStat({ path: path2, recursive = false, success, fail, complete }) {
670
+ const cbs = bindCallbacks(this, { success, fail, complete });
671
+ const { onSuccess, onFail, onComplete } = cbs;
672
+ if (guardFsAvailable("fsStat", cbs)) return;
673
+ const v = resolveOrBail(path2, "fsStat", cbs);
674
+ if (!v) return;
675
+ if (v.kind === "tmp") {
676
+ _tmpBytes(path2).then(
677
+ (buf) => {
678
+ onSuccess?.({
679
+ stats: {
680
+ size: buf.length,
681
+ mode: 0,
682
+ lastAccessedTime: 0,
683
+ lastModifiedTime: 0,
684
+ isFile: true,
685
+ isDirectory: false
686
+ },
687
+ errMsg: "fsStat:ok"
688
+ });
689
+ onComplete?.();
690
+ },
691
+ tmpFailHandler("fsStat", cbs)
692
+ );
693
+ return;
694
+ }
695
+ if (!v.realPath) {
696
+ onFail?.({ errMsg: "fsStat:fail invalid path" });
697
+ onComplete?.();
698
+ return;
699
+ }
700
+ const resolved = v.realPath;
701
+ if (recursive) {
702
+ const statsMap = {};
609
703
  let settled = false;
610
- let seekTimer;
611
- const finish = (metadata) => {
704
+ const failOnce = (err) => {
612
705
  if (settled) return;
613
706
  settled = true;
614
- clearTimeout(timer);
615
- if (seekTimer) clearTimeout(seekTimer);
616
- video.onloadedmetadata = null;
617
- video.onseeked = null;
618
- video.onerror = null;
619
- video.removeAttribute("src");
620
- video.load();
621
- resolve(metadata);
707
+ onFail?.({ errMsg: `fsStat:fail ${err.message}` });
708
+ onComplete?.();
622
709
  };
623
- const drawThumbnail = (width, height) => {
624
- return new Promise((resolveThumb) => {
625
- let resolved = false;
626
- const done = (value) => {
627
- if (resolved) return;
628
- resolved = true;
629
- resolveThumb(value);
630
- };
631
- let canvas;
632
- try {
633
- canvas = document.createElement("canvas");
634
- canvas.width = width || 1;
635
- canvas.height = height || 1;
636
- const ctx = canvas.getContext("2d");
637
- ctx?.drawImage(video, 0, 0, canvas.width, canvas.height);
638
- } catch {
639
- done("");
710
+ const okOnce = () => {
711
+ if (settled) return;
712
+ settled = true;
713
+ onSuccess?.({ stats: statsMap, errMsg: "fsStat:ok" });
714
+ onComplete?.();
715
+ };
716
+ const relKey = (full) => {
717
+ const rel = _path.relative(resolved, full);
718
+ return rel === "" ? "." : rel.split(_path.sep).join("/");
719
+ };
720
+ const statInto = (full, cb) => {
721
+ _fs.lstat(full, (err, s) => {
722
+ if (err) {
723
+ cb(err);
640
724
  return;
641
725
  }
642
- const toDataUrlFallback = () => {
643
- try {
644
- done(canvas.toDataURL("image/jpeg", 0.8));
645
- } catch {
646
- done("");
726
+ if (s.isSymbolicLink()) {
727
+ cb(new Error("invalid or unsafe path"));
728
+ return;
729
+ }
730
+ statsMap[relKey(full)] = toWxStats(s);
731
+ cb(null, s.isDirectory());
732
+ });
733
+ };
734
+ const walkDir = (dir, cb) => {
735
+ _fs.readdir(dir, { withFileTypes: true }, (err, entries) => {
736
+ if (err) {
737
+ cb(err);
738
+ return;
739
+ }
740
+ let pending = entries.length;
741
+ if (pending === 0) {
742
+ cb(null);
743
+ return;
744
+ }
745
+ let erred = false;
746
+ const done = (e) => {
747
+ if (erred) return;
748
+ if (e) {
749
+ erred = true;
750
+ cb(e);
751
+ return;
647
752
  }
753
+ if (--pending === 0) cb(null);
648
754
  };
649
- const fallbackTimer = setTimeout(toDataUrlFallback, VIDEO_THUMBNAIL_TIMEOUT_MS);
650
- try {
651
- canvas.toBlob(
652
- (blob) => {
653
- clearTimeout(fallbackTimer);
654
- if (settled || resolved) return;
655
- if (blob) {
656
- done(createTempFilePath(blob));
657
- } else {
658
- toDataUrlFallback();
659
- }
660
- },
661
- "image/jpeg",
662
- 0.8
663
- );
664
- } catch {
665
- clearTimeout(fallbackTimer);
666
- toDataUrlFallback();
755
+ for (const entry of entries) {
756
+ const full = _path.join(dir, entry.name);
757
+ statInto(full, (statErr, isDirectory) => {
758
+ if (statErr) {
759
+ done(statErr);
760
+ return;
761
+ }
762
+ if (isDirectory) walkDir(full, done);
763
+ else done(null);
764
+ });
667
765
  }
668
766
  });
669
767
  };
670
- const timer = setTimeout(() => finish({ width: 0, height: 0, duration: 0, thumbTempFilePath: "" }), VIDEO_METADATA_TIMEOUT_MS);
671
- video.preload = "metadata";
672
- video.muted = true;
673
- video.onloadedmetadata = () => {
674
- const width = video.videoWidth || 0;
675
- const height = video.videoHeight || 0;
676
- const duration = Number.isFinite(video.duration) ? video.duration : 0;
677
- const metadata = { width, height, duration, thumbTempFilePath: "" };
678
- if (!width || !height || duration <= 0) {
679
- finish(metadata);
768
+ statInto(resolved, (rootErr) => {
769
+ if (rootErr) {
770
+ failOnce(rootErr);
680
771
  return;
681
772
  }
682
- video.onseeked = async () => {
683
- if (seekTimer) {
684
- clearTimeout(seekTimer);
685
- seekTimer = void 0;
686
- }
687
- const thumbTempFilePath = await drawThumbnail(width, height);
688
- finish({ ...metadata, thumbTempFilePath });
689
- };
690
- seekTimer = setTimeout(() => finish(metadata), VIDEO_THUMBNAIL_TIMEOUT_MS);
691
- try {
692
- video.currentTime = Math.min(0.1, Math.max(0, duration - 0.01));
693
- } catch {
694
- finish(metadata);
695
- }
696
- };
697
- video.onerror = () => finish({ width: 0, height: 0, duration: 0, thumbTempFilePath: "" });
698
- video.src = src;
699
- });
700
- }
701
- async function buildChooseMediaTempFile(file) {
702
- const tempFilePath = createTempFilePath(file);
703
- const fileType = file.type.startsWith("video") ? "video" : "image";
704
- if (fileType === "video") {
705
- const metadata2 = await readVideoMetadata(tempFilePath);
706
- return {
707
- tempFilePath,
708
- size: file.size,
709
- duration: metadata2.duration,
710
- height: metadata2.height,
711
- width: metadata2.width,
712
- thumbTempFilePath: metadata2.thumbTempFilePath,
713
- fileType,
714
- originalFileObj: file
715
- };
773
+ walkDir(resolved, (err) => {
774
+ if (err) failOnce(err);
775
+ else okOnce();
776
+ });
777
+ });
778
+ } else {
779
+ _fs.stat(resolved, nodeComplete("fsStat", cbs, (s) => ({
780
+ stats: toWxStats(s)
781
+ })));
716
782
  }
717
- const metadata = await readImageMetadata(tempFilePath);
718
- return {
719
- tempFilePath,
720
- size: file.size,
721
- duration: 0,
722
- height: metadata.height,
723
- width: metadata.width,
724
- thumbTempFilePath: "",
725
- fileType,
726
- originalFileObj: file
727
- };
728
783
  }
729
- function chooseMedia({ count = 9, mediaType = ["image", "video"], sourceType = ["album", "camera"], camera = "back", success, fail, complete }) {
784
+ function fsReadFile({ filePath, encoding, success, fail, complete }) {
730
785
  const cbs = bindCallbacks(this, { success, fail, complete });
731
786
  const { onSuccess, onFail, onComplete } = cbs;
732
- const normalizedCount = normalizeChooseMediaCount(count);
733
- const normalizedMediaType = normalizeStringArray(mediaType, ["image", "video"]);
734
- const normalizedSourceType = normalizeStringArray(sourceType, ["album", "camera"]);
735
- createFilePicker(
736
- "chooseMedia",
737
- {
738
- accept: getChooseMediaAccept(normalizedMediaType),
739
- multiple: normalizedCount > 1,
740
- capture: captureAttrFor(normalizedSourceType, camera)
741
- },
742
- cbs,
743
- async (rawFiles, removeInput) => {
744
- const files = rawFiles.slice(0, normalizedCount);
745
- try {
746
- const tempFiles2 = await Promise.all(files.map(buildChooseMediaTempFile));
747
- onSuccess?.({
748
- tempFiles: tempFiles2,
749
- type: getChooseMediaResultType(tempFiles2),
750
- failedCount: 0,
751
- errMsg: "chooseMedia:ok"
752
- });
753
- } catch (error) {
754
- onFail?.({ errMsg: `chooseMedia:fail ${error.message}` });
755
- } finally {
787
+ if (guardFsAvailable("fsReadFile", cbs)) return;
788
+ const v = resolveOrBail(filePath, "fsReadFile", cbs);
789
+ if (!v) return;
790
+ if (v.kind === "tmp") {
791
+ _tmpBytes(filePath).then(
792
+ (buf) => {
793
+ const data = encoding ? buf.toString(encoding) : buf;
794
+ onSuccess?.({ data, errMsg: "fsReadFile:ok" });
756
795
  onComplete?.();
757
- removeInput();
758
- }
759
- }
760
- );
796
+ },
797
+ tmpFailHandler("fsReadFile", cbs)
798
+ );
799
+ return;
800
+ }
801
+ if (!v.realPath) {
802
+ onFail?.({ errMsg: "fsReadFile:fail invalid path" });
803
+ onComplete?.();
804
+ return;
805
+ }
806
+ _fs.readFile(v.realPath, encoding || null, nodeComplete("fsReadFile", cbs, (data) => ({ data })));
761
807
  }
762
- function chooseVideo({ sourceType, camera, success, fail, complete }) {
808
+ function fsWriteFile({ filePath, data, encoding = "utf8", success, fail, complete }) {
763
809
  const cbs = bindCallbacks(this, { success, fail, complete });
764
- const { onSuccess, onComplete } = cbs;
765
- const normalizedSourceType = normalizeStringArray(sourceType, ["album", "camera"]);
766
- createFilePicker(
767
- "chooseVideo",
768
- { accept: "video/*", multiple: false, capture: captureAttrFor(normalizedSourceType, camera) },
769
- cbs,
770
- async (files, removeInput) => {
771
- const file = files[0];
772
- const tempFilePath = createTempFilePath(file);
773
- const metadata = await readVideoMetadata(tempFilePath);
774
- onSuccess?.({
775
- tempFilePath,
776
- duration: metadata.duration,
777
- size: file.size,
778
- width: metadata.width,
779
- height: metadata.height,
780
- errMsg: "chooseVideo:ok"
781
- });
782
- onComplete?.();
783
- removeInput();
784
- }
785
- );
786
- }
787
- var AUDIO_EVENT_MAP = {
788
- play: "play",
789
- pause: "pause",
790
- ended: "ended",
791
- error: "error",
792
- timeupdate: "timeUpdate",
793
- waiting: "waiting",
794
- seeking: "seeking",
795
- seeked: "seeked",
796
- canplay: "canplay"
797
- };
798
- var _newAudioInstances = /* @__PURE__ */ new Map();
799
- var _audioEventDisposers = /* @__PURE__ */ new Map();
800
- var _audioFire = /* @__PURE__ */ new Map();
801
- function audioSnapshot(audio, event) {
802
- return {
803
- event,
804
- currentTime: audio.currentTime || 0,
805
- duration: Number.isFinite(audio.duration) ? audio.duration : 0,
806
- buffered: audio.buffered.length ? audio.buffered.end(audio.buffered.length - 1) : 0,
807
- paused: audio.paused
808
- };
809
- }
810
- function audioCreate({ audioId }) {
811
- _newAudioInstances.set(audioId, new Audio());
810
+ if (guardFsAvailable("fsWriteFile", cbs)) return;
811
+ const v = resolveOrBail(filePath, "fsWriteFile", cbs);
812
+ if (!v) return;
813
+ if (!ensureWritable(v, "fsWriteFile", cbs)) return;
814
+ mkdirpThenWrite(v.realPath, "fsWriteFile", cbs, (done) => _fs.writeFile(v.realPath, data, { encoding }, done));
812
815
  }
813
- function audioListen({ audioId, success }) {
814
- const audio = _newAudioInstances.get(audioId);
815
- const fire = this.createCallbackFunction(success);
816
- if (!audio || !fire) return;
817
- _audioFire.set(audioId, fire);
818
- _audioEventDisposers.get(audioId)?.();
819
- const dispose = bindDomEvents(
820
- audio,
821
- AUDIO_EVENT_MAP,
822
- fire,
823
- (event) => audioSnapshot(audio, event)
824
- );
825
- _audioEventDisposers.set(audioId, dispose);
816
+ function fsAppendFile({ filePath, data, encoding = "utf8", success, fail, complete }) {
817
+ const cbs = bindCallbacks(this, { success, fail, complete });
818
+ if (guardFsAvailable("fsAppendFile", cbs)) return;
819
+ const v = resolveOrBail(filePath, "fsAppendFile", cbs);
820
+ if (!v) return;
821
+ if (!ensureWritable(v, "fsAppendFile", cbs)) return;
822
+ _fs.appendFile(v.realPath, data, { encoding }, nodeComplete("fsAppendFile", cbs));
826
823
  }
827
- function audioSetProp({ audioId, prop, value, startTime, loop, volume, playbackRate, autoplay }) {
828
- const audio = _newAudioInstances.get(audioId);
829
- if (!audio) return;
830
- switch (prop) {
831
- case "src":
832
- audio.src = value;
833
- if (startTime != null) audio.currentTime = startTime;
834
- if (loop != null) audio.loop = loop;
835
- if (volume != null) audio.volume = Math.max(0, Math.min(1, volume));
836
- if (playbackRate != null) audio.playbackRate = playbackRate;
837
- if (autoplay) audio.play().catch(() => {
838
- });
839
- break;
840
- case "startTime":
841
- audio.currentTime = Number(value) || 0;
842
- break;
843
- case "autoplay":
844
- audio.autoplay = !!value;
845
- break;
846
- case "loop":
847
- audio.loop = !!value;
848
- break;
849
- case "volume":
850
- audio.volume = Math.max(0, Math.min(1, Number(value) || 0));
851
- break;
852
- case "playbackRate":
853
- audio.playbackRate = Number(value) || 1;
854
- break;
824
+ function fsCopyFile({ srcPath, destPath, success, fail, complete }) {
825
+ const cbs = bindCallbacks(this, { success, fail, complete });
826
+ const { onFail, onComplete } = cbs;
827
+ if (guardFsAvailable("fsCopyFile", cbs)) return;
828
+ const vSrc = resolveOrBail(srcPath, "fsCopyFile", cbs);
829
+ if (!vSrc) return;
830
+ const vDest = resolveOrBail(destPath, "fsCopyFile", cbs);
831
+ if (!vDest) return;
832
+ if (!ensureWritable(vDest, "fsCopyFile", cbs)) return;
833
+ if (vSrc.kind === "tmp") {
834
+ _tmpBytes(srcPath).then(
835
+ (buf) => mkdirpThenWrite(vDest.realPath, "fsCopyFile", cbs, (done) => _fs.writeFile(vDest.realPath, buf, done)),
836
+ tmpFailHandler("fsCopyFile", cbs)
837
+ );
838
+ return;
855
839
  }
840
+ if (!vSrc.realPath) {
841
+ onFail?.({ errMsg: "fsCopyFile:fail invalid src path" });
842
+ onComplete?.();
843
+ return;
844
+ }
845
+ _fs.copyFile(vSrc.realPath, vDest.realPath, nodeComplete("fsCopyFile", cbs));
856
846
  }
857
- function audioPlay({ audioId, src }) {
858
- const audio = _newAudioInstances.get(audioId);
859
- if (!audio) return;
860
- if (src && audio.src !== src) audio.src = src;
861
- audio.play().catch(() => {
862
- });
863
- }
864
- function audioPause({ audioId }) {
865
- _newAudioInstances.get(audioId)?.pause();
866
- }
867
- function audioStop({ audioId }) {
868
- const audio = _newAudioInstances.get(audioId);
869
- if (!audio) return;
870
- audio.pause();
871
- audio.currentTime = 0;
872
- _audioFire.get(audioId)?.(audioSnapshot(audio, "stop"));
873
- }
874
- function audioSeek({ audioId, position }) {
875
- const audio = _newAudioInstances.get(audioId);
876
- if (!audio) return;
877
- audio.currentTime = position;
847
+ function fsRename({ oldPath, newPath, success, fail, complete }) {
848
+ const cbs = bindCallbacks(this, { success, fail, complete });
849
+ if (guardFsAvailable("fsRename", cbs)) return;
850
+ const vOld = resolveOrBail(oldPath, "fsRename", cbs);
851
+ if (!vOld) return;
852
+ const vNew = resolveOrBail(newPath, "fsRename", cbs);
853
+ if (!vNew) return;
854
+ if (!ensureWritable(vOld, "fsRename", cbs)) return;
855
+ if (!ensureWritable(vNew, "fsRename", cbs)) return;
856
+ _fs.rename(vOld.realPath, vNew.realPath, nodeComplete("fsRename", cbs));
878
857
  }
879
- function audioDestroy({ audioId }) {
880
- _audioEventDisposers.get(audioId)?.();
881
- _audioEventDisposers.delete(audioId);
882
- _audioFire.delete(audioId);
883
- const audio = _newAudioInstances.get(audioId);
884
- if (!audio) return;
885
- audio.pause();
886
- audio.removeAttribute("src");
887
- audio.load();
888
- _newAudioInstances.delete(audioId);
858
+ function fsUnlink({ filePath, success, fail, complete }) {
859
+ const cbs = bindCallbacks(this, { success, fail, complete });
860
+ if (guardFsAvailable("fsUnlink", cbs)) return;
861
+ const v = resolveOrBail(filePath, "fsUnlink", cbs);
862
+ if (!v) return;
863
+ if (!ensureWritable(v, "fsUnlink", cbs)) return;
864
+ _fs.unlink(v.realPath, nodeComplete("fsUnlink", cbs));
889
865
  }
890
-
891
- // src/shared/vpath.ts
892
- var import_node_os = __toESM(require("node:os"), 1);
893
- var import_node_path = __toESM(require("node:path"), 1);
894
- var DIFILE_PREFIX = "difile://";
895
- function sandboxBase() {
896
- const env = typeof process !== "undefined" && process.env && process.env.DIMINA_HOME || "";
897
- if (env) {
898
- return import_node_path.default.join(env, "files");
899
- }
900
- return import_node_path.default.join(import_node_os.default.homedir(), ".dimina", "files");
866
+ function fsMkdir({ dirPath, recursive = false, success, fail, complete }) {
867
+ const cbs = bindCallbacks(this, { success, fail, complete });
868
+ if (guardFsAvailable("fsMkdir", cbs)) return;
869
+ const v = resolveOrBail(dirPath, "fsMkdir", cbs);
870
+ if (!v) return;
871
+ if (!ensureWritable(v, "fsMkdir", cbs)) return;
872
+ _fs.mkdir(v.realPath, { recursive }, nodeComplete("fsMkdir", cbs));
901
873
  }
902
- function resolveVPath(url) {
903
- if (typeof url !== "string") return null;
904
- if (!url.startsWith(DIFILE_PREFIX)) return null;
905
- const raw = url.slice(DIFILE_PREFIX.length);
906
- if (raw.length === 0) return null;
907
- let decoded;
908
- try {
909
- decoded = decodeURIComponent(raw);
910
- } catch {
911
- return null;
912
- }
913
- if (decoded.includes("\0")) return null;
914
- decoded = decoded.replace(/^[/\\]+/, "");
915
- if (decoded.length === 0) return null;
916
- const segments = decoded.split(/[/\\]+/);
917
- if (segments.some((s) => s === ".." || s === ".")) return null;
918
- let kind;
919
- let writable;
920
- if (decoded.startsWith("_tmp/")) {
921
- kind = "tmp";
922
- writable = false;
923
- } else if (decoded.startsWith("_store/")) {
924
- kind = "store";
925
- writable = false;
874
+ function fsRmdir({ dirPath, recursive = false, success, fail, complete }) {
875
+ const cbs = bindCallbacks(this, { success, fail, complete });
876
+ if (guardFsAvailable("fsRmdir", cbs)) return;
877
+ const v = resolveOrBail(dirPath, "fsRmdir", cbs);
878
+ if (!v) return;
879
+ if (!ensureWritable(v, "fsRmdir", cbs)) return;
880
+ if (recursive) {
881
+ const rmFn = _fs.rm ?? _fs.rmdir;
882
+ rmFn(v.realPath, { recursive: true }, nodeComplete("fsRmdir", cbs));
926
883
  } else {
927
- kind = "usr";
928
- writable = true;
929
- }
930
- if (kind === "tmp") {
931
- return { kind, writable };
884
+ _fs.rmdir(v.realPath, nodeComplete("fsRmdir", cbs));
932
885
  }
933
- const base = sandboxBase();
934
- const joined = import_node_path.default.join(base, decoded);
935
- const normalized = import_node_path.default.normalize(joined);
936
- if (normalized !== base && !normalized.startsWith(base + import_node_path.default.sep)) return null;
937
- return { kind, writable, realPath: normalized };
938
886
  }
939
-
940
- // src/simulator/simulator-api-fs.ts
941
- async function _tmpBytes(url) {
942
- try {
943
- const blob = await resolveTempFilePath(url);
944
- return await blobToBuffer(blob);
945
- } catch (err) {
946
- const msg = err instanceof Error ? err.message : String(err);
947
- throw new Error(`ENOENT: no such file (${url}): ${msg}`, { cause: err });
948
- }
949
- }
950
- function isArrayBuffer(value) {
951
- return Object.prototype.toString.call(value) === "[object ArrayBuffer]";
952
- }
953
- async function blobToBuffer(blob) {
954
- const readable = blob;
955
- if (typeof readable.arrayBuffer === "function") {
956
- const ab = await readable.arrayBuffer();
957
- return Buffer.from(new Uint8Array(ab));
887
+ function fsReaddir({ dirPath, success, fail, complete }) {
888
+ const cbs = bindCallbacks(this, { success, fail, complete });
889
+ const { onFail, onComplete } = cbs;
890
+ if (guardFsAvailable("fsReaddir", cbs)) return;
891
+ const v = resolveOrBail(dirPath, "fsReaddir", cbs);
892
+ if (!v) return;
893
+ if (v.kind === "tmp" || v.kind === "store") {
894
+ onFail?.({ errMsg: `fsReaddir:fail ${v.kind} is a flat namespace (no dir tree)` });
895
+ onComplete?.();
896
+ return;
958
897
  }
959
- if (typeof FileReader !== "function") {
960
- throw new Error("Blob cannot be read as ArrayBuffer");
898
+ if (!v.realPath) {
899
+ onFail?.({ errMsg: "fsReaddir:fail invalid path" });
900
+ onComplete?.();
901
+ return;
961
902
  }
962
- return new Promise((resolve, reject) => {
963
- const reader = new FileReader();
964
- reader.onerror = () => reject(reader.error ?? new Error("Blob read failed"));
965
- reader.onload = () => {
966
- const result = reader.result;
967
- if (!isArrayBuffer(result)) {
968
- reject(new Error("Blob read did not return an ArrayBuffer"));
969
- return;
970
- }
971
- resolve(Buffer.from(new Uint8Array(result)));
972
- };
973
- reader.readAsArrayBuffer(blob);
974
- });
975
- }
976
- var _fs = typeof require !== "undefined" ? require("fs") : null;
977
- var _path = typeof require !== "undefined" ? require("path") : null;
978
- var _crypto = typeof require !== "undefined" ? require("crypto") : null;
979
- function guardFsAvailable(apiName, cbs) {
980
- if (_fs) return false;
981
- cbs.onFail?.({ errMsg: `${apiName}:fail not available in browser context` });
982
- cbs.onComplete?.();
983
- return true;
984
- }
985
- function resolveOrBail(p, apiName, cbs) {
986
- const v = resolveVPath(p);
987
- if (v) return v;
988
- cbs.onFail?.({ errMsg: `${apiName}:fail invalid or unsafe path` });
989
- cbs.onComplete?.();
990
- return void 0;
991
- }
992
- function ensureWritable(v, apiName, cbs) {
993
- if (v.writable && v.realPath) return true;
994
- cbs.onFail?.({ errMsg: `${apiName}:fail permission denied` });
995
- cbs.onComplete?.();
996
- return false;
997
- }
998
- function tmpFailHandler(apiName, cbs) {
999
- return (err) => {
1000
- cbs.onFail?.({ errMsg: `${apiName}:fail ${err.message}` });
1001
- cbs.onComplete?.();
1002
- };
1003
- }
1004
- function nodeComplete(apiName, cbs, buildOk) {
1005
- return (err, ...args) => {
1006
- if (err) {
1007
- cbs.onFail?.({ errMsg: `${apiName}:fail ${err.message}` });
1008
- } else {
1009
- cbs.onSuccess?.({ ...buildOk ? buildOk(...args) : void 0, errMsg: `${apiName}:ok` });
1010
- }
1011
- cbs.onComplete?.();
1012
- };
1013
- }
1014
- function mkdirpThenWrite(destReal, apiName, cbs, writeFn, okExtra) {
1015
- _fs.mkdir(_path.dirname(destReal), { recursive: true }, (mkdirErr) => {
1016
- if (mkdirErr) {
1017
- cbs.onFail?.({ errMsg: `${apiName}:fail ${mkdirErr.message}` });
1018
- cbs.onComplete?.();
1019
- return;
1020
- }
1021
- writeFn(nodeComplete(apiName, cbs, () => okExtra));
1022
- });
903
+ _fs.readdir(v.realPath, nodeComplete("fsReaddir", cbs, (files) => ({ files })));
1023
904
  }
1024
- function fsAccess({ path: path2, success, fail, complete }) {
905
+ function fsGetFileInfo({ filePath, digestAlgorithm, success, fail, complete }) {
1025
906
  const cbs = bindCallbacks(this, { success, fail, complete });
1026
907
  const { onSuccess, onFail, onComplete } = cbs;
1027
- if (guardFsAvailable("fsAccess", cbs)) return;
1028
- const v = resolveOrBail(path2, "fsAccess", cbs);
908
+ if (guardFsAvailable("fsGetFileInfo", cbs)) return;
909
+ const v = resolveOrBail(filePath, "fsGetFileInfo", cbs);
1029
910
  if (!v) return;
911
+ const algorithm = digestAlgorithm === "sha1" ? "sha1" : "md5";
1030
912
  if (v.kind === "tmp") {
1031
- _tmpBytes(path2).then(
1032
- () => {
1033
- onSuccess?.({ errMsg: "fsAccess:ok" });
1034
- onComplete?.();
913
+ _tmpBytes(filePath).then(
914
+ (buf) => {
915
+ try {
916
+ const hash = _crypto.createHash(algorithm);
917
+ hash.update(buf);
918
+ onSuccess?.({ size: buf.length, digest: hash.digest("hex"), errMsg: "fsGetFileInfo:ok" });
919
+ onComplete?.();
920
+ } catch (hashErr) {
921
+ onFail?.({ errMsg: `fsGetFileInfo:fail ${hashErr.message}` });
922
+ onComplete?.();
923
+ }
1035
924
  },
1036
- tmpFailHandler("fsAccess", cbs)
925
+ tmpFailHandler("fsGetFileInfo", cbs)
1037
926
  );
1038
927
  return;
1039
928
  }
1040
929
  if (!v.realPath) {
1041
- onFail?.({ errMsg: "fsAccess:fail invalid path" });
930
+ onFail?.({ errMsg: "fsGetFileInfo:fail invalid path" });
1042
931
  onComplete?.();
1043
932
  return;
1044
933
  }
1045
- _fs.access(v.realPath, _fs.constants.F_OK, nodeComplete("fsAccess", cbs));
1046
- }
1047
- function fsStat({ path: path2, recursive = false, success, fail, complete }) {
1048
- const cbs = bindCallbacks(this, { success, fail, complete });
1049
- const { onSuccess, onFail, onComplete } = cbs;
1050
- if (guardFsAvailable("fsStat", cbs)) return;
1051
- const v = resolveOrBail(path2, "fsStat", cbs);
1052
- if (!v) return;
1053
- if (v.kind === "tmp") {
1054
- _tmpBytes(path2).then(
1055
- (buf) => {
1056
- onSuccess?.({
1057
- stats: {
1058
- size: buf.length,
1059
- mode: 0,
1060
- lastAccessedTime: 0,
1061
- lastModifiedTime: 0,
1062
- isFile: true,
1063
- isDirectory: false
1064
- },
1065
- errMsg: "fsStat:ok"
1066
- });
934
+ const resolved = v.realPath;
935
+ _fs.stat(resolved, (err, s) => {
936
+ if (err) {
937
+ onFail?.({ errMsg: `fsGetFileInfo:fail ${err.message}` });
938
+ onComplete?.();
939
+ return;
940
+ }
941
+ try {
942
+ const hash = _crypto.createHash(algorithm);
943
+ const stream = _fs.createReadStream(resolved);
944
+ stream.on("data", (chunk) => hash.update(chunk));
945
+ stream.on("end", () => {
946
+ onSuccess?.({ size: s.size, digest: hash.digest("hex"), errMsg: "fsGetFileInfo:ok" });
1067
947
  onComplete?.();
1068
- },
1069
- tmpFailHandler("fsStat", cbs)
948
+ });
949
+ stream.on("error", (hashErr) => {
950
+ onFail?.({ errMsg: `fsGetFileInfo:fail ${hashErr.message}` });
951
+ onComplete?.();
952
+ });
953
+ } catch (hashErr) {
954
+ onFail?.({ errMsg: `fsGetFileInfo:fail ${hashErr.message}` });
955
+ onComplete?.();
956
+ }
957
+ });
958
+ }
959
+ function resolveSaveFileDestination(tempFilePath, filePath, cbs) {
960
+ if (typeof filePath === "string" && filePath) {
961
+ const dest = resolveOrBail(filePath, "fsSaveFile", cbs);
962
+ if (!dest) return void 0;
963
+ if (!ensureWritable(dest, "fsSaveFile", cbs)) return void 0;
964
+ return { savedFilePath: filePath, destReal: dest.realPath };
965
+ }
966
+ const ext = _path.extname(tempFilePath) || "";
967
+ const id = _crypto.randomUUID() + ext;
968
+ const savedFilePath = `difile://_store/${id}`;
969
+ const destResolved = resolveVPath(savedFilePath);
970
+ if (!destResolved || !destResolved.realPath) {
971
+ cbs.onFail?.({ errMsg: "fsSaveFile:fail unable to allocate destination" });
972
+ cbs.onComplete?.();
973
+ return void 0;
974
+ }
975
+ if (containsSymlink(destResolved.realPath)) {
976
+ cbs.onFail?.({ errMsg: "fsSaveFile:fail invalid or unsafe path" });
977
+ cbs.onComplete?.();
978
+ return void 0;
979
+ }
980
+ return { savedFilePath, destReal: destResolved.realPath };
981
+ }
982
+ function fsSaveFile({ tempFilePath, filePath, success, fail, complete }) {
983
+ const cbs = bindCallbacks(this, { success, fail, complete });
984
+ const { onFail, onComplete } = cbs;
985
+ if (guardFsAvailable("fsSaveFile", cbs)) return;
986
+ const src = resolveOrBail(tempFilePath, "fsSaveFile", cbs);
987
+ if (!src) return;
988
+ const destination = resolveSaveFileDestination(tempFilePath, filePath, cbs);
989
+ if (!destination) return;
990
+ const { savedFilePath, destReal } = destination;
991
+ if (src.kind === "tmp") {
992
+ _tmpBytes(tempFilePath).then(
993
+ (bytes) => mkdirpThenWrite(destReal, "fsSaveFile", cbs, (done) => _fs.writeFile(destReal, bytes, done), { savedFilePath }),
994
+ tmpFailHandler("fsSaveFile", cbs)
1070
995
  );
1071
996
  return;
1072
997
  }
1073
- if (!v.realPath) {
1074
- onFail?.({ errMsg: "fsStat:fail invalid path" });
998
+ if (!src.realPath) {
999
+ onFail?.({ errMsg: "fsSaveFile:fail invalid src path" });
1075
1000
  onComplete?.();
1076
1001
  return;
1077
1002
  }
1078
- const resolved = v.realPath;
1079
- if (recursive) {
1080
- const statsMap = {};
1081
- const walkDir = (dir, cb) => {
1082
- _fs.readdir(dir, { withFileTypes: true }, (err, entries) => {
1083
- if (err) {
1084
- cb(err);
1085
- return;
1086
- }
1087
- let pending = entries.length;
1088
- if (pending === 0) {
1089
- cb(null);
1090
- return;
1091
- }
1092
- for (const entry of entries) {
1093
- const full = _path.join(dir, entry.name);
1094
- _fs.stat(full, (statErr, s) => {
1095
- if (!statErr) {
1096
- statsMap[full] = {
1097
- size: s.size,
1098
- mode: s.mode,
1099
- lastAccessedTime: s.atimeMs,
1100
- lastModifiedTime: s.mtimeMs,
1101
- isFile: s.isFile(),
1102
- isDirectory: s.isDirectory()
1103
- };
1104
- }
1105
- if (entry.isDirectory()) {
1106
- walkDir(full, () => {
1107
- if (--pending === 0) cb(null);
1108
- });
1109
- } else {
1110
- if (--pending === 0) cb(null);
1111
- }
1112
- });
1113
- }
1114
- });
1115
- };
1116
- walkDir(resolved, (err) => {
1117
- if (err) {
1118
- onFail?.({ errMsg: `fsStat:fail ${err.message}` });
1119
- } else {
1120
- onSuccess?.({ stats: statsMap, errMsg: "fsStat:ok" });
1003
+ if (src.realPath === destReal) {
1004
+ _fs.stat(src.realPath, (statErr, st) => {
1005
+ if (statErr) {
1006
+ onFail?.({ errMsg: `fsSaveFile:fail ${statErr.message}` });
1007
+ onComplete?.();
1008
+ return;
1009
+ }
1010
+ if (!st.isFile()) {
1011
+ onFail?.({ errMsg: `fsSaveFile:fail illegal operation on a directory, copyfile '${tempFilePath}'` });
1012
+ onComplete?.();
1013
+ return;
1121
1014
  }
1015
+ cbs.onSuccess?.({ savedFilePath, errMsg: "fsSaveFile:ok" });
1122
1016
  onComplete?.();
1123
1017
  });
1124
- } else {
1125
- _fs.stat(resolved, nodeComplete("fsStat", cbs, (s) => ({
1126
- stats: {
1127
- size: s.size,
1128
- mode: s.mode,
1129
- lastAccessedTime: s.atimeMs,
1130
- lastModifiedTime: s.mtimeMs,
1131
- isFile: s.isFile(),
1132
- isDirectory: s.isDirectory()
1133
- }
1134
- })));
1018
+ return;
1135
1019
  }
1020
+ mkdirpThenWrite(destReal, "fsSaveFile", cbs, (done) => _fs.copyFile(src.realPath, destReal, done), { savedFilePath });
1136
1021
  }
1137
- function fsReadFile({ filePath, encoding, success, fail, complete }) {
1022
+ function fsGetSavedFileList({ success, fail, complete } = {}) {
1138
1023
  const cbs = bindCallbacks(this, { success, fail, complete });
1139
1024
  const { onSuccess, onFail, onComplete } = cbs;
1140
- if (guardFsAvailable("fsReadFile", cbs)) return;
1141
- const v = resolveOrBail(filePath, "fsReadFile", cbs);
1142
- if (!v) return;
1143
- if (v.kind === "tmp") {
1144
- _tmpBytes(filePath).then(
1145
- (buf) => {
1146
- const data = encoding ? buf.toString(encoding) : buf;
1147
- onSuccess?.({ data, errMsg: "fsReadFile:ok" });
1148
- onComplete?.();
1149
- },
1150
- tmpFailHandler("fsReadFile", cbs)
1151
- );
1152
- return;
1153
- }
1154
- if (!v.realPath) {
1155
- onFail?.({ errMsg: "fsReadFile:fail invalid path" });
1025
+ if (guardFsAvailable("fsGetSavedFileList", cbs)) return;
1026
+ const storeVpath = resolveVPath("difile://_store/");
1027
+ const storeDir = storeVpath?.realPath;
1028
+ if (!storeDir) {
1029
+ onSuccess?.({ fileList: [], errMsg: "fsGetSavedFileList:ok" });
1156
1030
  onComplete?.();
1157
1031
  return;
1158
1032
  }
1159
- _fs.readFile(v.realPath, encoding || null, nodeComplete("fsReadFile", cbs, (data) => ({ data })));
1033
+ _fs.readdir(storeDir, (err, files) => {
1034
+ if (err) {
1035
+ if (err.code === "ENOENT") {
1036
+ onSuccess?.({ fileList: [], errMsg: "fsGetSavedFileList:ok" });
1037
+ } else {
1038
+ onFail?.({ errMsg: `fsGetSavedFileList:fail ${err.message}` });
1039
+ }
1040
+ onComplete?.();
1041
+ return;
1042
+ }
1043
+ let pending = files.length;
1044
+ if (pending === 0) {
1045
+ onSuccess?.({ fileList: [], errMsg: "fsGetSavedFileList:ok" });
1046
+ onComplete?.();
1047
+ return;
1048
+ }
1049
+ const fileList = [];
1050
+ let settled = false;
1051
+ for (const name of files) {
1052
+ const full = _path.join(storeDir, name);
1053
+ _fs.stat(full, (statErr, s) => {
1054
+ if (settled) return;
1055
+ if (statErr) {
1056
+ settled = true;
1057
+ onFail?.({ errMsg: `fsGetSavedFileList:fail ${statErr.message}` });
1058
+ onComplete?.();
1059
+ return;
1060
+ }
1061
+ fileList.push({
1062
+ filePath: `difile://_store/${name}`,
1063
+ size: s.size,
1064
+ // wx createTime is a seconds-unit epoch.
1065
+ createTime: Math.floor(s.birthtimeMs / 1e3)
1066
+ });
1067
+ if (--pending === 0) {
1068
+ settled = true;
1069
+ onSuccess?.({ fileList, errMsg: "fsGetSavedFileList:ok" });
1070
+ onComplete?.();
1071
+ }
1072
+ });
1073
+ }
1074
+ });
1160
1075
  }
1161
- function fsWriteFile({ filePath, data, encoding = "utf8", success, fail, complete }) {
1076
+ function fsRemoveSavedFile({ filePath, success, fail, complete }) {
1162
1077
  const cbs = bindCallbacks(this, { success, fail, complete });
1163
- if (guardFsAvailable("fsWriteFile", cbs)) return;
1164
- const v = resolveOrBail(filePath, "fsWriteFile", cbs);
1078
+ const { onFail, onComplete } = cbs;
1079
+ if (guardFsAvailable("fsRemoveSavedFile", cbs)) return;
1080
+ const v = resolveOrBail(filePath, "fsRemoveSavedFile", cbs);
1165
1081
  if (!v) return;
1166
- if (!ensureWritable(v, "fsWriteFile", cbs)) return;
1167
- mkdirpThenWrite(v.realPath, "fsWriteFile", cbs, (done) => _fs.writeFile(v.realPath, data, { encoding }, done));
1082
+ if (v.kind !== "store" || !v.realPath) {
1083
+ onFail?.({ errMsg: "fsRemoveSavedFile:fail only _store/ entries may be removed" });
1084
+ onComplete?.();
1085
+ return;
1086
+ }
1087
+ _fs.unlink(v.realPath, nodeComplete("fsRemoveSavedFile", cbs));
1168
1088
  }
1169
- function fsAppendFile({ filePath, data, encoding = "utf8", success, fail, complete }) {
1089
+ function fsTruncate({ filePath, length = 0, success, fail, complete }) {
1170
1090
  const cbs = bindCallbacks(this, { success, fail, complete });
1171
- if (guardFsAvailable("fsAppendFile", cbs)) return;
1172
- const v = resolveOrBail(filePath, "fsAppendFile", cbs);
1091
+ if (guardFsAvailable("fsTruncate", cbs)) return;
1092
+ const v = resolveOrBail(filePath, "fsTruncate", cbs);
1173
1093
  if (!v) return;
1174
- if (!ensureWritable(v, "fsAppendFile", cbs)) return;
1175
- _fs.appendFile(v.realPath, data, { encoding }, nodeComplete("fsAppendFile", cbs));
1094
+ if (!ensureWritable(v, "fsTruncate", cbs)) return;
1095
+ _fs.truncate(v.realPath, length, nodeComplete("fsTruncate", cbs));
1176
1096
  }
1177
- function fsCopyFile({ srcPath, destPath, success, fail, complete }) {
1097
+ function readVPathBytesSync(p) {
1098
+ if (!_fs) return null;
1099
+ const v = resolveVPath(p);
1100
+ if (!v?.realPath) return null;
1101
+ if (containsSymlink(v.realPath)) return null;
1102
+ try {
1103
+ return _fs.readFileSync(v.realPath);
1104
+ } catch {
1105
+ return null;
1106
+ }
1107
+ }
1108
+
1109
+ // src/simulator/simulator-api-media.ts
1110
+ function captureAttrFor(sourceType, camera) {
1111
+ if (sourceType.length !== 1 || sourceType[0] !== "camera") return void 0;
1112
+ return camera === "front" ? "user" : "environment";
1113
+ }
1114
+ function createFilePicker(apiName, config, cbs, onPick) {
1115
+ const input = document.createElement("input");
1116
+ input.type = "file";
1117
+ input.accept = config.accept;
1118
+ input.multiple = config.multiple;
1119
+ if (config.capture) input.setAttribute("capture", config.capture);
1120
+ input.style.display = "none";
1121
+ document.body.appendChild(input);
1122
+ const removeInput = () => input.remove();
1123
+ input.addEventListener("change", () => {
1124
+ const files = Array.from(input.files || []);
1125
+ if (files.length === 0) {
1126
+ cbs.onFail?.({ errMsg: `${apiName}:fail cancel` });
1127
+ cbs.onComplete?.();
1128
+ removeInput();
1129
+ return;
1130
+ }
1131
+ void onPick(files, removeInput);
1132
+ });
1133
+ input.click();
1134
+ }
1135
+ function chooseImage({ count = 9, sourceType, camera, success, fail, complete }) {
1178
1136
  const cbs = bindCallbacks(this, { success, fail, complete });
1179
- const { onFail, onComplete } = cbs;
1180
- if (guardFsAvailable("fsCopyFile", cbs)) return;
1181
- const vSrc = resolveOrBail(srcPath, "fsCopyFile", cbs);
1182
- if (!vSrc) return;
1183
- const vDest = resolveOrBail(destPath, "fsCopyFile", cbs);
1184
- if (!vDest) return;
1185
- if (!ensureWritable(vDest, "fsCopyFile", cbs)) return;
1186
- if (vSrc.kind === "tmp") {
1187
- _tmpBytes(srcPath).then(
1188
- (buf) => mkdirpThenWrite(vDest.realPath, "fsCopyFile", cbs, (done) => _fs.writeFile(vDest.realPath, buf, done)),
1189
- tmpFailHandler("fsCopyFile", cbs)
1137
+ const { onSuccess, onComplete } = cbs;
1138
+ const normalizedCount = normalizeChooseMediaCount(count);
1139
+ const normalizedSourceType = normalizeStringArray(sourceType, ["album", "camera"]);
1140
+ createFilePicker(
1141
+ "chooseImage",
1142
+ { accept: "image/*", multiple: normalizedCount > 1, capture: captureAttrFor(normalizedSourceType, camera) },
1143
+ cbs,
1144
+ (rawFiles, removeInput) => {
1145
+ const files = rawFiles.slice(0, normalizedCount);
1146
+ const tempFilePaths = files.map((f) => createTempFilePath(f));
1147
+ const tempFiles2 = files.map((f, i) => ({ path: tempFilePaths[i], size: f.size }));
1148
+ onSuccess?.({ tempFilePaths, tempFiles: tempFiles2, errMsg: "chooseImage:ok" });
1149
+ onComplete?.();
1150
+ removeInput();
1151
+ }
1152
+ );
1153
+ }
1154
+ function previewImage({ urls, current, success, complete }) {
1155
+ const { onSuccess, onComplete } = bindCallbacks(this, { success, complete });
1156
+ if (!urls || urls.length === 0) {
1157
+ onComplete?.();
1158
+ return;
1159
+ }
1160
+ const overlay = document.createElement("div");
1161
+ overlay.style.cssText = "position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;cursor:pointer;";
1162
+ const img = document.createElement("img");
1163
+ img.src = current || urls[0] || "";
1164
+ img.style.cssText = "max-width:90%;max-height:90%;object-fit:contain;";
1165
+ overlay.appendChild(img);
1166
+ overlay.addEventListener("click", () => overlay.remove());
1167
+ document.body.appendChild(overlay);
1168
+ onSuccess?.({ errMsg: "previewImage:ok" });
1169
+ onComplete?.();
1170
+ }
1171
+ function compressImage({ src, quality = 80, success, fail, complete }) {
1172
+ const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
1173
+ const img = new Image();
1174
+ img.crossOrigin = "anonymous";
1175
+ img.onload = () => {
1176
+ try {
1177
+ const canvas = document.createElement("canvas");
1178
+ canvas.width = img.naturalWidth;
1179
+ canvas.height = img.naturalHeight;
1180
+ const ctx = canvas.getContext("2d");
1181
+ ctx.drawImage(img, 0, 0);
1182
+ canvas.toBlob(
1183
+ (blob) => {
1184
+ if (blob) {
1185
+ const tempFilePath = createTempFilePath(blob);
1186
+ onSuccess?.({ tempFilePath, errMsg: "compressImage:ok" });
1187
+ } else {
1188
+ onFail?.({ errMsg: "compressImage:fail compression error" });
1189
+ }
1190
+ onComplete?.();
1191
+ },
1192
+ "image/jpeg",
1193
+ quality / 100
1194
+ );
1195
+ } catch (error) {
1196
+ onFail?.({ errMsg: `compressImage:fail ${error.message}` });
1197
+ onComplete?.();
1198
+ }
1199
+ };
1200
+ img.onerror = () => {
1201
+ onFail?.({ errMsg: "compressImage:fail image load error" });
1202
+ onComplete?.();
1203
+ };
1204
+ img.src = src;
1205
+ }
1206
+ function saveImageToPhotosAlbum({ filePath, success, fail, complete }) {
1207
+ const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
1208
+ const triggerDownload = (href) => {
1209
+ const a = document.createElement("a");
1210
+ a.href = href;
1211
+ a.download = "image";
1212
+ a.click();
1213
+ };
1214
+ if (typeof filePath === "string" && filePath.startsWith("difile://_tmp/")) {
1215
+ resolveTempFilePath(filePath).then(
1216
+ (blob) => new Promise((resolve, reject) => {
1217
+ const reader = new FileReader();
1218
+ reader.onerror = () => reject(reader.error ?? new Error("blob read failed"));
1219
+ reader.onload = () => resolve(String(reader.result));
1220
+ reader.readAsDataURL(blob);
1221
+ })
1222
+ ).then(
1223
+ (href) => {
1224
+ triggerDownload(href);
1225
+ onSuccess?.({ errMsg: "saveImageToPhotosAlbum:ok" });
1226
+ onComplete?.();
1227
+ },
1228
+ (err) => {
1229
+ const msg = err instanceof Error ? err.message : String(err);
1230
+ onFail?.({ errMsg: `saveImageToPhotosAlbum:fail ${msg}` });
1231
+ onComplete?.();
1232
+ }
1190
1233
  );
1191
1234
  return;
1192
1235
  }
1193
- if (!vSrc.realPath) {
1194
- onFail?.({ errMsg: "fsCopyFile:fail invalid src path" });
1195
- onComplete?.();
1196
- return;
1236
+ try {
1237
+ if (typeof filePath === "string" && filePath.startsWith("difile://")) {
1238
+ const bytes = readVPathBytesSync(filePath);
1239
+ if (bytes) {
1240
+ triggerDownload(`data:application/octet-stream;base64,${bytes.toString("base64")}`);
1241
+ onSuccess?.({ errMsg: "saveImageToPhotosAlbum:ok" });
1242
+ } else {
1243
+ onFail?.({ errMsg: "saveImageToPhotosAlbum:fail no such file" });
1244
+ }
1245
+ } else {
1246
+ onFail?.({ errMsg: "saveImageToPhotosAlbum:fail invalid file path" });
1247
+ }
1248
+ } catch (error) {
1249
+ onFail?.({ errMsg: `saveImageToPhotosAlbum:fail ${error.message}` });
1197
1250
  }
1198
- _fs.copyFile(vSrc.realPath, vDest.realPath, nodeComplete("fsCopyFile", cbs));
1251
+ onComplete?.();
1199
1252
  }
1200
- function fsRename({ oldPath, newPath, success, fail, complete }) {
1201
- const cbs = bindCallbacks(this, { success, fail, complete });
1202
- if (guardFsAvailable("fsRename", cbs)) return;
1203
- const vOld = resolveOrBail(oldPath, "fsRename", cbs);
1204
- if (!vOld) return;
1205
- const vNew = resolveOrBail(newPath, "fsRename", cbs);
1206
- if (!vNew) return;
1207
- if (!ensureWritable(vOld, "fsRename", cbs)) return;
1208
- if (!ensureWritable(vNew, "fsRename", cbs)) return;
1209
- _fs.rename(vOld.realPath, vNew.realPath, nodeComplete("fsRename", cbs));
1253
+ function getImageInfo({ src, success, fail, complete }) {
1254
+ const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
1255
+ const img = new Image();
1256
+ img.crossOrigin = "anonymous";
1257
+ img.onload = () => {
1258
+ onSuccess?.({
1259
+ width: img.naturalWidth,
1260
+ height: img.naturalHeight,
1261
+ path: src,
1262
+ orientation: "up",
1263
+ type: "unknown",
1264
+ errMsg: "getImageInfo:ok"
1265
+ });
1266
+ onComplete?.();
1267
+ };
1268
+ img.onerror = () => {
1269
+ onFail?.({ errMsg: "getImageInfo:fail image load error" });
1270
+ onComplete?.();
1271
+ };
1272
+ img.src = src;
1210
1273
  }
1211
- function fsUnlink({ filePath, success, fail, complete }) {
1212
- const cbs = bindCallbacks(this, { success, fail, complete });
1213
- if (guardFsAvailable("fsUnlink", cbs)) return;
1214
- const v = resolveOrBail(filePath, "fsUnlink", cbs);
1215
- if (!v) return;
1216
- if (!ensureWritable(v, "fsUnlink", cbs)) return;
1217
- _fs.unlink(v.realPath, nodeComplete("fsUnlink", cbs));
1274
+ var VIDEO_METADATA_TIMEOUT_MS = 5e3;
1275
+ var VIDEO_THUMBNAIL_TIMEOUT_MS = 500;
1276
+ function normalizeStringArray(value, fallback) {
1277
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : fallback;
1218
1278
  }
1219
- function fsMkdir({ dirPath, recursive = false, success, fail, complete }) {
1220
- const cbs = bindCallbacks(this, { success, fail, complete });
1221
- if (guardFsAvailable("fsMkdir", cbs)) return;
1222
- const v = resolveOrBail(dirPath, "fsMkdir", cbs);
1223
- if (!v) return;
1224
- if (!ensureWritable(v, "fsMkdir", cbs)) return;
1225
- _fs.mkdir(v.realPath, { recursive }, nodeComplete("fsMkdir", cbs));
1279
+ function normalizeChooseMediaCount(value) {
1280
+ const n = Number(value);
1281
+ if (!Number.isFinite(n)) return 9;
1282
+ return Math.max(1, Math.min(20, Math.floor(n)));
1226
1283
  }
1227
- function fsRmdir({ dirPath, recursive = false, success, fail, complete }) {
1228
- const cbs = bindCallbacks(this, { success, fail, complete });
1229
- if (guardFsAvailable("fsRmdir", cbs)) return;
1230
- const v = resolveOrBail(dirPath, "fsRmdir", cbs);
1231
- if (!v) return;
1232
- if (!ensureWritable(v, "fsRmdir", cbs)) return;
1233
- const rmFn = _fs.rm ?? _fs.rmdir;
1234
- rmFn(v.realPath, { recursive }, nodeComplete("fsRmdir", cbs));
1284
+ function getChooseMediaAccept(mediaType) {
1285
+ const wantsMix = mediaType.includes("mix");
1286
+ const wantsImage = wantsMix || mediaType.includes("image");
1287
+ const wantsVideo = wantsMix || mediaType.includes("video");
1288
+ if (wantsImage && wantsVideo) return "image/*,video/*";
1289
+ return wantsVideo ? "video/*" : "image/*";
1235
1290
  }
1236
- function fsReaddir({ dirPath, success, fail, complete }) {
1237
- const cbs = bindCallbacks(this, { success, fail, complete });
1238
- const { onFail, onComplete } = cbs;
1239
- if (guardFsAvailable("fsReaddir", cbs)) return;
1240
- const v = resolveOrBail(dirPath, "fsReaddir", cbs);
1241
- if (!v) return;
1242
- if (v.kind === "tmp" || v.kind === "store") {
1243
- onFail?.({ errMsg: `fsReaddir:fail ${v.kind} is a flat namespace (no dir tree)` });
1244
- onComplete?.();
1245
- return;
1246
- }
1247
- if (!v.realPath) {
1248
- onFail?.({ errMsg: "fsReaddir:fail invalid path" });
1249
- onComplete?.();
1250
- return;
1251
- }
1252
- _fs.readdir(v.realPath, nodeComplete("fsReaddir", cbs, (files) => ({ files })));
1291
+ function getChooseMediaResultType(files) {
1292
+ const types = new Set(files.map((file) => file.fileType));
1293
+ if (types.size > 1) return "mix";
1294
+ return files[0]?.fileType ?? "image";
1253
1295
  }
1254
- function fsGetFileInfo({ filePath, digestAlgorithm, success, fail, complete }) {
1255
- const cbs = bindCallbacks(this, { success, fail, complete });
1256
- const { onSuccess, onFail, onComplete } = cbs;
1257
- if (guardFsAvailable("fsGetFileInfo", cbs)) return;
1258
- const v = resolveOrBail(filePath, "fsGetFileInfo", cbs);
1259
- if (!v) return;
1260
- if (v.kind === "tmp") {
1261
- _tmpBytes(filePath).then(
1262
- (buf) => {
1263
- const result = { size: buf.length, errMsg: "fsGetFileInfo:ok" };
1264
- if (digestAlgorithm) {
1296
+ function readImageMetadata(src) {
1297
+ return new Promise((resolve) => {
1298
+ const img = new Image();
1299
+ img.onload = () => resolve({ width: img.naturalWidth || 0, height: img.naturalHeight || 0 });
1300
+ img.onerror = () => resolve({ width: 0, height: 0 });
1301
+ img.src = src;
1302
+ });
1303
+ }
1304
+ function readVideoMetadata(src) {
1305
+ return new Promise((resolve) => {
1306
+ const video = document.createElement("video");
1307
+ let settled = false;
1308
+ let seekTimer;
1309
+ const finish = (metadata) => {
1310
+ if (settled) return;
1311
+ settled = true;
1312
+ clearTimeout(timer);
1313
+ if (seekTimer) clearTimeout(seekTimer);
1314
+ video.onloadedmetadata = null;
1315
+ video.onseeked = null;
1316
+ video.onerror = null;
1317
+ video.removeAttribute("src");
1318
+ video.load();
1319
+ resolve(metadata);
1320
+ };
1321
+ const drawThumbnail = (width, height) => {
1322
+ return new Promise((resolveThumb) => {
1323
+ let resolved = false;
1324
+ const done = (value) => {
1325
+ if (resolved) return;
1326
+ resolved = true;
1327
+ resolveThumb(value);
1328
+ };
1329
+ let canvas;
1330
+ try {
1331
+ canvas = document.createElement("canvas");
1332
+ canvas.width = width || 1;
1333
+ canvas.height = height || 1;
1334
+ const ctx = canvas.getContext("2d");
1335
+ ctx?.drawImage(video, 0, 0, canvas.width, canvas.height);
1336
+ } catch {
1337
+ done("");
1338
+ return;
1339
+ }
1340
+ const toDataUrlFallback = () => {
1265
1341
  try {
1266
- const hash = _crypto.createHash(digestAlgorithm === "md5" ? "md5" : "sha1");
1267
- hash.update(buf);
1268
- result.digest = hash.digest("hex");
1342
+ done(canvas.toDataURL("image/jpeg", 0.8));
1269
1343
  } catch {
1344
+ done("");
1270
1345
  }
1346
+ };
1347
+ const fallbackTimer = setTimeout(toDataUrlFallback, VIDEO_THUMBNAIL_TIMEOUT_MS);
1348
+ try {
1349
+ canvas.toBlob(
1350
+ (blob) => {
1351
+ clearTimeout(fallbackTimer);
1352
+ if (settled || resolved) return;
1353
+ if (blob) {
1354
+ done(createTempFilePath(blob));
1355
+ } else {
1356
+ toDataUrlFallback();
1357
+ }
1358
+ },
1359
+ "image/jpeg",
1360
+ 0.8
1361
+ );
1362
+ } catch {
1363
+ clearTimeout(fallbackTimer);
1364
+ toDataUrlFallback();
1271
1365
  }
1272
- onSuccess?.(result);
1273
- onComplete?.();
1274
- },
1275
- tmpFailHandler("fsGetFileInfo", cbs)
1276
- );
1277
- return;
1278
- }
1279
- if (!v.realPath) {
1280
- onFail?.({ errMsg: "fsGetFileInfo:fail invalid path" });
1281
- onComplete?.();
1282
- return;
1366
+ });
1367
+ };
1368
+ const timer = setTimeout(() => finish({ width: 0, height: 0, duration: 0, thumbTempFilePath: "" }), VIDEO_METADATA_TIMEOUT_MS);
1369
+ video.preload = "metadata";
1370
+ video.muted = true;
1371
+ video.onloadedmetadata = () => {
1372
+ const width = video.videoWidth || 0;
1373
+ const height = video.videoHeight || 0;
1374
+ const duration = Number.isFinite(video.duration) ? video.duration : 0;
1375
+ const metadata = { width, height, duration, thumbTempFilePath: "" };
1376
+ if (!width || !height || duration <= 0) {
1377
+ finish(metadata);
1378
+ return;
1379
+ }
1380
+ video.onseeked = async () => {
1381
+ if (seekTimer) {
1382
+ clearTimeout(seekTimer);
1383
+ seekTimer = void 0;
1384
+ }
1385
+ const thumbTempFilePath = await drawThumbnail(width, height);
1386
+ finish({ ...metadata, thumbTempFilePath });
1387
+ };
1388
+ seekTimer = setTimeout(() => finish(metadata), VIDEO_THUMBNAIL_TIMEOUT_MS);
1389
+ try {
1390
+ video.currentTime = Math.min(0.1, Math.max(0, duration - 0.01));
1391
+ } catch {
1392
+ finish(metadata);
1393
+ }
1394
+ };
1395
+ video.onerror = () => finish({ width: 0, height: 0, duration: 0, thumbTempFilePath: "" });
1396
+ video.src = src;
1397
+ });
1398
+ }
1399
+ async function buildChooseMediaTempFile(file) {
1400
+ const tempFilePath = createTempFilePath(file);
1401
+ const fileType = file.type.startsWith("video") ? "video" : "image";
1402
+ if (fileType === "video") {
1403
+ const metadata2 = await readVideoMetadata(tempFilePath);
1404
+ return {
1405
+ tempFilePath,
1406
+ size: file.size,
1407
+ duration: metadata2.duration,
1408
+ height: metadata2.height,
1409
+ width: metadata2.width,
1410
+ thumbTempFilePath: metadata2.thumbTempFilePath,
1411
+ fileType,
1412
+ originalFileObj: file
1413
+ };
1283
1414
  }
1284
- const resolved = v.realPath;
1285
- _fs.stat(resolved, (err, s) => {
1286
- if (err) {
1287
- onFail?.({ errMsg: `fsGetFileInfo:fail ${err.message}` });
1288
- onComplete?.();
1289
- return;
1290
- }
1291
- const result = { size: s.size, errMsg: "fsGetFileInfo:ok" };
1292
- if (digestAlgorithm) {
1415
+ const metadata = await readImageMetadata(tempFilePath);
1416
+ return {
1417
+ tempFilePath,
1418
+ size: file.size,
1419
+ duration: 0,
1420
+ height: metadata.height,
1421
+ width: metadata.width,
1422
+ thumbTempFilePath: "",
1423
+ fileType,
1424
+ originalFileObj: file
1425
+ };
1426
+ }
1427
+ function chooseMedia({ count = 9, mediaType = ["image", "video"], sourceType = ["album", "camera"], camera = "back", success, fail, complete }) {
1428
+ const cbs = bindCallbacks(this, { success, fail, complete });
1429
+ const { onSuccess, onFail, onComplete } = cbs;
1430
+ const normalizedCount = normalizeChooseMediaCount(count);
1431
+ const normalizedMediaType = normalizeStringArray(mediaType, ["image", "video"]);
1432
+ const normalizedSourceType = normalizeStringArray(sourceType, ["album", "camera"]);
1433
+ createFilePicker(
1434
+ "chooseMedia",
1435
+ {
1436
+ accept: getChooseMediaAccept(normalizedMediaType),
1437
+ multiple: normalizedCount > 1,
1438
+ capture: captureAttrFor(normalizedSourceType, camera)
1439
+ },
1440
+ cbs,
1441
+ async (rawFiles, removeInput) => {
1442
+ const files = rawFiles.slice(0, normalizedCount);
1293
1443
  try {
1294
- const hash = _crypto.createHash(digestAlgorithm === "md5" ? "md5" : "sha1");
1295
- const stream = _fs.createReadStream(resolved);
1296
- stream.on("data", (chunk) => hash.update(chunk));
1297
- stream.on("end", () => {
1298
- result.digest = hash.digest("hex");
1299
- onSuccess?.(result);
1300
- onComplete?.();
1301
- });
1302
- stream.on("error", (hashErr) => {
1303
- onFail?.({ errMsg: `fsGetFileInfo:fail ${hashErr.message}` });
1304
- onComplete?.();
1444
+ const tempFiles2 = await Promise.all(files.map(buildChooseMediaTempFile));
1445
+ onSuccess?.({
1446
+ tempFiles: tempFiles2,
1447
+ type: getChooseMediaResultType(tempFiles2),
1448
+ failedCount: 0,
1449
+ errMsg: "chooseMedia:ok"
1305
1450
  });
1306
- return;
1307
- } catch {
1451
+ } catch (error) {
1452
+ onFail?.({ errMsg: `chooseMedia:fail ${error.message}` });
1453
+ } finally {
1454
+ onComplete?.();
1455
+ removeInput();
1308
1456
  }
1309
1457
  }
1310
- onSuccess?.(result);
1311
- onComplete?.();
1312
- });
1313
- }
1314
- function fsSaveFile({ tempFilePath, filePath: _filePath, success, fail, complete }) {
1315
- void _filePath;
1316
- const cbs = bindCallbacks(this, { success, fail, complete });
1317
- const { onFail, onComplete } = cbs;
1318
- if (guardFsAvailable("fsSaveFile", cbs)) return;
1319
- const src = resolveOrBail(tempFilePath, "fsSaveFile", cbs);
1320
- if (!src) return;
1321
- const ext = _path.extname(tempFilePath) || "";
1322
- const id = _crypto.randomUUID() + ext;
1323
- const savedFilePath = `difile://_store/${id}`;
1324
- const destResolved = resolveVPath(savedFilePath);
1325
- if (!destResolved || !destResolved.realPath) {
1326
- onFail?.({ errMsg: "fsSaveFile:fail unable to allocate destination" });
1327
- onComplete?.();
1328
- return;
1329
- }
1330
- const destReal = destResolved.realPath;
1331
- if (src.kind === "tmp") {
1332
- _tmpBytes(tempFilePath).then(
1333
- (bytes) => mkdirpThenWrite(destReal, "fsSaveFile", cbs, (done) => _fs.writeFile(destReal, bytes, done), { savedFilePath }),
1334
- tmpFailHandler("fsSaveFile", cbs)
1335
- );
1336
- return;
1337
- }
1338
- if (!src.realPath) {
1339
- onFail?.({ errMsg: "fsSaveFile:fail invalid src path" });
1340
- onComplete?.();
1341
- return;
1342
- }
1343
- mkdirpThenWrite(destReal, "fsSaveFile", cbs, (done) => _fs.copyFile(src.realPath, destReal, done), { savedFilePath });
1458
+ );
1344
1459
  }
1345
- function fsGetSavedFileList({ success, fail, complete } = {}) {
1460
+ function chooseVideo({ sourceType, camera, success, fail, complete }) {
1346
1461
  const cbs = bindCallbacks(this, { success, fail, complete });
1347
1462
  const { onSuccess, onComplete } = cbs;
1348
- if (guardFsAvailable("fsGetSavedFileList", cbs)) return;
1349
- const storeVpath = resolveVPath("difile://_store/");
1350
- const storeDir = storeVpath?.realPath;
1351
- if (!storeDir) {
1352
- onSuccess?.({ fileList: [], errMsg: "fsGetSavedFileList:ok" });
1353
- onComplete?.();
1354
- return;
1355
- }
1356
- _fs.readdir(storeDir, (err, files) => {
1357
- if (err) {
1358
- onSuccess?.({ fileList: [], errMsg: "fsGetSavedFileList:ok" });
1359
- onComplete?.();
1360
- return;
1361
- }
1362
- let pending = files.length;
1363
- if (pending === 0) {
1364
- onSuccess?.({ fileList: [], errMsg: "fsGetSavedFileList:ok" });
1463
+ const normalizedSourceType = normalizeStringArray(sourceType, ["album", "camera"]);
1464
+ createFilePicker(
1465
+ "chooseVideo",
1466
+ { accept: "video/*", multiple: false, capture: captureAttrFor(normalizedSourceType, camera) },
1467
+ cbs,
1468
+ async (files, removeInput) => {
1469
+ const file = files[0];
1470
+ const tempFilePath = createTempFilePath(file);
1471
+ const metadata = await readVideoMetadata(tempFilePath);
1472
+ onSuccess?.({
1473
+ tempFilePath,
1474
+ duration: metadata.duration,
1475
+ size: file.size,
1476
+ width: metadata.width,
1477
+ height: metadata.height,
1478
+ errMsg: "chooseVideo:ok"
1479
+ });
1365
1480
  onComplete?.();
1366
- return;
1481
+ removeInput();
1367
1482
  }
1368
- const fileList = [];
1369
- for (const name of files) {
1370
- const full = _path.join(storeDir, name);
1371
- _fs.stat(full, (statErr, s) => {
1372
- if (!statErr) {
1373
- fileList.push({
1374
- filePath: `difile://_store/${name}`,
1375
- size: s.size,
1376
- createTime: s.birthtimeMs
1377
- });
1378
- }
1379
- if (--pending === 0) {
1380
- onSuccess?.({ fileList, errMsg: "fsGetSavedFileList:ok" });
1381
- onComplete?.();
1382
- }
1483
+ );
1484
+ }
1485
+ var AUDIO_EVENT_MAP = {
1486
+ play: "play",
1487
+ pause: "pause",
1488
+ ended: "ended",
1489
+ error: "error",
1490
+ timeupdate: "timeUpdate",
1491
+ waiting: "waiting",
1492
+ seeking: "seeking",
1493
+ seeked: "seeked",
1494
+ canplay: "canplay"
1495
+ };
1496
+ var _newAudioInstances = /* @__PURE__ */ new Map();
1497
+ var _audioEventDisposers = /* @__PURE__ */ new Map();
1498
+ var _audioFire = /* @__PURE__ */ new Map();
1499
+ function audioSnapshot(audio, event) {
1500
+ return {
1501
+ event,
1502
+ currentTime: audio.currentTime || 0,
1503
+ duration: Number.isFinite(audio.duration) ? audio.duration : 0,
1504
+ buffered: audio.buffered.length ? audio.buffered.end(audio.buffered.length - 1) : 0,
1505
+ paused: audio.paused
1506
+ };
1507
+ }
1508
+ function audioCreate({ audioId }) {
1509
+ _newAudioInstances.set(audioId, new Audio());
1510
+ }
1511
+ function audioListen({ audioId, success }) {
1512
+ const audio = _newAudioInstances.get(audioId);
1513
+ const fire = this.createCallbackFunction(success);
1514
+ if (!audio || !fire) return;
1515
+ _audioFire.set(audioId, fire);
1516
+ _audioEventDisposers.get(audioId)?.();
1517
+ const dispose = bindDomEvents(
1518
+ audio,
1519
+ AUDIO_EVENT_MAP,
1520
+ fire,
1521
+ (event) => audioSnapshot(audio, event)
1522
+ );
1523
+ _audioEventDisposers.set(audioId, dispose);
1524
+ }
1525
+ function audioSetProp({ audioId, prop, value, startTime, loop, volume, playbackRate, autoplay }) {
1526
+ const audio = _newAudioInstances.get(audioId);
1527
+ if (!audio) return;
1528
+ switch (prop) {
1529
+ case "src":
1530
+ audio.src = value;
1531
+ if (startTime != null) audio.currentTime = startTime;
1532
+ if (loop != null) audio.loop = loop;
1533
+ if (volume != null) audio.volume = Math.max(0, Math.min(1, volume));
1534
+ if (playbackRate != null) audio.playbackRate = playbackRate;
1535
+ if (autoplay) audio.play().catch(() => {
1383
1536
  });
1384
- }
1537
+ break;
1538
+ case "startTime":
1539
+ audio.currentTime = Number(value) || 0;
1540
+ break;
1541
+ case "autoplay":
1542
+ audio.autoplay = !!value;
1543
+ break;
1544
+ case "loop":
1545
+ audio.loop = !!value;
1546
+ break;
1547
+ case "volume":
1548
+ audio.volume = Math.max(0, Math.min(1, Number(value) || 0));
1549
+ break;
1550
+ case "playbackRate":
1551
+ audio.playbackRate = Number(value) || 1;
1552
+ break;
1553
+ }
1554
+ }
1555
+ function audioPlay({ audioId, src }) {
1556
+ const audio = _newAudioInstances.get(audioId);
1557
+ if (!audio) return;
1558
+ if (src && audio.src !== src) audio.src = src;
1559
+ audio.play().catch(() => {
1385
1560
  });
1386
1561
  }
1387
- function fsRemoveSavedFile({ filePath, success, fail, complete }) {
1388
- const cbs = bindCallbacks(this, { success, fail, complete });
1389
- const { onFail, onComplete } = cbs;
1390
- if (guardFsAvailable("fsRemoveSavedFile", cbs)) return;
1391
- const v = resolveOrBail(filePath, "fsRemoveSavedFile", cbs);
1392
- if (!v) return;
1393
- if (v.kind !== "store" || !v.realPath) {
1394
- onFail?.({ errMsg: "fsRemoveSavedFile:fail only _store/ entries may be removed" });
1395
- onComplete?.();
1396
- return;
1562
+ function audioPause({ audioId }) {
1563
+ _newAudioInstances.get(audioId)?.pause();
1564
+ }
1565
+ function audioStop({ audioId }) {
1566
+ const audio = _newAudioInstances.get(audioId);
1567
+ if (!audio) return;
1568
+ audio.pause();
1569
+ audio.currentTime = 0;
1570
+ _audioFire.get(audioId)?.(audioSnapshot(audio, "stop"));
1571
+ }
1572
+ function audioSeek({ audioId, position }) {
1573
+ const audio = _newAudioInstances.get(audioId);
1574
+ if (!audio) return;
1575
+ audio.currentTime = position;
1576
+ }
1577
+ function audioDestroy({ audioId }) {
1578
+ _audioEventDisposers.get(audioId)?.();
1579
+ _audioEventDisposers.delete(audioId);
1580
+ _audioFire.delete(audioId);
1581
+ const audio = _newAudioInstances.get(audioId);
1582
+ if (!audio) return;
1583
+ audio.pause();
1584
+ audio.removeAttribute("src");
1585
+ audio.load();
1586
+ _newAudioInstances.delete(audioId);
1587
+ }
1588
+
1589
+ // src/simulator/simulator-api-fsm.ts
1590
+ var ARRAY_BUFFER_BASE64_KEY = "__diminaArrayBufferBase64";
1591
+ var FILE_DATA_TYPE_KEY = "__diminaFileDataType";
1592
+ var FILE_DATA_BASE64_KEY = "__diminaFileDataBase64";
1593
+ var FSM_ADAPTERS = [
1594
+ { short: "access", impl: fsAccess },
1595
+ { short: "stat", impl: fsStat },
1596
+ { short: "readFile", impl: fsReadFile, encodeReadResult: true },
1597
+ { short: "writeFile", impl: fsWriteFile, decodeWriteData: true },
1598
+ { short: "appendFile", impl: fsAppendFile, decodeWriteData: true },
1599
+ { short: "copyFile", impl: fsCopyFile },
1600
+ { short: "rename", impl: fsRename },
1601
+ { short: "unlink", impl: fsUnlink },
1602
+ { short: "mkdir", impl: fsMkdir },
1603
+ { short: "rmdir", impl: fsRmdir },
1604
+ { short: "readdir", impl: fsReaddir },
1605
+ { short: "getFileInfo", impl: fsGetFileInfo },
1606
+ { short: "saveFile", impl: fsSaveFile },
1607
+ { short: "getSavedFileList", impl: fsGetSavedFileList },
1608
+ { short: "removeSavedFile", impl: fsRemoveSavedFile },
1609
+ { short: "truncate", impl: fsTruncate }
1610
+ ];
1611
+ function legacyPrefix(short) {
1612
+ return `fs${short[0].toUpperCase()}${short.slice(1)}`;
1613
+ }
1614
+ function renameErrMsg(payload, legacy, short) {
1615
+ if (!payload || typeof payload !== "object") return payload;
1616
+ const rec = payload;
1617
+ if (typeof rec.errMsg === "string" && rec.errMsg.startsWith(`${legacy}:`)) {
1618
+ return { ...rec, errMsg: `${short}:${rec.errMsg.slice(legacy.length + 1)}` };
1397
1619
  }
1398
- _fs.unlink(v.realPath, nodeComplete("fsRemoveSavedFile", cbs));
1620
+ return payload;
1621
+ }
1622
+ var BufferImpl2 = getNodeBindings().buffer?.Buffer ?? (typeof Buffer !== "undefined" ? Buffer : void 0);
1623
+ function decodeFileData(data) {
1624
+ if (!BufferImpl2) return data;
1625
+ if (!data || typeof data !== "object") return data;
1626
+ const rec = data;
1627
+ if (rec[FILE_DATA_TYPE_KEY] !== "base64" || typeof rec[FILE_DATA_BASE64_KEY] !== "string") return data;
1628
+ return BufferImpl2.from(rec[FILE_DATA_BASE64_KEY], "base64");
1629
+ }
1630
+ function encodeReadResult(payload) {
1631
+ if (!BufferImpl2) return payload;
1632
+ if (!payload || typeof payload !== "object") return payload;
1633
+ const rec = payload;
1634
+ const data = rec.data;
1635
+ if (typeof data === "string" || !ArrayBuffer.isView(data)) return payload;
1636
+ const bytes = BufferImpl2.from(data.buffer, data.byteOffset, data.byteLength);
1637
+ return { ...rec, data: { [ARRAY_BUFFER_BASE64_KEY]: bytes.toString("base64") } };
1638
+ }
1639
+ function makeFsmHandler(spec) {
1640
+ const legacy = legacyPrefix(spec.short);
1641
+ return function(params) {
1642
+ const raw = params && typeof params === "object" && !Array.isArray(params) ? { ...params } : {};
1643
+ const { success, fail, complete, ...rest } = raw;
1644
+ const cbs = bindCallbacks(this, { success, fail, complete });
1645
+ const opts = rest;
1646
+ if (spec.decodeWriteData && "data" in opts) {
1647
+ opts.data = decodeFileData(opts.data);
1648
+ }
1649
+ let verdictFired = false;
1650
+ let completeFired = false;
1651
+ const fireSuccess = (out) => {
1652
+ if (verdictFired) return;
1653
+ verdictFired = true;
1654
+ cbs.onSuccess?.(out);
1655
+ };
1656
+ const fireFail = (out) => {
1657
+ if (verdictFired) return;
1658
+ verdictFired = true;
1659
+ cbs.onFail?.(out);
1660
+ };
1661
+ const fireComplete = () => {
1662
+ if (completeFired) return;
1663
+ completeFired = true;
1664
+ cbs.onComplete?.();
1665
+ };
1666
+ opts.success = (res) => {
1667
+ let out = renameErrMsg(res, legacy, spec.short);
1668
+ if (spec.encodeReadResult) out = encodeReadResult(out);
1669
+ fireSuccess(out);
1670
+ };
1671
+ opts.fail = (res) => {
1672
+ fireFail(renameErrMsg(res, legacy, spec.short));
1673
+ };
1674
+ opts.complete = fireComplete;
1675
+ const innerCtx = Object.create(this);
1676
+ innerCtx.createCallbackFunction = (fn) => typeof fn === "function" ? fn : void 0;
1677
+ try {
1678
+ spec.impl.call(innerCtx, opts);
1679
+ } catch (err) {
1680
+ const msg = err instanceof Error ? err.message : String(err);
1681
+ fireFail({ errMsg: `${spec.short}:fail ${msg}` });
1682
+ fireComplete();
1683
+ }
1684
+ };
1399
1685
  }
1400
- function fsTruncate({ filePath, length = 0, success, fail, complete }) {
1401
- const cbs = bindCallbacks(this, { success, fail, complete });
1402
- if (guardFsAvailable("fsTruncate", cbs)) return;
1403
- const v = resolveOrBail(filePath, "fsTruncate", cbs);
1404
- if (!v) return;
1405
- if (!ensureWritable(v, "fsTruncate", cbs)) return;
1406
- _fs.truncate(v.realPath, length, nodeComplete("fsTruncate", cbs));
1686
+ var handlers = {};
1687
+ for (const spec of FSM_ADAPTERS) {
1688
+ handlers[`FileSystemManager.${spec.short}`] = makeFsmHandler(spec);
1689
+ }
1690
+ var fileSystemManagerApis = handlers;
1691
+
1692
+ // ../dimina-electron-runtime/dist/shared/request-core.js
1693
+ var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
1694
+ var MAX_TIMEOUT_MS = 2147483647;
1695
+ function resolveTimeoutBudgetMs(timeout) {
1696
+ const t = Number(timeout);
1697
+ return Number.isFinite(t) && t > 0 && t <= MAX_TIMEOUT_MS ? t : DEFAULT_REQUEST_TIMEOUT_MS;
1407
1698
  }
1408
- var fsUnzip = notSupportedApi("fsUnzip");
1409
1699
 
1410
1700
  // src/simulator/simulator-api-network.ts
1411
- function downloadFile({ url, header = {}, filePath, success, fail, complete }) {
1701
+ function downloadFile({ url, header = {}, filePath, timeout, success, fail, complete }) {
1412
1702
  const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
1413
- fetch(url, { headers: header }).then(async (response) => {
1414
- if (!response.ok) throw new Error(response.statusText);
1703
+ let settled = false;
1704
+ const settleFail = (errMsg) => {
1705
+ if (settled) return;
1706
+ settled = true;
1707
+ clearTimeout(timer);
1708
+ onFail?.({ errMsg });
1709
+ onComplete?.();
1710
+ };
1711
+ const settleOk = (result) => {
1712
+ if (settled) return;
1713
+ settled = true;
1714
+ clearTimeout(timer);
1715
+ onSuccess?.(result);
1716
+ onComplete?.();
1717
+ };
1718
+ const controller = new AbortController();
1719
+ const timer = setTimeout(() => {
1720
+ settleFail("downloadFile:fail timeout");
1721
+ controller.abort();
1722
+ }, resolveTimeoutBudgetMs(timeout));
1723
+ fetch(url, { headers: header, signal: controller.signal }).then(async (response) => {
1724
+ if (!response.ok) {
1725
+ settleFail(
1726
+ `downloadFile:fail HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`
1727
+ );
1728
+ return;
1729
+ }
1415
1730
  const blob = await response.blob();
1416
1731
  const tempFilePath = createTempFilePath(blob);
1417
- onSuccess?.({
1732
+ settleOk({
1418
1733
  tempFilePath,
1419
1734
  filePath: filePath || tempFilePath,
1420
1735
  statusCode: response.status,
1421
1736
  errMsg: "downloadFile:ok"
1422
1737
  });
1423
1738
  }).catch((error) => {
1424
- onFail?.({ errMsg: `downloadFile:fail ${error.message}` });
1425
- }).finally(() => {
1426
- onComplete?.();
1739
+ settleFail(`downloadFile:fail ${error.message}`);
1427
1740
  });
1428
1741
  }
1429
1742
  var uploadRequests = /* @__PURE__ */ new Map();
@@ -1870,24 +2183,9 @@ var simulatorApis = {
1870
2183
  audioStop,
1871
2184
  audioSeek,
1872
2185
  audioDestroy,
1873
- // Filesystem (service-apis/file)
1874
- fsAccess,
1875
- fsStat,
1876
- fsReadFile,
1877
- fsWriteFile,
1878
- fsAppendFile,
1879
- fsCopyFile,
1880
- fsRename,
1881
- fsUnlink,
1882
- fsMkdir,
1883
- fsRmdir,
1884
- fsReaddir,
1885
- fsGetFileInfo,
1886
- fsSaveFile,
1887
- fsGetSavedFileList,
1888
- fsRemoveSavedFile,
1889
- fsTruncate,
1890
- fsUnzip
2186
+ // Filesystem: the service thread invokes the dotted FileSystemManager.*
2187
+ // wire names (see simulator-api-fsm.ts).
2188
+ ...fileSystemManagerApis
1891
2189
  };
1892
2190
 
1893
2191
  // src/preload/windows/main.ts