@dimina-kit/devtools 0.4.0-dev.20260731100034 → 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.
@@ -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
  }
@@ -412,6 +412,71 @@ function bindDomEvents(target, eventMap, fire, buildPayload) {
412
412
  };
413
413
  }
414
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
+
415
480
  // src/simulator/temp-files.ts
416
481
  var tempFiles = /* @__PURE__ */ new Map();
417
482
  var activeSink = null;
@@ -451,995 +516,1227 @@ function getTempFileName(path2, blob, fallback = "file") {
451
516
  return fallback;
452
517
  }
453
518
 
454
- // src/simulator/simulator-api-media.ts
455
- function captureAttrFor(sourceType, camera) {
456
- if (sourceType.length !== 1 || sourceType[0] !== "camera") return void 0;
457
- 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
+ }
458
528
  }
459
- function createFilePicker(apiName, config, cbs, onPick) {
460
- const input = document.createElement("input");
461
- input.type = "file";
462
- input.accept = config.accept;
463
- input.multiple = config.multiple;
464
- if (config.capture) input.setAttribute("capture", config.capture);
465
- input.style.display = "none";
466
- document.body.appendChild(input);
467
- const removeInput = () => input.remove();
468
- input.addEventListener("change", () => {
469
- const files = Array.from(input.files || []);
470
- if (files.length === 0) {
471
- cbs.onFail?.({ errMsg: `${apiName}:fail cancel` });
472
- cbs.onComplete?.();
473
- removeInput();
474
- return;
475
- }
476
- void onPick(files, removeInput);
477
- });
478
- input.click();
529
+ function isArrayBuffer(value) {
530
+ return Object.prototype.toString.call(value) === "[object ArrayBuffer]";
479
531
  }
480
- function chooseImage({ count = 9, sourceType, camera, success, fail, complete }) {
481
- const cbs = bindCallbacks(this, { success, fail, complete });
482
- const { onSuccess, onComplete } = cbs;
483
- const normalizedCount = normalizeChooseMediaCount(count);
484
- const normalizedSourceType = normalizeStringArray(sourceType, ["album", "camera"]);
485
- createFilePicker(
486
- "chooseImage",
487
- { accept: "image/*", multiple: normalizedCount > 1, capture: captureAttrFor(normalizedSourceType, camera) },
488
- cbs,
489
- (rawFiles, removeInput) => {
490
- const files = rawFiles.slice(0, normalizedCount);
491
- const tempFilePaths = files.map((f) => createTempFilePath(f));
492
- const tempFiles2 = files.map((f, i) => ({ path: tempFilePaths[i], size: f.size }));
493
- onSuccess?.({ tempFilePaths, tempFiles: tempFiles2, errMsg: "chooseImage:ok" });
494
- onComplete?.();
495
- removeInput();
496
- }
497
- );
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
+ });
498
558
  }
499
- function previewImage({ urls, current, success, complete }) {
500
- const { onSuccess, onComplete } = bindCallbacks(this, { success, complete });
501
- if (!urls || urls.length === 0) {
502
- onComplete?.();
503
- 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;
504
564
  }
505
- const overlay = document.createElement("div");
506
- 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;";
507
- const img = document.createElement("img");
508
- img.src = current || urls[0] || "";
509
- img.style.cssText = "max-width:90%;max-height:90%;object-fit:contain;";
510
- overlay.appendChild(img);
511
- overlay.addEventListener("click", () => overlay.remove());
512
- document.body.appendChild(overlay);
513
- onSuccess?.({ errMsg: "previewImage:ok" });
514
- onComplete?.();
565
+ return null;
515
566
  }
516
- function compressImage({ src, quality = 80, success, fail, complete }) {
517
- const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
518
- const img = new Image();
519
- img.crossOrigin = "anonymous";
520
- 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;
521
583
  try {
522
- const canvas = document.createElement("canvas");
523
- canvas.width = img.naturalWidth;
524
- canvas.height = img.naturalHeight;
525
- const ctx = canvas.getContext("2d");
526
- ctx.drawImage(img, 0, 0);
527
- canvas.toBlob(
528
- (blob) => {
529
- if (blob) {
530
- const tempFilePath = createTempFilePath(blob);
531
- onSuccess?.({ tempFilePath, errMsg: "compressImage:ok" });
532
- } else {
533
- onFail?.({ errMsg: "compressImage:fail compression error" });
534
- }
535
- onComplete?.();
536
- },
537
- "image/jpeg",
538
- quality / 100
539
- );
540
- } catch (error) {
541
- onFail?.({ errMsg: `compressImage:fail ${error.message}` });
542
- onComplete?.();
584
+ entry = _fs.lstatSync(cur);
585
+ } catch {
586
+ return false;
543
587
  }
544
- };
545
- img.onerror = () => {
546
- onFail?.({ errMsg: "compressImage:fail image load error" });
547
- onComplete?.();
548
- };
549
- img.src = src;
550
- }
551
- function saveImageToPhotosAlbum({ filePath, success, fail, complete }) {
552
- const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
553
- try {
554
- const a = document.createElement("a");
555
- a.href = filePath;
556
- a.download = "image";
557
- a.click();
558
- onSuccess?.({ errMsg: "saveImageToPhotosAlbum:ok" });
559
- } catch (error) {
560
- onFail?.({ errMsg: `saveImageToPhotosAlbum:fail ${error.message}` });
588
+ if (entry.isSymbolicLink()) return true;
561
589
  }
562
- onComplete?.();
590
+ return false;
563
591
  }
564
- function getImageInfo({ src, success, fail, complete }) {
565
- const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
566
- const img = new Image();
567
- img.crossOrigin = "anonymous";
568
- img.onload = () => {
569
- onSuccess?.({
570
- width: img.naturalWidth,
571
- height: img.naturalHeight,
572
- path: src,
573
- orientation: "up",
574
- type: "unknown",
575
- errMsg: "getImageInfo:ok"
576
- });
577
- onComplete?.();
578
- };
579
- img.onerror = () => {
580
- onFail?.({ errMsg: "getImageInfo:fail image load error" });
581
- onComplete?.();
582
- };
583
- img.src = src;
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;
584
598
  }
