@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.
Files changed (105) hide show
  1. package/README.md +68 -0
  2. package/dist/main/api.d.ts +1 -0
  3. package/dist/main/app/app.d.ts +3 -0
  4. package/dist/main/app/app.js +15 -0
  5. package/dist/main/app/bootstrap.d.ts +8 -5
  6. package/dist/main/app/bootstrap.js +15 -15
  7. package/dist/main/index.bundle.js +2359 -5132
  8. package/dist/main/ipc/app-lifecycle.d.ts +1 -31
  9. package/dist/main/ipc/app-lifecycle.js +1 -53
  10. package/dist/main/ipc/bridge-router.d.ts +19 -152
  11. package/dist/main/ipc/bridge-router.js +28 -2186
  12. package/dist/main/ipc/page-scroll.d.ts +1 -13
  13. package/dist/main/ipc/page-scroll.js +1 -16
  14. package/dist/main/ipc/session-listener-bag.d.ts +1 -40
  15. package/dist/main/ipc/session-listener-bag.js +1 -39
  16. package/dist/main/services/console-forward/index.d.ts +1 -60
  17. package/dist/main/services/console-forward/index.js +1 -225
  18. package/dist/main/services/console-forward/internal-log.d.ts +1 -10
  19. package/dist/main/services/console-forward/internal-log.js +1 -22
  20. package/dist/main/services/diagnostics/index.d.ts +1 -58
  21. package/dist/main/services/diagnostics/index.js +1 -60
  22. package/dist/main/services/dimina-resource-server.d.ts +1 -6
  23. package/dist/main/services/dimina-resource-server.js +1 -98
  24. package/dist/main/services/network-forward/index.d.ts +15 -3
  25. package/dist/main/services/network-forward/index.js +30 -4
  26. package/dist/main/services/network-forward/websocket.d.ts +36 -0
  27. package/dist/main/services/network-forward/websocket.js +120 -0
  28. package/dist/main/services/service-host-pool/pool.d.ts +1 -196
  29. package/dist/main/services/service-host-pool/pool.js +1 -363
  30. package/dist/main/services/simulator/custom-apis.d.ts +1 -42
  31. package/dist/main/services/simulator/custom-apis.js +1 -46
  32. package/dist/main/services/simulator/referer.d.ts +1 -9
  33. package/dist/main/services/simulator/referer.js +1 -19
  34. package/dist/main/services/simulator/ui-extensions.d.ts +13 -0
  35. package/dist/main/services/simulator/ui-extensions.js +166 -0
  36. package/dist/main/services/simulator-storage/service-storage-ops.d.ts +1 -39
  37. package/dist/main/services/simulator-storage/service-storage-ops.js +1 -66
  38. package/dist/main/services/simulator-temp-files/disk.d.ts +1 -54
  39. package/dist/main/services/simulator-temp-files/disk.js +1 -237
  40. package/dist/main/services/simulator-temp-files/fs-channels.d.ts +1 -68
  41. package/dist/main/services/simulator-temp-files/fs-channels.js +1 -107
  42. package/dist/main/services/simulator-temp-files/index.d.ts +1 -32
  43. package/dist/main/services/simulator-temp-files/index.js +1 -181
  44. package/dist/main/services/simulator-temp-files/request-handler.d.ts +1 -29
  45. package/dist/main/services/simulator-temp-files/request-handler.js +1 -177
  46. package/dist/main/services/simulator-temp-files/resolver.d.ts +1 -21
  47. package/dist/main/services/simulator-temp-files/resolver.js +1 -21
  48. package/dist/main/services/simulator-temp-files/store.d.ts +1 -13
  49. package/dist/main/services/simulator-temp-files/store.js +1 -23
  50. package/dist/main/services/views/miniapp-partition.d.ts +1 -89
  51. package/dist/main/services/views/miniapp-partition.js +1 -158
  52. package/dist/main/services/views/native-simulator-view.js +3 -0
  53. package/dist/main/services/views/simulator-session-policy.d.ts +1 -27
  54. package/dist/main/services/views/simulator-session-policy.js +1 -98
  55. package/dist/main/services/views/view-manager.d.ts +5 -0
  56. package/dist/main/services/workbench-context.d.ts +10 -1
  57. package/dist/main/services/workbench-context.js +20 -0
  58. package/dist/main/utils/ipc-registry.d.ts +21 -3
  59. package/dist/main/utils/ipc-registry.js +26 -23
  60. package/dist/main/utils/paths.d.ts +14 -0
  61. package/dist/main/utils/paths.js +14 -0
  62. package/dist/main/windows/service-host-window/create.d.ts +1 -92
  63. package/dist/main/windows/service-host-window/create.js +1 -154
  64. package/dist/native-host/render/render.js +51 -52
  65. package/dist/native-host/service/service.js +2 -2
  66. package/dist/preload/index.d.ts +2 -0
  67. package/dist/preload/index.js +1 -0
  68. package/dist/preload/runtime/clipboard.d.ts +6 -0
  69. package/dist/preload/runtime/clipboard.js +12 -0
  70. package/dist/preload/runtime/native-host.js +4 -10
  71. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  72. package/dist/preload/windows/main.cjs +46 -126
  73. package/dist/preload/windows/main.cjs.map +3 -3
  74. package/dist/preload/windows/simulator.cjs +83 -56
  75. package/dist/preload/windows/simulator.cjs.map +4 -4
  76. package/dist/preload/windows/simulator.js +83 -56
  77. package/dist/renderer/assets/{index-BJlFbXHb.js → index-BBg3YI5b.js} +2 -2
  78. package/dist/renderer/entries/main/index.html +1 -1
  79. package/dist/shared/bridge-channels.d.ts +1 -419
  80. package/dist/shared/bridge-channels.js +1 -89
  81. package/dist/shared/dmb-resource-url.d.ts +2 -0
  82. package/dist/shared/dmb-resource-url.js +2 -0
  83. package/dist/shared/ipc-channels.d.ts +2 -32
  84. package/dist/shared/ipc-channels.js +2 -5
  85. package/dist/shared/request-core.d.ts +1 -83
  86. package/dist/shared/request-core.js +1 -167
  87. package/dist/shared/simulator-ui.d.ts +61 -0
  88. package/dist/shared/simulator-ui.js +12 -0
  89. package/dist/shared/types.d.ts +7 -0
  90. package/dist/simulator/assets/device-shell-B03OuytR.js +2 -0
  91. package/dist/simulator/assets/device-shell-DbC5ccs8.css +1 -0
  92. package/dist/simulator/assets/{jsx-runtime-BDTY6fEq.js → jsx-runtime-hBv-rHqQ.js} +2 -2
  93. package/dist/simulator/assets/{simulator-CRyLSz1N.js → simulator-DpdXUf--.js} +5 -5
  94. package/dist/simulator/simulator.html +2 -2
  95. package/dist/simulator/temp-files.d.ts +29 -0
  96. package/dist/simulator/temp-files.js +79 -0
  97. package/dist/simulator/types.d.ts +43 -0
  98. package/dist/simulator/types.js +2 -0
  99. package/package.json +10 -5
  100. package/dist/shared/simulator-api-metadata.d.ts +0 -26
  101. package/dist/shared/simulator-api-metadata.js +0 -59
  102. package/dist/shared/vpath.d.ts +0 -42
  103. package/dist/shared/vpath.js +0 -105
  104. package/dist/simulator/assets/device-shell-DOy53Y0s.css +0 -1
  105. 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
- /** Encode a wx storage value to its localStorage string form. Matches sync-impls/storage.ts. */
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