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 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 };