585
- var VIDEO_METADATA_TIMEOUT_MS = 5e3;
586
- var VIDEO_THUMBNAIL_TIMEOUT_MS = 500;
587
- function normalizeStringArray(value, fallback) {
588
- return Array.isArray(value) ? value.filter((item) => typeof item === "string") : fallback;
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()
607
+ };
589
608
  }
590
- function normalizeChooseMediaCount(value) {
591
- const n = Number(value);
592
- if (!Number.isFinite(n)) return 9;
593
- return Math.max(1, Math.min(20, Math.floor(n)));
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;
594
614
  }
595
- function getChooseMediaAccept(mediaType) {
596
- const wantsMix = mediaType.includes("mix");
597
- const wantsImage = wantsMix || mediaType.includes("image");
598
- const wantsVideo = wantsMix || mediaType.includes("video");
599
- if (wantsImage && wantsVideo) return "image/*,video/*";
600
- return wantsVideo ? "video/*" : "image/*";
615
+ function tmpFailHandler(apiName, cbs) {
616
+ return (err) => {
617
+ cbs.onFail?.({ errMsg: `${apiName}:fail ${err.message}` });
618
+ cbs.onComplete?.();
619
+ };
601
620
  }
602
- function getChooseMediaResultType(files) {
603
- const types = new Set(files.map((file) => file.fileType));
604
- if (types.size > 1) return "mix";
605
- 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
+ };
606
630
  }
607
- function readImageMetadata(src) {
608
- return new Promise((resolve) => {
609
- const img = new Image();
610
- img.onload = () => resolve({ width: img.naturalWidth || 0, height: img.naturalHeight || 0 });
611
- img.onerror = () => resolve({ width: 0, height: 0 });
612
- 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
+ }
613
644
  });
614
645
  }
615
- function readVideoMetadata(src) {
616
- return new Promise((resolve) => {
617
- 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 = {};
618
703
  let settled = false;
619
- let seekTimer;
620
- const finish = (metadata) => {
704
+ const failOnce = (err) => {
621
705
  if (settled) return;
622
706
  settled = true;
623
- clearTimeout(timer);
624
- if (seekTimer) clearTimeout(seekTimer);
625
- video.onloadedmetadata = null;
626
- video.onseeked = null;
627
- video.onerror = null;
628
- video.removeAttribute("src");
629
- video.load();
630
- resolve(metadata);
707
+ onFail?.({ errMsg: `fsStat:fail ${err.message}` });
708
+ onComplete?.();
631
709
  };
632
- const drawThumbnail = (width, height) => {
633
- return new Promise((resolveThumb) => {
634
- let resolved = false;
635
- const done = (value) => {
636
- if (resolved) return;
637
- resolved = true;
638
- resolveThumb(value);
639
- };
640
- let canvas;
641
- try {
642
- canvas = document.createElement("canvas");
643
- canvas.width = width || 1;
644
- canvas.height = height || 1;
645
- const ctx = canvas.getContext("2d");
646
- ctx?.drawImage(video, 0, 0, canvas.width, canvas.height);
647
- } catch {
648
- 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);
649
724
  return;
650
725
  }
651
- const toDataUrlFallback = () => {
652
- try {
653
- done(canvas.toDataURL("image/jpeg", 0.8));
654
- } catch {
655
- 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;
656
752
  }
753
+ if (--pending === 0) cb(null);
657
754
  };
658
- const fallbackTimer = setTimeout(toDataUrlFallback, VIDEO_THUMBNAIL_TIMEOUT_MS);
659
- try {
660
- canvas.toBlob(
661
- (blob) => {
662
- clearTimeout(fallbackTimer);
663
- if (settled || resolved) return;
664
- if (blob) {
665
- done(createTempFilePath(blob));
666
- } else {
667
- toDataUrlFallback();
668
- }
669
- },
670
- "image/jpeg",
671
- 0.8
672
- );
673
- } catch {
674
- clearTimeout(fallbackTimer);
675
- 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
+ });
676
765
  }
677
766
  });
678
767
  };
679
- const timer = setTimeout(() => finish({ width: 0, height: 0, duration: 0, thumbTempFilePath: "" }), VIDEO_METADATA_TIMEOUT_MS);
680
- video.preload = "metadata";
681
- video.muted = true;
682
- video.onloadedmetadata = () => {
683
- const width = video.videoWidth || 0;
684
- const height = video.videoHeight || 0;
685
- const duration = Number.isFinite(video.duration) ? video.duration : 0;
686
- const metadata = { width, height, duration, thumbTempFilePath: "" };
687
- if (!width || !height || duration <= 0) {
688
- finish(metadata);
768
+ statInto(resolved, (rootErr) => {
769
+ if (rootErr) {
770
+ failOnce(rootErr);
689
771
  return;
690
772
  }
691
- video.onseeked = async () => {
692
- if (seekTimer) {
693
- clearTimeout(seekTimer);
694
- seekTimer = void 0;
695
- }
696
- const thumbTempFilePath = await drawThumbnail(width, height);
697
- finish({ ...metadata, thumbTempFilePath });
698
- };
699
- seekTimer = setTimeout(() => finish(metadata), VIDEO_THUMBNAIL_TIMEOUT_MS);
700
- try {
701
- video.currentTime = Math.min(0.1, Math.max(0, duration - 0.01));
702
- } catch {
703
- finish(metadata);
704
- }
705
- };
706
- video.onerror = () => finish({ width: 0, height: 0, duration: 0, thumbTempFilePath: "" });
707
- video.src = src;
708
- });
709
- }
710
- async function buildChooseMediaTempFile(file) {
711
- const tempFilePath = createTempFilePath(file);
712
- const fileType = file.type.startsWith("video") ? "video" : "image";
713
- if (fileType === "video") {
714
- const metadata2 = await readVideoMetadata(tempFilePath);
715
- return {
716
- tempFilePath,
717
- size: file.size,
718
- duration: metadata2.duration,
719
- height: metadata2.height,
720
- width: metadata2.width,
721
- thumbTempFilePath: metadata2.thumbTempFilePath,
722
- fileType,
723
- originalFileObj: file
724
- };
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
+ })));
725
782
  }
