@dimina-kit/devtools 0.4.0-dev.20260828161241 → 0.4.0-dev.20260907055341
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 +52 -729
- package/dist/main/app/app.d.ts +27 -7
- package/dist/main/app/app.js +285 -681
- package/dist/main/app/editor-view.d.ts +21 -0
- package/dist/main/app/editor-view.js +71 -0
- package/dist/main/app/global-mirrors.d.ts +23 -0
- package/dist/main/app/global-mirrors.js +37 -0
- package/dist/main/app/host-sidebar-default.d.ts +19 -0
- package/dist/main/app/host-sidebar-default.js +44 -0
- package/dist/main/app/menu-setup.d.ts +13 -0
- package/dist/main/app/menu-setup.js +49 -0
- package/dist/main/app/open-project-ipc.d.ts +13 -0
- package/dist/main/app/open-project-ipc.js +21 -0
- package/dist/main/app/project-list-surface.d.ts +31 -0
- package/dist/main/app/project-list-surface.js +20 -0
- package/dist/main/app/project-window.d.ts +36 -0
- package/dist/main/app/project-window.js +93 -0
- package/dist/main/app/servers.d.ts +17 -0
- package/dist/main/app/servers.js +60 -0
- package/dist/main/app/ui-extension-targets.d.ts +42 -0
- package/dist/main/app/ui-extension-targets.js +101 -0
- package/dist/main/app/window-close-reveal.harness.d.ts +34 -0
- package/dist/main/app/window-close-reveal.harness.js +65 -0
- package/dist/main/app/window-events.d.ts +15 -0
- package/dist/main/app/window-events.js +45 -0
- package/dist/main/app/window-runtime-services.d.ts +41 -0
- package/dist/main/app/window-runtime-services.js +178 -0
- package/dist/main/app/workbench-modules.d.ts +24 -0
- package/dist/main/app/workbench-modules.js +51 -0
- package/dist/main/app/workbench-window.d.ts +85 -0
- package/dist/main/app/workbench-window.js +310 -0
- package/dist/main/index.bundle.js +10669 -9096
- package/dist/main/ipc/app.d.ts +4 -1
- package/dist/main/ipc/app.js +4 -3
- package/dist/main/ipc/device-picker.d.ts +16 -0
- package/dist/main/ipc/device-picker.js +27 -0
- package/dist/main/ipc/index.d.ts +1 -0
- package/dist/main/ipc/index.js +1 -0
- package/dist/main/ipc/internal-devtools.d.ts +2 -1
- package/dist/main/ipc/internal-devtools.js +4 -3
- package/dist/main/ipc/popover.d.ts +4 -1
- package/dist/main/ipc/popover.js +25 -9
- package/dist/main/ipc/project-create.d.ts +2 -1
- package/dist/main/ipc/project-create.js +6 -5
- package/dist/main/ipc/project-fs.d.ts +3 -1
- package/dist/main/ipc/project-fs.js +17 -17
- package/dist/main/ipc/projects.d.ts +3 -3
- package/dist/main/ipc/projects.js +13 -12
- package/dist/main/ipc/session.d.ts +4 -1
- package/dist/main/ipc/session.js +16 -11
- package/dist/main/ipc/settings.d.ts +4 -1
- package/dist/main/ipc/settings.js +12 -19
- package/dist/main/ipc/simulator-module.d.ts +5 -0
- package/dist/main/ipc/simulator-module.js +9 -5
- package/dist/main/ipc/simulator.d.ts +4 -1
- package/dist/main/ipc/simulator.js +12 -18
- package/dist/main/ipc/tooltip.d.ts +2 -1
- package/dist/main/ipc/tooltip.js +8 -7
- package/dist/main/ipc/views.d.ts +4 -1
- package/dist/main/ipc/views.js +20 -9
- package/dist/main/runtime/devtools-backend-before-quit.harness.d.ts +2 -20
- package/dist/main/runtime/devtools-backend-before-quit.harness.js +14 -283
- package/dist/main/runtime/devtools-backend.js +14 -3
- package/dist/main/runtime/devtools-runtime-mock.harness.d.ts +21 -0
- package/dist/main/runtime/devtools-runtime-mock.harness.js +313 -0
- package/dist/main/services/app-services.d.ts +62 -0
- package/dist/main/services/app-services.js +64 -0
- package/dist/main/services/automation/connection-target.d.ts +36 -0
- package/dist/main/services/automation/connection-target.js +29 -0
- package/dist/main/services/automation/console-bridge.d.ts +28 -0
- package/dist/main/services/automation/console-bridge.js +91 -0
- package/dist/main/services/automation/handlers/app.js +4 -4
- package/dist/main/services/automation/handlers/element.js +55 -21
- package/dist/main/services/automation/index.d.ts +12 -1
- package/dist/main/services/automation/index.js +54 -133
- package/dist/main/services/default-adapter.js +1 -1
- package/dist/main/services/fs-bridge-protocol.d.ts +61 -0
- package/dist/main/services/fs-bridge-protocol.js +34 -0
- package/dist/main/services/fs-watch-sse.d.ts +8 -1
- package/dist/main/services/fs-watch-sse.js +19 -5
- package/dist/main/services/mcp/connection-owner.d.ts +38 -0
- package/dist/main/services/mcp/connection-owner.js +79 -0
- package/dist/main/services/mcp/index.d.ts +3 -1
- package/dist/main/services/mcp/index.js +4 -3
- package/dist/main/services/mcp/opened-project.d.ts +88 -0
- package/dist/main/services/mcp/opened-project.js +60 -0
- package/dist/main/services/mcp/target-manager.d.ts +71 -23
- package/dist/main/services/mcp/target-manager.js +269 -142
- package/dist/main/services/mcp/target-selection.d.ts +47 -0
- package/dist/main/services/mcp/target-selection.js +71 -0
- package/dist/main/services/mcp/tools/context-tools.js +20 -5
- package/dist/main/services/mcp/tools/project-tools.d.ts +29 -6
- package/dist/main/services/mcp/tools/project-tools.js +37 -25
- package/dist/main/services/module.d.ts +13 -5
- package/dist/main/services/notifications/renderer-notifier.d.ts +28 -4
- package/dist/main/services/notifications/renderer-notifier.js +15 -3
- package/dist/main/services/projects/local-provider.js +2 -0
- package/dist/main/services/projects/project-repository.d.ts +31 -1
- package/dist/main/services/projects/project-repository.js +144 -20
- package/dist/main/services/projects/types.d.ts +31 -8
- package/dist/main/services/safe-area/index.d.ts +19 -5
- package/dist/main/services/safe-area/index.js +38 -13
- package/dist/main/services/simulator/ui-extensions.d.ts +8 -0
- package/dist/main/services/simulator/ui-extensions.js +16 -6
- package/dist/main/services/simulator-storage/index.d.ts +12 -1
- package/dist/main/services/simulator-storage/index.js +22 -7
- package/dist/main/services/simulator-storage/window-scope.d.ts +20 -0
- package/dist/main/services/simulator-storage/window-scope.js +20 -0
- package/dist/main/services/views/host-slot-port-channel.d.ts +20 -3
- package/dist/main/services/views/host-slot-port-channel.js +33 -5
- package/dist/main/services/views/managed-web-contents-view.js +21 -1
- package/dist/main/services/views/native-simulator-view.js +13 -16
- package/dist/main/services/views/overlay-panels-view.d.ts +13 -0
- package/dist/main/services/views/overlay-panels-view.js +31 -0
- package/dist/main/services/views/view-manager.d.ts +1 -1
- package/dist/main/services/views/view-manager.js +3 -0
- package/dist/main/services/window-contexts/context-router.d.ts +36 -0
- package/dist/main/services/window-contexts/context-router.js +55 -0
- package/dist/main/services/workbench-coi-host.d.ts +52 -0
- package/dist/main/services/workbench-coi-host.js +227 -0
- package/dist/main/services/workbench-coi-server.js +75 -133
- package/dist/main/services/workbench-coi-shutdown.d.ts +40 -0
- package/dist/main/services/workbench-coi-shutdown.js +69 -0
- package/dist/main/services/workbench-coi-static.d.ts +23 -0
- package/dist/main/services/workbench-coi-static.js +87 -0
- package/dist/main/services/workbench-context.d.ts +27 -3
- package/dist/main/services/workbench-context.js +10 -5
- package/dist/main/services/workspace/compile-mode-adapter.d.ts +18 -0
- package/dist/main/services/workspace/compile-mode-adapter.js +57 -0
- package/dist/main/services/workspace/compile-mode-store.d.ts +25 -0
- package/dist/main/services/workspace/compile-mode-store.js +71 -0
- package/dist/main/services/workspace/workspace-compile-modes.d.ts +59 -0
- package/dist/main/services/workspace/workspace-compile-modes.js +93 -0
- package/dist/main/services/workspace/workspace-referer.d.ts +17 -0
- package/dist/main/services/workspace/workspace-referer.js +26 -0
- package/dist/main/services/workspace/workspace-service.d.ts +4 -0
- package/dist/main/services/workspace/workspace-service.js +51 -84
- package/dist/main/services/workspace/workspace-thumbnails.d.ts +22 -0
- package/dist/main/services/workspace/workspace-thumbnails.js +67 -0
- package/dist/main/utils/ipc-context-source.d.ts +40 -0
- package/dist/main/utils/ipc-context-source.js +23 -0
- package/dist/main/utils/ipc-registry.d.ts +42 -36
- package/dist/main/utils/ipc-registry.js +117 -50
- package/dist/main/utils/sender-policy.d.ts +9 -0
- package/dist/main/utils/sender-policy.js +26 -3
- package/dist/main/utils/theme.d.ts +3 -2
- package/dist/main/utils/theme.js +3 -2
- package/dist/main/windows/main-window/create.d.ts +16 -0
- package/dist/main/windows/main-window/create.js +19 -11
- package/dist/main/windows/main-window/events.d.ts +1 -1
- package/dist/main/windows/main-window/events.js +26 -13
- package/dist/main/windows/main-window/index.d.ts +1 -1
- package/dist/main/windows/main-window/index.js +1 -1
- package/dist/native-host/common/common.js +94 -73
- package/dist/native-host/render/render.js +4079 -3171
- package/dist/native-host/service/service.js +2 -2
- package/dist/preload/runtime/native-host.d.ts +4 -0
- package/dist/preload/runtime/temp-files.js +9 -2
- package/dist/preload/runtime/wait-for-slot-root.d.ts +7 -4
- package/dist/preload/runtime/wait-for-slot-root.js +36 -8
- package/dist/preload/windows/host-dialog-runtime.cjs +10 -3
- package/dist/preload/windows/host-dialog-runtime.cjs.map +2 -2
- package/dist/preload/windows/host-sidebar-runtime.cjs +10 -3
- package/dist/preload/windows/host-sidebar-runtime.cjs.map +2 -2
- package/dist/preload/windows/host-toolbar-runtime.cjs +10 -3
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
- package/dist/preload/windows/main.cjs +181 -4
- package/dist/preload/windows/main.cjs.map +4 -4
- package/dist/preload/windows/simulator.cjs +10 -0
- package/dist/preload/windows/simulator.cjs.map +2 -2
- package/dist/preload/windows/simulator.js +10 -0
- package/dist/render-host/pageFrame.html +1 -1
- package/dist/render-host/preload.cjs +16 -4
- package/dist/renderer/assets/button-CB2MWjHY.js +2 -0
- package/dist/renderer/assets/constants-CkoGPQTS.js +2 -0
- package/dist/renderer/assets/constants-zQZ2-y21.js +2 -0
- package/dist/renderer/assets/{createLucideIcon-Bqi8hpdi.js → createLucideIcon-cZ9FlZZw.js} +2 -2
- package/dist/renderer/assets/devicePicker-H2eIO4e1.js +2 -0
- package/dist/renderer/assets/dialog-pHzEA8Vx.js +46 -0
- package/dist/renderer/assets/dist-B5HADirS.js +2 -0
- package/dist/renderer/assets/{dist-DKWdE1TK.js → dist-DIFlRMKe.js} +2 -2
- package/dist/renderer/assets/hostSidebarDefault-DRWh6OCt.js +2 -0
- package/dist/renderer/assets/index-CEL_XFge.js +2 -0
- package/dist/renderer/assets/{input-Ct3alvkq.js → input-CAObw95L.js} +2 -2
- package/dist/renderer/assets/ipc-channels-overlays-gIPUFnqc.js +2 -0
- package/dist/renderer/assets/jsx-runtime-DICEQH_0.css +1 -0
- package/dist/renderer/assets/{jsx-runtime-DGLHmmOq.js → jsx-runtime-DIw5wnYl.js} +1 -1
- package/dist/renderer/assets/plus-BQ9u5EbL.js +2 -0
- package/dist/renderer/assets/popover-Cw7G2Ctp.js +2 -0
- package/dist/renderer/assets/presets-DMZOb9ay.js +2 -0
- package/dist/renderer/assets/project-api-YTOr_Yvo.js +2 -0
- package/dist/renderer/assets/{projectCreateDialog-Bbjz75bP.js → projectCreateDialog-BmLqiYxn.js} +2 -2
- package/dist/renderer/assets/search-BMvuYF48.js +2 -0
- package/dist/renderer/assets/select-nFP95TxM.js +2 -0
- package/dist/renderer/assets/settings-CnpVfSWt.js +2 -0
- package/dist/renderer/assets/settings-api-Ccsf-Nsj.js +2 -0
- package/dist/renderer/assets/settings-tab-bar-CAzrv32m.js +2 -0
- package/dist/renderer/assets/smartphone-Bb2GdIKy.js +2 -0
- package/dist/renderer/assets/tabs-CPKN3Mmq.js +2 -0
- package/dist/renderer/assets/{tooltip-B_-_YVbw.js → tooltip-D6AxXRk5.js} +2 -2
- package/dist/renderer/assets/types-Bh0hA7El.js +2 -0
- package/dist/renderer/assets/updateDialog-CwndJ0Bl.js +2 -0
- package/dist/renderer/assets/{utils-kfQov6Pa.js → utils-sEB5OZEQ.js} +2 -2
- package/dist/renderer/assets/view-api-B-2zPBR5.js +2 -0
- package/dist/renderer/assets/view-ids-BfaDVV5P.js +2 -0
- package/dist/renderer/assets/workbench-BLJ0wKsg.js +5 -0
- package/dist/renderer/assets/{workbenchSettings-CV4LVA9b.js → workbenchSettings-YD0Ctbm_.js} +3 -3
- package/dist/renderer/entries/device-picker/index.html +28 -0
- package/dist/renderer/entries/host-sidebar-default/index.html +6 -5
- package/dist/renderer/entries/main/index.html +16 -17
- package/dist/renderer/entries/popover/index.html +16 -10
- package/dist/renderer/entries/project-create-dialog/index.html +12 -12
- package/dist/renderer/entries/settings/index.html +10 -10
- package/dist/renderer/entries/tooltip/index.html +5 -5
- package/dist/renderer/entries/update-dialog/index.html +10 -10
- package/dist/renderer/entries/workbench/index.html +49 -0
- package/dist/renderer/entries/workbench-settings/index.html +12 -11
- package/dist/service-host/host-env-update.cjs +23 -0
- package/dist/service-host/preload.cjs +24 -49
- package/dist/service-host/service-message-queue.cjs +71 -0
- package/dist/service-host/sync-impls/system-info.js +31 -9
- package/dist/shared/compile-mode-state.d.ts +73 -0
- package/dist/shared/compile-mode-state.js +100 -0
- package/dist/shared/compile-modes.d.ts +152 -0
- package/dist/shared/compile-modes.js +237 -0
- package/dist/shared/ipc-channels-overlays.d.ts +8 -2
- package/dist/shared/ipc-channels-overlays.js +19 -2
- package/dist/shared/ipc-channels.d.ts +5 -10
- package/dist/shared/ipc-channels.js +17 -14
- package/dist/shared/ipc-schemas.d.ts +76 -24
- package/dist/shared/ipc-schemas.js +55 -10
- package/dist/shared/project-window-types.d.ts +42 -0
- package/dist/shared/project-window-types.js +2 -0
- package/dist/shared/types.d.ts +43 -17
- package/dist/shared/view-ids.d.ts +1 -0
- package/dist/shared/view-ids.js +3 -1
- package/dist/simulator/assets/bridge-channels-B3JAmRfl.js +2 -0
- package/dist/simulator/assets/device-shell-ATEhHV2_.css +1 -0
- package/dist/simulator/assets/device-shell-pxnZZp10.js +431 -0
- package/dist/simulator/assets/simulator-Dwp7lnvu.js +10 -0
- package/dist/simulator/assets/simulator-mini-app-DFo7fhi5.js +2 -0
- package/dist/simulator/assets/simulator-ui-B2ddJB1R.js +2 -0
- package/dist/simulator/simulator.html +3 -3
- package/dist/simulator/temp-files.d.ts +6 -0
- package/dist/simulator/temp-files.js +46 -4
- package/dist/simulator/types.d.ts +7 -0
- package/dist/vscode-workbench/assets/__vite-browser-external-JIkWewFf.js +1 -0
- package/dist/vscode-workbench/assets/{dist-CMfmHflW.js → dist-CXG-u1VB.js} +3 -3
- package/dist/vscode-workbench/assets/{iconv-lite-umd-Bx8CgaZq.js → iconv-lite-umd-Dxx9Qyb4.js} +1 -1
- package/dist/vscode-workbench/assets/{index-CdG6YWhW.js → index-6VJMsaWj.js} +460 -460
- package/dist/vscode-workbench/assets/{jschardet-M0qgd2lN.js → jschardet-Ba3D16vt.js} +1 -1
- package/dist/vscode-workbench/index.html +1 -1
- package/package.json +11 -8
- package/dist/renderer/assets/button-D5Q2wkXq.js +0 -2
- package/dist/renderer/assets/constants-D7DfVqMp.js +0 -2
- package/dist/renderer/assets/constants-D8zsejFq.js +0 -2
- package/dist/renderer/assets/dialog-e1TecTOY.js +0 -46
- package/dist/renderer/assets/hostSidebarDefault-B3yLfy8j.js +0 -2
- package/dist/renderer/assets/index-CsPD68N_.js +0 -5
- package/dist/renderer/assets/ipc-channels-overlays-LsDzGAtV.js +0 -2
- package/dist/renderer/assets/jsx-runtime-2vXHouwr.css +0 -1
- package/dist/renderer/assets/popover-Dix5cm_b.js +0 -2
- package/dist/renderer/assets/project-api-k7Oq9iaq.js +0 -2
- package/dist/renderer/assets/select-acGb0AmJ.js +0 -2
- package/dist/renderer/assets/settings-CQrTMr43.js +0 -2
- package/dist/renderer/assets/settings-api-CrUQ9rYV.js +0 -2
- package/dist/renderer/assets/settings-tab-bar-Cw1kVIf2.js +0 -2
- package/dist/renderer/assets/types-CsEKtFyr.js +0 -2
- package/dist/renderer/assets/updateDialog-NsF8223A.js +0 -2
- package/dist/renderer/assets/view-api-CLn3rcQE.js +0 -2
- package/dist/simulator/assets/bridge-channels-zhsumfky.js +0 -2
- package/dist/simulator/assets/device-shell-CmtM1QxP.js +0 -2
- package/dist/simulator/assets/device-shell-iFZo1igL.css +0 -1
- package/dist/simulator/assets/simulator-CIobn3Mh.js +0 -10
- package/dist/simulator/assets/simulator-mini-app-CRe6n9br.js +0 -2
- package/dist/simulator/assets/simulator-ui-C_cP602X.js +0 -2
- package/dist/vscode-workbench/assets/__vite-browser-external-Bc1vizTf.js +0 -1
|
@@ -1,116 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-Origin-Isolation static server for the embedded VS Code workbench.
|
|
3
|
-
*
|
|
4
|
-
* The workbench needs `SharedArrayBuffer` for the TS web ext-host's
|
|
5
|
-
* project-wide IntelliSense. Chromium only serves that to a document fetched
|
|
6
|
-
* over http(s) carrying COOP `same-origin` + COEP `require-corp` (a custom
|
|
7
|
-
* `protocol.handle` response does NOT unlock it), so the workbench bundle is
|
|
8
|
-
* served from this in-process http server bound to 127.0.0.1 on a random port.
|
|
9
|
-
* Every response carries the three isolation headers so same-origin
|
|
10
|
-
* sub-resources and workers satisfy COEP `require-corp`.
|
|
11
|
-
*
|
|
12
|
-
* Kept separate from `dimina-resource-server` on purpose: that server sends
|
|
13
|
-
* CORP `cross-origin` for the simulator bundles, which is incompatible with the
|
|
14
|
-
* workbench's `same-origin` isolation requirement.
|
|
15
|
-
*
|
|
16
|
-
* `/__fs/*` bridges a `diminafs:`-style FileSystemProvider in the workbench to
|
|
17
|
-
* the live active project on disk, reusing the renderer `project:fs:*`
|
|
18
|
-
* sandbox's realpath-based guards (`resolveWithinProjectRoot`,
|
|
19
|
-
* `assertWritableAncestor`, and the `O_NOFOLLOW` + post-open re-check inside
|
|
20
|
-
* `readFileBuffer`/`writeFile`) so there is a SINGLE sandbox implementation: a
|
|
21
|
-
* symlink inside the root pointing outside it cannot be followed to read or
|
|
22
|
-
* write past the sandbox. The active project root is read per-request (via the
|
|
23
|
-
* injected getter) so project switches take effect immediately.
|
|
24
|
-
*
|
|
25
|
-
* Mutating actions (write/mkdir/delete/rename) additionally require a non-GET
|
|
26
|
-
* method and a same-origin (or origin-less) request, so an arbitrary localhost
|
|
27
|
-
* page cannot drive a destructive `/__fs` call. GET serves only the read-only
|
|
28
|
-
* stat/readdir/read actions.
|
|
29
|
-
*
|
|
30
|
-
* `/__fs/watch` is a read-only SSE stream of the active project's disk changes
|
|
31
|
-
* (devtools-fs-core-feasibility.md §7), decoupled from the compile session so
|
|
32
|
-
* the editor's memfs↔disk sync engine has a source that stays live regardless
|
|
33
|
-
* of compile state; see {@link handleFsWatchRequest}.
|
|
34
|
-
*/
|
|
35
|
-
import http from 'node:http';
|
|
36
|
-
import nodeFs from 'node:fs';
|
|
37
1
|
import fs from 'node:fs/promises';
|
|
38
2
|
import path from 'node:path';
|
|
39
3
|
import { readFileBufferWithin, writeFileWithin, statWithin, readdirWithin, mkdirWithin, deleteWithin, renameWithin, SKIP_DIRS, } from '../ipc/project-fs.js';
|
|
40
4
|
import { handleFsWatchRequest } from './fs-watch-sse.js';
|
|
5
|
+
import { createCoiServerShutdown } from './workbench-coi-shutdown.js';
|
|
6
|
+
import { serveStaticFile } from './workbench-coi-static.js';
|
|
7
|
+
import { registerCoiHostSession } from './workbench-coi-host.js';
|
|
8
|
+
import { FS_TYPE_DIR, FS_TYPE_FILE, toFsBridgeEntry } from './fs-bridge-protocol.js';
|
|
41
9
|
import { jsonRes } from './http-json.js';
|
|
42
10
|
import { miniappPartitionKey } from './views/miniapp-partition.js';
|
|
43
11
|
/** Max `/__fs/write` body; enough to save large source files without OOM. */
|
|
44
12
|
const MAX_FS_WRITE_BYTES = 32 * 1024 * 1024;
|
|
45
|
-
const MIME = {
|
|
46
|
-
'.html': 'text/html; charset=utf-8',
|
|
47
|
-
'.js': 'text/javascript; charset=utf-8',
|
|
48
|
-
'.mjs': 'text/javascript; charset=utf-8',
|
|
49
|
-
'.css': 'text/css; charset=utf-8',
|
|
50
|
-
'.json': 'application/json; charset=utf-8',
|
|
51
|
-
'.map': 'application/json; charset=utf-8',
|
|
52
|
-
'.wasm': 'application/wasm',
|
|
53
|
-
'.svg': 'image/svg+xml',
|
|
54
|
-
'.png': 'image/png',
|
|
55
|
-
'.ttf': 'font/ttf',
|
|
56
|
-
'.woff': 'font/woff',
|
|
57
|
-
'.woff2': 'font/woff2',
|
|
58
|
-
'.ico': 'image/x-icon',
|
|
59
|
-
};
|
|
60
|
-
function setIsolationHeaders(res) {
|
|
61
|
-
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
|
62
|
-
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
|
|
63
|
-
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
|
|
64
|
-
}
|
|
65
|
-
/** Resolve a request pathname inside `root`, rejecting lexical traversal escapes. */
|
|
66
|
-
function containedStaticPath(root, pathname) {
|
|
67
|
-
const rel = pathname.replace(/^\/+/, '') || 'index.html';
|
|
68
|
-
const resolved = path.resolve(root, rel);
|
|
69
|
-
if (resolved !== root && !resolved.startsWith(root + path.sep))
|
|
70
|
-
return null;
|
|
71
|
-
return resolved;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Serve a static file under `root`: lexical containment, then `fs.realpath`
|
|
75
|
-
* BOTH the root and the resolved file before stat/stream, so a symlink inside
|
|
76
|
-
* `root` pointing outside it cannot be served (the lexical check alone follows
|
|
77
|
-
* symlinks because `stat`/`createReadStream` do). Sends the response itself.
|
|
78
|
-
*/
|
|
79
|
-
function serveStaticFile(res, root, pathname) {
|
|
80
|
-
const candidate = containedStaticPath(root, pathname);
|
|
81
|
-
if (!candidate) {
|
|
82
|
-
res.writeHead(403);
|
|
83
|
-
res.end('Forbidden');
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
Promise.all([fs.realpath(root), fs.realpath(candidate)])
|
|
87
|
-
.then(([realRoot, realFile]) => {
|
|
88
|
-
if (realFile !== realRoot && !realFile.startsWith(realRoot + path.sep)) {
|
|
89
|
-
res.writeHead(403);
|
|
90
|
-
res.end('Forbidden');
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
nodeFs.stat(realFile, (err, stat) => {
|
|
94
|
-
if (err || !stat.isFile()) {
|
|
95
|
-
res.writeHead(404);
|
|
96
|
-
res.end('Not Found');
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
res.writeHead(200, {
|
|
100
|
-
'Content-Type': MIME[path.extname(realFile)] ?? 'application/octet-stream',
|
|
101
|
-
'Content-Length': stat.size,
|
|
102
|
-
'Cache-Control': 'no-store',
|
|
103
|
-
});
|
|
104
|
-
// Stream the realpath'd file (already resolved to its real on-disk
|
|
105
|
-
// location, so a symlink inside the root cannot redirect it outside).
|
|
106
|
-
nodeFs.createReadStream(realFile).pipe(res);
|
|
107
|
-
});
|
|
108
|
-
})
|
|
109
|
-
.catch(() => { if (!res.headersSent) {
|
|
110
|
-
res.writeHead(404);
|
|
111
|
-
res.end('Not Found');
|
|
112
|
-
} });
|
|
113
|
-
}
|
|
114
13
|
/** Thrown by {@link readBody} when the accumulated body exceeds the cap. */
|
|
115
14
|
class BodyTooLargeError extends Error {
|
|
116
15
|
constructor() {
|
|
@@ -181,6 +80,22 @@ function rejectUnsafeMutation(req) {
|
|
|
181
80
|
return null;
|
|
182
81
|
}
|
|
183
82
|
/** Map a node fs error to the HTTP status the provider expects. */
|
|
83
|
+
/**
|
|
84
|
+
* Percent-decode a request path, answering the client on a malformed escape.
|
|
85
|
+
* A stray `%` is the client's error and belongs here, not at the shared
|
|
86
|
+
* listener's exception boundary, which cannot tell a bad request from a bug in
|
|
87
|
+
* this handler. Returns `null` once the 400 has been sent.
|
|
88
|
+
*/
|
|
89
|
+
function decodePath(res, pathname) {
|
|
90
|
+
try {
|
|
91
|
+
return decodeURIComponent(pathname);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
res.writeHead(400);
|
|
95
|
+
res.end('Bad Request');
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
184
99
|
function fsErrorStatus(e) {
|
|
185
100
|
const code = e.code;
|
|
186
101
|
if (code === 'ENOACTIVE')
|
|
@@ -199,7 +114,13 @@ function fsErrorStatus(e) {
|
|
|
199
114
|
}
|
|
200
115
|
async function fsStat(ctx) {
|
|
201
116
|
const st = await statWithin(ctx.projectRoot, ctx.rel);
|
|
202
|
-
|
|
117
|
+
const payload = {
|
|
118
|
+
type: st.isDirectory() ? FS_TYPE_DIR : FS_TYPE_FILE,
|
|
119
|
+
size: st.size,
|
|
120
|
+
ctime: st.ctimeMs,
|
|
121
|
+
mtime: st.mtimeMs,
|
|
122
|
+
};
|
|
123
|
+
jsonRes(ctx.res, 200, payload);
|
|
203
124
|
}
|
|
204
125
|
async function fsReaddir(ctx) {
|
|
205
126
|
const entries = await readdirWithin(ctx.projectRoot, ctx.rel);
|
|
@@ -208,14 +129,8 @@ async function fsReaddir(ctx) {
|
|
|
208
129
|
// this returns, so listing them would pull entire dependency trees into the
|
|
209
130
|
// editor memfs and the OPFS ledger. Same-named FILES stay visible.
|
|
210
131
|
const visible = entries.filter((e) => !(e.isDirectory && SKIP_DIRS.has(e.name)));
|
|
211
|
-
// Wire shape
|
|
212
|
-
|
|
213
|
-
// (@dimina-kit/fs-core/sync/watch-expander) needs size+mtimeMs to tell a stat-unchanged
|
|
214
|
-
// survivor from an actually-modified file without re-reading its content.
|
|
215
|
-
// `size`/`mtimeMs` are simply absent (not `null`) for a stat-less entry
|
|
216
|
-
// (readdirWithin's readdir/stat race) — the client side then always
|
|
217
|
-
// treats it as changed.
|
|
218
|
-
jsonRes(ctx.res, 200, visible.map((e) => (e.isDirectory ? [e.name, 2] : [e.name, 1, e.size, e.mtimeMs])));
|
|
132
|
+
// Wire shape and why it carries stats: see fs-bridge-protocol.ts.
|
|
133
|
+
jsonRes(ctx.res, 200, visible.map(toFsBridgeEntry));
|
|
219
134
|
}
|
|
220
135
|
async function fsRead(ctx) {
|
|
221
136
|
const buf = await readFileBufferWithin(ctx.projectRoot, ctx.rel);
|
|
@@ -356,15 +271,28 @@ function workspaceIdOf(identity) {
|
|
|
356
271
|
export async function startWorkbenchCoiServer(options) {
|
|
357
272
|
const root = path.resolve(options.rootDir);
|
|
358
273
|
const contribRoot = options.extensionsDir ? path.resolve(options.extensionsDir) : null;
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
274
|
+
const shutdown = createCoiServerShutdown();
|
|
275
|
+
const handle = (req, res, url) => {
|
|
276
|
+
// This window is tearing down. Anything accepted from here on would act on
|
|
277
|
+
// a project the user has already closed, and a new SSE stream would outlive
|
|
278
|
+
// the drain that was supposed to end them all.
|
|
279
|
+
if (shutdown.closing) {
|
|
280
|
+
res.writeHead(503);
|
|
281
|
+
res.end('Closing');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
362
284
|
// Special-cased ahead of the generic `/__fs/*` dispatch: unlike every other
|
|
363
285
|
// action, this is a long-lived SSE stream, not a one-shot JSON response.
|
|
364
286
|
if (url.pathname === '/__fs/watch') {
|
|
365
|
-
handleFsWatchRequest(req, res, options.getProjectRoot);
|
|
287
|
+
const endStream = handleFsWatchRequest(req, res, options.getProjectRoot);
|
|
288
|
+
if (endStream)
|
|
289
|
+
shutdown.trackWatchStream(res, endStream);
|
|
366
290
|
return;
|
|
367
291
|
}
|
|
292
|
+
// Everything below answers once. Shutdown lets these land rather than
|
|
293
|
+
// dropping the socket under them — a `/__fs/write` mid-body is the file the
|
|
294
|
+
// user just saved.
|
|
295
|
+
shutdown.trackRequest(res);
|
|
368
296
|
if (url.pathname.startsWith('/__fs/')) {
|
|
369
297
|
handleFsRequest(req, res, options.getProjectRoot(), url).catch((e) => {
|
|
370
298
|
if (!res.headersSent)
|
|
@@ -409,25 +337,39 @@ export async function startWorkbenchCoiServer(options) {
|
|
|
409
337
|
jsonRes(res, 500, { error: String(e) }); });
|
|
410
338
|
return;
|
|
411
339
|
}
|
|
412
|
-
const rel =
|
|
340
|
+
const rel = decodePath(res, url.pathname.slice('/__contrib/'.length));
|
|
341
|
+
if (rel === null)
|
|
342
|
+
return;
|
|
413
343
|
serveStaticFile(res, contribRoot, rel);
|
|
414
344
|
return;
|
|
415
345
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
});
|
|
425
|
-
const addr = server.address();
|
|
426
|
-
const port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
346
|
+
const filePath = decodePath(res, url.pathname);
|
|
347
|
+
if (filePath === null)
|
|
348
|
+
return;
|
|
349
|
+
serveStaticFile(res, root, filePath);
|
|
350
|
+
};
|
|
351
|
+
// The listener is shared with every other project window so they land on one
|
|
352
|
+
// origin; this window is reached through its own path prefix. See
|
|
353
|
+
// workbench-coi-host.ts for why the port cannot be per-window.
|
|
354
|
+
const registration = await registerCoiHostSession({ rootDir: root, extensionsDir: contribRoot, session: { handle } });
|
|
427
355
|
return {
|
|
428
|
-
baseUrl:
|
|
429
|
-
port,
|
|
430
|
-
|
|
356
|
+
baseUrl: `${registration.origin}/w/${registration.token}/`,
|
|
357
|
+
port: registration.port,
|
|
358
|
+
// Why this is not a bare `server.close()`: see workbench-coi-shutdown.ts.
|
|
359
|
+
// Drain first so this window's SSE streams end and its in-flight writes
|
|
360
|
+
// land, THEN drop the route — the listener itself only goes away with the
|
|
361
|
+
// last window.
|
|
362
|
+
close: async () => {
|
|
363
|
+
// `finally`: a drain that throws must still drop this window's route.
|
|
364
|
+
// Leaving it registered would keep a torn-down window's bridge reachable
|
|
365
|
+
// on the shared listener for the rest of the process.
|
|
366
|
+
try {
|
|
367
|
+
await shutdown.drain();
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
await registration.release();
|
|
371
|
+
}
|
|
372
|
+
},
|
|
431
373
|
};
|
|
432
374
|
}
|
|
433
375
|
//# sourceMappingURL=workbench-coi-server.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shutdown bookkeeping for the workbench COI bridge, at two levels.
|
|
3
|
+
*
|
|
4
|
+
* A project window's bridge is closed as part of that window's teardown, which
|
|
5
|
+
* awaits it. Two things pull against each other at that moment. `/__fs/watch`
|
|
6
|
+
* is an SSE stream that only ever ends when its client disconnects, so waiting
|
|
7
|
+
* on it waits on the editor being destroyed in the same breath — the window
|
|
8
|
+
* hangs. Dropping every socket outright instead loses a `/__fs/write` whose
|
|
9
|
+
* body is still arriving, which is the file the user just saved.
|
|
10
|
+
*
|
|
11
|
+
* So neither extreme: {@link CoiServerShutdown.drain} ends the streams this
|
|
12
|
+
* owner started, lets a request already being served finish, and returns once
|
|
13
|
+
* it has — or once the grace window expires, so teardown is bounded no matter
|
|
14
|
+
* what a client does. The shared listener behind every window (see
|
|
15
|
+
* workbench-coi-host.ts) then uses {@link CoiServerShutdown.close}, which adds
|
|
16
|
+
* the server close and takes the sockets that outlived it.
|
|
17
|
+
*/
|
|
18
|
+
import type http from 'node:http';
|
|
19
|
+
export interface CoiServerShutdown {
|
|
20
|
+
/** A live `/__fs/watch` stream, with the call that ends it server-side. */
|
|
21
|
+
trackWatchStream: (res: http.ServerResponse, endStream: () => void) => void;
|
|
22
|
+
/** A one-shot request whose response must be allowed to land. */
|
|
23
|
+
trackRequest: (res: http.ServerResponse) => void;
|
|
24
|
+
/**
|
|
25
|
+
* True once {@link drain} has started. The owner refuses new requests from
|
|
26
|
+
* here on: this bridge is bound to a window that is going away, and a
|
|
27
|
+
* request accepted now would act on a project the user has closed.
|
|
28
|
+
*/
|
|
29
|
+
readonly closing: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* End the tracked streams and wait for the tracked one-shot responses to
|
|
32
|
+
* land, bounded by the grace window. Leaves the listener alone — one window
|
|
33
|
+
* going away must not take the shared server with it.
|
|
34
|
+
*/
|
|
35
|
+
drain: () => Promise<void>;
|
|
36
|
+
/** {@link drain}, plus closing the server and taking any surviving sockets. */
|
|
37
|
+
close: (server: http.Server) => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export declare function createCoiServerShutdown(graceMs?: number): CoiServerShutdown;
|
|
40
|
+
//# sourceMappingURL=workbench-coi-shutdown.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** How long a request already in flight may hold up the window's teardown. */
|
|
2
|
+
const DEFAULT_GRACE_MS = 2_000;
|
|
3
|
+
export function createCoiServerShutdown(graceMs = DEFAULT_GRACE_MS) {
|
|
4
|
+
const watchStreams = new Set();
|
|
5
|
+
const inFlight = new Set();
|
|
6
|
+
let onDrained = null;
|
|
7
|
+
let closing = false;
|
|
8
|
+
const drain = () => new Promise((resolve) => {
|
|
9
|
+
closing = true;
|
|
10
|
+
for (const endStream of watchStreams)
|
|
11
|
+
endStream();
|
|
12
|
+
watchStreams.clear();
|
|
13
|
+
// Nothing is being served: the sockets that remain are a client's to
|
|
14
|
+
// keep alive, and waiting on them is waiting on nobody.
|
|
15
|
+
if (inFlight.size === 0) {
|
|
16
|
+
resolve();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let deadline = null;
|
|
20
|
+
const finish = () => {
|
|
21
|
+
onDrained = null;
|
|
22
|
+
if (deadline)
|
|
23
|
+
clearTimeout(deadline);
|
|
24
|
+
resolve();
|
|
25
|
+
};
|
|
26
|
+
const expire = () => {
|
|
27
|
+
// The grace window is up and something is still mid-request — a client
|
|
28
|
+
// that stalled halfway through a body, most likely. Destroying it here
|
|
29
|
+
// is the point: the handler still holds the closing window's project,
|
|
30
|
+
// so a body that lands minutes from now would write into a project the
|
|
31
|
+
// user has closed, and the socket would outlive the window on the
|
|
32
|
+
// shared listener.
|
|
33
|
+
for (const res of [...inFlight])
|
|
34
|
+
res.destroy();
|
|
35
|
+
inFlight.clear();
|
|
36
|
+
finish();
|
|
37
|
+
};
|
|
38
|
+
deadline = setTimeout(expire, graceMs);
|
|
39
|
+
onDrained = finish;
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
get closing() {
|
|
43
|
+
return closing;
|
|
44
|
+
},
|
|
45
|
+
trackWatchStream(res, endStream) {
|
|
46
|
+
watchStreams.add(endStream);
|
|
47
|
+
res.on('close', () => watchStreams.delete(endStream));
|
|
48
|
+
},
|
|
49
|
+
trackRequest(res) {
|
|
50
|
+
inFlight.add(res);
|
|
51
|
+
res.on('close', () => {
|
|
52
|
+
inFlight.delete(res);
|
|
53
|
+
if (inFlight.size === 0)
|
|
54
|
+
onDrained?.();
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
drain,
|
|
58
|
+
async close(server) {
|
|
59
|
+
// Stop accepting first, so nothing new arrives during the drain; the
|
|
60
|
+
// callback only fires once every connection is gone, which is why the
|
|
61
|
+
// sockets are taken below rather than waited on.
|
|
62
|
+
const closed = new Promise((resolve) => server.close(() => resolve()));
|
|
63
|
+
await drain();
|
|
64
|
+
server.closeAllConnections();
|
|
65
|
+
await closed;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=workbench-coi-shutdown.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static-file serving and the cross-origin-isolation headers shared by the
|
|
3
|
+
* workbench COI host and every window's bridge.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `workbench-coi-server.ts` so the host module can serve the
|
|
6
|
+
* bundle without importing the per-window bridge (which imports the host).
|
|
7
|
+
*/
|
|
8
|
+
import http from 'node:http';
|
|
9
|
+
/**
|
|
10
|
+
* The workbench needs `SharedArrayBuffer` for the TS web ext-host's
|
|
11
|
+
* project-wide IntelliSense, and Chromium only serves that to a
|
|
12
|
+
* cross-origin-isolated document. Every response carries the three headers so
|
|
13
|
+
* same-origin sub-resources and workers satisfy COEP `require-corp`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function setIsolationHeaders(res: http.ServerResponse): void;
|
|
16
|
+
/**
|
|
17
|
+
* Serve a static file under `root`: lexical containment, then `fs.realpath`
|
|
18
|
+
* BOTH the root and the resolved file before stat/stream, so a symlink inside
|
|
19
|
+
* `root` pointing outside it cannot be served (the lexical check alone follows
|
|
20
|
+
* symlinks because `stat`/`createReadStream` do). Sends the response itself.
|
|
21
|
+
*/
|
|
22
|
+
export declare function serveStaticFile(res: http.ServerResponse, root: string, pathname: string): void;
|
|
23
|
+
//# sourceMappingURL=workbench-coi-static.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import nodeFs from 'node:fs';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const MIME = {
|
|
5
|
+
'.html': 'text/html; charset=utf-8',
|
|
6
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
7
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
8
|
+
'.css': 'text/css; charset=utf-8',
|
|
9
|
+
'.json': 'application/json; charset=utf-8',
|
|
10
|
+
'.map': 'application/json; charset=utf-8',
|
|
11
|
+
'.wasm': 'application/wasm',
|
|
12
|
+
'.svg': 'image/svg+xml',
|
|
13
|
+
'.png': 'image/png',
|
|
14
|
+
'.ttf': 'font/ttf',
|
|
15
|
+
'.woff': 'font/woff',
|
|
16
|
+
'.woff2': 'font/woff2',
|
|
17
|
+
'.ico': 'image/x-icon',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The workbench needs `SharedArrayBuffer` for the TS web ext-host's
|
|
21
|
+
* project-wide IntelliSense, and Chromium only serves that to a
|
|
22
|
+
* cross-origin-isolated document. Every response carries the three headers so
|
|
23
|
+
* same-origin sub-resources and workers satisfy COEP `require-corp`.
|
|
24
|
+
*/
|
|
25
|
+
export function setIsolationHeaders(res) {
|
|
26
|
+
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
|
27
|
+
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
|
|
28
|
+
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
|
|
29
|
+
}
|
|
30
|
+
/** Resolve a request pathname inside `root`, rejecting lexical traversal escapes. */
|
|
31
|
+
function containedStaticPath(root, pathname) {
|
|
32
|
+
const rel = pathname.replace(/^\/+/, '') || 'index.html';
|
|
33
|
+
const resolved = path.resolve(root, rel);
|
|
34
|
+
if (resolved !== root && !resolved.startsWith(root + path.sep))
|
|
35
|
+
return null;
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Serve a static file under `root`: lexical containment, then `fs.realpath`
|
|
40
|
+
* BOTH the root and the resolved file before stat/stream, so a symlink inside
|
|
41
|
+
* `root` pointing outside it cannot be served (the lexical check alone follows
|
|
42
|
+
* symlinks because `stat`/`createReadStream` do). Sends the response itself.
|
|
43
|
+
*/
|
|
44
|
+
export function serveStaticFile(res, root, pathname) {
|
|
45
|
+
const candidate = containedStaticPath(root, pathname);
|
|
46
|
+
if (!candidate) {
|
|
47
|
+
res.writeHead(403);
|
|
48
|
+
res.end('Forbidden');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
Promise.all([fs.realpath(root), fs.realpath(candidate)])
|
|
52
|
+
.then(([realRoot, realFile]) => {
|
|
53
|
+
if (realFile !== realRoot && !realFile.startsWith(realRoot + path.sep)) {
|
|
54
|
+
res.writeHead(403);
|
|
55
|
+
res.end('Forbidden');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
nodeFs.stat(realFile, (err, stat) => {
|
|
59
|
+
if (err || !stat.isFile()) {
|
|
60
|
+
res.writeHead(404);
|
|
61
|
+
res.end('Not Found');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
res.writeHead(200, {
|
|
65
|
+
'Content-Type': MIME[path.extname(realFile)] ?? 'application/octet-stream',
|
|
66
|
+
'Content-Length': stat.size,
|
|
67
|
+
'Cache-Control': 'no-store',
|
|
68
|
+
});
|
|
69
|
+
// Stream the realpath'd file (already resolved to its real on-disk
|
|
70
|
+
// location, so a symlink inside the root cannot redirect it outside).
|
|
71
|
+
const stream = nodeFs.createReadStream(realFile);
|
|
72
|
+
// The file can vanish or lose permissions between the stat and the
|
|
73
|
+
// open. The stream reports that asynchronously, once the 200 and the
|
|
74
|
+
// Content-Length are already on the wire, so no status can be changed
|
|
75
|
+
// — and an unhandled 'error' on a stream is an uncaught exception in
|
|
76
|
+
// the main process. Take the socket instead, which is what a client
|
|
77
|
+
// sees for a truncated body anyway.
|
|
78
|
+
stream.on('error', () => { res.destroy(); });
|
|
79
|
+
stream.pipe(res);
|
|
80
|
+
});
|
|
81
|
+
})
|
|
82
|
+
.catch(() => { if (!res.headersSent) {
|
|
83
|
+
res.writeHead(404);
|
|
84
|
+
res.end('Not Found');
|
|
85
|
+
} });
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=workbench-coi-static.js.map
|
|
@@ -12,6 +12,7 @@ import type { StorageApi } from './simulator-storage/index.js';
|
|
|
12
12
|
import type { SyncStorageChange } from '../../shared/ipc-channels.js';
|
|
13
13
|
import { DisposableRegistry, type ConnectionRegistry } from '@dimina-kit/electron-deck/main';
|
|
14
14
|
import type { SenderPolicy } from '../utils/ipc-registry.js';
|
|
15
|
+
import type { AppServices } from './app-services.js';
|
|
15
16
|
import { type RendererNotifier } from './notifications/renderer-notifier.js';
|
|
16
17
|
import { type CompileLogBuffer } from './workspace/compile-log-buffer.js';
|
|
17
18
|
import { type SessionStatusStore } from './workspace/session-status-store.js';
|
|
@@ -120,6 +121,14 @@ export interface WorkbenchContext extends RuntimeContext {
|
|
|
120
121
|
* lazily so it stays correct as windows/views come and go.
|
|
121
122
|
*/
|
|
122
123
|
senderPolicy: SenderPolicy;
|
|
124
|
+
/**
|
|
125
|
+
* The subset of `senderPolicy` this context structurally OWNS — its own
|
|
126
|
+
* window's renderer, its settings window and the overlay views mounted
|
|
127
|
+
* inside it. Excludes the app-wide trusted-window map, which no single
|
|
128
|
+
* context can claim. `createWindowContextRouter` uses it to decide which
|
|
129
|
+
* window a message actually came from.
|
|
130
|
+
*/
|
|
131
|
+
ownsSender: SenderPolicy;
|
|
123
132
|
/**
|
|
124
133
|
* Reference-counted map of `webContents.id` → live registration count for
|
|
125
134
|
* host-owned BrowserWindows registered as trusted senders via
|
|
@@ -128,12 +137,20 @@ export interface WorkbenchContext extends RuntimeContext {
|
|
|
128
137
|
* single `closed` event, which zeroes the count outright) to un-trust it.
|
|
129
138
|
* Consulted by `createWorkbenchSenderPolicy` in addition to the static
|
|
130
139
|
* main-window / overlay checks.
|
|
140
|
+
*
|
|
141
|
+
* Supplied by {@link AppServices} when the caller has one: the host
|
|
142
|
+
* registers its window against the application, so the same map backs every
|
|
143
|
+
* context.
|
|
131
144
|
*/
|
|
132
145
|
trustedWindowSenderIds: Map<number, number>;
|
|
133
146
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
147
|
+
* Registry of host-registered simulator custom APIs. Populated via
|
|
148
|
+
* `instance.registerSimulatorApi`; read by the simulator IPC handlers.
|
|
149
|
+
*
|
|
150
|
+
* Supplied by {@link AppServices} when the caller has one, and therefore
|
|
151
|
+
* shared across contexts: the host registers each handler once, so a window
|
|
152
|
+
* opened afterwards must still answer those `wx.*` calls. A context built
|
|
153
|
+
* without app services (focused unit tests) gets its own registry.
|
|
137
154
|
*/
|
|
138
155
|
simulatorApis: SimulatorApiRegistry;
|
|
139
156
|
/**
|
|
@@ -258,6 +275,13 @@ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' |
|
|
|
258
275
|
mainWindow: BrowserWindow;
|
|
259
276
|
preloadPath: string;
|
|
260
277
|
rendererDir: string;
|
|
278
|
+
/**
|
|
279
|
+
* Services owned by the application rather than by this window. When
|
|
280
|
+
* present their instances are adopted instead of constructing per-context
|
|
281
|
+
* ones, so every window shares the host's single registration. Optional so
|
|
282
|
+
* focused unit tests can still build a stand-alone context.
|
|
283
|
+
*/
|
|
284
|
+
appServices?: AppServices;
|
|
261
285
|
brandingProvider?: WorkbenchContext['brandingProvider'];
|
|
262
286
|
/** Host-supplied project list backend. Defaults to LocalProjectsProvider. */
|
|
263
287
|
projectsProvider?: ProjectsProvider;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRuntimeEvents, } from '@dimina-kit/electron-runtime/main/runtime-context';
|
|
2
2
|
import { createCdpSessionBroker } from './cdp-session/index.js';
|
|
3
3
|
import { createConnectionRegistry, DisposableRegistry, } from '@dimina-kit/electron-deck/main';
|
|
4
|
-
import { createWorkbenchSenderPolicy } from '../utils/sender-policy.js';
|
|
4
|
+
import { createWorkbenchOwnedSenderCheck, createWorkbenchSenderPolicy, } from '../utils/sender-policy.js';
|
|
5
5
|
import { defaultAdapter } from './default-adapter.js';
|
|
6
6
|
import { createRendererNotifier, } from './notifications/renderer-notifier.js';
|
|
7
7
|
import { createCompileLogBuffer, } from './workspace/compile-log-buffer.js';
|
|
@@ -50,8 +50,9 @@ export function createWorkbenchContext(opts) {
|
|
|
50
50
|
ctx.connections = createConnectionRegistry();
|
|
51
51
|
ctx.cdpSessionBroker = createCdpSessionBroker({ connections: ctx.connections });
|
|
52
52
|
ctx.registry.add(() => ctx.cdpSessionBroker.dispose());
|
|
53
|
-
ctx.trustedWindowSenderIds =
|
|
54
|
-
|
|
53
|
+
ctx.trustedWindowSenderIds =
|
|
54
|
+
opts.appServices?.trustedWindowSenderIds ?? new Map();
|
|
55
|
+
ctx.simulatorApis = opts.appServices?.simulatorApis ?? createSimulatorApiRegistry();
|
|
55
56
|
ctx.simulatorUiExtensions = createSimulatorUiExtensionRegistry();
|
|
56
57
|
ctx.registry.add(() => ctx.simulatorUiExtensions.clear());
|
|
57
58
|
ctx.windows = createWindowService(opts.mainWindow);
|
|
@@ -71,12 +72,16 @@ export function createWorkbenchContext(opts) {
|
|
|
71
72
|
// at call time through the live context, which structurally satisfies the
|
|
72
73
|
// helper's narrow OpenSettingsWindowDeps.
|
|
73
74
|
ctx.openSettings = () => openSettingsWindow(ctx);
|
|
74
|
-
ctx.projectsProvider =
|
|
75
|
-
|
|
75
|
+
ctx.projectsProvider =
|
|
76
|
+
opts.appServices?.projectsProvider ?? opts.projectsProvider ?? createLocalProjectsProvider();
|
|
77
|
+
ctx.projectTemplates =
|
|
78
|
+
opts.appServices?.projectTemplates ??
|
|
79
|
+
resolveTemplates(BUILTIN_TEMPLATES, opts.projectTemplates ?? [], opts.builtinTemplates ?? 'all');
|
|
76
80
|
ctx.customCreateProjectDialog = opts.customCreateProjectDialog;
|
|
77
81
|
ctx.customEditProjectDialog = opts.customEditProjectDialog;
|
|
78
82
|
ctx.onBeforeOpenProject = opts.onBeforeOpenProject;
|
|
79
83
|
ctx.workspace = createWorkspaceService(ctx);
|
|
84
|
+
ctx.ownsSender = createWorkbenchOwnedSenderCheck(ctx);
|
|
80
85
|
ctx.senderPolicy = createWorkbenchSenderPolicy(ctx);
|
|
81
86
|
return ctx;
|
|
82
87
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapts the compile-mode half of `ProjectsProvider` — where every method is
|
|
3
|
+
* optional — onto the four methods `WorkspaceService` always exposes.
|
|
4
|
+
*
|
|
5
|
+
* A host may implement the mode list, only the older single-config pair, or
|
|
6
|
+
* neither. Each direction degrades on its own so a partial host still gets the
|
|
7
|
+
* behavior it can support instead of an exception.
|
|
8
|
+
*/
|
|
9
|
+
import type { CompileConfig, CompileModes } from '../../../shared/types.js';
|
|
10
|
+
import type { ProjectsProvider } from '../projects/types.js';
|
|
11
|
+
export interface CompileModeAdapter {
|
|
12
|
+
getCompileConfig(projectPath: string): Promise<CompileConfig>;
|
|
13
|
+
saveCompileConfig(projectPath: string, config: CompileConfig): Promise<void>;
|
|
14
|
+
getCompileModes(projectPath: string): Promise<CompileModes>;
|
|
15
|
+
saveCompileModes(projectPath: string, modes: CompileModes): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare function createCompileModeAdapter(provider: ProjectsProvider): CompileModeAdapter;
|
|
18
|
+
//# sourceMappingURL=compile-mode-adapter.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapts the compile-mode half of `ProjectsProvider` — where every method is
|
|
3
|
+
* optional — onto the four methods `WorkspaceService` always exposes.
|
|
4
|
+
*
|
|
5
|
+
* A host may implement the mode list, only the older single-config pair, or
|
|
6
|
+
* neither. Each direction degrades on its own so a partial host still gets the
|
|
7
|
+
* behavior it can support instead of an exception.
|
|
8
|
+
*/
|
|
9
|
+
import { compileConfigToModes, emptyCompileModes, resolveCompileConfig, } from '../../../shared/compile-modes.js';
|
|
10
|
+
import { DEFAULT_COMPILE_CONFIG } from '../projects/types.js';
|
|
11
|
+
export function createCompileModeAdapter(provider) {
|
|
12
|
+
return {
|
|
13
|
+
// Prefer the host's own resolver: only it can substitute the project's
|
|
14
|
+
// entry page for 普通编译's empty start page. Deriving from the mode list
|
|
15
|
+
// is the fallback for hosts that implement modes but not this.
|
|
16
|
+
async getCompileConfig(projectPath) {
|
|
17
|
+
if (provider.getCompileConfig) {
|
|
18
|
+
return (await provider.getCompileConfig(projectPath));
|
|
19
|
+
}
|
|
20
|
+
if (provider.getCompileModes) {
|
|
21
|
+
return resolveCompileConfig(await provider.getCompileModes(projectPath));
|
|
22
|
+
}
|
|
23
|
+
return DEFAULT_COMPILE_CONFIG;
|
|
24
|
+
},
|
|
25
|
+
async saveCompileConfig(projectPath, config) {
|
|
26
|
+
if (provider.saveCompileConfig) {
|
|
27
|
+
await provider.saveCompileConfig(projectPath, config);
|
|
28
|
+
}
|
|
29
|
+
// No persistence when the host opts out; the renderer's edits then
|
|
30
|
+
// do not survive a reload, matching the documented contract.
|
|
31
|
+
},
|
|
32
|
+
// Mirror of saveCompileModes' degradation: a host still on the single
|
|
33
|
+
// config gets it projected into one selected mode, so its start page and
|
|
34
|
+
// params actually launch instead of being read as 普通编译 and dropped.
|
|
35
|
+
async getCompileModes(projectPath) {
|
|
36
|
+
if (provider.getCompileModes) {
|
|
37
|
+
return await provider.getCompileModes(projectPath);
|
|
38
|
+
}
|
|
39
|
+
if (provider.getCompileConfig) {
|
|
40
|
+
return compileConfigToModes((await provider.getCompileConfig(projectPath)));
|
|
41
|
+
}
|
|
42
|
+
return emptyCompileModes();
|
|
43
|
+
},
|
|
44
|
+
async saveCompileModes(projectPath, modes) {
|
|
45
|
+
if (provider.saveCompileModes) {
|
|
46
|
+
await provider.saveCompileModes(projectPath, modes);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Degrade for a host still on the single-config setter: the selection
|
|
50
|
+
// still takes effect, it just cannot store the named list.
|
|
51
|
+
if (provider.saveCompileConfig) {
|
|
52
|
+
await provider.saveCompileConfig(projectPath, resolveCompileConfig(modes));
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=compile-mode-adapter.js.map
|