@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.
- package/dist/main/index.bundle.js +8 -1
- package/dist/main/services/mcp/target-manager.js +5 -1
- package/dist/native-host/common/common.js +154 -58
- package/dist/native-host/container/pageFrame.css +1 -1
- package/dist/native-host/render/render.js +6613 -3313
- package/dist/native-host/service/service.js +2 -2
- package/dist/preload/runtime/native-host.d.ts +2 -0
- package/dist/preload/runtime/native-host.js +2 -1
- package/dist/preload/runtime/node-bindings.d.ts +18 -0
- package/dist/preload/runtime/node-bindings.js +44 -0
- package/dist/preload/windows/main.cjs +1165 -883
- package/dist/preload/windows/main.cjs.map +4 -4
- package/dist/preload/windows/simulator.cjs +68 -6
- package/dist/preload/windows/simulator.cjs.map +4 -4
- package/dist/preload/windows/simulator.js +68 -6
- package/dist/render-host/pageFrame.html +3 -3
- package/dist/shared/node-bindings.d.ts +2 -0
- package/dist/shared/node-bindings.js +2 -0
- package/dist/simulator/assets/device-shell-Cw7c-IkG.js +2 -0
- package/dist/simulator/assets/{jsx-runtime-hBv-rHqQ.js → jsx-runtime-DkKmJyBb.js} +2 -2
- package/dist/simulator/assets/simulator-COIE6R49.js +10 -0
- package/dist/simulator/assets/simulator-mini-app-BLn7XV8e.js +2 -0
- package/dist/simulator/simulator.html +2 -2
- package/package.json +6 -6
- package/dist/simulator/assets/device-shell-B03OuytR.js +0 -2
- package/dist/simulator/assets/simulator-DpdXUf--.js +0 -10
- package/dist/simulator/assets/simulator-mini-app-w1Bhwkld.js +0 -2
|
@@ -108,8 +108,8 @@ function makeBaseContext() {
|
|
|
108
108
|
}
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
|
-
function runApiAsync(
|
|
112
|
-
const handler =
|
|
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-
|
|
455
|
-
function
|
|
456
|
-
|
|
457
|
-
|
|
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
|
|
460
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
|
500
|
-
|
|
501
|
-
if (
|
|
502
|
-
|
|
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
|
-
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
590
|
+
return false;
|
|
563
591
|
}
|
|
564
|
-
function
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
620
|
-
const finish = (metadata) => {
|
|
704
|
+
const failOnce = (err) => {
|
|
621
705
|
if (settled) return;
|
|
622
706
|
settled = true;
|
|
623
|
-
|
|
624
|
-
|
|
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
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
(
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
-
|
|
692
|
-
if (
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
|
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
|
-
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
|
|
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 (
|
|
946
|
-
|
|
801
|
+
if (!v.realPath) {
|
|
802
|
+
onFail?.({ errMsg: "fsReadFile:fail invalid path" });
|
|
803
|
+
onComplete?.();
|
|
804
|
+
return;
|
|
947
805
|
}
|
|
948
|
-
|
|
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
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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
|
|
967
|
-
|
|
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
|
-
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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 (
|
|
976
|
-
|
|
840
|
+
if (!vSrc.realPath) {
|
|
841
|
+
onFail?.({ errMsg: "fsCopyFile:fail invalid src path" });
|
|
842
|
+
onComplete?.();
|
|
843
|
+
return;
|
|
977
844
|
}
|
|
978
|
-
|
|
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
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
cbs
|
|
1012
|
-
return
|
|
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
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
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
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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
|
|
887
|
+
function fsReaddir({ dirPath, success, fail, complete }) {
|
|
1041
888
|
const cbs = bindCallbacks(this, { success, fail, complete });
|
|
1042
|
-
const {
|
|
1043
|
-
if (guardFsAvailable("
|
|
1044
|
-
const v = resolveOrBail(
|
|
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
|
-
|
|
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: "
|
|
899
|
+
onFail?.({ errMsg: "fsReaddir:fail invalid path" });
|
|
1058
900
|
onComplete?.();
|
|
1059
901
|
return;
|
|
1060
902
|
}
|
|
1061
|
-
_fs.
|
|
903
|
+
_fs.readdir(v.realPath, nodeComplete("fsReaddir", cbs, (files) => ({ files })));
|
|
1062
904
|
}
|
|
1063
|
-
function
|
|
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("
|
|
1067
|
-
const v = resolveOrBail(
|
|
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(
|
|
913
|
+
_tmpBytes(filePath).then(
|
|
1071
914
|
(buf) => {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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("
|
|
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 (!
|
|
1090
|
-
onFail?.({ errMsg: "
|
|
998
|
+
if (!src.realPath) {
|
|
999
|
+
onFail?.({ errMsg: "fsSaveFile:fail invalid src path" });
|
|
1091
1000
|
onComplete?.();
|
|
1092
1001
|
return;
|
|
1093
1002
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
|
|
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
|
-
|
|
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
|
|
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("
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1159
|
-
if (
|
|
1160
|
-
|
|
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.
|
|
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
|
|
1076
|
+
function fsRemoveSavedFile({ filePath, success, fail, complete }) {
|
|
1178
1077
|
const cbs = bindCallbacks(this, { success, fail, complete });
|
|
1179
|
-
|
|
1180
|
-
|
|
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 (
|
|
1183
|
-
|
|
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
|
|
1089
|
+
function fsTruncate({ filePath, length = 0, success, fail, complete }) {
|
|
1186
1090
|
const cbs = bindCallbacks(this, { success, fail, complete });
|
|
1187
|
-
if (guardFsAvailable("
|
|
1188
|
-
const v = resolveOrBail(filePath, "
|
|
1091
|
+
if (guardFsAvailable("fsTruncate", cbs)) return;
|
|
1092
|
+
const v = resolveOrBail(filePath, "fsTruncate", cbs);
|
|
1189
1093
|
if (!v) return;
|
|
1190
|
-
if (!ensureWritable(v, "
|
|
1191
|
-
_fs.
|
|
1094
|
+
if (!ensureWritable(v, "fsTruncate", cbs)) return;
|
|
1095
|
+
_fs.truncate(v.realPath, length, nodeComplete("fsTruncate", cbs));
|
|
1192
1096
|
}
|
|
1193
|
-
function
|
|
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 {
|
|
1196
|
-
|
|
1197
|
-
const
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
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
|
-
|
|
1251
|
+
onComplete?.();
|
|
1215
1252
|
}
|
|
1216
|
-
function
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
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
|
|
1236
|
-
const
|
|
1237
|
-
if (
|
|
1238
|
-
|
|
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
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1246
|
-
const
|
|
1247
|
-
if (
|
|
1248
|
-
|
|
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
|
|
1253
|
-
const
|
|
1254
|
-
|
|
1255
|
-
|
|
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
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
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
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
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
|
-
|
|
1323
|
-
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
onFail?.({ errMsg: `chooseMedia:fail ${error.message}` });
|
|
1453
|
+
} finally {
|
|
1454
|
+
onComplete?.();
|
|
1455
|
+
removeInput();
|
|
1324
1456
|
}
|
|
1325
1457
|
}
|
|
1326
|
-
|
|
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
|
|
1460
|
+
function chooseVideo({ sourceType, camera, success, fail, complete }) {
|
|
1362
1461
|
const cbs = bindCallbacks(this, { success, fail, complete });
|
|
1363
1462
|
const { onSuccess, onComplete } = cbs;
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
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
|
-
|
|
1481
|
+
removeInput();
|
|
1383
1482
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
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
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
const
|
|
1408
|
-
if (!
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
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
|
-
|
|
1430
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1890
|
-
|
|
1891
|
-
|
|
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
|