@dimina-kit/devtools 0.4.0-dev.20260728110120 → 0.4.0-dev.20260731100034
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/README.md +68 -0
- package/dist/main/api.d.ts +1 -0
- package/dist/main/app/app.d.ts +3 -0
- package/dist/main/app/app.js +15 -0
- package/dist/main/app/bootstrap.d.ts +8 -5
- package/dist/main/app/bootstrap.js +15 -15
- package/dist/main/index.bundle.js +2359 -5132
- package/dist/main/ipc/app-lifecycle.d.ts +1 -31
- package/dist/main/ipc/app-lifecycle.js +1 -53
- package/dist/main/ipc/bridge-router.d.ts +19 -152
- package/dist/main/ipc/bridge-router.js +28 -2186
- package/dist/main/ipc/page-scroll.d.ts +1 -13
- package/dist/main/ipc/page-scroll.js +1 -16
- package/dist/main/ipc/session-listener-bag.d.ts +1 -40
- package/dist/main/ipc/session-listener-bag.js +1 -39
- package/dist/main/services/console-forward/index.d.ts +1 -60
- package/dist/main/services/console-forward/index.js +1 -225
- package/dist/main/services/console-forward/internal-log.d.ts +1 -10
- package/dist/main/services/console-forward/internal-log.js +1 -22
- package/dist/main/services/diagnostics/index.d.ts +1 -58
- package/dist/main/services/diagnostics/index.js +1 -60
- package/dist/main/services/dimina-resource-server.d.ts +1 -6
- package/dist/main/services/dimina-resource-server.js +1 -98
- package/dist/main/services/network-forward/index.d.ts +15 -3
- package/dist/main/services/network-forward/index.js +30 -4
- package/dist/main/services/network-forward/websocket.d.ts +36 -0
- package/dist/main/services/network-forward/websocket.js +120 -0
- package/dist/main/services/service-host-pool/pool.d.ts +1 -196
- package/dist/main/services/service-host-pool/pool.js +1 -363
- package/dist/main/services/simulator/custom-apis.d.ts +1 -42
- package/dist/main/services/simulator/custom-apis.js +1 -46
- package/dist/main/services/simulator/referer.d.ts +1 -9
- package/dist/main/services/simulator/referer.js +1 -19
- package/dist/main/services/simulator/ui-extensions.d.ts +13 -0
- package/dist/main/services/simulator/ui-extensions.js +166 -0
- package/dist/main/services/simulator-storage/service-storage-ops.d.ts +1 -39
- package/dist/main/services/simulator-storage/service-storage-ops.js +1 -66
- package/dist/main/services/simulator-temp-files/disk.d.ts +1 -54
- package/dist/main/services/simulator-temp-files/disk.js +1 -237
- package/dist/main/services/simulator-temp-files/fs-channels.d.ts +1 -68
- package/dist/main/services/simulator-temp-files/fs-channels.js +1 -107
- package/dist/main/services/simulator-temp-files/index.d.ts +1 -32
- package/dist/main/services/simulator-temp-files/index.js +1 -181
- package/dist/main/services/simulator-temp-files/request-handler.d.ts +1 -29
- package/dist/main/services/simulator-temp-files/request-handler.js +1 -177
- package/dist/main/services/simulator-temp-files/resolver.d.ts +1 -21
- package/dist/main/services/simulator-temp-files/resolver.js +1 -21
- package/dist/main/services/simulator-temp-files/store.d.ts +1 -13
- package/dist/main/services/simulator-temp-files/store.js +1 -23
- package/dist/main/services/views/miniapp-partition.d.ts +1 -89
- package/dist/main/services/views/miniapp-partition.js +1 -158
- package/dist/main/services/views/native-simulator-view.js +3 -0
- package/dist/main/services/views/simulator-session-policy.d.ts +1 -27
- package/dist/main/services/views/simulator-session-policy.js +1 -98
- package/dist/main/services/views/view-manager.d.ts +5 -0
- package/dist/main/services/workbench-context.d.ts +10 -1
- package/dist/main/services/workbench-context.js +20 -0
- package/dist/main/utils/ipc-registry.d.ts +21 -3
- package/dist/main/utils/ipc-registry.js +26 -23
- package/dist/main/utils/paths.d.ts +14 -0
- package/dist/main/utils/paths.js +14 -0
- package/dist/main/windows/service-host-window/create.d.ts +1 -92
- package/dist/main/windows/service-host-window/create.js +1 -154
- package/dist/native-host/render/render.js +51 -52
- package/dist/native-host/service/service.js +2 -2
- package/dist/preload/index.d.ts +2 -0
- package/dist/preload/index.js +1 -0
- package/dist/preload/runtime/clipboard.d.ts +6 -0
- package/dist/preload/runtime/clipboard.js +12 -0
- package/dist/preload/runtime/native-host.js +4 -10
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
- package/dist/preload/windows/main.cjs +46 -126
- package/dist/preload/windows/main.cjs.map +3 -3
- package/dist/preload/windows/simulator.cjs +83 -56
- package/dist/preload/windows/simulator.cjs.map +4 -4
- package/dist/preload/windows/simulator.js +83 -56
- package/dist/renderer/assets/{index-BJlFbXHb.js → index-BBg3YI5b.js} +2 -2
- package/dist/renderer/entries/main/index.html +1 -1
- package/dist/shared/bridge-channels.d.ts +1 -419
- package/dist/shared/bridge-channels.js +1 -89
- package/dist/shared/dmb-resource-url.d.ts +2 -0
- package/dist/shared/dmb-resource-url.js +2 -0
- package/dist/shared/ipc-channels.d.ts +2 -32
- package/dist/shared/ipc-channels.js +2 -5
- package/dist/shared/request-core.d.ts +1 -83
- package/dist/shared/request-core.js +1 -167
- package/dist/shared/simulator-ui.d.ts +61 -0
- package/dist/shared/simulator-ui.js +12 -0
- package/dist/shared/types.d.ts +7 -0
- package/dist/simulator/assets/device-shell-B03OuytR.js +2 -0
- package/dist/simulator/assets/device-shell-DbC5ccs8.css +1 -0
- package/dist/simulator/assets/{jsx-runtime-BDTY6fEq.js → jsx-runtime-hBv-rHqQ.js} +2 -2
- package/dist/simulator/assets/{simulator-CRyLSz1N.js → simulator-DpdXUf--.js} +5 -5
- package/dist/simulator/simulator.html +2 -2
- package/dist/simulator/temp-files.d.ts +29 -0
- package/dist/simulator/temp-files.js +79 -0
- package/dist/simulator/types.d.ts +43 -0
- package/dist/simulator/types.js +2 -0
- package/package.json +10 -5
- package/dist/shared/simulator-api-metadata.d.ts +0 -26
- package/dist/shared/simulator-api-metadata.js +0 -59
- package/dist/shared/vpath.d.ts +0 -42
- package/dist/shared/vpath.js +0 -105
- package/dist/simulator/assets/device-shell-DOy53Y0s.css +0 -1
- package/dist/simulator/assets/device-shell-Sbkjw8Nb.js +0 -2
|
@@ -1,40 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* service-storage-ops — localStorage primitives executed INSIDE the service-host
|
|
3
|
-
* window (`wc`) via `executeJavaScript`, plus the wx-storage value encode/decode
|
|
4
|
-
* pair.
|
|
5
|
-
*
|
|
6
|
-
* Under native-host the mini-app's storage lives on the service-host window's
|
|
7
|
-
* `file://` origin (sync APIs write there via sync-impls/storage.ts, and the
|
|
8
|
-
* unified async path routes there too). The Storage panel + the async-storage
|
|
9
|
-
* runtime both read/write through here so there is exactly ONE store.
|
|
10
|
-
*
|
|
11
|
-
* The key/value scheme stays byte-compatible with
|
|
12
|
-
* `src/service-host/sync-impls/storage.ts` (the authoritative wx storage impl):
|
|
13
|
-
* - key = `${appId}_${key}`
|
|
14
|
-
* - value = `typeof data === 'object' ? JSON.stringify(data) : String(data)`
|
|
15
|
-
* - scoped clear removes every key whose name startsWith `${appId}_`
|
|
16
|
-
*
|
|
17
|
-
* Args are embedded via JSON.stringify so special characters can't break out of
|
|
18
|
-
* the injected code. Reads tolerate failure (→ []/null); writes propagate so the
|
|
19
|
-
* caller can fail the wx callback / surface the panel error.
|
|
20
|
-
*/
|
|
21
|
-
import type { WebContents } from 'electron';
|
|
22
|
-
/** Encode a wx storage value to its localStorage string form. Matches sync-impls/storage.ts. */
|
|
23
|
-
export declare function encodeStorageValue(data: unknown): string;
|
|
24
|
-
/** Decode a localStorage string back to a value (JSON.parse, falling back to the raw string). */
|
|
25
|
-
export declare function decodeStorageValue(raw: string): unknown;
|
|
26
|
-
export interface ServiceStorage {
|
|
27
|
-
/** All [fullKey, value] entries whose key startsWith `prefix`. */
|
|
28
|
-
readAll(prefix: string): Promise<Array<[string, string]>>;
|
|
29
|
-
/** Raw value for a full (already-prefixed) key, or null if absent. */
|
|
30
|
-
readOne(fullKey: string): Promise<string | null>;
|
|
31
|
-
writeOne(fullKey: string, value: string): Promise<void>;
|
|
32
|
-
removeOne(fullKey: string): Promise<void>;
|
|
33
|
-
/** Remove every key whose name startsWith `prefix`. */
|
|
34
|
-
clearPrefix(prefix: string): Promise<void>;
|
|
35
|
-
/** Origin-wide localStorage.clear(). */
|
|
36
|
-
clearAll(): Promise<void>;
|
|
37
|
-
}
|
|
38
|
-
/** localStorage ops executed inside `wc` (the service-host window) via executeJavaScript. */
|
|
39
|
-
export declare function serviceStorage(wc: WebContents): ServiceStorage;
|
|
1
|
+
export * from '@dimina-kit/electron-runtime/main/service-storage-ops';
|
|
40
2
|
//# sourceMappingURL=service-storage-ops.d.ts.map
|
|
@@ -1,67 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export function encodeStorageValue(data) {
|
|
3
|
-
return typeof data === 'object' ? JSON.stringify(data) : String(data);
|
|
4
|
-
}
|
|
5
|
-
/** Decode a localStorage string back to a value (JSON.parse, falling back to the raw string). */
|
|
6
|
-
export function decodeStorageValue(raw) {
|
|
7
|
-
try {
|
|
8
|
-
return JSON.parse(raw);
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
return raw;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
/** localStorage ops executed inside `wc` (the service-host window) via executeJavaScript. */
|
|
15
|
-
export function serviceStorage(wc) {
|
|
16
|
-
return {
|
|
17
|
-
async readAll(prefix) {
|
|
18
|
-
const code = `(() => {
|
|
19
|
-
const out = []
|
|
20
|
-
const p = ${JSON.stringify(prefix)}
|
|
21
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
22
|
-
const k = localStorage.key(i)
|
|
23
|
-
if (k && k.startsWith(p)) out.push([k, localStorage.getItem(k)])
|
|
24
|
-
}
|
|
25
|
-
return out
|
|
26
|
-
})()`;
|
|
27
|
-
try {
|
|
28
|
-
const result = await wc.executeJavaScript(code);
|
|
29
|
-
return Array.isArray(result) ? result : [];
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
async readOne(fullKey) {
|
|
36
|
-
try {
|
|
37
|
-
const result = await wc.executeJavaScript(`localStorage.getItem(${JSON.stringify(fullKey)})`);
|
|
38
|
-
return result === null || result === undefined ? null : String(result);
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
async writeOne(fullKey, value) {
|
|
45
|
-
await wc.executeJavaScript(`localStorage.setItem(${JSON.stringify(fullKey)}, ${JSON.stringify(value)})`);
|
|
46
|
-
},
|
|
47
|
-
async removeOne(fullKey) {
|
|
48
|
-
await wc.executeJavaScript(`localStorage.removeItem(${JSON.stringify(fullKey)})`);
|
|
49
|
-
},
|
|
50
|
-
async clearPrefix(prefix) {
|
|
51
|
-
const code = `(() => {
|
|
52
|
-
const p = ${JSON.stringify(prefix)}
|
|
53
|
-
const doomed = []
|
|
54
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
55
|
-
const k = localStorage.key(i)
|
|
56
|
-
if (k && k.startsWith(p)) doomed.push(k)
|
|
57
|
-
}
|
|
58
|
-
doomed.forEach((k) => localStorage.removeItem(k))
|
|
59
|
-
})()`;
|
|
60
|
-
await wc.executeJavaScript(code);
|
|
61
|
-
},
|
|
62
|
-
async clearAll() {
|
|
63
|
-
await wc.executeJavaScript('localStorage.clear()');
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
1
|
+
export * from '@dimina-kit/electron-runtime/main/service-storage-ops';
|
|
67
2
|
//# sourceMappingURL=service-storage-ops.js.map
|
|
@@ -1,55 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* Main-process disk reader/writer backing `difile://_store/*` and
|
|
3
|
-
* `difile://<usr-rel>` URLs.
|
|
4
|
-
*
|
|
5
|
-
* Spec: `packages/devtools/docs/file-system.md`.
|
|
6
|
-
*
|
|
7
|
-
* - `readDiskFile(realPath, opts?)`: read a (possibly ranged) slice of a
|
|
8
|
-
* file inside the USER_DATA_PATH sandbox. Returns `bytes` plus the
|
|
9
|
-
* content metadata callers need to assemble an HTTP response without a
|
|
10
|
-
* second `fs.stat` round-trip (`mime`, `etag`, `totalSize`).
|
|
11
|
-
* - `writeDiskFile(realPath, bytes)`: write bytes into the sandbox,
|
|
12
|
-
* creating parent directories on demand. Accepts both `Buffer` and
|
|
13
|
-
* `ArrayBuffer`.
|
|
14
|
-
* - `readDiskDir(realPath)`: list immediate children, no '.' / '..'.
|
|
15
|
-
* - `statDiskFile(realPath)`: minimal stat surface (size/mtime/mode/flags).
|
|
16
|
-
*
|
|
17
|
-
* The caller is responsible for canonicalizing `realPath` through
|
|
18
|
-
* `resolveVPath` *before* invoking these helpers — disk.ts assumes the
|
|
19
|
-
* argument is already known to be inside the sandbox base.
|
|
20
|
-
*
|
|
21
|
-
* MIME resolution prefers magic-byte sniffing (first ~12 bytes) over file
|
|
22
|
-
* extension, falling back to `application/octet-stream` for both unknown
|
|
23
|
-
* extensions and unknown magic.
|
|
24
|
-
*
|
|
25
|
-
* ETag shape is `W/"<mtime-ms>-<size>"` — weak validator, both axes folded
|
|
26
|
-
* into one token so byte-equal rewrites at the same timestamp still match.
|
|
27
|
-
*/
|
|
28
|
-
export interface DiskReadResult {
|
|
29
|
-
bytes: Buffer;
|
|
30
|
-
mime: string;
|
|
31
|
-
/** Weak ETag of the form `W/"<mtime>-<size>"`. */
|
|
32
|
-
etag: string;
|
|
33
|
-
/** Full file size in bytes, regardless of whether a Range slice was returned. */
|
|
34
|
-
totalSize: number;
|
|
35
|
-
}
|
|
36
|
-
export interface DiskReadOptions {
|
|
37
|
-
/** Inclusive byte range `[start, end]`. Omit to read the whole file. */
|
|
38
|
-
range?: {
|
|
39
|
-
start: number;
|
|
40
|
-
end: number;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
export interface DiskStat {
|
|
44
|
-
size: number;
|
|
45
|
-
/** Unix milliseconds. */
|
|
46
|
-
mtime: number;
|
|
47
|
-
mode: number;
|
|
48
|
-
isFile: boolean;
|
|
49
|
-
isDirectory: boolean;
|
|
50
|
-
}
|
|
51
|
-
export declare function readDiskFile(realPath: string, opts?: DiskReadOptions): Promise<DiskReadResult>;
|
|
52
|
-
export declare function writeDiskFile(realPath: string, bytes: Buffer | ArrayBuffer): Promise<void>;
|
|
53
|
-
export declare function readDiskDir(realPath: string): Promise<string[]>;
|
|
54
|
-
export declare function statDiskFile(realPath: string): Promise<DiskStat>;
|
|
1
|
+
export * from '@dimina-kit/electron-runtime/main/temp-files/disk';
|
|
55
2
|
//# sourceMappingURL=disk.d.ts.map
|
|
@@ -1,238 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* Main-process disk reader/writer backing `difile://_store/*` and
|
|
3
|
-
* `difile://<usr-rel>` URLs.
|
|
4
|
-
*
|
|
5
|
-
* Spec: `packages/devtools/docs/file-system.md`.
|
|
6
|
-
*
|
|
7
|
-
* - `readDiskFile(realPath, opts?)`: read a (possibly ranged) slice of a
|
|
8
|
-
* file inside the USER_DATA_PATH sandbox. Returns `bytes` plus the
|
|
9
|
-
* content metadata callers need to assemble an HTTP response without a
|
|
10
|
-
* second `fs.stat` round-trip (`mime`, `etag`, `totalSize`).
|
|
11
|
-
* - `writeDiskFile(realPath, bytes)`: write bytes into the sandbox,
|
|
12
|
-
* creating parent directories on demand. Accepts both `Buffer` and
|
|
13
|
-
* `ArrayBuffer`.
|
|
14
|
-
* - `readDiskDir(realPath)`: list immediate children, no '.' / '..'.
|
|
15
|
-
* - `statDiskFile(realPath)`: minimal stat surface (size/mtime/mode/flags).
|
|
16
|
-
*
|
|
17
|
-
* The caller is responsible for canonicalizing `realPath` through
|
|
18
|
-
* `resolveVPath` *before* invoking these helpers — disk.ts assumes the
|
|
19
|
-
* argument is already known to be inside the sandbox base.
|
|
20
|
-
*
|
|
21
|
-
* MIME resolution prefers magic-byte sniffing (first ~12 bytes) over file
|
|
22
|
-
* extension, falling back to `application/octet-stream` for both unknown
|
|
23
|
-
* extensions and unknown magic.
|
|
24
|
-
*
|
|
25
|
-
* ETag shape is `W/"<mtime-ms>-<size>"` — weak validator, both axes folded
|
|
26
|
-
* into one token so byte-equal rewrites at the same timestamp still match.
|
|
27
|
-
*/
|
|
28
|
-
import fs from 'node:fs/promises';
|
|
29
|
-
import path from 'node:path';
|
|
30
|
-
// -- MIME detection ---------------------------------------------------------
|
|
31
|
-
/**
|
|
32
|
-
* Extension-based MIME fallback. Kept deliberately small — anything more
|
|
33
|
-
* exotic should land in the magic-byte sniffer above. Lowercase keys only.
|
|
34
|
-
*/
|
|
35
|
-
const EXT_MIME = {
|
|
36
|
-
'.txt': 'text/plain',
|
|
37
|
-
'.log': 'text/plain',
|
|
38
|
-
'.md': 'text/markdown',
|
|
39
|
-
'.html': 'text/html',
|
|
40
|
-
'.htm': 'text/html',
|
|
41
|
-
'.css': 'text/css',
|
|
42
|
-
'.csv': 'text/csv',
|
|
43
|
-
'.json': 'application/json',
|
|
44
|
-
'.xml': 'application/xml',
|
|
45
|
-
'.js': 'application/javascript',
|
|
46
|
-
'.mjs': 'application/javascript',
|
|
47
|
-
'.svg': 'image/svg+xml',
|
|
48
|
-
'.png': 'image/png',
|
|
49
|
-
'.jpg': 'image/jpeg',
|
|
50
|
-
'.jpeg': 'image/jpeg',
|
|
51
|
-
'.gif': 'image/gif',
|
|
52
|
-
'.webp': 'image/webp',
|
|
53
|
-
'.bmp': 'image/bmp',
|
|
54
|
-
'.ico': 'image/x-icon',
|
|
55
|
-
'.mp4': 'video/mp4',
|
|
56
|
-
'.m4v': 'video/mp4',
|
|
57
|
-
'.webm': 'video/webm',
|
|
58
|
-
'.mov': 'video/quicktime',
|
|
59
|
-
'.mp3': 'audio/mpeg',
|
|
60
|
-
'.wav': 'audio/wav',
|
|
61
|
-
'.ogg': 'audio/ogg',
|
|
62
|
-
'.pdf': 'application/pdf',
|
|
63
|
-
'.zip': 'application/zip',
|
|
64
|
-
};
|
|
65
|
-
function isPng(head) {
|
|
66
|
-
return head.length >= 8
|
|
67
|
-
&& head[0] === 0x89 && head[1] === 0x50 && head[2] === 0x4e && head[3] === 0x47
|
|
68
|
-
&& head[4] === 0x0d && head[5] === 0x0a && head[6] === 0x1a && head[7] === 0x0a;
|
|
69
|
-
}
|
|
70
|
-
function isJpeg(head) {
|
|
71
|
-
return head.length >= 3 && head[0] === 0xff && head[1] === 0xd8 && head[2] === 0xff;
|
|
72
|
-
}
|
|
73
|
-
function isGif(head) {
|
|
74
|
-
return head.length >= 6
|
|
75
|
-
&& head[0] === 0x47 && head[1] === 0x49 && head[2] === 0x46
|
|
76
|
-
&& head[3] === 0x38 && (head[4] === 0x37 || head[4] === 0x39) && head[5] === 0x61;
|
|
77
|
-
}
|
|
78
|
-
function isWebp(head) {
|
|
79
|
-
return head.length >= 12
|
|
80
|
-
&& head[0] === 0x52 && head[1] === 0x49 && head[2] === 0x46 && head[3] === 0x46
|
|
81
|
-
&& head[8] === 0x57 && head[9] === 0x45 && head[10] === 0x42 && head[11] === 0x50;
|
|
82
|
-
}
|
|
83
|
-
/** MP4 ftyp box: brand bytes vary, treat all as video/mp4 for now. */
|
|
84
|
-
function isMp4(head) {
|
|
85
|
-
return head.length >= 12
|
|
86
|
-
&& head[4] === 0x66 && head[5] === 0x74 && head[6] === 0x79 && head[7] === 0x70;
|
|
87
|
-
}
|
|
88
|
-
function isPdf(head) {
|
|
89
|
-
return head.length >= 4
|
|
90
|
-
&& head[0] === 0x25 && head[1] === 0x50 && head[2] === 0x44 && head[3] === 0x46;
|
|
91
|
-
}
|
|
92
|
-
function isBmp(head) {
|
|
93
|
-
return head.length >= 2 && head[0] === 0x42 && head[1] === 0x4d;
|
|
94
|
-
}
|
|
95
|
-
function isZip(head) {
|
|
96
|
-
return head.length >= 4
|
|
97
|
-
&& head[0] === 0x50 && head[1] === 0x4b && head[2] === 0x03 && head[3] === 0x04;
|
|
98
|
-
}
|
|
99
|
-
/** ID3-tagged MP3. */
|
|
100
|
-
function isId3Mp3(head) {
|
|
101
|
-
return head.length >= 3 && head[0] === 0x49 && head[1] === 0x44 && head[2] === 0x33;
|
|
102
|
-
}
|
|
103
|
-
function isOgg(head) {
|
|
104
|
-
return head.length >= 4 && head[0] === 0x4f && head[1] === 0x67 && head[2] === 0x67 && head[3] === 0x53;
|
|
105
|
-
}
|
|
106
|
-
/** Magic-byte sniffers in priority order — first match wins. */
|
|
107
|
-
const MAGIC_SNIFFERS = [
|
|
108
|
-
{ mime: 'image/png', match: isPng },
|
|
109
|
-
{ mime: 'image/jpeg', match: isJpeg },
|
|
110
|
-
{ mime: 'image/gif', match: isGif },
|
|
111
|
-
{ mime: 'image/webp', match: isWebp },
|
|
112
|
-
{ mime: 'video/mp4', match: isMp4 },
|
|
113
|
-
{ mime: 'application/pdf', match: isPdf },
|
|
114
|
-
{ mime: 'image/bmp', match: isBmp },
|
|
115
|
-
{ mime: 'application/zip', match: isZip },
|
|
116
|
-
{ mime: 'audio/mpeg', match: isId3Mp3 },
|
|
117
|
-
{ mime: 'audio/ogg', match: isOgg },
|
|
118
|
-
];
|
|
119
|
-
function sniffMime(head) {
|
|
120
|
-
const hit = MAGIC_SNIFFERS.find((sniffer) => sniffer.match(head));
|
|
121
|
-
return hit ? hit.mime : null;
|
|
122
|
-
}
|
|
123
|
-
function extMime(realPath) {
|
|
124
|
-
const ext = path.extname(realPath).toLowerCase();
|
|
125
|
-
if (!ext)
|
|
126
|
-
return null;
|
|
127
|
-
return EXT_MIME[ext] ?? null;
|
|
128
|
-
}
|
|
129
|
-
function detectMime(realPath, head) {
|
|
130
|
-
const sniffed = sniffMime(head);
|
|
131
|
-
if (sniffed)
|
|
132
|
-
return sniffed;
|
|
133
|
-
const byExt = extMime(realPath);
|
|
134
|
-
if (byExt)
|
|
135
|
-
return byExt;
|
|
136
|
-
return 'application/octet-stream';
|
|
137
|
-
}
|
|
138
|
-
// -- ETag -------------------------------------------------------------------
|
|
139
|
-
function etagOf(mtimeMs, size) {
|
|
140
|
-
return `W/"${Math.floor(mtimeMs)}-${size}"`;
|
|
141
|
-
}
|
|
142
|
-
// -- read -------------------------------------------------------------------
|
|
143
|
-
/** Validate `range` against `totalSize` and resolve it to a byte slice, or throws. */
|
|
144
|
-
function resolveRange(range, totalSize) {
|
|
145
|
-
const { start, end } = range;
|
|
146
|
-
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
|
147
|
-
throw new RangeError(`invalid range: ${start}-${end}`);
|
|
148
|
-
}
|
|
149
|
-
if (start < 0)
|
|
150
|
-
throw new RangeError(`range start out of bounds: ${start}`);
|
|
151
|
-
if (start > end)
|
|
152
|
-
throw new RangeError(`range start > end: ${start} > ${end}`);
|
|
153
|
-
if (start >= totalSize) {
|
|
154
|
-
throw new RangeError(`range start beyond file size: ${start} >= ${totalSize}`);
|
|
155
|
-
}
|
|
156
|
-
const clampedEnd = Math.min(end, totalSize - 1);
|
|
157
|
-
return { sliceStart: start, sliceLen: clampedEnd - start + 1 };
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Read at most the first 12 bytes for magic sniffing, regardless of whether
|
|
161
|
-
* the caller asked for a range. The head bytes from position 0 are needed to
|
|
162
|
-
* label Content-Type correctly even on a tail Range request.
|
|
163
|
-
*/
|
|
164
|
-
async function readHeadBytes(handle, totalSize) {
|
|
165
|
-
const head = Buffer.alloc(Math.min(12, totalSize));
|
|
166
|
-
if (head.length > 0) {
|
|
167
|
-
await handle.read(head, 0, head.length, 0);
|
|
168
|
-
}
|
|
169
|
-
return head;
|
|
170
|
-
}
|
|
171
|
-
/** Full-file body, re-using `head` (already read) instead of a second read for the leading bytes. */
|
|
172
|
-
async function readFullBody(handle, head, totalSize) {
|
|
173
|
-
const bytes = Buffer.alloc(totalSize);
|
|
174
|
-
if (totalSize === 0)
|
|
175
|
-
return bytes;
|
|
176
|
-
if (totalSize <= head.length) {
|
|
177
|
-
head.copy(bytes, 0, 0, totalSize);
|
|
178
|
-
return bytes;
|
|
179
|
-
}
|
|
180
|
-
head.copy(bytes, 0, 0, head.length);
|
|
181
|
-
await handle.read(bytes, head.length, totalSize - head.length, head.length);
|
|
182
|
-
return bytes;
|
|
183
|
-
}
|
|
184
|
-
async function readRangeBody(handle, sliceStart, sliceLen) {
|
|
185
|
-
const bytes = Buffer.alloc(sliceLen);
|
|
186
|
-
if (sliceLen > 0) {
|
|
187
|
-
await handle.read(bytes, 0, sliceLen, sliceStart);
|
|
188
|
-
}
|
|
189
|
-
return bytes;
|
|
190
|
-
}
|
|
191
|
-
export async function readDiskFile(realPath, opts) {
|
|
192
|
-
const handle = await fs.open(realPath, 'r');
|
|
193
|
-
try {
|
|
194
|
-
const st = await handle.stat();
|
|
195
|
-
const totalSize = st.size;
|
|
196
|
-
const mtimeMs = st.mtimeMs;
|
|
197
|
-
const range = opts?.range;
|
|
198
|
-
const { sliceStart, sliceLen } = range
|
|
199
|
-
? resolveRange(range, totalSize)
|
|
200
|
-
: { sliceStart: 0, sliceLen: totalSize };
|
|
201
|
-
const head = await readHeadBytes(handle, totalSize);
|
|
202
|
-
const bytes = range
|
|
203
|
-
? await readRangeBody(handle, sliceStart, sliceLen)
|
|
204
|
-
: await readFullBody(handle, head, totalSize);
|
|
205
|
-
return {
|
|
206
|
-
bytes,
|
|
207
|
-
mime: detectMime(realPath, head),
|
|
208
|
-
etag: etagOf(mtimeMs, totalSize),
|
|
209
|
-
totalSize,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
finally {
|
|
213
|
-
await handle.close();
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
// -- write ------------------------------------------------------------------
|
|
217
|
-
export async function writeDiskFile(realPath, bytes) {
|
|
218
|
-
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
|
|
219
|
-
await fs.mkdir(path.dirname(realPath), { recursive: true });
|
|
220
|
-
await fs.writeFile(realPath, buf);
|
|
221
|
-
}
|
|
222
|
-
// -- dir --------------------------------------------------------------------
|
|
223
|
-
export async function readDiskDir(realPath) {
|
|
224
|
-
const entries = await fs.readdir(realPath);
|
|
225
|
-
return entries;
|
|
226
|
-
}
|
|
227
|
-
// -- stat -------------------------------------------------------------------
|
|
228
|
-
export async function statDiskFile(realPath) {
|
|
229
|
-
const st = await fs.stat(realPath);
|
|
230
|
-
return {
|
|
231
|
-
size: st.size,
|
|
232
|
-
mtime: Math.floor(st.mtimeMs),
|
|
233
|
-
mode: st.mode,
|
|
234
|
-
isFile: st.isFile(),
|
|
235
|
-
isDirectory: st.isDirectory(),
|
|
236
|
-
};
|
|
237
|
-
}
|
|
1
|
+
export * from '@dimina-kit/electron-runtime/main/temp-files/disk';
|
|
238
2
|
//# sourceMappingURL=disk.js.map
|
|
@@ -1,69 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* Main-process `simulator:fs:*` IPC channel handlers.
|
|
3
|
-
*
|
|
4
|
-
* Spec: `packages/devtools/docs/file-system.md`.
|
|
5
|
-
*
|
|
6
|
-
* Each handler is the pure function that backs one IPC channel:
|
|
7
|
-
*
|
|
8
|
-
* simulator:fs:read → handleFsRead
|
|
9
|
-
* simulator:fs:write → handleFsWrite
|
|
10
|
-
* simulator:fs:stat → handleFsStat
|
|
11
|
-
* simulator:fs:readdir → handleFsReaddir
|
|
12
|
-
* simulator:fs:unlink → handleFsUnlink
|
|
13
|
-
* simulator:fs:mkdir → handleFsMkdir
|
|
14
|
-
*
|
|
15
|
-
* Every handler MUST defensively re-assert that `realPath` lies inside the
|
|
16
|
-
* USER_DATA_PATH sandbox base (`DIMINA_HOME`-aware). The IPC boundary cannot
|
|
17
|
-
* trust the caller — a hostile preload or fuzzed payload could send a path
|
|
18
|
-
* that points outside the base. The renderer-side `resolveVPath` is one
|
|
19
|
-
* layer; this is the second.
|
|
20
|
-
*
|
|
21
|
-
* MIME / ETag / Range semantics mirror `disk.ts`; see `disk.test.ts` for
|
|
22
|
-
* the full contract on those axes.
|
|
23
|
-
*/
|
|
24
|
-
import { type DiskStat } from './disk.js';
|
|
25
|
-
export interface FsReadRequest {
|
|
26
|
-
realPath: string;
|
|
27
|
-
range?: {
|
|
28
|
-
start: number;
|
|
29
|
-
end: number;
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
export interface FsReadResult {
|
|
33
|
-
bytes: Buffer | ArrayBuffer;
|
|
34
|
-
mime: string;
|
|
35
|
-
/** Weak ETag of the form `W/"<mtime>-<size>"`. */
|
|
36
|
-
etag: string;
|
|
37
|
-
/** Full file size in bytes, regardless of whether a Range slice was returned. */
|
|
38
|
-
totalSize: number;
|
|
39
|
-
}
|
|
40
|
-
export interface FsWriteRequest {
|
|
41
|
-
realPath: string;
|
|
42
|
-
bytes: Buffer | ArrayBuffer;
|
|
43
|
-
}
|
|
44
|
-
export interface FsStatRequest {
|
|
45
|
-
realPath: string;
|
|
46
|
-
}
|
|
47
|
-
export interface FsReaddirRequest {
|
|
48
|
-
realPath: string;
|
|
49
|
-
}
|
|
50
|
-
export interface FsUnlinkRequest {
|
|
51
|
-
realPath: string;
|
|
52
|
-
}
|
|
53
|
-
export interface FsMkdirRequest {
|
|
54
|
-
realPath: string;
|
|
55
|
-
recursive?: boolean;
|
|
56
|
-
}
|
|
57
|
-
export declare function handleFsRead(req: FsReadRequest): Promise<FsReadResult>;
|
|
58
|
-
export declare function handleFsWrite(req: FsWriteRequest): Promise<{
|
|
59
|
-
ok: true;
|
|
60
|
-
}>;
|
|
61
|
-
export declare function handleFsStat(req: FsStatRequest): Promise<DiskStat>;
|
|
62
|
-
export declare function handleFsReaddir(req: FsReaddirRequest): Promise<string[]>;
|
|
63
|
-
export declare function handleFsUnlink(req: FsUnlinkRequest): Promise<{
|
|
64
|
-
ok: true;
|
|
65
|
-
}>;
|
|
66
|
-
export declare function handleFsMkdir(req: FsMkdirRequest): Promise<{
|
|
67
|
-
ok: true;
|
|
68
|
-
}>;
|
|
1
|
+
export * from '@dimina-kit/electron-runtime/main/temp-files/fs-channels';
|
|
69
2
|
//# sourceMappingURL=fs-channels.d.ts.map
|
|
@@ -1,108 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* Main-process `simulator:fs:*` IPC channel handlers.
|
|
3
|
-
*
|
|
4
|
-
* Spec: `packages/devtools/docs/file-system.md`.
|
|
5
|
-
*
|
|
6
|
-
* Each handler is the pure function that backs one IPC channel:
|
|
7
|
-
*
|
|
8
|
-
* simulator:fs:read → handleFsRead
|
|
9
|
-
* simulator:fs:write → handleFsWrite
|
|
10
|
-
* simulator:fs:stat → handleFsStat
|
|
11
|
-
* simulator:fs:readdir → handleFsReaddir
|
|
12
|
-
* simulator:fs:unlink → handleFsUnlink
|
|
13
|
-
* simulator:fs:mkdir → handleFsMkdir
|
|
14
|
-
*
|
|
15
|
-
* Every handler MUST defensively re-assert that `realPath` lies inside the
|
|
16
|
-
* USER_DATA_PATH sandbox base (`DIMINA_HOME`-aware). The IPC boundary cannot
|
|
17
|
-
* trust the caller — a hostile preload or fuzzed payload could send a path
|
|
18
|
-
* that points outside the base. The renderer-side `resolveVPath` is one
|
|
19
|
-
* layer; this is the second.
|
|
20
|
-
*
|
|
21
|
-
* MIME / ETag / Range semantics mirror `disk.ts`; see `disk.test.ts` for
|
|
22
|
-
* the full contract on those axes.
|
|
23
|
-
*/
|
|
24
|
-
import fs from 'node:fs/promises';
|
|
25
|
-
import path from 'node:path';
|
|
26
|
-
import { sandboxBase } from '../../../shared/vpath.js';
|
|
27
|
-
import { readDiskDir, readDiskFile, statDiskFile, writeDiskFile, } from './disk.js';
|
|
28
|
-
/**
|
|
29
|
-
* Defense-in-depth: a hostile preload could synthesize a `realPath` that
|
|
30
|
-
* resolveVPath would never produce. Re-canonicalize and re-anchor under the
|
|
31
|
-
* sandbox base every time, throwing if the result escapes.
|
|
32
|
-
*
|
|
33
|
-
* Two-layer check:
|
|
34
|
-
* 1. Lexical: `path.normalize` then `startsWith(base + sep)`.
|
|
35
|
-
* 2. Filesystem: `fs.realpath` to follow symlinks and re-assert containment.
|
|
36
|
-
*
|
|
37
|
-
* The symlink check is best-effort — if the path does not yet exist (e.g. a
|
|
38
|
-
* write/mkdir target), `fs.realpath` throws ENOENT and we fall back to
|
|
39
|
-
* checking the deepest existing ancestor instead. A symlinked ancestor that
|
|
40
|
-
* points outside the sandbox still gets caught that way.
|
|
41
|
-
*/
|
|
42
|
-
async function enforceSandbox(realPath) {
|
|
43
|
-
if (typeof realPath !== 'string' || realPath.length === 0) {
|
|
44
|
-
throw new Error('sandbox: realPath must be a non-empty string');
|
|
45
|
-
}
|
|
46
|
-
const base = sandboxBase();
|
|
47
|
-
const baseReal = await fs.realpath(base).catch(() => base);
|
|
48
|
-
const normalized = path.normalize(realPath);
|
|
49
|
-
if (normalized !== base && !normalized.startsWith(base + path.sep)) {
|
|
50
|
-
throw new Error('sandbox: realPath escapes the user-data base');
|
|
51
|
-
}
|
|
52
|
-
// Walk up until we find an existing path, realpath it, and assert the
|
|
53
|
-
// resolved ancestor stays under the sandbox base. This catches symlinks
|
|
54
|
-
// anywhere along the chain — both the leaf (read/stat) and the parent
|
|
55
|
-
// (write/mkdir creating a new file under a symlinked dir).
|
|
56
|
-
let probe = normalized;
|
|
57
|
-
while (probe !== path.parse(probe).root) {
|
|
58
|
-
try {
|
|
59
|
-
const resolved = await fs.realpath(probe);
|
|
60
|
-
if (resolved !== baseReal && !resolved.startsWith(baseReal + path.sep)) {
|
|
61
|
-
throw new Error('sandbox: realPath escapes the user-data base via symlink');
|
|
62
|
-
}
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
if (err.code === 'ENOENT') {
|
|
67
|
-
probe = path.dirname(probe);
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
throw err;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return normalized;
|
|
74
|
-
}
|
|
75
|
-
export async function handleFsRead(req) {
|
|
76
|
-
const safe = await enforceSandbox(req.realPath);
|
|
77
|
-
const result = await readDiskFile(safe, req.range ? { range: req.range } : undefined);
|
|
78
|
-
return {
|
|
79
|
-
bytes: result.bytes,
|
|
80
|
-
mime: result.mime,
|
|
81
|
-
etag: result.etag,
|
|
82
|
-
totalSize: result.totalSize,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
export async function handleFsWrite(req) {
|
|
86
|
-
const safe = await enforceSandbox(req.realPath);
|
|
87
|
-
await writeDiskFile(safe, req.bytes);
|
|
88
|
-
return { ok: true };
|
|
89
|
-
}
|
|
90
|
-
export async function handleFsStat(req) {
|
|
91
|
-
const safe = await enforceSandbox(req.realPath);
|
|
92
|
-
return statDiskFile(safe);
|
|
93
|
-
}
|
|
94
|
-
export async function handleFsReaddir(req) {
|
|
95
|
-
const safe = await enforceSandbox(req.realPath);
|
|
96
|
-
return readDiskDir(safe);
|
|
97
|
-
}
|
|
98
|
-
export async function handleFsUnlink(req) {
|
|
99
|
-
const safe = await enforceSandbox(req.realPath);
|
|
100
|
-
await fs.unlink(safe);
|
|
101
|
-
return { ok: true };
|
|
102
|
-
}
|
|
103
|
-
export async function handleFsMkdir(req) {
|
|
104
|
-
const safe = await enforceSandbox(req.realPath);
|
|
105
|
-
await fs.mkdir(safe, { recursive: !!req.recursive });
|
|
106
|
-
return { ok: true };
|
|
107
|
-
}
|
|
1
|
+
export * from '@dimina-kit/electron-runtime/main/temp-files/fs-channels';
|
|
108
2
|
//# sourceMappingURL=fs-channels.js.map
|
|
@@ -1,33 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* Wires the `difile://_tmp/*` protocol handler on the simulator session
|
|
3
|
-
* and bridges renderer-side `setTempFileSink` callbacks to the main-process
|
|
4
|
-
* byte store. Returned disposable clears the store, unregisters the protocol
|
|
5
|
-
* handler, and tears down its private IPC channels.
|
|
6
|
-
*
|
|
7
|
-
* SENDER POLICY: The default workbench sender-policy intentionally rejects
|
|
8
|
-
* the simulator `<webview>` (see `utils/sender-policy.ts`). The simulator
|
|
9
|
-
* is the only legitimate writer for these channels, so this module installs
|
|
10
|
-
* its own narrow policy that accepts a sender only when its WebContents
|
|
11
|
-
* belongs to the simulator `Session` we were handed. This bypasses the
|
|
12
|
-
* default policy without widening trust to any other source.
|
|
13
|
-
*
|
|
14
|
-
* WRITE-vs-RENDER RACE: `createTempFilePath` is synchronous on the renderer,
|
|
15
|
-
* but the bytes travel here through `blob.arrayBuffer().then(ipcRenderer.send)`
|
|
16
|
-
* — async. The simulator can race ahead: in `chooseMedia` the renderer
|
|
17
|
-
* assigns `img.src = tempFilePath` (via `readImageMetadata`) immediately
|
|
18
|
-
* after `createTempFilePath` returns, which fires a `difile://` GET before
|
|
19
|
-
* our IPC write arrives. The protocol handler therefore parks a request on
|
|
20
|
-
* a per-url waiter for up to PENDING_TIMEOUT_MS; the IPC `write` handler
|
|
21
|
-
* drains the waiter on arrival.
|
|
22
|
-
*
|
|
23
|
-
* QUOTA: A single in-memory store backs all simulator sessions for the
|
|
24
|
-
* lifetime of the WorkbenchApp instance. We cap it at MAX_STORE_ENTRIES
|
|
25
|
-
* (FIFO eviction). Project switches do NOT clear the store on their own —
|
|
26
|
-
* stale entries from a previous project simply age out as new ones land.
|
|
27
|
-
* This is the same eviction shape WeChat uses for tmp files (LRU under a
|
|
28
|
-
* size cap), simplified for a dev-tools session scope.
|
|
29
|
-
*/
|
|
30
|
-
import type { Session } from 'electron';
|
|
31
|
-
import { type Disposable } from '@dimina-kit/electron-deck/main';
|
|
32
|
-
export declare function setupSimulatorTempFiles(simSession: Session): Disposable;
|
|
1
|
+
export * from '@dimina-kit/electron-runtime/main/temp-files';
|
|
33
2
|
//# sourceMappingURL=index.d.ts.map
|