@dimina-kit/devtools 0.3.0 → 0.3.1-dev.20260515105504

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +103 -41
  2. package/dist/main/api.d.ts +6 -2
  3. package/dist/main/api.js +5 -2
  4. package/dist/main/app/app.d.ts +1 -1
  5. package/dist/main/app/app.js +71 -36
  6. package/dist/main/app/launch.d.ts +2 -2
  7. package/dist/main/app/launch.js +6 -3
  8. package/dist/main/index.bundle.js +1134 -300
  9. package/dist/main/ipc/app.d.ts +2 -1
  10. package/dist/main/ipc/app.js +5 -4
  11. package/dist/main/ipc/index.d.ts +5 -4
  12. package/dist/main/ipc/index.js +5 -4
  13. package/dist/main/ipc/panels.d.ts +2 -1
  14. package/dist/main/ipc/panels.js +14 -9
  15. package/dist/main/ipc/popover.d.ts +4 -1
  16. package/dist/main/ipc/popover.js +15 -5
  17. package/dist/main/ipc/projects.d.ts +4 -1
  18. package/dist/main/ipc/projects.js +42 -5
  19. package/dist/main/ipc/session.d.ts +4 -1
  20. package/dist/main/ipc/session.js +26 -6
  21. package/dist/main/ipc/settings.d.ts +4 -1
  22. package/dist/main/ipc/settings.js +30 -17
  23. package/dist/main/ipc/simulator-module.d.ts +8 -0
  24. package/dist/main/ipc/simulator-module.js +19 -0
  25. package/dist/main/ipc/simulator.d.ts +2 -1
  26. package/dist/main/ipc/simulator.js +29 -15
  27. package/dist/main/ipc/toolbar.d.ts +2 -1
  28. package/dist/main/ipc/toolbar.js +3 -2
  29. package/dist/main/services/automation/index.js +88 -13
  30. package/dist/main/services/default-adapter.js +1 -3
  31. package/dist/main/services/mcp/server.d.ts +2 -1
  32. package/dist/main/services/mcp/server.js +5 -0
  33. package/dist/main/services/module.d.ts +18 -0
  34. package/dist/main/services/module.js +2 -0
  35. package/dist/main/services/notifications/renderer-notifier.d.ts +2 -0
  36. package/dist/main/services/projects/project-repository.d.ts +1 -0
  37. package/dist/main/services/projects/project-repository.js +3 -0
  38. package/dist/main/services/settings/index.d.ts +4 -0
  39. package/dist/main/services/settings/index.js +12 -1
  40. package/dist/main/services/simulator/custom-apis.d.ts +10 -0
  41. package/dist/main/services/simulator/custom-apis.js +26 -0
  42. package/dist/main/services/simulator/dir.d.ts +1 -1
  43. package/dist/main/services/simulator/dir.js +1 -4
  44. package/dist/main/services/simulator-storage/index.d.ts +22 -1
  45. package/dist/main/services/simulator-storage/index.js +390 -84
  46. package/dist/main/services/update/update-manager.d.ts +16 -1
  47. package/dist/main/services/update/update-manager.js +18 -15
  48. package/dist/main/services/views/view-manager.d.ts +4 -0
  49. package/dist/main/services/views/view-manager.js +33 -2
  50. package/dist/main/services/window-service.d.ts +24 -0
  51. package/dist/main/services/window-service.js +26 -0
  52. package/dist/main/services/workbench-context.d.ts +15 -0
  53. package/dist/main/services/workbench-context.js +6 -0
  54. package/dist/main/services/workspace/workspace-service.d.ts +1 -0
  55. package/dist/main/services/workspace/workspace-service.js +7 -3
  56. package/dist/main/simulator-apis.d.ts +23 -0
  57. package/dist/main/simulator-apis.js +24 -0
  58. package/dist/main/utils/disposable.d.ts +13 -0
  59. package/dist/main/utils/disposable.js +60 -0
  60. package/dist/main/utils/ipc-registry.d.ts +39 -0
  61. package/dist/main/utils/ipc-registry.js +109 -0
  62. package/dist/main/utils/ipc-schema.d.ts +28 -0
  63. package/dist/main/utils/ipc-schema.js +38 -0
  64. package/dist/main/utils/paths.d.ts +8 -0
  65. package/dist/main/utils/paths.js +33 -4
  66. package/dist/main/utils/sender-policy.d.ts +23 -0
  67. package/dist/main/utils/sender-policy.js +42 -0
  68. package/dist/main/windows/main-window/create.d.ts +6 -0
  69. package/dist/main/windows/main-window/create.js +65 -9
  70. package/dist/main/windows/main-window/events.d.ts +2 -1
  71. package/dist/main/windows/main-window/events.js +25 -2
  72. package/dist/main/windows/navigation-hardening.d.ts +24 -0
  73. package/dist/main/windows/navigation-hardening.js +55 -0
  74. package/dist/main/windows/settings-window/create.js +11 -1
  75. package/dist/preload/index.d.ts +17 -0
  76. package/dist/preload/index.js +17 -0
  77. package/dist/preload/instrumentation/app-data.js +178 -19
  78. package/dist/preload/instrumentation/wxml-extract.js +134 -3
  79. package/dist/preload/runtime/bridge.d.ts +3 -1
  80. package/dist/preload/runtime/bridge.js +55 -4
  81. package/dist/preload/runtime/custom-apis.d.ts +6 -0
  82. package/dist/preload/runtime/custom-apis.js +18 -0
  83. package/dist/preload/runtime/host.d.ts +1 -0
  84. package/dist/preload/runtime/host.js +13 -0
  85. package/dist/preload/shared/types.d.ts +2 -1
  86. package/dist/preload/windows/main.cjs +47 -0
  87. package/dist/preload/windows/main.cjs.map +7 -0
  88. package/dist/preload/windows/simulator.js +274 -25
  89. package/dist/renderer/assets/index-DW_0thr-.js +46 -0
  90. package/dist/renderer/assets/{input-VLotwer2.js → input-C5M5Wxn6.js} +2 -2
  91. package/dist/renderer/assets/{ipc-transport-COKIC6Ny.js → ipc-transport-CUsl-fS2.js} +6 -6
  92. package/dist/renderer/assets/ipc-transport-CltRuvhi.css +1 -0
  93. package/dist/renderer/assets/{popover-Dn0RwMLI.js → popover-BSBPVO0o.js} +2 -2
  94. package/dist/renderer/assets/{select-DzIsLaKX.js → select-B1tF3UAc.js} +2 -2
  95. package/dist/renderer/assets/{settings-CD-dacWB.js → settings-7w54H6p-.js} +2 -2
  96. package/dist/renderer/assets/settings-api-BjJalghB.js +2 -0
  97. package/dist/renderer/assets/workbenchSettings-q2_-nc8t.js +8 -0
  98. package/dist/renderer/entries/main/index.html +12 -5
  99. package/dist/renderer/entries/popover/index.html +7 -5
  100. package/dist/renderer/entries/settings/index.html +7 -5
  101. package/dist/renderer/entries/workbench-settings/index.html +6 -4
  102. package/dist/shared/ipc-channels.d.ts +45 -0
  103. package/dist/shared/ipc-channels.js +28 -0
  104. package/dist/shared/ipc-schemas.d.ts +113 -0
  105. package/dist/shared/ipc-schemas.js +128 -0
  106. package/dist/shared/types.d.ts +2 -0
  107. package/dist/simulator/assets/simulator-DL6kOCq-.js +9 -0
  108. package/dist/simulator/simulator.html +1 -1
  109. package/package.json +11 -27
  110. package/dist/preload/windows/main.d.ts +0 -6
  111. package/dist/preload/windows/main.js +0 -6
  112. package/dist/renderer/assets/index-D_HFRqZA.js +0 -46
  113. package/dist/renderer/assets/ipc-transport-6Gy3FXIL.css +0 -1
  114. package/dist/renderer/assets/settings-api-BV9MPckG.js +0 -2
  115. package/dist/renderer/assets/workbenchSettings-ClwgsvJL.js +0 -8
  116. package/dist/simulator/assets/simulator-C1eHXnuq.js +0 -9
