@dimina-kit/devtools 0.3.0 → 0.3.1-dev.20260515105504
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 +103 -41
- package/dist/main/api.d.ts +6 -2
- package/dist/main/api.js +5 -2
- package/dist/main/app/app.d.ts +1 -1
- package/dist/main/app/app.js +71 -36
- package/dist/main/app/launch.d.ts +2 -2
- package/dist/main/app/launch.js +6 -3
- package/dist/main/index.bundle.js +1134 -300
- package/dist/main/ipc/app.d.ts +2 -1
- package/dist/main/ipc/app.js +5 -4
- package/dist/main/ipc/index.d.ts +5 -4
- package/dist/main/ipc/index.js +5 -4
- package/dist/main/ipc/panels.d.ts +2 -1
- package/dist/main/ipc/panels.js +14 -9
- package/dist/main/ipc/popover.d.ts +4 -1
- package/dist/main/ipc/popover.js +15 -5
- package/dist/main/ipc/projects.d.ts +4 -1
- package/dist/main/ipc/projects.js +42 -5
- package/dist/main/ipc/session.d.ts +4 -1
- package/dist/main/ipc/session.js +26 -6
- package/dist/main/ipc/settings.d.ts +4 -1
- package/dist/main/ipc/settings.js +30 -17
- package/dist/main/ipc/simulator-module.d.ts +8 -0
- package/dist/main/ipc/simulator-module.js +19 -0
- package/dist/main/ipc/simulator.d.ts +2 -1
- package/dist/main/ipc/simulator.js +29 -15
- package/dist/main/ipc/toolbar.d.ts +2 -1
- package/dist/main/ipc/toolbar.js +3 -2
- package/dist/main/services/automation/index.js +88 -13
- package/dist/main/services/default-adapter.js +1 -3
- package/dist/main/services/mcp/server.d.ts +2 -1
- package/dist/main/services/mcp/server.js +5 -0
- package/dist/main/services/module.d.ts +18 -0
- package/dist/main/services/module.js +2 -0
- package/dist/main/services/notifications/renderer-notifier.d.ts +2 -0
- package/dist/main/services/projects/project-repository.d.ts +1 -0
- package/dist/main/services/projects/project-repository.js +3 -0
- package/dist/main/services/settings/index.d.ts +4 -0
- package/dist/main/services/settings/index.js +12 -1
- package/dist/main/services/simulator/custom-apis.d.ts +10 -0
- package/dist/main/services/simulator/custom-apis.js +26 -0
- package/dist/main/services/simulator/dir.d.ts +1 -1
- package/dist/main/services/simulator/dir.js +1 -4
- package/dist/main/services/simulator-storage/index.d.ts +22 -1
- package/dist/main/services/simulator-storage/index.js +390 -84
- package/dist/main/services/update/update-manager.d.ts +16 -1
- package/dist/main/services/update/update-manager.js +18 -15
- package/dist/main/services/views/view-manager.d.ts +4 -0
- package/dist/main/services/views/view-manager.js +33 -2
- package/dist/main/services/window-service.d.ts +24 -0
- package/dist/main/services/window-service.js +26 -0
- package/dist/main/services/workbench-context.d.ts +15 -0
- package/dist/main/services/workbench-context.js +6 -0
- package/dist/main/services/workspace/workspace-service.d.ts +1 -0
- package/dist/main/services/workspace/workspace-service.js +7 -3
- package/dist/main/simulator-apis.d.ts +23 -0
- package/dist/main/simulator-apis.js +24 -0
- package/dist/main/utils/disposable.d.ts +13 -0
- package/dist/main/utils/disposable.js +60 -0
- package/dist/main/utils/ipc-registry.d.ts +39 -0
- package/dist/main/utils/ipc-registry.js +109 -0
- package/dist/main/utils/ipc-schema.d.ts +28 -0
- package/dist/main/utils/ipc-schema.js +38 -0
- package/dist/main/utils/paths.d.ts +8 -0
- package/dist/main/utils/paths.js +33 -4
- package/dist/main/utils/sender-policy.d.ts +23 -0
- package/dist/main/utils/sender-policy.js +42 -0
- package/dist/main/windows/main-window/create.d.ts +6 -0
- package/dist/main/windows/main-window/create.js +65 -9
- package/dist/main/windows/main-window/events.d.ts +2 -1
- package/dist/main/windows/main-window/events.js +25 -2
- package/dist/main/windows/navigation-hardening.d.ts +24 -0
- package/dist/main/windows/navigation-hardening.js +55 -0
- package/dist/main/windows/settings-window/create.js +11 -1
- package/dist/preload/index.d.ts +17 -0
- package/dist/preload/index.js +17 -0
- package/dist/preload/instrumentation/app-data.js +178 -19
- package/dist/preload/instrumentation/wxml-extract.js +134 -3
- package/dist/preload/runtime/bridge.d.ts +3 -1
- package/dist/preload/runtime/bridge.js +55 -4
- package/dist/preload/runtime/custom-apis.d.ts +6 -0
- package/dist/preload/runtime/custom-apis.js +18 -0
- package/dist/preload/runtime/host.d.ts +1 -0
- package/dist/preload/runtime/host.js +13 -0
- package/dist/preload/shared/types.d.ts +2 -1
- package/dist/preload/windows/main.cjs +47 -0
- package/dist/preload/windows/main.cjs.map +7 -0
- package/dist/preload/windows/simulator.js +274 -25
- package/dist/renderer/assets/index-DW_0thr-.js +46 -0
- package/dist/renderer/assets/{input-VLotwer2.js → input-C5M5Wxn6.js} +2 -2
- package/dist/renderer/assets/{ipc-transport-COKIC6Ny.js → ipc-transport-CUsl-fS2.js} +6 -6
- package/dist/renderer/assets/ipc-transport-CltRuvhi.css +1 -0
- package/dist/renderer/assets/{popover-Dn0RwMLI.js → popover-BSBPVO0o.js} +2 -2
- package/dist/renderer/assets/{select-DzIsLaKX.js → select-B1tF3UAc.js} +2 -2
- package/dist/renderer/assets/{settings-CD-dacWB.js → settings-7w54H6p-.js} +2 -2
- package/dist/renderer/assets/settings-api-BjJalghB.js +2 -0
- package/dist/renderer/assets/workbenchSettings-q2_-nc8t.js +8 -0
- package/dist/renderer/entries/main/index.html +12 -5
- package/dist/renderer/entries/popover/index.html +7 -5
- package/dist/renderer/entries/settings/index.html +7 -5
- package/dist/renderer/entries/workbench-settings/index.html +6 -4
- package/dist/shared/ipc-channels.d.ts +45 -0
- package/dist/shared/ipc-channels.js +28 -0
- package/dist/shared/ipc-schemas.d.ts +113 -0
- package/dist/shared/ipc-schemas.js +128 -0
- package/dist/shared/types.d.ts +2 -0
- package/dist/simulator/assets/simulator-DL6kOCq-.js +9 -0
- package/dist/simulator/simulator.html +1 -1
- package/package.json +11 -27
- package/dist/preload/windows/main.d.ts +0 -6
- package/dist/preload/windows/main.js +0 -6
- package/dist/renderer/assets/index-D_HFRqZA.js +0 -46
- package/dist/renderer/assets/ipc-transport-6Gy3FXIL.css +0 -1
- package/dist/renderer/assets/settings-api-BV9MPckG.js +0 -2
- package/dist/renderer/assets/workbenchSettings-ClwgsvJL.js +0 -8
- package/dist/simulator/assets/simulator-C1eHXnuq.js +0 -9
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default workbench sender white-list.
|
|
3
|
+
*
|
|
4
|
+
* An IPC message is accepted only when its `event.sender` matches one of
|
|
5
|
+
* the WebContents the main process itself created and trusts:
|
|
6
|
+
* - the main window's renderer
|
|
7
|
+
* - the optional workbench settings BrowserWindow's renderer (when open)
|
|
8
|
+
* - the settings overlay view (when open)
|
|
9
|
+
* - the popover overlay view (when open)
|
|
10
|
+
*
|
|
11
|
+
* The simulator webview is intentionally NOT on this list: its preload
|
|
12
|
+
* (`src/preload/windows/simulator.ts`) uses `ipcRenderer.sendToHost`
|
|
13
|
+
* exclusively, never `invoke`/`send`, so it can't legitimately reach an
|
|
14
|
+
* ipcMain handler. Including it would only widen the attack surface if
|
|
15
|
+
* the guest were ever compromised.
|
|
16
|
+
*
|
|
17
|
+
* Any other sender — including a stale/destroyed sender or an unknown
|
|
18
|
+
* iframe — is rejected.
|
|
19
|
+
*/
|
|
20
|
+
export function createWorkbenchSenderPolicy(ctx) {
|
|
21
|
+
return (sender) => {
|
|
22
|
+
if (sender.isDestroyed())
|
|
23
|
+
return false;
|
|
24
|
+
// Main window renderer
|
|
25
|
+
if (ctx.windows.isMainSender(sender.id))
|
|
26
|
+
return true;
|
|
27
|
+
// Standalone settings BrowserWindow renderer
|
|
28
|
+
if (ctx.windows.isSettingsWindowSender(sender.id))
|
|
29
|
+
return true;
|
|
30
|
+
// Settings overlay view (mounted inside the main window)
|
|
31
|
+
const settingsViewId = ctx.views.getSettingsWebContentsId();
|
|
32
|
+
if (settingsViewId != null && sender.id === settingsViewId)
|
|
33
|
+
return true;
|
|
34
|
+
// Popover overlay view
|
|
35
|
+
const popoverViewId = ctx.views.getPopoverWebContentsId();
|
|
36
|
+
if (popoverViewId != null && sender.id === popoverViewId)
|
|
37
|
+
return true;
|
|
38
|
+
// simulator preload uses sendToHost only; never reaches ipcMain handlers.
|
|
39
|
+
return false;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=sender-policy.js.map
|
|
@@ -6,6 +6,12 @@ export interface WindowOptions {
|
|
|
6
6
|
minWidth?: number;
|
|
7
7
|
minHeight?: number;
|
|
8
8
|
indexHtml: string;
|
|
9
|
+
/**
|
|
10
|
+
* Absolute path to the simulator preload bundle. Registered at the session
|
|
11
|
+
* level so every frame inside the simulator <webview> gets the preload that
|
|
12
|
+
* exposes the `wx` runtime to the mini-program.
|
|
13
|
+
*/
|
|
14
|
+
simulatorPreloadPath: string;
|
|
9
15
|
}
|
|
10
16
|
export declare function createMainWindow(opts: WindowOptions): BrowserWindow;
|
|
11
17
|
//# sourceMappingURL=create.d.ts.map
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import { app, BrowserWindow, View,
|
|
1
|
+
import { app, BrowserWindow, View, session, shell } from 'electron';
|
|
2
|
+
import path from 'path';
|
|
2
3
|
import { getSimulatorServicewechatReferer } from '../../services/simulator/referer.js';
|
|
4
|
+
import { mainPreloadPath } from '../../utils/paths.js';
|
|
3
5
|
import { themeBg } from '../../utils/theme.js';
|
|
6
|
+
import { applyNavigationHardening, handleWindowOpenExternal, } from '../navigation-hardening.js';
|
|
4
7
|
let simulatorSessionConfigured = false;
|
|
5
|
-
function configureSimulatorSession() {
|
|
8
|
+
function configureSimulatorSession(simulatorPreloadPath) {
|
|
6
9
|
if (simulatorSessionConfigured)
|
|
7
10
|
return;
|
|
8
11
|
simulatorSessionConfigured = true;
|
|
9
12
|
const simulatorSession = session.fromPartition('persist:simulator');
|
|
13
|
+
// Inject the simulator preload on every frame in this session. This is the
|
|
14
|
+
// mini-program runtime entry point (exposes `wx`, page registers, etc.).
|
|
15
|
+
simulatorSession.registerPreloadScript({
|
|
16
|
+
type: 'frame',
|
|
17
|
+
filePath: simulatorPreloadPath,
|
|
18
|
+
});
|
|
10
19
|
simulatorSession.webRequest.onBeforeSendHeaders((details, callback) => {
|
|
11
20
|
const forcedReferer = getSimulatorServicewechatReferer();
|
|
12
21
|
if (forcedReferer) {
|
|
@@ -25,7 +34,7 @@ function configureSimulatorSession() {
|
|
|
25
34
|
});
|
|
26
35
|
}
|
|
27
36
|
export function createMainWindow(opts) {
|
|
28
|
-
configureSimulatorSession();
|
|
37
|
+
configureSimulatorSession(opts.simulatorPreloadPath);
|
|
29
38
|
const mainWindow = new BrowserWindow({
|
|
30
39
|
width: opts.width ?? 1280,
|
|
31
40
|
height: opts.height ?? 980,
|
|
@@ -35,8 +44,13 @@ export function createMainWindow(opts) {
|
|
|
35
44
|
show: false,
|
|
36
45
|
backgroundColor: themeBg(),
|
|
37
46
|
webPreferences: {
|
|
38
|
-
nodeIntegration:
|
|
39
|
-
contextIsolation:
|
|
47
|
+
nodeIntegration: false,
|
|
48
|
+
contextIsolation: true,
|
|
49
|
+
// `sandbox: false` is required so the preload bundle can `require('electron')`
|
|
50
|
+
// to access contextBridge/ipcRenderer. The renderer itself runs isolated.
|
|
51
|
+
sandbox: false,
|
|
52
|
+
preload: mainPreloadPath,
|
|
53
|
+
// The simulator <webview> still relies on the webview tag; keep it enabled.
|
|
40
54
|
webviewTag: true,
|
|
41
55
|
},
|
|
42
56
|
});
|
|
@@ -51,17 +65,59 @@ export function createMainWindow(opts) {
|
|
|
51
65
|
}
|
|
52
66
|
}
|
|
53
67
|
});
|
|
54
|
-
|
|
68
|
+
const rendererDir = path.dirname(opts.indexHtml);
|
|
69
|
+
// Restrict the main renderer to file:// URLs under the renderer bundle and
|
|
70
|
+
// route every popup through the OS browser. See navigation-hardening.ts.
|
|
71
|
+
applyNavigationHardening(mainWindow.webContents, rendererDir);
|
|
72
|
+
mainWindow.webContents.on('will-attach-webview', (event, webPreferences, params) => {
|
|
73
|
+
// Pin the simulator <webview> onto `persist:simulator` so it shares storage
|
|
74
|
+
// with the rest of the session and picks up the session-registered preload.
|
|
75
|
+
// Empirically (Electron 41) `webPreferences.partition` is the field the
|
|
76
|
+
// guest session actually consults; `params.partition` alone is a no-op,
|
|
77
|
+
// so we write both belt-and-braces.
|
|
78
|
+
;
|
|
79
|
+
webPreferences.partition = 'persist:simulator';
|
|
80
|
+
params.partition = 'persist:simulator';
|
|
81
|
+
// The simulator <webview> intentionally runs with `contextIsolation: false`
|
|
82
|
+
// because the dimina runtime and the user-authored preload scripts share
|
|
83
|
+
// the same JavaScript realm as the page — the mini-program SDK reaches
|
|
84
|
+
// into the page's globals (e.g. AudioContext, custom registers) and
|
|
85
|
+
// attaches helpers via window-level injection. Flipping isolation on
|
|
86
|
+
// would break that contract.
|
|
55
87
|
webPreferences.contextIsolation = false;
|
|
88
|
+
void event;
|
|
89
|
+
});
|
|
90
|
+
// The simulator webview's webContents only exists once attached; wire its
|
|
91
|
+
// window-open / will-navigate handlers here so popups + redirects don't
|
|
92
|
+
// pivot to file:// or arbitrary origins.
|
|
93
|
+
mainWindow.webContents.on('did-attach-webview', (_event, webviewWebContents) => {
|
|
94
|
+
webviewWebContents.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
|
|
95
|
+
webviewWebContents.on('will-navigate', (e, url) => {
|
|
96
|
+
try {
|
|
97
|
+
const u = new URL(url);
|
|
98
|
+
// Allow the simulator's own host (localhost dev server) plus the
|
|
99
|
+
// initial about:blank that Chromium uses before src loads.
|
|
100
|
+
if (u.protocol === 'about:')
|
|
101
|
+
return;
|
|
102
|
+
if ((u.protocol === 'http:' || u.protocol === 'https:') &&
|
|
103
|
+
(u.hostname === 'localhost' || u.hostname === '127.0.0.1')) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
if (u.protocol === 'http:' || u.protocol === 'https:') {
|
|
108
|
+
void shell.openExternal(url);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
56
115
|
});
|
|
57
116
|
mainWindow.loadFile(opts.indexHtml);
|
|
58
117
|
const container = new View();
|
|
59
118
|
const mainWebView = mainWindow.contentView;
|
|
60
119
|
container.addChildView(mainWebView);
|
|
61
120
|
mainWindow.contentView = container;
|
|
62
|
-
globalShortcut.register('CommandOrControl+Shift+I', () => {
|
|
63
|
-
mainWindow.webContents.openDevTools({ mode: 'detach' });
|
|
64
|
-
});
|
|
65
121
|
return mainWindow;
|
|
66
122
|
}
|
|
67
123
|
//# sourceMappingURL=create.js.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { BrowserWindow } from 'electron';
|
|
2
2
|
import type { WorkbenchContext } from '../../services/workbench-context.js';
|
|
3
|
+
import { type Disposable } from '../../utils/disposable.js';
|
|
3
4
|
export interface MainWindowEventState {
|
|
4
5
|
context?: WorkbenchContext;
|
|
5
6
|
onResize?: () => void;
|
|
6
7
|
onClose?: (event: Electron.Event) => void | Promise<void>;
|
|
7
8
|
}
|
|
8
|
-
export declare function wireMainWindowEvents(win: BrowserWindow, state?: MainWindowEventState):
|
|
9
|
+
export declare function wireMainWindowEvents(win: BrowserWindow, state?: MainWindowEventState): Disposable;
|
|
9
10
|
//# sourceMappingURL=events.d.ts.map
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { globalShortcut } from 'electron';
|
|
2
|
+
import { DisposableRegistry, toDisposable } from '../../utils/disposable.js';
|
|
1
3
|
export function wireMainWindowEvents(win, state = {}) {
|
|
4
|
+
const registry = new DisposableRegistry();
|
|
2
5
|
const mainWebView = win.contentView.children[0];
|
|
3
6
|
const resizeMainWebView = () => {
|
|
4
7
|
if (mainWebView) {
|
|
@@ -9,10 +12,30 @@ export function wireMainWindowEvents(win, state = {}) {
|
|
|
9
12
|
};
|
|
10
13
|
resizeMainWebView();
|
|
11
14
|
win.on('resize', resizeMainWebView);
|
|
15
|
+
registry.add(toDisposable(() => {
|
|
16
|
+
win.removeListener('resize', resizeMainWebView);
|
|
17
|
+
}));
|
|
12
18
|
if (state.onClose) {
|
|
13
|
-
|
|
19
|
+
const onCloseHandler = (event) => {
|
|
14
20
|
void state.onClose?.(event);
|
|
15
|
-
}
|
|
21
|
+
};
|
|
22
|
+
win.on('close', onCloseHandler);
|
|
23
|
+
registry.add(toDisposable(() => {
|
|
24
|
+
win.removeListener('close', onCloseHandler);
|
|
25
|
+
}));
|
|
16
26
|
}
|
|
27
|
+
// DevTools shortcut: scoped to this main window so its disposal is tied
|
|
28
|
+
// to ctx.registry. `unregisterAll()` in lifecycle remains as a process-exit
|
|
29
|
+
// safety net.
|
|
30
|
+
const devToolsAccelerator = 'CommandOrControl+Shift+I';
|
|
31
|
+
const registered = globalShortcut.register(devToolsAccelerator, () => {
|
|
32
|
+
win.webContents.openDevTools({ mode: 'detach' });
|
|
33
|
+
});
|
|
34
|
+
if (registered) {
|
|
35
|
+
registry.add(toDisposable(() => {
|
|
36
|
+
globalShortcut.unregister(devToolsAccelerator);
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
return registry;
|
|
17
40
|
}
|
|
18
41
|
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { WebContents } from 'electron';
|
|
2
|
+
/**
|
|
3
|
+
* Navigation hardening for every WebContents created by the host application
|
|
4
|
+
* (main window renderer, settings overlay, popover overlay, standalone
|
|
5
|
+
* settings window). These WebContents load the contextBridge-exposing main
|
|
6
|
+
* preload, so an unintended navigation out of the renderer bundle would
|
|
7
|
+
* expose `window.devtools.ipc` to a foreign origin.
|
|
8
|
+
*
|
|
9
|
+
* `applyNavigationHardening` installs:
|
|
10
|
+
* - `setWindowOpenHandler`: deny popups; route http(s) through the OS browser.
|
|
11
|
+
* - `will-navigate`: cancel any in-place navigation outside `rendererDir`;
|
|
12
|
+
* route http(s) through the OS browser.
|
|
13
|
+
*/
|
|
14
|
+
/** Default popup handler: open http(s) externally, deny everything else. */
|
|
15
|
+
export declare function handleWindowOpenExternal(url: string): {
|
|
16
|
+
action: 'deny';
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Install `setWindowOpenHandler` + `will-navigate` on `wc` so it can only
|
|
20
|
+
* navigate to `file://` URLs under `rendererDir`. Safe to call once per
|
|
21
|
+
* WebContents.
|
|
22
|
+
*/
|
|
23
|
+
export declare function applyNavigationHardening(wc: WebContents, rendererDir: string): void;
|
|
24
|
+
//# sourceMappingURL=navigation-hardening.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { shell } from 'electron';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { pathToFileURL } from 'url';
|
|
4
|
+
/**
|
|
5
|
+
* Navigation hardening for every WebContents created by the host application
|
|
6
|
+
* (main window renderer, settings overlay, popover overlay, standalone
|
|
7
|
+
* settings window). These WebContents load the contextBridge-exposing main
|
|
8
|
+
* preload, so an unintended navigation out of the renderer bundle would
|
|
9
|
+
* expose `window.devtools.ipc` to a foreign origin.
|
|
10
|
+
*
|
|
11
|
+
* `applyNavigationHardening` installs:
|
|
12
|
+
* - `setWindowOpenHandler`: deny popups; route http(s) through the OS browser.
|
|
13
|
+
* - `will-navigate`: cancel any in-place navigation outside `rendererDir`;
|
|
14
|
+
* route http(s) through the OS browser.
|
|
15
|
+
*/
|
|
16
|
+
/** Default popup handler: open http(s) externally, deny everything else. */
|
|
17
|
+
export function handleWindowOpenExternal(url) {
|
|
18
|
+
try {
|
|
19
|
+
const u = new URL(url);
|
|
20
|
+
if (u.protocol === 'http:' || u.protocol === 'https:') {
|
|
21
|
+
void shell.openExternal(url);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
/* malformed URL — fall through to deny */
|
|
26
|
+
}
|
|
27
|
+
return { action: 'deny' };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Install `setWindowOpenHandler` + `will-navigate` on `wc` so it can only
|
|
31
|
+
* navigate to `file://` URLs under `rendererDir`. Safe to call once per
|
|
32
|
+
* WebContents.
|
|
33
|
+
*/
|
|
34
|
+
export function applyNavigationHardening(wc, rendererDir) {
|
|
35
|
+
const rendererPrefix = pathToFileURL(rendererDir + path.sep).href;
|
|
36
|
+
wc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
|
|
37
|
+
wc.on('will-navigate', (event, url) => {
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = new URL(url);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (parsed.protocol === 'file:' && parsed.href.startsWith(rendererPrefix)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
|
|
51
|
+
void shell.openExternal(url);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=navigation-hardening.js.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { BrowserWindow } from 'electron';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { mainPreloadPath } from '../../utils/paths.js';
|
|
3
4
|
import { themeBg } from '../../utils/theme.js';
|
|
5
|
+
import { applyNavigationHardening } from '../navigation-hardening.js';
|
|
4
6
|
export async function createSettingsWindow(parent, rendererDir) {
|
|
5
7
|
const win = new BrowserWindow({
|
|
6
8
|
width: 420,
|
|
@@ -10,8 +12,16 @@ export async function createSettingsWindow(parent, rendererDir) {
|
|
|
10
12
|
parent: parent ?? undefined,
|
|
11
13
|
title: '开发工具设置',
|
|
12
14
|
backgroundColor: themeBg(),
|
|
13
|
-
webPreferences: {
|
|
15
|
+
webPreferences: {
|
|
16
|
+
nodeIntegration: false,
|
|
17
|
+
contextIsolation: true,
|
|
18
|
+
sandbox: false,
|
|
19
|
+
preload: mainPreloadPath,
|
|
20
|
+
},
|
|
14
21
|
});
|
|
22
|
+
// Standalone settings window loads mainPreloadPath, so the same
|
|
23
|
+
// navigation rules as the main window apply — see navigation-hardening.ts.
|
|
24
|
+
applyNavigationHardening(win.webContents, rendererDir);
|
|
15
25
|
await win.loadFile(path.join(rendererDir, 'entries/workbench-settings/index.html'));
|
|
16
26
|
return win;
|
|
17
27
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public preload API surface for `@dimina-kit/devtools/preload`.
|
|
3
|
+
*
|
|
4
|
+
* External consumers compose their own simulator <webview> preload by
|
|
5
|
+
* importing the `install*` helpers from here. The built-in simulator
|
|
6
|
+
* preload at `windows/simulator.ts` is one such composition.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: do NOT add main-window preload concerns (contextBridge / ipc
|
|
9
|
+
* exposure) to this file — that lives in `windows/main.ts` and is
|
|
10
|
+
* bundled by esbuild, never re-exported through this barrel.
|
|
11
|
+
*/
|
|
12
|
+
export { installConsoleInstrumentation } from './instrumentation/console.js';
|
|
13
|
+
export { installAppDataInstrumentation, sendAllAppData } from './instrumentation/app-data.js';
|
|
14
|
+
export { installWxmlInstrumentation, sendWxmlTree, setupWxmlObserver } from './instrumentation/wxml.js';
|
|
15
|
+
export { installSimulatorBridge } from './runtime/bridge.js';
|
|
16
|
+
export { setupApiCompatHook } from './shared/api-compat.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public preload API surface for `@dimina-kit/devtools/preload`.
|
|
3
|
+
*
|
|
4
|
+
* External consumers compose their own simulator <webview> preload by
|
|
5
|
+
* importing the `install*` helpers from here. The built-in simulator
|
|
6
|
+
* preload at `windows/simulator.ts` is one such composition.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: do NOT add main-window preload concerns (contextBridge / ipc
|
|
9
|
+
* exposure) to this file — that lives in `windows/main.ts` and is
|
|
10
|
+
* bundled by esbuild, never re-exported through this barrel.
|
|
11
|
+
*/
|
|
12
|
+
export { installConsoleInstrumentation } from './instrumentation/console.js';
|
|
13
|
+
export { installAppDataInstrumentation, sendAllAppData } from './instrumentation/app-data.js';
|
|
14
|
+
export { installWxmlInstrumentation, sendWxmlTree, setupWxmlObserver } from './instrumentation/wxml.js';
|
|
15
|
+
export { installSimulatorBridge } from './runtime/bridge.js';
|
|
16
|
+
export { setupApiCompatHook } from './shared/api-compat.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -1,15 +1,69 @@
|
|
|
1
1
|
import { SimulatorChannel, BridgeChannel } from '../../shared/ipc-channels.js';
|
|
2
|
-
import { sendToHost } from '../runtime/host.js';
|
|
3
|
-
import {
|
|
2
|
+
import { onHostMessage, sendToHost } from '../runtime/host.js';
|
|
3
|
+
import { clearAppDataSnapshot, setAppDataSnapshot, } from '../runtime/bridge.js';
|
|
4
4
|
import { createDisposableSet } from './disposable.js';
|
|
5
5
|
const appDataCache = new Map();
|
|
6
|
+
// Bridges in insertion order — drives the `bridges` array in snapshots so
|
|
7
|
+
// renderer tabs render in a stable, chronological sequence.
|
|
8
|
+
const bridgeOrder = [];
|
|
9
|
+
// Page path per bridge: set from `page_*` init's body.path. Component init
|
|
10
|
+
// also carries a path but it's the component's own path, not the page route.
|
|
11
|
+
const bridgePagePath = new Map();
|
|
12
|
+
function recordBridge(bridgeId) {
|
|
13
|
+
if (!bridgeOrder.includes(bridgeId))
|
|
14
|
+
bridgeOrder.push(bridgeId);
|
|
15
|
+
}
|
|
16
|
+
function buildSnapshot() {
|
|
17
|
+
const bridges = [];
|
|
18
|
+
for (const id of bridgeOrder) {
|
|
19
|
+
bridges.push({ id, pagePath: bridgePagePath.get(id) ?? null });
|
|
20
|
+
}
|
|
21
|
+
const entries = {};
|
|
22
|
+
for (const [key, entry] of appDataCache) {
|
|
23
|
+
const slash = key.indexOf('/');
|
|
24
|
+
if (slash < 0)
|
|
25
|
+
continue;
|
|
26
|
+
const bridgeId = key.slice(0, slash);
|
|
27
|
+
const moduleId = key.slice(slash + 1);
|
|
28
|
+
if (!entries[bridgeId])
|
|
29
|
+
entries[bridgeId] = {};
|
|
30
|
+
const displayKey = entry.componentPath ?? moduleId;
|
|
31
|
+
entries[bridgeId][displayKey] = entry.data;
|
|
32
|
+
}
|
|
33
|
+
return { bridges, entries };
|
|
34
|
+
}
|
|
6
35
|
function publishSnapshot() {
|
|
7
36
|
const data = {};
|
|
8
|
-
for (const [key,
|
|
9
|
-
data[key] =
|
|
37
|
+
for (const [key, entry] of appDataCache) {
|
|
38
|
+
data[key] = entry.data;
|
|
10
39
|
}
|
|
11
40
|
setAppDataSnapshot(data);
|
|
12
41
|
}
|
|
42
|
+
function clearBridge(bridgeId) {
|
|
43
|
+
const prefix = `${bridgeId}/`;
|
|
44
|
+
for (const key of [...appDataCache.keys()]) {
|
|
45
|
+
if (key.startsWith(prefix))
|
|
46
|
+
appDataCache.delete(key);
|
|
47
|
+
}
|
|
48
|
+
const idx = bridgeOrder.indexOf(bridgeId);
|
|
49
|
+
if (idx >= 0)
|
|
50
|
+
bridgeOrder.splice(idx, 1);
|
|
51
|
+
bridgePagePath.delete(bridgeId);
|
|
52
|
+
publishSnapshot();
|
|
53
|
+
// Push a refreshed snapshot so the renderer can drop the evicted bridge
|
|
54
|
+
// immediately, without waiting for the user to hit the refresh button.
|
|
55
|
+
sendToHost(SimulatorChannel.AppDataAll, buildSnapshot());
|
|
56
|
+
}
|
|
57
|
+
// dimina runtime emits two kinds of service→main messages relevant to the
|
|
58
|
+
// AppData panel:
|
|
59
|
+
// • update batches `{type:'ub', body:{bridgeId, updates:[{moduleId,data}]}}`
|
|
60
|
+
// where each `data` is a partial setData patch.
|
|
61
|
+
// • instance init: type === instance id (prefixed `page_`/`component_`),
|
|
62
|
+
// body `{bridgeId, path, data}` with the COMPLETE initial state.
|
|
63
|
+
//
|
|
64
|
+
// Policy: surface PAGE entries only. Component entries are dropped — they
|
|
65
|
+
// sometimes flow on a bridge id distinct from their owning page's and never
|
|
66
|
+
// receive pageUnload, which would manifest as ghost tabs in the panel.
|
|
13
67
|
function decodeWorkerMessage(message) {
|
|
14
68
|
let payload = message;
|
|
15
69
|
if (typeof payload === 'string') {
|
|
@@ -20,10 +74,73 @@ function decodeWorkerMessage(message) {
|
|
|
20
74
|
return null;
|
|
21
75
|
}
|
|
22
76
|
}
|
|
77
|
+
if (!payload || typeof payload !== 'object')
|
|
78
|
+
return null;
|
|
23
79
|
const record = payload;
|
|
24
|
-
if (record
|
|
80
|
+
if (record.type === 'ub') {
|
|
81
|
+
const body = record.body;
|
|
82
|
+
if (!body || typeof body !== 'object')
|
|
83
|
+
return null;
|
|
84
|
+
if (typeof body.bridgeId !== 'string' || !Array.isArray(body.updates))
|
|
85
|
+
return null;
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const u of body.updates) {
|
|
88
|
+
if (!u || typeof u.moduleId !== 'string')
|
|
89
|
+
continue;
|
|
90
|
+
if (!u.moduleId.startsWith('page_'))
|
|
91
|
+
continue;
|
|
92
|
+
out.push({ mode: 'patch', bridgeId: body.bridgeId, moduleId: u.moduleId, data: u.data });
|
|
93
|
+
}
|
|
94
|
+
return out.length > 0 ? out : null;
|
|
95
|
+
}
|
|
96
|
+
if (typeof record.type === 'string' && record.type.startsWith('page_')) {
|
|
97
|
+
const body = record.body;
|
|
98
|
+
if (!body || typeof body !== 'object')
|
|
99
|
+
return null;
|
|
100
|
+
if (typeof body.bridgeId !== 'string' || typeof body.path !== 'string')
|
|
101
|
+
return null;
|
|
102
|
+
if (!body.data || typeof body.data !== 'object')
|
|
103
|
+
return null;
|
|
104
|
+
return [{
|
|
105
|
+
mode: 'init',
|
|
106
|
+
bridgeId: body.bridgeId,
|
|
107
|
+
moduleId: record.type,
|
|
108
|
+
componentPath: body.path,
|
|
109
|
+
data: body.data,
|
|
110
|
+
}];
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
// main→worker direction: container signals page teardown so cache can evict.
|
|
115
|
+
function decodeOutgoingMessage(message) {
|
|
116
|
+
let payload = message;
|
|
117
|
+
if (typeof payload === 'string') {
|
|
118
|
+
try {
|
|
119
|
+
payload = JSON.parse(payload);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!payload || typeof payload !== 'object')
|
|
126
|
+
return null;
|
|
127
|
+
const r = payload;
|
|
128
|
+
if (typeof r.type !== 'string')
|
|
25
129
|
return null;
|
|
26
|
-
return
|
|
130
|
+
return {
|
|
131
|
+
type: r.type,
|
|
132
|
+
bridgeId: typeof r.body?.bridgeId === 'string' ? r.body.bridgeId : undefined,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function instrumentPostMessage(worker) {
|
|
136
|
+
const orig = worker.postMessage.bind(worker);
|
|
137
|
+
worker.postMessage = function (msg, ...rest) {
|
|
138
|
+
const decoded = decodeOutgoingMessage(msg);
|
|
139
|
+
if (decoded?.type === 'pageUnload' && decoded.bridgeId) {
|
|
140
|
+
clearBridge(decoded.bridgeId);
|
|
141
|
+
}
|
|
142
|
+
return orig(msg, ...rest);
|
|
143
|
+
};
|
|
27
144
|
}
|
|
28
145
|
function createInstrumentedWorker(OriginalWorker) {
|
|
29
146
|
function InstrumentedWorker(scriptURL, options) {
|
|
@@ -31,15 +148,22 @@ function createInstrumentedWorker(OriginalWorker) {
|
|
|
31
148
|
? scriptURL
|
|
32
149
|
: new URL(scriptURL, window.location.href);
|
|
33
150
|
const worker = Reflect.construct(OriginalWorker, [resolvedScriptURL, options], new.target ?? InstrumentedWorker);
|
|
151
|
+
instrumentPostMessage(worker);
|
|
34
152
|
worker.addEventListener('message', (event) => {
|
|
35
|
-
const
|
|
36
|
-
if (!
|
|
153
|
+
const entries = decodeWorkerMessage(event.data);
|
|
154
|
+
if (!entries)
|
|
37
155
|
return;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
const hookBody = {
|
|
158
|
+
bridgeId: entry.bridgeId,
|
|
159
|
+
moduleId: entry.moduleId,
|
|
160
|
+
data: entry.data,
|
|
161
|
+
mode: entry.mode,
|
|
162
|
+
};
|
|
163
|
+
if (entry.mode === 'init')
|
|
164
|
+
hookBody.componentPath = entry.componentPath;
|
|
165
|
+
window.__simulatorHook?.appData(hookBody);
|
|
166
|
+
}
|
|
43
167
|
});
|
|
44
168
|
return worker;
|
|
45
169
|
}
|
|
@@ -57,12 +181,44 @@ export function installAppDataInstrumentation() {
|
|
|
57
181
|
window.__simulatorHook = {
|
|
58
182
|
appData: (body) => {
|
|
59
183
|
const record = body;
|
|
60
|
-
if (record?.bridgeId
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
184
|
+
if (!record?.bridgeId || !record?.moduleId)
|
|
185
|
+
return;
|
|
186
|
+
// R6: drop ub patches whose bridge has never been initialised. dimina
|
|
187
|
+
// dispatches pageUnload to the service worker, which fires onUnload —
|
|
188
|
+
// and onUnload's setData (e.g. console-test's stopTimer) produces a
|
|
189
|
+
// late `ub` that arrives AFTER our clearBridge. Without this gate the
|
|
190
|
+
// late patch would resurrect the unloaded bridge as a ghost tab.
|
|
191
|
+
if (record.mode !== 'init' && !bridgePagePath.has(record.bridgeId))
|
|
192
|
+
return;
|
|
193
|
+
const key = `${record.bridgeId}/${record.moduleId}`;
|
|
194
|
+
const prev = appDataCache.get(key);
|
|
195
|
+
const incoming = (record.data && typeof record.data === 'object'
|
|
196
|
+
? record.data
|
|
197
|
+
: {});
|
|
198
|
+
// init = full-state replace; patch = merge into previous (setData semantics)
|
|
199
|
+
const merged = record.mode === 'init'
|
|
200
|
+
? { ...incoming }
|
|
201
|
+
: { ...(prev?.data ?? {}), ...incoming };
|
|
202
|
+
const componentPath = record.componentPath ?? prev?.componentPath;
|
|
203
|
+
const next = componentPath !== undefined
|
|
204
|
+
? { componentPath, data: merged }
|
|
205
|
+
: { data: merged };
|
|
206
|
+
appDataCache.set(key, next);
|
|
207
|
+
recordBridge(record.bridgeId);
|
|
208
|
+
if (record.mode === 'init'
|
|
209
|
+
&& record.moduleId.startsWith('page_')
|
|
210
|
+
&& record.componentPath) {
|
|
211
|
+
bridgePagePath.set(record.bridgeId, record.componentPath);
|
|
64
212
|
}
|
|
65
|
-
|
|
213
|
+
publishSnapshot();
|
|
214
|
+
const payload = {
|
|
215
|
+
bridgeId: record.bridgeId,
|
|
216
|
+
moduleId: record.moduleId,
|
|
217
|
+
data: merged,
|
|
218
|
+
};
|
|
219
|
+
if (componentPath !== undefined)
|
|
220
|
+
payload.componentPath = componentPath;
|
|
221
|
+
sendToHost(SimulatorChannel.AppData, payload);
|
|
66
222
|
},
|
|
67
223
|
};
|
|
68
224
|
Object.defineProperty(window, 'Worker', {
|
|
@@ -70,6 +226,7 @@ export function installAppDataInstrumentation() {
|
|
|
70
226
|
writable: true,
|
|
71
227
|
value: createInstrumentedWorker(OriginalWorker),
|
|
72
228
|
});
|
|
229
|
+
disposables.add(onHostMessage(BridgeChannel.AppDataGetAllRequest, () => sendAllAppData()));
|
|
73
230
|
publishSnapshot();
|
|
74
231
|
disposables.add(() => {
|
|
75
232
|
if (originalDescriptor) {
|
|
@@ -89,6 +246,8 @@ export function installAppDataInstrumentation() {
|
|
|
89
246
|
});
|
|
90
247
|
disposables.add(() => {
|
|
91
248
|
appDataCache.clear();
|
|
249
|
+
bridgeOrder.length = 0;
|
|
250
|
+
bridgePagePath.clear();
|
|
92
251
|
clearAppDataSnapshot();
|
|
93
252
|
publishSnapshot();
|
|
94
253
|
});
|
|
@@ -98,6 +257,6 @@ export function installAppDataInstrumentation() {
|
|
|
98
257
|
}
|
|
99
258
|
export function sendAllAppData() {
|
|
100
259
|
publishSnapshot();
|
|
101
|
-
sendToHost(
|
|
260
|
+
sendToHost(SimulatorChannel.AppDataAll, buildSnapshot());
|
|
102
261
|
}
|
|
103
262
|
//# sourceMappingURL=app-data.js.map
|