@dimina-kit/devtools 0.3.1-dev.20260517100756 → 0.3.1-dev.20260518054451
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 +18 -7
- package/dist/main/index.bundle.js +106 -27
- package/dist/main/ipc/session.js +9 -1
- package/dist/main/menu/index.js +11 -0
- package/dist/main/services/projects/thumbnail.d.ts +4 -0
- package/dist/main/services/projects/thumbnail.js +32 -0
- package/dist/main/services/workspace/workspace-service.d.ts +2 -0
- package/dist/main/services/workspace/workspace-service.js +20 -0
- package/dist/main/utils/sender-policy.d.ts +7 -5
- package/dist/main/utils/sender-policy.js +8 -6
- package/dist/preload/runtime/custom-apis.js +32 -3
- package/dist/preload/windows/simulator.js +26 -5
- package/dist/renderer/assets/index-BFdItKBK.js +46 -0
- package/dist/renderer/assets/{input-C5M5Wxn6.js → input-AH9UDYmj.js} +2 -2
- package/dist/renderer/assets/{ipc-transport-CUsl-fS2.js → ipc-transport-DNgBM6Qz.js} +2 -2
- package/dist/renderer/assets/ipc-transport-Dvmd5JZj.css +1 -0
- package/dist/renderer/assets/{popover-BSBPVO0o.js → popover-D1hoVYiY.js} +2 -2
- package/dist/renderer/assets/{select-B1tF3UAc.js → select-BgDHRj6r.js} +2 -2
- package/dist/renderer/assets/{settings-api-BjJalghB.js → settings-api-DBKlNKV3.js} +2 -2
- package/dist/renderer/assets/{settings-7w54H6p-.js → settings-m-tcmZy_.js} +2 -2
- package/dist/renderer/assets/{workbenchSettings-q2_-nc8t.js → workbenchSettings-CGacu-pI.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 +6 -0
- package/dist/shared/ipc-channels.js +19 -0
- package/dist/shared/ipc-schemas.d.ts +4 -0
- package/dist/shared/ipc-schemas.js +4 -0
- package/package.json +4 -4
- package/dist/renderer/assets/index-DW_0thr-.js +0 -46
- package/dist/renderer/assets/ipc-transport-CltRuvhi.css +0 -1
package/README.md
CHANGED
|
@@ -379,26 +379,37 @@ launch({ preloadPath: '/absolute/path/to/my-preload.js' })
|
|
|
379
379
|
|
|
380
380
|
> ⚠️ 使用本功能要求模拟器 preload 调用 `installCustomApisBridge()`(详见上方 "Preload Instrumentation")。内置 preload 已包含此调用;若你用了自定义 preload 又忘记调用,注册的 API 在小程序运行时**没有任何反应**,且不会报错。
|
|
381
381
|
|
|
382
|
+
### 命名约定
|
|
383
|
+
|
|
384
|
+
`name` 是 dimina service 层 `invokeAPI(name, opts)` 用的**裸名**,不带 `wx.` 前缀。注册之后小程序里通过 `wx.<name>(...)`(以及你在 `apiNamespaces` 配的其它命名空间)调用都会命中:
|
|
385
|
+
|
|
386
|
+
| 小程序里写 | `registerSimulatorApi` 的 name |
|
|
387
|
+
| --- | --- |
|
|
388
|
+
| `wx.login(...)` | `'login'` |
|
|
389
|
+
| `wx.request(...)` | `'request'` |
|
|
390
|
+
| `wx.getStorage(...)` 覆盖内置 | `'getStorage'`(小心:这会**屏蔽** dimina 容器的实现) |
|
|
391
|
+
|
|
382
392
|
```typescript
|
|
383
393
|
import { registerSimulatorApi } from '@dimina-kit/devtools/simulator-apis'
|
|
384
394
|
|
|
385
|
-
|
|
395
|
+
// 提供 wx.login 的实现(dimina 容器默认未实现,注册后才能用)
|
|
396
|
+
const dispose = registerSimulatorApi('login', async ({ success }) => {
|
|
386
397
|
// 主进程上下文:可调用内部 HTTP、读取凭证文件、访问原生模块
|
|
387
|
-
|
|
398
|
+
const code = await callInternalAuth()
|
|
399
|
+
return { code }
|
|
388
400
|
})
|
|
389
401
|
|
|
390
402
|
// 不再需要时取消注册(不传也行,进程退出会一起清理)
|
|
391
403
|
dispose()
|
|
392
404
|
```
|
|
393
405
|
|
|
394
|
-
|
|
406
|
+
### 行为约定
|
|
395
407
|
|
|
408
|
+
- **命中顺序**:小程序运行时按 `apiRegistry[name] → MiniApp 实例方法 → 扩展模块兜底` 查找,所以 `registerSimulatorApi` 注册的 name **优先于内置实现**——既能给"未实现"的 wx.* 补功能(如 `wx.login`),也能覆盖默认的 wx.* 行为(如 `wx.request`,dimina-kit 本身就在用这条路把 `request` 接到主进程 fetch)。
|
|
396
409
|
- 同名重复注册:后注册的覆盖前者,静默替换。
|
|
397
410
|
- `dispose()` 只会移除自己创建的那次注册——如果之后被别人覆盖了,旧的 disposer 是 no-op。
|
|
398
|
-
-
|
|
399
|
-
- Handler
|
|
400
|
-
|
|
401
|
-
对小程序侧来说,这些 name 和内置 API(`wx.getStorage` 等)走同一个 `AppManager.apiRegistry`,没有区别。
|
|
411
|
+
- 调用未注册且容器内也没有的 name:小程序侧拿到 reject,错误信息里带 name。
|
|
412
|
+
- Handler 可同步或异步返回;**返回值需可 JSON 序列化**(IPC 限制)。函数、Symbol、Map/Set、循环引用都会丢失。
|
|
402
413
|
|
|
403
414
|
---
|
|
404
415
|
|
|
@@ -73,9 +73,9 @@ function setupCdpPort() {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// src/main/app/app.ts
|
|
76
|
-
import { app as
|
|
77
|
-
import
|
|
78
|
-
import
|
|
76
|
+
import { app as app12, BrowserWindow as BrowserWindow3, nativeImage } from "electron";
|
|
77
|
+
import fs6 from "fs";
|
|
78
|
+
import path9 from "path";
|
|
79
79
|
|
|
80
80
|
// src/main/utils/paths.ts
|
|
81
81
|
import fs2 from "fs";
|
|
@@ -441,7 +441,9 @@ var ProjectChannel = {
|
|
|
441
441
|
GetPages: "project:getPages",
|
|
442
442
|
GetCompileConfig: "project:getCompileConfig",
|
|
443
443
|
SaveCompileConfig: "project:saveCompileConfig",
|
|
444
|
-
Status: "project:status"
|
|
444
|
+
Status: "project:status",
|
|
445
|
+
CaptureThumbnail: "project:captureThumbnail",
|
|
446
|
+
GetThumbnail: "project:getThumbnail"
|
|
445
447
|
};
|
|
446
448
|
var ProjectsChannel = {
|
|
447
449
|
List: "projects:list",
|
|
@@ -867,6 +869,9 @@ function createWindowService(mainWindow) {
|
|
|
867
869
|
};
|
|
868
870
|
}
|
|
869
871
|
|
|
872
|
+
// src/main/services/workspace/workspace-service.ts
|
|
873
|
+
import { webContents as webContents2 } from "electron";
|
|
874
|
+
|
|
870
875
|
// src/main/services/projects/project-repository.ts
|
|
871
876
|
import { app as app6 } from "electron";
|
|
872
877
|
import path7 from "path";
|
|
@@ -1054,6 +1059,38 @@ function updateProjectSettings(projectPath, patch) {
|
|
|
1054
1059
|
);
|
|
1055
1060
|
}
|
|
1056
1061
|
|
|
1062
|
+
// src/main/services/projects/thumbnail.ts
|
|
1063
|
+
import { createHash } from "crypto";
|
|
1064
|
+
import fs4 from "fs";
|
|
1065
|
+
import path8 from "path";
|
|
1066
|
+
import { app as app7 } from "electron";
|
|
1067
|
+
function getThumbnailDir() {
|
|
1068
|
+
return path8.join(app7.getPath("userData"), "thumbnails");
|
|
1069
|
+
}
|
|
1070
|
+
function hashProjectPath(projectPath) {
|
|
1071
|
+
return createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
|
|
1072
|
+
}
|
|
1073
|
+
function getThumbnailPath(projectPath) {
|
|
1074
|
+
return path8.join(getThumbnailDir(), `${hashProjectPath(projectPath)}.png`);
|
|
1075
|
+
}
|
|
1076
|
+
function saveThumbnail(projectPath, image) {
|
|
1077
|
+
const png = image.toPNG();
|
|
1078
|
+
const dir = getThumbnailDir();
|
|
1079
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1080
|
+
const filePath = getThumbnailPath(projectPath);
|
|
1081
|
+
fs4.writeFileSync(filePath, png);
|
|
1082
|
+
return `data:image/png;base64,${png.toString("base64")}`;
|
|
1083
|
+
}
|
|
1084
|
+
function loadThumbnail(projectPath) {
|
|
1085
|
+
const filePath = getThumbnailPath(projectPath);
|
|
1086
|
+
try {
|
|
1087
|
+
const buf = fs4.readFileSync(filePath);
|
|
1088
|
+
return `data:image/png;base64,${buf.toString("base64")}`;
|
|
1089
|
+
} catch {
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1057
1094
|
// src/main/services/workspace/workspace-service.ts
|
|
1058
1095
|
function createWorkspaceService(ctx) {
|
|
1059
1096
|
let currentSession = null;
|
|
@@ -1143,6 +1180,21 @@ function createWorkspaceService(ctx) {
|
|
|
1143
1180
|
getSession: () => currentSession,
|
|
1144
1181
|
getProjectPath: () => currentProjectPath,
|
|
1145
1182
|
hasActiveSession: () => currentSession !== null,
|
|
1183
|
+
async captureThumbnail(projectPath) {
|
|
1184
|
+
const simWcId = ctx.views.getSimulatorWebContentsId();
|
|
1185
|
+
if (!simWcId) return null;
|
|
1186
|
+
const wc = webContents2.fromId(simWcId);
|
|
1187
|
+
if (!wc || wc.isDestroyed()) return null;
|
|
1188
|
+
try {
|
|
1189
|
+
const image = await wc.capturePage();
|
|
1190
|
+
return saveThumbnail(projectPath, image);
|
|
1191
|
+
} catch {
|
|
1192
|
+
return null;
|
|
1193
|
+
}
|
|
1194
|
+
},
|
|
1195
|
+
getThumbnail(projectPath) {
|
|
1196
|
+
return loadThumbnail(projectPath);
|
|
1197
|
+
},
|
|
1146
1198
|
getProjectPages: (projectPath) => getProjectPages(projectPath),
|
|
1147
1199
|
getCompileConfig: (projectPath) => getCompileConfig(projectPath),
|
|
1148
1200
|
saveCompileConfig: (projectPath, config) => saveCompileConfig(projectPath, config),
|
|
@@ -1208,6 +1260,17 @@ function installAppMenu(ctx) {
|
|
|
1208
1260
|
{ role: "quit" }
|
|
1209
1261
|
]
|
|
1210
1262
|
},
|
|
1263
|
+
{
|
|
1264
|
+
label: "\u9879\u76EE",
|
|
1265
|
+
submenu: [
|
|
1266
|
+
{
|
|
1267
|
+
label: "\u6253\u5F00\u9879\u76EE",
|
|
1268
|
+
click: () => {
|
|
1269
|
+
ctx.notify.windowNavigateBack();
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
]
|
|
1273
|
+
},
|
|
1211
1274
|
{
|
|
1212
1275
|
label: "\u7F16\u8F91",
|
|
1213
1276
|
submenu: [
|
|
@@ -1249,7 +1312,7 @@ function installAppMenu(ctx) {
|
|
|
1249
1312
|
}
|
|
1250
1313
|
|
|
1251
1314
|
// src/main/ipc/app.ts
|
|
1252
|
-
import
|
|
1315
|
+
import fs5 from "fs";
|
|
1253
1316
|
|
|
1254
1317
|
// src/main/utils/ipc-registry.ts
|
|
1255
1318
|
import { ipcMain } from "electron";
|
|
@@ -1355,7 +1418,7 @@ var IpcRegistry = class {
|
|
|
1355
1418
|
// src/main/ipc/app.ts
|
|
1356
1419
|
function registerAppIpc(ctx) {
|
|
1357
1420
|
return new IpcRegistry(ctx.senderPolicy).handle(AppChannel.GetPreloadPath, () => {
|
|
1358
|
-
return `file://${
|
|
1421
|
+
return `file://${fs5.realpathSync(ctx.preloadPath)}`;
|
|
1359
1422
|
}).handle(AppChannel.GetBranding, async () => {
|
|
1360
1423
|
if (ctx.brandingProvider) return ctx.brandingProvider();
|
|
1361
1424
|
return { appName: ctx.appName };
|
|
@@ -1424,6 +1487,8 @@ var SettingsProjectSettingsChangedSchema = z.tuple([
|
|
|
1424
1487
|
})
|
|
1425
1488
|
]);
|
|
1426
1489
|
var PanelSelectSchema = z.tuple([z.string().min(1).max(200)]);
|
|
1490
|
+
var ProjectCaptureThumbnailSchema = z.tuple([AbsolutePath]);
|
|
1491
|
+
var ProjectGetThumbnailSchema = z.tuple([AbsolutePath]);
|
|
1427
1492
|
|
|
1428
1493
|
// src/main/services/simulator/custom-apis.ts
|
|
1429
1494
|
function createSimulatorApiRegistry() {
|
|
@@ -1478,7 +1543,7 @@ function registerSimulatorIpc(ctx) {
|
|
|
1478
1543
|
}
|
|
1479
1544
|
|
|
1480
1545
|
// src/main/ipc/panels.ts
|
|
1481
|
-
import { webContents as
|
|
1546
|
+
import { webContents as webContents3 } from "electron";
|
|
1482
1547
|
var BUILTIN_PANELS = [
|
|
1483
1548
|
{ id: "wxml", label: "WXML" },
|
|
1484
1549
|
{ id: "appdata", label: "AppData" },
|
|
@@ -1491,7 +1556,7 @@ function registerPanelsIpc(ctx) {
|
|
|
1491
1556
|
const [expression] = validate(PanelChannel.Eval, PanelEvalSchema, args);
|
|
1492
1557
|
const simWcId = ctx.views.getSimulatorWebContentsId();
|
|
1493
1558
|
if (!ctx.workspace.hasActiveSession() || simWcId == null) return void 0;
|
|
1494
|
-
const sim =
|
|
1559
|
+
const sim = webContents3.fromId(simWcId);
|
|
1495
1560
|
if (!sim || sim.isDestroyed()) return void 0;
|
|
1496
1561
|
try {
|
|
1497
1562
|
return await sim.executeJavaScript(expression);
|
|
@@ -1596,6 +1661,20 @@ function registerSessionIpc(ctx) {
|
|
|
1596
1661
|
return ctx.workspace.saveCompileConfig(projectPath, config);
|
|
1597
1662
|
}).handle(ProjectChannel.Close, () => {
|
|
1598
1663
|
return ctx.workspace.closeProject();
|
|
1664
|
+
}).handle(ProjectChannel.CaptureThumbnail, (_, ...args) => {
|
|
1665
|
+
const [projectPath] = validate(
|
|
1666
|
+
ProjectChannel.CaptureThumbnail,
|
|
1667
|
+
ProjectCaptureThumbnailSchema,
|
|
1668
|
+
args
|
|
1669
|
+
);
|
|
1670
|
+
return ctx.workspace.captureThumbnail(projectPath);
|
|
1671
|
+
}).handle(ProjectChannel.GetThumbnail, (_, ...args) => {
|
|
1672
|
+
const [projectPath] = validate(
|
|
1673
|
+
ProjectChannel.GetThumbnail,
|
|
1674
|
+
ProjectGetThumbnailSchema,
|
|
1675
|
+
args
|
|
1676
|
+
);
|
|
1677
|
+
return ctx.workspace.getThumbnail(projectPath);
|
|
1599
1678
|
});
|
|
1600
1679
|
}
|
|
1601
1680
|
var sessionModule = {
|
|
@@ -1603,7 +1682,7 @@ var sessionModule = {
|
|
|
1603
1682
|
};
|
|
1604
1683
|
|
|
1605
1684
|
// src/main/ipc/settings.ts
|
|
1606
|
-
import { app as
|
|
1685
|
+
import { app as app8 } from "electron";
|
|
1607
1686
|
function registerSettingsIpc(ctx) {
|
|
1608
1687
|
return new IpcRegistry(ctx.senderPolicy).handle(WorkbenchSettingsChannel.Get, () => {
|
|
1609
1688
|
return loadWorkbenchSettings();
|
|
@@ -1625,8 +1704,8 @@ function registerSettingsIpc(ctx) {
|
|
|
1625
1704
|
applyTheme(theme);
|
|
1626
1705
|
}).handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
|
|
1627
1706
|
const settings = loadWorkbenchSettings();
|
|
1628
|
-
const switchValue =
|
|
1629
|
-
const implicitDevDefault = !
|
|
1707
|
+
const switchValue = app8.commandLine.getSwitchValue("remote-debugging-port");
|
|
1708
|
+
const implicitDevDefault = !app8.isPackaged && !settings.cdp.enabled && switchValue === "9222";
|
|
1630
1709
|
return {
|
|
1631
1710
|
configured: settings.cdp.enabled,
|
|
1632
1711
|
port: settings.cdp.port,
|
|
@@ -1710,11 +1789,11 @@ var simulatorModule = {
|
|
|
1710
1789
|
import { WebSocketServer } from "ws";
|
|
1711
1790
|
|
|
1712
1791
|
// src/main/services/automation/exec.ts
|
|
1713
|
-
import { webContents as
|
|
1792
|
+
import { webContents as webContents4 } from "electron";
|
|
1714
1793
|
function getSimulator(ctx) {
|
|
1715
1794
|
const simWcId = ctx.views.getSimulatorWebContentsId();
|
|
1716
1795
|
if (!ctx.workspace.hasActiveSession() || simWcId == null) return null;
|
|
1717
|
-
const sim =
|
|
1796
|
+
const sim = webContents4.fromId(simWcId);
|
|
1718
1797
|
if (!sim || sim.isDestroyed()) return null;
|
|
1719
1798
|
return sim;
|
|
1720
1799
|
}
|
|
@@ -2880,7 +2959,7 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
|
|
|
2880
2959
|
}
|
|
2881
2960
|
|
|
2882
2961
|
// src/main/services/simulator-storage/index.ts
|
|
2883
|
-
import { app as
|
|
2962
|
+
import { app as app9, webContents as wcStatic } from "electron";
|
|
2884
2963
|
function isSimulatorWebview(wc) {
|
|
2885
2964
|
if (wc.isDestroyed()) return false;
|
|
2886
2965
|
if (wc.getType() !== "webview") return false;
|
|
@@ -3130,9 +3209,9 @@ function setupSimulatorStorage(host, options) {
|
|
|
3130
3209
|
wcSubs.delete(wc);
|
|
3131
3210
|
});
|
|
3132
3211
|
};
|
|
3133
|
-
|
|
3212
|
+
app9.on("web-contents-created", onWcCreated);
|
|
3134
3213
|
registry.add(() => {
|
|
3135
|
-
|
|
3214
|
+
app9.removeListener("web-contents-created", onWcCreated);
|
|
3136
3215
|
});
|
|
3137
3216
|
registry.add(async () => {
|
|
3138
3217
|
const subs = Array.from(wcSubs.values());
|
|
@@ -3193,7 +3272,7 @@ async function clearElementInspection() {
|
|
|
3193
3272
|
}
|
|
3194
3273
|
|
|
3195
3274
|
// src/main/services/update/update-manager.ts
|
|
3196
|
-
import { app as
|
|
3275
|
+
import { app as app10, shell as shell3 } from "electron";
|
|
3197
3276
|
var UpdateManager = class {
|
|
3198
3277
|
checker;
|
|
3199
3278
|
mainWindow;
|
|
@@ -3206,7 +3285,7 @@ var UpdateManager = class {
|
|
|
3206
3285
|
constructor(opts) {
|
|
3207
3286
|
this.checker = opts.checker;
|
|
3208
3287
|
this.mainWindow = opts.mainWindow;
|
|
3209
|
-
this.getCurrentVersion = opts.getCurrentVersion ?? (() =>
|
|
3288
|
+
this.getCurrentVersion = opts.getCurrentVersion ?? (() => app10.getVersion());
|
|
3210
3289
|
this.ipc = new IpcRegistry(opts.senderPolicy);
|
|
3211
3290
|
this.registerIpc();
|
|
3212
3291
|
this.startPeriodicCheck(opts.checkInterval ?? 60 * 60 * 1e3, opts.initialDelay ?? 5e3);
|
|
@@ -3247,7 +3326,7 @@ var UpdateManager = class {
|
|
|
3247
3326
|
install() {
|
|
3248
3327
|
if (this.downloadedPath) {
|
|
3249
3328
|
shell3.openPath(this.downloadedPath);
|
|
3250
|
-
|
|
3329
|
+
app10.quit();
|
|
3251
3330
|
}
|
|
3252
3331
|
}
|
|
3253
3332
|
/**
|
|
@@ -3280,7 +3359,7 @@ var UpdateManager = class {
|
|
|
3280
3359
|
};
|
|
3281
3360
|
|
|
3282
3361
|
// src/main/services/update/github-release-checker.ts
|
|
3283
|
-
import { app as
|
|
3362
|
+
import { app as app11 } from "electron";
|
|
3284
3363
|
|
|
3285
3364
|
// src/main/app/app.ts
|
|
3286
3365
|
var DEFAULT_MODULES = {
|
|
@@ -3329,7 +3408,7 @@ async function disposeContext(ctx) {
|
|
|
3329
3408
|
function createConfiguredMainWindow(config, rendererDir2) {
|
|
3330
3409
|
const mainWindow = createMainWindow({
|
|
3331
3410
|
title: config.appName ?? "Dimina DevTools",
|
|
3332
|
-
indexHtml:
|
|
3411
|
+
indexHtml: path9.join(rendererDir2, "entries/main/index.html"),
|
|
3333
3412
|
width: config.window?.width,
|
|
3334
3413
|
height: config.window?.height,
|
|
3335
3414
|
minWidth: config.window?.minWidth,
|
|
@@ -3384,7 +3463,7 @@ async function setupAutomation(instance) {
|
|
|
3384
3463
|
function setupMcp() {
|
|
3385
3464
|
const settings = loadWorkbenchSettings();
|
|
3386
3465
|
if (!settings.mcp.enabled) return null;
|
|
3387
|
-
const cdpPortSwitch =
|
|
3466
|
+
const cdpPortSwitch = app12.commandLine.getSwitchValue("remote-debugging-port");
|
|
3388
3467
|
const cdpPort2 = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
|
|
3389
3468
|
return startMcpServer(cdpPort2, settings.mcp.port);
|
|
3390
3469
|
}
|
|
@@ -3405,12 +3484,12 @@ function wireAppWindowEvents(config, instance) {
|
|
|
3405
3484
|
});
|
|
3406
3485
|
}
|
|
3407
3486
|
function enableDevRendererAutoReload(rendererDir2) {
|
|
3408
|
-
if (
|
|
3487
|
+
if (app12.isPackaged) {
|
|
3409
3488
|
return toDisposable(() => {
|
|
3410
3489
|
});
|
|
3411
3490
|
}
|
|
3412
3491
|
let reloadTimer = null;
|
|
3413
|
-
const watcher =
|
|
3492
|
+
const watcher = fs6.watch(rendererDir2, { recursive: true }, () => {
|
|
3414
3493
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
3415
3494
|
reloadTimer = setTimeout(() => {
|
|
3416
3495
|
for (const win of BrowserWindow3.getAllWindows()) {
|
|
@@ -3432,7 +3511,7 @@ function createWorkbenchApp(config = {}) {
|
|
|
3432
3511
|
setupCdpPort();
|
|
3433
3512
|
async function setup() {
|
|
3434
3513
|
if (setupPromise) return setupPromise;
|
|
3435
|
-
setupPromise =
|
|
3514
|
+
setupPromise = app12.whenReady().then(async () => {
|
|
3436
3515
|
applyTheme(loadWorkbenchSettings().theme);
|
|
3437
3516
|
const rendererDir2 = config.rendererDir ?? rendererDir;
|
|
3438
3517
|
const mainWindow = createConfiguredMainWindow(config, rendererDir2);
|
|
@@ -3496,7 +3575,7 @@ function createWorkbenchApp(config = {}) {
|
|
|
3496
3575
|
|
|
3497
3576
|
// src/main/windows/settings-window/create.ts
|
|
3498
3577
|
import { BrowserWindow as BrowserWindow4 } from "electron";
|
|
3499
|
-
import
|
|
3578
|
+
import path10 from "path";
|
|
3500
3579
|
async function createSettingsWindow(parent, rendererDir2) {
|
|
3501
3580
|
const win = new BrowserWindow4({
|
|
3502
3581
|
width: 420,
|
|
@@ -3514,7 +3593,7 @@ async function createSettingsWindow(parent, rendererDir2) {
|
|
|
3514
3593
|
}
|
|
3515
3594
|
});
|
|
3516
3595
|
applyNavigationHardening(win.webContents, rendererDir2);
|
|
3517
|
-
await win.loadFile(
|
|
3596
|
+
await win.loadFile(path10.join(rendererDir2, "entries/workbench-settings/index.html"));
|
|
3518
3597
|
return win;
|
|
3519
3598
|
}
|
|
3520
3599
|
|
package/dist/main/ipc/session.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ProjectChannel } from '../../shared/ipc-channels.js';
|
|
2
|
-
import { ProjectGetCompileConfigSchema, ProjectGetPagesSchema, ProjectOpenSchema, ProjectSaveCompileConfigSchema, } from '../../shared/ipc-schemas.js';
|
|
2
|
+
import { ProjectCaptureThumbnailSchema, ProjectGetCompileConfigSchema, ProjectGetPagesSchema, ProjectGetThumbnailSchema, ProjectOpenSchema, ProjectSaveCompileConfigSchema, } from '../../shared/ipc-schemas.js';
|
|
3
3
|
import { validate } from '../utils/ipc-schema.js';
|
|
4
4
|
import { IpcRegistry } from '../utils/ipc-registry.js';
|
|
5
5
|
export function registerSessionIpc(ctx) {
|
|
@@ -22,6 +22,14 @@ export function registerSessionIpc(ctx) {
|
|
|
22
22
|
})
|
|
23
23
|
.handle(ProjectChannel.Close, () => {
|
|
24
24
|
return ctx.workspace.closeProject();
|
|
25
|
+
})
|
|
26
|
+
.handle(ProjectChannel.CaptureThumbnail, (_, ...args) => {
|
|
27
|
+
const [projectPath] = validate(ProjectChannel.CaptureThumbnail, ProjectCaptureThumbnailSchema, args);
|
|
28
|
+
return ctx.workspace.captureThumbnail(projectPath);
|
|
29
|
+
})
|
|
30
|
+
.handle(ProjectChannel.GetThumbnail, (_, ...args) => {
|
|
31
|
+
const [projectPath] = validate(ProjectChannel.GetThumbnail, ProjectGetThumbnailSchema, args);
|
|
32
|
+
return ctx.workspace.getThumbnail(projectPath);
|
|
25
33
|
});
|
|
26
34
|
}
|
|
27
35
|
export const sessionModule = {
|
package/dist/main/menu/index.js
CHANGED
|
@@ -23,6 +23,17 @@ export function installAppMenu(ctx) {
|
|
|
23
23
|
{ role: 'quit' },
|
|
24
24
|
],
|
|
25
25
|
},
|
|
26
|
+
{
|
|
27
|
+
label: '项目',
|
|
28
|
+
submenu: [
|
|
29
|
+
{
|
|
30
|
+
label: '打开项目',
|
|
31
|
+
click: () => {
|
|
32
|
+
ctx.notify.windowNavigateBack();
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
26
37
|
{
|
|
27
38
|
label: '编辑',
|
|
28
39
|
submenu: [
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { app } from 'electron';
|
|
5
|
+
function getThumbnailDir() {
|
|
6
|
+
return path.join(app.getPath('userData'), 'thumbnails');
|
|
7
|
+
}
|
|
8
|
+
function hashProjectPath(projectPath) {
|
|
9
|
+
return createHash('sha256').update(projectPath).digest('hex').slice(0, 16);
|
|
10
|
+
}
|
|
11
|
+
function getThumbnailPath(projectPath) {
|
|
12
|
+
return path.join(getThumbnailDir(), `${hashProjectPath(projectPath)}.png`);
|
|
13
|
+
}
|
|
14
|
+
export function saveThumbnail(projectPath, image) {
|
|
15
|
+
const png = image.toPNG();
|
|
16
|
+
const dir = getThumbnailDir();
|
|
17
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
18
|
+
const filePath = getThumbnailPath(projectPath);
|
|
19
|
+
fs.writeFileSync(filePath, png);
|
|
20
|
+
return `data:image/png;base64,${png.toString('base64')}`;
|
|
21
|
+
}
|
|
22
|
+
export function loadThumbnail(projectPath) {
|
|
23
|
+
const filePath = getThumbnailPath(projectPath);
|
|
24
|
+
try {
|
|
25
|
+
const buf = fs.readFileSync(filePath);
|
|
26
|
+
return `data:image/png;base64,${buf.toString('base64')}`;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=thumbnail.js.map
|
|
@@ -39,6 +39,8 @@ 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): string | null;
|
|
42
44
|
getProjectPages(projectPath: string): ProjectPages;
|
|
43
45
|
getCompileConfig(projectPath: string): CompileConfig;
|
|
44
46
|
saveCompileConfig(projectPath: string, config: CompileConfig): void;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { webContents } from 'electron';
|
|
1
2
|
import * as repo from '../projects/project-repository.js';
|
|
2
3
|
import { clearSimulatorServicewechatReferer, setSimulatorServicewechatReferer, } from '../simulator/referer.js';
|
|
3
4
|
import { loadWorkbenchSettings } from '../settings/index.js';
|
|
5
|
+
import { saveThumbnail, loadThumbnail } from '../projects/thumbnail.js';
|
|
4
6
|
/** Build a workspace service bound to the given workbench context. */
|
|
5
7
|
export function createWorkspaceService(ctx) {
|
|
6
8
|
let currentSession = null;
|
|
@@ -96,6 +98,24 @@ export function createWorkspaceService(ctx) {
|
|
|
96
98
|
getSession: () => currentSession,
|
|
97
99
|
getProjectPath: () => currentProjectPath,
|
|
98
100
|
hasActiveSession: () => currentSession !== null,
|
|
101
|
+
async captureThumbnail(projectPath) {
|
|
102
|
+
const simWcId = ctx.views.getSimulatorWebContentsId();
|
|
103
|
+
if (!simWcId)
|
|
104
|
+
return null;
|
|
105
|
+
const wc = webContents.fromId(simWcId);
|
|
106
|
+
if (!wc || wc.isDestroyed())
|
|
107
|
+
return null;
|
|
108
|
+
try {
|
|
109
|
+
const image = await wc.capturePage();
|
|
110
|
+
return saveThumbnail(projectPath, image);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
getThumbnail(projectPath) {
|
|
117
|
+
return loadThumbnail(projectPath);
|
|
118
|
+
},
|
|
99
119
|
getProjectPages: (projectPath) => repo.getProjectPages(projectPath),
|
|
100
120
|
getCompileConfig: (projectPath) => repo.getCompileConfig(projectPath),
|
|
101
121
|
saveCompileConfig: (projectPath, config) => repo.saveCompileConfig(projectPath, config),
|
|
@@ -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
|
}
|
|
@@ -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() {
|