726
- const metadata = await readImageMetadata(tempFilePath);
727
- return {
728
- tempFilePath,
729
- size: file.size,
730
- duration: 0,
731
- height: metadata.height,
732
- width: metadata.width,
733
- thumbTempFilePath: "",
734
- fileType,
735
- originalFileObj: file
736
- };
737
783
  }
738
- function chooseMedia({ count = 9, mediaType = ["image", "video"], sourceType = ["album", "camera"], camera = "back", success, fail, complete }) {
784
+ function fsReadFile({ filePath, encoding, success, fail, complete }) {
739
785
  const cbs = bindCallbacks(this, { success, fail, complete });
740
786
  const { onSuccess, onFail, onComplete } = cbs;
741
- const normalizedCount = normalizeChooseMediaCount(count);
742
- const normalizedMediaType = normalizeStringArray(mediaType, ["image", "video"]);
743
- const normalizedSourceType = normalizeStringArray(sourceType, ["album", "camera"]);
744
- createFilePicker(
745
- "chooseMedia",
746
- {
747
- accept: getChooseMediaAccept(normalizedMediaType),
748
- multiple: normalizedCount > 1,
749
- capture: captureAttrFor(normalizedSourceType, camera)
750
- },
751
- cbs,
752
- async (rawFiles, removeInput) => {
753
- const files = rawFiles.slice(0, normalizedCount);
754
- try {
755
- const tempFiles2 = await Promise.all(files.map(buildChooseMediaTempFile));
756
- onSuccess?.({
757
- tempFiles: tempFiles2,
758
- type: getChooseMediaResultType(tempFiles2),
759
- failedCount: 0,
760
- errMsg: "chooseMedia:ok"
761
- });
762
- } catch (error) {
763
- onFail?.({ errMsg: `chooseMedia:fail ${error.message}` });
764
- } 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" });
765
795
  onComplete?.();
766
- removeInput();
767
- }
768
- }
769
- );
770
- }
771
- function chooseVideo({ sourceType, camera, success, fail, complete }) {
772
- const cbs = bindCallbacks(this, { success, fail, complete });
773
- const { onSuccess, onComplete } = cbs;
774
- const normalizedSourceType = normalizeStringArray(sourceType, ["album", "camera"]);
775
- createFilePicker(
776
- "chooseVideo",
777
- { accept: "video/*", multiple: false, capture: captureAttrFor(normalizedSourceType, camera) },
778
- cbs,
779
- async (files, removeInput) => {
780
- const file = files[0];
781
- const tempFilePath = createTempFilePath(file);
782
- const metadata = await readVideoMetadata(tempFilePath);
783
- onSuccess?.({
784
- tempFilePath,
785
- duration: metadata.duration,
786
- size: file.size,
787
- width: metadata.width,
788
- height: metadata.height,
789
- errMsg: "chooseVideo:ok"
790
- });
791
- onComplete?.();
792
- removeInput();
793
- }
794
- );
795
- }
796
- var AUDIO_EVENT_MAP = {
797
- play: "play",
798
- pause: "pause",
799
- ended: "ended",
800
- error: "error",
801
- timeupdate: "timeUpdate",
802
- waiting: "waiting",
803
- seeking: "seeking",
804
- seeked: "seeked",
805
- canplay: "canplay"
806
- };
807
- var _newAudioInstances = /* @__PURE__ */ new Map();
808
- var _audioEventDisposers = /* @__PURE__ */ new Map();
809
- var _audioFire = /* @__PURE__ */ new Map();
810
- function audioSnapshot(audio, event) {
811
- return {
812
- event,
813
- currentTime: audio.currentTime || 0,
814
- duration: Number.isFinite(audio.duration) ? audio.duration : 0,
815
- buffered: audio.buffered.length ? audio.buffered.end(audio.buffered.length - 1) : 0,
816
- paused: audio.paused
817
- };
818
- }
819
- function audioCreate({ audioId }) {
820
- _newAudioInstances.set(audioId, new Audio());
821
- }
822
- function audioListen({ audioId, success }) {
823
- const audio = _newAudioInstances.get(audioId);
824
- const fire = this.createCallbackFunction(success);
825
- if (!audio || !fire) return;
826
- _audioFire.set(audioId, fire);
827
- _audioEventDisposers.get(audioId)?.();
828
- const dispose = bindDomEvents(
829
- audio,
830
- AUDIO_EVENT_MAP,
831
- fire,
832
- (event) => audioSnapshot(audio, event)
833
- );
834
- _audioEventDisposers.set(audioId, dispose);
835
- }
836
- function audioSetProp({ audioId, prop, value, startTime, loop, volume, playbackRate, autoplay }) {
837
- const audio = _newAudioInstances.get(audioId);
838
- if (!audio) return;
839
- switch (prop) {
840
- case "src":
841
- audio.src = value;
842
- if (startTime != null) audio.currentTime = startTime;
843
- if (loop != null) audio.loop = loop;
844
- if (volume != null) audio.volume = Math.max(0, Math.min(1, volume));
845
- if (playbackRate != null) audio.playbackRate = playbackRate;
846
- if (autoplay) audio.play().catch(() => {
847
- });
848
- break;
849
- case "startTime":
850
- audio.currentTime = Number(value) || 0;
851
- break;
852
- case "autoplay":
853
- audio.autoplay = !!value;
854
- break;
855
- case "loop":
856
- audio.loop = !!value;
857
- break;
858
- case "volume":
859
- audio.volume = Math.max(0, Math.min(1, Number(value) || 0));
860
- break;
861
- case "playbackRate":
862
- audio.playbackRate = Number(value) || 1;
863
- break;
864
- }
865
- }
866
- function audioPlay({ audioId, src }) {
867
- const audio = _newAudioInstances.get(audioId);
868
- if (!audio) return;
869
- if (src && audio.src !== src) audio.src = src;
870
- audio.play().catch(() => {
871
- });
872
- }
873
- function audioPause({ audioId }) {
874
- _newAudioInstances.get(audioId)?.pause();
875
- }
876
- function audioStop({ audioId }) {
877
- const audio = _newAudioInstances.get(audioId);
878
- if (!audio) return;
879
- audio.pause();
880
- audio.currentTime = 0;
881
- _audioFire.get(audioId)?.(audioSnapshot(audio, "stop"));
882
- }
883
- function audioSeek({ audioId, position }) {
884
- const audio = _newAudioInstances.get(audioId);
885
- if (!audio) return;
886
- audio.currentTime = position;
887
- }
888
- function audioDestroy({ audioId }) {
889
- _audioEventDisposers.get(audioId)?.();
890
- _audioEventDisposers.delete(audioId);
891
- _audioFire.delete(audioId);
892
- const audio = _newAudioInstances.get(audioId);
893
- if (!audio) return;
894
- audio.pause();
895
- audio.removeAttribute("src");
896
- audio.load();
897
- _newAudioInstances.delete(audioId);
898
- }
899
-
900
- // ../dimina-electron-runtime/dist/shared/vpath.js
901
- var import_node_os = __toESM(require("node:os"), 1);
902
- var import_node_path = __toESM(require("node:path"), 1);
903
- var DIFILE_PREFIX = "difile://";
904
- function sandboxBase() {
905
- const env = typeof process !== "undefined" && process.env && process.env.DIMINA_HOME || "";
906
- if (env) {
907
- return import_node_path.default.join(env, "files");
908
- }
909
- return import_node_path.default.join(import_node_os.default.homedir(), ".dimina", "files");
910
- }
911
- function resolveVPath(url) {
912
- if (typeof url !== "string")
913
- return null;
914
- if (!url.startsWith(DIFILE_PREFIX))
915
- return null;
916
- const raw = url.slice(DIFILE_PREFIX.length);
917
- if (raw.length === 0)
918
- return null;
919
- let decoded;
920
- try {
921
- decoded = decodeURIComponent(raw);
922
- } catch {
923
- return null;
924
- }
925
- if (decoded.includes("\0"))
926
- return null;
927
- decoded = decoded.replace(/^[/\\]+/, "");
928
- if (decoded.length === 0)
929
- return null;
930
- const segments = decoded.split(/[/\\]+/);
931
- if (segments.some((s) => s === ".." || s === "."))
932
- return null;
933
- let kind;
934
- let writable;
935
- if (decoded.startsWith("_tmp/")) {
936
- kind = "tmp";
937
- writable = false;
938
- } else if (decoded.startsWith("_store/")) {
939
- kind = "store";
940
- writable = false;
941
- } else {
942
- kind = "usr";
943
- writable = true;
796
+ },
797
+ tmpFailHandler("fsReadFile", cbs)
798
+ );
799
+ return;
944
800
  }
