@dimina-kit/devtools 0.3.2-dev.20260611063505 → 0.4.0-dev.20260612025610

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 (91) hide show
  1. package/README.md +56 -41
  2. package/dist/main/api.d.ts +4 -2
  3. package/dist/main/api.js +5 -1
  4. package/dist/main/app/app.d.ts +1 -5
  5. package/dist/main/app/app.js +25 -30
  6. package/dist/main/app/launch.d.ts +4 -4
  7. package/dist/main/app/launch.js +5 -17
  8. package/dist/main/index.bundle.js +535 -593
  9. package/dist/main/ipc/app.d.ts +1 -1
  10. package/dist/main/ipc/app.js +0 -7
  11. package/dist/main/ipc/index.d.ts +0 -2
  12. package/dist/main/ipc/index.js +0 -2
  13. package/dist/main/ipc/settings.d.ts +1 -1
  14. package/dist/main/ipc/settings.js +1 -13
  15. package/dist/main/ipc/simulator-module.d.ts +3 -3
  16. package/dist/main/ipc/simulator-module.js +3 -7
  17. package/dist/main/ipc/simulator.js +1 -12
  18. package/dist/main/menu/index.d.ts +2 -2
  19. package/dist/main/menu/index.js +4 -2
  20. package/dist/main/runtime/miniapp-runtime.d.ts +206 -28
  21. package/dist/main/runtime/miniapp-runtime.js +6 -11
  22. package/dist/main/services/layout/index.d.ts +0 -11
  23. package/dist/main/services/layout/index.js +0 -23
  24. package/dist/main/services/notifications/renderer-notifier.d.ts +0 -6
  25. package/dist/main/services/notifications/renderer-notifier.js +1 -10
  26. package/dist/main/services/service-host-pool/pool.d.ts +5 -4
  27. package/dist/main/services/views/host-toolbar-port-channel.d.ts +103 -0
  28. package/dist/main/services/views/host-toolbar-port-channel.js +248 -0
  29. package/dist/main/services/views/host-toolbar-session-runtime.d.ts +35 -0
  30. package/dist/main/services/views/host-toolbar-session-runtime.js +55 -0
  31. package/dist/main/services/views/view-manager.d.ts +80 -39
  32. package/dist/main/services/views/view-manager.js +153 -182
  33. package/dist/main/services/workbench-context.d.ts +13 -26
  34. package/dist/main/services/workbench-context.js +5 -14
  35. package/dist/main/services/workspace/workspace-service.d.ts +3 -7
  36. package/dist/main/services/workspace/workspace-service.js +34 -13
  37. package/dist/main/utils/paths.d.ts +11 -2
  38. package/dist/main/utils/paths.js +11 -2
  39. package/dist/main/windows/settings-window/index.d.ts +33 -0
  40. package/dist/main/windows/settings-window/index.js +59 -0
  41. package/dist/preload/index.d.ts +15 -0
  42. package/dist/preload/index.js +15 -0
  43. package/dist/preload/runtime/host-toolbar-port.d.ts +49 -0
  44. package/dist/preload/runtime/host-toolbar-port.js +151 -0
  45. package/dist/preload/runtime/host-toolbar-runtime.d.ts +41 -0
  46. package/dist/preload/runtime/host-toolbar-runtime.js +49 -0
  47. package/dist/preload/windows/{host-toolbar.cjs → host-toolbar-runtime.cjs} +103 -3
  48. package/dist/preload/windows/host-toolbar-runtime.cjs.map +7 -0
  49. package/dist/preload/windows/host-toolbar-runtime.d.ts +2 -0
  50. package/dist/preload/windows/host-toolbar-runtime.js +13 -0
  51. package/dist/preload/windows/simulator.cjs +0 -2
  52. package/dist/preload/windows/simulator.cjs.map +2 -2
  53. package/dist/preload/windows/simulator.js +0 -2
  54. package/dist/renderer/assets/constants-DI382mTP.js +2 -0
  55. package/dist/renderer/assets/index-DLzjz1wv.js +50 -0
  56. package/dist/renderer/assets/{input-CFHmbOgz.js → input-6fpCXV-n.js} +2 -2
  57. package/dist/renderer/assets/ipc-transport-Bs8Sf1B2.css +1 -0
  58. package/dist/renderer/assets/{ipc-transport-BkKKww3u.js → ipc-transport-D22e4dv7.js} +6 -6
  59. package/dist/renderer/assets/popover-CdGdKkkc.js +2 -0
  60. package/dist/renderer/assets/select-X0sbjwdP.js +2 -0
  61. package/dist/renderer/assets/{settings-Cl4IcWnG.js → settings-CS4jYaCw.js} +2 -2
  62. package/dist/renderer/assets/settings-api-CCWTAao_.js +2 -0
  63. package/dist/renderer/assets/workbenchSettings-Bpzaj3bm.js +8 -0
  64. package/dist/renderer/entries/main/index.html +7 -6
  65. package/dist/renderer/entries/popover/index.html +6 -6
  66. package/dist/renderer/entries/settings/index.html +5 -5
  67. package/dist/renderer/entries/workbench-settings/index.html +5 -5
  68. package/dist/shared/constants.d.ts +18 -0
  69. package/dist/shared/constants.js +18 -0
  70. package/dist/shared/ipc-channels.d.ts +10 -23
  71. package/dist/shared/ipc-channels.js +13 -34
  72. package/dist/shared/ipc-schemas.d.ts +0 -12
  73. package/dist/shared/ipc-schemas.js +0 -12
  74. package/dist/shared/types.d.ts +60 -76
  75. package/package.json +6 -6
  76. package/dist/main/ipc/panels.d.ts +0 -4
  77. package/dist/main/ipc/panels.js +0 -46
  78. package/dist/main/ipc/toolbar.d.ts +0 -4
  79. package/dist/main/ipc/toolbar.js +0 -21
  80. package/dist/main/services/toolbar/toolbar-store.d.ts +0 -23
  81. package/dist/main/services/toolbar/toolbar-store.js +0 -40
  82. package/dist/preload/windows/host-toolbar.cjs.map +0 -7
  83. package/dist/preload/windows/host-toolbar.d.ts +0 -2
  84. package/dist/preload/windows/host-toolbar.js +0 -12
  85. package/dist/renderer/assets/constants-CLAQX6VZ.js +0 -2
  86. package/dist/renderer/assets/index-CZQOpCvg.js +0 -50
  87. package/dist/renderer/assets/ipc-transport-Bv_BVu4T.css +0 -1
  88. package/dist/renderer/assets/popover-h1M7n3tt.js +0 -2
  89. package/dist/renderer/assets/select-kj0n1oYf.js +0 -2
  90. package/dist/renderer/assets/settings-api-CCwo2eOi.js +0 -2
  91. package/dist/renderer/assets/workbenchSettings-BzlpQnve.js +0 -8
