@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,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
|
|
@@ -70,6 +70,7 @@ export function createNativeSimulatorView(ctx, reconciler, deps) {
|
|
|
70
70
|
function tearDownNativeSimulatorView(label) {
|
|
71
71
|
if (!nativeSimulatorView)
|
|
72
72
|
return;
|
|
73
|
+
ctx.simulatorUiExtensions?.detach(nativeSimulatorView.webContents);
|
|
73
74
|
if (nativeSimulatorViewAdded && !ctx.windows.mainWindow.isDestroyed()) {
|
|
74
75
|
try {
|
|
75
76
|
ctx.windows.mainWindow.contentView.removeChildView(nativeSimulatorView);
|
|
@@ -219,6 +220,8 @@ export function createNativeSimulatorView(ctx, reconciler, deps) {
|
|
|
219
220
|
// and the renderer placeholder behind it are all the same color.
|
|
220
221
|
view.setBackgroundColor(simDeskBg());
|
|
221
222
|
const simWc = view.webContents;
|
|
223
|
+
// Keep downstream UI bundles bound to this WCV until shared teardown.
|
|
224
|
+
ctx.simulatorUiExtensions?.attach(simWc);
|
|
222
225
|
// Keep the WCV surface in sync with the active color scheme. The
|
|
223
226
|
// process-wide installThemeBackgroundSync() re-syncs BrowserWindows on a
|
|
224
227
|
// theme switch, but this top-level WebContentsView is not a window, so its
|
|
@@ -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
|
|
@@ -64,6 +64,11 @@ export interface ViewManagerContext {
|
|
|
64
64
|
* Optional so partial test contexts compile.
|
|
65
65
|
*/
|
|
66
66
|
simulatorApis?: WorkbenchContext['simulatorApis'];
|
|
67
|
+
/**
|
|
68
|
+
* Host-registered UI extensions that follow the native simulator WCV.
|
|
69
|
+
* Optional only for focused view-manager test doubles.
|
|
70
|
+
*/
|
|
71
|
+
simulatorUiExtensions?: WorkbenchContext['simulatorUiExtensions'];
|
|
67
72
|
}
|
|
68
73
|
/**
|
|
69
74
|
* Unified lifecycle manager for Electron WebContentsView overlays.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BrowserWindow } from 'electron';
|
|
2
|
+
import { type RuntimeContext, type RuntimeEvents } from '@dimina-kit/electron-runtime/main/runtime-context';
|
|
2
3
|
import type { CompilationAdapter, CustomFileTypes, WorkbenchConfig } from '../../shared/types.js';
|
|
3
4
|
import type { BridgeRouterHandle } from '../ipc/bridge-router.js';
|
|
4
5
|
import type { ConsoleForwarder } from './console-forward/index.js';
|
|
@@ -18,6 +19,7 @@ import { type ViewManager } from './views/view-manager.js';
|
|
|
18
19
|
import { type WindowService } from './window-service.js';
|
|
19
20
|
import { type WorkspaceService } from './workspace/workspace-service.js';
|
|
20
21
|
import { type SimulatorApiRegistry } from './simulator/custom-apis.js';
|
|
22
|
+
import { type SimulatorUiExtensionRegistry } from './simulator/ui-extensions.js';
|
|
21
23
|
import { sanitizeTemplates } from './projects/templates.js';
|
|
22
24
|
import type { ProjectsProvider, ProjectTemplate } from './projects/types.js';
|
|
23
25
|
import type { CustomCreateProjectDialogResult } from '../../shared/types.js';
|
|
@@ -25,7 +27,7 @@ import type { CustomCreateProjectDialogResult } from '../../shared/types.js';
|
|
|
25
27
|
* Shared mutable state for the workbench application.
|
|
26
28
|
* Passed to each IPC module so they can read/write shared state without closures.
|
|
27
29
|
*/
|
|
28
|
-
export interface WorkbenchContext {
|
|
30
|
+
export interface WorkbenchContext extends RuntimeContext {
|
|
29
31
|
adapter: CompilationAdapter;
|
|
30
32
|
/** Absolute path to the preload script loaded into the simulator webview */
|
|
31
33
|
preloadPath: string;
|
|
@@ -123,8 +125,15 @@ export interface WorkbenchContext {
|
|
|
123
125
|
* One registry per context — no process-global crosstalk.
|
|
124
126
|
*/
|
|
125
127
|
simulatorApis: SimulatorApiRegistry;
|
|
128
|
+
/**
|
|
129
|
+
* Per-context downstream simulator UI extensions. It owns renderer bundle
|
|
130
|
+
* installation and follows the active native simulator WebContentsView.
|
|
131
|
+
*/
|
|
132
|
+
simulatorUiExtensions: SimulatorUiExtensionRegistry;
|
|
126
133
|
/** Aggregates dispose handlers for every IPC handler, listener, watcher, and CDP session registered by the workbench. */
|
|
127
134
|
registry: DisposableRegistry;
|
|
135
|
+
/** Runtime-domain events consumed by optional DevTools panels and renderer notifications. */
|
|
136
|
+
events: RuntimeEvents;
|
|
128
137
|
/**
|
|
129
138
|
* Per-webContents connection registry. Each trusted webContents (main window,
|
|
130
139
|
* overlay views, native simulator/render guests) is one `Connection` that
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRuntimeEvents, } from '@dimina-kit/electron-runtime/main/runtime-context';
|
|
1
2
|
import { createCdpSessionBroker } from './cdp-session/index.js';
|
|
2
3
|
import { createConnectionRegistry, DisposableRegistry, } from '@dimina-kit/electron-deck/main';
|
|
3
4
|
import { createWorkbenchSenderPolicy } from '../utils/sender-policy.js';
|
|
@@ -12,11 +13,14 @@ import { openSettingsWindow } from '../windows/settings-window/index.js';
|
|
|
12
13
|
import { createWorkspaceService, } from './workspace/workspace-service.js';
|
|
13
14
|
import { createLocalProjectsProvider } from './projects/local-provider.js';
|
|
14
15
|
import { createSimulatorApiRegistry, } from './simulator/custom-apis.js';
|
|
16
|
+
import { createSimulatorUiExtensionRegistry, } from './simulator/ui-extensions.js';
|
|
15
17
|
import { resolveTemplates, sanitizeTemplates } from './projects/templates.js';
|
|
16
18
|
import { BUILTIN_TEMPLATES } from './projects/builtin-templates.js';
|
|
19
|
+
import { runtimeAssetPaths } from '../utils/paths.js';
|
|
17
20
|
export function createWorkbenchContext(opts) {
|
|
18
21
|
const ctx = {
|
|
19
22
|
adapter: opts.adapter ?? defaultAdapter,
|
|
23
|
+
assets: runtimeAssetPaths,
|
|
20
24
|
preloadPath: opts.preloadPath,
|
|
21
25
|
rendererDir: opts.rendererDir,
|
|
22
26
|
apiNamespaces: opts.apiNamespaces ?? [],
|
|
@@ -25,6 +29,20 @@ export function createWorkbenchContext(opts) {
|
|
|
25
29
|
brandingProvider: opts.brandingProvider,
|
|
26
30
|
};
|
|
27
31
|
ctx.registry = new DisposableRegistry();
|
|
32
|
+
ctx.events = createRuntimeEvents();
|
|
33
|
+
ctx.registry.add(ctx.events.on('session-status', (payload) => {
|
|
34
|
+
ctx.notify?.sessionRuntimeStatus(payload);
|
|
35
|
+
}));
|
|
36
|
+
ctx.registry.add(ctx.events.on('app-data-evict', ({ appId, bridgeId }) => {
|
|
37
|
+
ctx.appData?.evictBridge(appId, bridgeId);
|
|
38
|
+
}));
|
|
39
|
+
ctx.registry.add(ctx.events.on('app-data-message', ({ appId, message }) => {
|
|
40
|
+
ctx.appData?.onServiceToRender(appId, message);
|
|
41
|
+
}));
|
|
42
|
+
ctx.registry.add(ctx.events.on('storage-changed', ({ appId, change }) => {
|
|
43
|
+
ctx.onServiceStorageChanged?.(appId, change);
|
|
44
|
+
}));
|
|
45
|
+
ctx.registry.add(() => ctx.events.clear());
|
|
28
46
|
// Empty connection registry as a first-class context field. Connections are
|
|
29
47
|
// acquired by real wiring points (app bootstrap, view-manager endpoints),
|
|
30
48
|
// NOT here — the constructor stays side-effect-free so focused unit tests can
|
|
@@ -34,6 +52,8 @@ export function createWorkbenchContext(opts) {
|
|
|
34
52
|
ctx.registry.add(() => ctx.cdpSessionBroker.dispose());
|
|
35
53
|
ctx.trustedWindowSenderIds = new Map();
|
|
36
54
|
ctx.simulatorApis = createSimulatorApiRegistry();
|
|
55
|
+
ctx.simulatorUiExtensions = createSimulatorUiExtensionRegistry();
|
|
56
|
+
ctx.registry.add(() => ctx.simulatorUiExtensions.clear());
|
|
37
57
|
ctx.windows = createWindowService(opts.mainWindow);
|
|
38
58
|
ctx.views = createViewManager(ctx);
|
|
39
59
|
// Full view teardown belongs to the CONTEXT's life, not a project's:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { IpcMainEvent
|
|
1
|
+
import type { IpcMainEvent } from 'electron';
|
|
2
2
|
import { ipcMain } from 'electron';
|
|
3
|
-
import { type Disposable } from '@dimina-kit/electron-deck/main';
|
|
3
|
+
import { type Disposable, type IpcSenderPolicy } from '@dimina-kit/electron-deck/main';
|
|
4
4
|
type HandleFn = Parameters<typeof ipcMain.handle>[1];
|
|
5
5
|
type ListenerFn = Parameters<typeof ipcMain.on>[1];
|
|
6
6
|
/**
|
|
@@ -9,7 +9,25 @@ type ListenerFn = Parameters<typeof ipcMain.on>[1];
|
|
|
9
9
|
* Returns true when the calling WebContents is allowed to invoke / emit on
|
|
10
10
|
* the channel, false otherwise.
|
|
11
11
|
*/
|
|
12
|
-
export type SenderPolicy =
|
|
12
|
+
export type SenderPolicy = IpcSenderPolicy;
|
|
13
|
+
/**
|
|
14
|
+
* Defense-in-depth frame check (sits alongside the sender white-list). A trusted
|
|
15
|
+
* webContents could embed a sub-frame of arbitrary origin; only its top (main)
|
|
16
|
+
* frame should reach gated IPC, so a sub-frame can't spoof the trusted sender.
|
|
17
|
+
*
|
|
18
|
+
* Verified on Electron 41 that `event.senderFrame` is reliably present on
|
|
19
|
+
* invoke / send / sendSync — including the editor's `beforeunload` sendSync
|
|
20
|
+
* write — and equals `sender.mainFrame` for top-frame traffic, so this never
|
|
21
|
+
* mis-rejects legitimate callers.
|
|
22
|
+
*
|
|
23
|
+
* Fail-closed on a null frame for REAL events: a sub-frame can send a message
|
|
24
|
+
* and immediately navigate/destroy itself, so by delivery time `senderFrame`
|
|
25
|
+
* resolves to null — allowing that would let the navigate-after-send trick
|
|
26
|
+
* bypass the boundary. A real Electron event always exposes the `senderFrame`
|
|
27
|
+
* property and `sender.mainFrame`, so we can tell a real event (frame-modeled,
|
|
28
|
+
* possibly null) from a frame-unaware unit-test stub (neither present) and only
|
|
29
|
+
* skip the check for the latter (the sender-id white-list still gates tests).
|
|
30
|
+
*/
|
|
13
31
|
/**
|
|
14
32
|
* Tiny fluent helper that wraps every `ipcMain.handle` / `ipcMain.on` with a
|
|
15
33
|
* matching removeHandler/removeListener registered into an internal registry.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ipcMain } from 'electron';
|
|
2
|
+
import { isMainFrameIpcSender, } from '@dimina-kit/electron-deck/main';
|
|
2
3
|
import { IpcValidationError } from './ipc-schema.js';
|
|
3
4
|
import { createLogger } from './logger.js';
|
|
4
5
|
const log = createLogger('ipc');
|
|
@@ -29,23 +30,24 @@ function summarizeSender(sender) {
|
|
|
29
30
|
const url = sender.getURL();
|
|
30
31
|
return `id=${sender.id} ${url.slice(0, 120)}`;
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Defense-in-depth frame check (sits alongside the sender white-list). A trusted
|
|
35
|
+
* webContents could embed a sub-frame of arbitrary origin; only its top (main)
|
|
36
|
+
* frame should reach gated IPC, so a sub-frame can't spoof the trusted sender.
|
|
37
|
+
*
|
|
38
|
+
* Verified on Electron 41 that `event.senderFrame` is reliably present on
|
|
39
|
+
* invoke / send / sendSync — including the editor's `beforeunload` sendSync
|
|
40
|
+
* write — and equals `sender.mainFrame` for top-frame traffic, so this never
|
|
41
|
+
* mis-rejects legitimate callers.
|
|
42
|
+
*
|
|
43
|
+
* Fail-closed on a null frame for REAL events: a sub-frame can send a message
|
|
44
|
+
* and immediately navigate/destroy itself, so by delivery time `senderFrame`
|
|
45
|
+
* resolves to null — allowing that would let the navigate-after-send trick
|
|
46
|
+
* bypass the boundary. A real Electron event always exposes the `senderFrame`
|
|
47
|
+
* property and `sender.mainFrame`, so we can tell a real event (frame-modeled,
|
|
48
|
+
* possibly null) from a frame-unaware unit-test stub (neither present) and only
|
|
49
|
+
* skip the check for the latter (the sender-id white-list still gates tests).
|
|
50
|
+
*/
|
|
49
51
|
/**
|
|
50
52
|
* Tiny fluent helper that wraps every `ipcMain.handle` / `ipcMain.on` with a
|
|
51
53
|
* matching removeHandler/removeListener registered into an internal registry.
|
|
@@ -84,7 +86,7 @@ export class IpcRegistry {
|
|
|
84
86
|
const guarded = policy
|
|
85
87
|
? async (event, ...args) => {
|
|
86
88
|
const sender = event.sender;
|
|
87
|
-
if (!policy(sender) || !
|
|
89
|
+
if (!policy(sender) || !isMainFrameIpcSender(event)) {
|
|
88
90
|
console.warn(`[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`);
|
|
89
91
|
throw new Error(`IPC sender rejected for channel ${channel}`);
|
|
90
92
|
}
|
|
@@ -106,26 +108,27 @@ export class IpcRegistry {
|
|
|
106
108
|
handleSync(channel, fn) {
|
|
107
109
|
const policy = this.policy;
|
|
108
110
|
const listener = (event, ...args) => {
|
|
111
|
+
const syncEvent = event;
|
|
109
112
|
// Everything — including the policy check — runs inside the try so EVERY
|
|
110
113
|
// path sets `event.returnValue`. sendSync blocks the renderer until it is
|
|
111
114
|
// set, so an unset value (e.g. a throwing policy fn) would hang the
|
|
112
115
|
// renderer forever; the catch guarantees a sentinel instead.
|
|
113
116
|
try {
|
|
114
|
-
if (policy && (!policy(event.sender) || !
|
|
117
|
+
if (policy && (!policy(event.sender) || !isMainFrameIpcSender(event))) {
|
|
115
118
|
console.warn(`[ipc] sender rejected for channel '${channel}' (${summarizeSender(event.sender)})`);
|
|
116
|
-
|
|
119
|
+
syncEvent.returnValue = {
|
|
117
120
|
ok: false,
|
|
118
121
|
code: 'EREJECTED',
|
|
119
122
|
message: `IPC sender rejected for channel ${channel}`,
|
|
120
123
|
};
|
|
121
124
|
return;
|
|
122
125
|
}
|
|
123
|
-
|
|
126
|
+
syncEvent.returnValue = fn(event, ...args);
|
|
124
127
|
}
|
|
125
128
|
catch (err) {
|
|
126
129
|
reportListenerError(channel, err);
|
|
127
130
|
const code = err?.code;
|
|
128
|
-
|
|
131
|
+
syncEvent.returnValue = {
|
|
129
132
|
ok: false,
|
|
130
133
|
code: typeof code === 'string' ? code : 'EUNKNOWN',
|
|
131
134
|
message: err instanceof Error ? err.message : String(err),
|
|
@@ -156,7 +159,7 @@ export class IpcRegistry {
|
|
|
156
159
|
const guarded = policy
|
|
157
160
|
? (event, ...args) => {
|
|
158
161
|
const sender = event.sender;
|
|
159
|
-
if (!policy(sender) || !
|
|
162
|
+
if (!policy(sender) || !isMainFrameIpcSender(event)) {
|
|
160
163
|
console.warn(`[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`);
|
|
161
164
|
return;
|
|
162
165
|
}
|
|
@@ -32,6 +32,20 @@ export declare const mainPreloadPath: string;
|
|
|
32
32
|
*/
|
|
33
33
|
export declare const hostToolbarRuntimePreloadPath: string;
|
|
34
34
|
export declare const simulatorDir: string;
|
|
35
|
+
/**
|
|
36
|
+
* DevTools builds its own copy of every native mini-app runtime asset. Pin the
|
|
37
|
+
* shared runtime bridge to that copy so a packaged client never depends on the
|
|
38
|
+
* standalone npm package's optional asset assembly step.
|
|
39
|
+
*/
|
|
40
|
+
export declare const runtimeAssetPaths: {
|
|
41
|
+
root: string;
|
|
42
|
+
simulatorDir: string;
|
|
43
|
+
simulatorPreloadPath: string;
|
|
44
|
+
renderHostHtmlPath: string;
|
|
45
|
+
renderHostPreloadPath: string;
|
|
46
|
+
serviceHostHtmlPath: string;
|
|
47
|
+
serviceHostPreloadPath: string;
|
|
48
|
+
};
|
|
35
49
|
export declare function getRendererDir(): string;
|
|
36
50
|
export declare function getPreloadDir(): string;
|
|
37
51
|
export declare function getRendererHtml(filename: string): string;
|
package/dist/main/utils/paths.js
CHANGED
|
@@ -60,6 +60,20 @@ export const mainPreloadPath = path.join(devtoolsPackageRoot, 'dist/preload/wind
|
|
|
60
60
|
*/
|
|
61
61
|
export const hostToolbarRuntimePreloadPath = path.join(devtoolsPackageRoot, 'dist/preload/windows/host-toolbar-runtime.cjs');
|
|
62
62
|
export const simulatorDir = path.join(devtoolsPackageRoot, 'dist/simulator');
|
|
63
|
+
/**
|
|
64
|
+
* DevTools builds its own copy of every native mini-app runtime asset. Pin the
|
|
65
|
+
* shared runtime bridge to that copy so a packaged client never depends on the
|
|
66
|
+
* standalone npm package's optional asset assembly step.
|
|
67
|
+
*/
|
|
68
|
+
export const runtimeAssetPaths = {
|
|
69
|
+
root: path.join(devtoolsPackageRoot, 'dist'),
|
|
70
|
+
simulatorDir,
|
|
71
|
+
simulatorPreloadPath: path.join(devtoolsPackageRoot, 'dist/preload/windows/simulator.cjs'),
|
|
72
|
+
renderHostHtmlPath: path.join(devtoolsPackageRoot, 'dist/render-host/pageFrame.html'),
|
|
73
|
+
renderHostPreloadPath: path.join(devtoolsPackageRoot, 'dist/render-host/preload.cjs'),
|
|
74
|
+
serviceHostHtmlPath: path.join(devtoolsPackageRoot, 'dist/service-host/service.html'),
|
|
75
|
+
serviceHostPreloadPath: path.join(devtoolsPackageRoot, 'dist/service-host/preload.cjs'),
|
|
76
|
+
};
|
|
63
77
|
export function getRendererDir() {
|
|
64
78
|
return rendererDir;
|
|
65
79
|
}
|