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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/main/app/app.js +7 -0
  2. package/dist/main/app/bootstrap.d.ts +8 -5
  3. package/dist/main/app/bootstrap.js +15 -15
  4. package/dist/main/index.bundle.js +2207 -5152
  5. package/dist/main/ipc/app-lifecycle.d.ts +1 -31
  6. package/dist/main/ipc/app-lifecycle.js +1 -53
  7. package/dist/main/ipc/bridge-router.d.ts +19 -152
  8. package/dist/main/ipc/bridge-router.js +28 -2186
  9. package/dist/main/ipc/page-scroll.d.ts +1 -13
  10. package/dist/main/ipc/page-scroll.js +1 -16
  11. package/dist/main/ipc/session-listener-bag.d.ts +1 -40
  12. package/dist/main/ipc/session-listener-bag.js +1 -39
  13. package/dist/main/services/console-forward/index.d.ts +1 -60
  14. package/dist/main/services/console-forward/index.js +1 -225
  15. package/dist/main/services/console-forward/internal-log.d.ts +1 -10
  16. package/dist/main/services/console-forward/internal-log.js +1 -22
  17. package/dist/main/services/diagnostics/index.d.ts +1 -58
  18. package/dist/main/services/diagnostics/index.js +1 -60
  19. package/dist/main/services/dimina-resource-server.d.ts +1 -6
  20. package/dist/main/services/dimina-resource-server.js +1 -98
  21. package/dist/main/services/mcp/target-manager.js +5 -1
  22. package/dist/main/services/network-forward/index.d.ts +15 -3
  23. package/dist/main/services/network-forward/index.js +30 -4
  24. package/dist/main/services/network-forward/websocket.d.ts +36 -0
  25. package/dist/main/services/network-forward/websocket.js +120 -0
  26. package/dist/main/services/service-host-pool/pool.d.ts +1 -196
  27. package/dist/main/services/service-host-pool/pool.js +1 -363
  28. package/dist/main/services/simulator/custom-apis.d.ts +1 -42
  29. package/dist/main/services/simulator/custom-apis.js +1 -46
  30. package/dist/main/services/simulator/referer.d.ts +1 -9
  31. package/dist/main/services/simulator/referer.js +1 -19
  32. package/dist/main/services/simulator-storage/service-storage-ops.d.ts +1 -39
  33. package/dist/main/services/simulator-storage/service-storage-ops.js +1 -66
  34. package/dist/main/services/simulator-temp-files/disk.d.ts +1 -54
  35. package/dist/main/services/simulator-temp-files/disk.js +1 -237
  36. package/dist/main/services/simulator-temp-files/fs-channels.d.ts +1 -68
  37. package/dist/main/services/simulator-temp-files/fs-channels.js +1 -107
  38. package/dist/main/services/simulator-temp-files/index.d.ts +1 -32
  39. package/dist/main/services/simulator-temp-files/index.js +1 -181
  40. package/dist/main/services/simulator-temp-files/request-handler.d.ts +1 -29
  41. package/dist/main/services/simulator-temp-files/request-handler.js +1 -177
  42. package/dist/main/services/simulator-temp-files/resolver.d.ts +1 -21
  43. package/dist/main/services/simulator-temp-files/resolver.js +1 -21
  44. package/dist/main/services/simulator-temp-files/store.d.ts +1 -13
  45. package/dist/main/services/simulator-temp-files/store.js +1 -23
  46. package/dist/main/services/views/miniapp-partition.d.ts +1 -89
  47. package/dist/main/services/views/miniapp-partition.js +1 -158
  48. package/dist/main/services/views/simulator-session-policy.d.ts +1 -27
  49. package/dist/main/services/views/simulator-session-policy.js +1 -98
  50. package/dist/main/services/workbench-context.d.ts +4 -1
  51. package/dist/main/services/workbench-context.js +17 -0
  52. package/dist/main/utils/ipc-registry.d.ts +21 -3
  53. package/dist/main/utils/ipc-registry.js +26 -23
  54. package/dist/main/utils/paths.d.ts +14 -0
  55. package/dist/main/utils/paths.js +14 -0
  56. package/dist/main/windows/service-host-window/create.d.ts +1 -92
  57. package/dist/main/windows/service-host-window/create.js +1 -154
  58. package/dist/native-host/common/common.js +154 -58
  59. package/dist/native-host/container/pageFrame.css +1 -1
  60. package/dist/native-host/render/render.js +6611 -3312
  61. package/dist/native-host/service/service.js +2 -2
  62. package/dist/preload/index.d.ts +2 -0
  63. package/dist/preload/index.js +1 -0
  64. package/dist/preload/runtime/clipboard.d.ts +6 -0
  65. package/dist/preload/runtime/clipboard.js +12 -0
  66. package/dist/preload/runtime/native-host.d.ts +2 -0
  67. package/dist/preload/runtime/native-host.js +5 -10
  68. package/dist/preload/runtime/node-bindings.d.ts +18 -0
  69. package/dist/preload/runtime/node-bindings.js +44 -0
  70. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  71. package/dist/preload/windows/main.cjs +1195 -897
  72. package/dist/preload/windows/main.cjs.map +4 -4
  73. package/dist/preload/windows/simulator.cjs +149 -60
  74. package/dist/preload/windows/simulator.cjs.map +4 -4
  75. package/dist/preload/windows/simulator.js +149 -60
  76. package/dist/render-host/pageFrame.html +3 -3
  77. package/dist/shared/bridge-channels.d.ts +1 -419
  78. package/dist/shared/bridge-channels.js +1 -89
  79. package/dist/shared/dmb-resource-url.d.ts +2 -0
  80. package/dist/shared/dmb-resource-url.js +2 -0
  81. package/dist/shared/ipc-channels.d.ts +2 -32
  82. package/dist/shared/ipc-channels.js +2 -5
  83. package/dist/shared/node-bindings.d.ts +2 -0
  84. package/dist/shared/node-bindings.js +2 -0
  85. package/dist/shared/request-core.d.ts +1 -83
  86. package/dist/shared/request-core.js +1 -167
  87. package/dist/simulator/assets/device-shell-Cw7c-IkG.js +2 -0
  88. package/dist/simulator/assets/{device-shell-BHz1v3Yo.css → device-shell-DbC5ccs8.css} +1 -1
  89. package/dist/simulator/assets/{jsx-runtime-hBv-rHqQ.js → jsx-runtime-DkKmJyBb.js} +2 -2
  90. package/dist/simulator/assets/simulator-COIE6R49.js +10 -0
  91. package/dist/simulator/assets/simulator-mini-app-BLn7XV8e.js +2 -0
  92. package/dist/simulator/simulator.html +2 -2
  93. package/dist/simulator/temp-files.d.ts +29 -0
  94. package/dist/simulator/temp-files.js +79 -0
  95. package/dist/simulator/types.d.ts +43 -0
  96. package/dist/simulator/types.js +2 -0
  97. package/package.json +6 -5
  98. package/dist/shared/simulator-api-metadata.d.ts +0 -26
  99. package/dist/shared/simulator-api-metadata.js +0 -59
  100. package/dist/shared/vpath.d.ts +0 -42
  101. package/dist/shared/vpath.js +0 -105
  102. package/dist/simulator/assets/device-shell-CH_DMYaz.js +0 -2
  103. package/dist/simulator/assets/simulator-DXraYerh.js +0 -10
  104. package/dist/simulator/assets/simulator-mini-app-w1Bhwkld.js +0 -2
