@devframes/hub 0.9.0-beta.9 → 0.9.1
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/dist/client/index.d.mts +150 -6
- package/dist/client/index.mjs +120 -18
- package/dist/constants-C2b2cACy.mjs +120 -0
- package/dist/constants-CmESZCBq.d.mts +105 -0
- package/dist/constants.d.mts +2 -33
- package/dist/constants.mjs +2 -52
- package/dist/{context-DzqMQ2mI.mjs → context-90I4t0S3.mjs} +66 -45
- package/dist/{context-D5TJIHwp.d.mts → context-BVkxwz5k.d.mts} +6 -6
- package/dist/{index-coDmWngQ.d.mts → index-CyEyZHB3.d.mts} +2 -2
- package/dist/index.d.mts +3 -3
- package/dist/node/index.d.mts +10 -2
- package/dist/node/index.mjs +80 -57
- package/dist/node/initiate.d.mts +15 -2
- package/dist/node/initiate.mjs +12 -4
- package/dist/{remote-url-KVXtKP47.mjs → remote-url-Bgc7gtsP.mjs} +48 -2
- package/dist/{settings-DfrRa9Wf.d.mts → settings-D7whfcx2.d.mts} +29 -5
- package/dist/types/index.d.mts +3 -3
- package/package.json +3 -3
package/dist/client/index.d.mts
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
|
-
import { E as DevframeMessagesClient, x as DevframeMessageEntryInput } from "../context-
|
|
2
|
-
import { N as NavTarget, P as RemoteConnectionInfo, S as DevframeDockUserEntry, _ as DevframeDockEntry, a as DevframeCommandEntry, b as DevframeDockEntryIcon, g as DevframeDockEntriesGrouped, k as DevframeViewIframe, n as DevframeClientCommand, p as ClientScriptEntry, s as DevframeCommandKeybinding, t as DevframeDocksUserSettings } from "../settings-
|
|
3
|
-
import "../index-
|
|
1
|
+
import { E as DevframeMessagesClient, x as DevframeMessageEntryInput } from "../context-BVkxwz5k.mjs";
|
|
2
|
+
import { N as NavTarget, P as RemoteConnectionInfo, S as DevframeDockUserEntry, _ as DevframeDockEntry, a as DevframeCommandEntry, b as DevframeDockEntryIcon, g as DevframeDockEntriesGrouped, k as DevframeViewIframe, n as DevframeClientCommand, p as ClientScriptEntry, s as DevframeCommandKeybinding, t as DevframeDocksUserSettings } from "../settings-D7whfcx2.mjs";
|
|
3
|
+
import "../index-CyEyZHB3.mjs";
|
|
4
4
|
import { DevframeClientRpcHost, DevframeConnection, DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientOptions, DevframeRpcContext, RpcClientEvents, RpcClientEvents as RpcClientEvents$1 } from "devframe/client";
|
|
5
5
|
import { EventEmitter } from "devframe/types";
|
|
6
6
|
import { SharedState } from "devframe/utils/shared-state";
|
|
7
7
|
import { WhenContext } from "devframe/utils/when";
|
|
8
8
|
export * from "devframe/client";
|
|
9
|
+
//#region src/client-modules.d.ts
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a dock client script's `importFrom` to the URL to import. URL
|
|
12
|
+
* specifiers pass through untouched; a **bare** specifier resolves through
|
|
13
|
+
* the explicit `resolveClientModule` callback (a viewer's own policy), then
|
|
14
|
+
* the host-advertised `template` (`ConnectionMeta.configs.dock.clientModuleResolution`,
|
|
15
|
+
* its `{specifier}` token replaced — e.g. Vite's `'/@id/{specifier}'`),
|
|
16
|
+
* resolved against `metaBaseUrl` so it targets the host's origin from any
|
|
17
|
+
* viewer. With neither, it stays unchanged — a page import map may still
|
|
18
|
+
* cover it.
|
|
19
|
+
*/
|
|
20
|
+
declare function resolveClientModuleSpecifier(specifier: string, options?: {
|
|
21
|
+
resolveClientModule?: (specifier: string) => string | undefined;
|
|
22
|
+
template?: string;
|
|
23
|
+
metaBaseUrl?: string;
|
|
24
|
+
}): string;
|
|
25
|
+
/**
|
|
26
|
+
* Diagnose a failed client-script import: name the likely cause instead of
|
|
27
|
+
* leaving only the browser's opaque `TypeError`. `importFrom` is the entry's
|
|
28
|
+
* declared specifier, `specifier` what was actually imported (after
|
|
29
|
+
* {@link resolveClientModuleSpecifier}).
|
|
30
|
+
*/
|
|
31
|
+
declare function clientScriptFailureHint(importFrom: string, specifier: string): string;
|
|
32
|
+
//#endregion
|
|
9
33
|
//#region src/client/renderers.d.ts
|
|
10
34
|
/**
|
|
11
35
|
* Options handed to a dock renderer when the client host mounts a dock entry.
|
|
@@ -134,9 +158,30 @@ interface DockPanelStorage {
|
|
|
134
158
|
top: number;
|
|
135
159
|
left: number;
|
|
136
160
|
position: 'left' | 'right' | 'bottom' | 'top';
|
|
137
|
-
open: boolean;
|
|
138
161
|
inactiveTimeout: number;
|
|
139
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Per-tab UI state of the dock panel, distinct from the browser-shared geometry
|
|
165
|
+
* in {@link DockPanelStorage}. A viewer persists this to `sessionStorage` so a
|
|
166
|
+
* reload (or the RPC auth handshake that follows one) restores the panel to
|
|
167
|
+
* exactly where the developer left it — which dock was open, and the
|
|
168
|
+
* address-bar route of that dock's iframe. Kept `sessionStorage`-scoped (not
|
|
169
|
+
* `localStorage`) because it is per-tab navigation state: two tabs open against
|
|
170
|
+
* the same dev server each keep their own selection rather than fighting over a
|
|
171
|
+
* shared one. Every field is serializable.
|
|
172
|
+
*/
|
|
173
|
+
interface DockSessionStorage {
|
|
174
|
+
/** Whether the dock panel is open. */
|
|
175
|
+
open: boolean;
|
|
176
|
+
/** The currently selected dock entry id, or `null` when nothing is open. */
|
|
177
|
+
selectedDockId: string | null;
|
|
178
|
+
/**
|
|
179
|
+
* The live address-bar URL of the selected iframe dock, restored as that
|
|
180
|
+
* iframe's boot `src` on the next load so the deep-linked route survives a
|
|
181
|
+
* reload. `null` when the selected dock has no iframe route to remember.
|
|
182
|
+
*/
|
|
183
|
+
selectedDockRoute: string | null;
|
|
184
|
+
}
|
|
140
185
|
type DockClientType = 'embedded' | 'standalone';
|
|
141
186
|
interface DocksContext extends DevframeRpcContext {
|
|
142
187
|
/**
|
|
@@ -196,12 +241,29 @@ interface WhenClauseContext {
|
|
|
196
241
|
type DevframeClientContext = DocksContext;
|
|
197
242
|
interface DocksPanelContext {
|
|
198
243
|
store: DockPanelStorage;
|
|
244
|
+
/**
|
|
245
|
+
* Per-tab session UI state — whether the panel is open, which dock is
|
|
246
|
+
* selected, and that dock's iframe route. Restored across reloads (and the
|
|
247
|
+
* auth handshake that follows one) by a viewer that persists it to
|
|
248
|
+
* `sessionStorage`.
|
|
249
|
+
*/
|
|
250
|
+
session: DockSessionStorage;
|
|
199
251
|
isDragging: boolean;
|
|
200
252
|
isResizing: boolean;
|
|
201
253
|
readonly isVertical: boolean;
|
|
254
|
+
/**
|
|
255
|
+
* Claim the persisted {@link DockSessionStorage.selectedDockRoute boot route} for a dock
|
|
256
|
+
* id, once. Returns the saved address-bar URL only for the dock that was
|
|
257
|
+
* selected before the last reload — and only on the first call for it — so a
|
|
258
|
+
* restored iframe boots deep-linked while a later switch to a different dock
|
|
259
|
+
* (whose live route isn't reflected in {@link DockSessionStorage.selectedDockRoute} yet)
|
|
260
|
+
* doesn't reuse the stale value. Returns `null` otherwise.
|
|
261
|
+
*/
|
|
262
|
+
consumeBootRoute?: (id: string) => string | null;
|
|
202
263
|
}
|
|
203
264
|
interface DocksEntriesContext {
|
|
204
265
|
selectedId: string | null;
|
|
266
|
+
selectedRoute: string | null;
|
|
205
267
|
readonly selected: DevframeDockEntry | null;
|
|
206
268
|
entries: DevframeDockEntry[];
|
|
207
269
|
entryToStateMap: Map<string, DockEntryState>;
|
|
@@ -346,6 +408,79 @@ declare function resolveDockUrl(url: string, connection: DevframeConnection): st
|
|
|
346
408
|
/** Resolve URL-backed dock icons while preserving Iconify names and data URLs. */
|
|
347
409
|
declare function resolveDockIcon(icon: DevframeDockEntryIcon, connection: DevframeConnection): DevframeDockEntryIcon;
|
|
348
410
|
//#endregion
|
|
411
|
+
//#region src/client/frame-location.d.ts
|
|
412
|
+
/**
|
|
413
|
+
* Live location tracking for an iframe dock.
|
|
414
|
+
*
|
|
415
|
+
* An iframe dock's address bar — and the route persisted from it into
|
|
416
|
+
* `DockSessionStorage.selectedDockRoute` — has to follow wherever the embedded
|
|
417
|
+
* app actually goes. The `load` event only covers whole-document navigations, so
|
|
418
|
+
* an SPA router moving between routes with `history.pushState()` leaves both
|
|
419
|
+
* showing the URL the frame booted with.
|
|
420
|
+
*
|
|
421
|
+
* {@link watchFrameLocation} closes that gap for a same-origin frame, reporting
|
|
422
|
+
* `location.href` on every navigation it can observe:
|
|
423
|
+
*
|
|
424
|
+
* - `popstate` / `hashchange` — back/forward and hash routing;
|
|
425
|
+
* - `history.pushState`/`replaceState`, wrapped in place (and restored on
|
|
426
|
+
* dispose) because those two fire no event of their own;
|
|
427
|
+
* - the [Navigation API](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API)'s
|
|
428
|
+
* `currententrychange` where it exists, which reports post-commit and so also
|
|
429
|
+
* catches what a wrapper structurally cannot — a router holding a reference to
|
|
430
|
+
* `pushState` captured before this watch attached;
|
|
431
|
+
* - `load`, which also re-subscribes: a document navigation swaps the frame's
|
|
432
|
+
* `history`/`navigation` objects, so the previous subscription dies with them.
|
|
433
|
+
*
|
|
434
|
+
* These sources overlap deliberately rather than being chosen between, since a
|
|
435
|
+
* report is deduped against the last href — the cost of hearing the same
|
|
436
|
+
* navigation twice is nothing, and the cost of missing one is a stale route.
|
|
437
|
+
*
|
|
438
|
+
* A cross-origin frame reports nothing — its location is unreadable by design,
|
|
439
|
+
* so the caller keeps the last URL it knew.
|
|
440
|
+
*/
|
|
441
|
+
/** The minimal `history` surface the watcher wraps. Injectable for tests. */
|
|
442
|
+
interface FrameLocationHistory {
|
|
443
|
+
pushState: (...args: any[]) => void;
|
|
444
|
+
replaceState: (...args: any[]) => void;
|
|
445
|
+
}
|
|
446
|
+
/** The minimal frame-window surface the watcher reads and subscribes to. */
|
|
447
|
+
interface FrameLocationWindow {
|
|
448
|
+
readonly location: {
|
|
449
|
+
readonly href: string;
|
|
450
|
+
};
|
|
451
|
+
readonly history?: FrameLocationHistory;
|
|
452
|
+
/** Present only where the Navigation API ships. */
|
|
453
|
+
readonly navigation?: {
|
|
454
|
+
addEventListener: (type: 'currententrychange', listener: () => void) => void;
|
|
455
|
+
removeEventListener: (type: 'currententrychange', listener: () => void) => void;
|
|
456
|
+
};
|
|
457
|
+
addEventListener: (type: 'popstate' | 'hashchange', listener: () => void) => void;
|
|
458
|
+
removeEventListener: (type: 'popstate' | 'hashchange', listener: () => void) => void;
|
|
459
|
+
}
|
|
460
|
+
/** The minimal iframe surface the watcher needs. Injectable for tests. */
|
|
461
|
+
interface FrameLocationTarget {
|
|
462
|
+
readonly contentWindow: FrameLocationWindow | null;
|
|
463
|
+
addEventListener: (type: 'load', listener: () => void) => void;
|
|
464
|
+
removeEventListener: (type: 'load', listener: () => void) => void;
|
|
465
|
+
}
|
|
466
|
+
interface WatchFrameLocationOptions {
|
|
467
|
+
/** The live iframe whose location is tracked. */
|
|
468
|
+
iframe: FrameLocationTarget;
|
|
469
|
+
/** Called with the frame's `location.href` on each observed change. */
|
|
470
|
+
onChange: (href: string) => void;
|
|
471
|
+
/**
|
|
472
|
+
* The href the caller already shows, so an initial report is only made when
|
|
473
|
+
* the frame is somewhere else (a frame re-attached mid-session, say).
|
|
474
|
+
*/
|
|
475
|
+
initial?: string;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Track a frame's location until the returned disposer is called. Disposing
|
|
479
|
+
* detaches every listener and restores any wrapped `history` method, leaving the
|
|
480
|
+
* embedded page as it was found.
|
|
481
|
+
*/
|
|
482
|
+
declare function watchFrameLocation(options: WatchFrameLocationOptions): () => void;
|
|
483
|
+
//#endregion
|
|
349
484
|
//#region src/client/frame-nav.d.ts
|
|
350
485
|
/**
|
|
351
486
|
* Shared-iframe soft navigation — the viewer-side half of a host↔iframe
|
|
@@ -367,7 +502,7 @@ declare function resolveDockIcon(icon: DevframeDockEntryIcon, connection: Devfra
|
|
|
367
502
|
* decoupled — the embedded app takes no hub/RPC dependency, only the shim.
|
|
368
503
|
*/
|
|
369
504
|
/** `postMessage` channel tag shared by both halves of the protocol. */
|
|
370
|
-
declare const FRAME_NAV_CHANNEL
|
|
505
|
+
declare const FRAME_NAV_CHANNEL: string;
|
|
371
506
|
/** Protocol version. */
|
|
372
507
|
declare const FRAME_NAV_VERSION = 1;
|
|
373
508
|
/** Envelope every frame-nav message carries. */
|
|
@@ -488,6 +623,15 @@ interface DevframeClientHostOptions {
|
|
|
488
623
|
* `custom-render`, and iframe `clientScript`). Default `true`.
|
|
489
624
|
*/
|
|
490
625
|
loadClientScripts?: boolean;
|
|
626
|
+
/**
|
|
627
|
+
* Resolve a **bare-specifier** client script (`importFrom` naming an npm
|
|
628
|
+
* module) to a URL this page can import — a viewer's own policy, winning
|
|
629
|
+
* over the host-advertised `ConnectionMeta.configs.dock.clientModuleResolution`
|
|
630
|
+
* template (return `undefined` to fall through to it). URL specifiers never
|
|
631
|
+
* reach this hook. E.g. on a Vite-served page:
|
|
632
|
+
* `resolveClientModule: s => `/@id/${s}``.
|
|
633
|
+
*/
|
|
634
|
+
resolveClientModule?: (specifier: string) => string | undefined;
|
|
491
635
|
/**
|
|
492
636
|
* Dock renderers to register at boot, keyed by dock `type`. The host
|
|
493
637
|
* application injects the ones it wants (e.g.
|
|
@@ -587,4 +731,4 @@ declare function parseRemoteConnection(input?: string): RemoteConnectionInfo | n
|
|
|
587
731
|
*/
|
|
588
732
|
declare function connectRemoteDevframe(options?: ConnectRemoteDevframeOptions): Promise<DevframeRpcClient>;
|
|
589
733
|
//#endregion
|
|
590
|
-
export { CLIENT_CONTEXT_KEY, CommandsContext, ConnectRemoteDevframeOptions, CreateDockRenderersContextOptions, DevframeClientContext, DevframeClientHost, DevframeClientHostOptions, type DevframeClientRpcHost, DockClientScriptContext, DockClientType, DockEntryState, DockEntryStateEvents, DockPanelStorage, DockRegistration, DockRenderer, DockRendererInstance, DockRendererManifest, DockRendererMountOptions, DockRendererMountResult, DockRenderersContext, DocksConnectionContext, DocksContext, DocksEntriesContext, DocksPanelContext, FRAME_NAV_CHANNEL, FRAME_NAV_VERSION, FrameNavClient, FrameNavClientOptions, FrameNavEnvelope, FrameNavFrameMessage, FrameNavHostMessage, FrameNavHostPayload, FrameNavListenTarget, FrameTab, MessagesClientOptions, type RpcClientEvents, WhenClauseContext, attachFrameNavClient, buildRemoteDevframeUrl, connectRemoteDevframe, createDevframeClientHost, createDockRenderersContext, createMessagesClient, getDevframeClientContext, parseRemoteConnection, resolveDockIcon, resolveDockUrl, setDevframeClientContext, stripRemoteConnectionFromUrl };
|
|
734
|
+
export { CLIENT_CONTEXT_KEY, CommandsContext, ConnectRemoteDevframeOptions, CreateDockRenderersContextOptions, DevframeClientContext, DevframeClientHost, DevframeClientHostOptions, type DevframeClientRpcHost, DockClientScriptContext, DockClientType, DockEntryState, DockEntryStateEvents, DockPanelStorage, DockRegistration, DockRenderer, DockRendererInstance, DockRendererManifest, DockRendererMountOptions, DockRendererMountResult, DockRenderersContext, DockSessionStorage, DocksConnectionContext, DocksContext, DocksEntriesContext, DocksPanelContext, FRAME_NAV_CHANNEL, FRAME_NAV_VERSION, FrameLocationHistory, FrameLocationTarget, FrameLocationWindow, FrameNavClient, FrameNavClientOptions, FrameNavEnvelope, FrameNavFrameMessage, FrameNavHostMessage, FrameNavHostPayload, FrameNavListenTarget, FrameTab, MessagesClientOptions, type RpcClientEvents, WatchFrameLocationOptions, WhenClauseContext, attachFrameNavClient, buildRemoteDevframeUrl, clientScriptFailureHint, connectRemoteDevframe, createDevframeClientHost, createDockRenderersContext, createMessagesClient, getDevframeClientContext, parseRemoteConnection, resolveClientModuleSpecifier, resolveDockIcon, resolveDockUrl, setDevframeClientContext, stripRemoteConnectionFromUrl, watchFrameLocation };
|
package/dist/client/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as stripRemoteConnectionFromUrl, t as buildRemoteConnectionUrl } from "../remote-url-
|
|
1
|
+
import { i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, t as DEFAULT_CATEGORIES_ORDER } from "../constants-C2b2cACy.mjs";
|
|
2
|
+
import { a as resolveClientModuleSpecifier, n as stripRemoteConnectionFromUrl, r as clientScriptFailureHint, t as buildRemoteConnectionUrl } from "../remote-url-Bgc7gtsP.mjs";
|
|
3
3
|
import { REMOTE_CONNECTION_KEY } from "devframe/constants";
|
|
4
4
|
import { connectDevframe, getDevframeRpcClient, resolveWsUrl } from "devframe/client";
|
|
5
5
|
import { createEventEmitter } from "devframe/utils/events";
|
|
@@ -69,6 +69,88 @@ function resolveDockIcon(icon, connection) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
//#endregion
|
|
72
|
+
//#region src/client/frame-location.ts
|
|
73
|
+
/**
|
|
74
|
+
* Track a frame's location until the returned disposer is called. Disposing
|
|
75
|
+
* detaches every listener and restores any wrapped `history` method, leaving the
|
|
76
|
+
* embedded page as it was found.
|
|
77
|
+
*/
|
|
78
|
+
function watchFrameLocation(options) {
|
|
79
|
+
const { iframe, onChange } = options;
|
|
80
|
+
let lastReported = options.initial;
|
|
81
|
+
let detach;
|
|
82
|
+
let disposed = false;
|
|
83
|
+
/**
|
|
84
|
+
* The frame's window, or `null` when its location can't be read. Touching
|
|
85
|
+
* `location.href` is the cross-origin probe — it throws for a foreign
|
|
86
|
+
* document, where there is nothing to observe.
|
|
87
|
+
*/
|
|
88
|
+
function readableWindow() {
|
|
89
|
+
try {
|
|
90
|
+
const win = iframe.contentWindow;
|
|
91
|
+
return win && typeof win.location.href === "string" ? win : null;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function report() {
|
|
97
|
+
const href = readableWindow()?.location.href;
|
|
98
|
+
if (!href || href === "about:blank" || href === lastReported) return;
|
|
99
|
+
lastReported = href;
|
|
100
|
+
onChange(href);
|
|
101
|
+
}
|
|
102
|
+
function subscribe() {
|
|
103
|
+
const win = readableWindow();
|
|
104
|
+
if (!win) return;
|
|
105
|
+
const listeners = [];
|
|
106
|
+
win.addEventListener("popstate", report);
|
|
107
|
+
win.addEventListener("hashchange", report);
|
|
108
|
+
listeners.push(() => {
|
|
109
|
+
win.removeEventListener("popstate", report);
|
|
110
|
+
win.removeEventListener("hashchange", report);
|
|
111
|
+
});
|
|
112
|
+
const { navigation, history } = win;
|
|
113
|
+
if (navigation) {
|
|
114
|
+
navigation.addEventListener("currententrychange", report);
|
|
115
|
+
listeners.push(() => navigation.removeEventListener("currententrychange", report));
|
|
116
|
+
}
|
|
117
|
+
if (history) {
|
|
118
|
+
const originals = {
|
|
119
|
+
pushState: history.pushState,
|
|
120
|
+
replaceState: history.replaceState
|
|
121
|
+
};
|
|
122
|
+
for (const method of ["pushState", "replaceState"]) history[method] = function(...args) {
|
|
123
|
+
originals[method].apply(this, args);
|
|
124
|
+
report();
|
|
125
|
+
};
|
|
126
|
+
listeners.push(() => {
|
|
127
|
+
history.pushState = originals.pushState;
|
|
128
|
+
history.replaceState = originals.replaceState;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
detach = () => {
|
|
132
|
+
for (const off of listeners) off();
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function onLoad() {
|
|
136
|
+
if (disposed) return;
|
|
137
|
+
detach?.();
|
|
138
|
+
detach = void 0;
|
|
139
|
+
subscribe();
|
|
140
|
+
report();
|
|
141
|
+
}
|
|
142
|
+
iframe.addEventListener("load", onLoad);
|
|
143
|
+
subscribe();
|
|
144
|
+
report();
|
|
145
|
+
return () => {
|
|
146
|
+
if (disposed) return;
|
|
147
|
+
disposed = true;
|
|
148
|
+
iframe.removeEventListener("load", onLoad);
|
|
149
|
+
detach?.();
|
|
150
|
+
detach = void 0;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
72
154
|
//#region src/client/frame-nav.ts
|
|
73
155
|
/**
|
|
74
156
|
* Shared-iframe soft navigation — the viewer-side half of a host↔iframe
|
|
@@ -90,7 +172,7 @@ function resolveDockIcon(icon, connection) {
|
|
|
90
172
|
* decoupled — the embedded app takes no hub/RPC dependency, only the shim.
|
|
91
173
|
*/
|
|
92
174
|
/** `postMessage` channel tag shared by both halves of the protocol. */
|
|
93
|
-
const FRAME_NAV_CHANNEL =
|
|
175
|
+
const FRAME_NAV_CHANNEL = HUB_EVENTS.postMessage.frameNav;
|
|
94
176
|
/** Protocol version. */
|
|
95
177
|
const FRAME_NAV_VERSION = 1;
|
|
96
178
|
/**
|
|
@@ -121,16 +203,14 @@ function attachFrameNavClient(options) {
|
|
|
121
203
|
if (disposed) return;
|
|
122
204
|
if (expectedOrigin !== "*" && ev.origin !== expectedOrigin) return;
|
|
123
205
|
const data = ev.data;
|
|
124
|
-
if (!data || data.channel !==
|
|
206
|
+
if (!data || data.channel !== FRAME_NAV_CHANNEL || data.v !== 1 || data.frameId !== frameId || data.from !== "frame") return;
|
|
125
207
|
switch (data.type) {
|
|
126
208
|
case "ready":
|
|
127
209
|
case "manifest":
|
|
128
210
|
markReady();
|
|
129
211
|
reconcileManifest(data.tabs ?? [], data.current);
|
|
130
212
|
break;
|
|
131
|
-
case "navigated":
|
|
132
|
-
onNavigated(data.tabId);
|
|
133
|
-
break;
|
|
213
|
+
case "navigated": onNavigated(data.tabId);
|
|
134
214
|
}
|
|
135
215
|
}
|
|
136
216
|
function markReady() {
|
|
@@ -416,10 +496,10 @@ function createDockRenderersContext(options) {
|
|
|
416
496
|
}
|
|
417
497
|
//#endregion
|
|
418
498
|
//#region src/client/host.ts
|
|
419
|
-
const DOCKS_STATE_KEY =
|
|
420
|
-
const COMMANDS_STATE_KEY =
|
|
421
|
-
const USER_SETTINGS_STATE_KEY =
|
|
422
|
-
const DOCKS_ACTIVATE_EVENT =
|
|
499
|
+
const DOCKS_STATE_KEY = HUB_EVENTS.sharedState.docks;
|
|
500
|
+
const COMMANDS_STATE_KEY = HUB_EVENTS.sharedState.commands;
|
|
501
|
+
const USER_SETTINGS_STATE_KEY = HUB_EVENTS.sharedState.userSettings;
|
|
502
|
+
const DOCKS_ACTIVATE_EVENT = HUB_EVENTS.broadcast.docksActivate;
|
|
423
503
|
/**
|
|
424
504
|
* Boot the framework-level client host: connect RPC, assemble the full
|
|
425
505
|
* {@link DevframeClientContext} (panel, docks, commands, when) from the hub's
|
|
@@ -462,7 +542,7 @@ async function createDevframeClientHost(options = {}) {
|
|
|
462
542
|
when: { get context() {
|
|
463
543
|
return {
|
|
464
544
|
clientType,
|
|
465
|
-
dockOpen: panel.
|
|
545
|
+
dockOpen: panel.session.open,
|
|
466
546
|
paletteOpen: commands.paletteOpen,
|
|
467
547
|
dockSelectedId: selectedId ?? ""
|
|
468
548
|
};
|
|
@@ -584,7 +664,11 @@ async function createDevframeClientHost(options = {}) {
|
|
|
584
664
|
}
|
|
585
665
|
docks.entries = entries;
|
|
586
666
|
docks.groupedEntries = groupByCategory(entries, categoryOrder);
|
|
587
|
-
if (selectedId && !entryToStateMap.has(selectedId))
|
|
667
|
+
if (selectedId && !entryToStateMap.has(selectedId)) {
|
|
668
|
+
selectedId = null;
|
|
669
|
+
panel.session.selectedDockId = null;
|
|
670
|
+
panel.session.selectedDockRoute = null;
|
|
671
|
+
}
|
|
588
672
|
}
|
|
589
673
|
function createDocksContext() {
|
|
590
674
|
return {
|
|
@@ -594,6 +678,12 @@ async function createDevframeClientHost(options = {}) {
|
|
|
594
678
|
set selectedId(id) {
|
|
595
679
|
switchEntry(id);
|
|
596
680
|
},
|
|
681
|
+
get selectedRoute() {
|
|
682
|
+
return panel.session.selectedDockRoute;
|
|
683
|
+
},
|
|
684
|
+
set selectedRoute(route) {
|
|
685
|
+
panel.session.selectedDockRoute = route;
|
|
686
|
+
},
|
|
597
687
|
get selected() {
|
|
598
688
|
return selectedId && entryToStateMap.get(selectedId)?.entryMeta || null;
|
|
599
689
|
},
|
|
@@ -638,6 +728,8 @@ async function createDevframeClientHost(options = {}) {
|
|
|
638
728
|
if (next !== null && !entryToStateMap.has(next)) return false;
|
|
639
729
|
const previous = selectedId;
|
|
640
730
|
selectedId = next;
|
|
731
|
+
panel.session.selectedDockId = next;
|
|
732
|
+
if (next === null || entryToStateMap.get(next)?.entryMeta.type !== "iframe") panel.session.selectedDockRoute = null;
|
|
641
733
|
if (previous) entryToStateMap.get(previous)?.events.emit("entry:deactivated");
|
|
642
734
|
if (next) entryToStateMap.get(next)?.events.emit("entry:activated");
|
|
643
735
|
return true;
|
|
@@ -704,12 +796,17 @@ async function createDevframeClientHost(options = {}) {
|
|
|
704
796
|
}
|
|
705
797
|
}
|
|
706
798
|
async function runClientScript(entryId, script) {
|
|
799
|
+
const specifier = resolveClientModuleSpecifier(script.importFrom, {
|
|
800
|
+
resolveClientModule: options.resolveClientModule,
|
|
801
|
+
template: rpc.connectionMeta?.configs?.dock?.clientModuleResolution,
|
|
802
|
+
metaBaseUrl: rpc.connection?.metaBaseUrl
|
|
803
|
+
});
|
|
707
804
|
try {
|
|
708
805
|
const fn = (await import(
|
|
709
806
|
/* @vite-ignore */
|
|
710
807
|
/* webpackIgnore: true */
|
|
711
808
|
/* turbopackIgnore: true */
|
|
712
|
-
|
|
809
|
+
specifier
|
|
713
810
|
))[script.importName ?? "default"];
|
|
714
811
|
if (typeof fn !== "function") return;
|
|
715
812
|
const current = entryToStateMap.get(entryId);
|
|
@@ -722,7 +819,7 @@ async function createDevframeClientHost(options = {}) {
|
|
|
722
819
|
});
|
|
723
820
|
} catch (error) {
|
|
724
821
|
loadedScripts.delete(entryId);
|
|
725
|
-
console.error(`[@devframes/hub] failed to load client script for "${entryId}" from ${script.importFrom}`, error);
|
|
822
|
+
console.error(`[@devframes/hub] failed to load client script for "${entryId}" from ${specifier}${clientScriptFailureHint(script.importFrom, specifier)}`, error);
|
|
726
823
|
}
|
|
727
824
|
}
|
|
728
825
|
}
|
|
@@ -734,11 +831,15 @@ function createPanelContext(clientType) {
|
|
|
734
831
|
top: 0,
|
|
735
832
|
left: 0,
|
|
736
833
|
position: "right",
|
|
737
|
-
open: clientType === "standalone",
|
|
738
834
|
inactiveTimeout: 0
|
|
739
835
|
};
|
|
740
836
|
return {
|
|
741
837
|
store,
|
|
838
|
+
session: {
|
|
839
|
+
open: clientType === "standalone",
|
|
840
|
+
selectedDockId: null,
|
|
841
|
+
selectedDockRoute: null
|
|
842
|
+
},
|
|
742
843
|
isDragging: false,
|
|
743
844
|
isResizing: false,
|
|
744
845
|
get isVertical() {
|
|
@@ -785,10 +886,11 @@ function buildRemoteDevframeUrl(url, connection) {
|
|
|
785
886
|
} catch {
|
|
786
887
|
return url;
|
|
787
888
|
}
|
|
889
|
+
const websocket = resolveWsUrl(connection.connectionMeta.websocket, connection.metaBaseUrl, base);
|
|
788
890
|
return buildRemoteConnectionUrl(url, {
|
|
789
891
|
v: 1,
|
|
790
892
|
backend: "websocket",
|
|
791
|
-
websocket
|
|
893
|
+
websocket,
|
|
792
894
|
authToken: connection.authToken,
|
|
793
895
|
origin: base.origin
|
|
794
896
|
});
|
|
@@ -869,4 +971,4 @@ async function connectRemoteDevframe(options = {}) {
|
|
|
869
971
|
});
|
|
870
972
|
}
|
|
871
973
|
//#endregion
|
|
872
|
-
export { CLIENT_CONTEXT_KEY, FRAME_NAV_CHANNEL, FRAME_NAV_VERSION, attachFrameNavClient, buildRemoteDevframeUrl, connectRemoteDevframe, createDevframeClientHost, createDockRenderersContext, createMessagesClient, getDevframeClientContext, parseRemoteConnection, resolveDockIcon, resolveDockUrl, setDevframeClientContext, stripRemoteConnectionFromUrl };
|
|
974
|
+
export { CLIENT_CONTEXT_KEY, FRAME_NAV_CHANNEL, FRAME_NAV_VERSION, attachFrameNavClient, buildRemoteDevframeUrl, clientScriptFailureHint, connectRemoteDevframe, createDevframeClientHost, createDockRenderersContext, createMessagesClient, getDevframeClientContext, parseRemoteConnection, resolveClientModuleSpecifier, resolveDockIcon, resolveDockUrl, setDevframeClientContext, stripRemoteConnectionFromUrl, watchFrameLocation };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { cleanDoubleSlashes, withLeadingSlash, withTrailingSlash } from "ufo";
|
|
2
|
+
//#region src/events.ts
|
|
3
|
+
/**
|
|
4
|
+
* Centralized registry of every event, broadcast, RPC method, shared-state
|
|
5
|
+
* key, and channel name the hub uses — the single source of truth that keeps
|
|
6
|
+
* these names out of scattered string literals.
|
|
7
|
+
*
|
|
8
|
+
* **Keep this in sync with [`docs/guide/events.md`](../../../docs/guide/events.md)**
|
|
9
|
+
* (the Hub Events Reference): every name here appears in that page's tables, and
|
|
10
|
+
* every name there resolves to an entry here. Add, rename, or remove a name in
|
|
11
|
+
* both places in the same change, and reference `HUB_EVENTS.*` from call sites
|
|
12
|
+
* instead of re-typing a literal.
|
|
13
|
+
*
|
|
14
|
+
* The `.events` EventEmitter maps in `types/{docks,terminals,messages,commands}.ts`
|
|
15
|
+
* and the RPC augmentation interfaces in `node/context.ts` declare these same
|
|
16
|
+
* names as type-level keys (a literal is unavoidable in a type position); those
|
|
17
|
+
* declarations mirror this map and move with it.
|
|
18
|
+
*/
|
|
19
|
+
const HUB_EVENTS = {
|
|
20
|
+
/**
|
|
21
|
+
* Internal node `EventEmitter` events on `ctx.<subsystem>.events`. Emitted
|
|
22
|
+
* and consumed inside the node process (chiefly by `createHubContext`, which
|
|
23
|
+
* fans them out onto the wire); they never cross to the browser.
|
|
24
|
+
*/
|
|
25
|
+
bus: {
|
|
26
|
+
docksEntryUpdated: "docks:entry:updated",
|
|
27
|
+
docksActivate: "docks:activate",
|
|
28
|
+
terminalsSessionUpdated: "terminals:session:updated",
|
|
29
|
+
messagesAdded: "messages:added",
|
|
30
|
+
messagesUpdated: "messages:updated",
|
|
31
|
+
messagesRemoved: "messages:removed",
|
|
32
|
+
messagesCleared: "messages:cleared",
|
|
33
|
+
commandsRegistered: "commands:registered",
|
|
34
|
+
commandsUnregistered: "commands:unregistered"
|
|
35
|
+
},
|
|
36
|
+
/** Server RPC methods a connected client calls (client → server), `hub:` prefix. */
|
|
37
|
+
rpc: {
|
|
38
|
+
docksActivate: "hub:docks:activate",
|
|
39
|
+
commandsExecute: "hub:commands:execute",
|
|
40
|
+
messagesAdd: "hub:messages:add",
|
|
41
|
+
messagesUpdate: "hub:messages:update",
|
|
42
|
+
messagesRemove: "hub:messages:remove",
|
|
43
|
+
messagesClear: "hub:messages:clear",
|
|
44
|
+
terminalsWrite: "hub:terminals:write",
|
|
45
|
+
terminalsResize: "hub:terminals:resize",
|
|
46
|
+
terminalsTerminate: "hub:terminals:terminate",
|
|
47
|
+
terminalsRestart: "hub:terminals:restart",
|
|
48
|
+
terminalsRemove: "hub:terminals:remove"
|
|
49
|
+
},
|
|
50
|
+
/** Broadcast notifications the server pushes to clients (server → client), `devframe:` prefix. */
|
|
51
|
+
broadcast: {
|
|
52
|
+
docksActivate: "devframe:docks:activate",
|
|
53
|
+
terminalsUpdated: "devframe:terminals:updated",
|
|
54
|
+
messagesUpdated: "devframe:messages:updated"
|
|
55
|
+
},
|
|
56
|
+
/** Shared-state slot keys a hub-aware client reads (server → client), `devframe:` prefix. */
|
|
57
|
+
sharedState: {
|
|
58
|
+
docks: "devframe:docks",
|
|
59
|
+
docksActive: "devframe:docks:active",
|
|
60
|
+
commands: "devframe:commands",
|
|
61
|
+
userSettings: "devframe:user-settings",
|
|
62
|
+
dockRenderers: "devframe:dock-renderers"
|
|
63
|
+
},
|
|
64
|
+
/** Streaming channel ids (server → client), `devframe:` prefix. */
|
|
65
|
+
stream: { terminals: "devframe:terminals" },
|
|
66
|
+
/** `postMessage` channels for host ↔ iframe protocols, `devframe:` prefix. */
|
|
67
|
+
postMessage: { frameNav: "devframe:frame-nav" }
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/constants.ts
|
|
71
|
+
/** Default mount base for a hub instance — one namespace, one catch-all. */
|
|
72
|
+
const DEVFRAMES_HUB_BASE = "/__devframes/";
|
|
73
|
+
/**
|
|
74
|
+
* Normalize a hub mount base to an absolute path with leading and trailing
|
|
75
|
+
* slashes (e.g. `devframes` → `/devframes/`), collapsing any doubled
|
|
76
|
+
* slashes the input introduced. The one implementation every hub-aware
|
|
77
|
+
* host (`@devframes/hub` itself, and the Vite/Nuxt/Next adapters) resolves
|
|
78
|
+
* `options.base` through.
|
|
79
|
+
*/
|
|
80
|
+
function normalizeHubBase(base) {
|
|
81
|
+
return cleanDoubleSlashes(withTrailingSlash(withLeadingSlash(base)));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The default ordering weight for each known dock category — lower sorts
|
|
85
|
+
* earlier. Downstream viewers (e.g. `@vitejs/devtools-kit`) import this as the
|
|
86
|
+
* single source of truth so the hub and its viewers agree on category order.
|
|
87
|
+
* `framework` sorts first; `~builtin` (the viewer's own built-in views) last.
|
|
88
|
+
*
|
|
89
|
+
* The buckets read from "closest to your app" → "platform / analysis" →
|
|
90
|
+
* "peripheral". Gaps between the weights are intentional: a kit can interleave
|
|
91
|
+
* its own categories (or override these) without editing this table.
|
|
92
|
+
*/
|
|
93
|
+
const DEFAULT_CATEGORIES_ORDER = {
|
|
94
|
+
"framework": -100,
|
|
95
|
+
"default": 0,
|
|
96
|
+
"app": 100,
|
|
97
|
+
"ui": 150,
|
|
98
|
+
"data": 250,
|
|
99
|
+
"web": 300,
|
|
100
|
+
"performance": 350,
|
|
101
|
+
"advanced": 400,
|
|
102
|
+
"docs": 500,
|
|
103
|
+
"~builtin": 1e3
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Shared-state slot carrying the hub's renderer manifest — one
|
|
107
|
+
* {@link import('./client/renderers').DockRendererManifest} entry per dock
|
|
108
|
+
* `type`, published by `initHub({ renderers })` and consumed by every
|
|
109
|
+
* hub-aware client (the headless client host and viewers alike).
|
|
110
|
+
*/
|
|
111
|
+
const DOCK_RENDERERS_STATE_KEY = HUB_EVENTS.sharedState.dockRenderers;
|
|
112
|
+
const DEFAULT_STATE_USER_SETTINGS = () => ({
|
|
113
|
+
docksHidden: [],
|
|
114
|
+
docksCategoriesHidden: [],
|
|
115
|
+
docksPinned: [],
|
|
116
|
+
docksCustomOrder: {},
|
|
117
|
+
commandShortcuts: {}
|
|
118
|
+
});
|
|
119
|
+
//#endregion
|
|
120
|
+
export { normalizeHubBase as a, DOCK_RENDERERS_STATE_KEY as i, DEFAULT_STATE_USER_SETTINGS as n, HUB_EVENTS as o, DEVFRAMES_HUB_BASE as r, DEFAULT_CATEGORIES_ORDER as t };
|