@@ -1,3 +1,4 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
- export declare function registerAppIpc(ctx: Pick<WorkbenchContext, 'preloadPath' | 'brandingProvider' | 'appName'>): void;
2
+ import type { Disposable } from '../utils/disposable.js';
3
+ export declare function registerAppIpc(ctx: Pick<WorkbenchContext, 'preloadPath' | 'brandingProvider' | 'appName' | 'senderPolicy'>): Disposable;
3
4
  //# sourceMappingURL=app.d.ts.map
@@ -1,11 +1,12 @@
1
- import { ipcMain } from 'electron';
2
1
  import fs from 'fs';
3
2
  import { AppChannel } from '../../shared/ipc-channels.js';
3
+ import { IpcRegistry } from '../utils/ipc-registry.js';
4
4
  export function registerAppIpc(ctx) {
5
- ipcMain.handle(AppChannel.GetPreloadPath, () => {
5
+ return new IpcRegistry(ctx.senderPolicy)
6
+ .handle(AppChannel.GetPreloadPath, () => {
6
7
  return `file://${fs.realpathSync(ctx.preloadPath)}`;
7
- });
8
- ipcMain.handle(AppChannel.GetBranding, async () => {
8
+ })
9
+ .handle(AppChannel.GetBranding, async () => {
9
10
  if (ctx.brandingProvider)
10
11
  return ctx.brandingProvider();
11
12
  return { appName: ctx.appName };
@@ -1,9 +1,10 @@
1
1
  export { registerAppIpc } from './app.js';
2
2
  export { registerSimulatorIpc } from './simulator.js';
3
3
  export { registerPanelsIpc } from './panels.js';
4
- export { registerPopoverIpc } from './popover.js';
5
- export { registerProjectsIpc } from './projects.js';
6
- export { registerSessionIpc } from './session.js';
7
- export { registerSettingsIpc } from './settings.js';
4
+ export { registerPopoverIpc, popoverModule } from './popover.js';
5
+ export { registerProjectsIpc, projectsModule } from './projects.js';
6
+ export { registerSessionIpc, sessionModule } from './session.js';
7
+ export { registerSettingsIpc, settingsModule } from './settings.js';
8
8
  export { registerToolbarIpc } from './toolbar.js';
9
+ export { simulatorModule } from './simulator-module.js';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1,9 +1,10 @@
1
1
  export { registerAppIpc } from './app.js';
2
2
  export { registerSimulatorIpc } from './simulator.js';
3
3
  export { registerPanelsIpc } from './panels.js';
4
- export { registerPopoverIpc } from './popover.js';
5
- export { registerProjectsIpc } from './projects.js';
6
- export { registerSessionIpc } from './session.js';
7
- export { registerSettingsIpc } from './settings.js';
4
+ export { registerPopoverIpc, popoverModule } from './popover.js';
5
+ export { registerProjectsIpc, projectsModule } from './projects.js';
6
+ export { registerSessionIpc, sessionModule } from './session.js';
7
+ export { registerSettingsIpc, settingsModule } from './settings.js';
8
8
  export { registerToolbarIpc } from './toolbar.js';
9
+ export { simulatorModule } from './simulator-module.js';
9
10
  //# sourceMappingURL=index.js.map
@@ -1,3 +1,4 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
- export declare function registerPanelsIpc(ctx: Pick<WorkbenchContext, 'panels' | 'views' | 'workspace'>): void;
2
+ import type { Disposable } from '../utils/disposable.js';
3
+ export declare function registerPanelsIpc(ctx: Pick<WorkbenchContext, 'panels' | 'views' | 'workspace' | 'senderPolicy'>): Disposable;
3
4
  //# sourceMappingURL=panels.d.ts.map
@@ -1,16 +1,21 @@
1
- import { ipcMain, webContents } from 'electron';
1
+ import { webContents } from 'electron';
2
2
  import { PanelChannel } from '../../shared/ipc-channels.js';
3
+ import { PanelEvalSchema, PanelSelectSchema, } from '../../shared/ipc-schemas.js';
4
+ import { validate } from '../utils/ipc-schema.js';
5
+ import { IpcRegistry } from '../utils/ipc-registry.js';
3
6
  const BUILTIN_PANELS = [
4
7
  { id: 'wxml', label: 'WXML' },
5
8
  { id: 'appdata', label: 'AppData' },
6
9
  { id: 'storage', label: 'Storage' },
7
10
  ];
8
11
  export function registerPanelsIpc(ctx) {
9
- ipcMain.handle(PanelChannel.List, () => {
12
+ return new IpcRegistry(ctx.senderPolicy)
13
+ .handle(PanelChannel.List, () => {
10
14
  return BUILTIN_PANELS
11
15
  .filter((p) => ctx.panels.includes(p.id));
12
- });
13
- ipcMain.handle(PanelChannel.Eval, async (_, expression) => {
16
+ })
17
+ .handle(PanelChannel.Eval, async (_event, ...args) => {
18
+ const [expression] = validate(PanelChannel.Eval, PanelEvalSchema, args);
14
19
  const simWcId = ctx.views.getSimulatorWebContentsId();
15
20
  if (!ctx.workspace.hasActiveSession() || simWcId == null)
16
21
  return undefined;
@@ -23,14 +28,14 @@ export function registerPanelsIpc(ctx) {
23
28
  catch {
24
29
  return undefined;
25
30
  }
26
- });
27
- ipcMain.handle(PanelChannel.Select, (_event, _panelId) => {
28
- // Hide the simulator overlay when switching to a built-in panel
31
+ })
32
+ .handle(PanelChannel.Select, (_event, ...args) => {
33
+ validate(PanelChannel.Select, PanelSelectSchema, args);
29
34
  if (ctx.views.hasSimulatorView() && ctx.views.isSimulatorAdded()) {
30
35
  ctx.views.hideSimulator();
31
36
  }
32
- });
33
- ipcMain.handle(PanelChannel.SelectSimulator, () => {
37
+ })
38
+ .handle(PanelChannel.SelectSimulator, () => {
34
39
  if (ctx.views.hasSimulatorView() && !ctx.views.isSimulatorAdded()) {
35
40
  ctx.views.showSimulator(ctx.views.getLastSimWidth());
36
41
  }
@@ -1,3 +1,6 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
- export declare function registerPopoverIpc(ctx: Pick<WorkbenchContext, 'views' | 'notify'>): void;
2
+ import type { WorkbenchModule } from '../services/module.js';
3
+ import type { Disposable } from '../utils/disposable.js';
4
+ export declare function registerPopoverIpc(ctx: Pick<WorkbenchContext, 'views' | 'notify' | 'senderPolicy'>): Disposable;
5
+ export declare const popoverModule: WorkbenchModule;
3
6
  //# sourceMappingURL=popover.d.ts.map
@@ -1,13 +1,23 @@
1
- import { ipcMain } from 'electron';
2
1
  import { PopoverChannel } from '../../shared/ipc-channels.js';
2
+ import { PopoverShowSchema, PopoverRelaunchSchema, } from '../../shared/ipc-schemas.js';
3
+ import { validate } from '../utils/ipc-schema.js';
4
+ import { IpcRegistry } from '../utils/ipc-registry.js';
3
5
  export function registerPopoverIpc(ctx) {
4
- ipcMain.handle(PopoverChannel.Show, (_event, data) => {
6
+ return new IpcRegistry(ctx.senderPolicy)
7
+ .handle(PopoverChannel.Show, (_event, ...args) => {
8
+ const [data] = validate(PopoverChannel.Show, PopoverShowSchema, args);
5
9
  ctx.views.showPopover(data);
6
- });
7
- ipcMain.handle(PopoverChannel.Hide, () => ctx.views.hidePopover());
8
- ipcMain.on(PopoverChannel.Relaunch, (_, newConfig) => {
10
+ })
11
+ .handle(PopoverChannel.Hide, () => {
12
+ ctx.views.hidePopover();
13
+ })
14
+ .on(PopoverChannel.Relaunch, (_event, ...args) => {
15
+ const [newConfig] = validate(PopoverChannel.Relaunch, PopoverRelaunchSchema, args);
9
16
  ctx.views.hidePopover();
10
17
  ctx.notify.popoverRelaunch(newConfig);
11
18
  });
12
19
  }
20
+ export const popoverModule = {
21
+ setup: (ctx) => registerPopoverIpc(ctx),
22
+ };
13
23
  //# sourceMappingURL=popover.js.map
@@ -1,3 +1,6 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
- export declare function registerProjectsIpc(ctx: Pick<WorkbenchContext, 'workspace' | 'mainWindow'>): void;
2
+ import type { WorkbenchModule } from '../services/module.js';
3
+ import type { Disposable } from '../utils/disposable.js';
4
+ export declare function registerProjectsIpc(ctx: Pick<WorkbenchContext, 'workspace' | 'mainWindow' | 'senderPolicy'>): Disposable;
5
+ export declare const projectsModule: WorkbenchModule;
3
6
  //# sourceMappingURL=projects.d.ts.map
@@ -1,15 +1,52 @@
1
- import { ipcMain, dialog } from 'electron';
1
+ import { dialog } from 'electron';
2
2
  import { ProjectsChannel, DialogChannel } from '../../shared/ipc-channels.js';
3
+ import { ProjectsAddSchema, ProjectsRemoveSchema, } from '../../shared/ipc-schemas.js';
4
+ import { validate } from '../utils/ipc-schema.js';
5
+ import { IpcRegistry } from '../utils/ipc-registry.js';
3
6
  export function registerProjectsIpc(ctx) {
4
- ipcMain.handle(ProjectsChannel.List, () => ctx.workspace.listProjects());
5
- ipcMain.handle(DialogChannel.OpenDirectory, async () => {
7
+ return new IpcRegistry(ctx.senderPolicy)
8
+ .handle(ProjectsChannel.List, () => {
9
+ return ctx.workspace.listProjects();
10
+ })
11
+ .handle(DialogChannel.OpenDirectory, async () => {
6
12
  const result = await dialog.showOpenDialog(ctx.mainWindow, {
7
13
  properties: ['openDirectory'],
8
14
  title: '选择小程序项目目录',
9
15
  });
10
16
  return result.canceled ? null : result.filePaths[0];
17
+ })
18
+ .handle(ProjectsChannel.Add, async (_event, ...args) => {
19
+ const [dirPath] = validate(ProjectsChannel.Add, ProjectsAddSchema, args);
20
+ const dirError = ctx.workspace.validateProjectDir(dirPath);
21
+ if (dirError) {
22
+ await dialog.showMessageBox(ctx.mainWindow, {
23
+ type: 'error',
24
+ title: '无法导入项目',
25
+ message: '该目录不是有效的小程序项目',
26
+ detail: dirError,
27
+ buttons: ['确定'],
28
+ });
29
+ throw new Error(dirError);
30
+ }
31
+ const duplicate = ctx.workspace.hasProject(dirPath);
32
+ const project = ctx.workspace.addProject(dirPath);
33
+ if (duplicate) {
34
+ await dialog.showMessageBox(ctx.mainWindow, {
35
+ type: 'info',
36
+ title: '项目已存在',
37
+ message: '该项目已在列表中',
38
+ detail: dirPath,
39
+ buttons: ['确定'],
40
+ });
41
+ }
42
+ return project;
43
+ })
44
+ .handle(ProjectsChannel.Remove, (_event, ...args) => {
45
+ const [dirPath] = validate(ProjectsChannel.Remove, ProjectsRemoveSchema, args);
46
+ return ctx.workspace.removeProject(dirPath);
11
47
  });
12
- ipcMain.handle(ProjectsChannel.Add, (_, dirPath) => ctx.workspace.addProject(dirPath));
13
- ipcMain.handle(ProjectsChannel.Remove, (_, dirPath) => ctx.workspace.removeProject(dirPath));
14
48
  }
49
+ export const projectsModule = {
50
+ setup: (ctx) => registerProjectsIpc(ctx),
51
+ };
15
52
  //# sourceMappingURL=projects.js.map
@@ -1,3 +1,6 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
- export declare function registerSessionIpc(ctx: Pick<WorkbenchContext, 'workspace'>): void;
2
+ import type { WorkbenchModule } from '../services/module.js';
3
+ import type { Disposable } from '../utils/disposable.js';
4
+ export declare function registerSessionIpc(ctx: Pick<WorkbenchContext, 'workspace' | 'senderPolicy'>): Disposable;
5
+ export declare const sessionModule: WorkbenchModule;
3
6
  //# sourceMappingURL=session.d.ts.map
@@ -1,10 +1,30 @@
1
- import { ipcMain } from 'electron';
2
1
  import { ProjectChannel } from '../../shared/ipc-channels.js';
2
+ import { ProjectGetCompileConfigSchema, ProjectGetPagesSchema, ProjectOpenSchema, ProjectSaveCompileConfigSchema, } from '../../shared/ipc-schemas.js';
3
+ import { validate } from '../utils/ipc-schema.js';
4
+ import { IpcRegistry } from '../utils/ipc-registry.js';
3
5
  export function registerSessionIpc(ctx) {
4
- ipcMain.handle(ProjectChannel.Open, (_, projectPath) => ctx.workspace.openProject(projectPath));
5
- ipcMain.handle(ProjectChannel.GetPages, (_, projectPath) => ctx.workspace.getProjectPages(projectPath));
6
- ipcMain.handle(ProjectChannel.GetCompileConfig, (_, projectPath) => ctx.workspace.getCompileConfig(projectPath));
7
- ipcMain.handle(ProjectChannel.SaveCompileConfig, (_, projectPath, config) => ctx.workspace.saveCompileConfig(projectPath, config));
8
- ipcMain.handle(ProjectChannel.Close, () => ctx.workspace.closeProject());
6
+ return new IpcRegistry(ctx.senderPolicy)
7
+ .handle(ProjectChannel.Open, (_, ...args) => {
8
+ const [projectPath] = validate(ProjectChannel.Open, ProjectOpenSchema, args);
9
+ return ctx.workspace.openProject(projectPath);
10
+ })
11
+ .handle(ProjectChannel.GetPages, (_, ...args) => {
12
+ const [projectPath] = validate(ProjectChannel.GetPages, ProjectGetPagesSchema, args);
13
+ return ctx.workspace.getProjectPages(projectPath);
14
+ })
15
+ .handle(ProjectChannel.GetCompileConfig, (_, ...args) => {
16
+ const [projectPath] = validate(ProjectChannel.GetCompileConfig, ProjectGetCompileConfigSchema, args);
17
+ return ctx.workspace.getCompileConfig(projectPath);
18
+ })
19
+ .handle(ProjectChannel.SaveCompileConfig, (_, ...args) => {
20
+ const [projectPath, config] = validate(ProjectChannel.SaveCompileConfig, ProjectSaveCompileConfigSchema, args);
21
+ return ctx.workspace.saveCompileConfig(projectPath, config);
22
+ })
23
+ .handle(ProjectChannel.Close, () => {
24
+ return ctx.workspace.closeProject();
25
+ });
9
26
  }
27
+ export const sessionModule = {
28
+ setup: (ctx) => registerSessionIpc(ctx),
29
+ };
10
30
  //# sourceMappingURL=session.js.map
@@ -1,3 +1,6 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
- export declare function registerSettingsIpc(ctx: Pick<WorkbenchContext, 'workbenchSettingsWindow' | 'views' | 'notify' | 'workspace' | 'mainWindow' | 'rendererDir'>): void;
2
+ import type { Disposable } from '../utils/disposable.js';
3
+ import type { WorkbenchModule } from '../services/module.js';
4
+ export declare function registerSettingsIpc(ctx: Pick<WorkbenchContext, 'workbenchSettingsWindow' | 'views' | 'notify' | 'workspace' | 'mainWindow' | 'rendererDir' | 'senderPolicy' | 'windows'>): Disposable;
5
+ export declare const settingsModule: WorkbenchModule;
3
6
  //# sourceMappingURL=settings.d.ts.map
@@ -1,20 +1,26 @@
1
- import { app, ipcMain } from 'electron';
1
+ import { app } from 'electron';
2
2
  import { openSettingsWindow } from '../app/launch.js';
3
3
  import { loadWorkbenchSettings, saveWorkbenchSettings, applyTheme, } from '../services/settings/index.js';
4
4
  import { WorkbenchSettingsChannel, SettingsChannel } from '../../shared/ipc-channels.js';
5
+ import { SettingsConfigChangedSchema, SettingsProjectSettingsChangedSchema, SettingsSetVisibleSchema, WorkbenchSettingsSaveSchema, WorkbenchSettingsSetThemeSchema, WorkbenchSettingsSetVisibleSchema, } from '../../shared/ipc-schemas.js';
6
+ import { validate } from '../utils/ipc-schema.js';
7
+ import { IpcRegistry } from '../utils/ipc-registry.js';
5
8
  export function registerSettingsIpc(ctx) {
6
- ipcMain.handle(WorkbenchSettingsChannel.Get, () => {
9
+ return new IpcRegistry(ctx.senderPolicy)
10
+ .handle(WorkbenchSettingsChannel.Get, () => {
7
11
  return loadWorkbenchSettings();
8
- });
9
- ipcMain.handle(WorkbenchSettingsChannel.Save, (_, settings) => {
12
+ })
13
+ .handle(WorkbenchSettingsChannel.Save, (_, ...args) => {
14
+ const [settings] = validate(WorkbenchSettingsChannel.Save, WorkbenchSettingsSaveSchema, args);
10
15
  saveWorkbenchSettings(settings);
11
16
  applyTheme(settings.theme ?? 'system');
12
17
  return { success: true };
13
- });
14
- ipcMain.handle(WorkbenchSettingsChannel.SetTheme, (_, theme) => {
18
+ })
19
+ .handle(WorkbenchSettingsChannel.SetTheme, (_, ...args) => {
20
+ const [theme] = validate(WorkbenchSettingsChannel.SetTheme, WorkbenchSettingsSetThemeSchema, args);
15
21
  applyTheme(theme);
16
- });
17
- ipcMain.handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
22
+ })
23
+ .handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
18
24
  const settings = loadWorkbenchSettings();
19
25
  const switchValue = app.commandLine.getSwitchValue('remote-debugging-port');
20
26
  const implicitDevDefault = !app.isPackaged && !settings.cdp.enabled && switchValue === '9222';
@@ -25,16 +31,18 @@ export function registerSettingsIpc(ctx) {
25
31
  activePort: switchValue ? parseInt(switchValue, 10) : null,
26
32
  implicitDevDefault,
27
33
  };
28
- });
29
- ipcMain.handle(WorkbenchSettingsChannel.SetVisible, async (_, visible) => {
34
+ })
35
+ .handle(WorkbenchSettingsChannel.SetVisible, async (_, ...args) => {
36
+ const [visible] = validate(WorkbenchSettingsChannel.SetVisible, WorkbenchSettingsSetVisibleSchema, args);
30
37
  if (visible) {
31
38
  await openSettingsWindow(ctx);
32
39
  }
33
40
  else {
34
- ctx.workbenchSettingsWindow?.close();
41
+ ctx.windows.closeSettingsWindow();
35
42
  }
36
- });
37
- ipcMain.handle(SettingsChannel.SetVisible, async (_, visible) => {
43
+ })
44
+ .handle(SettingsChannel.SetVisible, async (_, ...args) => {
45
+ const [visible] = validate(SettingsChannel.SetVisible, SettingsSetVisibleSchema, args);
38
46
  if (visible) {
39
47
  await ctx.views.showSettings();
40
48
  ctx.notify.settingsInit({
@@ -47,15 +55,20 @@ export function registerSettingsIpc(ctx) {
47
55
  ctx.views.hideSettings();
48
56
  ctx.notify.settingsClosed();
49
57
  }
50
- });
51
- ipcMain.on(SettingsChannel.ConfigChanged, (_, config) => {
58
+ })
59
+ .on(SettingsChannel.ConfigChanged, (_, ...args) => {
60
+ const [config] = validate(SettingsChannel.ConfigChanged, SettingsConfigChangedSchema, args);
52
61
  if (ctx.workspace.getProjectPath()) {
53
62
  ctx.workspace.saveCompileConfig(ctx.workspace.getProjectPath(), config);
54
63
  }
55
64
  ctx.notify.settingsChanged(config);
56
- });
57
- ipcMain.on(SettingsChannel.ProjectSettingsChanged, (_, patch) => {
65
+ })
66
+ .on(SettingsChannel.ProjectSettingsChanged, (_, ...args) => {
67
+ const [patch] = validate(SettingsChannel.ProjectSettingsChanged, SettingsProjectSettingsChangedSchema, args);
58
68
  ctx.workspace.updateProjectSettings(ctx.workspace.getProjectPath(), patch);
59
69
  });
60
70
  }
71
+ export const settingsModule = {
72
+ setup: (ctx) => registerSettingsIpc(ctx),
73
+ };
61
74
  //# sourceMappingURL=settings.js.map
@@ -0,0 +1,8 @@
1
+ import type { WorkbenchModule } from '../services/module.js';
2
+ /**
3
+ * The 'simulator' built-in module fans out into three IPC registrars
4
+ * (core simulator, panels, toolbar). Bundle them under a single module
5
+ * so app.ts only sees one entry per BuiltinModuleId.
6
+ */
7
+ export declare const simulatorModule: WorkbenchModule;
8
+ //# sourceMappingURL=simulator-module.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { DisposableRegistry } from '../utils/disposable.js';
2
+ import { registerSimulatorIpc } from './simulator.js';
3
+ import { registerPanelsIpc } from './panels.js';
4
+ import { registerToolbarIpc } from './toolbar.js';
5
+ /**
6
+ * The 'simulator' built-in module fans out into three IPC registrars
7
+ * (core simulator, panels, toolbar). Bundle them under a single module
8
+ * so app.ts only sees one entry per BuiltinModuleId.
9
+ */
10
+ export const simulatorModule = {
11
+ setup: (ctx) => {
12
+ const reg = new DisposableRegistry();
13
+ reg.add(registerSimulatorIpc(ctx));
14
+ reg.add(registerPanelsIpc(ctx));
15
+ reg.add(registerToolbarIpc(ctx));
16
+ return reg;
17
+ },
18
+ };
19
+ //# sourceMappingURL=simulator-module.js.map
@@ -1,3 +1,4 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
- export declare function registerSimulatorIpc(ctx: Pick<WorkbenchContext, 'views' | 'panels' | 'apiNamespaces' | 'notify'>): void;
2
+ import type { Disposable } from '../utils/disposable.js';
3
+ export declare function registerSimulatorIpc(ctx: Pick<WorkbenchContext, 'views' | 'panels' | 'apiNamespaces' | 'notify' | 'senderPolicy'>): Disposable;
3
4
  //# sourceMappingURL=simulator.d.ts.map
@@ -1,25 +1,39 @@
1
- import { ipcMain } from 'electron';
2
- import { SimulatorChannel, WorkbenchChannel } from '../../shared/ipc-channels.js';
1
+ import { SimulatorChannel, SimulatorCustomApiChannel, WorkbenchChannel } from '../../shared/ipc-channels.js';
2
+ import { SimulatorAttachSchema, SimulatorCustomApiInvokeSchema, SimulatorResizeSchema, SimulatorSetVisibleSchema, } from '../../shared/ipc-schemas.js';
3
+ import { simulatorApiRegistry } from '../services/simulator/custom-apis.js';
4
+ import { validate } from '../utils/ipc-schema.js';
5
+ import { IpcRegistry } from '../utils/ipc-registry.js';
3
6
  export function registerSimulatorIpc(ctx) {
4
- ipcMain.handle(SimulatorChannel.Attach, (_, simWcId, simWidth) => {
7
+ return new IpcRegistry(ctx.senderPolicy)
8
+ .handle(SimulatorChannel.Attach, (_, ...args) => {
9
+ const [simWcId, simWidth] = validate(SimulatorChannel.Attach, SimulatorAttachSchema, args);
5
10
  ctx.views.attachSimulator(simWcId, simWidth);
6
- });
7
- ipcMain.handle(SimulatorChannel.Detach, () => {
11
+ })
12
+ .handle(SimulatorChannel.Detach, () => {
8
13
  ctx.views.detachSimulator();
9
- });
10
- ipcMain.handle(SimulatorChannel.Resize, (_, simWidth) => {
14
+ })
15
+ .handle(SimulatorChannel.Resize, (_, ...args) => {
16
+ const [simWidth] = validate(SimulatorChannel.Resize, SimulatorResizeSchema, args);
11
17
  ctx.views.resize(simWidth);
12
- });
13
- ipcMain.handle(SimulatorChannel.SetVisible, (_, visible, simWidth) => {
18
+ })
19
+ .handle(SimulatorChannel.SetVisible, (_, ...args) => {
20
+ const [visible, simWidth] = validate(SimulatorChannel.SetVisible, SimulatorSetVisibleSchema, args);
14
21
  ctx.views.setVisible(visible, simWidth);
15
- });
16
- ipcMain.handle(WorkbenchChannel.GetPanelConfig, () => {
22
+ })
23
+ .handle(SimulatorCustomApiChannel.List, () => {
24
+ return simulatorApiRegistry.list();
25
+ })
26
+ .handle(SimulatorCustomApiChannel.Invoke, (_, ...args) => {
27
+ const [name, params] = validate(SimulatorCustomApiChannel.Invoke, SimulatorCustomApiInvokeSchema, args);
28
+ return simulatorApiRegistry.invoke(name, params);
29
+ })
30
+ .handle(WorkbenchChannel.GetPanelConfig, () => {
17
31
  return ctx.panels;
18
- });
19
- ipcMain.handle(WorkbenchChannel.GetApiNamespaces, () => {
32
+ })
33
+ .handle(WorkbenchChannel.GetApiNamespaces, () => {
20
34
  return ctx.apiNamespaces;
21
- });
22
- ipcMain.on(WorkbenchChannel.Reset, () => {
35
+ })
36
+ .on(WorkbenchChannel.Reset, () => {
23
37
  ctx.notify.workbenchReset();
24
38
  });
25
39
  }
@@ -1,3 +1,4 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
- export declare function registerToolbarIpc(ctx: Pick<WorkbenchContext, 'toolbarActions'>): void;
2
+ import type { Disposable } from '../utils/disposable.js';
3
+ export declare function registerToolbarIpc(ctx: Pick<WorkbenchContext, 'toolbarActions' | 'senderPolicy'>): Disposable;
3
4
  //# sourceMappingURL=toolbar.d.ts.map
@@ -1,7 +1,8 @@
1
- import { ipcMain } from 'electron';
2
1
  import { ToolbarChannel } from '../../shared/ipc-channels.js';
2
+ import { IpcRegistry } from '../utils/ipc-registry.js';
3
3
  export function registerToolbarIpc(ctx) {
4
- ipcMain.handle(ToolbarChannel.GetActions, async () => {
4
+ return new IpcRegistry(ctx.senderPolicy)
5
+ .handle(ToolbarChannel.GetActions, async () => {
5
6
  if (ctx.toolbarActions)
6
7
  return ctx.toolbarActions();
7
8
  return [];
@@ -9,9 +9,9 @@
9
9
  * This server handles these messages by delegating to the workbench context
10
10
  * (simulator webContents, IPC handlers, etc.) without modifying dimina upstream.
11
11
  */
12
- import { ipcMain } from 'electron';
13
12
  import { WebSocketServer } from 'ws';
14
13
  import { AutomationChannel, SimulatorChannel } from '../../../shared/ipc-channels.js';
14
+ import { IpcRegistry } from '../../utils/ipc-registry.js';
15
15
  import { getSimulator } from './exec.js';
16
16
  import { toolHandlers } from './handlers/tool.js';
17
17
  import { appHandlers } from './handlers/app.js';
@@ -25,7 +25,6 @@ const handlers = {
25
25
  ...elementHandlers,
26
26
  };
27
27
  let currentPort = null;
28
- let portIpcRegistered = false;
29
28
  export function getAutomationPort() {
30
29
  return currentPort;
31
30
  }
@@ -40,10 +39,14 @@ export async function startAutomationServer(ctx, port = 0) {
40
39
  // ws always binds via an http.Server, so address() is AddressInfo here.
41
40
  const resolvedPort = typeof addr === 'object' && addr ? addr.port : port;
42
41
  currentPort = resolvedPort;
43
- if (!portIpcRegistered) {
44
- portIpcRegistered = true;
45
- ipcMain.handle(AutomationChannel.GetPort, () => currentPort);
46
- }
42
+ // Gate AutomationChannel.GetPort with the workbench sender policy (only
43
+ // the main renderer + workbench settings/popover overlays are allowed to
44
+ // read the port — see createWorkbenchSenderPolicy; the simulator webview
45
+ // is intentionally NOT trusted for IPC invokes).
46
+ const portIpc = new IpcRegistry(ctx.senderPolicy);
47
+ portIpc.handle(AutomationChannel.GetPort, () => {
48
+ return currentPort;
49
+ });
47
50
  function broadcast(event) {
48
51
  const msg = JSON.stringify(event);
49
52
  for (const ws of clients) {
@@ -51,19 +54,55 @@ export async function startAutomationServer(ctx, port = 0) {
51
54
  ws.send(msg);
52
55
  }
53
56
  }
54
- // Forward simulator console logs as App.logAdded events
57
+ // Forward simulator console logs as App.logAdded events.
58
+ // Track the polling interval + stop timer + currently-attached sim and
59
+ // the named handler so we can fully detach on close() or sim destruction.
60
+ // Without this the listener accumulates across create/dispose cycles and
61
+ // across simulator rebuilds.
55
62
  let consoleForwardingSetup = false;
63
+ let pollInterval = null;
64
+ let pollStopTimer = null;
65
+ let attachedSim = null;
66
+ let ipcMessageHostHandler = null;
67
+ let simDestroyedHandler = null;
68
+ function detachConsoleForwarding() {
69
+ if (attachedSim) {
70
+ if (ipcMessageHostHandler) {
71
+ try {
72
+ if (!attachedSim.isDestroyed()) {
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ ;
75
+ attachedSim.removeListener('ipc-message-host', ipcMessageHostHandler);
76
+ }
77
+ }
78
+ catch { /* noop */ }
79
+ }
80
+ if (simDestroyedHandler) {
81
+ try {
82
+ if (!attachedSim.isDestroyed()) {
83
+ attachedSim.removeListener('destroyed', simDestroyedHandler);
84
+ }
85
+ }
86
+ catch { /* noop */ }
87
+ }
88
+ }
89
+ attachedSim = null;
90
+ ipcMessageHostHandler = null;
91
+ simDestroyedHandler = null;
92
+ }
56
93
  function setupConsoleForwarding() {
57
94
  if (consoleForwardingSetup)
58
95
  return;
59
96
  consoleForwardingSetup = true;
60
- // Poll for simulator and set up listener
61
- const check = setInterval(() => {
97
+ pollInterval = setInterval(() => {
62
98
  const sim = getSimulator(ctx);
63
99
  if (!sim)
64
100
  return;
65
- clearInterval(check);
66
- sim.on('ipc-message-host', (_event, channel, data) => {
101
+ if (pollInterval) {
102
+ clearInterval(pollInterval);
103
+ pollInterval = null;
104
+ }
105
+ const onIpcMessageHost = (_event, channel, data) => {
67
106
  if (channel === SimulatorChannel.Console) {
68
107
  const logData = data;
69
108
  broadcast({
@@ -71,10 +110,29 @@ export async function startAutomationServer(ctx, port = 0) {
71
110
  params: { type: logData.level || 'log', args: logData.args || [] },
72
111
  });
73
112
  }
74
- });
113
+ };
114
+ const onSimDestroyed = () => {
115
+ // Sim webContents is dead — drop refs (removeListener on a destroyed
116
+ // sender would throw) and allow re-attach to a new simulator.
117
+ attachedSim = null;
118
+ ipcMessageHostHandler = null;
119
+ simDestroyedHandler = null;
120
+ consoleForwardingSetup = false;
121
+ };
122
+ attachedSim = sim;
123
+ ipcMessageHostHandler = onIpcMessageHost;
124
+ simDestroyedHandler = onSimDestroyed;
125
+ sim.on('ipc-message-host', onIpcMessageHost);
126
+ sim.once('destroyed', onSimDestroyed);
75
127
  }, 1000);
76
128
  // Stop polling after 30s
77
- setTimeout(() => clearInterval(check), 30000);
129
+ pollStopTimer = setTimeout(() => {
130
+ if (pollInterval) {
131
+ clearInterval(pollInterval);
132
+ pollInterval = null;
133
+ }
134
+ pollStopTimer = null;
135
+ }, 30000);
78
136
  }
79
137
  wss.on('connection', (ws) => {
80
138
  clients.add(ws);
@@ -109,8 +167,25 @@ export async function startAutomationServer(ctx, port = 0) {
109
167
  });
110
168
  return {
111
169
  close: () => {
170
+ if (pollInterval) {
171
+ clearInterval(pollInterval);
172
+ pollInterval = null;
173
+ }
174
+ if (pollStopTimer) {
175
+ clearTimeout(pollStopTimer);
176
+ pollStopTimer = null;
177
+ }
178
+ detachConsoleForwarding();
179
+ for (const ws of clients) {
180
+ try {
181
+ ws.close();
182
+ }
183
+ catch { /* noop */ }
184
+ }
185
+ clients.clear();
112
186
  if (currentPort === resolvedPort)
113
187
  currentPort = null;
188
+ void portIpc.dispose();
114
189
  wss.close();
115
190
  },
116
191
  port: resolvedPort,
@@ -1,8 +1,6 @@
1
1
  import path from 'path';
2
- import { fileURLToPath } from 'url';
3
2
  import { app } from 'electron';
4
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
- const simulatorDir = path.resolve(__dirname, '../../simulator');
3
+ import { simulatorDir } from '../utils/paths.js';
6
4
  export const defaultAdapter = {
7
5
  async openProject(opts) {
8
6
  const diminaKit = await import('@dimina-kit/devkit');