945
- if (kind === "tmp") {
946
- return { kind, writable };
801
+ if (!v.realPath) {
802
+ onFail?.({ errMsg: "fsReadFile:fail invalid path" });
803
+ onComplete?.();
804
+ return;
947
805
  }
948
- const base = sandboxBase();
949
- const joined = import_node_path.default.join(base, decoded);
950
- const normalized = import_node_path.default.normalize(joined);
951
- if (normalized !== base && !normalized.startsWith(base + import_node_path.default.sep))
952
- return null;
953
- return { kind, writable, realPath: normalized };
806
+ _fs.readFile(v.realPath, encoding || null, nodeComplete("fsReadFile", cbs, (data) => ({ data })));
954
807
  }
955
-
956
- // src/simulator/simulator-api-fs.ts
957
- async function _tmpBytes(url) {
958
- try {
959
- const blob = await resolveTempFilePath(url);
960
- return await blobToBuffer(blob);
961
- } catch (err) {
962
- const msg = err instanceof Error ? err.message : String(err);
963
- throw new Error(`ENOENT: no such file (${url}): ${msg}`, { cause: err });
964
- }
808
+ function fsWriteFile({ filePath, data, encoding = "utf8", success, fail, complete }) {
809
+ const cbs = bindCallbacks(this, { success, fail, complete });
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));
965
815
  }
966
- function isArrayBuffer(value) {
967
- return Object.prototype.toString.call(value) === "[object ArrayBuffer]";
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));
968
823
  }
969
- async function blobToBuffer(blob) {
970
- const readable = blob;
971
- if (typeof readable.arrayBuffer === "function") {
972
- const ab = await readable.arrayBuffer();
973
- return Buffer.from(new Uint8Array(ab));
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;
974
839
  }
975
- if (typeof FileReader !== "function") {
976
- throw new Error("Blob cannot be read as ArrayBuffer");
840
+ if (!vSrc.realPath) {
841
+ onFail?.({ errMsg: "fsCopyFile:fail invalid src path" });
842
+ onComplete?.();
843
+ return;
977
844
  }
978
- return new Promise((resolve, reject) => {
979
- const reader = new FileReader();
980
- reader.onerror = () => reject(reader.error ?? new Error("Blob read failed"));
981
- reader.onload = () => {
982
- const result = reader.result;
983
- if (!isArrayBuffer(result)) {
984
- reject(new Error("Blob read did not return an ArrayBuffer"));
985
- return;
986
- }
987
- resolve(Buffer.from(new Uint8Array(result)));
988
- };
989
- reader.readAsArrayBuffer(blob);
990
- });
991
- }
992
- var _fs = typeof require !== "undefined" ? require("fs") : null;
993
- var _path = typeof require !== "undefined" ? require("path") : null;
994
- var _crypto = typeof require !== "undefined" ? require("crypto") : null;
995
- function guardFsAvailable(apiName, cbs) {
996
- if (_fs) return false;
997
- cbs.onFail?.({ errMsg: `${apiName}:fail not available in browser context` });
998
- cbs.onComplete?.();
999
- return true;
1000
- }
1001
- function resolveOrBail(p, apiName, cbs) {
1002
- const v = resolveVPath(p);
1003
- if (v) return v;
1004
- cbs.onFail?.({ errMsg: `${apiName}:fail invalid or unsafe path` });
1005
- cbs.onComplete?.();
1006
- return void 0;
845
+ _fs.copyFile(vSrc.realPath, vDest.realPath, nodeComplete("fsCopyFile", cbs));
1007
846
  }
1008
- function ensureWritable(v, apiName, cbs) {
1009
- if (v.writable && v.realPath) return true;
1010
- cbs.onFail?.({ errMsg: `${apiName}:fail permission denied` });
1011
- cbs.onComplete?.();
1012
- return false;
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));
1013
857
  }
