devframe 0.6.0-beta.1 → 0.6.0-beta.3

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.
Files changed (62) hide show
  1. package/dist/adapters/build.d.mts +1 -1
  2. package/dist/adapters/build.mjs +2 -2
  3. package/dist/adapters/cli.d.mts +1 -1
  4. package/dist/adapters/cli.mjs +1 -1
  5. package/dist/adapters/dev.d.mts +2 -2
  6. package/dist/adapters/dev.mjs +1 -1
  7. package/dist/adapters/embedded.d.mts +1 -1
  8. package/dist/adapters/mcp.d.mts +1 -1
  9. package/dist/adapters/mcp.mjs +3 -3
  10. package/dist/client/index.d.mts +2 -2
  11. package/dist/client/index.mjs +56 -22
  12. package/dist/constants.d.mts +24 -1
  13. package/dist/constants.mjs +26 -1
  14. package/dist/{context-o-TwncyP.d.mts → context-B_I9fMBc.d.mts} +1 -1
  15. package/dist/{context-3x_bgBgZ.mjs → context-CKJheEtf.mjs} +2 -2
  16. package/dist/{dev-CSpLSEVh.mjs → dev-DSxKFn-v.mjs} +4 -4
  17. package/dist/{devframe-BwfavB78.d.mts → devframe-DgvncQy2.d.mts} +20 -4
  18. package/dist/{dump-DoXkj4ku.mjs → dump-D09PZb7T.mjs} +14 -4
  19. package/dist/{hash-DFc5WwhO.mjs → hash-PAfnyu2k.mjs} +1 -1
  20. package/dist/helpers/vite.d.mts +1 -1
  21. package/dist/helpers/vite.mjs +2 -2
  22. package/dist/{host-h3-C5SZlk03.mjs → host-h3-Dsf-Tgwx.mjs} +37 -11
  23. package/dist/index-B4lNDq5v.d.mts +107 -0
  24. package/dist/{index-CxaPKDXX.d.mts → index-Dy6GFDRf.d.mts} +1 -0
  25. package/dist/index.d.mts +3 -3
  26. package/dist/{launch-editor-CgXBgviI.mjs → launch-editor-DIzaS5EG.mjs} +146 -56
  27. package/dist/node/auth.d.mts +2 -64
  28. package/dist/node/auth.mjs +2 -100
  29. package/dist/node/hub-internals.d.mts +2 -2
  30. package/dist/node/hub-internals.mjs +1 -1
  31. package/dist/node/index.d.mts +4 -4
  32. package/dist/node/index.mjs +3 -3
  33. package/dist/recipes/interactive-auth.d.mts +53 -0
  34. package/dist/recipes/interactive-auth.mjs +132 -0
  35. package/dist/recipes/open-helpers.mjs +2 -2
  36. package/dist/{revoke-ENfxWkFK.mjs → revoke-BtQDKTp7.mjs} +1 -1
  37. package/dist/rpc/dump.mjs +1 -1
  38. package/dist/rpc/index.d.mts +2 -2
  39. package/dist/rpc/index.mjs +4 -1
  40. package/dist/rpc/transports/ws-client.mjs +21 -8
  41. package/dist/rpc/transports/ws-server.d.mts +2 -2
  42. package/dist/rpc/transports/ws-server.mjs +36 -7
  43. package/dist/{server-wyY3zh67.mjs → server-BdN41ckC.mjs} +37 -4
  44. package/dist/server-DXPq8q0K.d.mts +121 -0
  45. package/dist/{shared-state-D4PPieA0.mjs → shared-state-QtMas0NG.mjs} +34 -4
  46. package/dist/state-WX3HT5a3.mjs +101 -0
  47. package/dist/{storage-l2ezeend.mjs → storage-yrUZaiib.mjs} +55 -6
  48. package/dist/types/index.d.mts +3 -3
  49. package/dist/utils/crypto-token.mjs +1 -1
  50. package/dist/utils/events.d.mts +1 -1
  51. package/dist/utils/hash.mjs +1 -1
  52. package/dist/utils/launch-editor.mjs +1 -1
  53. package/dist/utils/open.mjs +1 -1
  54. package/dist/utils/serve-static.mjs +2 -1
  55. package/dist/utils/shared-state.d.mts +1 -1
  56. package/dist/utils/shared-state.mjs +1 -1
  57. package/dist/utils/streaming-channel.d.mts +1 -1
  58. package/dist/utils/when.d.mts +1 -1
  59. package/dist/{ws-server-BVVMDo2n.d.mts → ws-server-Bc2wBHl-.d.mts} +17 -1
  60. package/package.json +10 -9
  61. package/dist/server-oju7PTi2.d.mts +0 -82
  62. package/dist/{open-Dusa2Zzd.mjs → open-Deb5xmIT.mjs} +1 -1