@@ -1,178 +1,2 @@
1
- /**
2
- * Pure dispatcher for `difile://` URL requests.
3
- *
4
- * Spec: `packages/devtools/docs/file-system.md`.
5
- *
6
- * The shipping implementation in `index.ts` registers a thin
7
- * `simSession.protocol.handle('difile')` wrapper that delegates here; the race
8
- * waiter on `_tmp/*` lives in `index.ts` because it owns the IPC lifecycle.
9
- * For unit tests we assume the bytes are already in the store.
10
- *
11
- * Response shape:
12
- * - 200: full body, with Content-Type, Cache-Control (immutable), ETag
13
- * - 206: range slice, plus Content-Range
14
- * - 304: empty body when `If-None-Match` matches the on-disk ETag. Per RFC
15
- * 9110 §13.1.2 If-None-Match wins over Range.
16
- * - 404: anything `resolveVPath` rejects, plus disk-side ENOENT and any
17
- * other I/O error. The protocol handler in `index.ts` translates this
18
- * into the renderer's network failure surface; we deliberately do not
19
- * leak errno strings.
20
- */
21
- import { resolveVPath } from '../../../shared/vpath.js';
22
- import { readDiskFile } from './disk.js';
23
- const CACHE_CONTROL = 'public, max-age=31536000, immutable';
24
- function getHeader(headers, name) {
25
- if (!headers)
26
- return undefined;
27
- const lc = name.toLowerCase();
28
- for (const k of Object.keys(headers)) {
29
- if (k.toLowerCase() === lc)
30
- return headers[k];
31
- }
32
- return undefined;
33
- }
34
- /**
35
- * Weak ETag comparison per RFC 9110 §8.8.3.2: strip the optional `W/` prefix
36
- * from each side before comparing. Both our generated ETags and conditional
37
- * headers may or may not include the weak prefix; the comparison must accept
38
- * `W/"abc"` and `"abc"` as equivalent.
39
- */
40
- function etagsMatch(a, b) {
41
- if (!a)
42
- return false;
43
- const stripped = (s) => (s.startsWith('W/') ? s.slice(2) : s);
44
- return stripped(a.trim()) === stripped(b);
45
- }
46
- /**
47
- * Parse a `Range: bytes=<start>-<end>` header into an inclusive `{start,end}`.
48
- * Returns `null` if absent or malformed (callers should treat that as "no
49
- * range" — a full 200 response, not a 416). `totalSize` clamps the end.
50
- */
51
- function parseRange(header, totalSize) {
52
- if (!header)
53
- return null;
54
- const m = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
55
- if (!m)
56
- return null;
57
- const startStr = m[1];
58
- const endStr = m[2];
59
- if (startStr === '' && endStr === '')
60
- return null;
61
- if (startStr === '') {
62
- // Suffix range: last N bytes.
63
- const n = Number(endStr);
64
- if (!Number.isFinite(n) || n <= 0)
65
- return null;
66
- const start = Math.max(0, totalSize - n);
67
- return { start, end: totalSize - 1 };
68
- }
69
- const start = Number(startStr);
70
- if (!Number.isFinite(start) || start < 0)
71
- return null;
72
- const end = endStr === '' ? totalSize - 1 : Number(endStr);
73
- if (!Number.isFinite(end))
74
- return null;
75
- return { start, end };
76
- }
77
- function notFound() {
78
- return new Response(null, { status: 404 });
79
- }
80
- function notModified(etag) {
81
- return new Response(null, {
82
- status: 304,
83
- headers: {
84
- ETag: etag,
85
- 'Cache-Control': CACHE_CONTROL,
86
- 'Access-Control-Allow-Origin': '*',
87
- },
88
- });
89
- }
90
- function bufferToBody(bytes) {
91
- // Copy into a fresh ArrayBuffer so the result is definitely typed as
92
- // `ArrayBuffer` (not `ArrayBufferLike` / `SharedArrayBuffer`) and is
93
- // safe to hand to Response without aliasing the Node Buffer pool.
94
- const out = new ArrayBuffer(bytes.byteLength);
95
- new Uint8Array(out).set(bytes);
96
- return out;
97
- }
98
- function tempBody(bytes, mime) {
99
- return new Response(bufferToBody(bytes), {
100
- status: 200,
101
- headers: {
102
- 'Content-Type': mime,
103
- 'Cache-Control': CACHE_CONTROL,
104
- 'Access-Control-Allow-Origin': '*',
105
- },
106
- });
107
- }
108
- export async function handleDifileRequest(ctx, req) {
109
- const v = resolveVPath(req.url);
110
- if (!v)
111
- return notFound();
112
- if (v.kind === 'tmp') {
113
- const record = ctx.tempStore.get(req.url);
114
- if (!record)
115
- return notFound();
116
- return tempBody(record.bytes, record.mime || 'application/octet-stream');
117
- }
118
- if (!v.realPath)
119
- return notFound();
120
- // Disk-backed: _store or usr.
121
- try {
122
- // Probe first to learn the ETag/size so If-None-Match can short-circuit
123
- // without reading the body. The probe also doubles as our existence /
124
- // permission check, so anything that throws here (ENOENT, EACCES, ...)
125
- // surfaces as a 404.
126
- const probe = await readDiskFile(v.realPath);
127
- const etag = probe.etag;
128
- const totalSize = probe.totalSize;
129
- const mime = probe.mime;
130
- const ifNoneMatch = getHeader(req.headers, 'If-None-Match');
131
- if (etagsMatch(ifNoneMatch, etag)) {
132
- return notModified(etag);
133
- }
134
- const rangeHeader = getHeader(req.headers, 'Range');
135
- const range = parseRange(rangeHeader, totalSize);
136
- if (range) {
137
- // Range out of bounds → 416 (Range Not Satisfiable) per RFC 9110 §15.4.
138
- // Distinct from 404 so callers can tell "wrong file" from "wrong slice".
139
- if (range.start < 0 || range.start >= totalSize || range.end < range.start) {
140
- return new Response(null, {
141
- status: 416,
142
- headers: {
143
- 'Content-Range': `bytes */${totalSize}`,
144
- 'Access-Control-Allow-Origin': '*',
145
- },
146
- });
147
- }
148
- const clampedEnd = Math.min(range.end, totalSize - 1);
149
- const sliced = await readDiskFile(v.realPath, { range: { start: range.start, end: clampedEnd } });
150
- const { bytes } = sliced;
151
- const lastByte = range.start + bytes.length - 1;
152
- return new Response(bufferToBody(bytes), {
153
- status: 206,
154
- headers: {
155
- 'Content-Type': mime,
156
- 'Content-Length': String(bytes.length),
157
- 'Content-Range': `bytes ${range.start}-${lastByte}/${totalSize}`,
158
- 'Cache-Control': CACHE_CONTROL,
159
- ETag: etag,
160
- 'Access-Control-Allow-Origin': '*',
161
- },
162
- });
163
- }
164
- return new Response(bufferToBody(probe.bytes), {
165
- status: 200,
166
- headers: {
167
- 'Content-Type': mime,
168
- 'Cache-Control': CACHE_CONTROL,
169
- ETag: etag,
170
- 'Access-Control-Allow-Origin': '*',
171
- },
172
- });
173
- }
174
- catch {
175
- return notFound();
176
- }
177
- }
1
+ export * from '@dimina-kit/electron-runtime/main/temp-files/request-handler';
178
2
  //# sourceMappingURL=request-handler.js.map