1014
- function tmpFailHandler(apiName, cbs) {
1015
- return (err) => {
1016
- cbs.onFail?.({ errMsg: `${apiName}:fail ${err.message}` });
1017
- cbs.onComplete?.();
1018
- };
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));
1019
865
  }
1020
- function nodeComplete(apiName, cbs, buildOk) {
1021
- return (err, ...args) => {
1022
- if (err) {
1023
- cbs.onFail?.({ errMsg: `${apiName}:fail ${err.message}` });
1024
- } else {
1025
- cbs.onSuccess?.({ ...buildOk ? buildOk(...args) : void 0, errMsg: `${apiName}:ok` });
1026
- }
1027
- cbs.onComplete?.();
1028
- };
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));
1029
873
  }
1030
- function mkdirpThenWrite(destReal, apiName, cbs, writeFn, okExtra) {
1031
- _fs.mkdir(_path.dirname(destReal), { recursive: true }, (mkdirErr) => {
1032
- if (mkdirErr) {
1033
- cbs.onFail?.({ errMsg: `${apiName}:fail ${mkdirErr.message}` });
1034
- cbs.onComplete?.();
1035
- return;
1036
- }
1037
- writeFn(nodeComplete(apiName, cbs, () => okExtra));
1038
- });
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));
883
+ } else {
884
+ _fs.rmdir(v.realPath, nodeComplete("fsRmdir", cbs));
885
+ }
1039
886
  }
1040
- function fsAccess({ path: path2, success, fail, complete }) {
887
+ function fsReaddir({ dirPath, success, fail, complete }) {
1041
888
  const cbs = bindCallbacks(this, { success, fail, complete });
1042
- const { onSuccess, onFail, onComplete } = cbs;
1043
- if (guardFsAvailable("fsAccess", cbs)) return;
1044
- const v = resolveOrBail(path2, "fsAccess", cbs);
889
+ const { onFail, onComplete } = cbs;
890
+ if (guardFsAvailable("fsReaddir", cbs)) return;
891
+ const v = resolveOrBail(dirPath, "fsReaddir", cbs);
1045
892
  if (!v) return;
1046
- if (v.kind === "tmp") {
1047
- _tmpBytes(path2).then(
1048
- () => {
1049
- onSuccess?.({ errMsg: "fsAccess:ok" });
1050
- onComplete?.();
1051
- },
1052
- tmpFailHandler("fsAccess", cbs)
1053
- );
893
+ if (v.kind === "tmp" || v.kind === "store") {
894
+ onFail?.({ errMsg: `fsReaddir:fail ${v.kind} is a flat namespace (no dir tree)` });
895
+ onComplete?.();
1054
896
  return;
1055
897
  }
1056
898
  if (!v.realPath) {
1057
- onFail?.({ errMsg: "fsAccess:fail invalid path" });
899
+ onFail?.({ errMsg: "fsReaddir:fail invalid path" });
1058
900
  onComplete?.();
1059
901
  return;
1060
902
  }
1061
- _fs.access(v.realPath, _fs.constants.F_OK, nodeComplete("fsAccess", cbs));
903
+ _fs.readdir(v.realPath, nodeComplete("fsReaddir", cbs, (files) => ({ files })));
1062
904
  }
1063
- function fsStat({ path: path2, recursive = false, success, fail, complete }) {
905
+ function fsGetFileInfo({ filePath, digestAlgorithm, success, fail, complete }) {
1064
906
  const cbs = bindCallbacks(this, { success, fail, complete });
1065
907
  const { onSuccess, onFail, onComplete } = cbs;
1066
- if (guardFsAvailable("fsStat", cbs)) return;
1067
- const v = resolveOrBail(path2, "fsStat", cbs);
908
+ if (guardFsAvailable("fsGetFileInfo", cbs)) return;
909
+ const v = resolveOrBail(filePath, "fsGetFileInfo", cbs);
1068
910
  if (!v) return;
911
+ const algorithm = digestAlgorithm === "sha1" ? "sha1" : "md5";
1069
912
  if (v.kind === "tmp") {
1070
- _tmpBytes(path2).then(
913
+ _tmpBytes(filePath).then(
1071
914
  (buf) => {
1072
- onSuccess?.({
1073
- stats: {
1074
- size: buf.length,
1075
- mode: 0,
1076
- lastAccessedTime: 0,
1077
- lastModifiedTime: 0,
1078
- isFile: true,
1079
- isDirectory: false
1080
- },
1081
- errMsg: "fsStat:ok"
1082
- });
1083
- onComplete?.();
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
+ }
1084
924
  },
1085
- tmpFailHandler("fsStat", cbs)
925
+ tmpFailHandler("fsGetFileInfo", cbs)
926
+ );
927
+ return;
928
+ }
929
+ if (!v.realPath) {
930
+ onFail?.({ errMsg: "fsGetFileInfo:fail invalid path" });
931
+ onComplete?.();
932
+ return;
933
+ }
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" });
947
+ onComplete?.();
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)
1086
995
  );
1087
996
  return;
1088
997
  }
1089
- if (!v.realPath) {
1090
- onFail?.({ errMsg: "fsStat:fail invalid path" });
998
+ if (!src.realPath) {
999
+ onFail?.({ errMsg: "fsSaveFile:fail invalid src path" });
1091
1000
  onComplete?.();
1092
1001
  return;
1093
1002
  }
1094
- const resolved = v.realPath;
1095
- if (recursive) {
1096
- const statsMap = {};
1097
- const walkDir = (dir, cb) => {
1098
- _fs.readdir(dir, { withFileTypes: true }, (err, entries) => {
1099
- if (err) {
1100
- cb(err);
1101
- return;
1102
- }
1103
- let pending = entries.length;
1104
- if (pending === 0) {
1105
- cb(null);
1106
- return;
1107
- }
1108
- for (const entry of entries) {
1109
- const full = _path.join(dir, entry.name);
1110
- _fs.stat(full, (statErr, s) => {
1111
- if (!statErr) {
1112
- statsMap[full] = {
1113
- size: s.size,
1114
- mode: s.mode,
1115
- lastAccessedTime: s.atimeMs,
1116
- lastModifiedTime: s.mtimeMs,
1117
- isFile: s.isFile(),
1118
- isDirectory: s.isDirectory()
1119
- };
1120
- }
1121
- if (entry.isDirectory()) {
1122
- walkDir(full, () => {
1123
- if (--pending === 0) cb(null);
1124
- });
1125
- } else {
1126
- if (--pending === 0) cb(null);
1127
- }
1128
- });
1129
- }
1130
- });
1131
- };
1132
- walkDir(resolved, (err) => {
1133
- if (err) {
1134
- onFail?.({ errMsg: `fsStat:fail ${err.message}` });
1135
- } else {
1136
- 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;
1137
1014
  }
1015
+ cbs.onSuccess?.({ savedFilePath, errMsg: "fsSaveFile:ok" });
1138
1016
  onComplete?.();
1139
1017
  });
1140
- } else {
1141
- _fs.stat(resolved, nodeComplete("fsStat", cbs, (s) => ({
1142
- stats: {
1143
- size: s.size,
1144
- mode: s.mode,
1145
- lastAccessedTime: s.atimeMs,
1146
- lastModifiedTime: s.mtimeMs,
1147
- isFile: s.isFile(),
1148
- isDirectory: s.isDirectory()
1149
- }
1150
- })));
1018
+ return;
1151
1019
  }
1020
+ mkdirpThenWrite(destReal, "fsSaveFile", cbs, (done) => _fs.copyFile(src.realPath, destReal, done), { savedFilePath });
1152
1021
  }
1153
- function fsReadFile({ filePath, encoding, success, fail, complete }) {
1022
+ function fsGetSavedFileList({ success, fail, complete } = {}) {
1154
1023
  const cbs = bindCallbacks(this, { success, fail, complete });
1155
1024
  const { onSuccess, onFail, onComplete } = cbs;
1156
- if (guardFsAvailable("fsReadFile", cbs)) return;
1157
- const v = resolveOrBail(filePath, "fsReadFile", cbs);
1158
- if (!v) return;
1159
- if (v.kind === "tmp") {
1160
- _tmpBytes(filePath).then(
1161
- (buf) => {
1162
- const data = encoding ? buf.toString(encoding) : buf;
1163
- onSuccess?.({ data, errMsg: "fsReadFile:ok" });
1164
- onComplete?.();
1165
- },
1166
- tmpFailHandler("fsReadFile", cbs)
1167
- );
1168
- return;
1169
- }
1170
- if (!v.realPath) {
1171
- 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" });
1172
1030
  onComplete?.();
1173
1031
  return;
1174
1032
  }
1175
- _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
+ });
1176
1075
  }
