@dimina-kit/devtools 0.3.1-dev.20260517100756 → 0.3.1-dev.20260518114324
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 +138 -7
- package/dist/main/app/app.js +8 -0
- package/dist/main/index.bundle.js +374 -58
- package/dist/main/ipc/projects.d.ts +3 -1
- package/dist/main/ipc/projects.js +88 -4
- package/dist/main/ipc/session.js +9 -1
- package/dist/main/ipc/settings.js +8 -6
- package/dist/main/menu/index.js +11 -0
- package/dist/main/services/projects/builtin-templates.d.ts +3 -0
- package/dist/main/services/projects/builtin-templates.js +24 -0
- package/dist/main/services/projects/create-project-service.d.ts +7 -0
- package/dist/main/services/projects/create-project-service.js +84 -0
- package/dist/main/services/projects/index.d.ts +7 -0
- package/dist/main/services/projects/index.js +9 -0
- package/dist/main/services/projects/local-provider.d.ts +3 -0
- package/dist/main/services/projects/local-provider.js +25 -0
- package/dist/main/services/projects/templates.d.ts +21 -0
- package/dist/main/services/projects/templates.js +39 -0
- package/dist/main/services/projects/thumbnail.d.ts +8 -0
- package/dist/main/services/projects/thumbnail.js +37 -0
- package/dist/main/services/projects/types.d.ts +118 -0
- package/dist/main/services/projects/types.js +19 -0
- package/dist/main/services/settings/index.d.ts +7 -0
- package/dist/main/services/settings/index.js +4 -0
- package/dist/main/services/workbench-context.d.ts +35 -0
- package/dist/main/services/workbench-context.js +9 -0
- package/dist/main/services/workspace/workspace-service.d.ts +9 -7
- package/dist/main/services/workspace/workspace-service.js +81 -9
- package/dist/main/utils/sender-policy.d.ts +7 -5
- package/dist/main/utils/sender-policy.js +8 -6
- package/dist/main/windows/main-window/create.js +1 -1
- package/dist/preload/runtime/custom-apis.js +32 -3
- package/dist/preload/windows/simulator.js +26 -5
- package/dist/renderer/assets/index-Czz_bp6b.js +46 -0
- package/dist/renderer/assets/{input-C5M5Wxn6.js → input-DqVQNCx_.js} +2 -2
- package/dist/renderer/assets/{ipc-transport-CUsl-fS2.js → ipc-transport-CCHrCtDr.js} +2 -2
- package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +1 -0
- package/dist/renderer/assets/{popover-BSBPVO0o.js → popover-BRg4XI_w.js} +2 -2
- package/dist/renderer/assets/{select-B1tF3UAc.js → select-CvILHHZU.js} +2 -2
- package/dist/renderer/assets/{settings-7w54H6p-.js → settings-BFNfnYvU.js} +2 -2
- package/dist/renderer/assets/{settings-api-BjJalghB.js → settings-api-DOAgmBst.js} +2 -2
- package/dist/renderer/assets/{workbenchSettings-q2_-nc8t.js → workbenchSettings-DjVMgMMH.js} +2 -2
- package/dist/renderer/entries/main/index.html +5 -5
- package/dist/renderer/entries/popover/index.html +5 -5
- package/dist/renderer/entries/settings/index.html +5 -5
- package/dist/renderer/entries/workbench-settings/index.html +4 -4
- package/dist/shared/ipc-channels.d.ts +20 -1
- package/dist/shared/ipc-channels.js +27 -0
- package/dist/shared/ipc-schemas.d.ts +5 -0
- package/dist/shared/ipc-schemas.js +5 -0
- package/dist/shared/types.d.ts +80 -0
- package/package.json +9 -4
- package/templates/blank/app.js +6 -0
- package/templates/blank/app.json +11 -0
- package/templates/blank/app.wxss +5 -0
- package/templates/blank/pages/index/index.js +6 -0
- package/templates/blank/pages/index/index.json +3 -0
- package/templates/blank/pages/index/index.wxml +3 -0
- package/templates/blank/pages/index/index.wxss +11 -0
- package/templates/blank/project.config.json +13 -0
- package/templates/blank/sitemap.json +9 -0
- package/templates/taro-todo/app.js +1 -0
- package/templates/taro-todo/app.json +1 -0
- package/templates/taro-todo/app.wxss +1 -0
- package/templates/taro-todo/base.wxml +851 -0
- package/templates/taro-todo/common.js +1 -0
- package/templates/taro-todo/comp.js +1 -0
- package/templates/taro-todo/comp.json +1 -0
- package/templates/taro-todo/comp.wxml +2 -0
- package/templates/taro-todo/custom-wrapper.js +1 -0
- package/templates/taro-todo/custom-wrapper.json +1 -0
- package/templates/taro-todo/custom-wrapper.wxml +4 -0
- package/templates/taro-todo/pages/index/index.js +2 -0
- package/templates/taro-todo/pages/index/index.json +1 -0
- package/templates/taro-todo/pages/index/index.wxml +2 -0
- package/templates/taro-todo/pages/index/index.wxss +1 -0
- package/templates/taro-todo/project.config.json +44 -0
- package/templates/taro-todo/runtime.js +1 -0
- package/templates/taro-todo/taro.js +2 -0
- package/templates/taro-todo/utils.wxs +39 -0
- package/templates/taro-todo/vendors.js +2 -0
- package/dist/renderer/assets/index-DW_0thr-.js +0 -46
- package/dist/renderer/assets/ipc-transport-CltRuvhi.css +0 -1
|
@@ -14,6 +14,7 @@ const DEFAULTS = {
|
|
|
14
14
|
watch: true,
|
|
15
15
|
},
|
|
16
16
|
theme: 'system',
|
|
17
|
+
lastCreateBaseDir: null,
|
|
17
18
|
};
|
|
18
19
|
function getSettingsFile() {
|
|
19
20
|
return path.join(app.getPath('userData'), 'dimina-workbench-settings.json');
|
|
@@ -37,6 +38,9 @@ export function loadWorkbenchSettings() {
|
|
|
37
38
|
watch: data.compile?.watch ?? DEFAULTS.compile.watch,
|
|
38
39
|
},
|
|
39
40
|
theme,
|
|
41
|
+
lastCreateBaseDir: typeof data.lastCreateBaseDir === 'string'
|
|
42
|
+
? data.lastCreateBaseDir
|
|
43
|
+
: DEFAULTS.lastCreateBaseDir,
|
|
40
44
|
};
|
|
41
45
|
}
|
|
42
46
|
catch {
|
|
@@ -6,6 +6,8 @@ import { type RendererNotifier } from './notifications/renderer-notifier.js';
|
|
|
6
6
|
import { type ViewManager } from './views/view-manager.js';
|
|
7
7
|
import { type WindowService } from './window-service.js';
|
|
8
8
|
import { type WorkspaceService } from './workspace/workspace-service.js';
|
|
9
|
+
import { sanitizeTemplates } from './projects/templates.js';
|
|
10
|
+
import type { CreateProjectInput, ProjectsProvider, ProjectTemplate } from './projects/types.js';
|
|
9
11
|
/**
|
|
10
12
|
* Shared mutable state for the workbench application.
|
|
11
13
|
* Passed to each IPC module so they can read/write shared state without closures.
|
|
@@ -48,6 +50,30 @@ export interface WorkbenchContext {
|
|
|
48
50
|
notify: RendererNotifier;
|
|
49
51
|
/** Single source of truth for project + session + per-project settings */
|
|
50
52
|
workspace: WorkspaceService;
|
|
53
|
+
/**
|
|
54
|
+
* Pluggable backend for the project list. Defaults to a LocalProjectsProvider
|
|
55
|
+
* that persists to `<userData>/dimina-projects.json`. Hosts can inject a
|
|
56
|
+
* custom implementation (e.g. read from a remote workspace).
|
|
57
|
+
*/
|
|
58
|
+
projectsProvider: ProjectsProvider;
|
|
59
|
+
/**
|
|
60
|
+
* Merged template catalog used by the create-project flow. Combines the
|
|
61
|
+
* built-in templates with `WorkbenchAppConfig.projectTemplates` according
|
|
62
|
+
* to `WorkbenchAppConfig.builtinTemplates`. Keeps any `generate`
|
|
63
|
+
* functions intact — for IPC delivery, `sanitizeTemplates` is applied at
|
|
64
|
+
* the IPC boundary.
|
|
65
|
+
*/
|
|
66
|
+
projectTemplates: ProjectTemplate[];
|
|
67
|
+
/**
|
|
68
|
+
* Optional host hook for the "新建项目" dialog. When present, the
|
|
69
|
+
* `projects:openCreateDialog` IPC returns whatever this hook resolves to;
|
|
70
|
+
* when absent, the IPC returns null and the renderer shows the built-in
|
|
71
|
+
* dialog.
|
|
72
|
+
*/
|
|
73
|
+
customCreateProjectDialog?: (ctx: {
|
|
74
|
+
parentWindow: BrowserWindow;
|
|
75
|
+
templates: ProjectTemplate[];
|
|
76
|
+
}) => Promise<CreateProjectInput | null>;
|
|
51
77
|
/**
|
|
52
78
|
* Trust predicate consulted by `IpcRegistry` for every incoming IPC.
|
|
53
79
|
* Resolves the currently-trusted senders (main renderer + overlays)
|
|
@@ -67,8 +93,17 @@ export interface CreateContextOptions {
|
|
|
67
93
|
appName?: string;
|
|
68
94
|
toolbarActions?: WorkbenchContext['toolbarActions'];
|
|
69
95
|
brandingProvider?: WorkbenchContext['brandingProvider'];
|
|
96
|
+
/** Host-supplied project list backend. Defaults to LocalProjectsProvider. */
|
|
97
|
+
projectsProvider?: ProjectsProvider;
|
|
98
|
+
/** Templates injected by the host; same-id overrides a built-in. */
|
|
99
|
+
projectTemplates?: ProjectTemplate[];
|
|
100
|
+
/** Built-in template policy (default 'all'). */
|
|
101
|
+
builtinTemplates?: 'all' | 'none' | readonly string[];
|
|
102
|
+
/** Host-supplied "新建项目" dialog hook. */
|
|
103
|
+
customCreateProjectDialog?: WorkbenchContext['customCreateProjectDialog'];
|
|
70
104
|
}
|
|
71
105
|
export declare function hasBuiltinPanel(ctx: Pick<WorkbenchContext, 'panels'>, panelId: string): boolean;
|
|
72
106
|
export declare function getDefaultTab(ctx: Pick<WorkbenchContext, 'panels'>): string;
|
|
73
107
|
export declare function createWorkbenchContext(opts: CreateContextOptions): WorkbenchContext;
|
|
108
|
+
export { sanitizeTemplates };
|
|
74
109
|
//# sourceMappingURL=workbench-context.d.ts.map
|
|
@@ -5,6 +5,9 @@ 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
7
|
import { createWorkspaceService, } from './workspace/workspace-service.js';
|
|
8
|
+
import { createLocalProjectsProvider } from './projects/local-provider.js';
|
|
9
|
+
import { resolveTemplates, sanitizeTemplates } from './projects/templates.js';
|
|
10
|
+
import { BUILTIN_TEMPLATES } from './projects/builtin-templates.js';
|
|
8
11
|
export function hasBuiltinPanel(ctx, panelId) {
|
|
9
12
|
return ctx.panels.includes(panelId);
|
|
10
13
|
}
|
|
@@ -32,8 +35,14 @@ export function createWorkbenchContext(opts) {
|
|
|
32
35
|
ctx.windows = createWindowService(ctx.mainWindow);
|
|
33
36
|
ctx.views = createViewManager(ctx);
|
|
34
37
|
ctx.notify = createRendererNotifier(ctx);
|
|
38
|
+
ctx.projectsProvider = opts.projectsProvider ?? createLocalProjectsProvider();
|
|
39
|
+
ctx.projectTemplates = resolveTemplates(BUILTIN_TEMPLATES, opts.projectTemplates ?? [], opts.builtinTemplates ?? 'all');
|
|
40
|
+
ctx.customCreateProjectDialog = opts.customCreateProjectDialog;
|
|
35
41
|
ctx.workspace = createWorkspaceService(ctx);
|
|
36
42
|
ctx.senderPolicy = createWorkbenchSenderPolicy(ctx);
|
|
37
43
|
return ctx;
|
|
38
44
|
}
|
|
45
|
+
// Re-export at this stable module path so callers don't have to know about
|
|
46
|
+
// the projects/ subtree layout.
|
|
47
|
+
export { sanitizeTemplates };
|
|
39
48
|
//# sourceMappingURL=workbench-context.js.map
|
|
@@ -25,11 +25,11 @@ export interface OpenProjectResult {
|
|
|
25
25
|
* rather than touching the repository or adapter directly.
|
|
26
26
|
*/
|
|
27
27
|
export interface WorkspaceService {
|
|
28
|
-
listProjects(): Project[]
|
|
29
|
-
addProject(dirPath: string): Project
|
|
30
|
-
removeProject(dirPath: string): void
|
|
31
|
-
hasProject(dirPath: string): boolean
|
|
32
|
-
validateProjectDir(dirPath: string): string | null
|
|
28
|
+
listProjects(): Promise<Project[]>;
|
|
29
|
+
addProject(dirPath: string): Promise<Project>;
|
|
30
|
+
removeProject(dirPath: string): Promise<void>;
|
|
31
|
+
hasProject(dirPath: string): Promise<boolean>;
|
|
32
|
+
validateProjectDir(dirPath: string): Promise<string | null>;
|
|
33
33
|
openProject(projectPath: string): Promise<OpenProjectResult>;
|
|
34
34
|
closeProject(): Promise<void>;
|
|
35
35
|
getSession(): {
|
|
@@ -39,9 +39,11 @@ export interface WorkspaceService {
|
|
|
39
39
|
} | null;
|
|
40
40
|
getProjectPath(): string;
|
|
41
41
|
hasActiveSession(): boolean;
|
|
42
|
+
captureThumbnail(projectPath: string): Promise<string | null>;
|
|
43
|
+
getThumbnail(projectPath: string): Promise<string | null>;
|
|
42
44
|
getProjectPages(projectPath: string): ProjectPages;
|
|
43
|
-
getCompileConfig(projectPath: string): CompileConfig
|
|
44
|
-
saveCompileConfig(projectPath: string, config: CompileConfig): void
|
|
45
|
+
getCompileConfig(projectPath: string): Promise<CompileConfig>;
|
|
46
|
+
saveCompileConfig(projectPath: string, config: CompileConfig): Promise<void>;
|
|
45
47
|
getProjectSettings(projectPath: string): ProjectSettings;
|
|
46
48
|
updateProjectSettings(projectPath: string, patch: Partial<ProjectSettings>): void;
|
|
47
49
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { webContents } from 'electron';
|
|
1
2
|
import * as repo from '../projects/project-repository.js';
|
|
3
|
+
import { DEFAULT_COMPILE_CONFIG } from '../projects/types.js';
|
|
2
4
|
import { clearSimulatorServicewechatReferer, setSimulatorServicewechatReferer, } from '../simulator/referer.js';
|
|
3
5
|
import { loadWorkbenchSettings } from '../settings/index.js';
|
|
4
6
|
/** Build a workspace service bound to the given workbench context. */
|
|
@@ -37,12 +39,37 @@ export function createWorkspaceService(ctx) {
|
|
|
37
39
|
setSimulatorServicewechatReferer(appInfo.appId, typeof appInfo.version === 'string' ? appInfo.version : undefined);
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
|
-
|
|
42
|
+
// Delegate to the host-injected (or default) ProjectsProvider on the
|
|
43
|
+
// context. When the host omits an optional method, we apply a safe
|
|
44
|
+
// documented default — never silently fall back to `repo.*`, because a
|
|
45
|
+
// remote provider's project paths are not on the local filesystem.
|
|
46
|
+
//
|
|
47
|
+
// The `??` here is a back-compat path for hand-rolled WorkbenchContext
|
|
48
|
+
// values (used in a couple of legacy tests). createWorkbenchContext
|
|
49
|
+
// always installs LocalProjectsProvider, so production never enters this
|
|
50
|
+
// branch.
|
|
51
|
+
const provider = ctx.projectsProvider ?? {
|
|
41
52
|
listProjects: () => repo.listProjects(),
|
|
42
|
-
addProject: (
|
|
43
|
-
removeProject: (
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
addProject: (p) => repo.addProject(p),
|
|
54
|
+
removeProject: (p) => repo.removeProject(p),
|
|
55
|
+
validateProjectDir: (p) => repo.validateProjectDir(p),
|
|
56
|
+
updateLastOpened: (p) => repo.updateLastOpened(p),
|
|
57
|
+
getCompileConfig: (p) => repo.getCompileConfig(p),
|
|
58
|
+
saveCompileConfig: (p, c) => repo.saveCompileConfig(p, c),
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
listProjects: async () => provider.listProjects(),
|
|
62
|
+
addProject: async (dirPath) => provider.addProject(dirPath),
|
|
63
|
+
removeProject: async (dirPath) => {
|
|
64
|
+
await provider.removeProject(dirPath);
|
|
65
|
+
},
|
|
66
|
+
hasProject: async (dirPath) => {
|
|
67
|
+
const projects = await provider.listProjects();
|
|
68
|
+
return projects.some((p) => p.path === dirPath);
|
|
69
|
+
},
|
|
70
|
+
validateProjectDir: async (dirPath) => provider.validateProjectDir
|
|
71
|
+
? provider.validateProjectDir(dirPath)
|
|
72
|
+
: null,
|
|
46
73
|
async openProject(projectPath) {
|
|
47
74
|
clearSimulatorServicewechatReferer();
|
|
48
75
|
await disposeSession();
|
|
@@ -54,7 +81,9 @@ export function createWorkspaceService(ctx) {
|
|
|
54
81
|
// `fetch(…).then(r=>r.text()).then(JSON.parse)` pipeline dies on the
|
|
55
82
|
// dev server's HTML SPA fallback with
|
|
56
83
|
// `SyntaxError: Unexpected token '<', "<!doctype "…`.
|
|
57
|
-
const dirError =
|
|
84
|
+
const dirError = provider.validateProjectDir
|
|
85
|
+
? await provider.validateProjectDir(projectPath)
|
|
86
|
+
: null;
|
|
58
87
|
if (dirError) {
|
|
59
88
|
sendStatus('error', dirError);
|
|
60
89
|
return { success: false, error: dirError };
|
|
@@ -78,7 +107,10 @@ export function createWorkspaceService(ctx) {
|
|
|
78
107
|
}
|
|
79
108
|
currentSession = session;
|
|
80
109
|
currentProjectPath = projectPath;
|
|
81
|
-
bestEffort('updateLastOpened', () =>
|
|
110
|
+
bestEffort('updateLastOpened', () => {
|
|
111
|
+
if (provider.updateLastOpened)
|
|
112
|
+
provider.updateLastOpened(projectPath);
|
|
113
|
+
});
|
|
82
114
|
bestEffort('sendStatus', () => sendStatus('ready', '编译完成'));
|
|
83
115
|
bestEffort('applyReferer', () => applyRefererFromSession(session));
|
|
84
116
|
return {
|
|
@@ -96,9 +128,49 @@ export function createWorkspaceService(ctx) {
|
|
|
96
128
|
getSession: () => currentSession,
|
|
97
129
|
getProjectPath: () => currentProjectPath,
|
|
98
130
|
hasActiveSession: () => currentSession !== null,
|
|
131
|
+
async captureThumbnail(projectPath) {
|
|
132
|
+
const simWcId = ctx.views.getSimulatorWebContentsId();
|
|
133
|
+
if (!simWcId)
|
|
134
|
+
return null;
|
|
135
|
+
const wc = webContents.fromId(simWcId);
|
|
136
|
+
if (!wc || wc.isDestroyed())
|
|
137
|
+
return null;
|
|
138
|
+
try {
|
|
139
|
+
const image = await wc.capturePage();
|
|
140
|
+
const dataUrl = `data:image/png;base64,${image.toPNG().toString('base64')}`;
|
|
141
|
+
if (provider.saveThumbnail) {
|
|
142
|
+
await provider.saveThumbnail(projectPath, dataUrl);
|
|
143
|
+
}
|
|
144
|
+
// Always hand the renderer back the freshly-captured frame so the
|
|
145
|
+
// UI updates immediately even if the host's saveThumbnail is
|
|
146
|
+
// async or stores out-of-band.
|
|
147
|
+
return dataUrl;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
async getThumbnail(projectPath) {
|
|
154
|
+
if (provider.getThumbnail) {
|
|
155
|
+
return (await provider.getThumbnail(projectPath)) ?? null;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
},
|
|
159
|
+
// `getProjectPages` reads the project's own `app.json` from disk and is
|
|
160
|
+
// independent of the project registry — it stays on the repo helper.
|
|
99
161
|
getProjectPages: (projectPath) => repo.getProjectPages(projectPath),
|
|
100
|
-
getCompileConfig: (projectPath) =>
|
|
101
|
-
|
|
162
|
+
getCompileConfig: async (projectPath) => (provider.getCompileConfig
|
|
163
|
+
? await provider.getCompileConfig(projectPath)
|
|
164
|
+
: DEFAULT_COMPILE_CONFIG),
|
|
165
|
+
saveCompileConfig: async (projectPath, config) => {
|
|
166
|
+
if (provider.saveCompileConfig) {
|
|
167
|
+
await provider.saveCompileConfig(projectPath, config);
|
|
168
|
+
}
|
|
169
|
+
// No persistence when the host opts out; the renderer's edits then
|
|
170
|
+
// do not survive a reload, matching the documented contract.
|
|
171
|
+
},
|
|
172
|
+
// Per-project settings (uploadWithSourceMap etc.) live in the project's
|
|
173
|
+
// own `project.config.json`, not the registry — keep direct repo calls.
|
|
102
174
|
getProjectSettings: (projectPath) => repo.getProjectSettings(projectPath),
|
|
103
175
|
updateProjectSettings: (projectPath, patch) => repo.updateProjectSettings(projectPath, patch),
|
|
104
176
|
};
|
|
@@ -10,11 +10,13 @@ import type { SenderPolicy } from './ipc-registry.js';
|
|
|
10
10
|
* - the settings overlay view (when open)
|
|
11
11
|
* - the popover overlay view (when open)
|
|
12
12
|
*
|
|
13
|
-
* The simulator webview is intentionally NOT on this list
|
|
14
|
-
* (
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* The simulator webview is intentionally NOT on this list. Anything it
|
|
14
|
+
* needs from main (currently just the custom-apis bridge — see
|
|
15
|
+
* `installCustomApisBridge` in `src/preload/runtime/custom-apis.ts` and the
|
|
16
|
+
* matching `useCustomApiProxy` host hook) proxies through the trusted
|
|
17
|
+
* main-window renderer via `ipcRenderer.sendToHost` + `<webview>.send`.
|
|
18
|
+
* Keeping the guest off this list contains the blast radius if the
|
|
19
|
+
* simulator content is ever compromised.
|
|
18
20
|
*
|
|
19
21
|
* Any other sender — including a stale/destroyed sender or an unknown
|
|
20
22
|
* iframe — is rejected.
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
* - the settings overlay view (when open)
|
|
9
9
|
* - the popover overlay view (when open)
|
|
10
10
|
*
|
|
11
|
-
* The simulator webview is intentionally NOT on this list
|
|
12
|
-
* (
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* The simulator webview is intentionally NOT on this list. Anything it
|
|
12
|
+
* needs from main (currently just the custom-apis bridge — see
|
|
13
|
+
* `installCustomApisBridge` in `src/preload/runtime/custom-apis.ts` and the
|
|
14
|
+
* matching `useCustomApiProxy` host hook) proxies through the trusted
|
|
15
|
+
* main-window renderer via `ipcRenderer.sendToHost` + `<webview>.send`.
|
|
16
|
+
* Keeping the guest off this list contains the blast radius if the
|
|
17
|
+
* simulator content is ever compromised.
|
|
16
18
|
*
|
|
17
19
|
* Any other sender — including a stale/destroyed sender or an unknown
|
|
18
20
|
* iframe — is rejected.
|
|
@@ -35,7 +37,7 @@ export function createWorkbenchSenderPolicy(ctx) {
|
|
|
35
37
|
const popoverViewId = ctx.views.getPopoverWebContentsId();
|
|
36
38
|
if (popoverViewId != null && sender.id === popoverViewId)
|
|
37
39
|
return true;
|
|
38
|
-
// simulator
|
|
40
|
+
// simulator <webview> proxies through main-window renderer (see file header).
|
|
39
41
|
return false;
|
|
40
42
|
};
|
|
41
43
|
}
|
|
@@ -29,7 +29,7 @@ function configureSimulatorSession(simulatorPreloadPath) {
|
|
|
29
29
|
headers['access-control-allow-headers'] = ['*'];
|
|
30
30
|
headers['access-control-allow-methods'] = ['*'];
|
|
31
31
|
headers['Cross-Origin-Opener-Policy'] = ['same-origin'];
|
|
32
|
-
headers['Cross-Origin-Embedder-Policy'] = ['
|
|
32
|
+
headers['Cross-Origin-Embedder-Policy'] = ['credentialless'];
|
|
33
33
|
callback({ responseHeaders: headers });
|
|
34
34
|
});
|
|
35
35
|
}
|
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
import { contextBridge, ipcRenderer } from 'electron';
|
|
2
|
-
import {
|
|
2
|
+
import { SimulatorCustomApiBridgeChannel } from '../../shared/ipc-channels.js';
|
|
3
|
+
// The simulator <webview> is intentionally kept off the workbench sender-policy
|
|
4
|
+
// white-list, so it cannot reach `ipcMain.handle` directly. Instead the bridge
|
|
5
|
+
// asks the trusted main-window renderer to proxy the call: webview sends via
|
|
6
|
+
// `ipcRenderer.sendToHost`, host does the `ipcInvoke`, and posts the result
|
|
7
|
+
// back through `<webview>.send`. Requests and responses are correlated by id
|
|
8
|
+
// so concurrent invokes do not tangle.
|
|
3
9
|
function buildBridge() {
|
|
10
|
+
let nextId = 1;
|
|
11
|
+
const pending = new Map();
|
|
12
|
+
ipcRenderer.on(SimulatorCustomApiBridgeChannel.Response, (_event, payload) => {
|
|
13
|
+
const entry = pending.get(payload.id);
|
|
14
|
+
if (!entry)
|
|
15
|
+
return;
|
|
16
|
+
pending.delete(payload.id);
|
|
17
|
+
if ('error' in payload) {
|
|
18
|
+
entry.reject(new Error(payload.error));
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
entry.resolve(payload.result);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
const send = (req) => {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
pending.set(req.id, {
|
|
27
|
+
resolve: (value) => resolve(value),
|
|
28
|
+
reject,
|
|
29
|
+
});
|
|
30
|
+
ipcRenderer.sendToHost(SimulatorCustomApiBridgeChannel.Request, req);
|
|
31
|
+
});
|
|
32
|
+
};
|
|
4
33
|
return {
|
|
5
|
-
list: () =>
|
|
6
|
-
invoke: (name, params) =>
|
|
34
|
+
list: () => send({ id: nextId++, op: 'list' }),
|
|
35
|
+
invoke: (name, params) => send({ id: nextId++, op: 'invoke', name, params }),
|
|
7
36
|
};
|
|
8
37
|
}
|
|
9
38
|
export function installCustomApisBridge() {
|
|
@@ -187,9 +187,9 @@ var SimulatorChannel = {
|
|
|
187
187
|
AppData: "simulator:appdata",
|
|
188
188
|
AppDataAll: "simulator:appdata-all"
|
|
189
189
|
};
|
|
190
|
-
var
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
var SimulatorCustomApiBridgeChannel = {
|
|
191
|
+
Request: "simulator:custom-apis:bridge-request",
|
|
192
|
+
Response: "simulator:custom-apis:bridge-response"
|
|
193
193
|
};
|
|
194
194
|
var BridgeChannel = {
|
|
195
195
|
WxmlRefreshRequest: "wxml:refresh:request",
|
|
@@ -199,9 +199,30 @@ var BridgeChannel = {
|
|
|
199
199
|
|
|
200
200
|
// src/preload/runtime/custom-apis.ts
|
|
201
201
|
function buildBridge() {
|
|
202
|
+
let nextId = 1;
|
|
203
|
+
const pending = /* @__PURE__ */ new Map();
|
|
204
|
+
import_electron2.ipcRenderer.on(SimulatorCustomApiBridgeChannel.Response, (_event, payload) => {
|
|
205
|
+
const entry = pending.get(payload.id);
|
|
206
|
+
if (!entry) return;
|
|
207
|
+
pending.delete(payload.id);
|
|
208
|
+
if ("error" in payload) {
|
|
209
|
+
entry.reject(new Error(payload.error));
|
|
210
|
+
} else {
|
|
211
|
+
entry.resolve(payload.result);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
const send = (req) => {
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
pending.set(req.id, {
|
|
217
|
+
resolve: (value) => resolve(value),
|
|
218
|
+
reject
|
|
219
|
+
});
|
|
220
|
+
import_electron2.ipcRenderer.sendToHost(SimulatorCustomApiBridgeChannel.Request, req);
|
|
221
|
+
});
|
|
222
|
+
};
|
|
202
223
|
return {
|
|
203
|
-
list: () =>
|
|
204
|
-
invoke: (name, params) =>
|
|
224
|
+
list: () => send({ id: nextId++, op: "list" }),
|
|
225
|
+
invoke: (name, params) => send({ id: nextId++, op: "invoke", name, params })
|
|
205
226
|
};
|
|
206
227
|
}
|
|
207
228
|
function installCustomApisBridge() {
|