@devframes/hub 0.8.0 → 0.8.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.
@@ -2,7 +2,7 @@ import { N as NavTarget, O as DevframeViewIframe, P as RemoteConnectionInfo, a a
2
2
  import { DEFAULT_CATEGORIES_ORDER } from "../constants.mjs";
3
3
  import { w as DevframeMessagesClient, y as DevframeMessageEntryInput } from "../context-Cw5sDVoA.mjs";
4
4
  import "../index-Bx3T-R7i.mjs";
5
- import { DevframeClientRpcHost, DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientOptions, DevframeRpcContext, RpcClientEvents, RpcClientEvents as RpcClientEvents$1 } from "devframe/client";
5
+ import { DevframeClientRpcHost, DevframeConnection, DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientOptions, DevframeRpcContext, RpcClientEvents, RpcClientEvents as RpcClientEvents$1 } from "devframe/client";
6
6
  import { EventEmitter } from "devframe/types";
7
7
  import { SharedState } from "devframe/utils/shared-state";
8
8
  import { WhenContext } from "devframe/utils/when";
@@ -256,6 +256,16 @@ declare function getDevframeClientContext(): DevframeClientContext | undefined;
256
256
  */
257
257
  declare function setDevframeClientContext(ctx: DevframeClientContext | undefined): void;
258
258
  //#endregion
259
+ //#region src/client/dock-resources.d.ts
260
+ /**
261
+ * Resolve a dock iframe URL relative to the Devframe server that provided the
262
+ * dock entry. This keeps root-relative and dot-relative paths on the host
263
+ * server when an external viewer renders the hub.
264
+ */
265
+ declare function resolveDockUrl(url: string, connection: DevframeConnection): string;
266
+ /** Resolve URL-backed dock icons while preserving Iconify names and data URLs. */
267
+ declare function resolveDockIcon(icon: DevframeDockEntryIcon, connection: DevframeConnection): DevframeDockEntryIcon;
268
+ //#endregion
259
269
  //#region src/client/frame-nav.d.ts
260
270
  /**
261
271
  * Shared-iframe soft navigation — the viewer-side half of a host↔iframe
@@ -463,8 +473,18 @@ interface MessagesClientOptions {
463
473
  */
464
474
  declare function createMessagesClient(rpc: DevframeRpcClient, options?: MessagesClientOptions): DevframeMessagesClient;
465
475
  //#endregion
476
+ //#region src/remote-url.d.ts
477
+ /** Remove the remote connection descriptor from a URL before displaying or copying it. */
478
+ declare function stripRemoteConnectionFromUrl(input: string): string;
479
+ //#endregion
466
480
  //#region src/client/remote.d.ts
467
481
  type ConnectRemoteDevframeOptions = Omit<DevframeRpcClientOptions, 'connectionMeta' | 'authToken'>;
482
+ /**
483
+ * Build an external viewer URL from an existing trusted Devframe connection.
484
+ * Returns the original URL if the connection has no auth token, does not use
485
+ * WebSockets, or has an invalid metadata URL.
486
+ */
487
+ declare function buildRemoteDevframeUrl(url: string, connection: DevframeConnection): string;
468
488
  /**
469
489
  * Parse a {@link RemoteConnectionInfo} descriptor from the current page's URL
470
490
  * (or a provided URL/string). Checks the URL fragment first, then the query.
@@ -484,4 +504,4 @@ declare function parseRemoteConnection(input?: string): RemoteConnectionInfo | n
484
504
  */
485
505
  declare function connectRemoteDevframe(options?: ConnectRemoteDevframeOptions): Promise<DevframeRpcClient>;
486
506
  //#endregion
487
- export { CLIENT_CONTEXT_KEY, CommandsContext, ConnectRemoteDevframeOptions, DEFAULT_CATEGORIES_ORDER, DevframeClientContext, DevframeClientHost, DevframeClientHostOptions, type DevframeClientRpcHost, DockClientScriptContext, DockClientType, DockEntryState, DockEntryStateEvents, DockPanelStorage, DockRegistration, DockRenderer, DockRendererInstance, DockRendererMountOptions, DockRenderersContext, DocksConnectionContext, DocksContext, DocksEntriesContext, DocksPanelContext, FRAME_NAV_CHANNEL, FRAME_NAV_VERSION, FrameNavClient, FrameNavClientOptions, FrameNavEnvelope, FrameNavFrameMessage, FrameNavHostMessage, FrameNavHostPayload, FrameNavListenTarget, FrameTab, MessagesClientOptions, type RpcClientEvents, WhenClauseContext, attachFrameNavClient, connectRemoteDevframe, createDevframeClientHost, createMessagesClient, getDevframeClientContext, parseRemoteConnection, setDevframeClientContext };
507
+ export { CLIENT_CONTEXT_KEY, CommandsContext, ConnectRemoteDevframeOptions, DEFAULT_CATEGORIES_ORDER, DevframeClientContext, DevframeClientHost, DevframeClientHostOptions, type DevframeClientRpcHost, DockClientScriptContext, DockClientType, DockEntryState, DockEntryStateEvents, DockPanelStorage, DockRegistration, DockRenderer, DockRendererInstance, DockRendererMountOptions, 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, createMessagesClient, getDevframeClientContext, parseRemoteConnection, resolveDockIcon, resolveDockUrl, setDevframeClientContext, stripRemoteConnectionFromUrl };
@@ -1,6 +1,7 @@
1
1
  import { DEFAULT_CATEGORIES_ORDER, DEFAULT_STATE_USER_SETTINGS } from "../constants.mjs";
2
+ import { n as stripRemoteConnectionFromUrl, t as buildRemoteConnectionUrl } from "../remote-url-KVXtKP47.mjs";
2
3
  import { REMOTE_CONNECTION_KEY } from "devframe/constants";
3
- import { connectDevframe, getDevframeRpcClient } from "devframe/client";
4
+ import { connectDevframe, getDevframeRpcClient, resolveWsUrl } from "devframe/client";
4
5
  import { createEventEmitter } from "devframe/utils/events";
5
6
  import { destr } from "destr";
6
7
  export * from "devframe/client";
@@ -21,6 +22,53 @@ function setDevframeClientContext(ctx) {
21
22
  globalThis[CLIENT_CONTEXT_KEY] = ctx;
22
23
  }
23
24
  //#endregion
25
+ //#region src/client/dock-resources.ts
26
+ const URL_SCHEME_RE = /^[a-z][a-z\d+.-]*:/i;
27
+ function resolveResourceUrl(value, connection) {
28
+ const url = value.trim();
29
+ if (!url || URL_SCHEME_RE.test(url) || url.startsWith("//")) return url;
30
+ if (!url.startsWith("/") && !url.startsWith("./") && !url.startsWith("../")) return url;
31
+ try {
32
+ return new URL(url, connection.metaBaseUrl).href;
33
+ } catch {
34
+ return url;
35
+ }
36
+ }
37
+ function resolveIconUrl(value, connection) {
38
+ const url = value.trim();
39
+ if (!url || URL_SCHEME_RE.test(url) || url.startsWith("//")) return url;
40
+ if (!url.includes("/") && !url.includes(".")) return url;
41
+ try {
42
+ return new URL(url, connection.metaBaseUrl).href;
43
+ } catch {
44
+ return url;
45
+ }
46
+ }
47
+ /**
48
+ * Resolve a dock iframe URL relative to the Devframe server that provided the
49
+ * dock entry. This keeps root-relative and dot-relative paths on the host
50
+ * server when an external viewer renders the hub.
51
+ */
52
+ function resolveDockUrl(url, connection) {
53
+ const resolved = resolveResourceUrl(url, connection);
54
+ if (resolved !== url.trim()) return resolved;
55
+ const value = url.trim();
56
+ if (!value || URL_SCHEME_RE.test(value) || value.startsWith("//")) return /^localhost:\d/i.test(value) ? `http://${value}` : value;
57
+ try {
58
+ return new URL(`http://${value}`).href;
59
+ } catch {
60
+ return value;
61
+ }
62
+ }
63
+ /** Resolve URL-backed dock icons while preserving Iconify names and data URLs. */
64
+ function resolveDockIcon(icon, connection) {
65
+ if (typeof icon === "string") return resolveIconUrl(icon, connection);
66
+ return {
67
+ light: resolveIconUrl(icon.light, connection),
68
+ dark: resolveIconUrl(icon.dark, connection)
69
+ };
70
+ }
71
+ //#endregion
24
72
  //#region src/client/frame-nav.ts
25
73
  /**
26
74
  * Shared-iframe soft navigation — the viewer-side half of a host↔iframe
@@ -650,6 +698,27 @@ function base64UrlDecode(value) {
650
698
  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
651
699
  return new TextDecoder().decode(bytes);
652
700
  }
701
+ /**
702
+ * Build an external viewer URL from an existing trusted Devframe connection.
703
+ * Returns the original URL if the connection has no auth token, does not use
704
+ * WebSockets, or has an invalid metadata URL.
705
+ */
706
+ function buildRemoteDevframeUrl(url, connection) {
707
+ if (!connection.authToken || connection.connectionMeta.backend !== "websocket") return url;
708
+ let base;
709
+ try {
710
+ base = new URL(connection.metaBaseUrl);
711
+ } catch {
712
+ return url;
713
+ }
714
+ return buildRemoteConnectionUrl(url, {
715
+ v: 1,
716
+ backend: "websocket",
717
+ websocket: resolveWsUrl(connection.connectionMeta.websocket, connection.metaBaseUrl, base),
718
+ authToken: connection.authToken,
719
+ origin: base.origin
720
+ });
721
+ }
653
722
  function extractKeyFromFragment(hash) {
654
723
  if (!hash) return null;
655
724
  const raw = hash.startsWith("#") ? hash.slice(1) : hash;
@@ -726,4 +795,4 @@ async function connectRemoteDevframe(options = {}) {
726
795
  });
727
796
  }
