@dimina-kit/devtools 0.4.0-dev.20260828161241 → 0.4.0-dev.20260907055341
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 +52 -729
- package/dist/main/app/app.d.ts +27 -7
- package/dist/main/app/app.js +285 -681
- package/dist/main/app/editor-view.d.ts +21 -0
- package/dist/main/app/editor-view.js +71 -0
- package/dist/main/app/global-mirrors.d.ts +23 -0
- package/dist/main/app/global-mirrors.js +37 -0
- package/dist/main/app/host-sidebar-default.d.ts +19 -0
- package/dist/main/app/host-sidebar-default.js +44 -0
- package/dist/main/app/menu-setup.d.ts +13 -0
- package/dist/main/app/menu-setup.js +49 -0
- package/dist/main/app/open-project-ipc.d.ts +13 -0
- package/dist/main/app/open-project-ipc.js +21 -0
- package/dist/main/app/project-list-surface.d.ts +31 -0
- package/dist/main/app/project-list-surface.js +20 -0
- package/dist/main/app/project-window.d.ts +36 -0
- package/dist/main/app/project-window.js +93 -0
- package/dist/main/app/servers.d.ts +17 -0
- package/dist/main/app/servers.js +60 -0
- package/dist/main/app/ui-extension-targets.d.ts +42 -0
- package/dist/main/app/ui-extension-targets.js +101 -0
- package/dist/main/app/window-close-reveal.harness.d.ts +34 -0
- package/dist/main/app/window-close-reveal.harness.js +65 -0
- package/dist/main/app/window-events.d.ts +15 -0
- package/dist/main/app/window-events.js +45 -0
- package/dist/main/app/window-runtime-services.d.ts +41 -0
- package/dist/main/app/window-runtime-services.js +178 -0
- package/dist/main/app/workbench-modules.d.ts +24 -0
- package/dist/main/app/workbench-modules.js +51 -0
- package/dist/main/app/workbench-window.d.ts +85 -0
- package/dist/main/app/workbench-window.js +310 -0
- package/dist/main/index.bundle.js +10669 -9096
- package/dist/main/ipc/app.d.ts +4 -1
- package/dist/main/ipc/app.js +4 -3
- package/dist/main/ipc/device-picker.d.ts +16 -0
- package/dist/main/ipc/device-picker.js +27 -0
- package/dist/main/ipc/index.d.ts +1 -0
- package/dist/main/ipc/index.js +1 -0
- package/dist/main/ipc/internal-devtools.d.ts +2 -1
- package/dist/main/ipc/internal-devtools.js +4 -3
- package/dist/main/ipc/popover.d.ts +4 -1
- package/dist/main/ipc/popover.js +25 -9
- package/dist/main/ipc/project-create.d.ts +2 -1
- package/dist/main/ipc/project-create.js +6 -5
- package/dist/main/ipc/project-fs.d.ts +3 -1
- package/dist/main/ipc/project-fs.js +17 -17
- package/dist/main/ipc/projects.d.ts +3 -3
- package/dist/main/ipc/projects.js +13 -12
- package/dist/main/ipc/session.d.ts +4 -1
- package/dist/main/ipc/session.js +16 -11
- package/dist/main/ipc/settings.d.ts +4 -1
- package/dist/main/ipc/settings.js +12 -19
- package/dist/main/ipc/simulator-module.d.ts +5 -0
- package/dist/main/ipc/simulator-module.js +9 -5
- package/dist/main/ipc/simulator.d.ts +4 -1
- package/dist/main/ipc/simulator.js +12 -18
- package/dist/main/ipc/tooltip.d.ts +2 -1
- package/dist/main/ipc/tooltip.js +8 -7
- package/dist/main/ipc/views.d.ts +4 -1
- package/dist/main/ipc/views.js +20 -9
- package/dist/main/runtime/devtools-backend-before-quit.harness.d.ts +2 -20
- package/dist/main/runtime/devtools-backend-before-quit.harness.js +14 -283
- package/dist/main/runtime/devtools-backend.js +14 -3
- package/dist/main/runtime/devtools-runtime-mock.harness.d.ts +21 -0
- package/dist/main/runtime/devtools-runtime-mock.harness.js +313 -0
- package/dist/main/services/app-services.d.ts +62 -0
- package/dist/main/services/app-services.js +64 -0
- package/dist/main/services/automation/connection-target.d.ts +36 -0
- package/dist/main/services/automation/connection-target.js +29 -0
- package/dist/main/services/automation/console-bridge.d.ts +28 -0
- package/dist/main/services/automation/console-bridge.js +91 -0
- package/dist/main/services/automation/handlers/app.js +4 -4
- package/dist/main/services/automation/handlers/element.js +55 -21
- package/dist/main/services/automation/index.d.ts +12 -1
- package/dist/main/services/automation/index.js +54 -133
- package/dist/main/services/default-adapter.js +1 -1
- package/dist/main/services/fs-bridge-protocol.d.ts +61 -0
- package/dist/main/services/fs-bridge-protocol.js +34 -0
- package/dist/main/services/fs-watch-sse.d.ts +8 -1
- package/dist/main/services/fs-watch-sse.js +19 -5
- package/dist/main/services/mcp/connection-owner.d.ts +38 -0
- package/dist/main/services/mcp/connection-owner.js +79 -0
- package/dist/main/services/mcp/index.d.ts +3 -1
- package/dist/main/services/mcp/index.js +4 -3
- package/dist/main/services/mcp/opened-project.d.ts +88 -0
- package/dist/main/services/mcp/opened-project.js +60 -0
- package/dist/main/services/mcp/target-manager.d.ts +71 -23
- package/dist/main/services/mcp/target-manager.js +269 -142
- package/dist/main/services/mcp/target-selection.d.ts +47 -0
- package/dist/main/services/mcp/target-selection.js +71 -0
- package/dist/main/services/mcp/tools/context-tools.js +20 -5
- package/dist/main/services/mcp/tools/project-tools.d.ts +29 -6
- package/dist/main/services/mcp/tools/project-tools.js +37 -25
- package/dist/main/services/module.d.ts +13 -5
- package/dist/main/services/notifications/renderer-notifier.d.ts +28 -4
- package/dist/main/services/notifications/renderer-notifier.js +15 -3
- package/dist/main/services/projects/local-provider.js +2 -0
- package/dist/main/services/projects/project-repository.d.ts +31 -1
- package/dist/main/services/projects/project-repository.js +144 -20
- package/dist/main/services/projects/types.d.ts +31 -8
- package/dist/main/services/safe-area/index.d.ts +19 -5
- package/dist/main/services/safe-area/index.js +38 -13
- package/dist/main/services/simulator/ui-extensions.d.ts +8 -0
- package/dist/main/services/simulator/ui-extensions.js +16 -6
- package/dist/main/services/simulator-storage/index.d.ts +12 -1
- package/dist/main/services/simulator-storage/index.js +22 -7
- package/dist/main/services/simulator-storage/window-scope.d.ts +20 -0
- package/dist/main/services/simulator-storage/window-scope.js +20 -0
- package/dist/main/services/views/host-slot-port-channel.d.ts +20 -3
- package/dist/main/services/views/host-slot-port-channel.js +33 -5
- package/dist/main/services/views/managed-web-contents-view.js +21 -1
- package/dist/main/services/views/native-simulator-view.js +13 -16
- package/dist/main/services/views/overlay-panels-view.d.ts +13 -0
- package/dist/main/services/views/overlay-panels-view.js +31 -0
- package/dist/main/services/views/view-manager.d.ts +1 -1
- package/dist/main/services/views/view-manager.js +3 -0
- package/dist/main/services/window-contexts/context-router.d.ts +36 -0
- package/dist/main/services/window-contexts/context-router.js +55 -0
- package/dist/main/services/workbench-coi-host.d.ts +52 -0
- package/dist/main/services/workbench-coi-host.js +227 -0
- package/dist/main/services/workbench-coi-server.js +75 -133
- package/dist/main/services/workbench-coi-shutdown.d.ts +40 -0
- package/dist/main/services/workbench-coi-shutdown.js +69 -0
- package/dist/main/services/workbench-coi-static.d.ts +23 -0
- package/dist/main/services/workbench-coi-static.js +87 -0
- package/dist/main/services/workbench-context.d.ts +27 -3
- package/dist/main/services/workbench-context.js +10 -5
- package/dist/main/services/workspace/compile-mode-adapter.d.ts +18 -0
- package/dist/main/services/workspace/compile-mode-adapter.js +57 -0
- package/dist/main/services/workspace/compile-mode-store.d.ts +25 -0
- package/dist/main/services/workspace/compile-mode-store.js +71 -0
- package/dist/main/services/workspace/workspace-compile-modes.d.ts +59 -0
- package/dist/main/services/workspace/workspace-compile-modes.js +93 -0
- package/dist/main/services/workspace/workspace-referer.d.ts +17 -0
- package/dist/main/services/workspace/workspace-referer.js +26 -0
- package/dist/main/services/workspace/workspace-service.d.ts +4 -0
- package/dist/main/services/workspace/workspace-service.js +51 -84
- package/dist/main/services/workspace/workspace-thumbnails.d.ts +22 -0
- package/dist/main/services/workspace/workspace-thumbnails.js +67 -0
- package/dist/main/utils/ipc-context-source.d.ts +40 -0
- package/dist/main/utils/ipc-context-source.js +23 -0
- package/dist/main/utils/ipc-registry.d.ts +42 -36
- package/dist/main/utils/ipc-registry.js +117 -50
- package/dist/main/utils/sender-policy.d.ts +9 -0
- package/dist/main/utils/sender-policy.js +26 -3
- package/dist/main/utils/theme.d.ts +3 -2
- package/dist/main/utils/theme.js +3 -2
- package/dist/main/windows/main-window/create.d.ts +16 -0
- package/dist/main/windows/main-window/create.js +19 -11
- package/dist/main/windows/main-window/events.d.ts +1 -1
- package/dist/main/windows/main-window/events.js +26 -13
- package/dist/main/windows/main-window/index.d.ts +1 -1
- package/dist/main/windows/main-window/index.js +1 -1
- package/dist/native-host/common/common.js +94 -73
- package/dist/native-host/render/render.js +4079 -3171
- package/dist/native-host/service/service.js +2 -2
- package/dist/preload/runtime/native-host.d.ts +4 -0
- package/dist/preload/runtime/temp-files.js +9 -2
- package/dist/preload/runtime/wait-for-slot-root.d.ts +7 -4
- package/dist/preload/runtime/wait-for-slot-root.js +36 -8
- package/dist/preload/windows/host-dialog-runtime.cjs +10 -3
- package/dist/preload/windows/host-dialog-runtime.cjs.map +2 -2
- package/dist/preload/windows/host-sidebar-runtime.cjs +10 -3
- package/dist/preload/windows/host-sidebar-runtime.cjs.map +2 -2
- package/dist/preload/windows/host-toolbar-runtime.cjs +10 -3
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
- package/dist/preload/windows/main.cjs +181 -4
- package/dist/preload/windows/main.cjs.map +4 -4
- package/dist/preload/windows/simulator.cjs +10 -0
- package/dist/preload/windows/simulator.cjs.map +2 -2
- package/dist/preload/windows/simulator.js +10 -0
- package/dist/render-host/pageFrame.html +1 -1
- package/dist/render-host/preload.cjs +16 -4
- package/dist/renderer/assets/button-CB2MWjHY.js +2 -0
- package/dist/renderer/assets/constants-CkoGPQTS.js +2 -0
- package/dist/renderer/assets/constants-zQZ2-y21.js +2 -0
- package/dist/renderer/assets/{createLucideIcon-Bqi8hpdi.js → createLucideIcon-cZ9FlZZw.js} +2 -2
- package/dist/renderer/assets/devicePicker-H2eIO4e1.js +2 -0
- package/dist/renderer/assets/dialog-pHzEA8Vx.js +46 -0
- package/dist/renderer/assets/dist-B5HADirS.js +2 -0
- package/dist/renderer/assets/{dist-DKWdE1TK.js → dist-DIFlRMKe.js} +2 -2
- package/dist/renderer/assets/hostSidebarDefault-DRWh6OCt.js +2 -0
- package/dist/renderer/assets/index-CEL_XFge.js +2 -0
- package/dist/renderer/assets/{input-Ct3alvkq.js → input-CAObw95L.js} +2 -2
- package/dist/renderer/assets/ipc-channels-overlays-gIPUFnqc.js +2 -0
- package/dist/renderer/assets/jsx-runtime-DICEQH_0.css +1 -0
- package/dist/renderer/assets/{jsx-runtime-DGLHmmOq.js → jsx-runtime-DIw5wnYl.js} +1 -1
- package/dist/renderer/assets/plus-BQ9u5EbL.js +2 -0
- package/dist/renderer/assets/popover-Cw7G2Ctp.js +2 -0
- package/dist/renderer/assets/presets-DMZOb9ay.js +2 -0
- package/dist/renderer/assets/project-api-YTOr_Yvo.js +2 -0
- package/dist/renderer/assets/{projectCreateDialog-Bbjz75bP.js → projectCreateDialog-BmLqiYxn.js} +2 -2
- package/dist/renderer/assets/search-BMvuYF48.js +2 -0
- package/dist/renderer/assets/select-nFP95TxM.js +2 -0
- package/dist/renderer/assets/settings-CnpVfSWt.js +2 -0
- package/dist/renderer/assets/settings-api-Ccsf-Nsj.js +2 -0
- package/dist/renderer/assets/settings-tab-bar-CAzrv32m.js +2 -0
- package/dist/renderer/assets/smartphone-Bb2GdIKy.js +2 -0
- package/dist/renderer/assets/tabs-CPKN3Mmq.js +2 -0
- package/dist/renderer/assets/{tooltip-B_-_YVbw.js → tooltip-D6AxXRk5.js} +2 -2
- package/dist/renderer/assets/types-Bh0hA7El.js +2 -0
- package/dist/renderer/assets/updateDialog-CwndJ0Bl.js +2 -0
- package/dist/renderer/assets/{utils-kfQov6Pa.js → utils-sEB5OZEQ.js} +2 -2
- package/dist/renderer/assets/view-api-B-2zPBR5.js +2 -0
- package/dist/renderer/assets/view-ids-BfaDVV5P.js +2 -0
- package/dist/renderer/assets/workbench-BLJ0wKsg.js +5 -0
- package/dist/renderer/assets/{workbenchSettings-CV4LVA9b.js → workbenchSettings-YD0Ctbm_.js} +3 -3
- package/dist/renderer/entries/device-picker/index.html +28 -0
- package/dist/renderer/entries/host-sidebar-default/index.html +6 -5
- package/dist/renderer/entries/main/index.html +16 -17
- package/dist/renderer/entries/popover/index.html +16 -10
- package/dist/renderer/entries/project-create-dialog/index.html +12 -12
- package/dist/renderer/entries/settings/index.html +10 -10
- package/dist/renderer/entries/tooltip/index.html +5 -5
- package/dist/renderer/entries/update-dialog/index.html +10 -10
- package/dist/renderer/entries/workbench/index.html +49 -0
- package/dist/renderer/entries/workbench-settings/index.html +12 -11
- package/dist/service-host/host-env-update.cjs +23 -0
- package/dist/service-host/preload.cjs +24 -49
- package/dist/service-host/service-message-queue.cjs +71 -0
- package/dist/service-host/sync-impls/system-info.js +31 -9
- package/dist/shared/compile-mode-state.d.ts +73 -0
- package/dist/shared/compile-mode-state.js +100 -0
- package/dist/shared/compile-modes.d.ts +152 -0
- package/dist/shared/compile-modes.js +237 -0
- package/dist/shared/ipc-channels-overlays.d.ts +8 -2
- package/dist/shared/ipc-channels-overlays.js +19 -2
- package/dist/shared/ipc-channels.d.ts +5 -10
- package/dist/shared/ipc-channels.js +17 -14
- package/dist/shared/ipc-schemas.d.ts +76 -24
- package/dist/shared/ipc-schemas.js +55 -10
- package/dist/shared/project-window-types.d.ts +42 -0
- package/dist/shared/project-window-types.js +2 -0
- package/dist/shared/types.d.ts +43 -17
- package/dist/shared/view-ids.d.ts +1 -0
- package/dist/shared/view-ids.js +3 -1
- package/dist/simulator/assets/bridge-channels-B3JAmRfl.js +2 -0
- package/dist/simulator/assets/device-shell-ATEhHV2_.css +1 -0
- package/dist/simulator/assets/device-shell-pxnZZp10.js +431 -0
- package/dist/simulator/assets/simulator-Dwp7lnvu.js +10 -0
- package/dist/simulator/assets/simulator-mini-app-DFo7fhi5.js +2 -0
- package/dist/simulator/assets/simulator-ui-B2ddJB1R.js +2 -0
- package/dist/simulator/simulator.html +3 -3
- package/dist/simulator/temp-files.d.ts +6 -0
- package/dist/simulator/temp-files.js +46 -4
- package/dist/simulator/types.d.ts +7 -0
- package/dist/vscode-workbench/assets/__vite-browser-external-JIkWewFf.js +1 -0
- package/dist/vscode-workbench/assets/{dist-CMfmHflW.js → dist-CXG-u1VB.js} +3 -3
- package/dist/vscode-workbench/assets/{iconv-lite-umd-Bx8CgaZq.js → iconv-lite-umd-Dxx9Qyb4.js} +1 -1
- package/dist/vscode-workbench/assets/{index-CdG6YWhW.js → index-6VJMsaWj.js} +460 -460
- package/dist/vscode-workbench/assets/{jschardet-M0qgd2lN.js → jschardet-Ba3D16vt.js} +1 -1
- package/dist/vscode-workbench/index.html +1 -1
- package/package.json +11 -8
- package/dist/renderer/assets/button-D5Q2wkXq.js +0 -2
- package/dist/renderer/assets/constants-D7DfVqMp.js +0 -2
- package/dist/renderer/assets/constants-D8zsejFq.js +0 -2
- package/dist/renderer/assets/dialog-e1TecTOY.js +0 -46
- package/dist/renderer/assets/hostSidebarDefault-B3yLfy8j.js +0 -2
- package/dist/renderer/assets/index-CsPD68N_.js +0 -5
- package/dist/renderer/assets/ipc-channels-overlays-LsDzGAtV.js +0 -2
- package/dist/renderer/assets/jsx-runtime-2vXHouwr.css +0 -1
- package/dist/renderer/assets/popover-Dix5cm_b.js +0 -2
- package/dist/renderer/assets/project-api-k7Oq9iaq.js +0 -2
- package/dist/renderer/assets/select-acGb0AmJ.js +0 -2
- package/dist/renderer/assets/settings-CQrTMr43.js +0 -2
- package/dist/renderer/assets/settings-api-CrUQ9rYV.js +0 -2
- package/dist/renderer/assets/settings-tab-bar-Cw1kVIf2.js +0 -2
- package/dist/renderer/assets/types-CsEKtFyr.js +0 -2
- package/dist/renderer/assets/updateDialog-NsF8223A.js +0 -2
- package/dist/renderer/assets/view-api-CLn3rcQE.js +0 -2
- package/dist/simulator/assets/bridge-channels-zhsumfky.js +0 -2
- package/dist/simulator/assets/device-shell-CmtM1QxP.js +0 -2
- package/dist/simulator/assets/device-shell-iFZo1igL.css +0 -1
- package/dist/simulator/assets/simulator-CIobn3Mh.js +0 -10
- package/dist/simulator/assets/simulator-mini-app-CRe6n9br.js +0 -2
- package/dist/simulator/assets/simulator-ui-C_cP602X.js +0 -2
- package/dist/vscode-workbench/assets/__vite-browser-external-Bc1vizTf.js +0 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { WebContents } from 'electron';
|
|
2
|
+
import type { SenderPolicy } from './ipc-registry.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves which window context owns an incoming IPC sender.
|
|
5
|
+
*
|
|
6
|
+
* `register*Ipc` takes one of these instead of a bare context so a single
|
|
7
|
+
* handler body serves both shapes: one window (every accepted sender maps to
|
|
8
|
+
* that window's context) and several windows (each message is answered by the
|
|
9
|
+
* context of the window it actually came from).
|
|
10
|
+
*/
|
|
11
|
+
export interface IpcContextSource<T> {
|
|
12
|
+
/**
|
|
13
|
+
* Set only by the adapter over a context that carries no `senderPolicy` —
|
|
14
|
+
* narrow test contexts. It keeps {@link IpcRegistry} in its policy-less mode
|
|
15
|
+
* (accept every sender, skip the main-frame check), so wrapping such a
|
|
16
|
+
* context in a source is not itself a trust change.
|
|
17
|
+
*/
|
|
18
|
+
readonly ungated?: boolean;
|
|
19
|
+
/** The context owning `sender`, or null when no context claims it. */
|
|
20
|
+
resolve(sender: WebContents): T | null;
|
|
21
|
+
/** Every context this source can hand out. */
|
|
22
|
+
list(): T[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* What every `register*Ipc` accepts: a router covering all live windows, or a
|
|
26
|
+
* single context standing in for itself.
|
|
27
|
+
*/
|
|
28
|
+
export type IpcInput<T> = T | IpcContextSource<T>;
|
|
29
|
+
/** Structural test: a source exposes both members, a context exposes neither. */
|
|
30
|
+
export declare function isIpcContextSource<T>(value: unknown): value is IpcContextSource<T>;
|
|
31
|
+
/**
|
|
32
|
+
* Accept either a source or a single context. A single context gates on its
|
|
33
|
+
* own `senderPolicy` — the same predicate a policy-only registry applies — and
|
|
34
|
+
* owns every sender that predicate accepts. A context without a policy stays
|
|
35
|
+
* ungated so narrow test contexts keep the behaviour a bare registry gave them.
|
|
36
|
+
*/
|
|
37
|
+
export declare function toIpcContextSource<T extends {
|
|
38
|
+
senderPolicy?: SenderPolicy;
|
|
39
|
+
}>(input: T | IpcContextSource<T>): IpcContextSource<T>;
|
|
40
|
+
//# sourceMappingURL=ipc-context-source.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Structural test: a source exposes both members, a context exposes neither. */
|
|
2
|
+
export function isIpcContextSource(value) {
|
|
3
|
+
if (value == null || typeof value !== 'object')
|
|
4
|
+
return false;
|
|
5
|
+
const candidate = value;
|
|
6
|
+
return typeof candidate.resolve === 'function' && typeof candidate.list === 'function';
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Accept either a source or a single context. A single context gates on its
|
|
10
|
+
* own `senderPolicy` — the same predicate a policy-only registry applies — and
|
|
11
|
+
* owns every sender that predicate accepts. A context without a policy stays
|
|
12
|
+
* ungated so narrow test contexts keep the behaviour a bare registry gave them.
|
|
13
|
+
*/
|
|
14
|
+
export function toIpcContextSource(input) {
|
|
15
|
+
if (isIpcContextSource(input))
|
|
16
|
+
return input;
|
|
17
|
+
const ctx = input;
|
|
18
|
+
const policy = ctx.senderPolicy;
|
|
19
|
+
if (!policy)
|
|
20
|
+
return { ungated: true, resolve: () => ctx, list: () => [ctx] };
|
|
21
|
+
return { resolve: (sender) => (policy(sender) ? ctx : null), list: () => [ctx] };
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=ipc-context-source.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { IpcMainEvent } from 'electron';
|
|
1
|
+
import type { IpcMainEvent, IpcMainInvokeEvent } from 'electron';
|
|
2
2
|
import { ipcMain } from 'electron';
|
|
3
3
|
import { type Disposable, type IpcSenderPolicy } from '@dimina-kit/electron-deck/main';
|
|
4
|
+
import type { IpcContextSource } from './ipc-context-source.js';
|
|
4
5
|
type HandleFn = Parameters<typeof ipcMain.handle>[1];
|
|
5
6
|
type ListenerFn = Parameters<typeof ipcMain.on>[1];
|
|
6
7
|
/**
|
|
@@ -10,57 +11,62 @@ type ListenerFn = Parameters<typeof ipcMain.on>[1];
|
|
|
10
11
|
* the channel, false otherwise.
|
|
11
12
|
*/
|
|
12
13
|
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
|
-
*/
|
|
31
14
|
/**
|
|
32
15
|
* Tiny fluent helper that wraps every `ipcMain.handle` / `ipcMain.on` with a
|
|
33
16
|
* matching removeHandler/removeListener registered into an internal registry.
|
|
34
17
|
*
|
|
35
|
-
* Each `register*Ipc(
|
|
18
|
+
* Each `register*Ipc(...)` returns one of these as a Disposable so the
|
|
36
19
|
* workbench-level registry can dispose all built-in handlers in one shot.
|
|
37
20
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
21
|
+
* The constructor takes either a {@link SenderPolicy} (trust only: is this
|
|
22
|
+
* sender allowed?) or an {@link IpcContextSource} (trust plus ownership: which
|
|
23
|
+
* window context does this sender belong to?). With either, every incoming
|
|
24
|
+
* invocation is gated:
|
|
25
|
+
* - `handle` / `handleRouted`: rejected senders cause the invoke promise to
|
|
26
|
+
* reject with `Error('IPC sender rejected for channel <channel>')`.
|
|
27
|
+
* - `on` / `onRouted`: rejected senders are silently dropped (the original
|
|
28
|
+
* listener is never called).
|
|
29
|
+
* - `handleSync` / `handleSyncRouted`: rejected senders get a structured
|
|
30
|
+
* `{ ok: false, code: 'EREJECTED', … }` returnValue, because sendSync blocks
|
|
31
|
+
* the renderer until one is set.
|
|
32
|
+
* In every case a single `console.warn` is emitted with the channel name and a
|
|
33
|
+
* short sender summary. With no policy — or a source that declares itself
|
|
34
|
+
* `ungated` — the wrapper is a pass-through, preserving backwards
|
|
35
|
+
* compatibility for unit tests and callers that opted out.
|
|
36
|
+
*
|
|
37
|
+
* The `*Routed` variants hand the owning context to the handler as its first
|
|
38
|
+
* argument. `TCtx` is `never` unless the registry was built from a source, so
|
|
39
|
+
* they are unusable (and unreachable) on a policy-only registry.
|
|
47
40
|
*/
|
|
48
|
-
export declare class IpcRegistry implements Disposable {
|
|
49
|
-
private policy?;
|
|
41
|
+
export declare class IpcRegistry<TCtx = never> implements Disposable {
|
|
50
42
|
private cleanups;
|
|
51
43
|
private _disposed;
|
|
52
|
-
|
|
44
|
+
private readonly policy;
|
|
45
|
+
private readonly source;
|
|
46
|
+
/** False reproduces the policy-less pass-through exactly: no main-frame
|
|
47
|
+
* check, and a synchronous handler keeps its synchronous return value. */
|
|
48
|
+
private readonly gated;
|
|
49
|
+
constructor(policyOrSource?: SenderPolicy | IpcContextSource<TCtx>);
|
|
50
|
+
/** Trust gate and owner resolution for one incoming message. */
|
|
51
|
+
private admit;
|
|
52
|
+
private warnRejected;
|
|
53
|
+
private addInvokeHandler;
|
|
53
54
|
handle(channel: string, fn: HandleFn): this;
|
|
55
|
+
/** {@link handle} answered by the context that owns the calling sender. */
|
|
56
|
+
handleRouted(channel: string, fn: (ctx: TCtx, event: IpcMainInvokeEvent, ...args: unknown[]) => unknown): this;
|
|
57
|
+
private addSyncHandler;
|
|
54
58
|
/**
|
|
55
59
|
* Register a SYNCHRONOUS `ipcRenderer.sendSync` handler. Unlike {@link on}
|
|
56
60
|
* (fire-and-forget), the renderer is blocked until we set `event.returnValue`,
|
|
57
|
-
* so `fn` MUST be synchronous and return the value to hand back.
|
|
58
|
-
* policy gates it like {@link handle}; a rejected sender or a thrown error
|
|
59
|
-
* yields a structured `{ ok: false, code, message }` returnValue instead of
|
|
60
|
-
* the silent drop `on` uses, so the blocked renderer always gets an answer.
|
|
61
|
+
* so `fn` MUST be synchronous and return the value to hand back.
|
|
61
62
|
*/
|
|
62
63
|
handleSync(channel: string, fn: (event: IpcMainEvent, ...args: unknown[]) => unknown): this;
|
|
64
|
+
/** {@link handleSync} answered by the context that owns the calling sender. */
|
|
65
|
+
handleSyncRouted(channel: string, fn: (ctx: TCtx, event: IpcMainEvent, ...args: unknown[]) => unknown): this;
|
|
66
|
+
private addListener;
|
|
63
67
|
on(channel: string, fn: ListenerFn): this;
|
|
68
|
+
/** {@link on} answered by the context that owns the calling sender. */
|
|
69
|
+
onRouted(channel: string, fn: (ctx: TCtx, event: IpcMainEvent, ...args: unknown[]) => unknown): this;
|
|
64
70
|
dispose(): Promise<void>;
|
|
65
71
|
}
|
|
66
72
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ipcMain } from 'electron';
|
|
2
|
-
import { isMainFrameIpcSender, } from '@dimina-kit/electron-deck/main';
|
|
2
|
+
import { addMuxedInvokeHandler, isMainFrameIpcSender, } from '@dimina-kit/electron-deck/main';
|
|
3
3
|
import { IpcValidationError } from './ipc-schema.js';
|
|
4
4
|
import { createLogger } from './logger.js';
|
|
5
5
|
const log = createLogger('ipc');
|
|
@@ -48,25 +48,41 @@ function summarizeSender(sender) {
|
|
|
48
48
|
* possibly null) from a frame-unaware unit-test stub (neither present) and only
|
|
49
49
|
* skip the check for the latter (the sender-id white-list still gates tests).
|
|
50
50
|
*/
|
|
51
|
+
/** Internal marker for "this sender is not allowed on this channel". */
|
|
52
|
+
const REJECT = Symbol('ipc-reject');
|
|
53
|
+
// Channel multiplexing (one real `ipcMain.handle` per channel, dispatched to
|
|
54
|
+
// whichever per-window registration claims the sender — needed because the
|
|
55
|
+
// storage, WXML and AppData panels each register their channels once per
|
|
56
|
+
// window) is shared with dimina-electron-runtime's bridge router; see
|
|
57
|
+
// @dimina-kit/electron-deck/main's ipc-mux.ts for the mux itself.
|
|
51
58
|
/**
|
|
52
59
|
* Tiny fluent helper that wraps every `ipcMain.handle` / `ipcMain.on` with a
|
|
53
60
|
* matching removeHandler/removeListener registered into an internal registry.
|
|
54
61
|
*
|
|
55
|
-
* Each `register*Ipc(
|
|
62
|
+
* Each `register*Ipc(...)` returns one of these as a Disposable so the
|
|
56
63
|
* workbench-level registry can dispose all built-in handlers in one shot.
|
|
57
64
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
65
|
+
* The constructor takes either a {@link SenderPolicy} (trust only: is this
|
|
66
|
+
* sender allowed?) or an {@link IpcContextSource} (trust plus ownership: which
|
|
67
|
+
* window context does this sender belong to?). With either, every incoming
|
|
68
|
+
* invocation is gated:
|
|
69
|
+
* - `handle` / `handleRouted`: rejected senders cause the invoke promise to
|
|
70
|
+
* reject with `Error('IPC sender rejected for channel <channel>')`.
|
|
71
|
+
* - `on` / `onRouted`: rejected senders are silently dropped (the original
|
|
72
|
+
* listener is never called).
|
|
73
|
+
* - `handleSync` / `handleSyncRouted`: rejected senders get a structured
|
|
74
|
+
* `{ ok: false, code: 'EREJECTED', … }` returnValue, because sendSync blocks
|
|
75
|
+
* the renderer until one is set.
|
|
76
|
+
* In every case a single `console.warn` is emitted with the channel name and a
|
|
77
|
+
* short sender summary. With no policy — or a source that declares itself
|
|
78
|
+
* `ungated` — the wrapper is a pass-through, preserving backwards
|
|
79
|
+
* compatibility for unit tests and callers that opted out.
|
|
80
|
+
*
|
|
81
|
+
* The `*Routed` variants hand the owning context to the handler as its first
|
|
82
|
+
* argument. `TCtx` is `never` unless the registry was built from a source, so
|
|
83
|
+
* they are unusable (and unreachable) on a policy-only registry.
|
|
67
84
|
*/
|
|
68
85
|
export class IpcRegistry {
|
|
69
|
-
policy;
|
|
70
86
|
// Every cleanup is a synchronous ipcMain.removeHandler/removeListener call,
|
|
71
87
|
// so dispose() can (and must) run them all before returning: callers
|
|
72
88
|
// `dispose()` without awaiting and rely on every channel being unregistered
|
|
@@ -74,48 +90,81 @@ export class IpcRegistry {
|
|
|
74
90
|
// live until a later microtask.
|
|
75
91
|
cleanups = [];
|
|
76
92
|
_disposed = false;
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
policy;
|
|
94
|
+
source;
|
|
95
|
+
/** False reproduces the policy-less pass-through exactly: no main-frame
|
|
96
|
+
* check, and a synchronous handler keeps its synchronous return value. */
|
|
97
|
+
gated;
|
|
98
|
+
constructor(policyOrSource) {
|
|
99
|
+
const isPolicy = typeof policyOrSource === 'function';
|
|
100
|
+
this.policy = isPolicy ? policyOrSource : null;
|
|
101
|
+
this.source = policyOrSource != null && !isPolicy ? policyOrSource : null;
|
|
102
|
+
this.gated = this.policy != null || (this.source != null && !this.source.ungated);
|
|
79
103
|
}
|
|
80
|
-
|
|
81
|
-
|
|
104
|
+
/** Trust gate and owner resolution for one incoming message. */
|
|
105
|
+
admit(event) {
|
|
106
|
+
const source = this.source;
|
|
107
|
+
if (source) {
|
|
108
|
+
// An ungated source always claims the sender, so the frame check — part
|
|
109
|
+
// of the trust gate, not of ownership — stays off with it.
|
|
110
|
+
if (source.ungated)
|
|
111
|
+
return source.resolve(event.sender);
|
|
112
|
+
if (!isMainFrameIpcSender(event))
|
|
113
|
+
return REJECT;
|
|
114
|
+
const ctx = source.resolve(event.sender);
|
|
115
|
+
return ctx == null ? REJECT : ctx;
|
|
116
|
+
}
|
|
117
|
+
// A bare policy answers trust only, so the accepted branch carries no
|
|
118
|
+
// context; `TCtx` is `never` in that mode, so no handler can observe it.
|
|
119
|
+
if (!this.policy)
|
|
120
|
+
return undefined;
|
|
121
|
+
return this.policy(event.sender) && isMainFrameIpcSender(event)
|
|
122
|
+
? undefined
|
|
123
|
+
: REJECT;
|
|
124
|
+
}
|
|
125
|
+
warnRejected(channel, sender) {
|
|
126
|
+
console.warn(`[ipc] sender rejected for channel '${channel}' (${summarizeSender(sender)})`);
|
|
127
|
+
}
|
|
128
|
+
addInvokeHandler(channel, invoke) {
|
|
82
129
|
// The gate is `async` so a rejected sender surfaces as a *rejected
|
|
83
130
|
// promise* (the invoke-result contract) rather than a synchronous throw —
|
|
84
131
|
// synchronous throws can escape callers that wrap the result in
|
|
85
132
|
// `Promise.resolve(...)` instead of `await`-ing it directly.
|
|
86
|
-
const guarded =
|
|
133
|
+
const guarded = this.gated
|
|
87
134
|
? async (event, ...args) => {
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
|
|
135
|
+
const ctx = this.admit(event);
|
|
136
|
+
if (ctx === REJECT) {
|
|
137
|
+
this.warnRejected(channel, event.sender);
|
|
91
138
|
throw new Error(`IPC sender rejected for channel ${channel}`);
|
|
92
139
|
}
|
|
93
|
-
return
|
|
140
|
+
return invoke(ctx, event, args);
|
|
94
141
|
}
|
|
95
|
-
:
|
|
96
|
-
|
|
97
|
-
|
|
142
|
+
: (event, ...args) => invoke(this.admit(event), event, args);
|
|
143
|
+
this.cleanups.push(addMuxedInvokeHandler(ipcMain, channel, {
|
|
144
|
+
claims: (event) => this.admit(event) !== REJECT,
|
|
145
|
+
handle: guarded,
|
|
146
|
+
}));
|
|
98
147
|
return this;
|
|
99
148
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const policy = this.policy;
|
|
149
|
+
handle(channel, fn) {
|
|
150
|
+
const raw = fn;
|
|
151
|
+
return this.addInvokeHandler(channel, (_ctx, event, args) => raw(event, ...args));
|
|
152
|
+
}
|
|
153
|
+
/** {@link handle} answered by the context that owns the calling sender. */
|
|
154
|
+
handleRouted(channel, fn) {
|
|
155
|
+
return this.addInvokeHandler(channel, (ctx, event, args) => fn(ctx, event, ...args));
|
|
156
|
+
}
|
|
157
|
+
addSyncHandler(channel, invoke) {
|
|
110
158
|
const listener = (event, ...args) => {
|
|
111
159
|
const syncEvent = event;
|
|
112
|
-
// Everything — including the
|
|
113
|
-
//
|
|
160
|
+
// Everything — including the gate — runs inside the try so EVERY path
|
|
161
|
+
// sets `event.returnValue`. sendSync blocks the renderer until it is
|
|
114
162
|
// set, so an unset value (e.g. a throwing policy fn) would hang the
|
|
115
163
|
// renderer forever; the catch guarantees a sentinel instead.
|
|
116
164
|
try {
|
|
117
|
-
|
|
118
|
-
|
|
165
|
+
const ctx = this.admit(event);
|
|
166
|
+
if (ctx === REJECT) {
|
|
167
|
+
this.warnRejected(channel, event.sender);
|
|
119
168
|
syncEvent.returnValue = {
|
|
120
169
|
ok: false,
|
|
121
170
|
code: 'EREJECTED',
|
|
@@ -123,7 +172,7 @@ export class IpcRegistry {
|
|
|
123
172
|
};
|
|
124
173
|
return;
|
|
125
174
|
}
|
|
126
|
-
syncEvent.returnValue =
|
|
175
|
+
syncEvent.returnValue = invoke(ctx, event, args);
|
|
127
176
|
}
|
|
128
177
|
catch (err) {
|
|
129
178
|
reportListenerError(channel, err);
|
|
@@ -139,12 +188,22 @@ export class IpcRegistry {
|
|
|
139
188
|
this.cleanups.push(() => ipcMain.removeListener(channel, listener));
|
|
140
189
|
return this;
|
|
141
190
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Register a SYNCHRONOUS `ipcRenderer.sendSync` handler. Unlike {@link on}
|
|
193
|
+
* (fire-and-forget), the renderer is blocked until we set `event.returnValue`,
|
|
194
|
+
* so `fn` MUST be synchronous and return the value to hand back.
|
|
195
|
+
*/
|
|
196
|
+
handleSync(channel, fn) {
|
|
197
|
+
return this.addSyncHandler(channel, (_ctx, event, args) => fn(event, ...args));
|
|
198
|
+
}
|
|
199
|
+
/** {@link handleSync} answered by the context that owns the calling sender. */
|
|
200
|
+
handleSyncRouted(channel, fn) {
|
|
201
|
+
return this.addSyncHandler(channel, (ctx, event, args) => fn(ctx, event, ...args));
|
|
202
|
+
}
|
|
203
|
+
addListener(channel, invoke) {
|
|
204
|
+
const safeInvoke = (ctx, event, args) => {
|
|
146
205
|
try {
|
|
147
|
-
const ret =
|
|
206
|
+
const ret = invoke(ctx, event, args);
|
|
148
207
|
if (isThenable(ret)) {
|
|
149
208
|
// Async listeners would otherwise leak rejections into Electron's
|
|
150
209
|
// event loop as `UnhandledPromiseRejection`. Funnel them into the
|
|
@@ -156,20 +215,28 @@ export class IpcRegistry {
|
|
|
156
215
|
reportListenerError(channel, err);
|
|
157
216
|
}
|
|
158
217
|
};
|
|
159
|
-
const guarded =
|
|
218
|
+
const guarded = this.gated
|
|
160
219
|
? (event, ...args) => {
|
|
161
|
-
const
|
|
162
|
-
if (
|
|
163
|
-
|
|
220
|
+
const ctx = this.admit(event);
|
|
221
|
+
if (ctx === REJECT) {
|
|
222
|
+
this.warnRejected(channel, event.sender);
|
|
164
223
|
return;
|
|
165
224
|
}
|
|
166
|
-
safeInvoke(event, args);
|
|
225
|
+
safeInvoke(ctx, event, args);
|
|
167
226
|
}
|
|
168
|
-
: (event, ...args) => safeInvoke(event, args);
|
|
227
|
+
: (event, ...args) => safeInvoke(this.admit(event), event, args);
|
|
169
228
|
ipcMain.on(channel, guarded);
|
|
170
229
|
this.cleanups.push(() => ipcMain.removeListener(channel, guarded));
|
|
171
230
|
return this;
|
|
172
231
|
}
|
|
232
|
+
on(channel, fn) {
|
|
233
|
+
const raw = fn;
|
|
234
|
+
return this.addListener(channel, (_ctx, event, args) => raw(event, ...args));
|
|
235
|
+
}
|
|
236
|
+
/** {@link on} answered by the context that owns the calling sender. */
|
|
237
|
+
onRouted(channel, fn) {
|
|
238
|
+
return this.addListener(channel, (ctx, event, args) => fn(ctx, event, ...args));
|
|
239
|
+
}
|
|
173
240
|
dispose() {
|
|
174
241
|
// Synchronous drain (LIFO, idempotent): every channel is unregistered
|
|
175
242
|
// before this returns; the promise only carries aggregated errors.
|
|
@@ -31,4 +31,13 @@ import type { SenderPolicy } from './ipc-registry.js';
|
|
|
31
31
|
* iframe — is rejected.
|
|
32
32
|
*/
|
|
33
33
|
export declare function createWorkbenchSenderPolicy(ctx: Pick<WorkbenchContext, 'windows' | 'views' | 'trustedWindowSenderIds'>): SenderPolicy;
|
|
34
|
+
/**
|
|
35
|
+
* The senders this context structurally OWNS: its own main-window renderer,
|
|
36
|
+
* its own settings window and the overlay views mounted inside it. Deliberately
|
|
37
|
+
* excludes `trustedWindowSenderIds` — that map is shared by the whole app, so
|
|
38
|
+
* every context would claim the same host window and ownership would stop being
|
|
39
|
+
* unique. `createWindowContextRouter` routes on this check first for exactly
|
|
40
|
+
* that reason.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createWorkbenchOwnedSenderCheck(ctx: Pick<WorkbenchContext, 'windows' | 'views'>): SenderPolicy;
|
|
34
43
|
//# sourceMappingURL=sender-policy.d.ts.map
|
|
@@ -29,6 +29,26 @@
|
|
|
29
29
|
* iframe — is rejected.
|
|
30
30
|
*/
|
|
31
31
|
export function createWorkbenchSenderPolicy(ctx) {
|
|
32
|
+
const ownsSender = createWorkbenchOwnedSenderCheck(ctx);
|
|
33
|
+
return (sender) => {
|
|
34
|
+
if (ownsSender(sender))
|
|
35
|
+
return true;
|
|
36
|
+
if (sender.isDestroyed())
|
|
37
|
+
return false;
|
|
38
|
+
// Host-registered trusted windows (registerTrustedWindow). The map is
|
|
39
|
+
// app-wide, so this is the one accepted sender no single context owns.
|
|
40
|
+
return ctx.trustedWindowSenderIds.has(sender.id);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The senders this context structurally OWNS: its own main-window renderer,
|
|
45
|
+
* its own settings window and the overlay views mounted inside it. Deliberately
|
|
46
|
+
* excludes `trustedWindowSenderIds` — that map is shared by the whole app, so
|
|
47
|
+
* every context would claim the same host window and ownership would stop being
|
|
48
|
+
* unique. `createWindowContextRouter` routes on this check first for exactly
|
|
49
|
+
* that reason.
|
|
50
|
+
*/
|
|
51
|
+
export function createWorkbenchOwnedSenderCheck(ctx) {
|
|
32
52
|
return (sender) => {
|
|
33
53
|
if (sender.isDestroyed())
|
|
34
54
|
return false;
|
|
@@ -38,9 +58,6 @@ export function createWorkbenchSenderPolicy(ctx) {
|
|
|
38
58
|
// Standalone settings BrowserWindow renderer
|
|
39
59
|
if (ctx.windows.isSettingsWindowSender(sender.id))
|
|
40
60
|
return true;
|
|
41
|
-
// Host-registered trusted windows (registerTrustedWindow)
|
|
42
|
-
if (ctx.trustedWindowSenderIds.has(sender.id))
|
|
43
|
-
return true;
|
|
44
61
|
// Settings overlay view (mounted inside the main window)
|
|
45
62
|
const settingsViewId = ctx.views.getSettingsWebContentsId();
|
|
46
63
|
if (settingsViewId != null && sender.id === settingsViewId)
|
|
@@ -64,6 +81,12 @@ export function createWorkbenchSenderPolicy(ctx) {
|
|
|
64
81
|
const updateDialogViewId = ctx.views.getUpdateDialogWebContentsId();
|
|
65
82
|
if (updateDialogViewId != null && sender.id === updateDialogViewId)
|
|
66
83
|
return true;
|
|
84
|
+
// Device-picker overlay view. Same devtools-owned trust level — the
|
|
85
|
+
// simulator toolbar's device selector, moved out of the main renderer's
|
|
86
|
+
// DOM so the simulator WCV can't occlude it.
|
|
87
|
+
const devicePickerViewId = ctx.views.getDevicePickerWebContentsId();
|
|
88
|
+
if (devicePickerViewId != null && sender.id === devicePickerViewId)
|
|
89
|
+
return true;
|
|
67
90
|
// The host-toolbar overlay is DELIBERATELY NOT trusted here. The host loads
|
|
68
91
|
// arbitrary content into it, so granting it the global white-list would open
|
|
69
92
|
// all ~72 IpcRegistry channels to that content. Its one channel (the reverse
|
|
@@ -15,8 +15,9 @@ export declare function themeBg(): string;
|
|
|
15
15
|
* phone keeps contrast against it in BOTH schemes.
|
|
16
16
|
*
|
|
17
17
|
* Dark: #282828 | Light: #f7f7f9 — both match the renderer's --color-surface-2
|
|
18
|
-
* chrome tone (design.css). Dark is
|
|
19
|
-
* --qd-muted alias: pixel-verified against Figma
|
|
18
|
+
* chrome tone (@dimina-kit/design, css/tokens.css). Dark is a package-owned
|
|
19
|
+
* value rather than a Cornetto --qd-muted alias: pixel-verified against Figma
|
|
20
|
+
* node 66:1729 at rgb(40,40,40),
|
|
20
21
|
* which doesn't match any Cornetto dark token (--qd-muted is #383838, off by
|
|
21
22
|
* 16 units — confirmed too light against the app's own dark-mode screenshots).
|
|
22
23
|
* Light's #f7f7f9 IS an exact --qd-muted match (verified against node 25:5),
|
package/dist/main/utils/theme.js
CHANGED
|
@@ -19,8 +19,9 @@ export function themeBg() {
|
|
|
19
19
|
* phone keeps contrast against it in BOTH schemes.
|
|
20
20
|
*
|
|
21
21
|
* Dark: #282828 | Light: #f7f7f9 — both match the renderer's --color-surface-2
|
|
22
|
-
* chrome tone (design.css). Dark is
|
|
23
|
-
* --qd-muted alias: pixel-verified against Figma
|
|
22
|
+
* chrome tone (@dimina-kit/design, css/tokens.css). Dark is a package-owned
|
|
23
|
+
* value rather than a Cornetto --qd-muted alias: pixel-verified against Figma
|
|
24
|
+
* node 66:1729 at rgb(40,40,40),
|
|
24
25
|
* which doesn't match any Cornetto dark token (--qd-muted is #383838, off by
|
|
25
26
|
* 16 units — confirmed too light against the app's own dark-mode screenshots).
|
|
26
27
|
* Light's #f7f7f9 IS an exact --qd-muted match (verified against node 25:5),
|
|
@@ -6,6 +6,14 @@ export interface WindowOptions {
|
|
|
6
6
|
minWidth?: number;
|
|
7
7
|
minHeight?: number;
|
|
8
8
|
indexHtml: string;
|
|
9
|
+
/**
|
|
10
|
+
* Query string appended to the loaded `file://` URL. This is how a window
|
|
11
|
+
* receives its own bootstrap identity (the workbench window reads the project
|
|
12
|
+
* it belongs to from here), so the renderer entry knows what to mount without
|
|
13
|
+
* a round-trip. Survives navigation-hardening: that check matches on the
|
|
14
|
+
* `file://` prefix only and ignores the query (see navigation-hardening.ts).
|
|
15
|
+
*/
|
|
16
|
+
query?: Record<string, string>;
|
|
9
17
|
/**
|
|
10
18
|
* Auto-show the window on `ready-to-show` in non-test envs. Defaults to
|
|
11
19
|
* `true`. `false` lets a login-gating host keep the window hidden and call
|
|
@@ -13,5 +21,13 @@ export interface WindowOptions {
|
|
|
13
21
|
*/
|
|
14
22
|
autoShow?: boolean;
|
|
15
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Shows `win` the way the current env expects: `showInactive()` in test env
|
|
26
|
+
* so e2e windows don't steal focus, `show()` otherwise. Shared by this
|
|
27
|
+
* module's own `ready-to-show` handler and by the workbench window manager's
|
|
28
|
+
* reveal-after-hook-and-paint gate (`workbench-window.ts`), so the two reveal
|
|
29
|
+
* paths can never pick different show methods.
|
|
30
|
+
*/
|
|
31
|
+
export declare function revealMainWindow(win: BrowserWindow): void;
|
|
16
32
|
export declare function createMainWindow(opts: WindowOptions): BrowserWindow;
|
|
17
33
|
//# sourceMappingURL=create.d.ts.map
|
|
@@ -3,6 +3,19 @@ import path from 'path';
|
|
|
3
3
|
import { mainPreloadPath } from '../../utils/paths.js';
|
|
4
4
|
import { themeBg } from '../../utils/theme.js';
|
|
5
5
|
import { applyNavigationHardening } from '../navigation-hardening.js';
|
|
6
|
+
/**
|
|
7
|
+
* Shows `win` the way the current env expects: `showInactive()` in test env
|
|
8
|
+
* so e2e windows don't steal focus, `show()` otherwise. Shared by this
|
|
9
|
+
* module's own `ready-to-show` handler and by the workbench window manager's
|
|
10
|
+
* reveal-after-hook-and-paint gate (`workbench-window.ts`), so the two reveal
|
|
11
|
+
* paths can never pick different show methods.
|
|
12
|
+
*/
|
|
13
|
+
export function revealMainWindow(win) {
|
|
14
|
+
if (process.env.NODE_ENV === 'test')
|
|
15
|
+
win.showInactive();
|
|
16
|
+
else
|
|
17
|
+
win.show();
|
|
18
|
+
}
|
|
6
19
|
export function createMainWindow(opts) {
|
|
7
20
|
const mainWindow = new BrowserWindow({
|
|
8
21
|
width: opts.width ?? 1280,
|
|
@@ -25,16 +38,11 @@ export function createMainWindow(opts) {
|
|
|
25
38
|
const isTest = process.env.NODE_ENV === 'test';
|
|
26
39
|
// Visibility is governed by `autoShow` in BOTH envs. A login-gating host
|
|
27
40
|
// opts out via `autoShow: false` and reveals the window itself once auth
|
|
28
|
-
// passes — don't flash an un-authed window. The
|
|
29
|
-
// show
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (isTest)
|
|
34
|
-
mainWindow.showInactive();
|
|
35
|
-
else
|
|
36
|
-
mainWindow.show();
|
|
37
|
-
}
|
|
41
|
+
// passes — don't flash an un-authed window. The framework must never
|
|
42
|
+
// force-show when the host opted out, in test env either (that would
|
|
43
|
+
// fight the host's own reveal handler).
|
|
44
|
+
if (opts.autoShow !== false)
|
|
45
|
+
revealMainWindow(mainWindow);
|
|
38
46
|
// Don't auto-open a detached DevTools for the devtools UI shell itself —
|
|
39
47
|
// it's noise for normal use (the mini-app's Console lives in the embedded
|
|
40
48
|
// right-panel DevTools, not here). Opt in via env for debugging the shell.
|
|
@@ -46,7 +54,7 @@ export function createMainWindow(opts) {
|
|
|
46
54
|
// Restrict the main renderer to file:// URLs under the renderer bundle and
|
|
47
55
|
// route every popup through the OS browser. See navigation-hardening.ts.
|
|
48
56
|
applyNavigationHardening(mainWindow.webContents, rendererDir);
|
|
49
|
-
mainWindow.loadFile(opts.indexHtml);
|
|
57
|
+
mainWindow.loadFile(opts.indexHtml, opts.query ? { query: opts.query } : undefined);
|
|
50
58
|
const container = new View();
|
|
51
59
|
const mainWebView = mainWindow.contentView;
|
|
52
60
|
container.addChildView(mainWebView);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BrowserWindow } from 'electron';
|
|
2
2
|
import type { WorkbenchContext } from '../../services/workbench-context.js';
|
|
3
3
|
import { type Disposable } from '@dimina-kit/electron-deck/main';
|
|
4
4
|
export interface MainWindowEventState {
|
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
import { globalShortcut } from 'electron';
|
|
1
|
+
import { BrowserWindow, globalShortcut } from 'electron';
|
|
2
2
|
import { DisposableRegistry, toDisposable } from '@dimina-kit/electron-deck/main';
|
|
3
|
+
const DEVTOOLS_ACCELERATOR = 'CommandOrControl+Shift+I';
|
|
4
|
+
let devToolsShortcutHolders = 0;
|
|
5
|
+
/**
|
|
6
|
+
* Claims a share of the ONE process-wide DevTools accelerator. `globalShortcut`
|
|
7
|
+
* is a process singleton: a per-window registration is refused for every window
|
|
8
|
+
* after the first, which would give that first window the shortcut for the
|
|
9
|
+
* whole app and kill it outright when that window closed. So the accelerator is
|
|
10
|
+
* registered once, opens whichever window is focused when it is pressed, and is
|
|
11
|
+
* released only when the last holder lets go — a later window can then claim it
|
|
12
|
+
* again. `unregisterAll()` in lifecycle stays as a process-exit safety net.
|
|
13
|
+
*/
|
|
14
|
+
function acquireDevToolsShortcut() {
|
|
15
|
+
if (devToolsShortcutHolders === 0) {
|
|
16
|
+
globalShortcut.register(DEVTOOLS_ACCELERATOR, () => {
|
|
17
|
+
BrowserWindow.getFocusedWindow()?.webContents.openDevTools({ mode: 'detach' });
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
devToolsShortcutHolders += 1;
|
|
21
|
+
return toDisposable(() => {
|
|
22
|
+
devToolsShortcutHolders -= 1;
|
|
23
|
+
if (devToolsShortcutHolders === 0)
|
|
24
|
+
globalShortcut.unregister(DEVTOOLS_ACCELERATOR);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
3
27
|
export function wireMainWindowEvents(win, state = {}) {
|
|
4
28
|
const registry = new DisposableRegistry();
|
|
5
29
|
const mainWebView = win.contentView.children[0];
|
|
@@ -24,18 +48,7 @@ export function wireMainWindowEvents(win, state = {}) {
|
|
|
24
48
|
win.removeListener('close', onCloseHandler);
|
|
25
49
|
}));
|
|
26
50
|
}
|
|
27
|
-
|
|
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
|
-
}
|
|
51
|
+
registry.add(acquireDevToolsShortcut());
|
|
39
52
|
return registry;
|
|
40
53
|
}
|
|
41
54
|
//# sourceMappingURL=events.js.map
|