@@ -1,22 +1,2 @@
1
- /**
2
- * Pure lookup helper backing the main-process `difile://_tmp/*` protocol
3
- * handler. Receives the shared {@link TempFileStore} that the
4
- * `simulator:temp-file:*` IPC channels populate from the renderer.
5
- *
6
- * The full URL (including scheme + host) is used as the Map key — the resolver
7
- * never re-parses or normalises it, so any URL with a wrong scheme or host
8
- * misses regardless of suffix similarity.
9
- */
10
- export interface TempFileRecord {
11
- bytes: Buffer;
12
- mime: string;
13
- }
14
- export type TempFileStore = Map<string, TempFileRecord>;
15
- export declare function resolveTempFile(store: TempFileStore, url: string): {
16
- status: 200;
17
- bytes: Buffer;
18
- mime: string;
19
- } | {
20
- status: 404;
21
- };
1
+ export * from '@dimina-kit/electron-runtime/main/temp-files/resolver';
22
2
  //# sourceMappingURL=resolver.d.ts.map
@@ -1,22 +1,2 @@
1
- /**
2
- * Pure lookup helper backing the main-process `difile://_tmp/*` protocol
3
- * handler. Receives the shared {@link TempFileStore} that the
4
- * `simulator:temp-file:*` IPC channels populate from the renderer.
5
- *
6
- * The full URL (including scheme + host) is used as the Map key — the resolver
7
- * never re-parses or normalises it, so any URL with a wrong scheme or host
8
- * misses regardless of suffix similarity.
9
- */
10
- export function resolveTempFile(store, url) {
11
- if (!url.startsWith('difile://_tmp/'))
12
- return { status: 404 };
13
- const record = store.get(url);
14
- if (!record)
15
- return { status: 404 };
16
- return {
17
- status: 200,
18
- bytes: record.bytes,
19
- mime: record.mime || 'application/octet-stream',
20
- };
21
- }
1
+ export * from '@dimina-kit/electron-runtime/main/temp-files/resolver';
22
2
  //# sourceMappingURL=resolver.js.map