1177
- function fsWriteFile({ filePath, data, encoding = "utf8", success, fail, complete }) {
1076
+ function fsRemoveSavedFile({ filePath, success, fail, complete }) {
1178
1077
  const cbs = bindCallbacks(this, { success, fail, complete });
1179
- if (guardFsAvailable("fsWriteFile", cbs)) return;
1180
- const v = resolveOrBail(filePath, "fsWriteFile", cbs);
1078
+ const { onFail, onComplete } = cbs;
1079
+ if (guardFsAvailable("fsRemoveSavedFile", cbs)) return;
1080
+ const v = resolveOrBail(filePath, "fsRemoveSavedFile", cbs);
1181
1081
  if (!v) return;
1182
- if (!ensureWritable(v, "fsWriteFile", cbs)) return;
1183
- 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));
1184
1088
  }
1185
- function fsAppendFile({ filePath, data, encoding = "utf8", success, fail, complete }) {
1089
+ function fsTruncate({ filePath, length = 0, success, fail, complete }) {
1186
1090
  const cbs = bindCallbacks(this, { success, fail, complete });
1187
- if (guardFsAvailable("fsAppendFile", cbs)) return;
1188
- const v = resolveOrBail(filePath, "fsAppendFile", cbs);
1091
+ if (guardFsAvailable("fsTruncate", cbs)) return;
1092
+ const v = resolveOrBail(filePath, "fsTruncate", cbs);
1189
1093
  if (!v) return;
1190
- if (!ensureWritable(v, "fsAppendFile", cbs)) return;
1191
- _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));
1192
1096
  }
1193
- 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 }) {
1194
1136
  const cbs = bindCallbacks(this, { success, fail, complete });
1195
- const { onFail, onComplete } = cbs;
1196
- if (guardFsAvailable("fsCopyFile", cbs)) return;
1197
- const vSrc = resolveOrBail(srcPath, "fsCopyFile", cbs);
1198
- if (!vSrc) return;
1199
- const vDest = resolveOrBail(destPath, "fsCopyFile", cbs);
1200
- if (!vDest) return;
1201
- if (!ensureWritable(vDest, "fsCopyFile", cbs)) return;
1202
- if (vSrc.kind === "tmp") {
1203
- _tmpBytes(srcPath).then(
1204
- (buf) => mkdirpThenWrite(vDest.realPath, "fsCopyFile", cbs, (done) => _fs.writeFile(vDest.realPath, buf, done)),
1205
- 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
+ }
1206
1233
  );
1207
1234
  return;
1208
1235
  }
