devframe 0.8.2 → 0.9.0-beta.10
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-BPmX9fqK.d.mts +41 -0
- package/dist/_shared-CbZ_yhbz.mjs +68 -0
- package/dist/adapters/build.d.mts +10 -12
- package/dist/adapters/build.mjs +26 -29
- package/dist/adapters/cac.d.mts +1 -1
- package/dist/adapters/cac.mjs +156 -1
- package/dist/adapters/dev.d.mts +38 -46
- package/dist/adapters/dev.mjs +2 -1
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/initiate.d.mts +224 -0
- package/dist/adapters/initiate.mjs +2 -0
- package/dist/adapters/mcp.d.mts +24 -7
- package/dist/adapters/mcp.mjs +2 -2
- package/dist/{agent-tool-name-C3b5vEwJ.mjs → agent-tool-name-EgfoFO8C.mjs} +0 -3
- package/dist/cli/main.d.mts +0 -2
- package/dist/cli/main.mjs +4 -8
- package/dist/client/index.d.mts +71 -7
- package/dist/client/index.mjs +720 -1540
- package/dist/constants.d.mts +28 -7
- package/dist/constants.mjs +28 -7
- package/dist/{context-DoSozfvx.d.mts → context-B3YUu_95.d.mts} +19 -1
- package/dist/{host-h3-D2hYZvDK.mjs → context-CEe4PBz1.mjs} +25 -557
- package/dist/{context-CBg8iRbQ.mjs → context-xdynGSLP.mjs} +10 -1
- package/dist/dev--rAPzbvO.mjs +239 -0
- package/dist/{devframe-mQ4qZAuo.d.mts → devframe-DJMg0WLe.d.mts} +181 -134
- package/dist/{diagnostics-hwjXp_UV.mjs → diagnostics-CD8nlgll.mjs} +1 -3
- package/dist/{diagnostics-D7wM2cwM.mjs → diagnostics-DOQPnQmX.mjs} +51 -5
- package/dist/dist-eNI9qM_I.mjs +228 -0
- package/dist/host-agent-xLCSe65D.mjs +264 -0
- package/dist/host-h3-Crkzm42q.mjs +26 -0
- package/dist/{fetch-BKaD0Vb3.mjs → http-DYpK0cUi.mjs} +42 -11
- package/dist/{index-b64Uuhy2.d.mts → index-CB-fSUyn.d.mts} +2 -2
- package/dist/{index-Dbw1p5ch.d.mts → index-tdrk1bzV.d.mts} +3 -32
- package/dist/index.d.mts +12 -5
- package/dist/index.mjs +8 -1
- package/dist/{instance-registry-BnnA2Eoh.mjs → instance-registry-WvQkt42E.mjs} +9 -8
- package/dist/instance-shell-CMFyszWJ.mjs +486 -0
- package/dist/instance-shell-CljiKq1c.d.mts +255 -0
- package/dist/internal/index.d.mts +397 -0
- package/dist/internal/index.mjs +9 -0
- package/dist/node/auth.d.mts +2 -2
- package/dist/node/auth.mjs +1 -1
- package/dist/node/hub-internals.d.mts +2 -14
- package/dist/node/hub-internals.mjs +2 -2
- package/dist/node/index.d.mts +4 -268
- package/dist/node/index.mjs +3 -5
- package/dist/nostics-CzECRXpE.mjs +59 -0
- package/dist/nostics-SPGDgHEP.d.mts +33 -0
- package/dist/recipes/common-rpc-functions.d.mts +4 -4
- package/dist/recipes/interactive-auth.d.mts +3 -3
- package/dist/recipes/interactive-auth.mjs +8 -8
- package/dist/remote-assets-Bg4gCUZ_.d.mts +123 -0
- package/dist/remote-assets-nTTxY2MU.mjs +636 -0
- package/dist/rolldown-runtime-JspESFgx.mjs +35 -0
- package/dist/rpc/dump.d.mts +77 -1
- package/dist/rpc/dump.mjs +1 -1
- package/dist/rpc/index.d.mts +3 -3
- package/dist/rpc/index.mjs +4 -18
- package/dist/rpc/transports/sse-client.d.mts +2 -0
- package/dist/rpc/transports/sse-client.mjs +142 -0
- package/dist/rpc/transports/sse-server.d.mts +61 -0
- package/dist/rpc/transports/sse-server.mjs +209 -0
- package/dist/rpc/transports/ws-bun.d.mts +35 -0
- package/dist/rpc/transports/ws-bun.mjs +31 -0
- package/dist/rpc/transports/ws-client.d.mts +1 -1
- package/dist/rpc/transports/ws-client.mjs +10 -18
- package/dist/rpc/transports/ws-server.d.mts +2 -2
- package/dist/rpc/transports/ws-server.mjs +2 -235
- package/dist/rpc-core-Cc4lucI0.d.mts +48 -0
- package/dist/rpc-core-DMTe-hLo.mjs +81 -0
- package/dist/{serialization-C8Mnw9hK.mjs → serialization-BGzEwAdr.mjs} +2 -2
- package/dist/{shared-state-6kHolYZy.mjs → shared-state-fRRbRUtD.mjs} +1 -1
- package/dist/sse-client-CFBOIaUi.d.mts +37 -0
- package/dist/state-Cc1IeSmQ.mjs +1291 -0
- package/dist/{dump-CgZShDRB.mjs → static-BPZ1_OPA.mjs} +3 -266
- package/dist/storage-CYye7P7L.mjs +137 -0
- package/dist/types/index.d.mts +6 -4
- package/dist/types/index.mjs +1 -6
- package/dist/{types-CnJSgRVa.d.mts → types-Djq7CBXh.d.mts} +0 -7
- package/dist/utils/agent-tool-name.d.mts +0 -3
- package/dist/utils/agent-tool-name.mjs +0 -3
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/get-port.d.mts +17 -0
- package/dist/utils/get-port.mjs +2 -0
- package/dist/utils/launch-editor.mjs +1 -25
- package/dist/utils/nostics.d.mts +2 -0
- package/dist/utils/nostics.mjs +2 -0
- package/dist/utils/open.d.mts +2 -10
- package/dist/utils/open.mjs +56 -1
- package/dist/utils/remote-assets.d.mts +15 -0
- package/dist/utils/remote-assets.mjs +2 -0
- package/dist/utils/serve-static.d.mts +13 -5
- package/dist/utils/serve-static.mjs +119 -9
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/shared-state.mjs +1 -1
- package/dist/utils/simple-schema.d.mts +101 -1
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/validation-CpXFB6Dz.mjs +266 -0
- package/dist/wire-codec-0K-o5MYW.mjs +56 -0
- package/dist/{ws-client-B6Tnr0ic.d.mts → ws-client-DhjNyQo3.d.mts} +7 -2
- package/dist/ws-server-BdSLrhxE.mjs +267 -0
- package/dist/{ws-server-uKQcClcJ.d.mts → ws-server-D1d3QM9f.d.mts} +90 -9
- package/package.json +13 -12
- package/skills/devframe/SKILL.md +91 -93
- package/dist/_shared-bWRzeSa0.mjs +0 -19
- package/dist/adapters/cli.d.mts +0 -11
- package/dist/adapters/cli.mjs +0 -6
- package/dist/cac-D4_9ok0z.mjs +0 -157
- package/dist/dev-Bc49u3qg.mjs +0 -439
- package/dist/diagnostics-reporter-CsIG85Q5.mjs +0 -38
- package/dist/helpers/vite.d.mts +0 -100
- package/dist/helpers/vite.mjs +0 -105
- package/dist/http-C4T68YV6.mjs +0 -38
- package/dist/index-vAX6bYuc.d.mts +0 -78
- package/dist/open-Deb5xmIT.mjs +0 -547
- package/dist/recipes/open-helpers.d.mts +0 -6
- package/dist/recipes/open-helpers.mjs +0 -6
- package/dist/server-CD8XmBXl.mjs +0 -167
- package/dist/server-UHWae43Q.d.mts +0 -149
- package/dist/simple-schema-BDzLeJDk.d.mts +0 -102
- package/dist/state-BeUHDDjk.mjs +0 -108
- package/dist/storage-CTNVbuFB.mjs +0 -1122
- package/dist/utils/promise.d.mts +0 -8
- package/dist/utils/promise.mjs +0 -15
- package/dist/utils/scope.d.mts +0 -11
- package/dist/utils/scope.mjs +0 -15
- package/skills/devframe/templates/spa-devframe.ts +0 -38
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { n as strictJsonStringify } from "./serialization-BGzEwAdr.mjs";
|
|
2
|
+
import { n as structuredCloneStringify, t as structuredCloneParse } from "./structured-clone-CbAV5rFI.mjs";
|
|
3
|
+
//#region src/rpc/wire-codec.ts
|
|
4
|
+
const EMPTY_WIRE_DEFS = /* @__PURE__ */ new Map();
|
|
5
|
+
/**
|
|
6
|
+
* Build the per-connection wire codec every live transport (WS server, WS
|
|
7
|
+
* client, SSE server, SSE client) shares: per-method dispatch between strict
|
|
8
|
+
* JSON (methods declared `jsonSerializable: true`) and `s:`-prefixed
|
|
9
|
+
* structured-clone (everything else, including all error envelopes), with a
|
|
10
|
+
* request-id → method map so a response independently picks the same
|
|
11
|
+
* encoder as its request. One codec per connection — request-id spaces
|
|
12
|
+
* don't collide across connections.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
function createRpcWireCodec(definitions = EMPTY_WIRE_DEFS) {
|
|
17
|
+
const pendingRequestMethods = /* @__PURE__ */ new Map();
|
|
18
|
+
return {
|
|
19
|
+
serialize: (msg) => {
|
|
20
|
+
let method;
|
|
21
|
+
if (msg.t === "q") method = msg.m;
|
|
22
|
+
else {
|
|
23
|
+
method = pendingRequestMethods.get(msg.i);
|
|
24
|
+
pendingRequestMethods.delete(msg.i);
|
|
25
|
+
}
|
|
26
|
+
if (!(msg.t === "s" && "e" in msg) && !!method && definitions.get(method)?.jsonSerializable === true) return strictJsonStringify(msg, method ?? "");
|
|
27
|
+
return `s:${structuredCloneStringify(msg)}`;
|
|
28
|
+
},
|
|
29
|
+
deserialize: (raw) => {
|
|
30
|
+
const msg = raw.startsWith("s:") ? structuredCloneParse(raw.slice(2)) : JSON.parse(raw);
|
|
31
|
+
if (msg.t === "q" && msg.i && msg.m) pendingRequestMethods.set(msg.i, msg.m);
|
|
32
|
+
return msg;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Peek at a wire frame's birpc envelope without engaging a codec's
|
|
38
|
+
* request-id bookkeeping — used by the SSE transport to route a frame
|
|
39
|
+
* (park a POST for its response / answer with a bare 202) before it is
|
|
40
|
+
* handed to birpc proper.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
function peekRpcWireFrame(raw) {
|
|
45
|
+
try {
|
|
46
|
+
const msg = raw.startsWith("s:") ? structuredCloneParse(raw.slice(2)) : JSON.parse(raw);
|
|
47
|
+
return {
|
|
48
|
+
t: msg?.t,
|
|
49
|
+
i: msg?.i
|
|
50
|
+
};
|
|
51
|
+
} catch {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { peekRpcWireFrame as n, createRpcWireCodec as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { v as RpcFunctionDefinitionAny } from "./types-
|
|
1
|
+
import { v as RpcFunctionDefinitionAny } from "./types-Djq7CBXh.mjs";
|
|
2
2
|
import { ChannelOptions } from "birpc";
|
|
3
3
|
//#region src/rpc/transports/ws-client.d.ts
|
|
4
4
|
interface WsRpcChannelOptions {
|
|
@@ -19,7 +19,12 @@ interface WsRpcChannelOptions {
|
|
|
19
19
|
/**
|
|
20
20
|
* Build a birpc `ChannelOptions` object backed by a browser `WebSocket`.
|
|
21
21
|
* Pass the result straight to `createRpcClient`'s `channel` option.
|
|
22
|
+
*
|
|
23
|
+
* Also returns `close()`, closing the underlying socket — mirroring the server transport's
|
|
24
|
+
* existing `WsRpcTransport.close()`. `birpc`'s own `ChannelOptions` has no teardown of its own.
|
|
22
25
|
*/
|
|
23
|
-
declare function createWsRpcChannel(options: WsRpcChannelOptions): ChannelOptions
|
|
26
|
+
declare function createWsRpcChannel(options: WsRpcChannelOptions): ChannelOptions & {
|
|
27
|
+
close: () => void;
|
|
28
|
+
};
|
|
24
29
|
//#endregion
|
|
25
30
|
export { createWsRpcChannel as n, WsRpcChannelOptions as t };
|
|
@@ -0,0 +1,267 @@
|
|
|
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
|
+
function isLoopbackHostname(hostname) {
|
|
87
|
+
const h = hostname.replace(/^\[|\]$/g, "");
|
|
88
|
+
return h === "localhost" || h === "127.0.0.1" || h === "::1" || h.endsWith(".localhost") || h.startsWith("127.");
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Default origin policy for a localhost dev tool: allow requests with no
|
|
92
|
+
* `Origin` header (native, non-browser clients), allow any loopback origin
|
|
93
|
+
* (so cross-port localhost dev setups keep working), and allow explicitly
|
|
94
|
+
* configured origins. Everything else — a real remote page in the dev's
|
|
95
|
+
* browser — is rejected.
|
|
96
|
+
*/
|
|
97
|
+
function isAllowedOrigin(origin, allowedOrigins) {
|
|
98
|
+
if (!origin) return true;
|
|
99
|
+
if (allowedOrigins.includes(origin)) return true;
|
|
100
|
+
try {
|
|
101
|
+
return isLoopbackHostname(new URL(origin).hostname);
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function isWsOriginRegistry(value) {
|
|
107
|
+
return !!value && !Array.isArray(value);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build the `upgrade` listener that hands a request to the crossws adapter,
|
|
111
|
+
* optionally filtered to a single `path`. Non-matching requests are left
|
|
112
|
+
* untouched so other upgrade listeners (e.g. a Vite dev server's HMR socket)
|
|
113
|
+
* can claim them, unless `destroyUnmatched` is set.
|
|
114
|
+
*/
|
|
115
|
+
function createUpgradeListener(ws, path, destroyUnmatched, allowedOrigins) {
|
|
116
|
+
return (req, socket, head) => {
|
|
117
|
+
socket.on("error", () => {});
|
|
118
|
+
if (path) {
|
|
119
|
+
let pathname = req.url ?? "/";
|
|
120
|
+
try {
|
|
121
|
+
pathname = new URL(req.url ?? "/", "http://localhost").pathname;
|
|
122
|
+
} catch {}
|
|
123
|
+
if (!pathMatches(pathname, path)) {
|
|
124
|
+
if (destroyUnmatched) {
|
|
125
|
+
socket.write("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
|
|
126
|
+
socket.destroy();
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const originAllowed = isWsOriginRegistry(allowedOrigins) ? allowedOrigins.isAllowed(req.headers.origin) : isAllowedOrigin(req.headers.origin, allowedOrigins || []);
|
|
132
|
+
if (allowedOrigins !== false && !originAllowed) {
|
|
133
|
+
socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
|
|
134
|
+
socket.destroy();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
ws.handleUpgrade(req, socket, head);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The per-peer lifecycle hooks driving a devframe RPC WebSocket, shaped for
|
|
142
|
+
* any [crossws](https://crossws.h3.dev) adapter. {@link attachWsRpcTransport}
|
|
143
|
+
* feeds them to the Node adapter; runtime-specific attachments (e.g. Bun's
|
|
144
|
+
* fetch-upgrade adapter) reuse the same hooks so every transport speaks the
|
|
145
|
+
* identical wire protocol — one birpc channel per peer, per-method
|
|
146
|
+
* `jsonSerializable` dispatch between strict JSON and structured-clone.
|
|
147
|
+
*/
|
|
148
|
+
function createWsRpcPeerHooks(rpcGroup, options = {}) {
|
|
149
|
+
const { onConnected = NOOP, onDisconnected = NOOP, definitions = EMPTY_DEFS, serialize: serializeOverride, deserialize: deserializeOverride } = options;
|
|
150
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
151
|
+
return {
|
|
152
|
+
open: (peer) => {
|
|
153
|
+
const meta = createRpcSessionMeta();
|
|
154
|
+
meta.peer = peer;
|
|
155
|
+
const connection = {
|
|
156
|
+
id: meta.id,
|
|
157
|
+
transport: "websocket",
|
|
158
|
+
request: peer.request,
|
|
159
|
+
send: (data) => peer.send(data),
|
|
160
|
+
close: (code, reason) => peer.close(code, reason),
|
|
161
|
+
peer
|
|
162
|
+
};
|
|
163
|
+
const codec = createRpcWireCodec(definitions);
|
|
164
|
+
const state = {
|
|
165
|
+
meta,
|
|
166
|
+
connection,
|
|
167
|
+
channel: void 0
|
|
168
|
+
};
|
|
169
|
+
const channel = {
|
|
170
|
+
post: (data) => {
|
|
171
|
+
peer.send(data);
|
|
172
|
+
},
|
|
173
|
+
on: (fn) => {
|
|
174
|
+
state.onMessage = fn;
|
|
175
|
+
},
|
|
176
|
+
serialize: serializeOverride ?? codec.serialize,
|
|
177
|
+
deserialize: deserializeOverride ?? codec.deserialize,
|
|
178
|
+
meta
|
|
179
|
+
};
|
|
180
|
+
state.channel = channel;
|
|
181
|
+
states.set(peer, state);
|
|
182
|
+
rpcGroup.updateChannels((channels) => {
|
|
183
|
+
channels.push(channel);
|
|
184
|
+
});
|
|
185
|
+
onConnected(connection, meta);
|
|
186
|
+
},
|
|
187
|
+
message: (peer, message) => {
|
|
188
|
+
states.get(peer)?.onMessage?.(message.text());
|
|
189
|
+
},
|
|
190
|
+
close: (peer) => {
|
|
191
|
+
const state = states.get(peer);
|
|
192
|
+
if (!state) return;
|
|
193
|
+
states.delete(peer);
|
|
194
|
+
rpcGroup.updateChannels((channels) => {
|
|
195
|
+
const index = channels.indexOf(state.channel);
|
|
196
|
+
if (index >= 0) channels.splice(index, 1);
|
|
197
|
+
});
|
|
198
|
+
onDisconnected(state.connection, state.meta);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Attach a WebSocket transport to an existing RPC group, powered by
|
|
204
|
+
* [crossws](https://crossws.h3.dev). Either attach to an existing HTTP(S)
|
|
205
|
+
* `server` (sharing its port, optionally scoped to a `path`), or let this
|
|
206
|
+
* helper create a standalone server from `port` / `host` / `https`.
|
|
207
|
+
*
|
|
208
|
+
* Returns the crossws node adapter, standalone-server readiness/address
|
|
209
|
+
* accessors, `detach` (remove the upgrade listener from a shared `server`),
|
|
210
|
+
* and `close` (full deterministic teardown).
|
|
211
|
+
*/
|
|
212
|
+
function attachWsRpcTransport(rpcGroup, options = {}) {
|
|
213
|
+
const { server, port, host = "localhost", path, destroyUnmatched = false, unbound, https, allowedOrigins } = options;
|
|
214
|
+
const ws = crossws({ hooks: createWsRpcPeerHooks(rpcGroup, options) });
|
|
215
|
+
const sharedUpgradeListener = createUpgradeListener(ws, path, destroyUnmatched, allowedOrigins);
|
|
216
|
+
const ownedUpgradeListener = createUpgradeListener(ws, path, true, allowedOrigins);
|
|
217
|
+
/** Bind a server's `upgrade` events, tracked so `close()` detaches them. */
|
|
218
|
+
const attachments = /* @__PURE__ */ new Set();
|
|
219
|
+
function attachTo(target, listener) {
|
|
220
|
+
target.on("upgrade", listener);
|
|
221
|
+
const detachOne = () => {
|
|
222
|
+
target.off("upgrade", listener);
|
|
223
|
+
attachments.delete(detachOne);
|
|
224
|
+
};
|
|
225
|
+
attachments.add(detachOne);
|
|
226
|
+
return detachOne;
|
|
227
|
+
}
|
|
228
|
+
let ready = Promise.resolve();
|
|
229
|
+
let ownedServer;
|
|
230
|
+
if (unbound) {} else if (server) attachTo(server, sharedUpgradeListener);
|
|
231
|
+
else if (https) {
|
|
232
|
+
ownedServer = createServer$1(https);
|
|
233
|
+
attachTo(ownedServer, ownedUpgradeListener);
|
|
234
|
+
ready = listen(ownedServer, port ?? 0, host);
|
|
235
|
+
} else {
|
|
236
|
+
ownedServer = createServer((_req, res) => {
|
|
237
|
+
res.writeHead(426, { "content-type": "text/plain" });
|
|
238
|
+
res.end("Upgrade Required");
|
|
239
|
+
});
|
|
240
|
+
attachTo(ownedServer, ownedUpgradeListener);
|
|
241
|
+
ready = listen(ownedServer, port ?? 0, host);
|
|
242
|
+
}
|
|
243
|
+
const activeServer = server ?? ownedServer;
|
|
244
|
+
function detachAll() {
|
|
245
|
+
for (const detachOne of [...attachments]) detachOne();
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
ws,
|
|
249
|
+
ready,
|
|
250
|
+
address: () => activeServer?.address() ?? null,
|
|
251
|
+
handleUpgrade: sharedUpgradeListener,
|
|
252
|
+
attach: (target) => attachTo(target, sharedUpgradeListener),
|
|
253
|
+
detach: detachAll,
|
|
254
|
+
async close() {
|
|
255
|
+
detachAll();
|
|
256
|
+
ws.closeAll(void 0, void 0, true);
|
|
257
|
+
if (ownedServer) {
|
|
258
|
+
const srv = ownedServer;
|
|
259
|
+
await ready.catch(() => {});
|
|
260
|
+
if (!srv.listening) return;
|
|
261
|
+
await new Promise((r) => srv.close(() => r()));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
267
|
+
export { isLoopbackHostname as a, isAllowedOrigin as i, createWsOriginRegistry as n, createRpcSessionMeta as o, createWsRpcPeerHooks as r, attachWsRpcTransport as t };
|
|
@@ -1,14 +1,62 @@
|
|
|
1
|
-
import { v as RpcFunctionDefinitionAny } from "./types-
|
|
2
|
-
import { Peer } from "crossws";
|
|
1
|
+
import { v as RpcFunctionDefinitionAny } from "./types-Djq7CBXh.mjs";
|
|
3
2
|
import { BirpcGroup, ChannelOptions } from "birpc";
|
|
3
|
+
import { Hooks, Peer } from "crossws";
|
|
4
4
|
import { NodeAdapter } from "crossws/adapters/node";
|
|
5
|
-
import {
|
|
5
|
+
import { Buffer } from "node:buffer";
|
|
6
|
+
import { IncomingMessage, Server } from "node:http";
|
|
6
7
|
import { Server as Server$1, ServerOptions } from "node:https";
|
|
7
8
|
import { AddressInfo } from "node:net";
|
|
8
|
-
|
|
9
|
+
import { Duplex } from "node:stream";
|
|
10
|
+
//#region src/rpc/transports/session.d.ts
|
|
11
|
+
/**
|
|
12
|
+
* Which wire transport produced an RPC connection. Every transport speaks
|
|
13
|
+
* the same birpc channel protocol; the kind only matters to code that needs
|
|
14
|
+
* transport-specific behavior (e.g. reaching the WS escape hatch on
|
|
15
|
+
* {@link DevframeRpcConnection.peer}).
|
|
16
|
+
*/
|
|
17
|
+
type DevframeRpcTransportKind = 'websocket' | 'sse';
|
|
18
|
+
/**
|
|
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
|
+
* match both the web `Request` a crossws peer exposes and a plain
|
|
22
|
+
* `node:http` request wrapper, so auth hooks can read the bearer-token
|
|
23
|
+
* query param and the `Origin` header without caring which transport (or
|
|
24
|
+
* runtime) produced the connection.
|
|
25
|
+
*/
|
|
26
|
+
interface DevframeRpcConnectionRequest {
|
|
27
|
+
/** Request URL (may be path-only, e.g. `/__ws?devframe_auth_token=…`). */
|
|
28
|
+
url?: string;
|
|
29
|
+
/** Header lookup, `Headers`-style. */
|
|
30
|
+
headers?: {
|
|
31
|
+
get: (name: string) => string | null | undefined;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A live RPC connection, independent of the transport that carries it. One
|
|
36
|
+
* exists per connected client; transport bindings construct it alongside the
|
|
37
|
+
* session meta and hand both to the connect/disconnect hooks
|
|
38
|
+
* (`onPeerConnect` / `onPeerDisconnect`, {@link DevframeAuthHandler.onConnect}).
|
|
39
|
+
*/
|
|
40
|
+
interface DevframeRpcConnection {
|
|
41
|
+
/** Session id — the same value as the session meta's `id`. */
|
|
42
|
+
id: number;
|
|
43
|
+
/** The transport carrying this connection. */
|
|
44
|
+
transport: DevframeRpcTransportKind;
|
|
45
|
+
/** The connect-time HTTP request (upgrade request / stream request). */
|
|
46
|
+
request?: DevframeRpcConnectionRequest;
|
|
47
|
+
/** Send a raw wire frame to this client. Prefer the birpc channel. */
|
|
48
|
+
send?: (data: string) => void;
|
|
49
|
+
/** Terminate the connection from the server side. */
|
|
50
|
+
close?: (code?: number, reason?: string) => void;
|
|
51
|
+
/**
|
|
52
|
+
* The crossws peer backing a `websocket` connection — the WS-specific
|
|
53
|
+
* escape hatch (pub/sub, raw socket access). Absent on other transports.
|
|
54
|
+
*/
|
|
55
|
+
peer?: Peer;
|
|
56
|
+
}
|
|
9
57
|
interface DevframeNodeRpcSessionMeta {
|
|
10
58
|
id: number;
|
|
11
|
-
/** The crossws peer backing this session's socket. */
|
|
59
|
+
/** The crossws peer backing this session's socket (WS transport only). */
|
|
12
60
|
peer?: Peer;
|
|
13
61
|
clientAuthToken?: string;
|
|
14
62
|
isTrusted?: boolean;
|
|
@@ -26,6 +74,8 @@ interface DevframeNodeRpcSessionMeta {
|
|
|
26
74
|
*/
|
|
27
75
|
uploadingStreams?: Set<string>;
|
|
28
76
|
}
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/rpc/transports/ws-server.d.ts
|
|
29
79
|
interface WsRpcTransportOptions {
|
|
30
80
|
/**
|
|
31
81
|
* Attach to an existing HTTP(S) server, sharing its port. Combine with
|
|
@@ -43,12 +93,20 @@ interface WsRpcTransportOptions {
|
|
|
43
93
|
/** Host for the standalone WebSocket server. Defaults to `localhost`. */
|
|
44
94
|
host?: string;
|
|
45
95
|
/**
|
|
46
|
-
* Restrict the WS endpoint to a single upgrade route (e.g. `/
|
|
96
|
+
* Restrict the WS endpoint to a single upgrade route (e.g. `/__ws`). When
|
|
47
97
|
* sharing a `server`, non-matching upgrade requests are left untouched for
|
|
48
98
|
* other listeners to handle, so devframe's socket can sit alongside
|
|
49
99
|
* framework sockets (Vite HMR, etc.).
|
|
50
100
|
*/
|
|
51
101
|
path?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Create the adapter without binding it to anything: no server is created,
|
|
104
|
+
* no port is bound, no `upgrade` listener is installed. The caller drives
|
|
105
|
+
* the socket itself through {@link WsRpcTransport.handleUpgrade} (from its
|
|
106
|
+
* own `upgrade` listener) or {@link WsRpcTransport.attach} (to bind a
|
|
107
|
+
* server later). Takes precedence over `server` / `port` / `https`.
|
|
108
|
+
*/
|
|
109
|
+
unbound?: boolean;
|
|
52
110
|
/**
|
|
53
111
|
* Destroy upgrade requests that don't match `path` instead of leaving them
|
|
54
112
|
* for other listeners. Enable this when devframe owns the shared server
|
|
@@ -76,8 +134,8 @@ interface WsRpcTransportOptions {
|
|
|
76
134
|
* loses dev-time validation for `jsonSerializable: true` declarations.
|
|
77
135
|
*/
|
|
78
136
|
definitions?: ReadonlyMap<string, Pick<RpcFunctionDefinitionAny, 'jsonSerializable'>>;
|
|
79
|
-
onConnected?: (
|
|
80
|
-
onDisconnected?: (
|
|
137
|
+
onConnected?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
|
|
138
|
+
onDisconnected?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
|
|
81
139
|
/** Override the default per-call serializer. Most callers should leave this unset. */
|
|
82
140
|
serialize?: ChannelOptions['serialize'];
|
|
83
141
|
/** Override the default per-call deserializer. Most callers should leave this unset. */
|
|
@@ -114,6 +172,20 @@ interface WsRpcTransport {
|
|
|
114
172
|
ready: Promise<void>;
|
|
115
173
|
/** Returns the bound address, or `null` when the server is not listening. */
|
|
116
174
|
address: () => AddressInfo | string | null;
|
|
175
|
+
/**
|
|
176
|
+
* Complete a `node:http` `upgrade` event on this transport, applying the
|
|
177
|
+
* same `path` filter and origin gate the transport's own listener uses.
|
|
178
|
+
* Wire it into a host server directly — `server.on('upgrade',
|
|
179
|
+
* transport.handleUpgrade)` — or call it from an existing listener.
|
|
180
|
+
*/
|
|
181
|
+
handleUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
|
|
182
|
+
/**
|
|
183
|
+
* Route a server's `upgrade` events to this transport, returning a detach
|
|
184
|
+
* function. Use it to bind an `unbound` transport once the host server
|
|
185
|
+
* exists; {@link WsRpcTransport.close} detaches every server attached this
|
|
186
|
+
* way (without closing them — the caller owns their lifecycle).
|
|
187
|
+
*/
|
|
188
|
+
attach: (server: Server | Server$1) => () => void;
|
|
117
189
|
/** Remove the upgrade listener from a shared `server` (a no-op otherwise). */
|
|
118
190
|
detach: () => void;
|
|
119
191
|
/**
|
|
@@ -132,6 +204,15 @@ declare function isLoopbackHostname(hostname: string): boolean;
|
|
|
132
204
|
* browser — is rejected.
|
|
133
205
|
*/
|
|
134
206
|
declare function isAllowedOrigin(origin: string | undefined, allowedOrigins: readonly string[]): boolean;
|
|
207
|
+
/**
|
|
208
|
+
* The per-peer lifecycle hooks driving a devframe RPC WebSocket, shaped for
|
|
209
|
+
* any [crossws](https://crossws.h3.dev) adapter. {@link attachWsRpcTransport}
|
|
210
|
+
* feeds them to the Node adapter; runtime-specific attachments (e.g. Bun's
|
|
211
|
+
* fetch-upgrade adapter) reuse the same hooks so every transport speaks the
|
|
212
|
+
* identical wire protocol — one birpc channel per peer, per-method
|
|
213
|
+
* `jsonSerializable` dispatch between strict JSON and structured-clone.
|
|
214
|
+
*/
|
|
215
|
+
declare function createWsRpcPeerHooks<ClientFunctions extends object, ServerFunctions extends object>(rpcGroup: BirpcGroup<ClientFunctions, ServerFunctions, false>, options?: Pick<WsRpcTransportOptions, 'onConnected' | 'onDisconnected' | 'definitions' | 'serialize' | 'deserialize'>): Partial<Hooks>;
|
|
135
216
|
/**
|
|
136
217
|
* Attach a WebSocket transport to an existing RPC group, powered by
|
|
137
218
|
* [crossws](https://crossws.h3.dev). Either attach to an existing HTTP(S)
|
|
@@ -144,4 +225,4 @@ declare function isAllowedOrigin(origin: string | undefined, allowedOrigins: rea
|
|
|
144
225
|
*/
|
|
145
226
|
declare function attachWsRpcTransport<ClientFunctions extends object, ServerFunctions extends object>(rpcGroup: BirpcGroup<ClientFunctions, ServerFunctions, false>, options?: WsRpcTransportOptions): WsRpcTransport;
|
|
146
227
|
//#endregion
|
|
147
|
-
export {
|
|
228
|
+
export { attachWsRpcTransport as a, isAllowedOrigin as c, DevframeRpcConnection as d, DevframeRpcConnectionRequest as f, WsRpcTransportOptions as i, isLoopbackHostname as l, WsOriginRegistry as n, createWsOriginRegistry as o, DevframeRpcTransportKind as p, WsRpcTransport as r, createWsRpcPeerHooks as s, CreateWsOriginRegistryOptions as t, DevframeNodeRpcSessionMeta as u };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devframe",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"description": "Framework for building
|
|
4
|
+
"version": "0.9.0-beta.10",
|
|
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",
|
|
8
8
|
"homepage": "https://github.com/devframes/devframe#readme",
|
|
@@ -22,23 +22,25 @@
|
|
|
22
22
|
".": "./dist/index.mjs",
|
|
23
23
|
"./adapters/build": "./dist/adapters/build.mjs",
|
|
24
24
|
"./adapters/cac": "./dist/adapters/cac.mjs",
|
|
25
|
-
"./adapters/cli": "./dist/adapters/cli.mjs",
|
|
26
25
|
"./adapters/dev": "./dist/adapters/dev.mjs",
|
|
27
26
|
"./adapters/embedded": "./dist/adapters/embedded.mjs",
|
|
28
27
|
"./adapters/mcp": "./dist/adapters/mcp.mjs",
|
|
29
28
|
"./client": "./dist/client/index.mjs",
|
|
30
29
|
"./constants": "./dist/constants.mjs",
|
|
31
|
-
"./
|
|
30
|
+
"./initiate": "./dist/adapters/initiate.mjs",
|
|
31
|
+
"./internal": "./dist/internal/index.mjs",
|
|
32
32
|
"./node": "./dist/node/index.mjs",
|
|
33
33
|
"./node/auth": "./dist/node/auth.mjs",
|
|
34
34
|
"./node/hub-internals": "./dist/node/hub-internals.mjs",
|
|
35
35
|
"./recipes/common-rpc-functions": "./dist/recipes/common-rpc-functions.mjs",
|
|
36
36
|
"./recipes/interactive-auth": "./dist/recipes/interactive-auth.mjs",
|
|
37
|
-
"./recipes/open-helpers": "./dist/recipes/open-helpers.mjs",
|
|
38
37
|
"./rpc": "./dist/rpc/index.mjs",
|
|
39
38
|
"./rpc/client": "./dist/rpc/client.mjs",
|
|
40
39
|
"./rpc/dump": "./dist/rpc/dump.mjs",
|
|
41
40
|
"./rpc/server": "./dist/rpc/server.mjs",
|
|
41
|
+
"./rpc/transports/sse-client": "./dist/rpc/transports/sse-client.mjs",
|
|
42
|
+
"./rpc/transports/sse-server": "./dist/rpc/transports/sse-server.mjs",
|
|
43
|
+
"./rpc/transports/ws-bun": "./dist/rpc/transports/ws-bun.mjs",
|
|
42
44
|
"./rpc/transports/ws-client": "./dist/rpc/transports/ws-client.mjs",
|
|
43
45
|
"./rpc/transports/ws-server": "./dist/rpc/transports/ws-server.mjs",
|
|
44
46
|
"./types": "./dist/types/index.mjs",
|
|
@@ -46,13 +48,14 @@
|
|
|
46
48
|
"./utils/colors": "./dist/utils/colors.mjs",
|
|
47
49
|
"./utils/crypto-token": "./dist/utils/crypto-token.mjs",
|
|
48
50
|
"./utils/events": "./dist/utils/events.mjs",
|
|
51
|
+
"./utils/get-port": "./dist/utils/get-port.mjs",
|
|
49
52
|
"./utils/hash": "./dist/utils/hash.mjs",
|
|
50
53
|
"./utils/launch-editor": "./dist/utils/launch-editor.mjs",
|
|
51
54
|
"./utils/nanoid": "./dist/utils/nanoid.mjs",
|
|
55
|
+
"./utils/nostics": "./dist/utils/nostics.mjs",
|
|
52
56
|
"./utils/open": "./dist/utils/open.mjs",
|
|
53
|
-
"./utils/
|
|
57
|
+
"./utils/remote-assets": "./dist/utils/remote-assets.mjs",
|
|
54
58
|
"./utils/simple-schema": "./dist/utils/simple-schema.mjs",
|
|
55
|
-
"./utils/scope": "./dist/utils/scope.mjs",
|
|
56
59
|
"./utils/serve-static": "./dist/utils/serve-static.mjs",
|
|
57
60
|
"./utils/shared-state": "./dist/utils/shared-state.mjs",
|
|
58
61
|
"./utils/streaming-channel": "./dist/utils/streaming-channel.mjs",
|
|
@@ -87,9 +90,8 @@
|
|
|
87
90
|
},
|
|
88
91
|
"dependencies": {
|
|
89
92
|
"@standard-schema/spec": "^1.1.0",
|
|
90
|
-
"birpc": "^4.
|
|
93
|
+
"birpc": "^4.1.0",
|
|
91
94
|
"crossws": "^0.4.10",
|
|
92
|
-
"destr": "^2.0.5",
|
|
93
95
|
"h3": "^2.0.1-rc.26",
|
|
94
96
|
"mrmime": "^2.0.1",
|
|
95
97
|
"nostics": "^1.2.0",
|
|
@@ -101,12 +103,11 @@
|
|
|
101
103
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
102
104
|
"cac": "^7.0.0",
|
|
103
105
|
"get-port-please": "^3.2.0",
|
|
104
|
-
"immer": "^11.1.
|
|
106
|
+
"immer": "^11.1.16",
|
|
105
107
|
"launch-editor": "^2.14.1",
|
|
106
108
|
"mlly": "^1.8.2",
|
|
107
109
|
"obug": "^2.1.4",
|
|
108
110
|
"ohash": "^2.0.11",
|
|
109
|
-
"open": "^11.0.0",
|
|
110
111
|
"p-limit": "^7.3.1",
|
|
111
112
|
"perfect-debounce": "^2.1.0",
|
|
112
113
|
"structured-clone-es": "^2.0.1",
|
|
@@ -115,7 +116,7 @@
|
|
|
115
116
|
"ua-parser-modern": "^0.1.1",
|
|
116
117
|
"valibot": "^1.4.2",
|
|
117
118
|
"whenexpr": "^0.1.2",
|
|
118
|
-
"ws": "^8.21.
|
|
119
|
+
"ws": "^8.21.3"
|
|
119
120
|
},
|
|
120
121
|
"scripts": {
|
|
121
122
|
"build": "tsdown",
|