@@ -1,14 +1,2 @@
1
- /**
2
- * Mutators for the shared {@link TempFileStore}. The renderer-side bridge
3
- * forwards every `createTempFilePath` / `registerTempFilePath` /
4
- * `revokeTempFilePath` / `revokeAllTempFilePaths` call over IPC, and these
5
- * helpers translate the payloads into Map writes.
6
- *
7
- * ArrayBuffer inputs are normalised to Buffer at the boundary so the
8
- * resolver hot path can return the bytes directly without re-wrapping.
9
- */
10
- import type { TempFileStore } from './resolver.js';
11
- export declare function registerTempFile(store: TempFileStore, path: string, mime: string, bytes: ArrayBuffer | Buffer): void;
12
- export declare function revokeTempFile(store: TempFileStore, path: string): void;
13
- export declare function revokeAllTempFiles(store: TempFileStore): void;
1
+ export * from '@dimina-kit/electron-runtime/main/temp-files/store';
14
2
  //# sourceMappingURL=store.d.ts.map
@@ -1,24 +1,2 @@
1
- /**
2
- * Mutators for the shared {@link TempFileStore}. The renderer-side bridge
3
- * forwards every `createTempFilePath` / `registerTempFilePath` /
4
- * `revokeTempFilePath` / `revokeAllTempFilePaths` call over IPC, and these
5
- * helpers translate the payloads into Map writes.
6
- *
7
- * ArrayBuffer inputs are normalised to Buffer at the boundary so the
8
- * resolver hot path can return the bytes directly without re-wrapping.
9
- */
10
- export function registerTempFile(store, path, mime, bytes) {
11
- const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
12
- // delete-then-set so an existing entry's insertion order is refreshed.
13
- // Without this, a FIFO cap eviction over `store.keys()` could drop a
14
- // recently re-written path.
15
- store.delete(path);
16
- store.set(path, { bytes: buf, mime });
17
- }
18
- export function revokeTempFile(store, path) {
19
- store.delete(path);
20
- }
21
- export function revokeAllTempFiles(store) {
22
- store.clear();
23
- }
1
+ export * from '@dimina-kit/electron-runtime/main/temp-files/store';
24
2
  //# sourceMappingURL=store.js.map