728
797
  //#endregion
729
- export { CLIENT_CONTEXT_KEY, DEFAULT_CATEGORIES_ORDER, FRAME_NAV_CHANNEL, FRAME_NAV_VERSION, attachFrameNavClient, connectRemoteDevframe, createDevframeClientHost, createMessagesClient, getDevframeClientContext, parseRemoteConnection, setDevframeClientContext };
798
+ export { CLIENT_CONTEXT_KEY, DEFAULT_CATEGORIES_ORDER, FRAME_NAV_CHANNEL, FRAME_NAV_VERSION, attachFrameNavClient, buildRemoteDevframeUrl, connectRemoteDevframe, createDevframeClientHost, createMessagesClient, getDevframeClientContext, parseRemoteConnection, resolveDockIcon, resolveDockUrl, setDevframeClientContext, stripRemoteConnectionFromUrl };
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_CATEGORIES_ORDER, DEFAULT_STATE_USER_SETTINGS } from "../constants.mjs";
2
2
  import { r as defineHubRpcFunction } from "../define-Pbx8dsKz.mjs";
3
- import { REMOTE_CONNECTION_KEY } from "devframe/constants";
3
+ import { t as buildRemoteConnectionUrl } from "../remote-url-KVXtKP47.mjs";
4
4
  import { createEventEmitter } from "devframe/utils/events";