@@ -1,101 +1,3 @@
1
- import { DEVFRAME_OTP_URL_PARAM } from "../constants.mjs";
2
- import { a as timingSafeEqual, i as randomToken, n as revokeAuthToken, r as randomDigits, t as revokeActiveConnectionsForToken } from "../revoke-ENfxWkFK.mjs";
3
- //#region src/node/auth/state.ts
4
- /** Number of decimal digits in a human-typed one-time authentication code. */
5
- const TEMP_AUTH_CODE_LENGTH = 6;
6
- /**
7
- * How long an authentication code stays valid after it is (re)generated. A
8
- * 6-digit code only has ~20 bits of entropy, so a short lifetime plus the
9
- * attempt cap below are what keep it brute-force resistant.
10
- */
11
- const TEMP_AUTH_CODE_TTL = 5 * 6e4;
12
- /** Failed attempts allowed against a single code before it is rotated. */
13
- const TEMP_AUTH_MAX_ATTEMPTS = 5;
14
- let tempAuthCode = generateTempCode();
15
- let tempAuthCodeExpiresAt = Date.now() + TEMP_AUTH_CODE_TTL;
16
- let tempAuthFailedAttempts = 0;
17
- function generateTempCode() {
18
- return randomDigits(TEMP_AUTH_CODE_LENGTH);
19
- }
20
- /**
21
- * The current one-time authentication code. Display this to the user (e.g. in
22
- * the dev-server terminal) so they can type it into the browser to authenticate.
23
- */
24
- function getTempAuthCode() {
25
- return tempAuthCode;
26
- }
27
- /**
28
- * Rotate the authentication code, resetting its expiry window and failed-attempt
29
- * counter. Call this when a new authentication flow begins (e.g. when an
30
- * untrusted client starts authenticating) so the displayed code is freshly
31
- * valid for its full TTL.
32
- */
33
- function refreshTempAuthCode() {
34
- tempAuthCode = generateTempCode();
35
- tempAuthCodeExpiresAt = Date.now() + TEMP_AUTH_CODE_TTL;
36
- tempAuthFailedAttempts = 0;
37
- return tempAuthCode;
38
- }
39
- /**
40
- * Build a "magic link" authentication URL that embeds a one-time code (OTP) as
41
- * a query parameter. Opening it authenticates the client without typing — print
42
- * it on startup (devframe stays headless, so the host prints its own banner).
43
- * Defaults to the current code; the link is subject to the same TTL.
44
- */
45
- function buildOtpAuthUrl(baseUrl, code = tempAuthCode) {
46
- const url = new URL(baseUrl);
47
- url.searchParams.set(DEVFRAME_OTP_URL_PARAM, code);
48
- return url.href;
49
- }
50
- /**
51
- * Re-authenticate a connection that presents a previously-issued bearer token.
52
- * Returns `true` and marks the session trusted when the token is known.
53
- *
54
- * Used by the `devframe:anonymous:auth` handler so a client that already
55
- * authenticated (token persisted in the browser) is trusted on reconnect
56
- * without entering the code again.
57
- */
58
- function verifyAuthToken(token, session, storage) {
59
- if (!token || !storage.value().trusted[token]) return false;
60
- session.meta.clientAuthToken = token;
61
- session.meta.isTrusted = true;
62
- return true;
63
- }
64
- /**
65
- * Exchange a one-time authentication code for a fresh, node-issued bearer token.
66
- *
67
- * On success this mints a high-entropy token, records it in the trusted store,
68
- * marks the calling session trusted, rotates the code, and returns the token
69
- * for the client to persist. Returns `null` on any failure.
70
- *
71
- * Because the code is short and human-typed, verification is hardened against
72
- * brute force: it enforces a time-to-live, compares in constant time, and
73
- * rotates the code after {@link TEMP_AUTH_MAX_ATTEMPTS} failed attempts so an
74
- * attacker cannot keep guessing against the same code.
75
- */
76
- function exchangeTempAuthCode(code, session, info, storage) {
77
- if (Date.now() > tempAuthCodeExpiresAt) {
78
- refreshTempAuthCode();
79
- return null;
80
- }
81
- if (!timingSafeEqual(code, tempAuthCode)) {
82
- tempAuthFailedAttempts += 1;
83
- if (tempAuthFailedAttempts >= TEMP_AUTH_MAX_ATTEMPTS) refreshTempAuthCode();
84
- return null;
85
- }
86
- const authToken = randomToken();
87
- storage.mutate((state) => {
88
- state.trusted[authToken] = {
89
- authToken,
90
- ua: info.ua,
91
- origin: info.origin,
92
- timestamp: Date.now()
93
- };
94
- });
95
- session.meta.clientAuthToken = authToken;
96
- session.meta.isTrusted = true;
97
- refreshTempAuthCode();
98
- return authToken;
99
- }
100
- //#endregion
1
+ import { n as revokeAuthToken, t as revokeActiveConnectionsForToken } from "../revoke-BtQDKTp7.mjs";
2
+ import { a as verifyAuthToken, i as refreshTempAuthCode, n as exchangeTempAuthCode, r as getTempAuthCode, t as buildOtpAuthUrl } from "../state-WX3HT5a3.mjs";
101
3
  export { buildOtpAuthUrl, exchangeTempAuthCode, getTempAuthCode, refreshTempAuthCode, revokeActiveConnectionsForToken, revokeAuthToken, verifyAuthToken };
@@ -1,5 +1,5 @@
1
- import { i as DevframeDeploymentKind, r as DevframeDefinition } from "../devframe-BwfavB78.mjs";
2
- import { a as internalContextMap, i as getInternalContext, n as InternalAnonymousAuthStorage, r as RemoteTokenRecord, t as DevframeInternalContext } from "../context-o-TwncyP.mjs";
1
+ import { i as DevframeDeploymentKind, r as DevframeDefinition } from "../devframe-DgvncQy2.mjs";
2
+ import { a as internalContextMap, i as getInternalContext, n as InternalAnonymousAuthStorage, r as RemoteTokenRecord, t as DevframeInternalContext } from "../context-B_I9fMBc.mjs";
3
3
 
4
4
  //#region src/adapters/_shared.d.ts