@@ -1,90 +1,2 @@
1
- /**
2
- * Per-project session partition for the native-host miniapp runtime (P0 debt).
3
- *
4
- * Historically every project's miniapp webContents — the simulator content
5
- * WebContentsView, its nested render-host `<webview>` guests, AND the
6
- * service-host window — shared ONE hard-coded `persist:simulator` Electron
7
- * session. Cookies / localStorage / cache / IndexedDB written by project A were
8
- * visible to (and clobbered by) project B; two projects open at once cross-
9
- * contaminate and "clear storage" on one nukes the other.
10
- *
11
- * The fix derives a STABLE per-project partition from the project identity:
12
- * the miniapp `appId` (which flows in via the simulator URL `?appId=` and the
13
- * spawn path's `appId`) PLUS the project path. appId alone is insufficient —
14
- * it comes from the project manifest, so two DIFFERENT project paths can declare
15
- * the SAME appId and would otherwise share a partition; folding the path in
16
- * isolates them. Same project (same appId + path) → same partition (storage
17
- * survives a relaunch); different projects → different partitions (no cross-
18
- * contamination). The shape mirrors the IDE state of the art (WeChat's static
19
- * partition, ByteDance's `persist:miniapp-<id>` dynamic partition).
20
- *
21
- * The protocol handlers (`difile://`, `dmb-resource`) and webRequest policies
22
- * (referer / CORS) that the simulator runtime needs were installed once on the
23
- * single `persist:simulator` session. With per-project partitions those have to
24
- * be (re)applied to EACH project session the first time it is used. The setup
25
- * sites register a partition-agnostic configurator here; `configureMiniappSession`
26
- * runs every registered configurator exactly once per partition.
27
- *
28
- * Partition CLEANUP (reclaiming on-disk `persist:` data) is intentionally NOT
29
- * done here — leaving the data on disk is the whole point of a `persist:`
30
- * partition (cache/storage survives a relaunch). That is a separate concern.
31
- *
32
- * MIGRATION (intentional one-time reset): folding the project path into the key
33
- * means a project that previously ran on the appId-only `persist:miniapp-<appId>`
34
- * now runs on `persist:miniapp-<appId>-p<hash>` and starts with empty
35
- * cookies/localStorage/cache. Old data is NOT migrated — a best-effort copy
36
- * could carry already-cross-contaminated state into the newly isolated space,
37
- * which defeats the isolation. The reset is accepted as a one-time cost.
38
- */
39
- import type { Session } from 'electron';
40
- /** The legacy single-session partition. Still used by the pre-warm pool (which
41
- * is intentionally NOT isolation-aware — see `serviceHostSpec`) and as the
42
- * fallback when a project key cannot be derived. */
43
- export declare const SHARED_MINIAPP_PARTITION = "persist:simulator";
44
- /**
45
- * Derive a stable, filesystem-safe partition key from a project `appId`.
46
- *
47
- * `appId` is the project identity (e.g. a `wxapp…` id). We pass through the
48
- * characters Electron is happy to put in a session partition / on-disk folder
49
- * name (`[A-Za-z0-9_-]`) verbatim so the common case is human-legible, and fold
50
- * anything else into a short deterministic hash suffix so two appIds that differ
51
- * only in stripped characters never collide. The same `appId` always yields the
52
- * same key (so storage survives a relaunch); different `appId`s yield different
53
- * keys.
54
- */
55
- export declare function miniappPartitionKey(appId: string, projectPath?: string | null): string;
56
- /**
57
- * The Electron session partition for a project. Returns the SHARED partition
58
- * when `appId` is empty/unknown (so the runtime still has a session to load on,
59
- * matching the legacy behavior) and a `persist:miniapp-<key>` partition
60
- * otherwise.
61
- */
62
- export declare function miniappPartition(appId: string | null | undefined, projectPath?: string | null): string;
63
- type SessionConfigurator = (sess: Session, partition: string) => void;
64
- /**
65
- * Register a configurator that runs against every miniapp partition session.
66
- * Already-configured partitions are (re)configured immediately so registration
67
- * order does not matter. Returns a disposer that unregisters the configurator
68
- * (it does not undo work already applied to live sessions).
69
- */
70
- export declare function registerMiniappSessionConfigurator(fn: SessionConfigurator): () => void;
71
- /**
72
- * Ensure a partition's session has every registered configurator applied. Safe
73
- * to call repeatedly per partition (idempotent — each partition is configured
74
- * once). Call this before loading project content on `partition`.
75
- *
76
- * Returns `null` when there is nothing to configure (no configurator registered)
77
- * so the partition derivation stays independent of any live Electron session —
78
- * the partition is a constructor-time fact, not a side effect of touching the
79
- * session API. Setup sites register configurators at app boot; in unit tests
80
- * that mock `electron` without a `session` export this short-circuits cleanly.
81
- */
82
- export declare function configureMiniappSession(partition: string): Session | null;
83
- /**
84
- * Test-only: drop the configurator + configured-partition bookkeeping so each
85
- * test starts from a clean slate. (Module-level state otherwise leaks across
86
- * unit tests that mock `electron`.)
87
- */
88
- export declare function __resetMiniappSessionConfigForTests(): void;
89
- export {};
1
+ export * from '@dimina-kit/electron-runtime/main/miniapp-partition';
90
2
  //# sourceMappingURL=miniapp-partition.d.ts.map
