@zap-proto/web 0.1.0
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/LICENSE +21 -0
- package/README.md +142 -0
- package/dist/auth.d.ts +22 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +50 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +105 -0
- package/dist/client.js.map +1 -0
- package/dist/conn.d.ts +60 -0
- package/dist/conn.d.ts.map +1 -0
- package/dist/conn.js +145 -0
- package/dist/conn.js.map +1 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +27 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +35 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +101 -0
- package/dist/server.js.map +1 -0
- package/dist/transport.d.ts +46 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +140 -0
- package/dist/transport.js.map +1 -0
- package/package.json +76 -0
- package/src/auth.ts +25 -0
- package/src/client.ts +165 -0
- package/src/conn.ts +172 -0
- package/src/errors.ts +30 -0
- package/src/index.ts +36 -0
- package/src/server.ts +146 -0
- package/src/transport.ts +173 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zap-proto/web — browser-frontend RPC over ZAP.
|
|
3
|
+
*
|
|
4
|
+
* A drop-in tRPC replacement that speaks native ZAP envelopes over WebSocket
|
|
5
|
+
* binary frames instead of JSON-over-HTTP. Layered on @zap-proto/zap (the wire
|
|
6
|
+
* runtime). Service shape comes from .zap schemas via `zapgen --target=ts`, not
|
|
7
|
+
* a Zod/procedure DSL. Binary only — no JSON fallback, no Cap'n Proto.
|
|
8
|
+
*
|
|
9
|
+
* - serve(httpServer, opts) — Node: attach a ZAP RPC endpoint to http.Server.
|
|
10
|
+
* - connect(url, opts) — browser/Node: open a typed RPC connection.
|
|
11
|
+
* - {node,browser}WsTransport — isomorphic WS transport factories.
|
|
12
|
+
* - MintCap — the bearer→ctx auth slot at the upgrade.
|
|
13
|
+
*/
|
|
14
|
+
export { serve } from "./server.js";
|
|
15
|
+
export type { ServeOptions, ServeHandle, RootCap } from "./server.js";
|
|
16
|
+
export { connect } from "./client.js";
|
|
17
|
+
export type { ConnectOptions, Connection } from "./client.js";
|
|
18
|
+
export { nodeWsTransport, browserWsTransport, WS_CLOSE_UNSUPPORTED, } from "./transport.js";
|
|
19
|
+
export type { WsTransport } from "./transport.js";
|
|
20
|
+
export { Conn } from "./conn.js";
|
|
21
|
+
export type { CallHandler, CallOptions } from "./conn.js";
|
|
22
|
+
export type { MintCap } from "./auth.js";
|
|
23
|
+
export { WebRpcError, ZapParseError } from "./errors.js";
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE1D,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
2
|
+
// See the file LICENSE for licensing terms.
|
|
3
|
+
/**
|
|
4
|
+
* @zap-proto/web — browser-frontend RPC over ZAP.
|
|
5
|
+
*
|
|
6
|
+
* A drop-in tRPC replacement that speaks native ZAP envelopes over WebSocket
|
|
7
|
+
* binary frames instead of JSON-over-HTTP. Layered on @zap-proto/zap (the wire
|
|
8
|
+
* runtime). Service shape comes from .zap schemas via `zapgen --target=ts`, not
|
|
9
|
+
* a Zod/procedure DSL. Binary only — no JSON fallback, no Cap'n Proto.
|
|
10
|
+
*
|
|
11
|
+
* - serve(httpServer, opts) — Node: attach a ZAP RPC endpoint to http.Server.
|
|
12
|
+
* - connect(url, opts) — browser/Node: open a typed RPC connection.
|
|
13
|
+
* - {node,browser}WsTransport — isomorphic WS transport factories.
|
|
14
|
+
* - MintCap — the bearer→ctx auth slot at the upgrade.
|
|
15
|
+
*/
|
|
16
|
+
export { serve } from "./server.js";
|
|
17
|
+
export { connect } from "./client.js";
|
|
18
|
+
export { nodeWsTransport, browserWsTransport, WS_CLOSE_UNSUPPORTED, } from "./transport.js";
|
|
19
|
+
export { Conn } from "./conn.js";
|
|
20
|
+
export { WebRpcError, ZapParseError } from "./errors.js";
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,4CAA4C;AAE5C;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGpC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGtC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Server as HttpServer } from "node:http";
|
|
2
|
+
import type { CallHandler } from "./conn.js";
|
|
3
|
+
import type { MintCap } from "./auth.js";
|
|
4
|
+
/**
|
|
5
|
+
* rootCap produces the per-connection dispatch root for a minted ctx.
|
|
6
|
+
*
|
|
7
|
+
* On the real @zap-proto/zap foundation a "service" is a dispatcher over decoded
|
|
8
|
+
* Calls (method ordinal + payload), not a Cap'n Proto Server object — so the
|
|
9
|
+
* bootstrap capability is modelled here as a {@link CallHandler}. Generated
|
|
10
|
+
* zapgen bindings give you the typed param/result views to decode the payload
|
|
11
|
+
* and encode the response body inside that handler.
|
|
12
|
+
*/
|
|
13
|
+
export type RootCap<Ctx> = (ctx: Ctx) => CallHandler;
|
|
14
|
+
/** Options for {@link serve}. */
|
|
15
|
+
export interface ServeOptions<Ctx> {
|
|
16
|
+
/** Upgrade path this endpoint binds to (default "/zap"). */
|
|
17
|
+
path?: string;
|
|
18
|
+
/** Bearer→ctx boundary; return null to reject the upgrade with HTTP 401. */
|
|
19
|
+
mintCap: MintCap<Ctx>;
|
|
20
|
+
/** Produce the bootstrap dispatch handler for the minted ctx. */
|
|
21
|
+
rootCap: RootCap<Ctx>;
|
|
22
|
+
/** Optional sink for connection/dispatch errors. */
|
|
23
|
+
onError?: (err: unknown) => void;
|
|
24
|
+
}
|
|
25
|
+
/** A live ZAP-over-WebSocket endpoint attached to an http.Server. */
|
|
26
|
+
export interface ServeHandle {
|
|
27
|
+
/** Close the endpoint and every live connection. */
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Attach a ZAP RPC endpoint to `httpServer`. Returns a handle whose close()
|
|
32
|
+
* tears down the WebSocket server and all open connections.
|
|
33
|
+
*/
|
|
34
|
+
export declare function serve<Ctx>(httpServer: HttpServer, opts: ServeOptions<Ctx>): ServeHandle;
|
|
35
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAmB,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,MAAM,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,WAAW,CAAC;AAErD,iCAAiC;AACjC,MAAM,WAAW,YAAY,CAAC,GAAG;IAC/B,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,oDAAoD;IACpD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAClC;AAED,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EACvB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,YAAY,CAAC,GAAG,CAAC,GACtB,WAAW,CAmEb"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
2
|
+
// See the file LICENSE for licensing terms.
|
|
3
|
+
/**
|
|
4
|
+
* server.ts — serve(httpServer, opts): attach a ZAP-over-WebSocket RPC endpoint
|
|
5
|
+
* to an existing Node http.Server.
|
|
6
|
+
*
|
|
7
|
+
* Sharing one http.Server is the integration seam: Next.js's
|
|
8
|
+
* `app.getRequestHandler()` and a Remix Node server both run on a plain
|
|
9
|
+
* http.Server, so a single `serve(server, ...)` line adds ZAP RPC alongside the
|
|
10
|
+
* app without a second port. (Same shape as the WebSocket attachments in
|
|
11
|
+
* hanzo/platform's app/platform/server/server.ts.)
|
|
12
|
+
*
|
|
13
|
+
* Auth runs at the upgrade boundary: mintCap(req) is awaited BEFORE the
|
|
14
|
+
* handshake completes. A null mint writes HTTP 401 and destroys the socket — no
|
|
15
|
+
* WebSocket is ever opened for an unauthorized request. A non-null mint becomes
|
|
16
|
+
* the per-connection `ctx`, and rootCap(ctx) yields the CallHandler that
|
|
17
|
+
* dispatches every decoded ZAP Call on that connection.
|
|
18
|
+
*
|
|
19
|
+
* `ws` is a peer dependency (server side only); we import it lazily so the
|
|
20
|
+
* browser bundle never pulls it in.
|
|
21
|
+
*/
|
|
22
|
+
import { createRequire } from "node:module";
|
|
23
|
+
import { Conn } from "./conn.js";
|
|
24
|
+
import { nodeWsTransport } from "./transport.js";
|
|
25
|
+
/**
|
|
26
|
+
* Attach a ZAP RPC endpoint to `httpServer`. Returns a handle whose close()
|
|
27
|
+
* tears down the WebSocket server and all open connections.
|
|
28
|
+
*/
|
|
29
|
+
export function serve(httpServer, opts) {
|
|
30
|
+
const path = opts.path ?? "/zap";
|
|
31
|
+
const onError = opts.onError ?? (() => { });
|
|
32
|
+
// Lazy require so browser bundles never resolve `ws`. createRequire keeps
|
|
33
|
+
// this ESM-safe (no top-level `require`) and server-only (node:module never
|
|
34
|
+
// ends up in a browser bundle, since serve() is the Node entry).
|
|
35
|
+
const req = createRequire(import.meta.url);
|
|
36
|
+
const { WebSocketServer } = req("ws");
|
|
37
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
38
|
+
const conns = new Set();
|
|
39
|
+
const onUpgrade = (req, socket, head) => {
|
|
40
|
+
let url;
|
|
41
|
+
try {
|
|
42
|
+
url = new URL(req.url ?? "/", "http://localhost");
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return; // not ours; let other upgrade listeners handle it.
|
|
46
|
+
}
|
|
47
|
+
if (url.pathname !== path)
|
|
48
|
+
return; // path mismatch — not our endpoint.
|
|
49
|
+
void (async () => {
|
|
50
|
+
let ctx;
|
|
51
|
+
try {
|
|
52
|
+
ctx = await opts.mintCap(req);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
onError(err);
|
|
56
|
+
reject(socket, 500, "Internal Server Error");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (ctx === null) {
|
|
60
|
+
reject(socket, 401, "Unauthorized");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const minted = ctx;
|
|
64
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
65
|
+
const transport = nodeWsTransport(ws);
|
|
66
|
+
let handler;
|
|
67
|
+
try {
|
|
68
|
+
handler = opts.rootCap(minted);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
onError(err);
|
|
72
|
+
transport.close(1011, "rootCap failed");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const conn = new Conn(transport, { handler });
|
|
76
|
+
conns.add(conn);
|
|
77
|
+
transport.onClose(() => conns.delete(conn));
|
|
78
|
+
ws.on("error", onError);
|
|
79
|
+
});
|
|
80
|
+
})();
|
|
81
|
+
};
|
|
82
|
+
httpServer.on("upgrade", onUpgrade);
|
|
83
|
+
return {
|
|
84
|
+
async close() {
|
|
85
|
+
httpServer.removeListener("upgrade", onUpgrade);
|
|
86
|
+
for (const conn of conns)
|
|
87
|
+
conn.close(1001, "server shutting down");
|
|
88
|
+
conns.clear();
|
|
89
|
+
await new Promise((resolve) => wss.close(() => resolve()));
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Write a bare HTTP error response on the raw upgrade socket and destroy it. */
|
|
94
|
+
function reject(socket, code, text) {
|
|
95
|
+
socket.write(`HTTP/1.1 ${code} ${text}\r\n` +
|
|
96
|
+
"Connection: close\r\n" +
|
|
97
|
+
"Content-Length: 0\r\n" +
|
|
98
|
+
"\r\n");
|
|
99
|
+
socket.destroy();
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* transport.ts — an isomorphic WebSocket transport for ZAP RPC frames.
|
|
3
|
+
*
|
|
4
|
+
* The foundation runtime (@zap-proto/zap) frames a call as a single ZAP envelope
|
|
5
|
+
* (envelope.ts buildRequest / buildResponse). Over TCP, @zap-proto/zap's ZapClient
|
|
6
|
+
* length-prefixes each envelope to recover message boundaries. WebSocket binary
|
|
7
|
+
* frames already give us message boundaries for free, so the framing here is
|
|
8
|
+
* one ZAP envelope = one WS binary message — no extra length prefix.
|
|
9
|
+
*
|
|
10
|
+
* A single internal impl (makeWsTransport) feature-detects whether it is given
|
|
11
|
+
* the `ws` package WebSocket (Node, post-upgrade) or the browser's native
|
|
12
|
+
* WebSocket, and normalises both to the same {@link WsTransport} shape. Bytes
|
|
13
|
+
* in arrive as ArrayBuffer or Uint8Array (or, on Node `ws`, a Buffer) and are
|
|
14
|
+
* normalised to Uint8Array; bytes out are written as Uint8Array.
|
|
15
|
+
*
|
|
16
|
+
* Cap'n Proto-style text frames are a protocol violation: ZAP is binary-only.
|
|
17
|
+
* A received text frame closes the connection with WebSocket close code 1003
|
|
18
|
+
* (Unsupported Data).
|
|
19
|
+
*/
|
|
20
|
+
/** WebSocket close code for a non-binary (text) frame — Unsupported Data. */
|
|
21
|
+
export declare const WS_CLOSE_UNSUPPORTED = 1003;
|
|
22
|
+
/**
|
|
23
|
+
* WsTransport is the byte pipe a ZAP RPC connection rides on. It is duplex and
|
|
24
|
+
* message-oriented: every {@link send} ships exactly one ZAP envelope, and the
|
|
25
|
+
* {@link onMessage} callback fires once per inbound envelope.
|
|
26
|
+
*/
|
|
27
|
+
export interface WsTransport {
|
|
28
|
+
/** Ship one ZAP envelope as a single binary WebSocket message. */
|
|
29
|
+
send(bytes: Uint8Array): void;
|
|
30
|
+
/** Register the inbound-envelope handler. At most one is active. */
|
|
31
|
+
onMessage(handler: (bytes: Uint8Array) => void): void;
|
|
32
|
+
/** Register the close handler (fires once, on either side closing). */
|
|
33
|
+
onClose(handler: (code: number, reason: string) => void): void;
|
|
34
|
+
/** Close the underlying socket. */
|
|
35
|
+
close(code?: number, reason?: string): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Wrap a Node `ws` package WebSocket (after the HTTP upgrade completes) as a
|
|
39
|
+
* {@link WsTransport}. Server side.
|
|
40
|
+
*/
|
|
41
|
+
export declare function nodeWsTransport(ws: unknown): WsTransport;
|
|
42
|
+
/**
|
|
43
|
+
* Wrap the browser's native WebSocket as a {@link WsTransport}. Client side.
|
|
44
|
+
*/
|
|
45
|
+
export declare function browserWsTransport(ws: unknown): WsTransport;
|
|
46
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,oEAAoE;IACpE,SAAS,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI,CAAC;IACtD,uEAAuE;IACvE,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/D,mCAAmC;IACnC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7C;AAuHD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,OAAO,GAAG,WAAW,CAExD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,OAAO,GAAG,WAAW,CAE3D"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
2
|
+
// See the file LICENSE for licensing terms.
|
|
3
|
+
/**
|
|
4
|
+
* transport.ts — an isomorphic WebSocket transport for ZAP RPC frames.
|
|
5
|
+
*
|
|
6
|
+
* The foundation runtime (@zap-proto/zap) frames a call as a single ZAP envelope
|
|
7
|
+
* (envelope.ts buildRequest / buildResponse). Over TCP, @zap-proto/zap's ZapClient
|
|
8
|
+
* length-prefixes each envelope to recover message boundaries. WebSocket binary
|
|
9
|
+
* frames already give us message boundaries for free, so the framing here is
|
|
10
|
+
* one ZAP envelope = one WS binary message — no extra length prefix.
|
|
11
|
+
*
|
|
12
|
+
* A single internal impl (makeWsTransport) feature-detects whether it is given
|
|
13
|
+
* the `ws` package WebSocket (Node, post-upgrade) or the browser's native
|
|
14
|
+
* WebSocket, and normalises both to the same {@link WsTransport} shape. Bytes
|
|
15
|
+
* in arrive as ArrayBuffer or Uint8Array (or, on Node `ws`, a Buffer) and are
|
|
16
|
+
* normalised to Uint8Array; bytes out are written as Uint8Array.
|
|
17
|
+
*
|
|
18
|
+
* Cap'n Proto-style text frames are a protocol violation: ZAP is binary-only.
|
|
19
|
+
* A received text frame closes the connection with WebSocket close code 1003
|
|
20
|
+
* (Unsupported Data).
|
|
21
|
+
*/
|
|
22
|
+
/** WebSocket close code for a non-binary (text) frame — Unsupported Data. */
|
|
23
|
+
export const WS_CLOSE_UNSUPPORTED = 1003;
|
|
24
|
+
/** Normalise any WS payload shape to a Uint8Array, or null for a text frame. */
|
|
25
|
+
function toBytes(data) {
|
|
26
|
+
if (data instanceof Uint8Array)
|
|
27
|
+
return data;
|
|
28
|
+
if (data instanceof ArrayBuffer)
|
|
29
|
+
return new Uint8Array(data);
|
|
30
|
+
if (ArrayBuffer.isView(data)) {
|
|
31
|
+
const v = data;
|
|
32
|
+
return new Uint8Array(v.buffer, v.byteOffset, v.byteLength);
|
|
33
|
+
}
|
|
34
|
+
// Node `ws` can hand back an array of Buffers when fragmented; coalesce.
|
|
35
|
+
if (Array.isArray(data)) {
|
|
36
|
+
const parts = data.map(toBytes);
|
|
37
|
+
if (parts.some((p) => p === null))
|
|
38
|
+
return null;
|
|
39
|
+
const total = parts.reduce((n, p) => n + p.byteLength, 0);
|
|
40
|
+
const out = new Uint8Array(total);
|
|
41
|
+
let off = 0;
|
|
42
|
+
for (const p of parts) {
|
|
43
|
+
out.set(p, off);
|
|
44
|
+
off += p.byteLength;
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
// A string payload is a text frame — a protocol violation for ZAP.
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Wrap a socket (browser native WebSocket or Node `ws` WebSocket) as a
|
|
53
|
+
* {@link WsTransport}. Branches on whether the socket exposes the browser
|
|
54
|
+
* `addEventListener` API or the Node `ws` `on(...)` emitter API.
|
|
55
|
+
*/
|
|
56
|
+
function makeWsTransport(socket) {
|
|
57
|
+
// Ask for ArrayBuffer payloads where the API allows it (browser + `ws`),
|
|
58
|
+
// so toBytes has a fast path and never sees a Blob.
|
|
59
|
+
if ("binaryType" in socket)
|
|
60
|
+
socket.binaryType = "arraybuffer";
|
|
61
|
+
let onMsg = () => { };
|
|
62
|
+
let onClose = () => { };
|
|
63
|
+
let closed = false;
|
|
64
|
+
const handleData = (data) => {
|
|
65
|
+
const bytes = toBytes(data);
|
|
66
|
+
if (bytes === null) {
|
|
67
|
+
// Text frame (or unrecognised payload): reject per ZAP binary-only rule.
|
|
68
|
+
if (!closed) {
|
|
69
|
+
closed = true;
|
|
70
|
+
socket.close(WS_CLOSE_UNSUPPORTED, "zap: binary frames only");
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
onMsg(bytes);
|
|
75
|
+
};
|
|
76
|
+
const handleClose = (code, reason) => {
|
|
77
|
+
if (closed)
|
|
78
|
+
return;
|
|
79
|
+
closed = true;
|
|
80
|
+
onClose(code, reason);
|
|
81
|
+
};
|
|
82
|
+
if (typeof socket.addEventListener === "function") {
|
|
83
|
+
// Browser native WebSocket.
|
|
84
|
+
socket.addEventListener("message", (ev) => handleData(ev.data));
|
|
85
|
+
socket.addEventListener("close", (ev) => {
|
|
86
|
+
const ce = ev;
|
|
87
|
+
handleClose(ce.code ?? 1006, ce.reason ?? "");
|
|
88
|
+
});
|
|
89
|
+
socket.addEventListener("error", () => handleClose(1006, "error"));
|
|
90
|
+
}
|
|
91
|
+
else if (typeof socket.on === "function") {
|
|
92
|
+
// Node `ws` WebSocket.
|
|
93
|
+
socket.on("message", (data, isBinary) => {
|
|
94
|
+
// `ws` passes (data, isBinary); a false isBinary is a text frame.
|
|
95
|
+
if (isBinary === false) {
|
|
96
|
+
handleData("text");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
handleData(data);
|
|
100
|
+
});
|
|
101
|
+
socket.on("close", (code, reason) => handleClose(typeof code === "number" ? code : 1006, reason ? String(reason) : ""));
|
|
102
|
+
socket.on("error", () => handleClose(1006, "error"));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
throw new TypeError("zap-web: unsupported WebSocket implementation");
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
send(bytes) {
|
|
109
|
+
if (closed)
|
|
110
|
+
throw new Error("zap-web: transport closed");
|
|
111
|
+
socket.send(bytes);
|
|
112
|
+
},
|
|
113
|
+
onMessage(handler) {
|
|
114
|
+
onMsg = handler;
|
|
115
|
+
},
|
|
116
|
+
onClose(handler) {
|
|
117
|
+
onClose = handler;
|
|
118
|
+
},
|
|
119
|
+
close(code, reason) {
|
|
120
|
+
if (closed)
|
|
121
|
+
return;
|
|
122
|
+
closed = true;
|
|
123
|
+
socket.close(code, reason);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Wrap a Node `ws` package WebSocket (after the HTTP upgrade completes) as a
|
|
129
|
+
* {@link WsTransport}. Server side.
|
|
130
|
+
*/
|
|
131
|
+
export function nodeWsTransport(ws) {
|
|
132
|
+
return makeWsTransport(ws);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Wrap the browser's native WebSocket as a {@link WsTransport}. Client side.
|
|
136
|
+
*/
|
|
137
|
+
export function browserWsTransport(ws) {
|
|
138
|
+
return makeWsTransport(ws);
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zap-proto/web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser frontend RPC over ZAP — a drop-in tRPC replacement for Next.js / Remix / SvelteKit. Native ZAP envelopes over WebSocket binary frames; layered on @zap-proto/zap. No Cap'n Proto, no JSON.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./server": {
|
|
15
|
+
"types": "./dist/server.d.ts",
|
|
16
|
+
"import": "./dist/server.js",
|
|
17
|
+
"default": "./dist/server.js"
|
|
18
|
+
},
|
|
19
|
+
"./client": {
|
|
20
|
+
"types": "./dist/client.d.ts",
|
|
21
|
+
"import": "./dist/client.js",
|
|
22
|
+
"default": "./dist/client.js"
|
|
23
|
+
},
|
|
24
|
+
"./transport": {
|
|
25
|
+
"types": "./dist/transport.d.ts",
|
|
26
|
+
"import": "./dist/transport.js",
|
|
27
|
+
"default": "./dist/transport.js"
|
|
28
|
+
},
|
|
29
|
+
"./auth": {
|
|
30
|
+
"types": "./dist/auth.d.ts",
|
|
31
|
+
"import": "./dist/auth.js",
|
|
32
|
+
"default": "./dist/auth.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"main": "./dist/index.js",
|
|
36
|
+
"module": "./dist/index.js",
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"src"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@zap-proto/zap": "^1.1.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"ws": ">=8"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"ws": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^22.0.0",
|
|
55
|
+
"@types/ws": "^8.5.12",
|
|
56
|
+
"eslint": "^9.0.0",
|
|
57
|
+
"prettier": "^3.3.0",
|
|
58
|
+
"typescript": "^5.9.3",
|
|
59
|
+
"vitest": "^4.1.4",
|
|
60
|
+
"ws": "^8.18.0"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=18"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsc -p tsconfig.build.json",
|
|
70
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
71
|
+
"typecheck": "tsc --noEmit",
|
|
72
|
+
"test": "vitest run",
|
|
73
|
+
"lint": "eslint src test",
|
|
74
|
+
"format": "prettier --write src test"
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
2
|
+
// See the file LICENSE for licensing terms.
|
|
3
|
+
|
|
4
|
+
import type { IncomingMessage } from "node:http";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MintCap is the boundary where a bearer (cookie, header, session) becomes a
|
|
8
|
+
* typed Capability that downstream RPC methods are authorized against.
|
|
9
|
+
*
|
|
10
|
+
* v0 ships the SLOT — apps supply their own fn. The canonical ML-DSA-65 signed
|
|
11
|
+
* Capability mint (per zap-spec/capabilities.zap — the 3408-byte signature
|
|
12
|
+
* footer, BLAKE3 object identity, attenuable/revocable authority) lands in a
|
|
13
|
+
* later version. Until then you can return any object: whatever non-null value
|
|
14
|
+
* you return becomes the `ctx` threaded into your `rootCap(ctx)` factory and
|
|
15
|
+
* carried on every {@link import("@zap-proto/zap").Call} as its capability buffer.
|
|
16
|
+
*
|
|
17
|
+
* Return `null` to reject the connection with HTTP 401 before the WebSocket
|
|
18
|
+
* upgrade completes — no socket is opened for an unauthorized request.
|
|
19
|
+
*
|
|
20
|
+
* @typeParam Ctx - the minted authority/context type your handlers consume.
|
|
21
|
+
* @param req - the raw HTTP upgrade request (read `req.headers.authorization`,
|
|
22
|
+
* cookies, etc.).
|
|
23
|
+
* @returns the minted context, or `null` to reject with 401.
|
|
24
|
+
*/
|
|
25
|
+
export type MintCap<Ctx> = (req: IncomingMessage) => Promise<Ctx | null>;
|