1209
- if (!vSrc.realPath) {
1210
- onFail?.({ errMsg: "fsCopyFile:fail invalid src path" });
1211
- onComplete?.();
1212
- 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}` });
1213
1250
  }
1214
- _fs.copyFile(vSrc.realPath, vDest.realPath, nodeComplete("fsCopyFile", cbs));
1251
+ onComplete?.();
1215
1252
  }
1216
- function fsRename({ oldPath, newPath, success, fail, complete }) {
1217
- const cbs = bindCallbacks(this, { success, fail, complete });
1218
- if (guardFsAvailable("fsRename", cbs)) return;
1219
- const vOld = resolveOrBail(oldPath, "fsRename", cbs);
1220
- if (!vOld) return;
1221
- const vNew = resolveOrBail(newPath, "fsRename", cbs);
1222
- if (!vNew) return;
1223
- if (!ensureWritable(vOld, "fsRename", cbs)) return;
1224
- if (!ensureWritable(vNew, "fsRename", cbs)) return;
1225
- _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;
1226
1273
  }
1227
- function fsUnlink({ filePath, success, fail, complete }) {
1228
- const cbs = bindCallbacks(this, { success, fail, complete });
1229
- if (guardFsAvailable("fsUnlink", cbs)) return;
1230
- const v = resolveOrBail(filePath, "fsUnlink", cbs);
1231
- if (!v) return;
1232
- if (!ensureWritable(v, "fsUnlink", cbs)) return;
1233
- _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;
1234
1278
  }
1235
- function fsMkdir({ dirPath, recursive = false, success, fail, complete }) {
1236
- const cbs = bindCallbacks(this, { success, fail, complete });
1237
- if (guardFsAvailable("fsMkdir", cbs)) return;
1238
- const v = resolveOrBail(dirPath, "fsMkdir", cbs);
1239
- if (!v) return;
1240
- if (!ensureWritable(v, "fsMkdir", cbs)) return;
1241
- _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)));
1242
1283
  }
1243
- function fsRmdir({ dirPath, recursive = false, success, fail, complete }) {
1244
- const cbs = bindCallbacks(this, { success, fail, complete });
1245
- if (guardFsAvailable("fsRmdir", cbs)) return;
1246
- const v = resolveOrBail(dirPath, "fsRmdir", cbs);
1247
- if (!v) return;
1248
- if (!ensureWritable(v, "fsRmdir", cbs)) return;
1249
- const rmFn = _fs.rm ?? _fs.rmdir;
1250
- 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/*";
1251
1290
  }
1252
- function fsReaddir({ dirPath, success, fail, complete }) {
1253
- const cbs = bindCallbacks(this, { success, fail, complete });
1254
- const { onFail, onComplete } = cbs;
1255
- if (guardFsAvailable("fsReaddir", cbs)) return;
1256
- const v = resolveOrBail(dirPath, "fsReaddir", cbs);
1257
- if (!v) return;
1258
- if (v.kind === "tmp" || v.kind === "store") {
1259
- onFail?.({ errMsg: `fsReaddir:fail ${v.kind} is a flat namespace (no dir tree)` });
1260
- onComplete?.();
1261
- return;
1262
- }
1263
- if (!v.realPath) {
1264
- onFail?.({ errMsg: "fsReaddir:fail invalid path" });
1265
- onComplete?.();
1266
- return;
1267
- }
1268
- _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";
1269
1295
  }
1270
- function fsGetFileInfo({ filePath, digestAlgorithm, success, fail, complete }) {
1271
- const cbs = bindCallbacks(this, { success, fail, complete });
1272
- const { onSuccess, onFail, onComplete } = cbs;
1273
- if (guardFsAvailable("fsGetFileInfo", cbs)) return;
1274
- const v = resolveOrBail(filePath, "fsGetFileInfo", cbs);
1275
- if (!v) return;
1276
- if (v.kind === "tmp") {
1277
- _tmpBytes(filePath).then(
1278
- (buf) => {
1279
- const result = { size: buf.length, errMsg: "fsGetFileInfo:ok" };
1280
- 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 = () => {
1281
1341
  try {
1282
- const hash = _crypto.createHash(digestAlgorithm === "md5" ? "md5" : "sha1");
1283
- hash.update(buf);
1284
- result.digest = hash.digest("hex");
1342
+ done(canvas.toDataURL("image/jpeg", 0.8));
1285
1343
  } catch {
1344
+ done("");
1286
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();
1287
1365
  }
1288
- onSuccess?.(result);
1289
- onComplete?.();
1290
- },
1291
- tmpFailHandler("fsGetFileInfo", cbs)
1292
- );
1293
- return;
1294
- }
1295
- if (!v.realPath) {
1296
- onFail?.({ errMsg: "fsGetFileInfo:fail invalid path" });
1297
- onComplete?.();
1298
- 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
+ };
1299
1414
  }
1300
- const resolved = v.realPath;
1301
- _fs.stat(resolved, (err, s) => {
1302
- if (err) {
1303
- onFail?.({ errMsg: `fsGetFileInfo:fail ${err.message}` });
1304
- onComplete?.();
1305
- return;
1306
- }
1307
- const result = { size: s.size, errMsg: "fsGetFileInfo:ok" };
1308
- 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);
1309
1443
  try {
1310
- const hash = _crypto.createHash(digestAlgorithm === "md5" ? "md5" : "sha1");
1311
- const stream = _fs.createReadStream(resolved);
1312
- stream.on("data", (chunk) => hash.update(chunk));
1313
- stream.on("end", () => {
1314
- result.digest = hash.digest("hex");
1315
- onSuccess?.(result);
1316
- onComplete?.();
1317
- });
1318
- stream.on("error", (hashErr) => {
1319
- onFail?.({ errMsg: `fsGetFileInfo:fail ${hashErr.message}` });
1320
- 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"
1321
1450
  });
1322
- return;
1323
- } catch {
1451
+ } catch (error) {
1452
+ onFail?.({ errMsg: `chooseMedia:fail ${error.message}` });
1453
+ } finally {
1454
+ onComplete?.();
1455
+ removeInput();
1324
1456
  }
1325
1457
  }
1326
- onSuccess?.(result);
1327
- onComplete?.();
1328
- });
1329
- }
1330
- function fsSaveFile({ tempFilePath, filePath: _filePath, success, fail, complete }) {
1331
- void _filePath;
1332
- const cbs = bindCallbacks(this, { success, fail, complete });
1333
- const { onFail, onComplete } = cbs;
1334
- if (guardFsAvailable("fsSaveFile", cbs)) return;
1335
- const src = resolveOrBail(tempFilePath, "fsSaveFile", cbs);
1336
- if (!src) return;
1337
- const ext = _path.extname(tempFilePath) || "";
1338
- const id = _crypto.randomUUID() + ext;
1339
- const savedFilePath = `difile://_store/${id}`;
1340
- const destResolved = resolveVPath(savedFilePath);
1341
- if (!destResolved || !destResolved.realPath) {
1342
- onFail?.({ errMsg: "fsSaveFile:fail unable to allocate destination" });
1343
- onComplete?.();
1344
- return;
1345
- }
1346
- const destReal = destResolved.realPath;
1347
- if (src.kind === "tmp") {
1348
- _tmpBytes(tempFilePath).then(
1349
- (bytes) => mkdirpThenWrite(destReal, "fsSaveFile", cbs, (done) => _fs.writeFile(destReal, bytes, done), { savedFilePath }),
1350
- tmpFailHandler("fsSaveFile", cbs)
1351
- );
1352
- return;
1353
- }
1354
- if (!src.realPath) {
1355
- onFail?.({ errMsg: "fsSaveFile:fail invalid src path" });
1356
- onComplete?.();
1357
- return;
1358
- }
1359
- mkdirpThenWrite(destReal, "fsSaveFile", cbs, (done) => _fs.copyFile(src.realPath, destReal, done), { savedFilePath });
1458
+ );
1360
1459
  }