@@ -1,159 +1,2 @@
1
- /**
2
- * Per-project session partition for the native-host miniapp runtime (P0 debt).
3
- *
4
- * Historically every project's miniapp webContents — the simulator content
5
- * WebContentsView, its nested render-host `<webview>` guests, AND the
6
- * service-host window — shared ONE hard-coded `persist:simulator` Electron
7
- * session. Cookies / localStorage / cache / IndexedDB written by project A were
8
- * visible to (and clobbered by) project B; two projects open at once cross-
9
- * contaminate and "clear storage" on one nukes the other.
10
- *
11
- * The fix derives a STABLE per-project partition from the project identity:
12
- * the miniapp `appId` (which flows in via the simulator URL `?appId=` and the
13
- * spawn path's `appId`) PLUS the project path. appId alone is insufficient —
14
- * it comes from the project manifest, so two DIFFERENT project paths can declare
15
- * the SAME appId and would otherwise share a partition; folding the path in
16
- * isolates them. Same project (same appId + path) → same partition (storage
17
- * survives a relaunch); different projects → different partitions (no cross-
18
- * contamination). The shape mirrors the IDE state of the art (WeChat's static
19
- * partition, ByteDance's `persist:miniapp-<id>` dynamic partition).
20
- *
21
- * The protocol handlers (`difile://`, `dmb-resource`) and webRequest policies
22
- * (referer / CORS) that the simulator runtime needs were installed once on the
23
- * single `persist:simulator` session. With per-project partitions those have to
24
- * be (re)applied to EACH project session the first time it is used. The setup
25
- * sites register a partition-agnostic configurator here; `configureMiniappSession`
26
- * runs every registered configurator exactly once per partition.
27
- *
28
- * Partition CLEANUP (reclaiming on-disk `persist:` data) is intentionally NOT
29
- * done here — leaving the data on disk is the whole point of a `persist:`
30
- * partition (cache/storage survives a relaunch). That is a separate concern.
31
- *
32
- * MIGRATION (intentional one-time reset): folding the project path into the key
33
- * means a project that previously ran on the appId-only `persist:miniapp-<appId>`
34
- * now runs on `persist:miniapp-<appId>-p<hash>` and starts with empty
35
- * cookies/localStorage/cache. Old data is NOT migrated — a best-effort copy
36
- * could carry already-cross-contaminated state into the newly isolated space,
37
- * which defeats the isolation. The reset is accepted as a one-time cost.
38
- */
39
- import * as electron from 'electron';
40
- /** The legacy single-session partition. Still used by the pre-warm pool (which
41
- * is intentionally NOT isolation-aware — see `serviceHostSpec`) and as the
42
- * fallback when a project key cannot be derived. */
43
- export const SHARED_MINIAPP_PARTITION = 'persist:simulator';
44
- const PARTITION_PREFIX = 'persist:miniapp-';
45
- /**
46
- * Derive a stable, filesystem-safe partition key from a project `appId`.
47
- *
48
- * `appId` is the project identity (e.g. a `wxapp…` id). We pass through the
49
- * characters Electron is happy to put in a session partition / on-disk folder
50
- * name (`[A-Za-z0-9_-]`) verbatim so the common case is human-legible, and fold
51
- * anything else into a short deterministic hash suffix so two appIds that differ
52
- * only in stripped characters never collide. The same `appId` always yields the
53
- * same key (so storage survives a relaunch); different `appId`s yield different
54
- * keys.
55
- */
56
- export function miniappPartitionKey(appId, projectPath) {
57
- const safe = appId.replace(/[^A-Za-z0-9_-]/g, '');
58
- // Folding may have dropped distinguishing characters; if the input was not
59
- // already fully safe, append a deterministic hash of the RAW appId so two
60
- // distinct appIds can never alias onto the same key.
61
- const base = safe === appId && safe.length > 0
62
- ? safe
63
- : safe.length > 0
64
- ? `${safe}-${djb2(appId).toString(36)}`
65
- : djb2(appId).toString(36);
66
- // `appId` comes from the project manifest, not the path — two DIFFERENT
67
- // project paths can declare the SAME appId. Fold the project path into the key
68
- // so those projects get isolated partitions (no storage/cache cross-talk),
69
- // while the same path always yields the same suffix (a relaunch reuses its
70
- // storage). Path characters aren't partition-safe, so always hash. Omitting
71
- // projectPath preserves the legacy appId-only key (pre-warm pool / callers
72
- // that have no project identity).
73
- if (!projectPath)
74
- return base;
75
- return `${base}-p${djb2(projectPath).toString(36)}`;
76
- }
77
- /**
78
- * The Electron session partition for a project. Returns the SHARED partition
79
- * when `appId` is empty/unknown (so the runtime still has a session to load on,
80
- * matching the legacy behavior) and a `persist:miniapp-<key>` partition
81
- * otherwise.
82
- */
83
- export function miniappPartition(appId, projectPath) {
84
- if (!appId)
85
- return SHARED_MINIAPP_PARTITION;
86
- return `${PARTITION_PREFIX}${miniappPartitionKey(appId, projectPath)}`;
87
- }
88
- /** Small, stable string hash (djb2). Not cryptographic — only needs to be
89
- * deterministic and collision-resistant enough to disambiguate appIds. */
90
- function djb2(input) {
91
- let h = 5381;
92
- for (let i = 0; i < input.length; i++) {
93
- h = ((h << 5) + h + input.charCodeAt(i)) >>> 0;
94
- }
95
- return h;
96
- }
97
- const configurators = new Set();
98
- const configuredPartitions = new Set();
99
- /**
100
- * Register a configurator that runs against every miniapp partition session.
101
- * Already-configured partitions are (re)configured immediately so registration
102
- * order does not matter. Returns a disposer that unregisters the configurator
103
- * (it does not undo work already applied to live sessions).
104
- */
105
- export function registerMiniappSessionConfigurator(fn) {
106
- configurators.add(fn);
107
- for (const partition of configuredPartitions) {
108
- try {
109
- fn(electron.session.fromPartition(partition), partition);
110
- }
111
- catch (err) {
112
- console.warn('[miniapp-partition] configurator failed for', partition, err);
113
- }
114
- }
115
- return () => {
116
- configurators.delete(fn);
117
- };
118
- }
119
- /**
120
- * Ensure a partition's session has every registered configurator applied. Safe
121
- * to call repeatedly per partition (idempotent — each partition is configured
122
- * once). Call this before loading project content on `partition`.
123
- *
124
- * Returns `null` when there is nothing to configure (no configurator registered)
125
- * so the partition derivation stays independent of any live Electron session —
126
- * the partition is a constructor-time fact, not a side effect of touching the
127
- * session API. Setup sites register configurators at app boot; in unit tests
128
- * that mock `electron` without a `session` export this short-circuits cleanly.
129
- */
130
- export function configureMiniappSession(partition) {
131
- // Record that this partition should be configured even if no configurator is
132
- // registered YET — a later `registerMiniappSessionConfigurator` back-fills it.
133
- const alreadyConfigured = configuredPartitions.has(partition);
134
- configuredPartitions.add(partition);
135
- if (configurators.size === 0)
136
- return null;
137
- const sess = electron.session.fromPartition(partition);
138
- if (alreadyConfigured)
139
- return sess;
140
- for (const fn of configurators) {
141
- try {
142
- fn(sess, partition);
143
- }
144
- catch (err) {
145
- console.warn('[miniapp-partition] configurator failed for', partition, err);
146
- }
147
- }
148
- return sess;
149
- }
150
- /**
151
- * Test-only: drop the configurator + configured-partition bookkeeping so each
152
- * test starts from a clean slate. (Module-level state otherwise leaks across
153
- * unit tests that mock `electron`.)
154
- */
155
- export function __resetMiniappSessionConfigForTests() {
156
- configurators.clear();
157
- configuredPartitions.clear();
158
- }
1
+ export * from '@dimina-kit/electron-runtime/main/miniapp-partition';
159
2
  //# sourceMappingURL=miniapp-partition.js.map