5
5
  /**
@@ -1,3 +1,3 @@
1
- import { n as internalContextMap, t as getInternalContext } from "../context-3x_bgBgZ.mjs";
1
+ import { n as internalContextMap, t as getInternalContext } from "../context-CKJheEtf.mjs";
2
2
  import { n as resolveBasePath, t as normalizeBasePath } from "../_shared-bWRzeSa0.mjs";
3
3
  export { getInternalContext, internalContextMap, normalizeBasePath, resolveBasePath };
@@ -1,8 +1,8 @@
1
- import { G as DevframeScopedNodeContext, J as DevframeSettings, L as SharedState, S as RpcStreamingHost, _ as RpcFunctionsHost$1, _t as AgentToolInput, bt as EventEmitter, dt as AgentHandle, ft as AgentManifest, g as RpcBroadcastOptions, gt as AgentTool, h as DevframeNodeRpcSession, ht as AgentResourceInput, it as DevframeRpcServerFunctions, lt as DevframeDiagnosticsHost$1, m as DevframeNodeContext, mt as AgentResourceContent, nt as DevframeViewHost$1, ot as DevframeHost, pt as AgentResource, rt as DevframeRpcClientFunctions, ut as DevframeDiagnosticsLogger, vt as DevframeAgentHost$1, y as RpcSharedStateHost, yt as DevframeAgentHostEvents } from "../devframe-BwfavB78.mjs";
1
+ import { G as DevframeScopedNodeContext, J as DevframeSettings, L as SharedState, S as RpcStreamingHost, _ as RpcFunctionsHost$1, _t as AgentToolInput, bt as EventEmitter, dt as AgentHandle, ft as AgentManifest, g as RpcBroadcastOptions, gt as AgentTool, h as DevframeNodeRpcSession, ht as AgentResourceInput, it as DevframeRpcServerFunctions, lt as DevframeDiagnosticsHost$1, m as DevframeNodeContext, mt as AgentResourceContent, nt as DevframeViewHost$1, ot as DevframeHost, pt as AgentResource, rt as DevframeRpcClientFunctions, ut as DevframeDiagnosticsLogger, vt as DevframeAgentHost$1, y as RpcSharedStateHost, yt as DevframeAgentHostEvents } from "../devframe-DgvncQy2.mjs";
2
2
  import { v as RpcFunctionDefinitionAny } from "../types-DZEx4ffs.mjs";
3
- import { S as RpcFunctionsCollectorBase } from "../index-CxaPKDXX.mjs";
4
- import { t as DevframeNodeRpcSessionMeta } from "../ws-server-BVVMDo2n.mjs";
5
- import { n as StartedServer, r as startHttpAndWs, t as StartHttpAndWsOptions } from "../server-oju7PTi2.mjs";
3
+ import { S as RpcFunctionsCollectorBase } from "../index-Dy6GFDRf.mjs";
4
+ import { t as DevframeNodeRpcSessionMeta } from "../ws-server-Bc2wBHl-.mjs";
5
+ import { n as StartedServer, r as startHttpAndWs, t as StartHttpAndWsOptions } from "../server-DXPq8q0K.mjs";
6
6
  import { BirpcGroup } from "birpc";
7
7
  import { AsyncLocalStorage } from "node:async_hooks";
8
8
 
@@ -1,6 +1,6 @@
1
- import { t as createStorage } from "../storage-l2ezeend.mjs";
2
- import { a as DevframeViewHost, c as createRpcSharedStateServerHost, i as createNodeSettings, l as DevframeDiagnosticsHost, n as createHostContext, o as RpcFunctionsHost, r as createScopedNodeContext, s as createRpcStreamingServerHost, t as createH3DevframeHost, u as DevframeAgentHost } from "../host-h3-C5SZlk03.mjs";
3
- import { t as startHttpAndWs } from "../server-wyY3zh67.mjs";
1
+ import { t as createStorage } from "../storage-yrUZaiib.mjs";
2
+ import { a as DevframeViewHost, c as createRpcSharedStateServerHost, i as createNodeSettings, l as DevframeDiagnosticsHost, n as createHostContext, o as RpcFunctionsHost, r as createScopedNodeContext, s as createRpcStreamingServerHost, t as createH3DevframeHost, u as DevframeAgentHost } from "../host-h3-Dsf-Tgwx.mjs";
3
+ import { t as startHttpAndWs } from "../server-BdN41ckC.mjs";
4
4
  import { isIP } from "node:net";
5
5
  //#region src/node/utils.ts
6
6
  function isObject(value) {
@@ -0,0 +1,53 @@
1
+ import { m as DevframeNodeContext } from "../devframe-DgvncQy2.mjs";
2
+ import { c as DevframeAuthHandler } from "../index-B4lNDq5v.mjs";
3
+
4
+ //#region src/recipes/interactive-auth.d.ts
5
+ interface CreateInteractiveAuthOptions {
6
+ /**
7
+ * Static, pre-shared bearer tokens that are always trusted — for CI runs
8
+ * or shared machines where the interactive code prompt would only get in
9
+ * the way. Checked in both the handshake handler and the connect-time
10
+ * hook, alongside tokens minted by a real code exchange.
11
+ */
12
+ clientAuthTokens?: string[];
13
+ /**
14
+ * Print the current code + magic-link URL. Devframe stays headless, so
15
+ * there is no default banner printed automatically — call
16
+ * `auth.printBanner()` yourself once the server is listening. Override
17
+ * this to customize the format; defaults to a small boxed message on
18
+ * stdout.
19
+ */
20
+ banner?: (info: {
21
+ code: string;
22
+ url: string;
23
+ }) => void;
24
+ /**
25
+ * The base URL the magic link should point at. Defaults to
26
+ * `context.host.resolveOrigin()`.
27
+ */
28
+ serverUrl?: () => string;
29
+ }
30
+ /**
31
+ * Package the interactive OTP auth protocol devframe's primitives
32
+ * (`exchangeTempAuthCode`, `verifyAuthToken`, `revokeAuthToken`,
33
+ * `getTempAuthCode`, `buildOtpAuthUrl`) implement into a ready-made
34
+ * {@link DevframeAuthHandler} — the handshake RPC functions, the resolver
35
+ * gate, the connect-time trust hook, and the startup banner.
36
+ *
37
+ * The auth storage stays internal to this handler — callers never reach into
38
+ * `devframe/node/hub-internals` themselves.
39
+ *
40
+ * ```ts
41
+ * import { createInteractiveAuth } from 'devframe/recipes/interactive-auth'
42
+ *
43
+ * const auth = createInteractiveAuth(ctx)
44
+ * auth.rpcFunctions.forEach(fn => ctx.rpc.register(fn))
45
+ * auth.printBanner()
46
+ *
47
+ * // wire `auth.authorize` / `auth.onConnect` into your transport, or pass
48
+ * // the whole handler to `startHttpAndWs({ auth, ... })`.
49
+ * ```
50
+ */
51
+ declare function createInteractiveAuth(context: DevframeNodeContext, options?: CreateInteractiveAuthOptions): DevframeAuthHandler;
52
+ //#endregion
53
+ export { CreateInteractiveAuthOptions, createInteractiveAuth };
@@ -0,0 +1,132 @@
1
+ import { n as colors } from "../diagnostics-reporter-CsIG85Q5.mjs";
2
+ import { isAnonymousRpcMethod } from "../constants.mjs";
3
+ import { n as defineRpcFunction } from "../define-BLWPsH6y.mjs";
4
+ import { t as getInternalContext } from "../context-CKJheEtf.mjs";
5
+ import { a as verifyAuthToken, n as exchangeTempAuthCode, r as getTempAuthCode, t as buildOtpAuthUrl } from "../state-WX3HT5a3.mjs";
6
+ import * as v from "valibot";
7
+ //#region src/recipes/interactive-auth.ts
8
+ function defaultBanner(info) {
9
+ console.log(`\n ${colors.dim("devframe auth code")} ${colors.bold(info.code)}\n ${colors.dim("or open")} ${colors.cyan(info.url)}\n`);
10
+ }
11
+ /**
12
+ * Package the interactive OTP auth protocol devframe's primitives
13
+ * (`exchangeTempAuthCode`, `verifyAuthToken`, `revokeAuthToken`,
14
+ * `getTempAuthCode`, `buildOtpAuthUrl`) implement into a ready-made
15
+ * {@link DevframeAuthHandler} — the handshake RPC functions, the resolver
16
+ * gate, the connect-time trust hook, and the startup banner.
17
+ *
18
+ * The auth storage stays internal to this handler — callers never reach into
19
+ * `devframe/node/hub-internals` themselves.
20
+ *
21
+ * ```ts
22
+ * import { createInteractiveAuth } from 'devframe/recipes/interactive-auth'
23
+ *
24
+ * const auth = createInteractiveAuth(ctx)
25
+ * auth.rpcFunctions.forEach(fn => ctx.rpc.register(fn))
26
+ * auth.printBanner()
27
+ *
28
+ * // wire `auth.authorize` / `auth.onConnect` into your transport, or pass
29
+ * // the whole handler to `startHttpAndWs({ auth, ... })`.
30
+ * ```
31
+ */
32
+ function createInteractiveAuth(context, options = {}) {
33
+ const internal = getInternalContext(context);
34
+ const storage = internal.storage.auth;
35
+ const staticTokens = new Set(options.clientAuthTokens ?? []);
36
+ function isStaticToken(token) {
37
+ return !!token && staticTokens.has(token);
38
+ }
39
+ function resolveServerUrl() {
40
+ return options.serverUrl?.() ?? context.host.resolveOrigin();
41
+ }
42
+ let bannerPrintedForCode;
43
+ function printBanner() {
44
+ const code = getTempAuthCode();
45
+ if (code === bannerPrintedForCode) return;
46
+ bannerPrintedForCode = code;
47
+ const url = buildOtpAuthUrl(resolveServerUrl(), code);
48
+ (options.banner ?? defaultBanner)({
49
+ code,
50
+ url
51
+ });
52
+ }
53
+ const anonymousAuth = defineRpcFunction({
54
+ name: "anonymous:devframe:auth",
55
+ type: "action",
56
+ jsonSerializable: true,
57
+ args: [v.object({
58
+ authToken: v.string(),
59
+ ua: v.string(),
60
+ origin: v.string()
61
+ })],
62
+ returns: v.object({ isTrusted: v.boolean() }),
63
+ handler(params) {
64
+ const session = context.rpc.getCurrentRpcSession();
65
+ if (!session) return { isTrusted: false };
66
+ if (isStaticToken(params.authToken)) {
67
+ session.meta.clientAuthToken = params.authToken;
68
+ session.meta.isTrusted = true;
69
+ return { isTrusted: true };
70
+ }
71
+ return { isTrusted: verifyAuthToken(params.authToken, session, storage) };
72
+ }
73
+ });
74
+ const anonymousAuthExchange = defineRpcFunction({
75
+ name: "anonymous:devframe:auth:exchange",
76
+ type: "action",
77
+ jsonSerializable: true,
78
+ args: [v.object({
79
+ code: v.string(),
80
+ ua: v.string(),
81
+ origin: v.string()
82
+ })],
83
+ returns: v.object({ authToken: v.nullable(v.string()) }),
84
+ handler(params) {
85
+ const session = context.rpc.getCurrentRpcSession();
86
+ if (!session) return { authToken: null };
87
+ const authToken = exchangeTempAuthCode(params.code, session, params, storage);
88
+ printBanner();
89
+ return { authToken };
90
+ }
91
+ });
92
+ const revoke = defineRpcFunction({
93
+ name: "devframe:auth:revoke",
94
+ type: "action",
95
+ jsonSerializable: true,
96
+ args: [],
97
+ returns: v.void(),
98
+ async handler() {
99
+ const token = context.rpc.getCurrentRpcSession()?.meta.clientAuthToken;
100
+ if (token) await internal.revokeAuthToken(token);
101
+ }
102
+ });
103
+ function authorize(methodName, session) {
104
+ if (isAnonymousRpcMethod(methodName)) return true;
105
+ return !!session.meta.isTrusted;
106
+ }
107
+ function onConnect(peer, session) {
108
+ let token;
109
+ try {
110
+ token = new URL(peer.request?.url ?? "", "http://localhost").searchParams.get("devframe_auth_token") ?? void 0;
111
+ } catch {}
112
+ if (!token) return;
113
+ if (isStaticToken(token)) {
114
+ session.meta.clientAuthToken = token;
115
+ session.meta.isTrusted = true;
116
+ return;
117
+ }
118
+ verifyAuthToken(token, session, storage);
119
+ }
120
+ return {
121
+ rpcFunctions: [
122
+ anonymousAuth,
123
+ anonymousAuthExchange,
124
+ revoke
125
+ ],
126
+ authorize,
127
+ onConnect,
128
+ printBanner
129
+ };
130
+ }
131
+ //#endregion
132
+ export { createInteractiveAuth };
@@ -1,6 +1,6 @@
1
- import { t as launchEditor } from "../launch-editor-CgXBgviI.mjs";
1
+ import { t as launchEditor } from "../launch-editor-DIzaS5EG.mjs";
2
2
  import { n as defineRpcFunction } from "../define-BLWPsH6y.mjs";