1361
- function fsGetSavedFileList({ success, fail, complete } = {}) {
1460
+ function chooseVideo({ sourceType, camera, success, fail, complete }) {
1362
1461
  const cbs = bindCallbacks(this, { success, fail, complete });
1363
1462
  const { onSuccess, onComplete } = cbs;
1364
- if (guardFsAvailable("fsGetSavedFileList", cbs)) return;
1365
- const storeVpath = resolveVPath("difile://_store/");
1366
- const storeDir = storeVpath?.realPath;
1367
- if (!storeDir) {
1368
- onSuccess?.({ fileList: [], errMsg: "fsGetSavedFileList:ok" });
1369
- onComplete?.();
1370
- return;
1371
- }
1372
- _fs.readdir(storeDir, (err, files) => {
1373
- if (err) {
1374
- onSuccess?.({ fileList: [], errMsg: "fsGetSavedFileList:ok" });
1375
- onComplete?.();
1376
- return;
1377
- }
1378
- let pending = files.length;
1379
- if (pending === 0) {
1380
- 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
+ });
1381
1480
  onComplete?.();
1382
- return;
1481
+ removeInput();
1383
1482
  }
1384
- const fileList = [];
1385
- for (const name of files) {
1386
- const full = _path.join(storeDir, name);
1387
- _fs.stat(full, (statErr, s) => {
1388
- if (!statErr) {
1389
- fileList.push({
1390
- filePath: `difile://_store/${name}`,
1391
- size: s.size,
1392
- createTime: s.birthtimeMs
1393
- });
1394
- }
1395
- if (--pending === 0) {
1396
- onSuccess?.({ fileList, errMsg: "fsGetSavedFileList:ok" });
1397
- onComplete?.();
1398
- }
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(() => {
1399
1536
  });
1400
- }
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(() => {
1401
1560
  });
1402
1561
  }
1403
- function fsRemoveSavedFile({ filePath, success, fail, complete }) {
1404
- const cbs = bindCallbacks(this, { success, fail, complete });
1405
- const { onFail, onComplete } = cbs;
1406
- if (guardFsAvailable("fsRemoveSavedFile", cbs)) return;
1407
- const v = resolveOrBail(filePath, "fsRemoveSavedFile", cbs);
1408
- if (!v) return;
1409
- if (v.kind !== "store" || !v.realPath) {
1410
- onFail?.({ errMsg: "fsRemoveSavedFile:fail only _store/ entries may be removed" });
1411
- onComplete?.();
1412
- 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)}` };
1413
1619
  }
1414
- _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
+ };
1415
1685
  }
1416
- function fsTruncate({ filePath, length = 0, success, fail, complete }) {
1417
- const cbs = bindCallbacks(this, { success, fail, complete });
1418
- if (guardFsAvailable("fsTruncate", cbs)) return;
1419
- const v = resolveOrBail(filePath, "fsTruncate", cbs);
1420
- if (!v) return;
1421
- if (!ensureWritable(v, "fsTruncate", cbs)) return;
1422
- _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;
1423
1698
  }
1424
- var fsUnzip = notSupportedApi("fsUnzip");
1425
1699
 
1426
1700
  // src/simulator/simulator-api-network.ts
1427
- function downloadFile({ url, header = {}, filePath, success, fail, complete }) {
1701
+ function downloadFile({ url, header = {}, filePath, timeout, success, fail, complete }) {
1428
1702
  const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
1429
- fetch(url, { headers: header }).then(async (response) => {
1430
- 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
+ }
1431
1730
  const blob = await response.blob();
1432
1731
  const tempFilePath = createTempFilePath(blob);
1433
- onSuccess?.({
1732
+ settleOk({
1434
1733
  tempFilePath,
1435
1734
  filePath: filePath || tempFilePath,
1436
1735
  statusCode: response.status,
1437
1736
  errMsg: "downloadFile:ok"
1438
1737
  });
1439
1738
  }).catch((error) => {
1440
- onFail?.({ errMsg: `downloadFile:fail ${error.message}` });
1441
- }).finally(() => {
1442
- onComplete?.();
1739
+ settleFail(`downloadFile:fail ${error.message}`);
1443
1740
  });
1444
1741
  }
1445
1742
  var uploadRequests = /* @__PURE__ */ new Map();
@@ -1886,24 +2183,9 @@ var simulatorApis = {
1886
2183
  audioStop,
1887
2184
  audioSeek,
1888
2185
  audioDestroy,
1889
- // Filesystem (service-apis/file)
1890
- fsAccess,
1891
- fsStat,
1892
- fsReadFile,
1893
- fsWriteFile,
1894
- fsAppendFile,
1895
- fsCopyFile,
1896
- fsRename,
1897
- fsUnlink,
1898
- fsMkdir,
1899
- fsRmdir,
1900
- fsReaddir,
1901
- fsGetFileInfo,
1902
- fsSaveFile,
1903
- fsGetSavedFileList,
1904
- fsRemoveSavedFile,
1905
- fsTruncate,
1906
- fsUnzip
2186
+ // Filesystem: the service thread invokes the dotted FileSystemManager.*
2187
+ // wire names (see simulator-api-fsm.ts).
2188
+ ...fileSystemManagerApis
1907
2189
  };
1908
2190
 
1909
2191
  // src/preload/windows/main.ts