@dimina-kit/devtools 0.3.1 → 0.3.2-dev.20260522085907
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 +297 -133
- 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 +84 -36
- package/dist/main/app/bootstrap.js +2 -1
- package/dist/main/app/launch.d.ts +2 -2
- package/dist/main/app/launch.js +10 -8
- package/dist/main/index.bundle.js +1567 -477
- 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 +16 -13
- package/dist/main/ipc/popover.d.ts +4 -1
- package/dist/main/ipc/popover.js +15 -5
- package/dist/main/ipc/projects.d.ts +6 -1
- package/dist/main/ipc/projects.js +127 -6
- package/dist/main/ipc/session.d.ts +4 -1
- package/dist/main/ipc/session.js +34 -6
- package/dist/main/ipc/settings.d.ts +4 -1
- package/dist/main/ipc/settings.js +51 -23
- 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 +23 -18
- package/dist/main/ipc/toolbar.d.ts +2 -1
- package/dist/main/ipc/toolbar.js +3 -2
- package/dist/main/menu/index.js +11 -0
- package/dist/main/services/automation/exec.d.ts +2 -0
- package/dist/main/services/automation/exec.js +7 -9
- package/dist/main/services/automation/handlers/app.js +37 -51
- package/dist/main/services/automation/handlers/element.js +27 -41
- package/dist/main/services/automation/index.js +88 -13
- package/dist/main/services/default-adapter.js +3 -3
- package/dist/main/services/mcp/index.d.ts +1 -0
- package/dist/main/services/mcp/index.js +1 -0
- package/dist/main/services/mcp/server.d.ts +2 -1
- package/dist/main/services/mcp/server.js +10 -0
- package/dist/main/services/mcp/status.d.ts +10 -0
- package/dist/main/services/mcp/status.js +14 -0
- package/dist/main/services/mcp/target-manager.js +2 -1
- 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 +5 -3
- package/dist/main/services/notifications/renderer-notifier.js +3 -6
- package/dist/main/services/projects/builtin-templates.d.ts +3 -0
- package/dist/main/services/projects/builtin-templates.js +24 -0
- package/dist/main/services/projects/create-project-service.d.ts +7 -0
- package/dist/main/services/projects/create-project-service.js +84 -0
- package/dist/main/services/projects/index.d.ts +7 -0
- package/dist/main/services/projects/index.js +9 -0
- package/dist/main/services/projects/local-provider.d.ts +3 -0
- package/dist/main/services/projects/local-provider.js +25 -0
- package/dist/main/services/projects/project-repository.d.ts +1 -1
- package/dist/main/services/projects/project-repository.js +3 -0
- package/dist/main/services/projects/templates.d.ts +21 -0
- package/dist/main/services/projects/templates.js +39 -0
- package/dist/main/services/projects/thumbnail.d.ts +8 -0
- package/dist/main/services/projects/thumbnail.js +37 -0
- package/dist/main/services/projects/types.d.ts +118 -0
- package/dist/main/services/projects/types.js +19 -0
- package/dist/main/services/settings/index.d.ts +11 -0
- package/dist/main/services/settings/index.js +18 -2
- 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-storage/index.d.ts +22 -1
- package/dist/main/services/simulator-storage/index.js +334 -97
- 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 +7 -7
- package/dist/main/services/views/view-manager.js +53 -29
- 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 +49 -2
- package/dist/main/services/workbench-context.js +15 -2
- package/dist/main/services/workspace/workspace-service.d.ts +9 -6
- package/dist/main/services/workspace/workspace-service.js +71 -11
- 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 +6 -0
- package/dist/main/utils/paths.js +6 -0
- package/dist/main/utils/sender-policy.d.ts +25 -0
- package/dist/main/utils/sender-policy.js +44 -0
- package/dist/main/utils/theme.d.ts +24 -0
- package/dist/main/utils/theme.js +38 -1
- package/dist/main/windows/main-window/create.d.ts +6 -0
- package/dist/main/windows/main-window/create.js +66 -10
- 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 +22 -0
- package/dist/preload/index.js +19 -0
- package/dist/preload/instrumentation/app-data.d.ts +32 -2
- package/dist/preload/instrumentation/app-data.js +253 -79
- package/dist/preload/instrumentation/index.d.ts +2 -2
- package/dist/preload/instrumentation/index.js +2 -2
- package/dist/preload/instrumentation/wxml.d.ts +16 -4
- package/dist/preload/instrumentation/wxml.js +188 -178
- package/dist/preload/miniapp-snapshot/host.d.ts +12 -0
- package/dist/preload/miniapp-snapshot/host.js +117 -0
- package/dist/preload/miniapp-snapshot/types.d.ts +62 -0
- package/dist/preload/miniapp-snapshot/types.js +15 -0
- package/dist/preload/runtime/bridge.js +5 -24
- package/dist/preload/runtime/custom-apis.d.ts +6 -0
- package/dist/preload/runtime/custom-apis.js +43 -0
- package/dist/preload/runtime/host.d.ts +1 -0
- package/dist/preload/runtime/host.js +13 -0
- package/dist/preload/shared/expose.d.ts +15 -0
- package/dist/preload/shared/expose.js +33 -0
- package/dist/preload/shared/page-iframe.d.ts +3 -0
- package/dist/preload/shared/page-iframe.js +6 -0
- package/dist/preload/shared/types.d.ts +2 -0
- package/dist/preload/windows/main.cjs +47 -0
- package/dist/preload/windows/main.cjs.map +7 -0
- package/dist/preload/windows/simulator.js +501 -272
- package/dist/renderer/assets/constants-DI382mTP.js +2 -0
- package/dist/renderer/assets/index-D8VFYBh-.js +46 -0
- package/dist/renderer/assets/{input-DxfYnzX_.js → input-CoWcfa8u.js} +2 -2
- package/dist/renderer/assets/ipc-transport-Cd2xPrsa.js +9 -0
- package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +1 -0
- package/dist/renderer/assets/popover-Nq5gT9DO.js +2 -0
- package/dist/renderer/assets/select-By5KH-UG.js +2 -0
- package/dist/renderer/assets/{settings-DteGraEU.js → settings-Dj_N70qR.js} +2 -2
- package/dist/renderer/assets/settings-api-Do0_Smq1.js +2 -0
- package/dist/renderer/assets/workbenchSettings-CazSLGYt.js +8 -0
- package/dist/renderer/entries/main/index.html +13 -5
- package/dist/renderer/entries/popover/index.html +8 -5
- package/dist/renderer/entries/settings/index.html +7 -5
- package/dist/renderer/entries/workbench-settings/index.html +7 -4
- package/dist/shared/ipc-channels.d.ts +46 -13
- package/dist/shared/ipc-channels.js +60 -14
- package/dist/shared/ipc-schemas.d.ts +118 -0
- package/dist/shared/ipc-schemas.js +133 -0
- package/dist/shared/simulator-route.d.ts +72 -0
- package/dist/shared/simulator-route.js +169 -0
- package/dist/shared/types.d.ts +82 -8
- package/dist/simulator/assets/simulator-DJCozrU5.js +9 -0
- package/dist/simulator/simulator.html +6 -43
- package/package.json +18 -29
- package/templates/blank/app.js +6 -0
- package/templates/blank/app.json +11 -0
- package/templates/blank/app.wxss +5 -0
- package/templates/blank/pages/index/index.js +6 -0
- package/templates/blank/pages/index/index.json +3 -0
- package/templates/blank/pages/index/index.wxml +3 -0
- package/templates/blank/pages/index/index.wxss +11 -0
- package/templates/blank/project.config.json +13 -0
- package/templates/blank/sitemap.json +9 -0
- package/templates/taro-todo/app.js +1 -0
- package/templates/taro-todo/app.json +1 -0
- package/templates/taro-todo/app.wxss +1 -0
- package/templates/taro-todo/base.wxml +851 -0
- package/templates/taro-todo/common.js +1 -0
- package/templates/taro-todo/comp.js +1 -0
- package/templates/taro-todo/comp.json +1 -0
- package/templates/taro-todo/comp.wxml +2 -0
- package/templates/taro-todo/custom-wrapper.js +1 -0
- package/templates/taro-todo/custom-wrapper.json +1 -0
- package/templates/taro-todo/custom-wrapper.wxml +4 -0
- package/templates/taro-todo/pages/index/index.js +2 -0
- package/templates/taro-todo/pages/index/index.json +1 -0
- package/templates/taro-todo/pages/index/index.wxml +2 -0
- package/templates/taro-todo/pages/index/index.wxss +1 -0
- package/templates/taro-todo/project.config.json +44 -0
- package/templates/taro-todo/runtime.js +1 -0
- package/templates/taro-todo/taro.js +2 -0
- package/templates/taro-todo/utils.wxs +39 -0
- package/templates/taro-todo/vendors.js +2 -0
- package/dist/preload/instrumentation/disposable.d.ts +0 -11
- package/dist/preload/instrumentation/disposable.js +0 -15
- package/dist/preload/windows/main.d.ts +0 -6
- package/dist/preload/windows/main.js +0 -6
- package/dist/renderer/assets/index-D1p6crtf.js +0 -46
- package/dist/renderer/assets/ipc-transport-ClIvDcYY.js +0 -9
- package/dist/renderer/assets/ipc-transport-EiWxu_hG.css +0 -1
- package/dist/renderer/assets/popover-CtXcBJtM.js +0 -2
- package/dist/renderer/assets/select-DMfp49f1.js +0 -2
- package/dist/renderer/assets/settings-api-CY84UMpN.js +0 -2
- package/dist/renderer/assets/workbenchSettings-DjELLnSJ.js +0 -8
- package/dist/simulator/assets/simulator-C1eHXnuq.js +0 -9
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { WebContents } from 'electron';
|
|
2
|
+
import { ipcMain } from 'electron';
|
|
3
|
+
import { type Disposable } from './disposable.js';
|
|
4
|
+
type HandleFn = Parameters<typeof ipcMain.handle>[1];
|
|
5
|
+
type ListenerFn = Parameters<typeof ipcMain.on>[1];
|
|
6
|
+
/**
|
|
7
|
+
* Trust predicate used to gate IPC delivery on the main process.
|
|
8
|
+
*
|
|
9
|
+
* Returns true when the calling WebContents is allowed to invoke / emit on
|
|
10
|
+
* the channel, false otherwise.
|
|
11
|
+
*/
|
|
12
|
+
export type SenderPolicy = (sender: WebContents) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Tiny fluent helper that wraps every `ipcMain.handle` / `ipcMain.on` with a
|
|
15
|
+
* matching removeHandler/removeListener registered into an internal registry.
|
|
16
|
+
*
|
|
17
|
+
* Each `register*Ipc(ctx)` returns one of these as a Disposable so the
|
|
18
|
+
* workbench-level registry can dispose all built-in handlers in one shot.
|
|
19
|
+
*
|
|
20
|
+
* If a {@link SenderPolicy} is provided, every incoming invocation is gated:
|
|
21
|
+
* - `handle`: rejected senders cause the invoke promise to reject with
|
|
22
|
+
* `Error('IPC sender rejected for channel <channel>')`.
|
|
23
|
+
* - `on`: rejected senders are silently dropped (the original listener is
|
|
24
|
+
* never called).
|
|
25
|
+
* In both cases a single `console.warn` is emitted with the channel name
|
|
26
|
+
* and a short sender summary. Without a policy the wrapper is a no-op,
|
|
27
|
+
* preserving backwards compatibility for unit tests and callers that
|
|
28
|
+
* opted out.
|
|
29
|
+
*/
|
|
30
|
+
export declare class IpcRegistry implements Disposable {
|
|
31
|
+
private policy?;
|
|
32
|
+
private registry;
|
|
33
|
+
constructor(policy?: SenderPolicy | undefined);
|
|
34
|
+
handle(channel: string, fn: HandleFn): this;
|
|
35
|
+
on(channel: string, fn: ListenerFn): this;
|
|
36
|
+
dispose(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=ipc-registry.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ipcMain } from 'electron';
|
|
2
|
+
import { DisposableRegistry } from './disposable.js';
|
|
3
|
+
import { IpcValidationError } from './ipc-schema.js';
|
|
4
|
+
import { createLogger } from './logger.js';
|
|
5
|
+
const log = createLogger('ipc');
|
|
6
|
+
/**
|
|
7
|
+
* Funnels errors thrown by an `on()` listener into the logger so they don't
|
|
8
|
+
* escape into Electron's event loop. Validation errors get a compact `warn`
|
|
9
|
+
* (channel + zod paths), other errors get a full `error` with stack.
|
|
10
|
+
*/
|
|
11
|
+
function reportListenerError(channel, err) {
|
|
12
|
+
if (err instanceof IpcValidationError) {
|
|
13
|
+
log.warn(`schema reject on '${channel}' at [${err.paths.join(', ')}]`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (err instanceof Error) {
|
|
17
|
+
log.error(`listener threw on '${channel}': ${err.message}`, err.stack);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
log.error(`listener threw on '${channel}'`, err);
|
|
21
|
+
}
|
|
22
|
+
function isThenable(v) {
|
|
23
|
+
return (v != null &&
|
|
24
|
+
(typeof v === 'object' || typeof v === 'function') &&
|
|
25
|
+
typeof v.then === 'function');
|
|
26
|
+
}
|
|
27
|
+
function summarizeSender(sender) {
|
|
28
|
+
if (sender.isDestroyed())
|
|
29
|
+
return '<destroyed>';
|
|
30
|
+
const url = sender.getURL();
|
|
31
|
+
return `id=${sender.id} ${url.slice(0, 120)}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Tiny fluent helper that wraps every `ipcMain.handle` / `ipcMain.on` with a
|
|
35
|
+
* matching removeHandler/removeListener registered into an internal registry.
|
|
36
|
+
*
|
|
37
|
+
* Each `register*Ipc(ctx)` returns one of these as a Disposable so the
|
|
38
|
+
* workbench-level registry can dispose all built-in handlers in one shot.
|
|
39
|
+
*
|
|
40
|
+
* If a {@link SenderPolicy} is provided, every incoming invocation is gated:
|
|
41
|
+
* - `handle`: rejected senders cause the invoke promise to reject with
|
|
42
|
+
* `Error('IPC sender rejected for channel <channel>')`.
|
|
43
|
+
* - `on`: rejected senders are silently dropped (the original listener is
|
|
44
|
+
* never called).
|
|
45
|
+
* In both cases a single `console.warn` is emitted with the channel name
|
|
46
|
+
* and a short sender summary. Without a policy the wrapper is a no-op,
|
|
47
|
+
* preserving backwards compatibility for unit tests and callers that
|
|
48
|
+
* opted out.
|
|
49
|
+
*/
|
|
50
|
+
export class IpcRegistry {
|
|
51
|
+
policy;
|
|
52
|
+
registry = new DisposableRegistry();
|
|
53
|
+
constructor(policy) {
|
|
54
|
+
this.policy = policy;
|
|
55
|
+
}
|
|
56
|
+
handle(channel, fn) {
|
|
57
|
+
const policy = this.policy;
|
|
58
|
+
const guarded = policy
|
|
59
|
+
? (event, ...args) => {
|
|
60
|
+
const sender = event.sender;
|
|
61
|
+
if (!policy(sender)) {
|
|
62
|
+
console.warn(`[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`);
|
|
63
|
+
throw new Error(`IPC sender rejected for channel ${channel}`);
|
|
64
|
+
}
|
|
65
|
+
return fn(event, ...args);
|
|
66
|
+
}
|
|
67
|
+
: fn;
|
|
68
|
+
ipcMain.handle(channel, guarded);
|
|
69
|
+
this.registry.add(() => ipcMain.removeHandler(channel));
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
on(channel, fn) {
|
|
73
|
+
const policy = this.policy;
|
|
74
|
+
const raw = fn;
|
|
75
|
+
const safeInvoke = (event, args) => {
|
|
76
|
+
try {
|
|
77
|
+
const ret = raw(event, ...args);
|
|
78
|
+
if (isThenable(ret)) {
|
|
79
|
+
// Async listeners would otherwise leak rejections into Electron's
|
|
80
|
+
// event loop as `UnhandledPromiseRejection`. Funnel them into the
|
|
81
|
+
// same logger path as sync throws.
|
|
82
|
+
Promise.resolve(ret).catch((err) => reportListenerError(channel, err));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
reportListenerError(channel, err);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const guarded = policy
|
|
90
|
+
? (event, ...args) => {
|
|
91
|
+
const sender = event.sender;
|
|
92
|
+
if (!policy(sender)) {
|
|
93
|
+
console.warn(`[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
safeInvoke(event, args);
|
|
97
|
+
}
|
|
98
|
+
: (event, ...args) => safeInvoke(event, args);
|
|
99
|
+
ipcMain.on(channel, guarded);
|
|
100
|
+
this.registry.add(() => {
|
|
101
|
+
ipcMain.removeListener(channel, guarded);
|
|
102
|
+
});
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
dispose() {
|
|
106
|
+
return this.registry.dispose();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=ipc-registry.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by {@link validate} when an IPC payload fails its zod schema.
|
|
4
|
+
*
|
|
5
|
+
* Carries the channel name plus a compact list of failing zod paths so
|
|
6
|
+
* the IpcRegistry wrapper can log a short, actionable summary without
|
|
7
|
+
* dumping the full payload.
|
|
8
|
+
*/
|
|
9
|
+
export declare class IpcValidationError extends Error {
|
|
10
|
+
readonly channel: string;
|
|
11
|
+
readonly paths: string[];
|
|
12
|
+
constructor(channel: string, issues: ReadonlyArray<{
|
|
13
|
+
path: ReadonlyArray<PropertyKey>;
|
|
14
|
+
}>);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse an IPC handler's argument tuple against a zod schema.
|
|
18
|
+
*
|
|
19
|
+
* On failure: warns to console with the channel name + issue list, then throws
|
|
20
|
+
* an {@link IpcValidationError} so that:
|
|
21
|
+
* - `ipcMain.handle` invocations reject the renderer's invoke promise
|
|
22
|
+
* without leaking schema internals.
|
|
23
|
+
* - `ipcMain.on` listeners (wrapped by IpcRegistry.on) can recognise the
|
|
24
|
+
* error type and log a structured warning instead of letting the throw
|
|
25
|
+
* escape into Electron's event loop.
|
|
26
|
+
*/
|
|
27
|
+
export declare function validate<T extends z.ZodTypeAny>(channel: string, schema: T, args: unknown[]): z.infer<T>;
|
|
28
|
+
//# sourceMappingURL=ipc-schema.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by {@link validate} when an IPC payload fails its zod schema.
|
|
3
|
+
*
|
|
4
|
+
* Carries the channel name plus a compact list of failing zod paths so
|
|
5
|
+
* the IpcRegistry wrapper can log a short, actionable summary without
|
|
6
|
+
* dumping the full payload.
|
|
7
|
+
*/
|
|
8
|
+
export class IpcValidationError extends Error {
|
|
9
|
+
channel;
|
|
10
|
+
paths;
|
|
11
|
+
constructor(channel, issues) {
|
|
12
|
+
const paths = issues.map((i) => i.path.map((p) => String(p)).join('.') || '<root>');
|
|
13
|
+
super(`Invalid IPC arguments for ${channel}`);
|
|
14
|
+
this.name = 'IpcValidationError';
|
|
15
|
+
this.channel = channel;
|
|
16
|
+
this.paths = paths;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse an IPC handler's argument tuple against a zod schema.
|
|
21
|
+
*
|
|
22
|
+
* On failure: warns to console with the channel name + issue list, then throws
|
|
23
|
+
* an {@link IpcValidationError} so that:
|
|
24
|
+
* - `ipcMain.handle` invocations reject the renderer's invoke promise
|
|
25
|
+
* without leaking schema internals.
|
|
26
|
+
* - `ipcMain.on` listeners (wrapped by IpcRegistry.on) can recognise the
|
|
27
|
+
* error type and log a structured warning instead of letting the throw
|
|
28
|
+
* escape into Electron's event loop.
|
|
29
|
+
*/
|
|
30
|
+
export function validate(channel, schema, args) {
|
|
31
|
+
const r = schema.safeParse(args);
|
|
32
|
+
if (!r.success) {
|
|
33
|
+
console.warn('[ipc] schema reject', channel, r.error.issues);
|
|
34
|
+
throw new IpcValidationError(channel, r.error.issues);
|
|
35
|
+
}
|
|
36
|
+
return r.data;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=ipc-schema.js.map
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export declare const devtoolsPackageRoot: string;
|
|
2
2
|
export declare const rendererDir: string;
|
|
3
3
|
export declare const defaultPreloadPath: string;
|
|
4
|
+
/**
|
|
5
|
+
* Preload bundle for the main window, settings window, and the settings /
|
|
6
|
+
* popover overlay WebContentsViews. Exposes `window.devtools.ipc` via
|
|
7
|
+
* contextBridge so the renderer never needs `window.require('electron')`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const mainPreloadPath: string;
|
|
4
10
|
export declare const simulatorDir: string;
|
|
5
11
|
export declare function getRendererDir(): string;
|
|
6
12
|
export declare function getPreloadDir(): string;
|
package/dist/main/utils/paths.js
CHANGED
|
@@ -25,6 +25,12 @@ function resolveDevtoolsPackageRoot() {
|
|
|
25
25
|
export const devtoolsPackageRoot = resolveDevtoolsPackageRoot();
|
|
26
26
|
export const rendererDir = path.join(devtoolsPackageRoot, 'dist/renderer');
|
|
27
27
|
export const defaultPreloadPath = path.join(devtoolsPackageRoot, 'dist/preload/windows/simulator.js');
|
|
28
|
+
/**
|
|
29
|
+
* Preload bundle for the main window, settings window, and the settings /
|
|
30
|
+
* popover overlay WebContentsViews. Exposes `window.devtools.ipc` via
|
|
31
|
+
* contextBridge so the renderer never needs `window.require('electron')`.
|
|
32
|
+
*/
|
|
33
|
+
export const mainPreloadPath = path.join(devtoolsPackageRoot, 'dist/preload/windows/main.cjs');
|
|
28
34
|
export const simulatorDir = path.join(devtoolsPackageRoot, 'dist/simulator');
|
|
29
35
|
export function getRendererDir() {
|
|
30
36
|
return rendererDir;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WorkbenchContext } from '../services/workbench-context.js';
|
|
2
|
+
import type { SenderPolicy } from './ipc-registry.js';
|
|
3
|
+
/**
|
|
4
|
+
* Default workbench sender white-list.
|
|
5
|
+
*
|
|
6
|
+
* An IPC message is accepted only when its `event.sender` matches one of
|
|
7
|
+
* the WebContents the main process itself created and trusts:
|
|
8
|
+
* - the main window's renderer
|
|
9
|
+
* - the optional workbench settings BrowserWindow's renderer (when open)
|
|
10
|
+
* - the settings overlay view (when open)
|
|
11
|
+
* - the popover overlay view (when open)
|
|
12
|
+
*
|
|
13
|
+
* The simulator webview is intentionally NOT on this list. Anything it
|
|
14
|
+
* needs from main (currently just the custom-apis bridge — see
|
|
15
|
+
* `installCustomApisBridge` in `src/preload/runtime/custom-apis.ts` and the
|
|
16
|
+
* matching `useCustomApiProxy` host hook) proxies through the trusted
|
|
17
|
+
* main-window renderer via `ipcRenderer.sendToHost` + `<webview>.send`.
|
|
18
|
+
* Keeping the guest off this list contains the blast radius if the
|
|
19
|
+
* simulator content is ever compromised.
|
|
20
|
+
*
|
|
21
|
+
* Any other sender — including a stale/destroyed sender or an unknown
|
|
22
|
+
* iframe — is rejected.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createWorkbenchSenderPolicy(ctx: Pick<WorkbenchContext, 'windows' | 'views'>): SenderPolicy;
|
|
25
|
+
//# sourceMappingURL=sender-policy.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
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. Anything it
|
|
12
|
+
* needs from main (currently just the custom-apis bridge — see
|
|
13
|
+
* `installCustomApisBridge` in `src/preload/runtime/custom-apis.ts` and the
|
|
14
|
+
* matching `useCustomApiProxy` host hook) proxies through the trusted
|
|
15
|
+
* main-window renderer via `ipcRenderer.sendToHost` + `<webview>.send`.
|
|
16
|
+
* Keeping the guest off this list contains the blast radius if the
|
|
17
|
+
* simulator content is ever compromised.
|
|
18
|
+
*
|
|
19
|
+
* Any other sender — including a stale/destroyed sender or an unknown
|
|
20
|
+
* iframe — is rejected.
|
|
21
|
+
*/
|
|
22
|
+
export function createWorkbenchSenderPolicy(ctx) {
|
|
23
|
+
return (sender) => {
|
|
24
|
+
if (sender.isDestroyed())
|
|
25
|
+
return false;
|
|
26
|
+
// Main window renderer
|
|
27
|
+
if (ctx.windows.isMainSender(sender.id))
|
|
28
|
+
return true;
|
|
29
|
+
// Standalone settings BrowserWindow renderer
|
|
30
|
+
if (ctx.windows.isSettingsWindowSender(sender.id))
|
|
31
|
+
return true;
|
|
32
|
+
// Settings overlay view (mounted inside the main window)
|
|
33
|
+
const settingsViewId = ctx.views.getSettingsWebContentsId();
|
|
34
|
+
if (settingsViewId != null && sender.id === settingsViewId)
|
|
35
|
+
return true;
|
|
36
|
+
// Popover overlay view
|
|
37
|
+
const popoverViewId = ctx.views.getPopoverWebContentsId();
|
|
38
|
+
if (popoverViewId != null && sender.id === popoverViewId)
|
|
39
|
+
return true;
|
|
40
|
+
// simulator <webview> proxies through main-window renderer (see file header).
|
|
41
|
+
return false;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=sender-policy.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Disposable } from './disposable.js';
|
|
1
2
|
/**
|
|
2
3
|
* Background color that matches the current system color scheme.
|
|
3
4
|
* Pass as `backgroundColor` when creating BrowserWindows to prevent
|
|
@@ -7,4 +8,27 @@
|
|
|
7
8
|
* Light: hsl(0 0% 98%) ≈ #fafafa (--color-bg)
|
|
8
9
|
*/
|
|
9
10
|
export declare function themeBg(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Install a single process-wide listener that keeps every window's native
|
|
13
|
+
* `backgroundColor` in sync with the active color scheme.
|
|
14
|
+
*
|
|
15
|
+
* A window's `backgroundColor` is otherwise frozen at the value passed to its
|
|
16
|
+
* constructor: after a theme switch the stale color bleeds through wherever
|
|
17
|
+
* native chrome and the WebContents meet. On Windows/Linux (in-window menu
|
|
18
|
+
* bar) it shows as a light hairline between the menu bar and the page; on
|
|
19
|
+
* macOS (global menu bar) it is latent — only a wrong-color flash on resize.
|
|
20
|
+
* The defect is platform-agnostic, so the fix is too: one `nativeTheme`
|
|
21
|
+
* `updated` listener re-syncs every current and future BrowserWindow.
|
|
22
|
+
*
|
|
23
|
+
* Known limitation: on Linux the `updated` event does not fire for OS-level
|
|
24
|
+
* system theme changes (electron/electron#25925) — only for explicit
|
|
25
|
+
* `nativeTheme.themeSource` assignments. In-app theme switches go through
|
|
26
|
+
* `applyTheme()` and are unaffected on every platform; the gap is limited to
|
|
27
|
+
* Linux + `theme: 'system'` + an OS theme change, an upstream Electron
|
|
28
|
+
* limitation that also leaves the renderer's `prefers-color-scheme` stale.
|
|
29
|
+
*
|
|
30
|
+
* Returns a Disposable that detaches the listener. Install it once during
|
|
31
|
+
* app setup and hand the Disposable to the workbench registry.
|
|
32
|
+
*/
|
|
33
|
+
export declare function installThemeBackgroundSync(): Disposable;
|
|
10
34
|
//# sourceMappingURL=theme.d.ts.map
|
package/dist/main/utils/theme.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { nativeTheme } from 'electron';
|
|
1
|
+
import { BrowserWindow, nativeTheme } from 'electron';
|
|
2
|
+
import { toDisposable } from './disposable.js';
|
|
2
3
|
/**
|
|
3
4
|
* Background color that matches the current system color scheme.
|
|
4
5
|
* Pass as `backgroundColor` when creating BrowserWindows to prevent
|
|
@@ -10,4 +11,40 @@ import { nativeTheme } from 'electron';
|
|
|
10
11
|
export function themeBg() {
|
|
11
12
|
return nativeTheme.shouldUseDarkColors ? '#1a1a1a' : '#fafafa';
|
|
12
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Install a single process-wide listener that keeps every window's native
|
|
16
|
+
* `backgroundColor` in sync with the active color scheme.
|
|
17
|
+
*
|
|
18
|
+
* A window's `backgroundColor` is otherwise frozen at the value passed to its
|
|
19
|
+
* constructor: after a theme switch the stale color bleeds through wherever
|
|
20
|
+
* native chrome and the WebContents meet. On Windows/Linux (in-window menu
|
|
21
|
+
* bar) it shows as a light hairline between the menu bar and the page; on
|
|
22
|
+
* macOS (global menu bar) it is latent — only a wrong-color flash on resize.
|
|
23
|
+
* The defect is platform-agnostic, so the fix is too: one `nativeTheme`
|
|
24
|
+
* `updated` listener re-syncs every current and future BrowserWindow.
|
|
25
|
+
*
|
|
26
|
+
* Known limitation: on Linux the `updated` event does not fire for OS-level
|
|
27
|
+
* system theme changes (electron/electron#25925) — only for explicit
|
|
28
|
+
* `nativeTheme.themeSource` assignments. In-app theme switches go through
|
|
29
|
+
* `applyTheme()` and are unaffected on every platform; the gap is limited to
|
|
30
|
+
* Linux + `theme: 'system'` + an OS theme change, an upstream Electron
|
|
31
|
+
* limitation that also leaves the renderer's `prefers-color-scheme` stale.
|
|
32
|
+
*
|
|
33
|
+
* Returns a Disposable that detaches the listener. Install it once during
|
|
34
|
+
* app setup and hand the Disposable to the workbench registry.
|
|
35
|
+
*/
|
|
36
|
+
export function installThemeBackgroundSync() {
|
|
37
|
+
const apply = () => {
|
|
38
|
+
const bg = themeBg();
|
|
39
|
+
for (const win of BrowserWindow.getAllWindows()) {
|
|
40
|
+
if (!win.isDestroyed()) {
|
|
41
|
+
win.setBackgroundColor(bg);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
nativeTheme.on('updated', apply);
|
|
46
|
+
return toDisposable(() => {
|
|
47
|
+
nativeTheme.removeListener('updated', apply);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
13
50
|
//# sourceMappingURL=theme.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) {
|
|
@@ -20,12 +29,12 @@ function configureSimulatorSession() {
|
|
|
20
29
|
headers['access-control-allow-headers'] = ['*'];
|
|
21
30
|
headers['access-control-allow-methods'] = ['*'];
|
|
22
31
|
headers['Cross-Origin-Opener-Policy'] = ['same-origin'];
|
|
23
|
-
headers['Cross-Origin-Embedder-Policy'] = ['
|
|
32
|
+
headers['Cross-Origin-Embedder-Policy'] = ['credentialless'];
|
|
24
33
|
callback({ responseHeaders: headers });
|
|
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
|