@dimina-kit/devtools 0.3.1 → 0.3.2-dev.20260522110734
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 +298 -133
- package/dist/main/api.d.ts +6 -2
- package/dist/main/api.js +5 -2
- package/dist/main/app/app.d.ts +1 -1
- package/dist/main/app/app.js +84 -36
- package/dist/main/app/bootstrap.js +2 -1
- package/dist/main/app/launch.d.ts +2 -2
- package/dist/main/app/launch.js +10 -8
- package/dist/main/index.bundle.js +1567 -477
- package/dist/main/ipc/app.d.ts +2 -1
- package/dist/main/ipc/app.js +5 -4
- package/dist/main/ipc/index.d.ts +5 -4
- package/dist/main/ipc/index.js +5 -4
- package/dist/main/ipc/panels.d.ts +2 -1
- package/dist/main/ipc/panels.js +16 -13
- package/dist/main/ipc/popover.d.ts +4 -1
- package/dist/main/ipc/popover.js +15 -5
- package/dist/main/ipc/projects.d.ts +6 -1
- package/dist/main/ipc/projects.js +127 -6
- package/dist/main/ipc/session.d.ts +4 -1
- package/dist/main/ipc/session.js +34 -6
- package/dist/main/ipc/settings.d.ts +4 -1
- package/dist/main/ipc/settings.js +51 -23
- package/dist/main/ipc/simulator-module.d.ts +8 -0
- package/dist/main/ipc/simulator-module.js +19 -0
- package/dist/main/ipc/simulator.d.ts +2 -1
- package/dist/main/ipc/simulator.js +23 -18
- package/dist/main/ipc/toolbar.d.ts +2 -1
- package/dist/main/ipc/toolbar.js +3 -2
- package/dist/main/menu/index.js +11 -0
- package/dist/main/services/automation/exec.d.ts +2 -0
- package/dist/main/services/automation/exec.js +7 -9
- package/dist/main/services/automation/handlers/app.js +37 -51
- package/dist/main/services/automation/handlers/element.js +27 -41
- package/dist/main/services/automation/index.js +88 -13
- package/dist/main/services/default-adapter.js +3 -3
- package/dist/main/services/mcp/index.d.ts +1 -0
- package/dist/main/services/mcp/index.js +1 -0
- package/dist/main/services/mcp/server.d.ts +2 -1
- package/dist/main/services/mcp/server.js +10 -0
- package/dist/main/services/mcp/status.d.ts +10 -0
- package/dist/main/services/mcp/status.js +14 -0
- package/dist/main/services/mcp/target-manager.js +2 -1
- package/dist/main/services/module.d.ts +18 -0
- package/dist/main/services/module.js +2 -0
- package/dist/main/services/notifications/renderer-notifier.d.ts +5 -3
- package/dist/main/services/notifications/renderer-notifier.js +3 -6
- 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/project-repository.d.ts +1 -1
- package/dist/main/services/projects/project-repository.js +3 -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 +11 -0
- package/dist/main/services/settings/index.js +18 -2
- package/dist/main/services/simulator/custom-apis.d.ts +10 -0
- package/dist/main/services/simulator/custom-apis.js +26 -0
- package/dist/main/services/simulator-storage/index.d.ts +22 -1
- package/dist/main/services/simulator-storage/index.js +334 -97
- package/dist/main/services/update/update-manager.d.ts +16 -1
- package/dist/main/services/update/update-manager.js +18 -15
- package/dist/main/services/views/view-manager.d.ts +7 -7
- package/dist/main/services/views/view-manager.js +53 -29
- package/dist/main/services/window-service.d.ts +24 -0
- package/dist/main/services/window-service.js +26 -0
- package/dist/main/services/workbench-context.d.ts +49 -2
- package/dist/main/services/workbench-context.js +15 -2
- package/dist/main/services/workspace/workspace-service.d.ts +9 -6
- package/dist/main/services/workspace/workspace-service.js +71 -11
- package/dist/main/simulator-apis.d.ts +23 -0
- package/dist/main/simulator-apis.js +24 -0
- package/dist/main/utils/disposable.d.ts +13 -0
- package/dist/main/utils/disposable.js +60 -0
- package/dist/main/utils/ipc-registry.d.ts +39 -0
- package/dist/main/utils/ipc-registry.js +109 -0
- package/dist/main/utils/ipc-schema.d.ts +28 -0
- package/dist/main/utils/ipc-schema.js +38 -0
- package/dist/main/utils/paths.d.ts +6 -0
- package/dist/main/utils/paths.js +6 -0
- package/dist/main/utils/sender-policy.d.ts +25 -0
- package/dist/main/utils/sender-policy.js +44 -0
- package/dist/main/utils/theme.d.ts +24 -0
- package/dist/main/utils/theme.js +38 -1
- package/dist/main/windows/main-window/create.d.ts +6 -0
- package/dist/main/windows/main-window/create.js +66 -10
- package/dist/main/windows/main-window/events.d.ts +2 -1
- package/dist/main/windows/main-window/events.js +25 -2
- package/dist/main/windows/navigation-hardening.d.ts +24 -0
- package/dist/main/windows/navigation-hardening.js +55 -0
- package/dist/main/windows/settings-window/create.js +11 -1
- package/dist/preload/index.d.ts +22 -0
- package/dist/preload/index.js +19 -0
- package/dist/preload/instrumentation/app-data.d.ts +32 -2
- package/dist/preload/instrumentation/app-data.js +253 -79
- package/dist/preload/instrumentation/index.d.ts +2 -2
- package/dist/preload/instrumentation/index.js +2 -2
- package/dist/preload/instrumentation/wxml.d.ts +16 -4
- package/dist/preload/instrumentation/wxml.js +188 -178
- package/dist/preload/miniapp-snapshot/host.d.ts +12 -0
- package/dist/preload/miniapp-snapshot/host.js +117 -0
- package/dist/preload/miniapp-snapshot/types.d.ts +62 -0
- package/dist/preload/miniapp-snapshot/types.js +15 -0
- package/dist/preload/runtime/bridge.js +5 -24
- package/dist/preload/runtime/custom-apis.d.ts +6 -0
- package/dist/preload/runtime/custom-apis.js +43 -0
- package/dist/preload/runtime/host.d.ts +1 -0
- package/dist/preload/runtime/host.js +13 -0
- package/dist/preload/shared/expose.d.ts +15 -0
- package/dist/preload/shared/expose.js +33 -0
- package/dist/preload/shared/page-iframe.d.ts +3 -0
- package/dist/preload/shared/page-iframe.js +6 -0
- package/dist/preload/shared/types.d.ts +2 -0
- package/dist/preload/windows/main.cjs +47 -0
- package/dist/preload/windows/main.cjs.map +7 -0
- package/dist/preload/windows/simulator.js +501 -272
- package/dist/renderer/assets/constants-DI382mTP.js +2 -0
- package/dist/renderer/assets/index-D8VFYBh-.js +46 -0
- package/dist/renderer/assets/{input-DxfYnzX_.js → input-CoWcfa8u.js} +2 -2
- package/dist/renderer/assets/ipc-transport-Cd2xPrsa.js +9 -0
- package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +1 -0
- package/dist/renderer/assets/popover-Nq5gT9DO.js +2 -0
- package/dist/renderer/assets/select-By5KH-UG.js +2 -0
- package/dist/renderer/assets/{settings-DteGraEU.js → settings-Dj_N70qR.js} +2 -2
- package/dist/renderer/assets/settings-api-Do0_Smq1.js +2 -0
- package/dist/renderer/assets/workbenchSettings-CazSLGYt.js +8 -0
- package/dist/renderer/entries/main/index.html +13 -5
- package/dist/renderer/entries/popover/index.html +8 -5
- package/dist/renderer/entries/settings/index.html +7 -5
- package/dist/renderer/entries/workbench-settings/index.html +7 -4
- package/dist/shared/ipc-channels.d.ts +46 -13
- package/dist/shared/ipc-channels.js +60 -14
- package/dist/shared/ipc-schemas.d.ts +118 -0
- package/dist/shared/ipc-schemas.js +133 -0
- package/dist/shared/simulator-route.d.ts +72 -0
- package/dist/shared/simulator-route.js +169 -0
- package/dist/shared/types.d.ts +82 -8
- package/dist/simulator/assets/simulator-CZc88Jeh.js +9 -0
- package/dist/simulator/simulator.html +6 -43
- package/package.json +16 -27
- 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/preload/instrumentation/disposable.d.ts +0 -11
- package/dist/preload/instrumentation/disposable.js +0 -15
- package/dist/preload/windows/main.d.ts +0 -6
- package/dist/preload/windows/main.js +0 -6
- package/dist/renderer/assets/index-D1p6crtf.js +0 -46
- package/dist/renderer/assets/ipc-transport-ClIvDcYY.js +0 -9
- package/dist/renderer/assets/ipc-transport-EiWxu_hG.css +0 -1
- package/dist/renderer/assets/popover-CtXcBJtM.js +0 -2
- package/dist/renderer/assets/select-DMfp49f1.js +0 -2
- package/dist/renderer/assets/settings-api-CY84UMpN.js +0 -2
- package/dist/renderer/assets/workbenchSettings-DjELLnSJ.js +0 -8
- package/dist/simulator/assets/simulator-C1eHXnuq.js +0 -9
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
* If a user opens DevTools on the simulator the debugger detaches and
|
|
16
16
|
* events stop flowing until they close it.
|
|
17
17
|
*/
|
|
18
|
-
import { app,
|
|
18
|
+
import { app, webContents as wcStatic } from 'electron';
|
|
19
19
|
import { SimulatorElementChannel, SimulatorStorageChannel, } from '../../../shared/ipc-channels.js';
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
import { DisposableRegistry } from '../../utils/disposable.js';
|
|
21
|
+
import { IpcRegistry } from '../../utils/ipc-registry.js';
|
|
22
22
|
function isSimulatorWebview(wc) {
|
|
23
23
|
if (wc.isDestroyed())
|
|
24
24
|
return false;
|
|
@@ -26,87 +26,351 @@ function isSimulatorWebview(wc) {
|
|
|
26
26
|
return false;
|
|
27
27
|
return wc.getURL().includes('simulator.html');
|
|
28
28
|
}
|
|
29
|
-
function
|
|
30
|
-
if (!attachedWc)
|
|
31
|
-
return;
|
|
32
|
-
const wc = attachedWc;
|
|
33
|
-
attachedWc = null;
|
|
29
|
+
function safeOff(target, event, fn) {
|
|
34
30
|
try {
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
if (target.isDestroyed?.())
|
|
32
|
+
return;
|
|
33
|
+
target.removeListener(event, fn);
|
|
38
34
|
}
|
|
39
35
|
catch {
|
|
40
36
|
// best-effort
|
|
41
37
|
}
|
|
42
38
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
export function setupSimulatorStorage(host, options) {
|
|
40
|
+
const registry = new DisposableRegistry();
|
|
41
|
+
const getActiveAppId = options.getActiveAppId;
|
|
42
|
+
let attachedWc = null;
|
|
43
|
+
let attachDisposables = null;
|
|
44
|
+
/**
|
|
45
|
+
* Cached origin discovered via `executeJavaScript('location.origin')` on
|
|
46
|
+
* first use. The simulator <webview> loads `simulator.html` from a stable
|
|
47
|
+
* scheme/host/port for the lifetime of the CDP attach, so caching avoids an
|
|
48
|
+
* extra IPC round-trip per write call. Cleared on detach.
|
|
49
|
+
*/
|
|
50
|
+
let cachedOrigin = null;
|
|
51
|
+
/** Active appId prefix (e.g. `wx123_`), or null when no session is active. */
|
|
52
|
+
function activePrefix() {
|
|
53
|
+
const appId = getActiveAppId();
|
|
54
|
+
return appId ? `${appId}_` : null;
|
|
55
|
+
}
|
|
56
|
+
function detachFromSim() {
|
|
57
|
+
if (!attachedWc)
|
|
58
|
+
return;
|
|
59
|
+
const wc = attachedWc;
|
|
60
|
+
attachedWc = null;
|
|
61
|
+
cachedOrigin = null;
|
|
62
|
+
const ad = attachDisposables;
|
|
63
|
+
attachDisposables = null;
|
|
64
|
+
if (ad)
|
|
65
|
+
void ad.disposeAll().catch(() => { });
|
|
66
|
+
try {
|
|
67
|
+
if (!wc.isDestroyed() && wc.debugger.isAttached()) {
|
|
68
|
+
wc.debugger.detach();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// best-effort
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function attachToSim(wc) {
|
|
76
|
+
if (attachedWc === wc)
|
|
77
|
+
return;
|
|
78
|
+
if (wc.isDestroyed())
|
|
79
|
+
return;
|
|
80
|
+
detachFromSim();
|
|
81
|
+
try {
|
|
82
|
+
if (!wc.debugger.isAttached()) {
|
|
83
|
+
wc.debugger.attach('1.3');
|
|
84
|
+
}
|
|
85
|
+
await wc.debugger.sendCommand('DOMStorage.enable');
|
|
86
|
+
const attach = new DisposableRegistry();
|
|
87
|
+
attachDisposables = attach;
|
|
88
|
+
const onMessage = (_event, method, params) => forwardCdpMessage(method, params);
|
|
89
|
+
wc.debugger.on('message', onMessage);
|
|
90
|
+
attach.add(() => safeOff(wc.debugger, 'message', onMessage));
|
|
91
|
+
const onDetach = () => {
|
|
92
|
+
if (attachedWc === wc)
|
|
93
|
+
attachedWc = null;
|
|
94
|
+
};
|
|
95
|
+
wc.debugger.on('detach', onDetach);
|
|
96
|
+
attach.add(() => safeOff(wc.debugger, 'detach', onDetach));
|
|
97
|
+
const onDestroyed = () => {
|
|
98
|
+
// When the attached wc is destroyed, the lingering attachDisposables
|
|
99
|
+
// hold listener refs to a wc that will never emit again. Dispose the
|
|
100
|
+
// registry here so debugger/wc listeners are removed deterministically
|
|
101
|
+
// and a subsequent attachToSim() starts from a clean slate.
|
|
102
|
+
if (attachedWc === wc) {
|
|
103
|
+
attachedWc = null;
|
|
104
|
+
const ad = attachDisposables;
|
|
105
|
+
attachDisposables = null;
|
|
106
|
+
if (ad)
|
|
107
|
+
void ad.disposeAll().catch(() => { });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
wc.once('destroyed', onDestroyed);
|
|
111
|
+
attach.add(() => safeOff(wc, 'destroyed', onDestroyed));
|
|
112
|
+
attachedWc = wc;
|
|
52
113
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
114
|
+
catch (e) {
|
|
115
|
+
console.warn('[storage-watcher] attach failed:', e.message);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function forwardCdpMessage(method, params) {
|
|
119
|
+
if (host.isDestroyed())
|
|
120
|
+
return;
|
|
121
|
+
const p = params;
|
|
122
|
+
const prefix = activePrefix();
|
|
123
|
+
let evt;
|
|
124
|
+
switch (method) {
|
|
125
|
+
case 'DOMStorage.domStorageItemAdded': {
|
|
126
|
+
const key = String(p.key);
|
|
127
|
+
if (prefix && !key.startsWith(prefix))
|
|
128
|
+
return;
|
|
129
|
+
evt = { type: 'added', key, newValue: String(p.newValue ?? '') };
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'DOMStorage.domStorageItemUpdated': {
|
|
133
|
+
const key = String(p.key);
|
|
134
|
+
if (prefix && !key.startsWith(prefix))
|
|
135
|
+
return;
|
|
136
|
+
evt = {
|
|
137
|
+
type: 'updated',
|
|
138
|
+
key,
|
|
139
|
+
oldValue: String(p.oldValue ?? ''),
|
|
140
|
+
newValue: String(p.newValue ?? ''),
|
|
141
|
+
};
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'DOMStorage.domStorageItemRemoved': {
|
|
145
|
+
const key = String(p.key);
|
|
146
|
+
if (prefix && !key.startsWith(prefix))
|
|
147
|
+
return;
|
|
148
|
+
evt = { type: 'removed', key };
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'DOMStorage.domStorageItemsCleared':
|
|
152
|
+
// Forwarded unfiltered: this CDP event only fires on a true
|
|
153
|
+
// origin-wide `localStorage.clear()`. The dimina runtime's
|
|
154
|
+
// `wx.clearStorageSync` removes keys one-by-one (see
|
|
155
|
+
// `simulator-api-storage.ts`), so prefix-scoped clears arrive as
|
|
156
|
+
// a stream of `domStorageItemRemoved` events that the filter above
|
|
157
|
+
// handles. Any `cleared` we see here is genuinely a full-origin
|
|
158
|
+
// wipe (e.g. from DevTools console) and should propagate.
|
|
159
|
+
evt = { type: 'cleared' };
|
|
160
|
+
break;
|
|
161
|
+
default:
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
host.send(SimulatorStorageChannel.Event, evt);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Lazy-attach guard.
|
|
168
|
+
*
|
|
169
|
+
* The `attachToSim` call inside `web-contents-created` only fires after the
|
|
170
|
+
* simulator webview emits `did-finish-load`. UI affordances (the storage
|
|
171
|
+
* panel's "+ 新增", inline edit, delete, clear) are reachable as soon as
|
|
172
|
+
* the right-pane tab renders — which can happen BEFORE that load completes.
|
|
173
|
+
* Without this hook, a fast click bounces off `!attachedWc` and the write
|
|
174
|
+
* IPC returns `{ ok:false, error:'simulator not attached' }` silently,
|
|
175
|
+
* leaving the user (and the e2e suite) staring at a no-op.
|
|
176
|
+
*
|
|
177
|
+
* Behaviour:
|
|
178
|
+
* - If a wc is already attached, this is a fast no-op.
|
|
179
|
+
* - If no wc is attached, scan `webContents.getAllWebContents()` for a
|
|
180
|
+
* simulator webview and `await attachToSim()` on the first match.
|
|
181
|
+
* - If no simulator exists at all, leave `attachedWc` null so the
|
|
182
|
+
* caller's existing `not attached` error branch fires — preserving
|
|
183
|
+
* the documented contract exercised by `Set returns ok:false when
|
|
184
|
+
* no simulator is attached`.
|
|
185
|
+
*/
|
|
186
|
+
async function ensureAttached() {
|
|
187
|
+
if (attachedWc && !attachedWc.isDestroyed())
|
|
188
|
+
return;
|
|
189
|
+
const sim = findSimulatorWebContents();
|
|
190
|
+
if (!sim)
|
|
191
|
+
return;
|
|
192
|
+
await attachToSim(sim);
|
|
193
|
+
}
|
|
194
|
+
async function getStorageId() {
|
|
195
|
+
if (!attachedWc || attachedWc.isDestroyed())
|
|
196
|
+
return null;
|
|
197
|
+
if (!cachedOrigin) {
|
|
198
|
+
try {
|
|
199
|
+
cachedOrigin = String(await attachedWc.executeJavaScript('location.origin'));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { securityOrigin: cachedOrigin, isLocalStorage: true };
|
|
206
|
+
}
|
|
207
|
+
async function withStorageWrite(fn) {
|
|
208
|
+
await ensureAttached();
|
|
209
|
+
if (!attachedWc || attachedWc.isDestroyed()) {
|
|
210
|
+
return { ok: false, error: 'simulator not attached' };
|
|
211
|
+
}
|
|
212
|
+
const storageId = await getStorageId();
|
|
213
|
+
if (!storageId)
|
|
214
|
+
return { ok: false, error: 'failed to resolve simulator origin' };
|
|
215
|
+
try {
|
|
216
|
+
return await fn({ wc: attachedWc, storageId });
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
return { ok: false, error: e.message };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function withStorageRead(fallback, fn) {
|
|
223
|
+
await ensureAttached();
|
|
224
|
+
if (!attachedWc || attachedWc.isDestroyed())
|
|
225
|
+
return fallback;
|
|
226
|
+
try {
|
|
227
|
+
const storageId = await getStorageId();
|
|
228
|
+
if (!storageId)
|
|
229
|
+
return fallback;
|
|
230
|
+
return await fn({ wc: attachedWc, storageId });
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return fallback;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Push a storage event synthesised from the write path itself, in addition
|
|
238
|
+
* to whatever CDP forwards via `DOMStorage.domStorageItem*`. The CDP
|
|
239
|
+
* tracker has a small registration window after `DOMStorage.enable`
|
|
240
|
+
* during which the first write's notification can be dropped — the e2e
|
|
241
|
+
* `add a new entry via the footer form` case races exactly this gap when
|
|
242
|
+
* the storage tab is clicked before the simulator finishes loading and
|
|
243
|
+
* `ensureAttached()` performs the first attach. Renderer state reducers
|
|
244
|
+
* are idempotent on the same key, so an extra push from here cannot
|
|
245
|
+
* duplicate rows even when CDP also delivers.
|
|
246
|
+
*
|
|
247
|
+
* Active-prefix filtering mirrors `forwardCdpMessage` so the panel's
|
|
248
|
+
* per-appId filter behaves identically whether the event came from CDP
|
|
249
|
+
* or from this synthesised push.
|
|
250
|
+
*/
|
|
251
|
+
function pushSyntheticEvent(evt) {
|
|
252
|
+
if (host.isDestroyed())
|
|
253
|
+
return;
|
|
254
|
+
const prefix = activePrefix();
|
|
255
|
+
if (prefix && !evt.key.startsWith(prefix))
|
|
256
|
+
return;
|
|
257
|
+
host.send(SimulatorStorageChannel.Event, evt);
|
|
258
|
+
}
|
|
259
|
+
async function setItem(key, value) {
|
|
260
|
+
return withStorageWrite(async ({ wc, storageId }) => {
|
|
261
|
+
await wc.debugger.sendCommand('DOMStorage.setDOMStorageItem', { storageId, key, value });
|
|
262
|
+
pushSyntheticEvent({ type: 'added', key, newValue: value });
|
|
263
|
+
return { ok: true };
|
|
58
264
|
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
265
|
+
}
|
|
266
|
+
async function removeItem(key) {
|
|
267
|
+
return withStorageWrite(async ({ wc, storageId }) => {
|
|
268
|
+
await wc.debugger.sendCommand('DOMStorage.removeDOMStorageItem', { storageId, key });
|
|
269
|
+
pushSyntheticEvent({ type: 'removed', key });
|
|
270
|
+
return { ok: true };
|
|
62
271
|
});
|
|
63
|
-
attachedWc = wc;
|
|
64
272
|
}
|
|
65
|
-
|
|
66
|
-
|
|
273
|
+
// Scoped clear: when an appId is active, removes only its prefixed keys so
|
|
274
|
+
// we don't nuke other projects' state sharing the simulator partition.
|
|
275
|
+
// Falls back to origin-wide `DOMStorage.clear` only when no active appId is
|
|
276
|
+
// set (matches the snapshot filter's documented null-fallback).
|
|
277
|
+
async function clearScoped() {
|
|
278
|
+
return withStorageWrite(async ({ wc, storageId }) => {
|
|
279
|
+
const prefix = activePrefix();
|
|
280
|
+
if (!prefix) {
|
|
281
|
+
await wc.debugger.sendCommand('DOMStorage.clear', { storageId });
|
|
282
|
+
return { ok: true };
|
|
283
|
+
}
|
|
284
|
+
const result = (await wc.debugger.sendCommand('DOMStorage.getDOMStorageItems', {
|
|
285
|
+
storageId,
|
|
286
|
+
}));
|
|
287
|
+
for (const [key] of result.entries) {
|
|
288
|
+
if (!key.startsWith(prefix))
|
|
289
|
+
continue;
|
|
290
|
+
await wc.debugger.sendCommand('DOMStorage.removeDOMStorageItem', { storageId, key });
|
|
291
|
+
}
|
|
292
|
+
return { ok: true };
|
|
293
|
+
});
|
|
67
294
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
evt = { type: 'added', key: String(p.key), newValue: String(p.newValue ?? '') };
|
|
77
|
-
break;
|
|
78
|
-
case 'DOMStorage.domStorageItemUpdated':
|
|
79
|
-
evt = {
|
|
80
|
-
type: 'updated',
|
|
81
|
-
key: String(p.key),
|
|
82
|
-
oldValue: String(p.oldValue ?? ''),
|
|
83
|
-
newValue: String(p.newValue ?? ''),
|
|
84
|
-
};
|
|
85
|
-
break;
|
|
86
|
-
case 'DOMStorage.domStorageItemRemoved':
|
|
87
|
-
evt = { type: 'removed', key: String(p.key) };
|
|
88
|
-
break;
|
|
89
|
-
case 'DOMStorage.domStorageItemsCleared':
|
|
90
|
-
evt = { type: 'cleared' };
|
|
91
|
-
break;
|
|
92
|
-
default:
|
|
93
|
-
return;
|
|
295
|
+
// Origin-wide clear regardless of the active appId — wipes every project's
|
|
296
|
+
// state in the shared simulator partition. The renderer guards this with a
|
|
297
|
+
// confirm dialog; we don't second-guess here.
|
|
298
|
+
async function clearAll() {
|
|
299
|
+
return withStorageWrite(async ({ wc, storageId }) => {
|
|
300
|
+
await wc.debugger.sendCommand('DOMStorage.clear', { storageId });
|
|
301
|
+
return { ok: true };
|
|
302
|
+
});
|
|
94
303
|
}
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
304
|
+
async function getSnapshot() {
|
|
305
|
+
return withStorageRead([], async ({ wc, storageId }) => {
|
|
306
|
+
const result = (await wc.debugger.sendCommand('DOMStorage.getDOMStorageItems', {
|
|
307
|
+
storageId,
|
|
308
|
+
}));
|
|
309
|
+
const prefix = activePrefix();
|
|
310
|
+
const filtered = prefix
|
|
311
|
+
? result.entries.filter(([key]) => key.startsWith(prefix))
|
|
312
|
+
: result.entries;
|
|
313
|
+
// Keys are returned with their `${appId}_` prefix intact; stripping is
|
|
314
|
+
// a follow-up so the panel UI can stay compatible with the raw keys.
|
|
315
|
+
return filtered.map(([key, value]) => ({ key, value }));
|
|
316
|
+
});
|
|
106
317
|
}
|
|
107
|
-
|
|
108
|
-
|
|
318
|
+
// Attach to any simulator webview that already exists (project may have
|
|
319
|
+
// been opened before this function runs).
|
|
320
|
+
for (const wc of wcStatic.getAllWebContents()) {
|
|
321
|
+
if (isSimulatorWebview(wc)) {
|
|
322
|
+
void attachToSim(wc);
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
109
325
|
}
|
|
326
|
+
// Catch new simulator webviews on creation. did-finish-load is the right
|
|
327
|
+
// moment because getURL() may still be 'about:blank' at creation.
|
|
328
|
+
// Track each per-wc listener so it can be removed on storage dispose,
|
|
329
|
+
// and also self-clean when the wc is destroyed.
|
|
330
|
+
const wcSubs = new Map();
|
|
331
|
+
const onWcCreated = (_event, wc) => {
|
|
332
|
+
const sub = new DisposableRegistry();
|
|
333
|
+
wcSubs.set(wc, sub);
|
|
334
|
+
const onFinishLoad = () => {
|
|
335
|
+
if (isSimulatorWebview(wc))
|
|
336
|
+
void attachToSim(wc);
|
|
337
|
+
};
|
|
338
|
+
wc.on('did-finish-load', onFinishLoad);
|
|
339
|
+
sub.add(() => safeOff(wc, 'did-finish-load', onFinishLoad));
|
|
340
|
+
wc.once('destroyed', () => {
|
|
341
|
+
void sub.disposeAll().catch(() => { });
|
|
342
|
+
wcSubs.delete(wc);
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
app.on('web-contents-created', onWcCreated);
|
|
346
|
+
registry.add(() => {
|
|
347
|
+
app.removeListener('web-contents-created', onWcCreated);
|
|
348
|
+
});
|
|
349
|
+
registry.add(async () => {
|
|
350
|
+
const subs = Array.from(wcSubs.values());
|
|
351
|
+
wcSubs.clear();
|
|
352
|
+
for (const sub of subs) {
|
|
353
|
+
await sub.disposeAll().catch(() => { });
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
// IPC handlers — gated by the workbench SenderPolicy when provided so this
|
|
357
|
+
// module routes through the same C-stage sender white-list as every other
|
|
358
|
+
// workbench-built-in (main-window renderer + overlay views only). Without
|
|
359
|
+
// a policy (unit tests with mocked electron) the registry is a transparent
|
|
360
|
+
// pass-through and still owns the removeHandler lifecycle.
|
|
361
|
+
const ipc = new IpcRegistry(options.senderPolicy);
|
|
362
|
+
ipc.handle(SimulatorStorageChannel.GetSnapshot, () => getSnapshot());
|
|
363
|
+
ipc.handle(SimulatorStorageChannel.GetActivePrefix, () => activePrefix() ?? '');
|
|
364
|
+
ipc.handle(SimulatorStorageChannel.Set, (_event, payload) => setItem(String(payload?.key ?? ''), String(payload?.value ?? '')));
|
|
365
|
+
ipc.handle(SimulatorStorageChannel.Remove, (_event, payload) => removeItem(String(payload?.key ?? '')));
|
|
366
|
+
ipc.handle(SimulatorStorageChannel.Clear, () => clearScoped());
|
|
367
|
+
ipc.handle(SimulatorStorageChannel.ClearAll, () => clearAll());
|
|
368
|
+
ipc.handle(SimulatorElementChannel.Inspect, (_event, sid) => inspectElement(sid));
|
|
369
|
+
ipc.handle(SimulatorElementChannel.Clear, () => clearElementInspection());
|
|
370
|
+
registry.add(ipc);
|
|
371
|
+
// Detach active CDP session last
|
|
372
|
+
registry.add(() => detachFromSim());
|
|
373
|
+
return registry;
|
|
110
374
|
}
|
|
111
375
|
// Element inspection delegates to the simulator preload's __simulatorData
|
|
112
376
|
// bridge via executeJavaScript. Looked up independently from `attachedWc` so
|
|
@@ -145,31 +409,4 @@ async function clearElementInspection() {
|
|
|
145
409
|
// best-effort
|
|
146
410
|
}
|
|
147
411
|
}
|
|
148
|
-
export function setupSimulatorStorage(host) {
|
|
149
|
-
hostWc = host;
|
|
150
|
-
// Attach to any simulator webview that already exists (project may have
|
|
151
|
-
// been opened before this function runs).
|
|
152
|
-
for (const wc of wcStatic.getAllWebContents()) {
|
|
153
|
-
if (isSimulatorWebview(wc)) {
|
|
154
|
-
void attachToSim(wc);
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// Catch new simulator webviews on creation. did-finish-load is the right
|
|
159
|
-
// moment because getURL() may still be 'about:blank' at creation.
|
|
160
|
-
app.on('web-contents-created', (_event, wc) => {
|
|
161
|
-
wc.on('did-finish-load', () => {
|
|
162
|
-
if (isSimulatorWebview(wc)) {
|
|
163
|
-
void attachToSim(wc);
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
wc.on('did-navigate-in-page', () => {
|
|
167
|
-
// Project switched but same webview — attach already correct, debugger
|
|
168
|
-
// session survives in-page navigations. No-op.
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
ipcMain.handle(SimulatorStorageChannel.GetSnapshot, getSnapshot);
|
|
172
|
-
ipcMain.handle(SimulatorElementChannel.Inspect, (_event, sid) => inspectElement(sid));
|
|
173
|
-
ipcMain.handle(SimulatorElementChannel.Clear, clearElementInspection);
|
|
174
|
-
}
|
|
175
412
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BrowserWindow } from 'electron';
|
|
2
2
|
import type { UpdateChecker, UpdateInfo } from '../../../shared/types.js';
|
|
3
|
+
import { type SenderPolicy } from '../../utils/ipc-registry.js';
|
|
3
4
|
export interface UpdateManagerOptions {
|
|
4
5
|
checker: UpdateChecker;
|
|
5
6
|
mainWindow: BrowserWindow;
|
|
@@ -9,6 +10,13 @@ export interface UpdateManagerOptions {
|
|
|
9
10
|
initialDelay?: number;
|
|
10
11
|
/** Override the version string passed to the checker. Default: app.getVersion() */
|
|
11
12
|
getCurrentVersion?: () => string;
|
|
13
|
+
/**
|
|
14
|
+
* Optional sender gate. When provided, IpcRegistry rejects invocations
|
|
15
|
+
* whose `event.sender` is not whitelisted (same C-stage policy used by
|
|
16
|
+
* every other workbench-built-in). Omitted in unit tests that mock
|
|
17
|
+
* electron without a real WebContents.
|
|
18
|
+
*/
|
|
19
|
+
senderPolicy?: SenderPolicy;
|
|
12
20
|
}
|
|
13
21
|
export declare class UpdateManager {
|
|
14
22
|
private checker;
|
|
@@ -18,6 +26,7 @@ export declare class UpdateManager {
|
|
|
18
26
|
private downloadedPath;
|
|
19
27
|
private timer;
|
|
20
28
|
private initialTimer;
|
|
29
|
+
private ipc;
|
|
21
30
|
constructor(opts: UpdateManagerOptions);
|
|
22
31
|
private registerIpc;
|
|
23
32
|
check(): Promise<{
|
|
@@ -30,7 +39,13 @@ export declare class UpdateManager {
|
|
|
30
39
|
error?: string;
|
|
31
40
|
}>;
|
|
32
41
|
install(): void;
|
|
33
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Tear down timers + IPC handlers. Returns a Promise so callers that need
|
|
44
|
+
* a settled cleanup (e.g. workbench registry shutdown, the unit-test
|
|
45
|
+
* lifecycle suite) can await it; calling without await is also safe since
|
|
46
|
+
* the timer clears + handler removals are scheduled synchronously.
|
|
47
|
+
*/
|
|
48
|
+
dispose(): Promise<void>;
|
|
34
49
|
private startPeriodicCheck;
|
|
35
50
|
private checkAndNotify;
|
|
36
51
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { app,
|
|
1
|
+
import { app, shell } from 'electron';
|
|
2
|
+
import { UpdateChannel } from '../../../shared/ipc-channels.js';
|
|
3
|
+
import { IpcRegistry } from '../../utils/ipc-registry.js';
|
|
2
4
|
export class UpdateManager {
|
|
3
5
|
checker;
|
|
4
6
|
mainWindow;
|
|
@@ -7,23 +9,20 @@ export class UpdateManager {
|
|
|
7
9
|
downloadedPath = null;
|
|
8
10
|
timer = null;
|
|
9
11
|
initialTimer = null;
|
|
12
|
+
ipc;
|
|
10
13
|
constructor(opts) {
|
|
11
14
|
this.checker = opts.checker;
|
|
12
15
|
this.mainWindow = opts.mainWindow;
|
|
13
16
|
this.getCurrentVersion = opts.getCurrentVersion ?? (() => app.getVersion());
|
|
17
|
+
this.ipc = new IpcRegistry(opts.senderPolicy);
|
|
14
18
|
this.registerIpc();
|
|
15
19
|
this.startPeriodicCheck(opts.checkInterval ?? 60 * 60 * 1000, opts.initialDelay ?? 5000);
|
|
16
20
|
}
|
|
17
21
|
registerIpc() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return this.download();
|
|
23
|
-
});
|
|
24
|
-
ipcMain.handle('updates:install', async () => {
|
|
25
|
-
this.install();
|
|
26
|
-
});
|
|
22
|
+
this.ipc
|
|
23
|
+
.handle(UpdateChannel.Check, async () => this.check())
|
|
24
|
+
.handle(UpdateChannel.Download, async () => this.download())
|
|
25
|
+
.handle(UpdateChannel.Install, async () => this.install());
|
|
27
26
|
}
|
|
28
27
|
async check() {
|
|
29
28
|
try {
|
|
@@ -47,7 +46,7 @@ export class UpdateManager {
|
|
|
47
46
|
}
|
|
48
47
|
try {
|
|
49
48
|
const filePath = await this.checker.downloadUpdate(this.latestUpdate, (percent) => {
|
|
50
|
-
this.mainWindow.webContents.send(
|
|
49
|
+
this.mainWindow.webContents.send(UpdateChannel.DownloadProgress, { percent });
|
|
51
50
|
});
|
|
52
51
|
this.downloadedPath = filePath;
|
|
53
52
|
return { success: true, filePath };
|
|
@@ -63,6 +62,12 @@ export class UpdateManager {
|
|
|
63
62
|
app.quit();
|
|
64
63
|
}
|
|
65
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Tear down timers + IPC handlers. Returns a Promise so callers that need
|
|
67
|
+
* a settled cleanup (e.g. workbench registry shutdown, the unit-test
|
|
68
|
+
* lifecycle suite) can await it; calling without await is also safe since
|
|
69
|
+
* the timer clears + handler removals are scheduled synchronously.
|
|
70
|
+
*/
|
|
66
71
|
dispose() {
|
|
67
72
|
if (this.timer) {
|
|
68
73
|
clearInterval(this.timer);
|
|
@@ -72,9 +77,7 @@ export class UpdateManager {
|
|
|
72
77
|
clearTimeout(this.initialTimer);
|
|
73
78
|
this.initialTimer = null;
|
|
74
79
|
}
|
|
75
|
-
|
|
76
|
-
ipcMain.removeHandler('updates:download');
|
|
77
|
-
ipcMain.removeHandler('updates:install');
|
|
80
|
+
return this.ipc.dispose();
|
|
78
81
|
}
|
|
79
82
|
startPeriodicCheck(interval, initialDelay) {
|
|
80
83
|
this.initialTimer = setTimeout(() => void this.checkAndNotify(), initialDelay);
|
|
@@ -83,7 +86,7 @@ export class UpdateManager {
|
|
|
83
86
|
async checkAndNotify() {
|
|
84
87
|
const result = await this.check();
|
|
85
88
|
if (result.hasUpdate && result.info) {
|
|
86
|
-
this.mainWindow.webContents.send(
|
|
89
|
+
this.mainWindow.webContents.send(UpdateChannel.Available, result.info);
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
92
|
}
|
|
@@ -5,7 +5,7 @@ import { type WorkbenchContext } from '../workbench-context.js';
|
|
|
5
5
|
* full WorkbenchContext here; typing it this way documents the actual dependency.
|
|
6
6
|
*/
|
|
7
7
|
export interface ViewManagerContext {
|
|
8
|
-
|
|
8
|
+
windows: WorkbenchContext['windows'];
|
|
9
9
|
rendererDir: string;
|
|
10
10
|
panels: string[];
|
|
11
11
|
notify: WorkbenchContext['notify'];
|
|
@@ -52,14 +52,10 @@ export interface ViewManager {
|
|
|
52
52
|
repositionAll(): void;
|
|
53
53
|
/** Destroy all overlay webContents and null out the cached views. */
|
|
54
54
|
disposeAll(): void;
|
|
55
|
-
/** Log current child-view count of the main contentView. */
|
|
56
|
-
logChildViews(label: string): void;
|
|
57
|
-
/** Reposition only the simulator overlay (for simulator width changes). */
|
|
58
|
-
positionSimulator(simWidth: number): void;
|
|
59
|
-
/** Reposition only the settings overlay. */
|
|
60
|
-
positionSettings(): void;
|
|
61
55
|
/** Return the webContents ID of the currently attached simulator. */
|
|
62
56
|
getSimulatorWebContentsId(): number | null;
|
|
57
|
+
/** Return the live webContents of the currently attached simulator, or null. */
|
|
58
|
+
getSimulatorWebContents(): WebContents | null;
|
|
63
59
|
/** Return the last known simulator width. */
|
|
64
60
|
getLastSimWidth(): number;
|
|
65
61
|
/** Whether the simulator overlay is currently added to the contentView. */
|
|
@@ -68,6 +64,10 @@ export interface ViewManager {
|
|
|
68
64
|
hasSimulatorView(): boolean;
|
|
69
65
|
/** Return the settings overlay's WebContents (for renderer-notifier). */
|
|
70
66
|
getSettingsWebContents(): WebContents | null;
|
|
67
|
+
/** Return the webContents ID of the settings overlay if alive, else null. */
|
|
68
|
+
getSettingsWebContentsId(): number | null;
|
|
69
|
+
/** Return the webContents ID of the popover overlay if alive, else null. */
|
|
70
|
+
getPopoverWebContentsId(): number | null;
|
|
71
71
|
/** Update lastSimWidth; reposition simulator + settings if they are added. */
|
|
72
72
|
resize(simWidth: number): void;
|
|
73
73
|
/** Show or hide the simulator overlay based on visibility flag. */
|