@@ -1,28 +1,2 @@
1
- /**
2
- * Referer + CORS webRequest policy for the simulator runtime's sessions.
3
- *
4
- * The native render/service hosts fetch the compiled `<appId>/…` resources
5
- * cross-origin and need a WeChat-style page-frame `Referer`; this installs that
6
- * policy on the shared fallback session AND on every per-project
7
- * `persist:miniapp-<key>` partition (current + future) via the partition
8
- * configurator registry, so isolated projects load resources identically.
9
- *
10
- * TEARDOWN: a `webRequest` listener is per-session and there is exactly one
11
- * slot per (session, event) — re-installing replaces, never stacks. But the
12
- * configurator registration itself leaks if discarded: re-creating the
13
- * WorkbenchApp in the same process would register a second configurator that
14
- * keeps firing for every future partition. So this returns a {@link Disposable}
15
- * that unregisters the configurator AND clears the listeners off every session
16
- * it touched. Wire it into the context registry like any other module.
17
- */
18
- import { type Disposable } from '@dimina-kit/electron-deck/main';
19
- /**
20
- * Install the simulator referer/CORS policy on the shared fallback session and
21
- * register a configurator so every per-project partition session gets it too.
22
- * Returns a disposable that unregisters the configurator and clears the policy
23
- * off every session it installed on — call its teardown when the owning
24
- * context is disposed so re-creating the app never leaks a duplicate
25
- * configurator/listener.
26
- */
27
- export declare function setupSimulatorSessionPolicy(): Disposable;
1
+ export * from '@dimina-kit/electron-runtime/main/session-policy';
28
2
  //# sourceMappingURL=simulator-session-policy.d.ts.map
