electrobun-preact-devtools 0.0.0 → 0.1.0
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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/app-preload.d.mts +41 -0
- package/dist/app-preload.mjs +126 -0
- package/dist/bun.d.mts +70 -0
- package/dist/bun.mjs +298 -0
- package/dist/devtools-view.d.mts +36 -0
- package/dist/devtools-view.mjs +221 -0
- package/dist/index-ohWyACpe.d.mts +105 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +2 -0
- package/dist/logger-Bpp6JxH4.mjs +23 -0
- package/dist/protocol-rAxFKNGR.mjs +7 -0
- package/dist/rpc-Ct-cJYzf.d.mts +13 -0
- package/dist/style.css +1918 -0
- package/dist/upstream-CToW1NK7.d.mts +1 -0
- package/dist/upstream-DgCs5wGH.mjs +6603 -0
- package/docs/context.md +92 -0
- package/examples/compose-existing-rpc.ts +145 -0
- package/examples/minimal.ts +52 -0
- package/examples/tsconfig.json +16 -0
- package/package.json +75 -10
- package/readme.md +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Alec Larson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# electrobun-preact-devtools
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
`electrobun-preact-devtools` runs the Preact devtools UI in a standalone Electrobun window.
|
|
6
|
+
It gives you a Bun-side host, an app-window preload bridge, and a devtools-window mount API.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add electrobun-preact-devtools
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Example
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { BrowserWindow } from 'electrobun/bun'
|
|
18
|
+
import { Electroview } from 'electrobun/view'
|
|
19
|
+
import { createPreactDevtoolsHost } from 'electrobun-preact-devtools/bun'
|
|
20
|
+
import {
|
|
21
|
+
composeAppWindowViewRPC,
|
|
22
|
+
installPreactDevtoolsBridge,
|
|
23
|
+
} from 'electrobun-preact-devtools/app-preload'
|
|
24
|
+
import {
|
|
25
|
+
composeDevtoolsWindowViewRPC,
|
|
26
|
+
mountPreactDevtoolsWindow,
|
|
27
|
+
} from 'electrobun-preact-devtools/devtools-view'
|
|
28
|
+
|
|
29
|
+
const host = createPreactDevtoolsHost({
|
|
30
|
+
openWindow({ rpc }) {
|
|
31
|
+
return new BrowserWindow({
|
|
32
|
+
title: 'Preact Devtools',
|
|
33
|
+
url: 'views://preact-devtools/index.html',
|
|
34
|
+
rpc,
|
|
35
|
+
})
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const appWindow = new BrowserWindow({
|
|
40
|
+
title: 'My App',
|
|
41
|
+
url: 'views://app/index.html',
|
|
42
|
+
preload: 'views://app/preload.js',
|
|
43
|
+
rpc: host.createAppWindowRPC({ targetId: 'main' }),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
host.registerAppWindow({ targetId: 'main', window: appWindow })
|
|
47
|
+
|
|
48
|
+
const appElectroview = new Electroview({ rpc: composeAppWindowViewRPC({}) })
|
|
49
|
+
installPreactDevtoolsBridge({ electroview: appElectroview })
|
|
50
|
+
|
|
51
|
+
const devtoolsElectroview = new Electroview({
|
|
52
|
+
rpc: composeDevtoolsWindowViewRPC({}),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
mountPreactDevtoolsWindow({
|
|
56
|
+
container: document.getElementById('root')!,
|
|
57
|
+
electroview: devtoolsElectroview,
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Documentation Map
|
|
62
|
+
|
|
63
|
+
- Concepts and lifecycle: [docs/context.md](/Users/alec/dev/alloc/preact-devtools-standalone/docs/context.md)
|
|
64
|
+
- Minimal example: [examples/minimal.ts](/Users/alec/dev/alloc/preact-devtools-standalone/examples/minimal.ts)
|
|
65
|
+
- Existing RPC composition: [examples/compose-existing-rpc.ts](/Users/alec/dev/alloc/preact-devtools-standalone/examples/compose-existing-rpc.ts)
|
|
66
|
+
- Exact exported signatures: generated in `dist/*.d.mts` after `pnpm build`
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { u as PreactDevtoolsAppWindowRPC } from "./index-ohWyACpe.mjs";
|
|
2
|
+
import { n as PreactDevtoolsLogger, t as ComposeRPCHandlers } from "./rpc-Ct-cJYzf.mjs";
|
|
3
|
+
import { ElectrobunRPCSchema, Electroview } from "electrobun/view";
|
|
4
|
+
|
|
5
|
+
//#region src/app-preload/bridge.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Options for installing the application-window bridge.
|
|
8
|
+
*
|
|
9
|
+
* The bridge must be installed before the application imports `preact/debug`
|
|
10
|
+
* or `preact/devtools`.
|
|
11
|
+
*/
|
|
12
|
+
interface InstallPreactDevtoolsBridgeOptions {
|
|
13
|
+
electroview?: Electroview<any>;
|
|
14
|
+
globalName?: string;
|
|
15
|
+
logger?: PreactDevtoolsLogger;
|
|
16
|
+
}
|
|
17
|
+
/** Handle returned by `installPreactDevtoolsBridge(...)`. */
|
|
18
|
+
interface InstalledPreactDevtoolsBridge {
|
|
19
|
+
dispose(): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Installs `window.__PREACT_DEVTOOLS__` for the current application window and
|
|
23
|
+
* wires it to Electrobun RPC.
|
|
24
|
+
*/
|
|
25
|
+
declare function installPreactDevtoolsBridge(options?: InstallPreactDevtoolsBridgeOptions): InstalledPreactDevtoolsBridge;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/app-preload/rpc.d.ts
|
|
28
|
+
/** The browser-side RPC definition type returned by `Electroview.defineRPC(...)`. */
|
|
29
|
+
type ViewRPCDefinition<Schema extends ElectrobunRPCSchema = ElectrobunRPCSchema> = ReturnType<typeof Electroview.defineRPC<Schema>>;
|
|
30
|
+
/** Consumer-owned webview handlers that are merged with the reserved app-bridge channel. */
|
|
31
|
+
type ViewComposeRPCHandlers<Schema extends ElectrobunRPCSchema> = ComposeRPCHandlers<Schema, 'webview'>;
|
|
32
|
+
/**
|
|
33
|
+
* Creates the app-window `Electroview.defineRPC(...)` result for apps that
|
|
34
|
+
* already have their own webview RPC handlers.
|
|
35
|
+
*/
|
|
36
|
+
declare function composeAppWindowViewRPC<TRPC extends PreactDevtoolsAppWindowRPC>(options: {
|
|
37
|
+
handlers?: ViewComposeRPCHandlers<TRPC>;
|
|
38
|
+
maxRequestTime?: number;
|
|
39
|
+
}): ViewRPCDefinition<TRPC>;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { type InstallPreactDevtoolsBridgeOptions, type InstalledPreactDevtoolsBridge, type ViewComposeRPCHandlers, type ViewRPCDefinition, composeAppWindowViewRPC, installPreactDevtoolsBridge };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { t as PREACT_DEVTOOLS_MESSAGE } from "./protocol-rAxFKNGR.mjs";
|
|
2
|
+
import { n as warnUnboundHandler, r as composeRPCHandlers, t as emitLog } from "./logger-Bpp6JxH4.mjs";
|
|
3
|
+
import { r as createHook } from "./upstream-DgCs5wGH.mjs";
|
|
4
|
+
import { Electroview } from "electrobun/view";
|
|
5
|
+
//#region src/app-preload/rpc.ts
|
|
6
|
+
/**
|
|
7
|
+
* Creates the app-window `Electroview.defineRPC(...)` result for apps that
|
|
8
|
+
* already have their own webview RPC handlers.
|
|
9
|
+
*/
|
|
10
|
+
function composeAppWindowViewRPC(options) {
|
|
11
|
+
return Electroview.defineRPC({
|
|
12
|
+
maxRequestTime: options.maxRequestTime,
|
|
13
|
+
handlers: composeRPCHandlers(options.handlers, (message) => {
|
|
14
|
+
const bridge = getActiveBridge();
|
|
15
|
+
if (!bridge) {
|
|
16
|
+
warnUnboundHandler("app bridge");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
bridge.handleMessage(message);
|
|
20
|
+
})
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/app-preload/bridge.ts
|
|
25
|
+
var BridgeController = class {
|
|
26
|
+
electroview;
|
|
27
|
+
globalName;
|
|
28
|
+
logger;
|
|
29
|
+
hook;
|
|
30
|
+
disposed = false;
|
|
31
|
+
listeners = /* @__PURE__ */ new Map();
|
|
32
|
+
pageListeners = /* @__PURE__ */ new Map();
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
this.electroview = options.electroview ?? new Electroview({ rpc: composeAppWindowViewRPC({}) });
|
|
35
|
+
this.globalName = options.globalName ?? "__PREACT_DEVTOOLS__";
|
|
36
|
+
this.logger = options.logger;
|
|
37
|
+
const currentValue = window[this.globalName];
|
|
38
|
+
if (currentValue && currentValue !== activeBridge?.hook) throw new Error(`Cannot install the Preact devtools bridge because window.${this.globalName} is already defined.`);
|
|
39
|
+
this.hook = createHook({
|
|
40
|
+
listen: (type, callback) => {
|
|
41
|
+
this.addListener(this.listeners, type, callback);
|
|
42
|
+
},
|
|
43
|
+
send: (type, data) => {
|
|
44
|
+
if (this.disposed) return;
|
|
45
|
+
if (type === "root-order-page") {
|
|
46
|
+
this.emit(this.pageListeners, type, data);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.sendToBun({
|
|
50
|
+
kind: "hook-event",
|
|
51
|
+
type,
|
|
52
|
+
data
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
listenToPage: (type, callback) => {
|
|
56
|
+
this.addListener(this.pageListeners, type, callback);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
window[this.globalName] = this.hook;
|
|
60
|
+
this.sendToBun({ kind: "bridge-ready" });
|
|
61
|
+
}
|
|
62
|
+
handleMessage(message) {
|
|
63
|
+
switch (message.kind) {
|
|
64
|
+
case "session-connected": return;
|
|
65
|
+
case "session-disconnected":
|
|
66
|
+
this.emit(this.listeners, "disconnect", null);
|
|
67
|
+
return;
|
|
68
|
+
case "request-refresh":
|
|
69
|
+
this.emit(this.listeners, "refresh", null);
|
|
70
|
+
return;
|
|
71
|
+
case "devtools-command":
|
|
72
|
+
this.emit(this.listeners, message.type, message.data);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
dispose() {
|
|
77
|
+
if (this.disposed) return;
|
|
78
|
+
this.disposed = true;
|
|
79
|
+
this.listeners.clear();
|
|
80
|
+
this.pageListeners.clear();
|
|
81
|
+
this.sendToBun({ kind: "bridge-disposed" });
|
|
82
|
+
if (window[this.globalName] === this.hook) delete window[this.globalName];
|
|
83
|
+
if (activeBridge === this) activeBridge = void 0;
|
|
84
|
+
}
|
|
85
|
+
sendToBun(message) {
|
|
86
|
+
const send = this.electroview.rpc?.sendProxy?.[PREACT_DEVTOOLS_MESSAGE];
|
|
87
|
+
if (typeof send === "function") {
|
|
88
|
+
send(message);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
emitLog(this.logger, {
|
|
92
|
+
level: "warn",
|
|
93
|
+
code: "missing-app-transport",
|
|
94
|
+
message: "Unable to send a Preact devtools bridge message because the RPC transport is unavailable.",
|
|
95
|
+
detail: message
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
addListener(map, type, callback) {
|
|
99
|
+
let listeners = map.get(type);
|
|
100
|
+
if (!listeners) {
|
|
101
|
+
listeners = /* @__PURE__ */ new Set();
|
|
102
|
+
map.set(type, listeners);
|
|
103
|
+
}
|
|
104
|
+
listeners.add(callback);
|
|
105
|
+
}
|
|
106
|
+
emit(map, type, data) {
|
|
107
|
+
const listeners = map.get(type);
|
|
108
|
+
if (!listeners) return;
|
|
109
|
+
for (const listener of listeners) listener(data);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
let activeBridge;
|
|
113
|
+
function getActiveBridge() {
|
|
114
|
+
return activeBridge;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Installs `window.__PREACT_DEVTOOLS__` for the current application window and
|
|
118
|
+
* wires it to Electrobun RPC.
|
|
119
|
+
*/
|
|
120
|
+
function installPreactDevtoolsBridge(options = {}) {
|
|
121
|
+
if (activeBridge) return activeBridge;
|
|
122
|
+
activeBridge = new BridgeController(options);
|
|
123
|
+
return activeBridge;
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export { composeAppWindowViewRPC, installPreactDevtoolsBridge };
|
package/dist/bun.d.mts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { a as DevtoolsTargetId, d as PreactDevtoolsDevtoolsWindowRPC, s as HostConnectionState, u as PreactDevtoolsAppWindowRPC } from "./index-ohWyACpe.mjs";
|
|
2
|
+
import { n as PreactDevtoolsLogger, t as ComposeRPCHandlers } from "./rpc-Ct-cJYzf.mjs";
|
|
3
|
+
import { BrowserView, BrowserWindow, ElectrobunRPCSchema } from "electrobun/bun";
|
|
4
|
+
import { Electroview } from "electrobun/view";
|
|
5
|
+
|
|
6
|
+
//#region src/bun/host.d.ts
|
|
7
|
+
/** The Bun-side RPC definition type returned by `BrowserView.defineRPC(...)`. */
|
|
8
|
+
type BrowserWindowRPCDefinition<Schema extends ElectrobunRPCSchema = ElectrobunRPCSchema> = ReturnType<typeof BrowserView.defineRPC<Schema>>;
|
|
9
|
+
/** The browser-side RPC definition type returned by `Electroview.defineRPC(...)`. */
|
|
10
|
+
type ViewRPCDefinition<Schema extends ElectrobunRPCSchema = ElectrobunRPCSchema> = ReturnType<typeof Electroview.defineRPC<Schema>>;
|
|
11
|
+
/** Consumer-owned Bun handlers that are merged with the reserved devtools message channel. */
|
|
12
|
+
type BunComposeRPCHandlers<Schema extends ElectrobunRPCSchema> = ComposeRPCHandlers<Schema, 'bun'>;
|
|
13
|
+
/** Arguments passed to `openWindow(...)` when the host needs a standalone devtools window. */
|
|
14
|
+
interface OpenPreactDevtoolsWindowArgs {
|
|
15
|
+
targetId: DevtoolsTargetId;
|
|
16
|
+
rpc: BrowserWindowRPCDefinition<PreactDevtoolsDevtoolsWindowRPC>;
|
|
17
|
+
composeRPC<TRPC extends PreactDevtoolsDevtoolsWindowRPC>(options: {
|
|
18
|
+
handlers?: BunComposeRPCHandlers<TRPC>;
|
|
19
|
+
}): BrowserWindowRPCDefinition<TRPC>;
|
|
20
|
+
}
|
|
21
|
+
/** Options for creating the Bun-side devtools host. */
|
|
22
|
+
interface CreatePreactDevtoolsHostOptions {
|
|
23
|
+
openWindow(args: OpenPreactDevtoolsWindowArgs): BrowserWindow | Promise<BrowserWindow>;
|
|
24
|
+
logger?: PreactDevtoolsLogger;
|
|
25
|
+
}
|
|
26
|
+
/** Associates one target id with an application BrowserWindow. */
|
|
27
|
+
interface RegisterAppWindowOptions {
|
|
28
|
+
targetId: DevtoolsTargetId;
|
|
29
|
+
window: BrowserWindow;
|
|
30
|
+
}
|
|
31
|
+
/** Bun-side controller that tracks targets, opens devtools windows, and routes protocol traffic. */
|
|
32
|
+
interface PreactDevtoolsHost {
|
|
33
|
+
createAppWindowRPC(options: {
|
|
34
|
+
targetId: DevtoolsTargetId;
|
|
35
|
+
}): BrowserWindowRPCDefinition<PreactDevtoolsAppWindowRPC>;
|
|
36
|
+
composeAppWindowRPC<TRPC extends PreactDevtoolsAppWindowRPC>(options: {
|
|
37
|
+
targetId: DevtoolsTargetId;
|
|
38
|
+
handlers?: BunComposeRPCHandlers<TRPC>;
|
|
39
|
+
}): BrowserWindowRPCDefinition<TRPC>;
|
|
40
|
+
registerAppWindow(options: RegisterAppWindowOptions): void;
|
|
41
|
+
open(targetId: DevtoolsTargetId): Promise<BrowserWindow>;
|
|
42
|
+
focus(targetId: DevtoolsTargetId): boolean;
|
|
43
|
+
close(targetId: DevtoolsTargetId): Promise<void>;
|
|
44
|
+
isOpen(targetId: DevtoolsTargetId): boolean;
|
|
45
|
+
getState(targetId: DevtoolsTargetId): HostConnectionState;
|
|
46
|
+
dispose(): void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Creates the Bun-side RPC definition for an application window that already has
|
|
50
|
+
* consumer-defined handlers.
|
|
51
|
+
*/
|
|
52
|
+
declare function composeAppWindowRPC<TRPC extends PreactDevtoolsAppWindowRPC>(host: PreactDevtoolsHost, options: {
|
|
53
|
+
targetId: DevtoolsTargetId;
|
|
54
|
+
handlers?: BunComposeRPCHandlers<TRPC>;
|
|
55
|
+
}): BrowserWindowRPCDefinition<TRPC>;
|
|
56
|
+
/**
|
|
57
|
+
* Creates the Bun-side RPC definition for a standalone devtools window that already has
|
|
58
|
+
* consumer-defined handlers.
|
|
59
|
+
*/
|
|
60
|
+
declare function composeDevtoolsWindowRPC<TRPC extends PreactDevtoolsDevtoolsWindowRPC>(host: PreactDevtoolsHost, options: {
|
|
61
|
+
targetId: DevtoolsTargetId;
|
|
62
|
+
handlers?: BunComposeRPCHandlers<TRPC>;
|
|
63
|
+
}): BrowserWindowRPCDefinition<TRPC>;
|
|
64
|
+
/**
|
|
65
|
+
* Creates the Bun-side host that pairs application windows with standalone
|
|
66
|
+
* Preact devtools windows.
|
|
67
|
+
*/
|
|
68
|
+
declare function createPreactDevtoolsHost(options: CreatePreactDevtoolsHostOptions): PreactDevtoolsHost;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { type BrowserWindowRPCDefinition, type BunComposeRPCHandlers, type CreatePreactDevtoolsHostOptions, type OpenPreactDevtoolsWindowArgs, type PreactDevtoolsHost, type RegisterAppWindowOptions, type ViewRPCDefinition, composeAppWindowRPC, composeDevtoolsWindowRPC, createPreactDevtoolsHost };
|
package/dist/bun.mjs
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { t as PREACT_DEVTOOLS_MESSAGE } from "./protocol-rAxFKNGR.mjs";
|
|
2
|
+
import { r as composeRPCHandlers, t as emitLog } from "./logger-Bpp6JxH4.mjs";
|
|
3
|
+
import { BrowserView } from "electrobun/bun";
|
|
4
|
+
//#region src/bun/host.ts
|
|
5
|
+
const hostInternals = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
const unsupportedDevtoolsCommands = new Set([
|
|
7
|
+
"inspect-host-node",
|
|
8
|
+
"load-host-selection",
|
|
9
|
+
"view-source"
|
|
10
|
+
]);
|
|
11
|
+
function getInternals(host) {
|
|
12
|
+
const internals = hostInternals.get(host);
|
|
13
|
+
if (!internals) throw new Error("Invalid Preact devtools host instance.");
|
|
14
|
+
return internals;
|
|
15
|
+
}
|
|
16
|
+
function sendReservedMessage(target, payload) {
|
|
17
|
+
const send = target?.webview?.rpc?.sendProxy?.[PREACT_DEVTOOLS_MESSAGE];
|
|
18
|
+
if (typeof send === "function") send(payload);
|
|
19
|
+
}
|
|
20
|
+
function connectSession(session) {
|
|
21
|
+
if (session.devtoolsWindow) sendReservedMessage(session.devtoolsWindow, {
|
|
22
|
+
kind: "connection-state",
|
|
23
|
+
connected: session.appReady && session.devtoolsReady,
|
|
24
|
+
targetId: session.targetId
|
|
25
|
+
});
|
|
26
|
+
if (session.appReady && session.devtoolsReady && session.appWindow) {
|
|
27
|
+
sendReservedMessage(session.appWindow, { kind: "session-connected" });
|
|
28
|
+
sendReservedMessage(session.appWindow, { kind: "request-refresh" });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function closeDevtoolsWindow(session) {
|
|
32
|
+
const window = session.devtoolsWindow;
|
|
33
|
+
session.devtoolsWindow = void 0;
|
|
34
|
+
session.devtoolsReady = false;
|
|
35
|
+
session.openingPromise = void 0;
|
|
36
|
+
if (window) try {
|
|
37
|
+
window.close();
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
40
|
+
function handleDevtoolsClosed(internals, session, reason) {
|
|
41
|
+
const hadDevtools = !!session.devtoolsWindow || session.devtoolsReady;
|
|
42
|
+
session.devtoolsWindow = void 0;
|
|
43
|
+
session.devtoolsReady = false;
|
|
44
|
+
session.openingPromise = void 0;
|
|
45
|
+
if (hadDevtools && session.appReady && session.appWindow) sendReservedMessage(session.appWindow, {
|
|
46
|
+
kind: "session-disconnected",
|
|
47
|
+
reason
|
|
48
|
+
});
|
|
49
|
+
if (!session.appWindow) internals.sessions.delete(session.targetId);
|
|
50
|
+
}
|
|
51
|
+
function handleAppWindowClosed(internals, session) {
|
|
52
|
+
session.closing = true;
|
|
53
|
+
session.appReady = false;
|
|
54
|
+
session.appWindow = void 0;
|
|
55
|
+
if (session.devtoolsWindow) closeDevtoolsWindow(session);
|
|
56
|
+
internals.sessions.delete(session.targetId);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Creates the Bun-side RPC definition for an application window that already has
|
|
60
|
+
* consumer-defined handlers.
|
|
61
|
+
*/
|
|
62
|
+
function composeAppWindowRPC(host, options) {
|
|
63
|
+
const internals = getInternals(host);
|
|
64
|
+
return BrowserView.defineRPC({ handlers: composeRPCHandlers(options.handlers, (message) => {
|
|
65
|
+
if (internals.disposed) {
|
|
66
|
+
emitLog(internals.logger, {
|
|
67
|
+
level: "warn",
|
|
68
|
+
code: "host-disposed-app-message",
|
|
69
|
+
message: "Dropped app-window message because the host has been disposed.",
|
|
70
|
+
targetId: options.targetId,
|
|
71
|
+
detail: message
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
internals.handleAppMessage(options.targetId, message);
|
|
76
|
+
}) });
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Creates the Bun-side RPC definition for a standalone devtools window that already has
|
|
80
|
+
* consumer-defined handlers.
|
|
81
|
+
*/
|
|
82
|
+
function composeDevtoolsWindowRPC(host, options) {
|
|
83
|
+
const internals = getInternals(host);
|
|
84
|
+
return BrowserView.defineRPC({ handlers: composeRPCHandlers(options.handlers, (message) => {
|
|
85
|
+
if (internals.disposed) {
|
|
86
|
+
emitLog(internals.logger, {
|
|
87
|
+
level: "warn",
|
|
88
|
+
code: "host-disposed-devtools-message",
|
|
89
|
+
message: "Dropped devtools-window message because the host has been disposed.",
|
|
90
|
+
targetId: options.targetId,
|
|
91
|
+
detail: message
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
internals.handleDevtoolsMessage(options.targetId, message);
|
|
96
|
+
}) });
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Creates the Bun-side host that pairs application windows with standalone
|
|
100
|
+
* Preact devtools windows.
|
|
101
|
+
*/
|
|
102
|
+
function createPreactDevtoolsHost(options) {
|
|
103
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
104
|
+
const ensureSession = (targetId) => {
|
|
105
|
+
let session = sessions.get(targetId);
|
|
106
|
+
if (!session) {
|
|
107
|
+
session = {
|
|
108
|
+
targetId,
|
|
109
|
+
appReady: false,
|
|
110
|
+
devtoolsReady: false,
|
|
111
|
+
closing: false
|
|
112
|
+
};
|
|
113
|
+
sessions.set(targetId, session);
|
|
114
|
+
}
|
|
115
|
+
return session;
|
|
116
|
+
};
|
|
117
|
+
const host = {
|
|
118
|
+
createAppWindowRPC({ targetId }) {
|
|
119
|
+
return composeAppWindowRPC(host, { targetId });
|
|
120
|
+
},
|
|
121
|
+
composeAppWindowRPC({ targetId, handlers }) {
|
|
122
|
+
return composeAppWindowRPC(host, {
|
|
123
|
+
targetId,
|
|
124
|
+
handlers
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
registerAppWindow({ targetId, window }) {
|
|
128
|
+
const internals = getInternals(host);
|
|
129
|
+
const session = internals.ensureSession(targetId);
|
|
130
|
+
if (session.appWindow && session.appWindow !== window) throw new Error(`Target "${targetId}" is already registered to a different app window.`);
|
|
131
|
+
if (session.appWindow === window) return;
|
|
132
|
+
session.appWindow = window;
|
|
133
|
+
session.closing = false;
|
|
134
|
+
window.on("close", () => {
|
|
135
|
+
const active = internals.sessions.get(targetId);
|
|
136
|
+
if (!active || active.appWindow !== window) return;
|
|
137
|
+
handleAppWindowClosed(internals, active);
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
async open(targetId) {
|
|
141
|
+
const internals = getInternals(host);
|
|
142
|
+
if (internals.disposed) throw new Error("Cannot open devtools after the host has been disposed.");
|
|
143
|
+
const session = internals.sessions.get(targetId);
|
|
144
|
+
if (!session?.appWindow) throw new Error(`Target "${targetId}" has not been registered.`);
|
|
145
|
+
if (session.devtoolsWindow) {
|
|
146
|
+
session.devtoolsWindow.show();
|
|
147
|
+
session.devtoolsWindow.focus();
|
|
148
|
+
return session.devtoolsWindow;
|
|
149
|
+
}
|
|
150
|
+
if (session.openingPromise) return session.openingPromise;
|
|
151
|
+
session.openingPromise = Promise.resolve(options.openWindow({
|
|
152
|
+
targetId,
|
|
153
|
+
rpc: composeDevtoolsWindowRPC(host, { targetId }),
|
|
154
|
+
composeRPC({ handlers }) {
|
|
155
|
+
return composeDevtoolsWindowRPC(host, {
|
|
156
|
+
targetId,
|
|
157
|
+
handlers
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
})).then((window) => {
|
|
161
|
+
session.devtoolsWindow = window;
|
|
162
|
+
session.devtoolsReady = false;
|
|
163
|
+
session.closing = false;
|
|
164
|
+
window.on("close", () => {
|
|
165
|
+
const active = internals.sessions.get(targetId);
|
|
166
|
+
if (!active || active.devtoolsWindow !== window) return;
|
|
167
|
+
handleDevtoolsClosed(internals, active, "devtools-closed");
|
|
168
|
+
});
|
|
169
|
+
return window;
|
|
170
|
+
}).finally(() => {
|
|
171
|
+
const active = internals.sessions.get(targetId);
|
|
172
|
+
if (active) active.openingPromise = void 0;
|
|
173
|
+
});
|
|
174
|
+
return session.openingPromise;
|
|
175
|
+
},
|
|
176
|
+
focus(targetId) {
|
|
177
|
+
const session = getInternals(host).sessions.get(targetId);
|
|
178
|
+
if (!session?.devtoolsWindow) return false;
|
|
179
|
+
session.devtoolsWindow.show();
|
|
180
|
+
session.devtoolsWindow.focus();
|
|
181
|
+
return true;
|
|
182
|
+
},
|
|
183
|
+
async close(targetId) {
|
|
184
|
+
const session = getInternals(host).sessions.get(targetId);
|
|
185
|
+
if (!session?.devtoolsWindow) return;
|
|
186
|
+
session.closing = true;
|
|
187
|
+
session.devtoolsWindow.close();
|
|
188
|
+
},
|
|
189
|
+
isOpen(targetId) {
|
|
190
|
+
return !!getInternals(host).sessions.get(targetId)?.devtoolsWindow;
|
|
191
|
+
},
|
|
192
|
+
getState(targetId) {
|
|
193
|
+
const session = getInternals(host).sessions.get(targetId);
|
|
194
|
+
if (!session?.appWindow) return "unregistered";
|
|
195
|
+
if (session.closing) return "closing";
|
|
196
|
+
if (session.devtoolsWindow && (!session.devtoolsReady || !session.appReady)) return "devtools-opening";
|
|
197
|
+
if (session.devtoolsWindow && session.devtoolsReady && session.appReady) return "devtools-connected";
|
|
198
|
+
if (session.appReady) return "app-connected";
|
|
199
|
+
return "registered";
|
|
200
|
+
},
|
|
201
|
+
dispose() {
|
|
202
|
+
const internals = getInternals(host);
|
|
203
|
+
if (internals.disposed) return;
|
|
204
|
+
internals.disposed = true;
|
|
205
|
+
for (const session of internals.sessions.values()) {
|
|
206
|
+
session.closing = true;
|
|
207
|
+
if (session.appReady && session.appWindow) sendReservedMessage(session.appWindow, {
|
|
208
|
+
kind: "session-disconnected",
|
|
209
|
+
reason: "host-disposed"
|
|
210
|
+
});
|
|
211
|
+
if (session.devtoolsWindow) closeDevtoolsWindow(session);
|
|
212
|
+
}
|
|
213
|
+
internals.sessions.clear();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
const internals = {
|
|
217
|
+
options,
|
|
218
|
+
logger: options.logger,
|
|
219
|
+
sessions,
|
|
220
|
+
disposed: false,
|
|
221
|
+
ensureSession,
|
|
222
|
+
handleAppMessage(targetId, message) {
|
|
223
|
+
const session = ensureSession(targetId);
|
|
224
|
+
switch (message.kind) {
|
|
225
|
+
case "bridge-ready":
|
|
226
|
+
session.appReady = true;
|
|
227
|
+
session.closing = false;
|
|
228
|
+
connectSession(session);
|
|
229
|
+
return;
|
|
230
|
+
case "bridge-disposed":
|
|
231
|
+
session.appReady = false;
|
|
232
|
+
if (session.devtoolsWindow) {
|
|
233
|
+
sendReservedMessage(session.devtoolsWindow, {
|
|
234
|
+
kind: "connection-state",
|
|
235
|
+
connected: false,
|
|
236
|
+
targetId
|
|
237
|
+
});
|
|
238
|
+
sendReservedMessage(session.devtoolsWindow, {
|
|
239
|
+
kind: "clear-store",
|
|
240
|
+
reason: "target-reset"
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return;
|
|
244
|
+
case "hook-event":
|
|
245
|
+
if (!session.devtoolsWindow || !session.devtoolsReady) return;
|
|
246
|
+
sendReservedMessage(session.devtoolsWindow, {
|
|
247
|
+
kind: "deliver-event",
|
|
248
|
+
type: message.type,
|
|
249
|
+
data: message.data
|
|
250
|
+
});
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
handleDevtoolsMessage(targetId, message) {
|
|
255
|
+
const session = ensureSession(targetId);
|
|
256
|
+
switch (message.kind) {
|
|
257
|
+
case "view-ready":
|
|
258
|
+
session.devtoolsReady = true;
|
|
259
|
+
connectSession(session);
|
|
260
|
+
return;
|
|
261
|
+
case "view-disposed":
|
|
262
|
+
handleDevtoolsClosed(internals, session, "devtools-closed");
|
|
263
|
+
return;
|
|
264
|
+
case "ui-command":
|
|
265
|
+
if (unsupportedDevtoolsCommands.has(message.type)) {
|
|
266
|
+
emitLog(internals.logger, {
|
|
267
|
+
level: "warn",
|
|
268
|
+
code: "unsupported-devtools-command",
|
|
269
|
+
message: `Dropped unsupported devtools command "${message.type}".`,
|
|
270
|
+
targetId,
|
|
271
|
+
detail: message
|
|
272
|
+
});
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (!session.appReady || !session.appWindow) {
|
|
276
|
+
emitLog(internals.logger, {
|
|
277
|
+
level: "warn",
|
|
278
|
+
code: "dropped-devtools-command",
|
|
279
|
+
message: "Dropped devtools command because the target bridge is not connected.",
|
|
280
|
+
targetId,
|
|
281
|
+
detail: message
|
|
282
|
+
});
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
sendReservedMessage(session.appWindow, {
|
|
286
|
+
kind: "devtools-command",
|
|
287
|
+
type: message.type,
|
|
288
|
+
data: message.data
|
|
289
|
+
});
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
hostInternals.set(host, internals);
|
|
295
|
+
return host;
|
|
296
|
+
}
|
|
297
|
+
//#endregion
|
|
298
|
+
export { composeAppWindowRPC, composeDevtoolsWindowRPC, createPreactDevtoolsHost };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { d as PreactDevtoolsDevtoolsWindowRPC } from "./index-ohWyACpe.mjs";
|
|
2
|
+
import { n as PreactDevtoolsLogger, t as ComposeRPCHandlers } from "./rpc-Ct-cJYzf.mjs";
|
|
3
|
+
import { ElectrobunRPCSchema, Electroview } from "electrobun/view";
|
|
4
|
+
|
|
5
|
+
//#region src/devtools-view/controller.d.ts
|
|
6
|
+
/** Options for mounting the standalone devtools UI inside a browser window. */
|
|
7
|
+
interface MountPreactDevtoolsWindowOptions {
|
|
8
|
+
container: HTMLElement;
|
|
9
|
+
electroview?: Electroview<any>;
|
|
10
|
+
logger?: PreactDevtoolsLogger;
|
|
11
|
+
}
|
|
12
|
+
/** Handle returned by `mountPreactDevtoolsWindow(...)`. */
|
|
13
|
+
interface MountedPreactDevtoolsWindow {
|
|
14
|
+
dispose(): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Mounts the vendored Preact devtools UI into the current browser window and
|
|
18
|
+
* connects it to the Bun host over Electrobun RPC.
|
|
19
|
+
*/
|
|
20
|
+
declare function mountPreactDevtoolsWindow(options: MountPreactDevtoolsWindowOptions): MountedPreactDevtoolsWindow;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/devtools-view/rpc.d.ts
|
|
23
|
+
/** The browser-side RPC definition type returned by `Electroview.defineRPC(...)`. */
|
|
24
|
+
type ViewRPCDefinition<Schema extends ElectrobunRPCSchema = ElectrobunRPCSchema> = ReturnType<typeof Electroview.defineRPC<Schema>>;
|
|
25
|
+
/** Consumer-owned webview handlers that are merged with the reserved devtools message channel. */
|
|
26
|
+
type ViewComposeRPCHandlers<Schema extends ElectrobunRPCSchema> = ComposeRPCHandlers<Schema, 'webview'>;
|
|
27
|
+
/**
|
|
28
|
+
* Creates the devtools-window `Electroview.defineRPC(...)` result for windows
|
|
29
|
+
* that already have their own webview RPC handlers.
|
|
30
|
+
*/
|
|
31
|
+
declare function composeDevtoolsWindowViewRPC<TRPC extends PreactDevtoolsDevtoolsWindowRPC>(options: {
|
|
32
|
+
handlers?: ViewComposeRPCHandlers<TRPC>;
|
|
33
|
+
maxRequestTime?: number;
|
|
34
|
+
}): ViewRPCDefinition<TRPC>;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { type MountPreactDevtoolsWindowOptions, type MountedPreactDevtoolsWindow, type ViewComposeRPCHandlers, type ViewRPCDefinition, composeDevtoolsWindowViewRPC, mountPreactDevtoolsWindow };
|