devframe 0.9.8 → 0.9.9
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-BM3PdYli.mjs → _shared-C08SdQRl.mjs} +15 -4
- package/dist/{_shared-mtXpcJIv.d.mts → _shared-CK3czkH4.d.mts} +25 -2
- package/dist/adapters/build.d.mts +6 -6
- package/dist/adapters/build.mjs +32 -22
- package/dist/adapters/cac.d.mts +16 -1
- package/dist/adapters/cac.mjs +8 -7
- package/dist/adapters/dev.d.mts +13 -13
- package/dist/adapters/dev.mjs +2 -2
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/initiate.d.mts +28 -26
- package/dist/adapters/initiate.mjs +1 -1
- package/dist/adapters/mcp.d.mts +28 -14
- package/dist/adapters/mcp.mjs +75 -39
- package/dist/{agent-tool-name-EgfoFO8C.mjs → agent-tool-name-DGzeuWcO.mjs} +2 -2
- package/dist/cli/main.d.mts +1 -1
- package/dist/cli/main.mjs +41 -17
- package/dist/client/index.d.mts +36 -36
- package/dist/client/index.mjs +83 -51
- package/dist/{client-assets-C7SXgTWY.mjs → client-assets-DNdhagGm.mjs} +2 -2
- package/dist/constants.d.mts +8 -7
- package/dist/constants.mjs +7 -6
- package/dist/{context-CLF4-8Pp.d.mts → context-4AIVRLYF.d.mts} +158 -122
- package/dist/{context-omlma_1n.mjs → context-CwPUinjn.mjs} +126 -88
- package/dist/{context-KUqAfawQ.mjs → context-DIUvJW4D.mjs} +3 -3
- package/dist/{context-DftYyXaR.d.mts → context-rgBSPFHS.d.mts} +5 -5
- package/dist/{crypto-token-XCqTSMg9.mjs → crypto-token-TVbGGFuj.mjs} +1 -1
- package/dist/{dev-CzkPCJwi.mjs → dev-Eqq5PX0k.mjs} +37 -18
- package/dist/{diagnostics-BXWW3VK-.mjs → diagnostics-BMfN8mb1.mjs} +30 -17
- package/dist/{diagnostics-CD8nlgll.mjs → diagnostics-D0EVm5M6.mjs} +2 -2
- package/dist/{events-4daw0uK9.mjs → events-BV4Dj59a.mjs} +2 -2
- package/dist/{host-h3-fRbF9yor.mjs → host-h3-BJ4siyz7.mjs} +1 -1
- package/dist/in-page-channel/index.d.mts +14 -14
- package/dist/in-page-channel/index.mjs +63 -54
- package/dist/{index-BzyopqxV.d.mts → index-pIVt92jP.d.mts} +9 -9
- package/dist/{index-D3k0BIhe.d.mts → index-pKl67H6w.d.mts} +4 -4
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +2 -2
- package/dist/{instance-registry-BjlMQbFB.mjs → instance-registry-C7-41RSp.mjs} +8 -8
- package/dist/{instance-shell-Tcp5hGoq.d.mts → instance-shell-7Z52PE4_.d.mts} +18 -18
- package/dist/{instance-shell-CpVW4zG0.mjs → instance-shell-cdVETpfz.mjs} +227 -58
- package/dist/internal/index.d.mts +40 -23
- package/dist/internal/index.mjs +10 -10
- package/dist/node/auth.d.mts +2 -2
- package/dist/node/auth.mjs +2 -2
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/hub-internals.mjs +2 -2
- package/dist/node/index.d.mts +5 -5
- package/dist/node/index.mjs +2 -2
- package/dist/{nostics-SPGDgHEP.d.mts → nostics-0sKe5HIj.d.mts} +2 -2
- package/dist/{nostics-CzECRXpE.mjs → nostics-D0PvLZsn.mjs} +1 -1
- package/dist/origin-DGDH8BI9.d.mts +45 -0
- package/dist/origin-DZit9g29.mjs +80 -0
- package/dist/recipes/common-rpc-functions.d.mts +5 -5
- package/dist/recipes/common-rpc-functions.mjs +3 -3
- package/dist/recipes/interactive-auth.d.mts +6 -6
- package/dist/recipes/interactive-auth.mjs +6 -6
- package/dist/{remote-assets-DezQmPQU.mjs → remote-assets-BIFWOlBW.mjs} +26 -6
- package/dist/{remote-assets-B13_vRkJ.d.mts → remote-assets-eBvSKZMS.d.mts} +7 -7
- package/dist/{revoke-COLW0cOi.mjs → revoke-cqztiCX4.mjs} +1 -1
- package/dist/rpc/dump.d.mts +2 -11
- package/dist/rpc/dump.mjs +1 -1
- package/dist/rpc/index.d.mts +2 -2
- package/dist/rpc/index.mjs +3 -3
- package/dist/rpc/transports/sse-client.d.mts +1 -1
- package/dist/rpc/transports/sse-client.mjs +30 -14
- package/dist/rpc/transports/sse-server.d.mts +4 -4
- package/dist/rpc/transports/sse-server.mjs +7 -7
- package/dist/rpc/transports/ws-bun.d.mts +5 -5
- package/dist/rpc/transports/ws-bun.mjs +8 -2
- package/dist/rpc/transports/ws-client.d.mts +1 -1
- package/dist/rpc/transports/ws-client.mjs +2 -2
- package/dist/rpc/transports/ws-deno.d.mts +4 -4
- package/dist/rpc/transports/ws-deno.mjs +8 -2
- package/dist/rpc/transports/ws-server.d.mts +2 -1
- package/dist/rpc/transports/ws-server.mjs +232 -1
- package/dist/{rpc-core-B3W3z9ff.d.mts → rpc-core-Ceu6AUz8.d.mts} +4 -4
- package/dist/{rpc-core-DlHd276T.mjs → rpc-core-DVQ7xi89.mjs} +14 -2
- package/dist/{serialization-BGzEwAdr.mjs → serialization-DTZnPQ1-.mjs} +20 -18
- package/dist/{services-install-CqqQVls5.mjs → services-install-P_DV7VYL.mjs} +32 -21
- package/dist/session-DPfj6zQG.mjs +16 -0
- package/dist/{shared-state--sduc6Xi.mjs → shared-state-CaxB1q0h.mjs} +2 -2
- package/dist/{simple-schema-DQPZrAaZ.mjs → simple-schema-Bi40l-P6.mjs} +2 -2
- package/dist/{sse-client-DwX021ST.d.mts → sse-client-BQ4jS3eO.d.mts} +4 -4
- package/dist/{state-CK9LjrnT.mjs → state-CeQQWzM-.mjs} +4 -4
- package/dist/{static-DV_qUSRS.mjs → static-DZ8P2Kn7.mjs} +130 -135
- package/dist/{storage-BXNVhKyR.mjs → storage-Dccf_IyD.mjs} +1 -1
- package/dist/types/index.d.mts +6 -6
- package/dist/{types-CMdPYhOx.d.mts → types-Dq4i156u.d.mts} +17 -17
- package/dist/utils/agent-tool-name.d.mts +2 -2
- package/dist/utils/agent-tool-name.mjs +2 -2
- package/dist/utils/colors.d.mts +1 -1
- package/dist/utils/crypto-token.d.mts +1 -1
- package/dist/utils/crypto-token.mjs +1 -1
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/nostics.d.mts +1 -1
- package/dist/utils/nostics.mjs +1 -1
- package/dist/utils/open.mjs +1 -1
- package/dist/utils/origin.d.mts +2 -0
- package/dist/utils/origin.mjs +80 -0
- package/dist/utils/remote-assets.d.mts +2 -2
- package/dist/utils/remote-assets.mjs +1 -1
- package/dist/utils/serve-static.d.mts +4 -4
- package/dist/utils/serve-static.mjs +56 -19
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/shared-state.mjs +1 -1
- package/dist/utils/simple-schema.d.mts +5 -5
- package/dist/utils/simple-schema.mjs +2 -2
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/utils/streaming-channel.mjs +2 -2
- package/dist/utils/when.d.mts +5 -5
- package/dist/{validation-YrZH6atx.mjs → validation-BOy8IoFZ.mjs} +2 -2
- package/dist/{wire-codec-0K-o5MYW.mjs → wire-codec-dcCeXcSo.mjs} +3 -3
- package/dist/{ws-client-7_lZi-B9.d.mts → ws-client-Xpxt0oPA.d.mts} +3 -3
- package/dist/{ws-server-VyHlanzy.d.mts → ws-server-D2nv68Hg.d.mts} +13 -37
- package/package.json +2 -1
- package/dist/ws-server-Y_AumUQH.mjs +0 -294
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as diagnostics } from "./diagnostics-
|
|
1
|
+
import { t as diagnostics } from "./diagnostics-D0EVm5M6.mjs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
//#region ../../node_modules/.pnpm/ohash@2.0.12/node_modules/ohash/dist/_chunks/is-equal.mjs
|
|
4
4
|
function serialize(input) {
|
|
@@ -220,7 +220,7 @@ async function validateRpcArgs(name, argsSchema, args) {
|
|
|
220
220
|
/**
|
|
221
221
|
* Validate a handler's resolved return value against its declared schema.
|
|
222
222
|
* Throws `DF0039` when the value fails the schema, otherwise returns the
|
|
223
|
-
* original value unchanged (guard-only, never rewriting the payload
|
|
223
|
+
* original value unchanged (guard-only, never rewriting the payload; see
|
|
224
224
|
* {@link validateRpcArgs}). Passes through when no return schema is set.
|
|
225
225
|
*
|
|
226
226
|
* @internal
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as strictJsonStringify } from "./serialization-
|
|
1
|
+
import { n as strictJsonStringify } from "./serialization-DTZnPQ1-.mjs";
|
|
2
2
|
import { n as structuredCloneStringify, t as structuredCloneParse } from "./structured-clone-CbAV5rFI.mjs";
|
|
3
3
|
//#region src/rpc/wire-codec.ts
|
|
4
4
|
const EMPTY_WIRE_DEFS = /* @__PURE__ */ new Map();
|
|
@@ -8,7 +8,7 @@ const EMPTY_WIRE_DEFS = /* @__PURE__ */ new Map();
|
|
|
8
8
|
* JSON (methods declared `jsonSerializable: true`) and `s:`-prefixed
|
|
9
9
|
* structured-clone (everything else, including all error envelopes), with a
|
|
10
10
|
* request-id → method map so a response independently picks the same
|
|
11
|
-
* encoder as its request. One codec per connection
|
|
11
|
+
* encoder as its request. One codec per connection; request-id spaces
|
|
12
12
|
* don't collide across connections.
|
|
13
13
|
*
|
|
14
14
|
* @internal
|
|
@@ -35,7 +35,7 @@ function createRpcWireCodec(definitions = EMPTY_WIRE_DEFS) {
|
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
37
|
* Peek at a wire frame's birpc envelope without engaging a codec's
|
|
38
|
-
* request-id bookkeeping
|
|
38
|
+
* request-id bookkeeping; used by the SSE transport to route a frame
|
|
39
39
|
* (park a POST for its response / answer with a bare 202) before it is
|
|
40
40
|
* handed to birpc proper.
|
|
41
41
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { v as RpcFunctionDefinitionAny } from "./types-
|
|
1
|
+
import { v as RpcFunctionDefinitionAny } from "./types-Dq4i156u.mjs";
|
|
2
2
|
import { ChannelOptions } from "birpc";
|
|
3
3
|
//#region src/rpc/transports/ws-client.d.ts
|
|
4
4
|
interface WsRpcChannelOptions {
|
|
@@ -10,7 +10,7 @@ interface WsRpcChannelOptions {
|
|
|
10
10
|
/**
|
|
11
11
|
* RPC function definitions (or just the `jsonSerializable` flag per
|
|
12
12
|
* method) used to dispatch the per-call wire serializer. Pass an
|
|
13
|
-
* empty / partial map on clients that don't have the full registry
|
|
13
|
+
* empty / partial map on clients that don't have the full registry;
|
|
14
14
|
* encoding falls back to structured-clone (the safer superset) and
|
|
15
15
|
* decoding still routes correctly via the wire prefix.
|
|
16
16
|
*/
|
|
@@ -20,7 +20,7 @@ interface WsRpcChannelOptions {
|
|
|
20
20
|
* Build a birpc `ChannelOptions` object backed by a browser `WebSocket`.
|
|
21
21
|
* Pass the result straight to `createRpcClient`'s `channel` option.
|
|
22
22
|
*
|
|
23
|
-
* Also returns `close()`, closing the underlying socket
|
|
23
|
+
* Also returns `close()`, closing the underlying socket, mirroring the server transport's
|
|
24
24
|
* existing `WsRpcTransport.close()`. `birpc`'s own `ChannelOptions` has no teardown of its own.
|
|
25
25
|
*/
|
|
26
26
|
declare function createWsRpcChannel(options: WsRpcChannelOptions): ChannelOptions & {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { v as RpcFunctionDefinitionAny } from "./types-
|
|
1
|
+
import { v as RpcFunctionDefinitionAny } from "./types-Dq4i156u.mjs";
|
|
2
2
|
import { BirpcGroup, ChannelOptions } from "birpc";
|
|
3
3
|
import { Hooks, Peer } from "crossws";
|
|
4
4
|
import { NodeAdapter } from "crossws/adapters/node";
|
|
@@ -16,8 +16,8 @@ import { Duplex } from "node:stream";
|
|
|
16
16
|
*/
|
|
17
17
|
type DevframeRpcTransportKind = 'websocket' | 'sse';
|
|
18
18
|
/**
|
|
19
|
-
* Structural view of the connect-time HTTP request behind an RPC connection
|
|
20
|
-
*
|
|
19
|
+
* Structural view of the connect-time HTTP request behind an RPC connection:
|
|
20
|
+
* the WS upgrade request, or the request opening an SSE stream. Shaped to
|
|
21
21
|
* match both the web `Request` a crossws peer exposes and a plain
|
|
22
22
|
* `node:http` request wrapper, so auth hooks can read the bearer-token
|
|
23
23
|
* query param and the `Origin` header without caring which transport (or
|
|
@@ -38,7 +38,7 @@ interface DevframeRpcConnectionRequest {
|
|
|
38
38
|
* (`onPeerConnect` / `onPeerDisconnect`, {@link DevframeAuthHandler.onConnect}).
|
|
39
39
|
*/
|
|
40
40
|
interface DevframeRpcConnection {
|
|
41
|
-
/** Session id
|
|
41
|
+
/** Session id, the same value as the session meta's `id`. */
|
|
42
42
|
id: number;
|
|
43
43
|
/** The transport carrying this connection. */
|
|
44
44
|
transport: DevframeRpcTransportKind;
|
|
@@ -49,7 +49,7 @@ interface DevframeRpcConnection {
|
|
|
49
49
|
/** Terminate the connection from the server side. */
|
|
50
50
|
close?: (code?: number, reason?: string) => void;
|
|
51
51
|
/**
|
|
52
|
-
* The crossws peer backing a `websocket` connection
|
|
52
|
+
* The crossws peer backing a `websocket` connection: the WS-specific
|
|
53
53
|
* escape hatch (pub/sub, raw socket access). Absent on other transports.
|
|
54
54
|
*/
|
|
55
55
|
peer?: Peer;
|
|
@@ -81,7 +81,7 @@ interface WsRpcTransportOptions {
|
|
|
81
81
|
* Attach to an existing HTTP(S) server, sharing its port. Combine with
|
|
82
82
|
* `path` to bind the WS endpoint to a single route so it coexists with
|
|
83
83
|
* other upgrade handlers on the same server (e.g. a Vite dev server's HMR
|
|
84
|
-
* socket). The shared server's lifecycle is owned by the caller
|
|
84
|
+
* socket). The shared server's lifecycle is owned by the caller; closing
|
|
85
85
|
* this transport detaches the upgrade listener without closing the server.
|
|
86
86
|
*/
|
|
87
87
|
server?: Server | Server$1;
|
|
@@ -130,7 +130,7 @@ interface WsRpcTransportOptions {
|
|
|
130
130
|
* dispatch between strict-JSON and structured-clone encoding based
|
|
131
131
|
* on each function's `jsonSerializable` flag.
|
|
132
132
|
*
|
|
133
|
-
* When omitted, all messages fall back to structured-clone
|
|
133
|
+
* When omitted, all messages fall back to structured-clone: safe but
|
|
134
134
|
* loses dev-time validation for `jsonSerializable: true` declarations.
|
|
135
135
|
*/
|
|
136
136
|
definitions?: ReadonlyMap<string, Pick<RpcFunctionDefinitionAny, 'jsonSerializable'>>;
|
|
@@ -164,7 +164,7 @@ interface WsOriginRegistry {
|
|
|
164
164
|
declare function createWsOriginRegistry(options?: CreateWsOriginRegistryOptions): WsOriginRegistry;
|
|
165
165
|
interface WsRpcTransport {
|
|
166
166
|
/**
|
|
167
|
-
* The crossws node adapter driving the socket
|
|
167
|
+
* The crossws node adapter driving the socket; exposes the connected
|
|
168
168
|
* `peers` and pub/sub. See https://crossws.h3.dev.
|
|
169
169
|
*/
|
|
170
170
|
ws: NodeAdapter;
|
|
@@ -175,15 +175,15 @@ interface WsRpcTransport {
|
|
|
175
175
|
/**
|
|
176
176
|
* Complete a `node:http` `upgrade` event on this transport, applying the
|
|
177
177
|
* same `path` filter and origin gate the transport's own listener uses.
|
|
178
|
-
* Wire it into a host server directly
|
|
179
|
-
* transport.handleUpgrade)`
|
|
178
|
+
* Wire it into a host server directly (`server.on('upgrade',
|
|
179
|
+
* transport.handleUpgrade)`) or call it from an existing listener.
|
|
180
180
|
*/
|
|
181
181
|
handleUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
|
|
182
182
|
/**
|
|
183
183
|
* Route a server's `upgrade` events to this transport, returning a detach
|
|
184
184
|
* function. Use it to bind an `unbound` transport once the host server
|
|
185
185
|
* exists; {@link WsRpcTransport.close} detaches every server attached this
|
|
186
|
-
* way (without closing them
|
|
186
|
+
* way (without closing them, since the caller owns their lifecycle).
|
|
187
187
|
*/
|
|
188
188
|
attach: (server: Server | Server$1) => () => void;
|
|
189
189
|
/** Remove the upgrade listener from a shared `server` (a no-op otherwise). */
|
|
@@ -195,36 +195,12 @@ interface WsRpcTransport {
|
|
|
195
195
|
*/
|
|
196
196
|
close: () => Promise<void>;
|
|
197
197
|
}
|
|
198
|
-
/**
|
|
199
|
-
* Whether `hostname` names a loopback host: `localhost` (or any `*.localhost`
|
|
200
|
-
* subdomain), the IPv6 loopback `::1`, or an IPv4 literal inside the
|
|
201
|
-
* `127.0.0.0/8` loopback block.
|
|
202
|
-
*
|
|
203
|
-
* The IPv4 case is matched **structurally** — the whole hostname must be a
|
|
204
|
-
* canonical dotted-decimal IPv4 literal whose first octet is `127`. A bare
|
|
205
|
-
* `startsWith('127.')` prefix check would also accept an attacker-controlled
|
|
206
|
-
* DNS name that merely *begins* with `127.` (`127.attacker.example`,
|
|
207
|
-
* `127.0.0.1.attacker.example`), letting a cross-origin browser page defeat
|
|
208
|
-
* the loopback origin gate that guards the RPC/MCP surface (a DNS-rebinding /
|
|
209
|
-
* cross-site WebSocket-hijacking bypass). Requiring a real IPv4 literal keeps
|
|
210
|
-
* genuine loopback addresses (`127.0.0.1`, `127.5.5.5`) allowed while rejecting
|
|
211
|
-
* those DNS names.
|
|
212
|
-
*/
|
|
213
|
-
declare function isLoopbackHostname(hostname: string): boolean;
|
|
214
|
-
/**
|
|
215
|
-
* Default origin policy for a localhost dev tool: allow requests with no
|
|
216
|
-
* `Origin` header (native, non-browser clients), allow any loopback origin
|
|
217
|
-
* (so cross-port localhost dev setups keep working), and allow explicitly
|
|
218
|
-
* configured origins. Everything else — a real remote page in the dev's
|
|
219
|
-
* browser — is rejected.
|
|
220
|
-
*/
|
|
221
|
-
declare function isAllowedOrigin(origin: string | undefined, allowedOrigins: readonly string[]): boolean;
|
|
222
198
|
/**
|
|
223
199
|
* The per-peer lifecycle hooks driving a devframe RPC WebSocket, shaped for
|
|
224
200
|
* any [crossws](https://crossws.h3.dev) adapter. {@link attachWsRpcTransport}
|
|
225
201
|
* feeds them to the Node adapter; runtime-specific attachments (e.g. Bun's
|
|
226
202
|
* fetch-upgrade adapter) reuse the same hooks so every transport speaks the
|
|
227
|
-
* identical wire protocol
|
|
203
|
+
* identical wire protocol: one birpc channel per peer, per-method
|
|
228
204
|
* `jsonSerializable` dispatch between strict JSON and structured-clone.
|
|
229
205
|
*/
|
|
230
206
|
declare function createWsRpcPeerHooks<ClientFunctions extends object, ServerFunctions extends object>(rpcGroup: BirpcGroup<ClientFunctions, ServerFunctions, false>, options?: Pick<WsRpcTransportOptions, 'onConnected' | 'onDisconnected' | 'definitions' | 'serialize' | 'deserialize'>): Partial<Hooks>;
|
|
@@ -240,4 +216,4 @@ declare function createWsRpcPeerHooks<ClientFunctions extends object, ServerFunc
|
|
|
240
216
|
*/
|
|
241
217
|
declare function attachWsRpcTransport<ClientFunctions extends object, ServerFunctions extends object>(rpcGroup: BirpcGroup<ClientFunctions, ServerFunctions, false>, options?: WsRpcTransportOptions): WsRpcTransport;
|
|
242
218
|
//#endregion
|
|
243
|
-
export { attachWsRpcTransport as a,
|
|
219
|
+
export { attachWsRpcTransport as a, DevframeNodeRpcSessionMeta as c, DevframeRpcTransportKind as d, WsRpcTransportOptions as i, DevframeRpcConnection as l, WsOriginRegistry as n, createWsOriginRegistry as o, WsRpcTransport as r, createWsRpcPeerHooks as s, CreateWsOriginRegistryOptions as t, DevframeRpcConnectionRequest as u };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devframe",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.9",
|
|
5
5
|
"description": "Framework for building one portable devtool integration that runs in any viewer.",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"./utils/nanoid": "./dist/utils/nanoid.mjs",
|
|
57
57
|
"./utils/nostics": "./dist/utils/nostics.mjs",
|
|
58
58
|
"./utils/open": "./dist/utils/open.mjs",
|
|
59
|
+
"./utils/origin": "./dist/utils/origin.mjs",
|
|
59
60
|
"./utils/remote-assets": "./dist/utils/remote-assets.mjs",
|
|
60
61
|
"./utils/simple-schema": "./dist/utils/simple-schema.mjs",
|
|
61
62
|
"./utils/serve-static": "./dist/utils/serve-static.mjs",
|
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import "./constants.mjs";
|
|
2
|
-
import { t as createRpcWireCodec } from "./wire-codec-0K-o5MYW.mjs";
|
|
3
|
-
import { n as randomToken, r as timingSafeEqual } from "./crypto-token-XCqTSMg9.mjs";
|
|
4
|
-
import { createServer } from "node:http";
|
|
5
|
-
import { createServer as createServer$1 } from "node:https";
|
|
6
|
-
import crossws from "crossws/adapters/node";
|
|
7
|
-
//#region src/rpc/transports/session.ts
|
|
8
|
-
let sessionId = 0;
|
|
9
|
-
/**
|
|
10
|
-
* Mint the per-connection session meta every transport binding shares —
|
|
11
|
-
* one id space across transports, so session bookkeeping (streaming
|
|
12
|
-
* subscriptions, shared-state sync, auth trust) never collides between a
|
|
13
|
-
* WS peer and an SSE session on the same server.
|
|
14
|
-
*/
|
|
15
|
-
function createRpcSessionMeta() {
|
|
16
|
-
return {
|
|
17
|
-
id: sessionId++,
|
|
18
|
-
subscribedStates: /* @__PURE__ */ new Set()
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
//#endregion
|
|
22
|
-
//#region src/rpc/transports/ws-server.ts
|
|
23
|
-
/**
|
|
24
|
-
* Create a live, token-protected origin allowlist for external browser
|
|
25
|
-
* viewers. Pass it to {@link WsRpcTransportOptions.allowedOrigins}, then use
|
|
26
|
-
* `registerFromUrl()` in the connection metadata handler to authorize a
|
|
27
|
-
* viewer without sharing a mutable array or disabling DNS-rebinding protection.
|
|
28
|
-
*/
|
|
29
|
-
function createWsOriginRegistry(options = {}) {
|
|
30
|
-
const token = randomToken();
|
|
31
|
-
const origins = new Set(options.allowedOrigins ?? []);
|
|
32
|
-
function normalizeOrigin(origin) {
|
|
33
|
-
if (!origin) return;
|
|
34
|
-
try {
|
|
35
|
-
const url = new URL(origin);
|
|
36
|
-
const normalized = url.origin === "null" ? `${url.protocol}//${url.host}` : url.origin;
|
|
37
|
-
return origin === normalized ? normalized : void 0;
|
|
38
|
-
} catch {}
|
|
39
|
-
}
|
|
40
|
-
function registerOrigin(origin, candidateToken) {
|
|
41
|
-
const normalized = normalizeOrigin(origin);
|
|
42
|
-
if (!normalized || !candidateToken || !timingSafeEqual(token, candidateToken)) return false;
|
|
43
|
-
if (options.validateOrigin && !options.validateOrigin(normalized)) return false;
|
|
44
|
-
origins.add(normalized);
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
token,
|
|
49
|
-
registerFromUrl(url) {
|
|
50
|
-
let parsed;
|
|
51
|
-
try {
|
|
52
|
-
parsed = new URL(url, "http://localhost");
|
|
53
|
-
} catch {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
const origin = parsed.searchParams.get("devframe_viewer_origin") ?? void 0;
|
|
57
|
-
return registerOrigin(origin, parsed.searchParams.get("devframe_viewer_origin_token") ?? void 0) ? origin : void 0;
|
|
58
|
-
},
|
|
59
|
-
isAllowed(origin) {
|
|
60
|
-
return isAllowedOrigin(origin, [...origins]);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
const EMPTY_DEFS = /* @__PURE__ */ new Map();
|
|
65
|
-
function NOOP() {}
|
|
66
|
-
function listen(server, port, host) {
|
|
67
|
-
return new Promise((resolve, reject) => {
|
|
68
|
-
const onError = (error) => reject(error);
|
|
69
|
-
server.once("error", onError);
|
|
70
|
-
try {
|
|
71
|
-
server.listen(port, host, () => {
|
|
72
|
-
server.off("error", onError);
|
|
73
|
-
resolve();
|
|
74
|
-
});
|
|
75
|
-
} catch (error) {
|
|
76
|
-
server.off("error", onError);
|
|
77
|
-
reject(error);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
/** Compare two URL paths ignoring a trailing slash. */
|
|
82
|
-
function pathMatches(a, b) {
|
|
83
|
-
const strip = (p) => p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
|
|
84
|
-
return strip(a) === strip(b);
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Whether `hostname` names a loopback host: `localhost` (or any `*.localhost`
|
|
88
|
-
* subdomain), the IPv6 loopback `::1`, or an IPv4 literal inside the
|
|
89
|
-
* `127.0.0.0/8` loopback block.
|
|
90
|
-
*
|
|
91
|
-
* The IPv4 case is matched **structurally** — the whole hostname must be a
|
|
92
|
-
* canonical dotted-decimal IPv4 literal whose first octet is `127`. A bare
|
|
93
|
-
* `startsWith('127.')` prefix check would also accept an attacker-controlled
|
|
94
|
-
* DNS name that merely *begins* with `127.` (`127.attacker.example`,
|
|
95
|
-
* `127.0.0.1.attacker.example`), letting a cross-origin browser page defeat
|
|
96
|
-
* the loopback origin gate that guards the RPC/MCP surface (a DNS-rebinding /
|
|
97
|
-
* cross-site WebSocket-hijacking bypass). Requiring a real IPv4 literal keeps
|
|
98
|
-
* genuine loopback addresses (`127.0.0.1`, `127.5.5.5`) allowed while rejecting
|
|
99
|
-
* those DNS names.
|
|
100
|
-
*/
|
|
101
|
-
function isLoopbackHostname(hostname) {
|
|
102
|
-
const h = hostname.replace(/^\[|\]$/g, "");
|
|
103
|
-
if (h === "localhost" || h.endsWith(".localhost") || h === "::1") return true;
|
|
104
|
-
return isLoopbackIPv4(h);
|
|
105
|
-
}
|
|
106
|
-
/** A canonical dotted-decimal IPv4 literal in `127.0.0.0/8`. */
|
|
107
|
-
function isLoopbackIPv4(hostname) {
|
|
108
|
-
const octets = hostname.split(".");
|
|
109
|
-
if (octets.length !== 4 || !octets.every(isDecimalOctet)) return false;
|
|
110
|
-
return Number(octets[0]) === 127;
|
|
111
|
-
}
|
|
112
|
-
/** A single canonical IPv4 octet: 1–3 digits, no leading zero, value 0–255. */
|
|
113
|
-
function isDecimalOctet(part) {
|
|
114
|
-
if (!/^\d{1,3}$/.test(part) || part.length > 1 && part[0] === "0") return false;
|
|
115
|
-
return Number(part) <= 255;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Default origin policy for a localhost dev tool: allow requests with no
|
|
119
|
-
* `Origin` header (native, non-browser clients), allow any loopback origin
|
|
120
|
-
* (so cross-port localhost dev setups keep working), and allow explicitly
|
|
121
|
-
* configured origins. Everything else — a real remote page in the dev's
|
|
122
|
-
* browser — is rejected.
|
|
123
|
-
*/
|
|
124
|
-
function isAllowedOrigin(origin, allowedOrigins) {
|
|
125
|
-
if (!origin) return true;
|
|
126
|
-
if (allowedOrigins.includes(origin)) return true;
|
|
127
|
-
try {
|
|
128
|
-
return isLoopbackHostname(new URL(origin).hostname);
|
|
129
|
-
} catch {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function isWsOriginRegistry(value) {
|
|
134
|
-
return !!value && !Array.isArray(value);
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Build the `upgrade` listener that hands a request to the crossws adapter,
|
|
138
|
-
* optionally filtered to a single `path`. Non-matching requests are left
|
|
139
|
-
* untouched so other upgrade listeners (e.g. a Vite dev server's HMR socket)
|
|
140
|
-
* can claim them, unless `destroyUnmatched` is set.
|
|
141
|
-
*/
|
|
142
|
-
function createUpgradeListener(ws, path, destroyUnmatched, allowedOrigins) {
|
|
143
|
-
return (req, socket, head) => {
|
|
144
|
-
socket.on("error", () => {});
|
|
145
|
-
if (path) {
|
|
146
|
-
let pathname = req.url ?? "/";
|
|
147
|
-
try {
|
|
148
|
-
pathname = new URL(req.url ?? "/", "http://localhost").pathname;
|
|
149
|
-
} catch {}
|
|
150
|
-
if (!pathMatches(pathname, path)) {
|
|
151
|
-
if (destroyUnmatched) {
|
|
152
|
-
socket.write("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
|
|
153
|
-
socket.destroy();
|
|
154
|
-
}
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const originAllowed = isWsOriginRegistry(allowedOrigins) ? allowedOrigins.isAllowed(req.headers.origin) : isAllowedOrigin(req.headers.origin, allowedOrigins || []);
|
|
159
|
-
if (allowedOrigins !== false && !originAllowed) {
|
|
160
|
-
socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
|
|
161
|
-
socket.destroy();
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
ws.handleUpgrade(req, socket, head);
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* The per-peer lifecycle hooks driving a devframe RPC WebSocket, shaped for
|
|
169
|
-
* any [crossws](https://crossws.h3.dev) adapter. {@link attachWsRpcTransport}
|
|
170
|
-
* feeds them to the Node adapter; runtime-specific attachments (e.g. Bun's
|
|
171
|
-
* fetch-upgrade adapter) reuse the same hooks so every transport speaks the
|
|
172
|
-
* identical wire protocol — one birpc channel per peer, per-method
|
|
173
|
-
* `jsonSerializable` dispatch between strict JSON and structured-clone.
|
|
174
|
-
*/
|
|
175
|
-
function createWsRpcPeerHooks(rpcGroup, options = {}) {
|
|
176
|
-
const { onConnected = NOOP, onDisconnected = NOOP, definitions = EMPTY_DEFS, serialize: serializeOverride, deserialize: deserializeOverride } = options;
|
|
177
|
-
const states = /* @__PURE__ */ new WeakMap();
|
|
178
|
-
return {
|
|
179
|
-
open: (peer) => {
|
|
180
|
-
const meta = createRpcSessionMeta();
|
|
181
|
-
meta.peer = peer;
|
|
182
|
-
const connection = {
|
|
183
|
-
id: meta.id,
|
|
184
|
-
transport: "websocket",
|
|
185
|
-
request: peer.request,
|
|
186
|
-
send: (data) => peer.send(data),
|
|
187
|
-
close: (code, reason) => peer.close(code, reason),
|
|
188
|
-
peer
|
|
189
|
-
};
|
|
190
|
-
const codec = createRpcWireCodec(definitions);
|
|
191
|
-
const state = {
|
|
192
|
-
meta,
|
|
193
|
-
connection,
|
|
194
|
-
channel: void 0
|
|
195
|
-
};
|
|
196
|
-
const channel = {
|
|
197
|
-
post: (data) => {
|
|
198
|
-
peer.send(data);
|
|
199
|
-
},
|
|
200
|
-
on: (fn) => {
|
|
201
|
-
state.onMessage = fn;
|
|
202
|
-
},
|
|
203
|
-
serialize: serializeOverride ?? codec.serialize,
|
|
204
|
-
deserialize: deserializeOverride ?? codec.deserialize,
|
|
205
|
-
meta
|
|
206
|
-
};
|
|
207
|
-
state.channel = channel;
|
|
208
|
-
states.set(peer, state);
|
|
209
|
-
rpcGroup.updateChannels((channels) => {
|
|
210
|
-
channels.push(channel);
|
|
211
|
-
});
|
|
212
|
-
onConnected(connection, meta);
|
|
213
|
-
},
|
|
214
|
-
message: (peer, message) => {
|
|
215
|
-
states.get(peer)?.onMessage?.(message.text());
|
|
216
|
-
},
|
|
217
|
-
close: (peer) => {
|
|
218
|
-
const state = states.get(peer);
|
|
219
|
-
if (!state) return;
|
|
220
|
-
states.delete(peer);
|
|
221
|
-
rpcGroup.updateChannels((channels) => {
|
|
222
|
-
const index = channels.indexOf(state.channel);
|
|
223
|
-
if (index >= 0) channels.splice(index, 1);
|
|
224
|
-
});
|
|
225
|
-
onDisconnected(state.connection, state.meta);
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Attach a WebSocket transport to an existing RPC group, powered by
|
|
231
|
-
* [crossws](https://crossws.h3.dev). Either attach to an existing HTTP(S)
|
|
232
|
-
* `server` (sharing its port, optionally scoped to a `path`), or let this
|
|
233
|
-
* helper create a standalone server from `port` / `host` / `https`.
|
|
234
|
-
*
|
|
235
|
-
* Returns the crossws node adapter, standalone-server readiness/address
|
|
236
|
-
* accessors, `detach` (remove the upgrade listener from a shared `server`),
|
|
237
|
-
* and `close` (full deterministic teardown).
|
|
238
|
-
*/
|
|
239
|
-
function attachWsRpcTransport(rpcGroup, options = {}) {
|
|
240
|
-
const { server, port, host = "localhost", path, destroyUnmatched = false, unbound, https, allowedOrigins } = options;
|
|
241
|
-
const ws = crossws({ hooks: createWsRpcPeerHooks(rpcGroup, options) });
|
|
242
|
-
const sharedUpgradeListener = createUpgradeListener(ws, path, destroyUnmatched, allowedOrigins);
|
|
243
|
-
const ownedUpgradeListener = createUpgradeListener(ws, path, true, allowedOrigins);
|
|
244
|
-
/** Bind a server's `upgrade` events, tracked so `close()` detaches them. */
|
|
245
|
-
const attachments = /* @__PURE__ */ new Set();
|
|
246
|
-
function attachTo(target, listener) {
|
|
247
|
-
target.on("upgrade", listener);
|
|
248
|
-
const detachOne = () => {
|
|
249
|
-
target.off("upgrade", listener);
|
|
250
|
-
attachments.delete(detachOne);
|
|
251
|
-
};
|
|
252
|
-
attachments.add(detachOne);
|
|
253
|
-
return detachOne;
|
|
254
|
-
}
|
|
255
|
-
let ready = Promise.resolve();
|
|
256
|
-
let ownedServer;
|
|
257
|
-
if (unbound) {} else if (server) attachTo(server, sharedUpgradeListener);
|
|
258
|
-
else if (https) {
|
|
259
|
-
ownedServer = createServer$1(https);
|
|
260
|
-
attachTo(ownedServer, ownedUpgradeListener);
|
|
261
|
-
ready = listen(ownedServer, port ?? 0, host);
|
|
262
|
-
} else {
|
|
263
|
-
ownedServer = createServer((_req, res) => {
|
|
264
|
-
res.writeHead(426, { "content-type": "text/plain" });
|
|
265
|
-
res.end("Upgrade Required");
|
|
266
|
-
});
|
|
267
|
-
attachTo(ownedServer, ownedUpgradeListener);
|
|
268
|
-
ready = listen(ownedServer, port ?? 0, host);
|
|
269
|
-
}
|
|
270
|
-
const activeServer = server ?? ownedServer;
|
|
271
|
-
function detachAll() {
|
|
272
|
-
for (const detachOne of [...attachments]) detachOne();
|
|
273
|
-
}
|
|
274
|
-
return {
|
|
275
|
-
ws,
|
|
276
|
-
ready,
|
|
277
|
-
address: () => activeServer?.address() ?? null,
|
|
278
|
-
handleUpgrade: sharedUpgradeListener,
|
|
279
|
-
attach: (target) => attachTo(target, sharedUpgradeListener),
|
|
280
|
-
detach: detachAll,
|
|
281
|
-
async close() {
|
|
282
|
-
detachAll();
|
|
283
|
-
ws.closeAll(void 0, void 0, true);
|
|
284
|
-
if (ownedServer) {
|
|
285
|
-
const srv = ownedServer;
|
|
286
|
-
await ready.catch(() => {});
|
|
287
|
-
if (!srv.listening) return;
|
|
288
|
-
await new Promise((r) => srv.close(() => r()));
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
//#endregion
|
|
294
|
-
export { isLoopbackHostname as a, isAllowedOrigin as i, createWsOriginRegistry as n, createRpcSessionMeta as o, createWsRpcPeerHooks as r, attachWsRpcTransport as t };
|