3
- import { t as open } from "../open-Dusa2Zzd.mjs";
3
+ import { t as open } from "../open-Deb5xmIT.mjs";
4
4
  import * as v from "valibot";
5
5
  //#region src/recipes/open-helpers.ts
6
6
  /**
@@ -19,7 +19,7 @@ function randomToken(byteLength = 16) {
19
19
  */
20
20
  function randomDigits(length) {
21
21
  const limit = 250;
22
- const buf = new Uint8Array(1);
22
+ const buf = /* @__PURE__ */ new Uint8Array(1);
23
23
  let out = "";
24
24
  while (out.length < length) {
25
25
  globalThis.crypto.getRandomValues(buf);
package/dist/rpc/dump.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as dumpFunctions, c as serializeDumpError, i as createClientFromDump, o as getDefinitionsWithDumps, s as reviveDumpError, t as collectStaticRpcDump } from "../dump-DoXkj4ku.mjs";
1
+ import { a as dumpFunctions, c as serializeDumpError, i as createClientFromDump, o as getDefinitionsWithDumps, s as reviveDumpError, t as collectStaticRpcDump } from "../dump-D09PZb7T.mjs";
2
2
  export { collectStaticRpcDump, createClientFromDump, dumpFunctions, getDefinitionsWithDumps, reviveDumpError, serializeDumpError };
@@ -1,3 +1,3 @@
1
1
  import { C as RpcFunctionType, E as Thenable, S as RpcFunctionSetupResult, T as RpcReturnSchema, _ as RpcFunctionDefinition, a as RpcDefinitionsFilter, b as RpcFunctionDefinitionBase, c as RpcDump, d as RpcDumpDefinition, f as RpcDumpGetter, g as RpcFunctionAgentOptions, h as RpcDumpStore, i as RpcArgsSchema, l as RpcDumpClientOptions, m as RpcDumpRecordError, n as BirpcReturn, o as RpcDefinitionsToFunctions, p as RpcDumpRecord, r as EntriesToObject, s as RpcDefinitionsToFunctionsWithNamespace, t as BirpcFn, u as RpcDumpCollectionOptions, v as RpcFunctionDefinitionAny, w as RpcFunctionsCollector, x as RpcFunctionDefinitionToFunction, y as RpcFunctionDefinitionAnyWithContext } from "../types-DZEx4ffs.mjs";
2
- import { C as RpcCacheManager, S as RpcFunctionsCollectorBase, _ as strictJsonStringify, a as StaticRpcDumpManifestStaticEntry, b as createDefineWrapperWithContext, c as collectStaticRpcDump, d as getDefinitionsWithDumps, f as reviveDumpError, g as STRUCTURED_CLONE_PREFIX, h as validateDefinitions, i as StaticRpcDumpManifestQueryEntry, l as createClientFromDump, m as validateDefinition, n as StaticRpcDumpFile, o as StaticRpcDumpManifestValue, p as serializeDumpError, r as StaticRpcDumpManifest, s as StaticRpcDumpSerialization, t as StaticRpcDumpCollection, u as dumpFunctions, v as getRpcHandler, w as RpcCacheOptions, x as defineRpcFunction, y as getRpcResolvedSetupResult } from "../index-CxaPKDXX.mjs";
3
- export { BirpcFn, BirpcReturn, EntriesToObject, RpcArgsSchema, RpcCacheManager, RpcCacheOptions, RpcDefinitionsFilter, RpcDefinitionsToFunctions, RpcDefinitionsToFunctionsWithNamespace, RpcDump, RpcDumpClientOptions, RpcDumpCollectionOptions, RpcDumpDefinition, RpcDumpGetter, RpcDumpRecord, RpcDumpRecordError, RpcDumpStore, RpcFunctionAgentOptions, RpcFunctionDefinition, RpcFunctionDefinitionAny, RpcFunctionDefinitionAnyWithContext, RpcFunctionDefinitionBase, RpcFunctionDefinitionToFunction, RpcFunctionSetupResult, RpcFunctionType, RpcFunctionsCollector, RpcFunctionsCollectorBase, RpcReturnSchema, STRUCTURED_CLONE_PREFIX, StaticRpcDumpCollection, StaticRpcDumpFile, StaticRpcDumpManifest, StaticRpcDumpManifestQueryEntry, StaticRpcDumpManifestStaticEntry, StaticRpcDumpManifestValue, StaticRpcDumpSerialization, Thenable, collectStaticRpcDump, createClientFromDump, createDefineWrapperWithContext, defineRpcFunction, dumpFunctions, getDefinitionsWithDumps, getRpcHandler, getRpcResolvedSetupResult, reviveDumpError, serializeDumpError, strictJsonStringify, validateDefinition, validateDefinitions };
2
+ import { C as RpcCacheManager, S as RpcFunctionsCollectorBase, _ as strictJsonStringify, a as StaticRpcDumpManifestStaticEntry, b as createDefineWrapperWithContext, c as collectStaticRpcDump, d as getDefinitionsWithDumps, f as reviveDumpError, g as STRUCTURED_CLONE_PREFIX, h as validateDefinitions, i as StaticRpcDumpManifestQueryEntry, l as createClientFromDump, m as validateDefinition, n as StaticRpcDumpFile, o as StaticRpcDumpManifestValue, p as serializeDumpError, r as StaticRpcDumpManifest, s as StaticRpcDumpSerialization, t as StaticRpcDumpCollection, u as dumpFunctions, v as getRpcHandler, w as RpcCacheOptions, x as defineRpcFunction, y as getRpcResolvedSetupResult } from "../index-Dy6GFDRf.mjs";
3
+ export { type BirpcFn, type BirpcReturn, EntriesToObject, RpcArgsSchema, RpcCacheManager, RpcCacheOptions, RpcDefinitionsFilter, RpcDefinitionsToFunctions, RpcDefinitionsToFunctionsWithNamespace, RpcDump, RpcDumpClientOptions, RpcDumpCollectionOptions, RpcDumpDefinition, RpcDumpGetter, RpcDumpRecord, RpcDumpRecordError, RpcDumpStore, RpcFunctionAgentOptions, RpcFunctionDefinition, RpcFunctionDefinitionAny, RpcFunctionDefinitionAnyWithContext, RpcFunctionDefinitionBase, RpcFunctionDefinitionToFunction, RpcFunctionSetupResult, RpcFunctionType, RpcFunctionsCollector, RpcFunctionsCollectorBase, RpcReturnSchema, STRUCTURED_CLONE_PREFIX, StaticRpcDumpCollection, StaticRpcDumpFile, StaticRpcDumpManifest, StaticRpcDumpManifestQueryEntry, StaticRpcDumpManifestStaticEntry, StaticRpcDumpManifestValue, StaticRpcDumpSerialization, Thenable, collectStaticRpcDump, createClientFromDump, createDefineWrapperWithContext, defineRpcFunction, dumpFunctions, getDefinitionsWithDumps, getRpcHandler, getRpcResolvedSetupResult, reviveDumpError, serializeDumpError, strictJsonStringify, validateDefinition, validateDefinitions };
@@ -1,4 +1,4 @@
1
- import { a as dumpFunctions$1, c as serializeDumpError$1, d as hash, i as createClientFromDump$1, l as validateDefinition, n as getRpcHandler, o as getDefinitionsWithDumps$1, r as getRpcResolvedSetupResult, s as reviveDumpError$1, t as collectStaticRpcDump$1, u as validateDefinitions } from "../dump-DoXkj4ku.mjs";
1
+ import { a as dumpFunctions$1, c as serializeDumpError$1, d as hash, i as createClientFromDump$1, l as validateDefinition, n as getRpcHandler, o as getDefinitionsWithDumps$1, r as getRpcResolvedSetupResult, s as reviveDumpError$1, t as collectStaticRpcDump$1, u as validateDefinitions } from "../dump-D09PZb7T.mjs";
2
2
  import { t as diagnostics } from "../diagnostics-BXwBQmoN.mjs";
3
3
  import { n as defineRpcFunction, t as createDefineWrapperWithContext } from "../define-BLWPsH6y.mjs";
4
4
  import { n as strictJsonStringify, t as STRUCTURED_CLONE_PREFIX } from "../serialization-DpLXCy13.mjs";
@@ -24,6 +24,9 @@ var RpcCacheManager = class {
24
24
  const methodCache = this.cacheMap.get(m);
25
25
  if (methodCache) return methodCache.get(this.keySerializer(a));
26
26
  }
27
+ has(m, a) {
28
+ return this.cacheMap.get(m)?.has(this.keySerializer(a)) ?? false;
29
+ }
27
30
  apply(req, res) {
28
31
  const methodCache = this.cacheMap.get(req.m) || /* @__PURE__ */ new Map();
29
32
  methodCache.set(this.keySerializer(req.a), res);
@@ -1,3 +1,4 @@
1
+ import { DEVFRAME_AUTH_TOKEN_QUERY_PARAM } from "../../constants.mjs";
1
2
  import { n as strictJsonStringify } from "../../serialization-DpLXCy13.mjs";
2
3
  import { n as structuredCloneStringify, t as structuredCloneParse } from "../../structured-clone-XpHLZ8nr.mjs";
3
4
  //#region src/rpc/transports/ws-client.ts
@@ -9,14 +10,15 @@ const EMPTY_DEFS = /* @__PURE__ */ new Map();
9
10
  */
10
11
  function createWsRpcChannel(options) {
11
12
  let url = options.url;
12
- if (options.authToken) url = `${url}?devframe_auth_token=${encodeURIComponent(options.authToken)}`;
13
+ if (options.authToken) url = `${url}?${DEVFRAME_AUTH_TOKEN_QUERY_PARAM}=${encodeURIComponent(options.authToken)}`;
13
14
  const ws = new WebSocket(url);
14
15
  const { onConnected = NOOP, onError = NOOP, onDisconnected = NOOP, definitions = EMPTY_DEFS } = options;
15
16
  ws.addEventListener("open", (e) => {
16
17
  onConnected(e);
17
18
  });
18
19
  ws.addEventListener("error", (e) => {
19
- onError(e instanceof Error ? e : new Error(e.type));
20
+ const _e = e instanceof Error ? e : new Error(e.type);
21
+ onError(_e);
20
22
  });
21
23
  ws.addEventListener("close", (e) => {
22
24
  onDisconnected(e);
@@ -29,14 +31,25 @@ function createWsRpcChannel(options) {
29
31
  });
30
32
  },
31
33
  post: (data) => {
32
- if (ws.readyState === WebSocket.OPEN) ws.send(data);
33
- else {
34
- function handler() {
35
- ws.send(data);
36
- ws.removeEventListener("open", handler);
34
+ if (ws.readyState === WebSocket.OPEN) {
35
+ ws.send(data);
36
+ return;
37
+ }
38
+ if (ws.readyState === WebSocket.CONNECTING) {
39
+ const onOpen = () => {
40
+ cleanup();
41
+ if (ws.readyState === WebSocket.OPEN) ws.send(data);
42
+ };
43
+ const onClose = () => cleanup();
44
+ function cleanup() {
45
+ ws.removeEventListener("open", onOpen);
46
+ ws.removeEventListener("close", onClose);
37
47
  }
38
- ws.addEventListener("open", handler);
48
+ ws.addEventListener("open", onOpen);
49
+ ws.addEventListener("close", onClose);
50
+ return;
39
51
  }
52
+ onError(/* @__PURE__ */ new Error("Devframe WebSocket is not open; message dropped"));
40
53
  },
41
54
  serialize: (msg) => {
42
55
  let method;
@@ -1,2 +1,2 @@
1
- import { i as attachWsRpcTransport, n as WsRpcTransport, r as WsRpcTransportOptions, t as DevframeNodeRpcSessionMeta } from "../../ws-server-BVVMDo2n.mjs";
2
- export { DevframeNodeRpcSessionMeta, WsRpcTransport, WsRpcTransportOptions, attachWsRpcTransport };
1
+ import { a as isAllowedOrigin, i as attachWsRpcTransport, n as WsRpcTransport, o as isLoopbackHostname, r as WsRpcTransportOptions, t as DevframeNodeRpcSessionMeta } from "../../ws-server-Bc2wBHl-.mjs";
2
+ export { DevframeNodeRpcSessionMeta, WsRpcTransport, WsRpcTransportOptions, attachWsRpcTransport, isAllowedOrigin, isLoopbackHostname };
@@ -12,6 +12,26 @@ function pathMatches(a, b) {
12
12
  const strip = (p) => p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
13
13
  return strip(a) === strip(b);
14
14
  }
15
+ function isLoopbackHostname(hostname) {
16
+ const h = hostname.replace(/^\[|\]$/g, "");
17
+ return h === "localhost" || h === "127.0.0.1" || h === "::1" || h.endsWith(".localhost") || h.startsWith("127.");
18
+ }
19
+ /**
20
+ * Default origin policy for a localhost dev tool: allow requests with no
21
+ * `Origin` header (native, non-browser clients), allow any loopback origin
22
+ * (so cross-port localhost dev setups keep working), and allow explicitly
23
+ * configured origins. Everything else — a real remote page in the dev's
24
+ * browser — is rejected.
25
+ */
26
+ function isAllowedOrigin(origin, allowedOrigins) {
27
+ if (!origin) return true;
28
+ if (allowedOrigins.includes(origin)) return true;
29
+ try {
30
+ return isLoopbackHostname(new URL(origin).hostname);
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
15
35
  /**
16
36
  * Route `upgrade` events on a server to the crossws adapter, optionally
17
37
  * filtered to a single `path`. Non-matching requests are left untouched so
@@ -19,18 +39,27 @@ function pathMatches(a, b) {
19
39
  * them, unless `destroyUnmatched` is set. Returns a detach function that
20
40
  * removes the listener.
21
41
  */
22
- function routeUpgrades(server, ws, path, destroyUnmatched) {
42
+ function routeUpgrades(server, ws, path, destroyUnmatched, allowedOrigins) {
23
43
  const listener = (req, socket, head) => {
44
+ socket.on("error", () => {});
24
45
  if (path) {
25
46
  let pathname = req.url ?? "/";
26
47
  try {
27
48
  pathname = new URL(req.url ?? "/", "http://localhost").pathname;
28
49
  } catch {}
29
50
  if (!pathMatches(pathname, path)) {
30
- if (destroyUnmatched) socket.destroy();
51
+ if (destroyUnmatched) {
52
+ socket.write("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
53
+ socket.destroy();
54
+ }
31
55
  return;
32
56
  }
33
57
  }
58
+ if (allowedOrigins !== false && !isAllowedOrigin(req.headers.origin, allowedOrigins ?? [])) {
59
+ socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
60
+ socket.destroy();
61
+ return;
62
+ }
34
63
  ws.handleUpgrade(req, socket, head);
35
64
  };
36
65
  server.on("upgrade", listener);
@@ -47,7 +76,7 @@ function routeUpgrades(server, ws, path, destroyUnmatched) {
47
76
  * teardown).
48
77
  */
49
78
  function attachWsRpcTransport(rpcGroup, options = {}) {
50
- const { server, port, host = "localhost", path, destroyUnmatched = false, https, onConnected = NOOP, onDisconnected = NOOP, definitions = EMPTY_DEFS, serialize: serializeOverride, deserialize: deserializeOverride } = options;
79
+ const { server, port, host = "localhost", path, destroyUnmatched = false, https, allowedOrigins, onConnected = NOOP, onDisconnected = NOOP, definitions = EMPTY_DEFS, serialize: serializeOverride, deserialize: deserializeOverride } = options;
51
80
  const states = /* @__PURE__ */ new WeakMap();
52
81
  const ws = crossws({ hooks: {
53
82
  open: (peer) => {
@@ -108,17 +137,17 @@ function attachWsRpcTransport(rpcGroup, options = {}) {
108
137
  } });
109
138
  let detach = NOOP;
110
139
  let ownedServer;
111
- if (server) detach = routeUpgrades(server, ws, path, destroyUnmatched);
140
+ if (server) detach = routeUpgrades(server, ws, path, destroyUnmatched, allowedOrigins);
112
141
  else if (https) {
113
142
  ownedServer = createServer$1(https);
114
- detach = routeUpgrades(ownedServer, ws, path, true);
143
+ detach = routeUpgrades(ownedServer, ws, path, true, allowedOrigins);
115
144
  ownedServer.listen(port, host);
116
145
  } else {
117
146
  ownedServer = createServer((_req, res) => {
118
147
  res.writeHead(426, { "content-type": "text/plain" });
119
148
  res.end("Upgrade Required");
120
149
  });
121
- detach = routeUpgrades(ownedServer, ws, path, true);
150
+ detach = routeUpgrades(ownedServer, ws, path, true, allowedOrigins);
122
151
  ownedServer.listen(port, host);
123
152
  }
124
153
  return {
@@ -135,4 +164,4 @@ function attachWsRpcTransport(rpcGroup, options = {}) {
135
164
  };
136
165
  }
137
166
  //#endregion
138
- export { attachWsRpcTransport };
167
+ export { attachWsRpcTransport, isAllowedOrigin, isLoopbackHostname };
@@ -1,6 +1,7 @@
1
1
  import { createRpcServer } from "./rpc/server.mjs";
2
2
  import { attachWsRpcTransport } from "./rpc/transports/ws-server.mjs";
3
- import { t as getInternalContext } from "./context-3x_bgBgZ.mjs";
3
+ import { a as diagnostics } from "./storage-yrUZaiib.mjs";
4
+ import { t as getInternalContext } from "./context-CKJheEtf.mjs";
4
5
  import { createServer } from "node:http";
5
6
  import { AsyncLocalStorage } from "node:async_hooks";
6
7
  import { H3, toNodeHandler } from "h3";
@@ -18,13 +19,23 @@ async function startHttpAndWs(options) {
18
19
  const httpServer = options.server ?? createServer(toNodeHandler(app));
19
20
  const rpcHost = context.rpc;
20
21
  const asyncStorage = new AsyncLocalStorage();
22
+ const authHandler = typeof options.auth === "object" ? options.auth : void 0;
23
+ const effectiveAuthorize = options.authorize ?? authHandler?.authorize;
24
+ if (authHandler) {
25
+ for (const fn of authHandler.rpcFunctions) if (!rpcHost.definitions.has(fn.name)) rpcHost.register(fn);
26
+ }
21
27
  const rpcGroup = createRpcServer(rpcHost.functions, { rpcOptions: { resolver(name, fn) {
22
28
  const rpc = this;
23
29
  if (!fn) return void 0;
24
30
  return async function(...args) {
31
+ const meta = rpc.$meta;
32
+ if (effectiveAuthorize && !effectiveAuthorize(name, {
33
+ meta,
34
+ rpc
35
+ })) throw diagnostics.DF0036({ name });
25
36
  return await asyncStorage.run({
26
37
  rpc,
27
- meta: rpc.$meta
38
+ meta
28
39
  }, async () => {
29
40
  return (await fn).apply(this, args);
30
41
  });
@@ -38,6 +49,15 @@ async function startHttpAndWs(options) {
38
49
  } : { server: httpServer },
39
50
  path: options.path,
40
51
  destroyUnmatched: ownsHttpServer,
52
+ allowedOrigins: options.allowedOrigins,
53
+ onConnected: authHandler || options.onPeerConnect ? (peer, meta) => {
54
+ const session = {
55
+ meta,
56
+ rpc: rpcGroup.clients.find((client) => client.$meta === meta)
57
+ };
58
+ authHandler?.onConnect(peer, session);
59
+ options.onPeerConnect?.(peer, session);
60
+ } : void 0,
41
61
  onDisconnected: (_peer, meta) => {
42
62
  rpcHost._emitSessionDisconnected(meta);
43
63
  }
@@ -45,8 +65,8 @@ async function startHttpAndWs(options) {
45
65
  rpcHost._rpcGroup = rpcGroup;
46
66
  rpcHost._asyncStorage = asyncStorage;
47
67
  rpcHost._authDisabled = options.auth === false;
48
- if (options.auth === false && !rpcHost.definitions.has("devframe:anonymous:auth")) rpcHost.register({
49
- name: "devframe:anonymous:auth",
68
+ if (options.auth === false && !rpcHost.definitions.has("anonymous:devframe:auth")) rpcHost.register({
69
+ name: "anonymous:devframe:auth",
50
70
  type: "action",
51
71
  handler: () => {
52
72
  const session = rpcHost.getCurrentRpcSession();
@@ -68,12 +88,25 @@ async function startHttpAndWs(options) {
68
88
  port: resolvedPort,
69
89
  app
70
90
  });
91
+ function connectionMeta() {
92
+ const jsonSerializableMethods = [];
93
+ for (const def of rpcHost.definitions.values()) if (def.jsonSerializable === true) jsonSerializableMethods.push(def.name);
94
+ return {
95
+ backend: "websocket",
96
+ websocket: separateWsPort != null ? {
97
+ port: separateWsPort,
98
+ path: options.path
99
+ } : { path: options.path },
100
+ jsonSerializableMethods
101
+ };
102
+ }
71
103
  return {
72
104
  origin,
73
105
  port: resolvedPort,
74
106
  app,
75
107
  ws,
76
108
  rpcGroup,
109
+ connectionMeta,
77
110
  async close() {
78
111
  await closeWs();
79
112
  if (ownsHttpServer) await new Promise((r) => httpServer.close(() => r()));