devframe 0.5.4 → 0.6.0-beta.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/{_shared-CUFqO4kJ.mjs → _shared-bWRzeSa0.mjs} +2 -3
- package/dist/adapters/build.d.mts +1 -1
- package/dist/adapters/build.mjs +7 -7
- package/dist/adapters/cli.d.mts +1 -1
- package/dist/adapters/cli.mjs +2 -2
- package/dist/adapters/dev.d.mts +8 -2
- package/dist/adapters/dev.mjs +1 -1
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/mcp.d.mts +1 -1
- package/dist/adapters/mcp.mjs +4 -4
- package/dist/client/index.d.mts +148 -6
- package/dist/client/index.mjs +257 -44
- package/dist/constants.d.mts +17 -1
- package/dist/constants.mjs +17 -1
- package/dist/context-3x_bgBgZ.mjs +48 -0
- package/dist/{context-DTRcO_UH.d.mts → context-o-TwncyP.d.mts} +1 -1
- package/dist/{dev-Cv43GfqM.mjs → dev-CSpLSEVh.mjs} +39 -13
- package/dist/{devframe-BuR6n9ZD.d.mts → devframe-BwfavB78.d.mts} +365 -15
- package/dist/{diagnostics-GitRGKbr.mjs → diagnostics-BXwBQmoN.mjs} +1 -1
- package/dist/{dump-9lKIJTLh.mjs → dump-DoXkj4ku.mjs} +1 -1
- package/dist/helpers/vite.d.mts +1 -1
- package/dist/helpers/vite.mjs +11 -11
- package/dist/{host-h3-Dgpgr1Ul.mjs → host-h3-C5SZlk03.mjs} +134 -6
- package/dist/{index-C7M1hnvL.d.mts → index-CxaPKDXX.d.mts} +2 -2
- package/dist/{index-DH2sBIwd.d.mts → index-DXHWuwJk.d.mts} +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/node/auth.d.mts +43 -23
- package/dist/node/auth.mjs +84 -37
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/hub-internals.mjs +2 -2
- package/dist/node/index.d.mts +22 -6
- package/dist/node/index.mjs +4 -4
- package/dist/recipes/open-helpers.d.mts +1 -1
- package/dist/recipes/open-helpers.mjs +3 -3
- package/dist/revoke-ENfxWkFK.mjs +79 -0
- package/dist/rpc/dump.d.mts +1 -1
- package/dist/rpc/dump.mjs +1 -1
- package/dist/rpc/index.d.mts +3 -3
- package/dist/rpc/index.mjs +4 -4
- package/dist/rpc/transports/ws-client.d.mts +1 -1
- package/dist/rpc/transports/ws-client.mjs +2 -2
- package/dist/rpc/transports/ws-server.d.mts +2 -2
- package/dist/rpc/transports/ws-server.mjs +124 -60
- package/dist/{serialization-BD_qXGjd.mjs → serialization-DpLXCy13.mjs} +1 -1
- package/dist/{server-wHlpcdZ9.d.mts → server-oju7PTi2.d.mts} +30 -3
- package/dist/{server-BBaBJaUL.mjs → server-wyY3zh67.mjs} +18 -14
- package/dist/{shared-state-u0y123fi.mjs → shared-state-D4PPieA0.mjs} +4 -4
- package/dist/{shared-state-BlBNYziY.mjs → storage-l2ezeend.mjs} +128 -6
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-BkkQ0Txg.d.mts → types-DZEx4ffs.d.mts} +9 -1
- package/dist/utils/crypto-token.d.mts +24 -0
- package/dist/utils/crypto-token.mjs +45 -0
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/hash.mjs +1 -1
- package/dist/utils/launch-editor.mjs +1 -1
- package/dist/utils/open.mjs +1 -1
- package/dist/utils/scope.d.mts +11 -0
- package/dist/utils/scope.mjs +15 -0
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/shared-state.mjs +1 -1
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/utils/structured-clone.mjs +1 -1
- package/dist/{ws-client-CVYX9niP.d.mts → ws-client-D0z0pOhn.d.mts} +1 -1
- package/dist/ws-server-BVVMDo2n.d.mts +103 -0
- package/package.json +12 -7
- package/skills/devframe/SKILL.md +123 -23
- package/skills/devframe/templates/counter-devframe.ts +14 -4
- package/skills/devframe/templates/spa-devframe.ts +13 -3
- package/skills/devframe/templates/vite-client.ts +3 -2
- package/dist/context-DaKmhhHY.mjs +0 -164
- package/dist/revoke-CL0LSAN9.mjs +0 -803
- package/dist/utils/human-id.d.mts +0 -8
- package/dist/utils/human-id.mjs +0 -769
- package/dist/ws-server-C1LjmRnp.d.mts +0 -61
- /package/dist/{define-CW9gLnyG.mjs → define-BLWPsH6y.mjs} +0 -0
- /package/dist/{diagnostics-reporter-CBBZwoMv.mjs → diagnostics-reporter-CsIG85Q5.mjs} +0 -0
- /package/dist/{hash-bwOD8RgU.mjs → hash-DFc5WwhO.mjs} +0 -0
- /package/dist/{launch-editor-BbNhtg7b.mjs → launch-editor-CgXBgviI.mjs} +0 -0
- /package/dist/{open-DiQn6zCH.mjs → open-Dusa2Zzd.mjs} +0 -0
- /package/dist/{structured-clone-PdCZwt7F.mjs → structured-clone-CeZOHvkd.mjs} +0 -0
- /package/dist/{structured-clone-jegjz0hM.mjs → structured-clone-XpHLZ8nr.mjs} +0 -0
- /package/dist/{transports-DTFoMUbE.mjs → transports-BmDVn4SF.mjs} +0 -0
package/dist/client/index.mjs
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
import { t as hash } from "../hash-
|
|
1
|
+
import { t as hash } from "../hash-DFc5WwhO.mjs";
|
|
2
2
|
import { colors } from "../utils/colors.mjs";
|
|
3
3
|
import { createEventEmitter } from "../utils/events.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
6
|
-
import { i as structuredCloneStringify, n as structuredCloneParse, t as structuredCloneDeserialize } from "../structured-clone-PdCZwt7F.mjs";
|
|
4
|
+
import { t as createSharedState } from "../shared-state-D4PPieA0.mjs";
|
|
5
|
+
import { i as structuredCloneStringify, n as structuredCloneParse, t as structuredCloneDeserialize } from "../structured-clone-CeZOHvkd.mjs";
|
|
7
6
|
import { createStreamReader, createStreamSink } from "../utils/streaming-channel.mjs";
|
|
8
7
|
import { promiseWithResolver } from "../utils/promise.mjs";
|
|
8
|
+
import { isQualifiedName, qualifyName } from "../utils/scope.mjs";
|
|
9
9
|
import { defineDiagnostics } from "nostics";
|
|
10
10
|
import { ansiFormatter } from "nostics/formatters/ansi";
|
|
11
|
+
import { withBase, withProtocol } from "ufo";
|
|
11
12
|
import { createBirpc } from "birpc";
|
|
12
13
|
//#region src/constants.ts
|
|
13
14
|
const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
|
|
14
15
|
const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
|
|
16
|
+
/**
|
|
17
|
+
* Page-URL query parameter carrying a one-time authentication code (OTP) for
|
|
18
|
+
* "magic link" auth. A host can print a link like `<origin>/?devframe_otp=<code>`;
|
|
19
|
+
* the client reads the code, exchanges it for a token, and strips the parameter
|
|
20
|
+
* from the URL. See `buildOtpAuthUrl` (node) and the `authenticateWithUrlOtp` /
|
|
21
|
+
* `consumeOtpFromUrl` client utilities (or `connectDevframe`'s `otpParam`).
|
|
22
|
+
*/
|
|
23
|
+
const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
|
|
15
24
|
//#endregion
|
|
16
25
|
//#region src/utils/diagnostics-reporter.ts
|
|
17
26
|
const formatAnsi = ansiFormatter(colors);
|
|
@@ -259,6 +268,54 @@ function formatPath(parent, key) {
|
|
|
259
268
|
return key;
|
|
260
269
|
}
|
|
261
270
|
//#endregion
|
|
271
|
+
//#region src/client/otp.ts
|
|
272
|
+
/**
|
|
273
|
+
* Read a one-time authentication code (OTP) from the current page URL's query
|
|
274
|
+
* string, without side effects. Returns `undefined` when the parameter is absent.
|
|
275
|
+
*/
|
|
276
|
+
function readOtpFromUrl(param = DEVFRAME_OTP_URL_PARAM) {
|
|
277
|
+
try {
|
|
278
|
+
return new URLSearchParams(globalThis.location?.search).get(param) || void 0;
|
|
279
|
+
} catch {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function stripParamFromUrl(param) {
|
|
284
|
+
try {
|
|
285
|
+
const url = new URL(globalThis.location.href);
|
|
286
|
+
if (!url.searchParams.has(param)) return;
|
|
287
|
+
url.searchParams.delete(param);
|
|
288
|
+
globalThis.history?.replaceState(globalThis.history.state, "", url.href);
|
|
289
|
+
} catch {}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Read the one-time code from the page URL and remove it from the address bar
|
|
293
|
+
* (and the current history entry), so the single-use code isn't left in the
|
|
294
|
+
* URL, browser history, or a `Referer`. Returns the code, or `undefined` when
|
|
295
|
+
* absent.
|
|
296
|
+
*/
|
|
297
|
+
function consumeOtpFromUrl(param = DEVFRAME_OTP_URL_PARAM) {
|
|
298
|
+
const code = readOtpFromUrl(param);
|
|
299
|
+
if (code) stripParamFromUrl(param);
|
|
300
|
+
return code;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Consume a one-time code from the page URL (see {@link consumeOtpFromUrl}) and
|
|
304
|
+
* exchange it for a token via the client. Resolves `true` when the client is
|
|
305
|
+
* authenticated (already trusted, or the exchange succeeded), and `false` when
|
|
306
|
+
* no code is present or the exchange failed.
|
|
307
|
+
*
|
|
308
|
+
* Higher-level integrations (e.g. Vite DevTools) that want to drive their own
|
|
309
|
+
* authentication UI can disable `connectDevframe`'s built-in handling with
|
|
310
|
+
* `otpParam: false` and call this — or {@link consumeOtpFromUrl} — themselves.
|
|
311
|
+
*/
|
|
312
|
+
async function authenticateWithUrlOtp(rpc, options = {}) {
|
|
313
|
+
const code = consumeOtpFromUrl(options.param ?? "devframe_otp");
|
|
314
|
+
if (!code) return false;
|
|
315
|
+
if (rpc.isTrusted) return true;
|
|
316
|
+
return rpc.requestTrustWithCode(code);
|
|
317
|
+
}
|
|
318
|
+
//#endregion
|
|
262
319
|
//#region src/client/rpc-shared-state.ts
|
|
263
320
|
function createRpcSharedStateClientHost(rpc) {
|
|
264
321
|
const sharedState = /* @__PURE__ */ new Map();
|
|
@@ -425,6 +482,7 @@ async function createStaticRpcClientMode(options) {
|
|
|
425
482
|
isTrusted: true,
|
|
426
483
|
requestTrust: async () => true,
|
|
427
484
|
requestTrustWithToken: async () => true,
|
|
485
|
+
requestTrustWithCode: async () => null,
|
|
428
486
|
ensureTrusted: async () => true,
|
|
429
487
|
call: (...args) => staticCaller.call(args[0], args.slice(1)),
|
|
430
488
|
callEvent: (...args) => staticCaller.callEvent(args[0], args.slice(1)),
|
|
@@ -1763,15 +1821,51 @@ function parseUA(uastringOrExtensions, extensions) {
|
|
|
1763
1821
|
}
|
|
1764
1822
|
//#endregion
|
|
1765
1823
|
//#region src/client/rpc-ws.ts
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1824
|
+
/**
|
|
1825
|
+
* Resolve a {@link ConnectionMeta.websocket} descriptor into a concrete
|
|
1826
|
+
* `ws(s)://` URL.
|
|
1827
|
+
*
|
|
1828
|
+
* The object / relative-path forms connect to the page's own origin (only the
|
|
1829
|
+
* `http`→`ws` protocol swap is applied), resolving the path against where
|
|
1830
|
+
* `__connection.json` was loaded. This is deliberately host-agnostic so the
|
|
1831
|
+
* connection survives a reverse proxy that changes the domain or port — the
|
|
1832
|
+
* client trusts its own location, never a server-baked hostname. An explicit
|
|
1833
|
+
* `port`/`host` (or a full `ws(s)://` URL string) opts into a cross-origin
|
|
1834
|
+
* endpoint, e.g. a side-car server on its own port.
|
|
1835
|
+
*/
|
|
1836
|
+
function resolveWsUrl(websocket, metaBaseUrl, loc) {
|
|
1837
|
+
const wsProtocol = loc.protocol === "https:" ? "wss:" : "ws:";
|
|
1838
|
+
const base = (() => {
|
|
1839
|
+
try {
|
|
1840
|
+
return new URL(metaBaseUrl, loc.href);
|
|
1841
|
+
} catch {
|
|
1842
|
+
return new URL(loc.href);
|
|
1843
|
+
}
|
|
1844
|
+
})();
|
|
1845
|
+
if (websocket && typeof websocket === "object") {
|
|
1846
|
+
if (websocket.host != null || websocket.port != null) {
|
|
1847
|
+
const host = websocket.host ?? `${loc.hostname}:${websocket.port}`;
|
|
1848
|
+
const target = new URL(websocket.path ?? "/", `${wsProtocol}//${host}`);
|
|
1849
|
+
target.protocol = wsProtocol;
|
|
1850
|
+
return target.href;
|
|
1851
|
+
}
|
|
1852
|
+
const target = new URL(websocket.path ?? "", base);
|
|
1853
|
+
target.protocol = wsProtocol;
|
|
1854
|
+
return target.href;
|
|
1855
|
+
}
|
|
1856
|
+
if (typeof websocket === "number") return `${wsProtocol}//${loc.hostname}:${websocket}`;
|
|
1857
|
+
const str = websocket ?? "";
|
|
1858
|
+
if (/^wss?:\/\//i.test(str)) return str;
|
|
1859
|
+
if (/^https?:\/\//i.test(str)) return withProtocol(str, /^https/i.test(str) ? "wss://" : "ws://");
|
|
1860
|
+
const target = new URL(str, base);
|
|
1861
|
+
target.protocol = wsProtocol;
|
|
1862
|
+
return target.href;
|
|
1769
1863
|
}
|
|
1770
1864
|
function createWsRpcClientMode(options) {
|
|
1771
|
-
const { authToken, connectionMeta, events, clientRpc, rpcOptions = {}, wsOptions = {} } = options;
|
|
1865
|
+
const { authToken, connectionMeta, metaBaseUrl, events, clientRpc, rpcOptions = {}, wsOptions = {} } = options;
|
|
1772
1866
|
let isTrusted = false;
|
|
1773
1867
|
const trustedPromise = promiseWithResolver();
|
|
1774
|
-
const url =
|
|
1868
|
+
const url = resolveWsUrl(connectionMeta.websocket, metaBaseUrl ?? "./", location);
|
|
1775
1869
|
const definitions = /* @__PURE__ */ new Map();
|
|
1776
1870
|
for (const name of connectionMeta.jsonSerializableMethods ?? []) definitions.set(name, { jsonSerializable: true });
|
|
1777
1871
|
const serverRpc = createRpcClient(clientRpc.functions, {
|
|
@@ -1792,10 +1886,9 @@ function createWsRpcClientMode(options) {
|
|
|
1792
1886
|
}
|
|
1793
1887
|
});
|
|
1794
1888
|
let currentAuthToken = authToken;
|
|
1795
|
-
|
|
1796
|
-
currentAuthToken = token;
|
|
1889
|
+
function describeUA() {
|
|
1797
1890
|
const info = parseUA(navigator.userAgent);
|
|
1798
|
-
|
|
1891
|
+
return [
|
|
1799
1892
|
info.browser.name,
|
|
1800
1893
|
info.browser.version,
|
|
1801
1894
|
"|",
|
|
@@ -1803,19 +1896,36 @@ function createWsRpcClientMode(options) {
|
|
|
1803
1896
|
info.os.version,
|
|
1804
1897
|
info.device.type
|
|
1805
1898
|
].filter((i) => i).join(" ");
|
|
1899
|
+
}
|
|
1900
|
+
async function requestTrustWithToken(token) {
|
|
1901
|
+
currentAuthToken = token;
|
|
1806
1902
|
const result = await serverRpc.$call("devframe:anonymous:auth", {
|
|
1807
1903
|
authToken: token,
|
|
1808
|
-
ua,
|
|
1904
|
+
ua: describeUA(),
|
|
1809
1905
|
origin: location.origin
|
|
1810
1906
|
});
|
|
1811
1907
|
isTrusted = result.isTrusted;
|
|
1812
|
-
trustedPromise.resolve(
|
|
1908
|
+
if (isTrusted) trustedPromise.resolve(true);
|
|
1813
1909
|
events.emit("rpc:is-trusted:updated", isTrusted);
|
|
1814
1910
|
return result.isTrusted;
|
|
1815
1911
|
}
|
|
1912
|
+
async function requestTrustWithCode(code) {
|
|
1913
|
+
const token = (await serverRpc.$call("devframe:auth:exchange", {
|
|
1914
|
+
code,
|
|
1915
|
+
ua: describeUA(),
|
|
1916
|
+
origin: location.origin
|
|
1917
|
+
}))?.authToken ?? null;
|
|
1918
|
+
if (token) {
|
|
1919
|
+
currentAuthToken = token;
|
|
1920
|
+
isTrusted = true;
|
|
1921
|
+
trustedPromise.resolve(true);
|
|
1922
|
+
events.emit("rpc:is-trusted:updated", true);
|
|
1923
|
+
}
|
|
1924
|
+
return token;
|
|
1925
|
+
}
|
|
1816
1926
|
async function requestTrust() {
|
|
1817
1927
|
if (isTrusted) return true;
|
|
1818
|
-
return requestTrustWithToken(currentAuthToken);
|
|
1928
|
+
return requestTrustWithToken(currentAuthToken ?? "");
|
|
1819
1929
|
}
|
|
1820
1930
|
async function ensureTrusted(timeout = 6e4) {
|
|
1821
1931
|
if (isTrusted) trustedPromise.resolve(true);
|
|
@@ -1835,6 +1945,7 @@ function createWsRpcClientMode(options) {
|
|
|
1835
1945
|
},
|
|
1836
1946
|
requestTrust,
|
|
1837
1947
|
requestTrustWithToken,
|
|
1948
|
+
requestTrustWithCode,
|
|
1838
1949
|
ensureTrusted,
|
|
1839
1950
|
call: (...args) => {
|
|
1840
1951
|
return serverRpc.$call(...args);
|
|
@@ -1848,26 +1959,101 @@ function createWsRpcClientMode(options) {
|
|
|
1848
1959
|
};
|
|
1849
1960
|
}
|
|
1850
1961
|
//#endregion
|
|
1962
|
+
//#region src/client/settings.ts
|
|
1963
|
+
function createClientSettingsStore(rpc, namespace, scope) {
|
|
1964
|
+
const stateKey = `devframe:settings:${scope}:${namespace}`;
|
|
1965
|
+
let statePromise;
|
|
1966
|
+
function store() {
|
|
1967
|
+
if (!statePromise) statePromise = rpc.sharedState.get(stateKey, { initialValue: {} });
|
|
1968
|
+
return statePromise;
|
|
1969
|
+
}
|
|
1970
|
+
return {
|
|
1971
|
+
async get(key) {
|
|
1972
|
+
return (await store()).value()[key];
|
|
1973
|
+
},
|
|
1974
|
+
async set(key, value) {
|
|
1975
|
+
(await store()).mutate((draft) => {
|
|
1976
|
+
draft[key] = value;
|
|
1977
|
+
});
|
|
1978
|
+
},
|
|
1979
|
+
async delete(key) {
|
|
1980
|
+
(await store()).mutate((draft) => {
|
|
1981
|
+
delete draft[key];
|
|
1982
|
+
});
|
|
1983
|
+
},
|
|
1984
|
+
async all() {
|
|
1985
|
+
return (await store()).value();
|
|
1986
|
+
},
|
|
1987
|
+
async onChange(fn) {
|
|
1988
|
+
return (await store()).on("updated", (full) => fn(full));
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Build the client-side `settings` surface for a scope namespace. Mirrors
|
|
1994
|
+
* the node-side stores over the shared-state sync protocol.
|
|
1995
|
+
*/
|
|
1996
|
+
function createClientSettings(rpc, namespace) {
|
|
1997
|
+
return {
|
|
1998
|
+
global: createClientSettingsStore(rpc, namespace, "global"),
|
|
1999
|
+
project: createClientSettingsStore(rpc, namespace, "project")
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
//#endregion
|
|
2003
|
+
//#region src/client/scope.ts
|
|
2004
|
+
/**
|
|
2005
|
+
* Build a namespace-scoped view of a {@link DevframeRpcClient}. Every RPC
|
|
2006
|
+
* id, shared-state key, and streaming channel passed through the returned
|
|
2007
|
+
* `rpc` surface is auto-namespaced with `<namespace>:`.
|
|
2008
|
+
*/
|
|
2009
|
+
function createScopedClientContext(rpc, namespace) {
|
|
2010
|
+
return {
|
|
2011
|
+
namespace,
|
|
2012
|
+
base: rpc,
|
|
2013
|
+
rpc: {
|
|
2014
|
+
namespace,
|
|
2015
|
+
register(fn) {
|
|
2016
|
+
if (isQualifiedName(fn.name)) throw new Error(`[devframe] Scoped client RPC registration for namespace "${namespace}" received an already-namespaced function name "${fn.name}". Pass a bare name without a ":" separator.`);
|
|
2017
|
+
rpc.client.register({
|
|
2018
|
+
...fn,
|
|
2019
|
+
name: `${namespace}:${fn.name}`
|
|
2020
|
+
});
|
|
2021
|
+
},
|
|
2022
|
+
call: ((method, ...args) => rpc.call(qualifyName(namespace, method), ...args)),
|
|
2023
|
+
callEvent: ((method, ...args) => rpc.callEvent(qualifyName(namespace, method), ...args)),
|
|
2024
|
+
callOptional: ((method, ...args) => rpc.callOptional(qualifyName(namespace, method), ...args)),
|
|
2025
|
+
sharedState: ((key, options) => rpc.sharedState.get(qualifyName(namespace, key), options)),
|
|
2026
|
+
streaming: {
|
|
2027
|
+
subscribe: (channel, id, options) => rpc.streaming.subscribe(qualifyName(namespace, channel), id, options),
|
|
2028
|
+
upload: (channel, id) => rpc.streaming.upload(qualifyName(namespace, channel), id)
|
|
2029
|
+
}
|
|
2030
|
+
},
|
|
2031
|
+
settings: createClientSettings(rpc, namespace),
|
|
2032
|
+
scope: rpc.scope
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
//#endregion
|
|
1851
2036
|
//#region src/client/rpc.ts
|
|
1852
2037
|
const CONNECTION_META_KEY = "__DEVFRAME_CONNECTION_META__";
|
|
1853
2038
|
const CONNECTION_AUTH_TOKEN_KEY = "__DEVFRAME_CONNECTION_AUTH_TOKEN__";
|
|
1854
|
-
function
|
|
2039
|
+
function getStoredAuthToken(userAuthToken) {
|
|
1855
2040
|
const getters = [
|
|
1856
2041
|
() => userAuthToken,
|
|
1857
|
-
() => localStorage.getItem(CONNECTION_AUTH_TOKEN_KEY),
|
|
2042
|
+
() => localStorage.getItem(CONNECTION_AUTH_TOKEN_KEY) ?? void 0,
|
|
1858
2043
|
() => window?.[CONNECTION_AUTH_TOKEN_KEY],
|
|
1859
2044
|
() => globalThis?.[CONNECTION_AUTH_TOKEN_KEY],
|
|
1860
2045
|
() => parent.window?.[CONNECTION_AUTH_TOKEN_KEY]
|
|
1861
2046
|
];
|
|
1862
|
-
let value;
|
|
1863
2047
|
for (const getter of getters) try {
|
|
1864
|
-
value = getter();
|
|
1865
|
-
if (value)
|
|
2048
|
+
const value = getter();
|
|
2049
|
+
if (value) return value;
|
|
1866
2050
|
} catch {}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2051
|
+
}
|
|
2052
|
+
function persistAuthToken(token) {
|
|
2053
|
+
try {
|
|
2054
|
+
localStorage.setItem(CONNECTION_AUTH_TOKEN_KEY, token);
|
|
2055
|
+
} catch {}
|
|
2056
|
+
globalThis[CONNECTION_AUTH_TOKEN_KEY] = token;
|
|
1871
2057
|
}
|
|
1872
2058
|
function findConnectionMetaFromWindows() {
|
|
1873
2059
|
const getters = [
|
|
@@ -1886,18 +2072,18 @@ async function getDevframeRpcClient(options = {}) {
|
|
|
1886
2072
|
const bases = Array.isArray(baseURL) ? baseURL : [baseURL];
|
|
1887
2073
|
let connectionMeta = options.connectionMeta || findConnectionMetaFromWindows();
|
|
1888
2074
|
let resolvedBaseURL = bases[0] ?? "./";
|
|
1889
|
-
function
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
2075
|
+
function resolveMetaBaseUrl() {
|
|
2076
|
+
const metaPath = withBase(DEVFRAME_CONNECTION_META_FILENAME, resolvedBaseURL);
|
|
2077
|
+
try {
|
|
2078
|
+
return new URL(metaPath, globalThis.location?.href).href;
|
|
2079
|
+
} catch {
|
|
2080
|
+
return metaPath;
|
|
2081
|
+
}
|
|
1896
2082
|
}
|
|
1897
2083
|
if (!connectionMeta) {
|
|
1898
2084
|
const errors = [];
|
|
1899
2085
|
for (const base of bases) try {
|
|
1900
|
-
connectionMeta = await fetch(
|
|
2086
|
+
connectionMeta = await fetch(withBase(DEVFRAME_CONNECTION_META_FILENAME, base)).then((r) => r.json());
|
|
1901
2087
|
resolvedBaseURL = base;
|
|
1902
2088
|
globalThis[CONNECTION_META_KEY] = connectionMeta;
|
|
1903
2089
|
break;
|
|
@@ -1911,13 +2097,14 @@ async function getDevframeRpcClient(options = {}) {
|
|
|
1911
2097
|
...typeof options.cacheOptions === "object" ? options.cacheOptions : {}
|
|
1912
2098
|
});
|
|
1913
2099
|
const context = { rpc: void 0 };
|
|
1914
|
-
const authToken =
|
|
2100
|
+
const authToken = getStoredAuthToken(options.authToken);
|
|
2101
|
+
if (authToken) persistAuthToken(authToken);
|
|
1915
2102
|
const clientRpc = new RpcFunctionsCollectorBase(context);
|
|
1916
2103
|
async function fetchJsonFromBases(path) {
|
|
1917
2104
|
const candidates = [resolvedBaseURL, ...bases.filter((base) => base !== resolvedBaseURL)].filter((x) => x != null);
|
|
1918
2105
|
const errors = [];
|
|
1919
2106
|
for (const base of candidates) try {
|
|
1920
|
-
return await fetch(
|
|
2107
|
+
return await fetch(withBase(path, base)).then((r) => {
|
|
1921
2108
|
if (!r.ok) throw new Error(`Failed to fetch ${path} from ${base}: ${r.status}`);
|
|
1922
2109
|
return r.json();
|
|
1923
2110
|
});
|
|
@@ -1929,6 +2116,7 @@ async function getDevframeRpcClient(options = {}) {
|
|
|
1929
2116
|
const mode = connectionMeta.backend === "static" ? await createStaticRpcClientMode({ fetchJsonFromBases }) : createWsRpcClientMode({
|
|
1930
2117
|
authToken,
|
|
1931
2118
|
connectionMeta,
|
|
2119
|
+
metaBaseUrl: resolveMetaBaseUrl(),
|
|
1932
2120
|
events,
|
|
1933
2121
|
clientRpc,
|
|
1934
2122
|
rpcOptions: {
|
|
@@ -1947,6 +2135,10 @@ async function getDevframeRpcClient(options = {}) {
|
|
|
1947
2135
|
},
|
|
1948
2136
|
wsOptions: options.wsOptions
|
|
1949
2137
|
});
|
|
2138
|
+
let authChannel;
|
|
2139
|
+
try {
|
|
2140
|
+
authChannel = new BroadcastChannel("devframe-auth");
|
|
2141
|
+
} catch {}
|
|
1950
2142
|
const rpc = {
|
|
1951
2143
|
events,
|
|
1952
2144
|
get isTrusted() {
|
|
@@ -1956,32 +2148,53 @@ async function getDevframeRpcClient(options = {}) {
|
|
|
1956
2148
|
ensureTrusted: mode.ensureTrusted,
|
|
1957
2149
|
requestTrust: mode.requestTrust,
|
|
1958
2150
|
requestTrustWithToken: async (token) => {
|
|
1959
|
-
|
|
1960
|
-
globalThis[CONNECTION_AUTH_TOKEN_KEY] = token;
|
|
2151
|
+
persistAuthToken(token);
|
|
1961
2152
|
return mode.requestTrustWithToken(token);
|
|
1962
2153
|
},
|
|
2154
|
+
requestTrustWithCode: async (code) => {
|
|
2155
|
+
const token = await mode.requestTrustWithCode(code);
|
|
2156
|
+
if (!token) return false;
|
|
2157
|
+
persistAuthToken(token);
|
|
2158
|
+
try {
|
|
2159
|
+
authChannel?.postMessage({
|
|
2160
|
+
type: "auth-update",
|
|
2161
|
+
authToken: token
|
|
2162
|
+
});
|
|
2163
|
+
} catch {}
|
|
2164
|
+
return true;
|
|
2165
|
+
},
|
|
1963
2166
|
call: mode.call,
|
|
1964
2167
|
callEvent: mode.callEvent,
|
|
1965
2168
|
callOptional: mode.callOptional,
|
|
1966
2169
|
client: clientRpc,
|
|
1967
2170
|
sharedState: void 0,
|
|
1968
2171
|
streaming: void 0,
|
|
1969
|
-
cacheManager
|
|
2172
|
+
cacheManager,
|
|
2173
|
+
scope: void 0
|
|
1970
2174
|
};
|
|
1971
2175
|
rpc.sharedState = createRpcSharedStateClientHost(rpc);
|
|
1972
2176
|
rpc.streaming = createRpcStreamingClientHost(rpc);
|
|
2177
|
+
const scopedCache = /* @__PURE__ */ new Map();
|
|
2178
|
+
rpc.scope = ((namespace) => {
|
|
2179
|
+
if (!namespace) return rpc;
|
|
2180
|
+
let scoped = scopedCache.get(namespace);
|
|
2181
|
+
if (!scoped) {
|
|
2182
|
+
scoped = createScopedClientContext(rpc, namespace);
|
|
2183
|
+
scopedCache.set(namespace, scoped);
|
|
2184
|
+
}
|
|
2185
|
+
return scoped;
|
|
2186
|
+
});
|
|
1973
2187
|
context.rpc = rpc;
|
|
1974
2188
|
mode.requestTrust();
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
} catch {}
|
|
2189
|
+
const otpParam = options.otpParam ?? "devframe_otp";
|
|
2190
|
+
if (otpParam) authenticateWithUrlOtp(rpc, { param: otpParam });
|
|
2191
|
+
if (authChannel) authChannel.onmessage = (event) => {
|
|
2192
|
+
if (event.data?.type === "auth-update" && event.data.authToken) rpc.requestTrustWithToken(event.data.authToken);
|
|
2193
|
+
};
|
|
1981
2194
|
return rpc;
|
|
1982
2195
|
}
|
|
1983
2196
|
//#endregion
|
|
1984
2197
|
//#region src/client/index.ts
|
|
1985
2198
|
const connectDevframe = getDevframeRpcClient;
|
|
1986
2199
|
//#endregion
|
|
1987
|
-
export { connectDevframe, createRpcStreamingClientHost, getDevframeRpcClient };
|
|
2200
|
+
export { authenticateWithUrlOtp, connectDevframe, consumeOtpFromUrl, createClientSettings, createRpcStreamingClientHost, createScopedClientContext, getDevframeRpcClient, readOtpFromUrl };
|
package/dist/constants.d.mts
CHANGED
|
@@ -3,6 +3,14 @@ declare const DEVFRAME_MOUNT_PATH = "/__devframe/";
|
|
|
3
3
|
declare const DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH = "/__devframe";
|
|
4
4
|
declare const DEVFRAME_DIRNAME = "__devframe";
|
|
5
5
|
declare const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
|
|
6
|
+
/**
|
|
7
|
+
* Route the WebSocket RPC endpoint is bound to, relative to a devframe's
|
|
8
|
+
* base path. Sits next to `__connection.json` so the deployed SPA can reach
|
|
9
|
+
* it on the same origin it loaded from — the dev server shares one port for
|
|
10
|
+
* both HTTP and WS, and a host server (Vite, etc.) can mount the WS upgrade
|
|
11
|
+
* handler here without colliding with its own routes (HMR, asset serving).
|
|
12
|
+
*/
|
|
13
|
+
declare const DEVFRAME_WS_ROUTE = "__devframe_ws";
|
|
6
14
|
declare const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
|
|
7
15
|
declare const DEVFRAME_DOCK_IMPORTS_FILENAME = "__client-imports.js";
|
|
8
16
|
declare const DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID = "/__devframe-client-imports.js";
|
|
@@ -13,5 +21,13 @@ declare const DEVFRAME_RPC_DUMP_DIRNAME = "__rpc-dump";
|
|
|
13
21
|
* `@vitejs/devtools-kit`) injected into remote-UI iframe dock URLs.
|
|
14
22
|
*/
|
|
15
23
|
declare const REMOTE_CONNECTION_KEY = "devframe-remote-connection";
|
|
24
|
+
/**
|
|
25
|
+
* Page-URL query parameter carrying a one-time authentication code (OTP) for
|
|
26
|
+
* "magic link" auth. A host can print a link like `<origin>/?devframe_otp=<code>`;
|
|
27
|
+
* the client reads the code, exchanges it for a token, and strips the parameter
|
|
28
|
+
* from the URL. See `buildOtpAuthUrl` (node) and the `authenticateWithUrlOtp` /
|
|
29
|
+
* `consumeOtpFromUrl` client utilities (or `connectDevframe`'s `otpParam`).
|
|
30
|
+
*/
|
|
31
|
+
declare const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
|
|
16
32
|
//#endregion
|
|
17
|
-
export { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, REMOTE_CONNECTION_KEY };
|
|
33
|
+
export { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY };
|
package/dist/constants.mjs
CHANGED
|
@@ -3,6 +3,14 @@ const DEVFRAME_MOUNT_PATH = "/__devframe/";
|
|
|
3
3
|
const DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH = "/__devframe";
|
|
4
4
|
const DEVFRAME_DIRNAME = "__devframe";
|
|
5
5
|
const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
|
|
6
|
+
/**
|
|
7
|
+
* Route the WebSocket RPC endpoint is bound to, relative to a devframe's
|
|
8
|
+
* base path. Sits next to `__connection.json` so the deployed SPA can reach
|
|
9
|
+
* it on the same origin it loaded from — the dev server shares one port for
|
|
10
|
+
* both HTTP and WS, and a host server (Vite, etc.) can mount the WS upgrade
|
|
11
|
+
* handler here without colliding with its own routes (HMR, asset serving).
|
|
12
|
+
*/
|
|
13
|
+
const DEVFRAME_WS_ROUTE = "__devframe_ws";
|
|
6
14
|
const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
|
|
7
15
|
const DEVFRAME_DOCK_IMPORTS_FILENAME = "__client-imports.js";
|
|
8
16
|
const DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID = "/__devframe-client-imports.js";
|
|
@@ -13,5 +21,13 @@ const DEVFRAME_RPC_DUMP_DIRNAME = "__rpc-dump";
|
|
|
13
21
|
* `@vitejs/devtools-kit`) injected into remote-UI iframe dock URLs.
|
|
14
22
|
*/
|
|
15
23
|
const REMOTE_CONNECTION_KEY = "devframe-remote-connection";
|
|
24
|
+
/**
|
|
25
|
+
* Page-URL query parameter carrying a one-time authentication code (OTP) for
|
|
26
|
+
* "magic link" auth. A host can print a link like `<origin>/?devframe_otp=<code>`;
|
|
27
|
+
* the client reads the code, exchanges it for a token, and strips the parameter
|
|
28
|
+
* from the URL. See `buildOtpAuthUrl` (node) and the `authenticateWithUrlOtp` /
|
|
29
|
+
* `consumeOtpFromUrl` client utilities (or `connectDevframe`'s `otpParam`).
|
|
30
|
+
*/
|
|
31
|
+
const DEVFRAME_OTP_URL_PARAM = "devframe_otp";
|
|
16
32
|
//#endregion
|
|
17
|
-
export { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, REMOTE_CONNECTION_KEY };
|
|
33
|
+
export { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DIRNAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_DOCK_IMPORTS_VIRTUAL_ID, DEVFRAME_MOUNT_PATH, DEVFRAME_MOUNT_PATH_NO_TRAILING_SLASH, DEVFRAME_OTP_URL_PARAM, DEVFRAME_RPC_DUMP_DIRNAME, DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, DEVFRAME_WS_ROUTE, REMOTE_CONNECTION_KEY };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { t as createStorage } from "./storage-l2ezeend.mjs";
|
|
2
|
+
import { i as randomToken, n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "./revoke-ENfxWkFK.mjs";
|
|
3
|
+
import { join } from "pathe";
|
|
4
|
+
//#region src/node/hub-internals/context.ts
|
|
5
|
+
const internalContextMap = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function getInternalContext(context) {
|
|
7
|
+
if (!internalContextMap.has(context)) {
|
|
8
|
+
const storage = createStorage({
|
|
9
|
+
filepath: join(context.host.getStorageDir("global"), "auth.json"),
|
|
10
|
+
initialValue: { trusted: {} }
|
|
11
|
+
});
|
|
12
|
+
const remoteTokens = /* @__PURE__ */ new Map();
|
|
13
|
+
function revokeRemoteToken(token) {
|
|
14
|
+
if (!remoteTokens.delete(token)) return;
|
|
15
|
+
revokeActiveConnectionsForToken(context, token);
|
|
16
|
+
}
|
|
17
|
+
const internalContext = {
|
|
18
|
+
storage: { auth: storage },
|
|
19
|
+
revokeAuthToken: (token) => revokeAuthToken(context, storage, token),
|
|
20
|
+
remoteTokens,
|
|
21
|
+
allocateRemoteToken(dockId, origin, originLock) {
|
|
22
|
+
const token = randomToken();
|
|
23
|
+
remoteTokens.set(token, {
|
|
24
|
+
dockId,
|
|
25
|
+
origin,
|
|
26
|
+
originLock
|
|
27
|
+
});
|
|
28
|
+
return token;
|
|
29
|
+
},
|
|
30
|
+
revokeRemoteToken,
|
|
31
|
+
revokeRemoteTokensForDock(dockId) {
|
|
32
|
+
const tokensToRevoke = [];
|
|
33
|
+
for (const [token, record] of remoteTokens) if (record.dockId === dockId) tokensToRevoke.push(token);
|
|
34
|
+
for (const token of tokensToRevoke) revokeRemoteToken(token);
|
|
35
|
+
},
|
|
36
|
+
isRemoteTokenTrusted(token, requestOrigin) {
|
|
37
|
+
const record = remoteTokens.get(token);
|
|
38
|
+
if (!record) return false;
|
|
39
|
+
if (!record.originLock) return true;
|
|
40
|
+
return !!requestOrigin && record.origin === requestOrigin;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
internalContextMap.set(context, internalContext);
|
|
44
|
+
}
|
|
45
|
+
return internalContextMap.get(context);
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { internalContextMap as n, getInternalContext as t };
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs";
|
|
2
|
-
import { n as createHostContext, t as createH3DevframeHost } from "./host-h3-
|
|
3
|
-
import { t as startHttpAndWs } from "./server-
|
|
4
|
-
import { n as resolveBasePath, t as normalizeBasePath } from "./_shared-
|
|
5
|
-
import { t as open } from "./open-
|
|
2
|
+
import { n as createHostContext, t as createH3DevframeHost } from "./host-h3-C5SZlk03.mjs";
|
|
3
|
+
import { t as startHttpAndWs } from "./server-wyY3zh67.mjs";
|
|
4
|
+
import { n as resolveBasePath, t as normalizeBasePath } from "./_shared-bWRzeSa0.mjs";
|
|
5
|
+
import { t as open } from "./open-Dusa2Zzd.mjs";
|
|
6
6
|
import { mountStaticHandler } from "./utils/serve-static.mjs";
|
|
7
|
+
import { resolve } from "pathe";
|
|
7
8
|
import { networkInterfaces } from "node:os";
|
|
8
9
|
import process$1 from "node:process";
|
|
9
|
-
import { resolve } from "pathe";
|
|
10
10
|
import { H3 } from "h3";
|
|
11
11
|
import { createServer } from "node:net";
|
|
12
|
+
import { joinURL, withBase as withBase$1, withLeadingSlash, withoutLeadingSlash } from "ufo";
|
|
12
13
|
//#region ../../node_modules/.pnpm/get-port-please@3.2.0/node_modules/get-port-please/dist/index.mjs
|
|
13
14
|
const unsafePorts = /* @__PURE__ */ new Set([
|
|
14
15
|
1,
|
|
@@ -283,10 +284,11 @@ async function createDevServer(def, options = {}) {
|
|
|
283
284
|
});
|
|
284
285
|
const setupInfo = { flags };
|
|
285
286
|
await def.setup(ctx, setupInfo);
|
|
286
|
-
const
|
|
287
|
+
const { bindPath, wsPort, meta } = resolveWsConnection(def, options, basePath);
|
|
288
|
+
const connectionMetaPath = joinURL(basePath, DEVFRAME_CONNECTION_META_FILENAME);
|
|
287
289
|
app.use(connectionMetaPath, () => ({
|
|
288
290
|
backend: "websocket",
|
|
289
|
-
websocket:
|
|
291
|
+
websocket: meta
|
|
290
292
|
}));
|
|
291
293
|
if (distDir) mountStaticHandler(app, basePath, resolve(distDir));
|
|
292
294
|
return startHttpAndWs({
|
|
@@ -294,6 +296,8 @@ async function createDevServer(def, options = {}) {
|
|
|
294
296
|
host,
|
|
295
297
|
port,
|
|
296
298
|
app,
|
|
299
|
+
path: bindPath,
|
|
300
|
+
wsPort,
|
|
297
301
|
auth: def.cli?.auth,
|
|
298
302
|
onReady: async (info) => {
|
|
299
303
|
await options.onReady?.(info);
|
|
@@ -301,20 +305,42 @@ async function createDevServer(def, options = {}) {
|
|
|
301
305
|
}
|
|
302
306
|
});
|
|
303
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Resolve the three WS connection scenarios from the definition / call-site
|
|
310
|
+
* config into a concrete server bind path, optional dedicated port, and the
|
|
311
|
+
* `__connection.json` descriptor the browser resolves.
|
|
312
|
+
*/
|
|
313
|
+
function resolveWsConnection(def, options, basePath) {
|
|
314
|
+
const ws = options.ws ?? def.cli?.ws ?? {};
|
|
315
|
+
const route = withoutLeadingSlash(ws.route ?? "__devframe_ws");
|
|
316
|
+
if (ws.url) return {
|
|
317
|
+
bindPath: joinURL(basePath, route),
|
|
318
|
+
wsPort: void 0,
|
|
319
|
+
meta: ws.url
|
|
320
|
+
};
|
|
321
|
+
if (ws.port != null) return {
|
|
322
|
+
bindPath: withLeadingSlash(route),
|
|
323
|
+
wsPort: ws.port,
|
|
324
|
+
meta: {
|
|
325
|
+
port: ws.port,
|
|
326
|
+
path: route
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
return {
|
|
330
|
+
bindPath: joinURL(basePath, route),
|
|
331
|
+
wsPort: void 0,
|
|
332
|
+
meta: { path: route }
|
|
333
|
+
};
|
|
334
|
+
}
|
|
304
335
|
async function maybeOpenBrowser(def, flags, origin, override) {
|
|
305
336
|
const flagsOpen = flags.open;
|
|
306
337
|
const cliOpen = def.cli?.open;
|
|
307
338
|
const resolved = override ?? flagsOpen ?? cliOpen;
|
|
308
339
|
if (resolved === void 0 || resolved === false) return;
|
|
309
|
-
const target = typeof resolved === "string" ?
|
|
340
|
+
const target = typeof resolved === "string" ? withBase$1(resolved, origin) : origin;
|
|
310
341
|
try {
|
|
311
342
|
await open(target);
|
|
312
343
|
} catch {}
|
|
313
344
|
}
|
|
314
|
-
function resolveOpenTarget(origin, target) {
|
|
315
|
-
if (/^https?:/.test(target)) return target;
|
|
316
|
-
if (target.startsWith("/")) return origin.replace(/\/$/, "") + target;
|
|
317
|
-
return origin.replace(/\/$/, "") + (target ? `/${target}` : "");
|
|
318
|
-
}
|
|
319
345
|
//#endregion
|
|
320
346
|
export { resolveDevServerPort as n, createDevServer as t };
|