@@ -1,99 +1,2 @@
1
- /**
2
- * Referer + CORS webRequest policy for the simulator runtime's sessions.
3
- *
4
- * The native render/service hosts fetch the compiled `<appId>/…` resources
5
- * cross-origin and need a WeChat-style page-frame `Referer`; this installs that
6
- * policy on the shared fallback session AND on every per-project
7
- * `persist:miniapp-<key>` partition (current + future) via the partition
8
- * configurator registry, so isolated projects load resources identically.
9
- *
10
- * TEARDOWN: a `webRequest` listener is per-session and there is exactly one
11
- * slot per (session, event) — re-installing replaces, never stacks. But the
12
- * configurator registration itself leaks if discarded: re-creating the
13
- * WorkbenchApp in the same process would register a second configurator that
14
- * keeps firing for every future partition. So this returns a {@link Disposable}
15
- * that unregisters the configurator AND clears the listeners off every session
16
- * it touched. Wire it into the context registry like any other module.
17
- */
18
- import { session } from 'electron';
19
- import { toDisposable } from '@dimina-kit/electron-deck/main';
20
- import { getSimulatorServicewechatReferer } from '../simulator/referer.js';
21
- import { registerMiniappSessionConfigurator, SHARED_MINIAPP_PARTITION, } from './miniapp-partition.js';
22
- /** The response headers this policy owns outright (compared lowercase). */
23
- const CORS_OVERRIDE_HEADERS = new Set([
24
- 'access-control-allow-origin',
25
- 'access-control-allow-headers',
26
- 'access-control-allow-methods',
27
- ]);
28
- /** Apply the simulator runtime's referer + CORS webRequest policy to one
29
- * session. Each session installs its own listeners (a webRequest listener is
30
- * per-session), so this runs once per partition. */
31
- function applySimulatorWebRequestPolicy(simulatorSession) {
32
- simulatorSession.webRequest.onBeforeSendHeaders((details, callback) => {
33
- const forcedReferer = getSimulatorServicewechatReferer();
34
- if (forcedReferer) {
35
- details.requestHeaders['Referer'] = forcedReferer;
36
- }
37
- callback({ requestHeaders: details.requestHeaders });
38
- });
39
- simulatorSession.webRequest.onHeadersReceived((details, callback) => {
40
- const headers = details.responseHeaders ?? {};
41
- // CORS for the native render/service hosts to fetch compiled app resources
42
- // cross-origin. (The COOP/COEP cross-origin-isolation headers were only for
43
- // the removed default-path SharedArrayBuffer sync Worker — dropped.)
44
- //
45
- // This policy is the single authority for these three headers: any value
46
- // the server itself sent must be REPLACED, not shadowed. responseHeaders
47
- // keys keep the server's original casing, so assigning the lowercase key
48
- // beside an existing `Access-Control-Allow-Origin` puts BOTH on the wire —
49
- // Chromium then rejects the response for carrying multiple ACAO values
50
- // (`*, *`), breaking every fetch against a CORS-compliant backend.
51
- for (const key of Object.keys(headers)) {
52
- if (CORS_OVERRIDE_HEADERS.has(key.toLowerCase()))
53
- delete headers[key];
54
- }
55
- headers['access-control-allow-origin'] = ['*'];
56
- headers['access-control-allow-headers'] = ['*'];
57
- headers['access-control-allow-methods'] = ['*'];
58
- callback({ responseHeaders: headers });
59
- });
60
- }
61
- /** Remove the policy listeners from a session (passing `null` clears the slot). */
62
- function clearSimulatorWebRequestPolicy(simulatorSession) {
63
- try {
64
- simulatorSession.webRequest.onBeforeSendHeaders(null);
65
- simulatorSession.webRequest.onHeadersReceived(null);
66
- }
67
- catch {
68
- // Session already gone (app shutdown) — nothing to clear.
69
- }
70
- }
71
- /**
72
- * Install the simulator referer/CORS policy on the shared fallback session and
73
- * register a configurator so every per-project partition session gets it too.
74
- * Returns a disposable that unregisters the configurator and clears the policy
75
- * off every session it installed on — call its teardown when the owning
76
- * context is disposed so re-creating the app never leaks a duplicate
77
- * configurator/listener.
78
- */
79
- export function setupSimulatorSessionPolicy() {
80
- const configured = new Set();
81
- function install(sess) {
82
- if (configured.has(sess))
83
- return;
84
- configured.add(sess);
85
- applySimulatorWebRequestPolicy(sess);
86
- }
87
- // Shared fallback session (pre-warm pool + unknown-appId path).
88
- install(session.fromPartition(SHARED_MINIAPP_PARTITION));
89
- // Every per-project miniapp partition session (current + future) gets the
90
- // same referer/CORS policy so isolated projects load resources identically.
91
- const unregister = registerMiniappSessionConfigurator((sess) => install(sess));
92
- return toDisposable(() => {
93
- unregister();
94
- for (const sess of configured)
95
- clearSimulatorWebRequestPolicy(sess);
96
- configured.clear();
97
- });
98
- }
1
+ export * from '@dimina-kit/electron-runtime/main/session-policy';
99
2
  //# sourceMappingURL=simulator-session-policy.js.map