5
5
  import { coerceAgentPositionalArgs, createHostContext, createStorage } from "devframe/node";
6
6
  import { debounce } from "perfect-debounce";
@@ -231,45 +231,6 @@ function normaliseRemoteOptions(remote) {
231
231
  originLock: opts.originLock ?? true
232
232
  };
233
233
  }
234
- function base64UrlEncode(value) {
235
- const bytes = new TextEncoder().encode(value);
236
- let binary = "";
237
- for (const byte of bytes) binary += String.fromCharCode(byte);
238
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
239
- }
240
- function buildRemoteUrl(baseUrl, payload, transport) {
241
- const encoded = base64UrlEncode(JSON.stringify(payload));
242
- const param = `${REMOTE_CONNECTION_KEY}=${encoded}`;
243
- if (transport === "fragment") {
244
- const hashIdx = baseUrl.indexOf("#");
245
- if (hashIdx === -1) return `${baseUrl}#${param}`;
246
- const before = baseUrl.slice(0, hashIdx);
247
- const rawHash = baseUrl.slice(hashIdx + 1);
248
- if (!rawHash) return `${before}#${param}`;
249
- const routeQueryIdx = rawHash.indexOf("?");
250
- if (routeQueryIdx !== -1) {
251
- const beforeQuery = rawHash.slice(0, routeQueryIdx + 1);
252
- const params = new URLSearchParams(rawHash.slice(routeQueryIdx + 1));
253
- params.set(REMOTE_CONNECTION_KEY, encoded);
254
- return `${before}#${beforeQuery}${params.toString()}`;
255
- }
256
- const parts = rawHash.split("&");
257
- const existingIdx = parts.findIndex((part) => {
258
- const [key] = part.split("=");
259
- return key === REMOTE_CONNECTION_KEY;
260
- });
261
- if (existingIdx >= 0) {
262
- parts[existingIdx] = param;
263
- return `${before}#${parts.join("&")}`;
264
- }
265
- return `${before}#${rawHash}&${param}`;
266
- }
267
- const qIdx = baseUrl.indexOf("?");
268
- const hashIdx = baseUrl.indexOf("#");
269
- const hash = hashIdx === -1 ? "" : baseUrl.slice(hashIdx);
270
- const beforeHash = hashIdx === -1 ? baseUrl : baseUrl.slice(0, hashIdx);
271
- return `${beforeHash}${qIdx === -1 || qIdx >= (hashIdx === -1 ? beforeHash.length : hashIdx) ? "?" : "&"}${param}${hash}`;
272
- }
273
234
  var DevframeDocksHost = class {
274
235
  context;
275
236
  views = /* @__PURE__ */ new Map();
@@ -303,7 +264,7 @@ var DevframeDocksHost = class {
303
264
  };
304
265
  return {
305
266
  ...view,
306
- url: buildRemoteUrl(view.url, payload, record.options.transport)
267
+ url: buildRemoteConnectionUrl(view.url, payload, record.options.transport)
307
268
  };
308
269
  }
