@dimina-kit/devtools 0.3.1-dev.20260513123738 → 0.3.1-dev.20260515105504
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -41
- 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 +71 -36
- package/dist/main/app/launch.d.ts +2 -2
- package/dist/main/app/launch.js +6 -3
- package/dist/main/index.bundle.js +1074 -289
- 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 +14 -9
- package/dist/main/ipc/popover.d.ts +4 -1
- package/dist/main/ipc/popover.js +15 -5
- package/dist/main/ipc/projects.d.ts +4 -1
- package/dist/main/ipc/projects.js +42 -5
- package/dist/main/ipc/session.d.ts +4 -1
- package/dist/main/ipc/session.js +26 -6
- package/dist/main/ipc/settings.d.ts +4 -1
- package/dist/main/ipc/settings.js +30 -17
- 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 +29 -15
- package/dist/main/ipc/toolbar.d.ts +2 -1
- package/dist/main/ipc/toolbar.js +3 -2
- package/dist/main/services/automation/index.js +88 -13
- package/dist/main/services/mcp/server.d.ts +2 -1
- package/dist/main/services/mcp/server.js +5 -0
- 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 +2 -0
- package/dist/main/services/projects/project-repository.d.ts +1 -0
- package/dist/main/services/projects/project-repository.js +3 -0
- package/dist/main/services/settings/index.d.ts +4 -0
- package/dist/main/services/settings/index.js +12 -1
- 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 +365 -98
- 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 +4 -0
- package/dist/main/services/views/view-manager.js +33 -2
- 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 +15 -0
- package/dist/main/services/workbench-context.js +6 -0
- package/dist/main/services/workspace/workspace-service.d.ts +1 -0
- package/dist/main/services/workspace/workspace-service.js +7 -3
- 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 +23 -0
- package/dist/main/utils/sender-policy.js +42 -0
- package/dist/main/windows/main-window/create.d.ts +6 -0
- package/dist/main/windows/main-window/create.js +65 -9
- 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 +17 -0
- package/dist/preload/index.js +17 -0
- package/dist/preload/instrumentation/app-data.js +178 -19
- package/dist/preload/runtime/custom-apis.d.ts +6 -0
- package/dist/preload/runtime/custom-apis.js +18 -0
- package/dist/preload/runtime/host.d.ts +1 -0
- package/dist/preload/runtime/host.js +13 -0
- package/dist/preload/windows/main.cjs +47 -0
- package/dist/preload/windows/main.cjs.map +7 -0
- package/dist/preload/windows/simulator.js +174 -18
- package/dist/renderer/assets/index-DW_0thr-.js +46 -0
- package/dist/renderer/assets/{input-DxfYnzX_.js → input-C5M5Wxn6.js} +2 -2
- package/dist/renderer/assets/{ipc-transport-ClIvDcYY.js → ipc-transport-CUsl-fS2.js} +2 -2
- package/dist/renderer/assets/ipc-transport-CltRuvhi.css +1 -0
- package/dist/renderer/assets/{popover-CtXcBJtM.js → popover-BSBPVO0o.js} +2 -2
- package/dist/renderer/assets/{select-DMfp49f1.js → select-B1tF3UAc.js} +2 -2
- package/dist/renderer/assets/{settings-DteGraEU.js → settings-7w54H6p-.js} +2 -2
- package/dist/renderer/assets/settings-api-BjJalghB.js +2 -0
- package/dist/renderer/assets/workbenchSettings-q2_-nc8t.js +8 -0
- package/dist/renderer/entries/main/index.html +12 -5
- package/dist/renderer/entries/popover/index.html +7 -5
- package/dist/renderer/entries/settings/index.html +7 -5
- package/dist/renderer/entries/workbench-settings/index.html +6 -4
- package/dist/shared/ipc-channels.d.ts +22 -0
- package/dist/shared/ipc-channels.js +23 -0
- package/dist/shared/ipc-schemas.d.ts +113 -0
- package/dist/shared/ipc-schemas.js +128 -0
- package/dist/shared/types.d.ts +2 -0
- package/dist/simulator/assets/simulator-DL6kOCq-.js +9 -0
- package/dist/simulator/simulator.html +1 -1
- package/package.json +11 -27
- 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-EiWxu_hG.css +0 -1
- 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,381 @@ 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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
wc.once('destroyed', () => {
|
|
60
|
-
if (attachedWc === wc)
|
|
61
|
-
attachedWc = null;
|
|
62
|
-
});
|
|
63
|
-
attachedWc = wc;
|
|
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;
|
|
64
55
|
}
|
|
65
|
-
|
|
66
|
-
|
|
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
|
+
}
|
|
67
74
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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;
|
|
84
94
|
};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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;
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
console.warn('[storage-watcher] attach failed:', e.message);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function forwardCdpMessage(method, params) {
|
|
119
|
+
if (host.isDestroyed())
|
|
93
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);
|
|
94
165
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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);
|
|
106
193
|
}
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
/**
|
|
208
|
+
* Push a storage event synthesised from the write path itself, in addition
|
|
209
|
+
* to whatever CDP forwards via `DOMStorage.domStorageItem*`. The CDP
|
|
210
|
+
* tracker has a small registration window after `DOMStorage.enable`
|
|
211
|
+
* during which the first write's notification can be dropped — the e2e
|
|
212
|
+
* `add a new entry via the footer form` case races exactly this gap when
|
|
213
|
+
* the storage tab is clicked before the simulator finishes loading and
|
|
214
|
+
* `ensureAttached()` performs the first attach. Renderer state reducers
|
|
215
|
+
* are idempotent on the same key, so an extra push from here cannot
|
|
216
|
+
* duplicate rows even when CDP also delivers.
|
|
217
|
+
*
|
|
218
|
+
* Active-prefix filtering mirrors `forwardCdpMessage` so the panel's
|
|
219
|
+
* per-appId filter behaves identically whether the event came from CDP
|
|
220
|
+
* or from this synthesised push.
|
|
221
|
+
*/
|
|
222
|
+
function pushSyntheticEvent(evt) {
|
|
223
|
+
if (host.isDestroyed())
|
|
224
|
+
return;
|
|
225
|
+
const prefix = activePrefix();
|
|
226
|
+
if (prefix && !evt.key.startsWith(prefix))
|
|
227
|
+
return;
|
|
228
|
+
host.send(SimulatorStorageChannel.Event, evt);
|
|
229
|
+
}
|
|
230
|
+
async function setItem(key, value) {
|
|
231
|
+
await ensureAttached();
|
|
232
|
+
if (!attachedWc || attachedWc.isDestroyed()) {
|
|
233
|
+
return { ok: false, error: 'simulator not attached' };
|
|
234
|
+
}
|
|
235
|
+
const storageId = await getStorageId();
|
|
236
|
+
if (!storageId)
|
|
237
|
+
return { ok: false, error: 'failed to resolve simulator origin' };
|
|
238
|
+
try {
|
|
239
|
+
await attachedWc.debugger.sendCommand('DOMStorage.setDOMStorageItem', {
|
|
240
|
+
storageId,
|
|
241
|
+
key,
|
|
242
|
+
value,
|
|
243
|
+
});
|
|
244
|
+
pushSyntheticEvent({ type: 'added', key, newValue: value });
|
|
245
|
+
return { ok: true };
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
return { ok: false, error: e.message };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function removeItem(key) {
|
|
252
|
+
await ensureAttached();
|
|
253
|
+
if (!attachedWc || attachedWc.isDestroyed()) {
|
|
254
|
+
return { ok: false, error: 'simulator not attached' };
|
|
255
|
+
}
|
|
256
|
+
const storageId = await getStorageId();
|
|
257
|
+
if (!storageId)
|
|
258
|
+
return { ok: false, error: 'failed to resolve simulator origin' };
|
|
259
|
+
try {
|
|
260
|
+
await attachedWc.debugger.sendCommand('DOMStorage.removeDOMStorageItem', {
|
|
261
|
+
storageId,
|
|
262
|
+
key,
|
|
263
|
+
});
|
|
264
|
+
pushSyntheticEvent({ type: 'removed', key });
|
|
265
|
+
return { ok: true };
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
return { ok: false, error: e.message };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Scoped clear: when an appId is active, removes only its prefixed keys so
|
|
272
|
+
// we don't nuke other projects' state sharing the simulator partition.
|
|
273
|
+
// Falls back to origin-wide `DOMStorage.clear` only when no active appId is
|
|
274
|
+
// set (matches the snapshot filter's documented null-fallback).
|
|
275
|
+
async function clearScoped() {
|
|
276
|
+
await ensureAttached();
|
|
277
|
+
if (!attachedWc || attachedWc.isDestroyed()) {
|
|
278
|
+
return { ok: false, error: 'simulator not attached' };
|
|
279
|
+
}
|
|
280
|
+
const storageId = await getStorageId();
|
|
281
|
+
if (!storageId)
|
|
282
|
+
return { ok: false, error: 'failed to resolve simulator origin' };
|
|
283
|
+
const prefix = activePrefix();
|
|
284
|
+
try {
|
|
285
|
+
if (!prefix) {
|
|
286
|
+
await attachedWc.debugger.sendCommand('DOMStorage.clear', { storageId });
|
|
287
|
+
return { ok: true };
|
|
288
|
+
}
|
|
289
|
+
const result = (await attachedWc.debugger.sendCommand('DOMStorage.getDOMStorageItems', {
|
|
290
|
+
storageId,
|
|
291
|
+
}));
|
|
292
|
+
for (const [key] of result.entries) {
|
|
293
|
+
if (!key.startsWith(prefix))
|
|
294
|
+
continue;
|
|
295
|
+
await attachedWc.debugger.sendCommand('DOMStorage.removeDOMStorageItem', {
|
|
296
|
+
storageId,
|
|
297
|
+
key,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
return { ok: true };
|
|
301
|
+
}
|
|
302
|
+
catch (e) {
|
|
303
|
+
return { ok: false, error: e.message };
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Origin-wide clear regardless of the active appId — wipes every project's
|
|
307
|
+
// state in the shared simulator partition. The renderer guards this with a
|
|
308
|
+
// confirm dialog; we don't second-guess here.
|
|
309
|
+
async function clearAll() {
|
|
310
|
+
await ensureAttached();
|
|
311
|
+
if (!attachedWc || attachedWc.isDestroyed()) {
|
|
312
|
+
return { ok: false, error: 'simulator not attached' };
|
|
313
|
+
}
|
|
314
|
+
const storageId = await getStorageId();
|
|
315
|
+
if (!storageId)
|
|
316
|
+
return { ok: false, error: 'failed to resolve simulator origin' };
|
|
317
|
+
try {
|
|
318
|
+
await attachedWc.debugger.sendCommand('DOMStorage.clear', { storageId });
|
|
319
|
+
return { ok: true };
|
|
320
|
+
}
|
|
321
|
+
catch (e) {
|
|
322
|
+
return { ok: false, error: e.message };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async function getSnapshot() {
|
|
326
|
+
await ensureAttached();
|
|
327
|
+
if (!attachedWc || attachedWc.isDestroyed())
|
|
328
|
+
return [];
|
|
329
|
+
try {
|
|
330
|
+
const storageId = await getStorageId();
|
|
331
|
+
if (!storageId)
|
|
332
|
+
return [];
|
|
333
|
+
const result = (await attachedWc.debugger.sendCommand('DOMStorage.getDOMStorageItems', {
|
|
334
|
+
storageId,
|
|
335
|
+
}));
|
|
336
|
+
const prefix = activePrefix();
|
|
337
|
+
const filtered = prefix
|
|
338
|
+
? result.entries.filter(([key]) => key.startsWith(prefix))
|
|
339
|
+
: result.entries;
|
|
340
|
+
// Keys are returned with their `${appId}_` prefix intact; stripping is
|
|
341
|
+
// a follow-up so the panel UI can stay compatible with the raw keys.
|
|
342
|
+
return filtered.map(([key, value]) => ({ key, value }));
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Attach to any simulator webview that already exists (project may have
|
|
349
|
+
// been opened before this function runs).
|
|
350
|
+
for (const wc of wcStatic.getAllWebContents()) {
|
|
351
|
+
if (isSimulatorWebview(wc)) {
|
|
352
|
+
void attachToSim(wc);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
109
355
|
}
|
|
356
|
+
// Catch new simulator webviews on creation. did-finish-load is the right
|
|
357
|
+
// moment because getURL() may still be 'about:blank' at creation.
|
|
358
|
+
// Track each per-wc listener so it can be removed on storage dispose,
|
|
359
|
+
// and also self-clean when the wc is destroyed.
|
|
360
|
+
const wcSubs = new Map();
|
|
361
|
+
const onWcCreated = (_event, wc) => {
|
|
362
|
+
const sub = new DisposableRegistry();
|
|
363
|
+
wcSubs.set(wc, sub);
|
|
364
|
+
const onFinishLoad = () => {
|
|
365
|
+
if (isSimulatorWebview(wc))
|
|
366
|
+
void attachToSim(wc);
|
|
367
|
+
};
|
|
368
|
+
wc.on('did-finish-load', onFinishLoad);
|
|
369
|
+
sub.add(() => safeOff(wc, 'did-finish-load', onFinishLoad));
|
|
370
|
+
wc.once('destroyed', () => {
|
|
371
|
+
void sub.disposeAll().catch(() => { });
|
|
372
|
+
wcSubs.delete(wc);
|
|
373
|
+
});
|
|
374
|
+
};
|
|
375
|
+
app.on('web-contents-created', onWcCreated);
|
|
376
|
+
registry.add(() => {
|
|
377
|
+
app.removeListener('web-contents-created', onWcCreated);
|
|
378
|
+
});
|
|
379
|
+
registry.add(async () => {
|
|
380
|
+
const subs = Array.from(wcSubs.values());
|
|
381
|
+
wcSubs.clear();
|
|
382
|
+
for (const sub of subs) {
|
|
383
|
+
await sub.disposeAll().catch(() => { });
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
// IPC handlers — gated by the workbench SenderPolicy when provided so this
|
|
387
|
+
// module routes through the same C-stage sender white-list as every other
|
|
388
|
+
// workbench-built-in (main-window renderer + overlay views only). Without
|
|
389
|
+
// a policy (unit tests with mocked electron) the registry is a transparent
|
|
390
|
+
// pass-through and still owns the removeHandler lifecycle.
|
|
391
|
+
const ipc = new IpcRegistry(options.senderPolicy);
|
|
392
|
+
ipc.handle(SimulatorStorageChannel.GetSnapshot, () => getSnapshot());
|
|
393
|
+
ipc.handle(SimulatorStorageChannel.GetActivePrefix, () => activePrefix() ?? '');
|
|
394
|
+
ipc.handle(SimulatorStorageChannel.Set, (_event, payload) => setItem(String(payload?.key ?? ''), String(payload?.value ?? '')));
|
|
395
|
+
ipc.handle(SimulatorStorageChannel.Remove, (_event, payload) => removeItem(String(payload?.key ?? '')));
|
|
396
|
+
ipc.handle(SimulatorStorageChannel.Clear, () => clearScoped());
|
|
397
|
+
ipc.handle(SimulatorStorageChannel.ClearAll, () => clearAll());
|
|
398
|
+
ipc.handle(SimulatorElementChannel.Inspect, (_event, sid) => inspectElement(sid));
|
|
399
|
+
ipc.handle(SimulatorElementChannel.Clear, () => clearElementInspection());
|
|
400
|
+
registry.add(ipc);
|
|
401
|
+
// Detach active CDP session last
|
|
402
|
+
registry.add(() => detachFromSim());
|
|
403
|
+
return registry;
|
|
110
404
|
}
|
|
111
405
|
// Element inspection delegates to the simulator preload's __simulatorData
|
|
112
406
|
// bridge via executeJavaScript. Looked up independently from `attachedWc` so
|
|
@@ -145,31 +439,4 @@ async function clearElementInspection() {
|
|
|
145
439
|
// best-effort
|
|
146
440
|
}
|
|
147
441
|
}
|
|
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
442
|
//# 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
|
}
|
|
@@ -68,6 +68,10 @@ export interface ViewManager {
|
|
|
68
68
|
hasSimulatorView(): boolean;
|
|
69
69
|
/** Return the settings overlay's WebContents (for renderer-notifier). */
|
|
70
70
|
getSettingsWebContents(): WebContents | null;
|
|
71
|
+
/** Return the webContents ID of the settings overlay if alive, else null. */
|
|
72
|
+
getSettingsWebContentsId(): number | null;
|
|
73
|
+
/** Return the webContents ID of the popover overlay if alive, else null. */
|
|
74
|
+
getPopoverWebContentsId(): number | null;
|
|
71
75
|
/** Update lastSimWidth; reposition simulator + settings if they are added. */
|
|
72
76
|
resize(simWidth: number): void;
|
|
73
77
|
/** Show or hide the simulator overlay based on visibility flag. */
|