@@ -4,31 +4,19 @@ import { defaultAdapter } from './default-adapter.js';
4
4
  import { createRendererNotifier, } from './notifications/renderer-notifier.js';
5
5
  import { createViewManager } from './views/view-manager.js';
6
6
  import { createWindowService } from './window-service.js';
7
+ import { openSettingsWindow } from '../windows/settings-window/index.js';
7
8
  import { createWorkspaceService, } from './workspace/workspace-service.js';
8
9
  import { createLocalProjectsProvider } from './projects/local-provider.js';
9
10
  import { createSimulatorApiRegistry, } from './simulator/custom-apis.js';
10
- import { createToolbarStore } from './toolbar/toolbar-store.js';
11
11
  import { resolveTemplates, sanitizeTemplates } from './projects/templates.js';
12
12
  import { BUILTIN_TEMPLATES } from './projects/builtin-templates.js';
13
- export function hasBuiltinPanel(ctx, panelId) {
14
- return ctx.panels.includes(panelId);
15
- }
16
- export function getDefaultTab(ctx) {
17
- if (hasBuiltinPanel(ctx, 'console'))
18
- return 'simulator';
19
- if (ctx.panels.length > 0)
20
- return ctx.panels[0];
21
- return 'simulator';
22
- }
23
13
  export function createWorkbenchContext(opts) {
24
14
  const ctx = {
25
15
  adapter: opts.adapter ?? defaultAdapter,
26
16
  preloadPath: opts.preloadPath,
27
17
  rendererDir: opts.rendererDir,
28
- panels: opts.panels ?? ['wxml', 'console', 'appdata', 'storage'],
29
18
  apiNamespaces: opts.apiNamespaces ?? [],
30
19
  appName: opts.appName ?? 'Dimina DevTools',
31
- headerHeight: opts.headerHeight ?? 40,
32
20
  brandingProvider: opts.brandingProvider,
33
21
  };
34
22
  ctx.registry = new DisposableRegistry();
@@ -39,10 +27,13 @@ export function createWorkbenchContext(opts) {
39
27
  ctx.connections = createConnectionRegistry();
40
28
  ctx.trustedWindowSenderIds = new Map();
41
29
  ctx.simulatorApis = createSimulatorApiRegistry();
42
- ctx.toolbar = createToolbarStore();
43
30
  ctx.windows = createWindowService(opts.mainWindow);
44
31
  ctx.views = createViewManager(ctx);
45
32
  ctx.notify = createRendererNotifier(ctx);
33
+ // Lazy closure (not a bound snapshot): reads ctx.windows/notify/rendererDir
34
+ // at call time through the live context, which structurally satisfies the
35
+ // helper's narrow OpenSettingsWindowDeps.
36
+ ctx.openSettings = () => openSettingsWindow(ctx);
46
37
  ctx.projectsProvider = opts.projectsProvider ?? createLocalProjectsProvider();
47
38
  ctx.projectTemplates = resolveTemplates(BUILTIN_TEMPLATES, opts.projectTemplates ?? [], opts.builtinTemplates ?? 'all');
48
39
  ctx.customCreateProjectDialog = opts.customCreateProjectDialog;
@@ -1,4 +1,4 @@
1
- import type { CompileConfig } from '../../../shared/types.js';
1
+ import type { AppInfo, CompileConfig, ProjectSession } from '../../../shared/types.js';
2
2
  import type { WorkbenchContext } from '../workbench-context.js';
3
3
  import type { Project, ProjectPages, ProjectSettings } from '../projects/project-repository.js';
4
4
  /**
@@ -9,7 +9,7 @@ import type { Project, ProjectPages, ProjectSettings } from '../projects/project
9
9
  export interface OpenProjectResult {
10
10
  success: boolean;
11
11
  port?: number;
12
- appInfo?: unknown;
12
+ appInfo?: AppInfo;
13
13
  error?: string;
14
14
  }
15
15
  /**
@@ -32,11 +32,7 @@ export interface WorkspaceService {
32
32
  validateProjectDir(dirPath: string): Promise<string | null>;
33
33
  openProject(projectPath: string): Promise<OpenProjectResult>;
34
34
  closeProject(): Promise<void>;
35
- getSession(): {
36
- close: () => Promise<void>;
37
- port: number;
38
- appInfo: unknown;
39
- } | null;
35
+ getSession(): ProjectSession | null;
40
36
  getProjectPath(): string;
41
37
  /**
42
38
  * The project path most recently torn down by `closeProject`, or '' if none
@@ -1,7 +1,15 @@
1
+ import { z } from 'zod';
1
2
  import * as repo from '../projects/project-repository.js';
2
3
  import { DEFAULT_COMPILE_CONFIG } from '../projects/types.js';
3
4
  import { clearSimulatorServicewechatReferer, setSimulatorServicewechatReferer, } from '../simulator/referer.js';
4
5
  import { loadWorkbenchSettings } from '../settings/index.js';
6
+ /**
7
+ * Runtime guard for the adapter-return boundary: `session.appInfo` must be an
8
+ * object carrying a string `appId` (the renderer derives its IPC scoping from
9
+ * it; a session without one cannot be driven). Loose: extra fields pass
10
+ * through untouched — only the contract-critical `appId` is enforced.
11
+ */
12
+ const SessionAppInfoSchema = z.looseObject({ appId: z.string() });
5
13
  /** Build a workspace service bound to the given workbench context. */
6
14
  export function createWorkspaceService(ctx) {
7
15
  let currentSession = null;
@@ -62,6 +70,12 @@ export function createWorkspaceService(ctx) {
62
70
  ? provider.validateProjectDir(dirPath)
63
71
  : null,
64
72
  async openProject(projectPath) {
73
+ clearSimulatorServicewechatReferer();
74
+ await disposeSession();
75
+ currentProjectPath = '';
76
+ // A fresh open starts a clean sandbox window — drop any previously
77
+ // recorded last-closed root so it can't accept writes after this point.
78
+ lastClosedProjectPath = '';
65
79
  // Reject obviously broken projects up front so we never spin up a
66
80
  // compile/server that the simulator will then fetch asset-by-asset.
67
81
  // Without this guard, deleted recent-list entries and typo'd CLI paths
@@ -76,18 +90,6 @@ export function createWorkspaceService(ctx) {
76
90
  sendStatus('error', dirError);
77
91
  return { success: false, error: dirError };
78
92
  }
79
- const gate = ctx.beforeOpenProject ? await ctx.beforeOpenProject(projectPath) : undefined;
80
- if (gate && !gate.allow) {
81
- const message = gate.userMessage ?? gate.reason;
82
- sendStatus('error', message);
83
- return { success: false, error: message };
84
- }
85
- clearSimulatorServicewechatReferer();
86
- await disposeSession();
87
- currentProjectPath = '';
88
- // A fresh open starts a clean sandbox window — drop any previously
89
- // recorded last-closed root so it can't accept writes after this point.
90
- lastClosedProjectPath = '';
91
93
  sendStatus('compiling', '正在编译...');
92
94
  const { compile } = loadWorkbenchSettings();
93
95
  let session;
@@ -105,13 +107,32 @@ export function createWorkspaceService(ctx) {
105
107
  sendStatus('error', String(err));
106
108
  return { success: false, error: String(err) };
107
109
  }
110
+ // Adapter-return boundary: enforce the AppInfo producer contract the
111
+ // moment the adapter resolves, BEFORE the session is recorded. A
112
+ // session without a string appId cannot be driven by the renderer, so
113
+ // it must never become the active session. The adapter already spun up
114
+ // live resources (compile watcher, dev-server port) — close them
115
+ // best-effort before reporting; the validation report is the law and a
116
+ // failing close() must not mask it (or escape as a throw).
117
+ if (!SessionAppInfoSchema.safeParse(session.appInfo).success) {
118
+ try {
119
+ await session.close();
120
+ }
121
+ catch (closeErr) {
122
+ console.warn('[workspace] closing appId-less adapter session failed (non-fatal):', closeErr);
123
+ }
124
+ const error = 'adapter returned session.appInfo without a string appId — ' +
125
+ 'the CompilationAdapter must supply appInfo.appId';
126
+ sendStatus('error', error);
127
+ return { success: false, error };
128
+ }
108
129
  currentSession = session;
109
130
  currentProjectPath = projectPath;
110
131
  bestEffort('updateLastOpened', () => {
111
132
  if (provider.updateLastOpened)
112
133
  provider.updateLastOpened(projectPath);
113
134
  });
114
- bestEffort('sendStatus', () => sendStatus('ready', gate?.statusMessage ?? '编译完成'));
135
+ bestEffort('sendStatus', () => sendStatus('ready', '编译完成'));
115
136
  bestEffort('applyReferer', () => applyRefererFromSession(session));
116
137
  return {
117
138
  success: true,
@@ -20,8 +20,17 @@ export declare function cjsSiblingPreloadPath(preloadPath: string): string;
20
20
  * contextBridge so the renderer never needs `window.require('electron')`.
21
21
  */
22
22
  export declare const mainPreloadPath: string;
23
- /** Preload for the host-controllable toolbar WCV — runs the reverse size-advertiser. */
24
- export declare const hostToolbarPreloadPath: string;
23
+ /**
24
+ * Session-registered toolbar-runtime preload bundle (the reverse
25
+ * size-advertiser, self-guarded by the `--dimina-host-toolbar` marker +
26
+ * `isMainFrame`). Registered on `session.defaultSession` via
27
+ * `registerPreloadScript({ type: 'frame', filePath })` — see
28
+ * `src/main/services/views/host-toolbar-session-runtime.ts`. Session preloads
29
+ * are always loaded as CommonJS, so this points at the `.cjs` bundle (same
30
+ * rule `cjsSiblingPreloadPath` exists for). Resolved from the package root so
31
+ * npm/pnpm node_modules and ASAR packaging alike get an absolute, correct path.
32
+ */
33
+ export declare const hostToolbarRuntimePreloadPath: string;
25
34
  export declare const simulatorDir: string;
26
35
  export declare function getRendererDir(): string;
27
36
  export declare function getPreloadDir(): string;
@@ -48,8 +48,17 @@ export function cjsSiblingPreloadPath(preloadPath) {
48
48
  * contextBridge so the renderer never needs `window.require('electron')`.
49
49
  */
50
50
  export const mainPreloadPath = path.join(devtoolsPackageRoot, 'dist/preload/windows/main.cjs');
51
- /** Preload for the host-controllable toolbar WCV — runs the reverse size-advertiser. */
52
- export const hostToolbarPreloadPath = path.join(devtoolsPackageRoot, 'dist/preload/windows/host-toolbar.cjs');
51
+ /**
52
+ * Session-registered toolbar-runtime preload bundle (the reverse
53
+ * size-advertiser, self-guarded by the `--dimina-host-toolbar` marker +
54
+ * `isMainFrame`). Registered on `session.defaultSession` via
55
+ * `registerPreloadScript({ type: 'frame', filePath })` — see
56
+ * `src/main/services/views/host-toolbar-session-runtime.ts`. Session preloads
57
+ * are always loaded as CommonJS, so this points at the `.cjs` bundle (same
58
+ * rule `cjsSiblingPreloadPath` exists for). Resolved from the package root so
59
+ * npm/pnpm node_modules and ASAR packaging alike get an absolute, correct path.
60
+ */
61
+ export const hostToolbarRuntimePreloadPath = path.join(devtoolsPackageRoot, 'dist/preload/windows/host-toolbar-runtime.cjs');
53
62
  export const simulatorDir = path.join(devtoolsPackageRoot, 'dist/simulator');
54
63
  export function getRendererDir() {
55
64
  return rendererDir;
@@ -1,3 +1,36 @@
1
+ import type { BrowserWindow } from 'electron';
2
+ import { type WorkbenchSettings } from '../../services/settings/index.js';
1
3
  export { createSettingsWindow } from './create.js';
2
4
  export { wireSettingsWindowEvents } from './events.js';
5
+ /**
6
+ * Exactly what `openSettingsWindow` needs — its OWN narrow deps interface,
7
+ * not a `Pick<WorkbenchContext, …>`: picking from the full context couples
8
+ * this helper to the whole context type (and made the contract's old
9
+ * `windows: object` pass-through promise unfulfillable). A full
10
+ * `WorkbenchContext` satisfies this structurally, so assembly points pass
11
+ * the context straight through.
12
+ */
13
+ export interface OpenSettingsWindowDeps {
14
+ /** Absolute path to the renderer dist directory (settings entry HTML). */
15
+ rendererDir: string;
16
+ windows: {
17
+ readonly mainWindow: BrowserWindow;
18
+ readonly settingsWindow: BrowserWindow | null;
19
+ setSettingsWindow(win: BrowserWindow | null): void;
20
+ };
21
+ notify: {
22
+ workbenchSettingsInit(window: BrowserWindow, payload: {
23
+ settings: WorkbenchSettings;
24
+ }): void;
25
+ };
26
+ }
27
+ /**
28
+ * Open (or re-focus) the standalone workbench-settings window. Reuses the
29
+ * live window when one is already registered; otherwise creates it, registers
30
+ * it on the window service, and wires its `closed` cleanup. Concurrent calls
31
+ * share a single in-flight creation (exactly one window; every caller still
32
+ * gets the show/focus/snapshot semantics). Always shows + focuses, then
33
+ * pushes the current settings snapshot into it.
34
+ */
35
+ export declare function openSettingsWindow(deps: OpenSettingsWindowDeps): Promise<void>;
3
36
  //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,62 @@
1
+ import { createSettingsWindow } from './create.js';
2
+ import { wireSettingsWindowEvents } from './events.js';
3
+ import { loadWorkbenchSettings, } from '../../services/settings/index.js';
1
4
  export { createSettingsWindow } from './create.js';
2
5
  export { wireSettingsWindowEvents } from './events.js';
6
+ /**
7
+ * In-flight creation guard, keyed by the windows registry (the identity that
8
+ * owns the `settingsWindow` slot). Overlapping `openSettingsWindow` calls
9
+ * before the first creation lands must share ONE creation — without this,
10
+ * both observe `settingsWindow === null` across the async construction
11
+ * boundary, build two BrowserWindows, and the loser is orphaned (alive,
12
+ * unreachable, never reused).
13
+ */
14
+ const inFlightCreations = new WeakMap();
15
+ async function getOrCreateSettingsWindow(deps) {
16
+ const existing = deps.windows.settingsWindow;
17
+ if (existing && !existing.isDestroyed())
18
+ return existing;
19
+ const inFlight = inFlightCreations.get(deps.windows);
20
+ if (inFlight)
21
+ return inFlight;
22
+ const creation = (async () => {
23
+ const win = await createSettingsWindow(deps.windows.mainWindow, deps.rendererDir);
24
+ deps.windows.setSettingsWindow(win);
25
+ wireSettingsWindowEvents(win, () => {
26
+ // Electron delivers 'closed' asynchronously: a just-destroyed window's
27
+ // late callback may arrive AFTER a successor was registered. Only the
28
+ // CURRENT registration's own close may clear the slot — a stale
29
+ // window's close must not drop a live successor.
30
+ if (deps.windows.settingsWindow === win) {
31
+ deps.windows.setSettingsWindow(null);
32
+ }
33
+ });
34
+ return win;
35
+ })();
36
+ inFlightCreations.set(deps.windows, creation);
37
+ try {
38
+ return await creation;
39
+ }
40
+ finally {
41
+ if (inFlightCreations.get(deps.windows) === creation) {
42
+ inFlightCreations.delete(deps.windows);
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * Open (or re-focus) the standalone workbench-settings window. Reuses the
48
+ * live window when one is already registered; otherwise creates it, registers
49
+ * it on the window service, and wires its `closed` cleanup. Concurrent calls
50
+ * share a single in-flight creation (exactly one window; every caller still
51
+ * gets the show/focus/snapshot semantics). Always shows + focuses, then
52
+ * pushes the current settings snapshot into it.
53
+ */
54
+ export async function openSettingsWindow(deps) {
55
+ const win = await getOrCreateSettingsWindow(deps);
56
+ win.show();
57
+ win.focus();
58
+ deps.notify.workbenchSettingsInit(win, {
59
+ settings: loadWorkbenchSettings(),
60
+ });
61
+ }
3
62
  //# sourceMappingURL=index.js.map
@@ -12,7 +12,22 @@
12
12
  export { installConsoleInstrumentation } from './instrumentation/console.js';
13
13
  export { createAppDataSource } from './instrumentation/app-data.js';
14
14
  export type { AppDataSnapshot } from './instrumentation/app-data.js';
15
+ /**
16
+ * @deprecated Will be removed in the next minor. Under the native-host (sole)
17
+ * runtime the simulator top frame is a top-level WebContentsView — the page DOM
18
+ * lives in child render-host `<webview>` guests, so this top-frame DOM observer
19
+ * only ever publishes `null`. Panel WXML flows over the main-process
20
+ * `SimulatorWxmlChannel` (`src/main/services/simulator-wxml`) instead.
21
+ */
15
22
  export { createWxmlSource } from './instrumentation/wxml.js';
23
+ /**
24
+ * @deprecated Will be removed in the next minor. The host's
25
+ * `miniapp-snapshot:push/pull` IPC has no receiving end under the native-host
26
+ * (sole) runtime: the simulator is a top-level WebContentsView without an
27
+ * embedder (its `sendToHost` fires into the void) and the renderer-side puller
28
+ * was removed. Panel data flows over the main-process `SimulatorWxmlChannel` /
29
+ * `SimulatorAppDataChannel` / `SimulatorStorageChannel` instead.
30
+ */
16
31
  export { createMiniappSnapshotHost } from './miniapp-snapshot/host.js';
17
32
  export type { MiniappSnapshotHost, MiniappSnapshotSource, SnapshotEnvelope, SnapshotSourceId, } from './miniapp-snapshot/types.js';
18
33
  export { installSimulatorBridge } from './runtime/bridge.js';
@@ -11,7 +11,22 @@
11
11
  */
12
12
  export { installConsoleInstrumentation } from './instrumentation/console.js';
13
13
  export { createAppDataSource } from './instrumentation/app-data.js';
14
+ /**
15
+ * @deprecated Will be removed in the next minor. Under the native-host (sole)
16
+ * runtime the simulator top frame is a top-level WebContentsView — the page DOM
17
+ * lives in child render-host `<webview>` guests, so this top-frame DOM observer
18
+ * only ever publishes `null`. Panel WXML flows over the main-process
19
+ * `SimulatorWxmlChannel` (`src/main/services/simulator-wxml`) instead.
20
+ */
14
21
  export { createWxmlSource } from './instrumentation/wxml.js';
22
+ /**
23
+ * @deprecated Will be removed in the next minor. The host's
24
+ * `miniapp-snapshot:push/pull` IPC has no receiving end under the native-host
25
+ * (sole) runtime: the simulator is a top-level WebContentsView without an
26
+ * embedder (its `sendToHost` fires into the void) and the renderer-side puller
27
+ * was removed. Panel data flows over the main-process `SimulatorWxmlChannel` /
28
+ * `SimulatorAppDataChannel` / `SimulatorStorageChannel` instead.
29
+ */
15
30
  export { createMiniappSnapshotHost } from './miniapp-snapshot/host.js';
16
31
  export { installSimulatorBridge } from './runtime/bridge.js';
17
32
  export { installCustomApisBridge } from './runtime/custom-apis.js';
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Preload side of the host-toolbar gated narrow channel.
3
+ *
4
+ * ── PUBLIC SURFACE EVOLUTION RULE ───────────────────────────────────────────
5
+ * `window.diminaHostToolbar` is a published page-facing API, typed by the
6
+ * exported `DiminaHostToolbarPageBridge` (src/main/runtime/miniapp-runtime.ts)
7
+ * — arbitrary host toolbar pages compile against it. Members may only be
8
+ * ADDED, never changed: any semantic change to an existing member (signature,
9
+ * return shape, queueing/throw behavior) ships under a NEW name instead.
10
+ * Exception: security fixes / compliance corrections may change existing
11
+ * member semantics, and MUST be called out in the release's version notes.
12
+ * Keep the exported bridge type in lockstep with what is exposed here.
13
+ *
14
+ * Receives the per-load MessagePort main transfers on `did-finish-load`
15
+ * (`ViewChannel.HostToolbarPort`, `event.ports[0]`) and bridges it to the page
16
+ * as `window.diminaHostToolbar` — EXACTLY `{ send, onMessage }`, functions
17
+ * only. The raw MessagePort never crosses into the main world: the page only
18
+ * ever talks through these two functions, so the `{ channel, payload }`
19
+ * envelope stays the single waist (same posture as the main side's
20
+ * host-toolbar-port-channel.ts).
21
+ *
22
+ * Ordering reality this module absorbs (spike .repro/wave3-spike):
23
+ * - The page script runs BEFORE the handshake can complete (the port is
24
+ * posted on did-finish-load). Page `send()`s issued before the port
25
+ * arrives go into a PENDING QUEUE and flush in order on handshake —
26
+ * without it the first message of every load is silently dropped. The
27
+ * queue is BOUNDED at `HOST_TOOLBAR_PENDING_LIMIT` (128): the toolbar page
28
+ * is arbitrary host content, and a page whose handshake never arrives must
29
+ * not grow main-world-driven memory without limit. Overflow drops the
30
+ * NEWEST send (boot sequences front-load their important messages; the
31
+ * first-comers survive), warns ONCE per load, and never throws into page
32
+ * code.
33
+ * - Page handlers likewise register before the port exists; the registry is
34
+ * module-level (per-load — the preload re-runs on every navigation) and is
35
+ * re-attached to each newly delivered port, so a same-load re-handshake
36
+ * keeps existing handlers alive. The LATER port wins for sends.
37
+ * - Inbound dispatch uses `addEventListener('message')` + `start()`
38
+ * (without `start()` a DOM MessagePort never delivers) and DROPS malformed
39
+ * envelopes without throwing.
40
+ */
41
+ /**
42
+ * Cap on pre-handshake queued sends. 128 comfortably covers any sane toolbar
43
+ * boot sequence while bounding what a page whose handshake never completes
44
+ * can make the isolated world retain. Overflow policy: drop the NEWEST
45
+ * envelope (queued first-comers survive), one console.warn per load.
46
+ */
47
+ export declare const HOST_TOOLBAR_PENDING_LIMIT = 128;
48
+ export declare function installHostToolbarPortBridge(): void;
49
+ //# sourceMappingURL=host-toolbar-port.d.ts.map
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Preload side of the host-toolbar gated narrow channel.
3
+ *
4
+ * ── PUBLIC SURFACE EVOLUTION RULE ───────────────────────────────────────────
5
+ * `window.diminaHostToolbar` is a published page-facing API, typed by the
6
+ * exported `DiminaHostToolbarPageBridge` (src/main/runtime/miniapp-runtime.ts)
7
+ * — arbitrary host toolbar pages compile against it. Members may only be
8
+ * ADDED, never changed: any semantic change to an existing member (signature,
9
+ * return shape, queueing/throw behavior) ships under a NEW name instead.
10
+ * Exception: security fixes / compliance corrections may change existing
11
+ * member semantics, and MUST be called out in the release's version notes.
12
+ * Keep the exported bridge type in lockstep with what is exposed here.
13
+ *
14
+ * Receives the per-load MessagePort main transfers on `did-finish-load`
15
+ * (`ViewChannel.HostToolbarPort`, `event.ports[0]`) and bridges it to the page
16
+ * as `window.diminaHostToolbar` — EXACTLY `{ send, onMessage }`, functions
17
+ * only. The raw MessagePort never crosses into the main world: the page only
18
+ * ever talks through these two functions, so the `{ channel, payload }`
19
+ * envelope stays the single waist (same posture as the main side's
20
+ * host-toolbar-port-channel.ts).
21
+ *
22
+ * Ordering reality this module absorbs (spike .repro/wave3-spike):
23
+ * - The page script runs BEFORE the handshake can complete (the port is
24
+ * posted on did-finish-load). Page `send()`s issued before the port
25
+ * arrives go into a PENDING QUEUE and flush in order on handshake —
26
+ * without it the first message of every load is silently dropped. The
27
+ * queue is BOUNDED at `HOST_TOOLBAR_PENDING_LIMIT` (128): the toolbar page
28
+ * is arbitrary host content, and a page whose handshake never arrives must
29
+ * not grow main-world-driven memory without limit. Overflow drops the
30
+ * NEWEST send (boot sequences front-load their important messages; the
31
+ * first-comers survive), warns ONCE per load, and never throws into page
32
+ * code.
33
+ * - Page handlers likewise register before the port exists; the registry is
34
+ * module-level (per-load — the preload re-runs on every navigation) and is
35
+ * re-attached to each newly delivered port, so a same-load re-handshake
36
+ * keeps existing handlers alive. The LATER port wins for sends.
37
+ * - Inbound dispatch uses `addEventListener('message')` + `start()`
38
+ * (without `start()` a DOM MessagePort never delivers) and DROPS malformed
39
+ * envelopes without throwing.
40
+ */
41
+ import { contextBridge, ipcRenderer } from 'electron';
42
+ import { ViewChannel } from '../../shared/ipc-channels.js';
43
+ const BRIDGE_KEY = 'diminaHostToolbar';
44
+ /**
45
+ * Cap on pre-handshake queued sends. 128 comfortably covers any sane toolbar
46
+ * boot sequence while bounding what a page whose handshake never completes
47
+ * can make the isolated world retain. Overflow policy: drop the NEWEST
48
+ * envelope (queued first-comers survive), one console.warn per load.
49
+ */
50
+ export const HOST_TOOLBAR_PENDING_LIMIT = 128;
51
+ /**
52
+ * Subscribe the handshake channel and expose the page bridge. Call ONLY from
53
+ * a passing toolbar-runtime guard (`activateHostToolbarRuntime`) — a failing
54
+ * guard must leave zero footprint (no bridge key, no IPC listener).
55
+ */
56
+ /**
57
+ * Entry-waist channel validation, parity with the main side's
58
+ * `hostToolbar.onMessage` guard (same TypeError, message names `channel`).
59
+ * Runs BEFORE any state is touched (no queue slot, no registry entry), in
60
+ * both port states — a page author's typo throws at the call site instead of
61
+ * vanishing into main's silent inbound drop.
62
+ */
63
+ function assertValidChannel(method, channel) {
64
+ if (typeof channel !== 'string' || channel === '') {
65
+ throw new TypeError(`diminaHostToolbar.${method}: channel must be a non-empty string`);
66
+ }
67
+ }
68
+ export function installHostToolbarPortBridge() {
69
+ let activePort = null;
70
+ const pending = [];
71
+ // One overflow warning per load (this installer runs once per document) —
72
+ // a runaway page send-loop must not get per-drop console spam.
73
+ let warnedPendingOverflow = false;
74
+ const handlers = [];
75
+ const dispatch = (data) => {
76
+ // Defensive symmetry with main: drop anything that is not an object
77
+ // envelope with a string channel — never throw in the dispatcher.
78
+ if (typeof data !== 'object' || data === null)
79
+ return;
80
+ const { channel, payload } = data;
81
+ if (typeof channel !== 'string')
82
+ return;
83
+ for (const entry of [...handlers]) {
84
+ if (entry.channel === channel)
85
+ entry.handler(payload);
86
+ }
87
+ };
88
+ const onPortMessage = (event) => {
89
+ dispatch(event.data);
90
+ };
91
+ ipcRenderer.on(ViewChannel.HostToolbarPort, (event) => {
92
+ const port = event.ports[0];
93
+ if (!port)
94
+ return;
95
+ // Same-load duplicate handshake: the LATER port wins. Main closed (or is
96
+ // about to close) its end of the old pair — detach and stop writing to it.
97
+ if (activePort) {
98
+ try {
99
+ activePort.removeEventListener('message', onPortMessage);
100
+ activePort.close();
101
+ }
102
+ catch {
103
+ /* already dead */
104
+ }
105
+ }
106
+ activePort = port;
107
+ // addEventListener (not onmessage) keeps the handler removable on
108
+ // re-handshake; it REQUIRES start() or inbound never delivers.
109
+ port.addEventListener('message', onPortMessage);
110
+ port.start();
111
+ // Flush sends issued before the port arrived, in order.
112
+ while (pending.length > 0) {
113
+ const envelope = pending.shift();
114
+ port.postMessage(envelope);
115
+ }
116
+ });
117
+ // EXACTLY { send, onMessage } — functions only; the port stays in the
118
+ // isolated world.
119
+ contextBridge.exposeInMainWorld(BRIDGE_KEY, {
120
+ send(channel, payload) {
121
+ assertValidChannel('send', channel);
122
+ const envelope = { channel, payload };
123
+ if (activePort) {
124
+ activePort.postMessage(envelope);
125
+ return;
126
+ }
127
+ // Bounded queue: drop the NEWEST send on overflow (FIFO first-comers
128
+ // survive), warn once per load, never throw into page code.
129
+ if (pending.length >= HOST_TOOLBAR_PENDING_LIMIT) {
130
+ if (!warnedPendingOverflow) {
131
+ warnedPendingOverflow = true;
132
+ console.warn(`[dimina-devtools] host-toolbar pending queue full (${HOST_TOOLBAR_PENDING_LIMIT}); ` +
133
+ 'dropping further pre-handshake send() calls until the port arrives');
134
+ }
135
+ return;
136
+ }
137
+ pending.push(envelope);
138
+ },
139
+ onMessage(channel, handler) {
140
+ assertValidChannel('onMessage', channel);
141
+ const entry = { channel, handler };
142
+ handlers.push(entry);
143
+ return () => {
144
+ const i = handlers.indexOf(entry);
145
+ if (i >= 0)
146
+ handlers.splice(i, 1);
147
+ };
148
+ },
149
+ });
150
+ }
151
+ //# sourceMappingURL=host-toolbar-port.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Session-resident host-toolbar framework runtime: GUARD + ACTIVATION.
3
+ *
4
+ * The toolbar-runtime preload is registered on `session.defaultSession` via
5
+ * `registerPreloadScript({ type: 'frame', … })`, so it executes in EVERY
6
+ * defaultSession renderer — the main window, settings/popover overlays, and
7
+ * (with `nodeIntegrationInSubFrames`) even subframes. This module is the guard
8
+ * that keeps it inert everywhere except the host-toolbar WCV's main frame:
9
+ *
10
+ * - the toolbar WCV's creation injects `HOST_TOOLBAR_RUNTIME_MARKER` into its
11
+ * process argv via `webPreferences.additionalArguments`;
12
+ * - the marker is PROCESS-level (subframes of the toolbar window carry it
13
+ * too — spike .repro/wave3-spike/RESULTS.md item 3), so the guard needs
14
+ * BOTH wings: `isMainFrame` AND `argv.includes(marker)`;
15
+ * - a renderer that fails the guard returns immediately with ZERO footprint
16
+ * (no advertiser, no listeners, no globals — spike item 4).
17
+ *
18
+ * Why session-resident at all: the advertiser used to ride the toolbar WCV's
19
+ * `webPreferences.preload`, so a host calling `setPreloadPath(<its own
20
+ * preload>)` REPLACED it and the strip height collapsed to 0. With the runtime
21
+ * on the session layer, the host preload and the framework runtime coexist.
22
+ */
23
+ /**
24
+ * Pure guard predicate: should the toolbar runtime activate in a renderer with
25
+ * this `argv` / `isMainFrame`? True only for the MAIN frame of a process whose
26
+ * argv carries the `'--dimina-host-toolbar'` marker.
27
+ */
28
+ export declare function shouldActivateHostToolbarRuntime(argv: readonly string[], isMainFrame: boolean): boolean;
29
+ /**
30
+ * Run the guard; only when it passes, install the height advertiser
31
+ * (`installHostToolbarAdvertiserWhenReady`) and the narrow-channel page
32
+ * bridge (`installHostToolbarPortBridge` — `window.diminaHostToolbar` +
33
+ * the MessagePort handshake listener). Returns whether the runtime
34
+ * activated. A failed guard installs NOTHING (zero footprint in non-toolbar
35
+ * windows and subframes: no advertiser, no bridge key, no IPC listener).
36
+ */
37
+ export declare function activateHostToolbarRuntime(env: {
38
+ argv: readonly string[];
39
+ isMainFrame: boolean;
40
+ }): boolean;
41
+ //# sourceMappingURL=host-toolbar-runtime.d.ts.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Session-resident host-toolbar framework runtime: GUARD + ACTIVATION.
3
+ *
4
+ * The toolbar-runtime preload is registered on `session.defaultSession` via
5
+ * `registerPreloadScript({ type: 'frame', … })`, so it executes in EVERY
6
+ * defaultSession renderer — the main window, settings/popover overlays, and
7
+ * (with `nodeIntegrationInSubFrames`) even subframes. This module is the guard
8
+ * that keeps it inert everywhere except the host-toolbar WCV's main frame:
9
+ *
10
+ * - the toolbar WCV's creation injects `HOST_TOOLBAR_RUNTIME_MARKER` into its
11
+ * process argv via `webPreferences.additionalArguments`;
12
+ * - the marker is PROCESS-level (subframes of the toolbar window carry it
13
+ * too — spike .repro/wave3-spike/RESULTS.md item 3), so the guard needs
14
+ * BOTH wings: `isMainFrame` AND `argv.includes(marker)`;
15
+ * - a renderer that fails the guard returns immediately with ZERO footprint
16
+ * (no advertiser, no listeners, no globals — spike item 4).
17
+ *
18
+ * Why session-resident at all: the advertiser used to ride the toolbar WCV's
19
+ * `webPreferences.preload`, so a host calling `setPreloadPath(<its own
20
+ * preload>)` REPLACED it and the strip height collapsed to 0. With the runtime
21
+ * on the session layer, the host preload and the framework runtime coexist.
22
+ */
23
+ import { HOST_TOOLBAR_RUNTIME_MARKER } from '../../shared/constants.js';
24
+ import { installHostToolbarAdvertiserWhenReady } from './host-toolbar-advertiser.js';
25
+ import { installHostToolbarPortBridge } from './host-toolbar-port.js';
26
+ /**
27
+ * Pure guard predicate: should the toolbar runtime activate in a renderer with
28
+ * this `argv` / `isMainFrame`? True only for the MAIN frame of a process whose
29
+ * argv carries the `'--dimina-host-toolbar'` marker.
30
+ */
31
+ export function shouldActivateHostToolbarRuntime(argv, isMainFrame) {
32
+ return isMainFrame && argv.includes(HOST_TOOLBAR_RUNTIME_MARKER);
33
+ }
34
+ /**
35
+ * Run the guard; only when it passes, install the height advertiser
36
+ * (`installHostToolbarAdvertiserWhenReady`) and the narrow-channel page
37
+ * bridge (`installHostToolbarPortBridge` — `window.diminaHostToolbar` +
38
+ * the MessagePort handshake listener). Returns whether the runtime
39
+ * activated. A failed guard installs NOTHING (zero footprint in non-toolbar
40
+ * windows and subframes: no advertiser, no bridge key, no IPC listener).
41
+ */
42
+ export function activateHostToolbarRuntime(env) {
43
+ if (!shouldActivateHostToolbarRuntime(env.argv, env.isMainFrame))
44
+ return false;
45
+ installHostToolbarAdvertiserWhenReady();
46
+ installHostToolbarPortBridge();
47
+ return true;
48
+ }
49
+ //# sourceMappingURL=host-toolbar-runtime.js.map