309
270
  resolveDevServerOrigin() {
@@ -0,0 +1,73 @@
1
+ import { REMOTE_CONNECTION_KEY } from "devframe/constants";
2
+ //#region src/remote-url.ts
3
+ function base64UrlEncode(value) {
4
+ const bytes = new TextEncoder().encode(value);
5
+ let binary = "";
6
+ for (const byte of bytes) binary += String.fromCharCode(byte);
7
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
8
+ }
9
+ function setRemoteConnectionParam(value, param) {
10
+ const parts = value ? value.split("&") : [];
11
+ const existingIdx = parts.findIndex((part) => part.split("=")[0] === REMOTE_CONNECTION_KEY);
12
+ if (existingIdx >= 0) parts[existingIdx] = param;
13
+ else parts.push(param);
14
+ return parts.join("&");
15
+ }
16
+ /** Encode a remote connection descriptor into an external viewer URL. */
17
+ function buildRemoteConnectionUrl(baseUrl, payload, transport = "fragment") {
18
+ const param = `${REMOTE_CONNECTION_KEY}=${base64UrlEncode(JSON.stringify(payload))}`;
19
+ if (transport === "fragment") {
20
+ const hashIdx = baseUrl.indexOf("#");
21
+ if (hashIdx === -1) return `${baseUrl}#${param}`;
22
+ const beforeHash = baseUrl.slice(0, hashIdx);
23
+ const rawHash = baseUrl.slice(hashIdx + 1);
24
+ if (!rawHash) return `${beforeHash}#${param}`;
25
+ const routeQueryIdx = rawHash.indexOf("?");
26
+ if (routeQueryIdx !== -1) return `${beforeHash}#${rawHash.slice(0, routeQueryIdx + 1)}${setRemoteConnectionParam(rawHash.slice(routeQueryIdx + 1), param)}`;
27
+ return `${beforeHash}#${setRemoteConnectionParam(rawHash, param)}`;
28
+ }
29
+ const hashIdx = baseUrl.indexOf("#");
30
+ const hash = hashIdx === -1 ? "" : baseUrl.slice(hashIdx);
31
+ const beforeHash = hashIdx === -1 ? baseUrl : baseUrl.slice(0, hashIdx);
32
+ return `${beforeHash}${beforeHash.includes("?") ? "&" : "?"}${param}${hash}`;
33
+ }
34
+ /** Remove the remote connection descriptor from a URL before displaying or copying it. */
35
+ function stripRemoteConnectionFromUrl(input) {
36
+ function stripParamList(value) {
37
+ const parts = value.split("&");
38
+ const filtered = parts.filter((part) => part.split("=")[0] !== REMOTE_CONNECTION_KEY);
39
+ return [filtered.join("&"), filtered.length !== parts.length];
40
+ }
41
+ const hashIdx = input.indexOf("#");
42
+ let beforeHash = hashIdx === -1 ? input : input.slice(0, hashIdx);
43
+ let hash = hashIdx === -1 ? void 0 : input.slice(hashIdx + 1);
44
+ let changed = false;
45
+ if (hash !== void 0) {
46
+ const routeQueryIdx = hash.indexOf("?");
47
+ if (routeQueryIdx === -1) {
48
+ const [nextHash, didChange] = stripParamList(hash);
49
+ hash = nextHash;
50
+ changed ||= didChange;
51
+ } else {
52
+ const route = hash.slice(0, routeQueryIdx);
53
+ const [query, didChange] = stripParamList(hash.slice(routeQueryIdx + 1));
54
+ if (didChange) {
55
+ hash = query ? `${route}?${query}` : route;
56
+ changed = true;
57
+ }
58
+ }
59
+ }
60
+ const queryIdx = beforeHash.indexOf("?");
61
+ if (queryIdx !== -1) {
62
+ const path = beforeHash.slice(0, queryIdx);
63
+ const [query, didChange] = stripParamList(beforeHash.slice(queryIdx + 1));
64
+ if (didChange) {
65
+ beforeHash = query ? `${path}?${query}` : path;
66
+ changed = true;
67
+ }
68
+ }
69
+ if (!changed) return input;
70
+ return hash ? `${beforeHash}#${hash}` : beforeHash;
71
+ }
72
+ //#endregion
73
+ export { stripRemoteConnectionFromUrl as n, buildRemoteConnectionUrl as t };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@devframes/hub",
3
3
  "type": "module",
4
- "version": "0.8.0",
4
+ "version": "0.8.1",
5
5
  "description": "Framework-neutral hub layer for devframe — docks, terminals, messages, commands.",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "dist"
34
34
  ],
35
35
  "peerDependencies": {
36
- "devframe": "0.8.0"
36
+ "devframe": "0.8.1"
37
37
  },
38
38
  "dependencies": {
39
39
  "@standard-schema/spec": "^1.1.0",
@@ -49,7 +49,7 @@
49
49
  "mlly": "^1.8.2",
50
50
  "tsdown": "^0.22.14",
51
51
  "valibot": "^1.4.2",
52
- "devframe": "0.8.0"
52
+ "devframe": "0.8.1"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsdown",