@trevrpc/trevrpc-js 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 +202 -0
- package/bin/protoc-gen-trevrpc-js.js +17 -0
- package/examples/greeter/README.md +35 -0
- package/examples/greeter/client.js +172 -0
- package/examples/greeter/greeter.node.trevrpc.d.ts +53 -0
- package/examples/greeter/greeter.node.trevrpc.js +9 -0
- package/examples/greeter/greeter.proto +18 -0
- package/examples/greeter/greeter.trevrpc.d.ts +55 -0
- package/examples/greeter/greeter.trevrpc.js +103 -0
- package/examples/greeter/index.html +140 -0
- package/examples/greeter/static-server.js +168 -0
- package/package.json +89 -0
- package/src/advanced.d.ts +29 -0
- package/src/advanced.js +1 -0
- package/src/browser-channel.js +45 -0
- package/src/channel.js +375 -0
- package/src/client.js +1397 -0
- package/src/connect.js +6 -0
- package/src/connect.node.js +6 -0
- package/src/framing.js +618 -0
- package/src/generator.js +868 -0
- package/src/index.d.ts +577 -0
- package/src/index.js +3 -0
- package/src/index.node.d.ts +10 -0
- package/src/index.node.js +3 -0
- package/src/metadata.js +112 -0
- package/src/native-loader.js +94 -0
- package/src/node-advanced.d.ts +27 -0
- package/src/node-advanced.js +1 -0
- package/src/node-generated.d.ts +24 -0
- package/src/node-generated.js +126 -0
- package/src/node-index.d.ts +1 -0
- package/src/node-index.js +7 -0
- package/src/node.d.ts +298 -0
- package/src/node.js +1456 -0
- package/src/runtime.js +66 -0
- package/src/server-response.js +49 -0
- package/src/status.js +145 -0
- package/src/timer.js +29 -0
- package/src/webtransport.js +624 -0
- package/src/wire.js +403 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 trev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# trevrpc-js
|
|
2
|
+
|
|
3
|
+
`trevrpc-js` is the ESM-only JavaScript runtime and protobuf.js generator for TrevRPC.
|
|
4
|
+
|
|
5
|
+
## Generated files
|
|
6
|
+
|
|
7
|
+
For every protobuf file containing services, `protoc-gen-trevrpc-js` emits an ordinary browser-safe client module and a separate Node server module:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
greeter.trevrpc.js
|
|
11
|
+
greeter.trevrpc.d.ts
|
|
12
|
+
greeter.node.trevrpc.js
|
|
13
|
+
greeter.node.trevrpc.d.ts
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The ordinary module contains the protobuf root, service descriptors, typed clients, and client factories. The Node companion contains typed handler interfaces and registration helpers backed by `@trevrpc/trevrpc-js/node/generated`.
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
protoc \
|
|
20
|
+
--plugin=protoc-gen-trevrpc-js=./node_modules/.bin/protoc-gen-trevrpc-js \
|
|
21
|
+
--trevrpc-js_out=. \
|
|
22
|
+
greeter.proto
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Generator options are `runtime_import`, `runtime_type_import`, `node_runtime_import`, `node_runtime_type_import`, and `file_suffix`. Runtime type imports default to their matching runtime import for bare and relative specifiers. URL specifiers such as `file:` require an explicit type import because TypeScript NodeNext does not resolve them in declarations. A Node import is never inferred by appending `/node` to another specifier.
|
|
26
|
+
|
|
27
|
+
## Browser clients
|
|
28
|
+
|
|
29
|
+
Browser declarations accept only `BrowserChannelOptions`.
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { connect } from "@trevrpc/trevrpc-js";
|
|
33
|
+
import { GreeterClient } from "./greeter.trevrpc.js";
|
|
34
|
+
|
|
35
|
+
const channel = await connect("https://localhost:50051/trevrpc", {
|
|
36
|
+
timeoutMs: 10_000,
|
|
37
|
+
congestionControl: "low-latency",
|
|
38
|
+
serverCertificateHashes: [{ algorithm: "sha-256", value: certificateHash }],
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
const client = new GreeterClient(channel, { timeoutMs: 5_000 });
|
|
42
|
+
const reply = await client.sayHello({ name: "TrevRPC" });
|
|
43
|
+
console.log(reply.message);
|
|
44
|
+
} finally {
|
|
45
|
+
channel.close({ closeCode: 0, reason: "client shutdown" });
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Native TLS bypass, certificate validation, MsQuic buffering, and native stream options are intentionally unavailable under browser or Bundler resolution.
|
|
50
|
+
|
|
51
|
+
## Node clients
|
|
52
|
+
|
|
53
|
+
Under Node conditions, the root package uses the native channel. The explicit `@trevrpc/trevrpc-js/node` subpath also exports `Channel`, `NodeServer`, and `NodeServerCall`.
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
import { connect } from "@trevrpc/trevrpc-js";
|
|
57
|
+
import { GreeterClient } from "./greeter.trevrpc.js";
|
|
58
|
+
|
|
59
|
+
const channel = await connect("https://localhost:50051", {
|
|
60
|
+
timeoutMs: 10_000,
|
|
61
|
+
caCertFile: "localhost-cert.pem",
|
|
62
|
+
});
|
|
63
|
+
try {
|
|
64
|
+
console.log(channel.endpoint); // frozen { host, port }
|
|
65
|
+
console.log(channel.options); // frozen effective NodeChannelOptions
|
|
66
|
+
const client = new GreeterClient(channel, { timeoutMs: 5_000 });
|
|
67
|
+
console.log((await client.sayHello({ name: "TrevRPC" })).message);
|
|
68
|
+
} finally {
|
|
69
|
+
channel.close();
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
URL and `{ host, port, ...options }` forms normalize to the same public state. Channels reconnect for future calls after native transport loss, but never replay or resume an operation that already failed.
|
|
74
|
+
|
|
75
|
+
## Streaming clients
|
|
76
|
+
|
|
77
|
+
Server-streaming methods resolve to `ResponseAsyncIterable<T>`:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
const replies = await client.lotsOfReplies({ name: "TrevRPC" });
|
|
81
|
+
try {
|
|
82
|
+
for await (const reply of replies) {
|
|
83
|
+
console.log(reply.message);
|
|
84
|
+
}
|
|
85
|
+
const terminal = await replies.status;
|
|
86
|
+
console.log(terminal.code, terminal.message, terminal.metadata);
|
|
87
|
+
} finally {
|
|
88
|
+
await replies.close();
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Full clean consumption resolves `status`. A non-OK terminal status rejects iteration and `status` with the same `TrevRpcError`. `close()`, iterator `return()`, and breaking a `for await` loop cancel the stream and reject `status` with `Code.Cancelled`. Consuming a valid terminal frame claims terminal precedence and cancels the absolute deadline, but `status` settles only after trailing EOF is validated; merely buffering a status frame is not completion.
|
|
93
|
+
|
|
94
|
+
The status/error text distinction is deliberate:
|
|
95
|
+
|
|
96
|
+
- `StreamStatus.message` is the unmodified peer terminal message.
|
|
97
|
+
- `TrevRpcError.statusMessage` is the unmodified peer error message.
|
|
98
|
+
- `TrevRpcError.message` is formatted JavaScript text such as `"Unavailable: down"`.
|
|
99
|
+
|
|
100
|
+
Unary and client-streaming terminal metadata is available through `sayHelloWithResponse()` and `closeAndRecvWithResponse()`.
|
|
101
|
+
|
|
102
|
+
Client streaming:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
const call = await client.lotsOfGreetings();
|
|
106
|
+
await call.send({ name: "Alice" });
|
|
107
|
+
await call.send({ name: "Bob" });
|
|
108
|
+
const { message, metadata } = await call.closeAndRecvWithResponse();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Bidirectional streaming:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const call = await client.bidiHello();
|
|
115
|
+
try {
|
|
116
|
+
await call.send({ name: "Alice" });
|
|
117
|
+
console.log(await call.recv());
|
|
118
|
+
await call.closeSend();
|
|
119
|
+
while ((await call.recv()) != null) {}
|
|
120
|
+
console.log(await call.status);
|
|
121
|
+
} finally {
|
|
122
|
+
await call.close();
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`timeoutMs` is one absolute RPC deadline that begins before transport setup and covers upload, send, receive, and cleanup. The same `DeadlineExceeded` object wins signal and rejection settlement. `streamIdleTimeoutMs` remains an independent per-read inactivity bound.
|
|
127
|
+
|
|
128
|
+
## Typed Node servers
|
|
129
|
+
|
|
130
|
+
Import the generated registration helper from the Node companion. Direct protobuf responses are never confused with metadata envelopes, even when they have `message` or `messages` properties.
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
import { Code, createStreamingResponse, createUnaryResponse } from "@trevrpc/trevrpc-js";
|
|
134
|
+
import { NodeServer } from "@trevrpc/trevrpc-js/node";
|
|
135
|
+
import { registerGreeterServer } from "./greeter.node.trevrpc.js";
|
|
136
|
+
|
|
137
|
+
const server = await NodeServer.listen({
|
|
138
|
+
host: "127.0.0.1",
|
|
139
|
+
port: 50051,
|
|
140
|
+
certFile: "localhost-cert.pem",
|
|
141
|
+
keyFile: "localhost-key.pem",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
registerGreeterServer(server, {
|
|
145
|
+
sayHello(request, call) {
|
|
146
|
+
console.log(call.deadline, call.signal.aborted);
|
|
147
|
+
return createUnaryResponse(
|
|
148
|
+
{ message: `Hello, ${request.name || "world"}` },
|
|
149
|
+
{ trailer: "unary" },
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
async lotsOfGreetings(requests) {
|
|
154
|
+
const names = [];
|
|
155
|
+
for await (const request of requests) names.push(request.name);
|
|
156
|
+
return createUnaryResponse({ message: names.join(", ") }, { trailer: "client-stream" });
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
lotsOfReplies(request) {
|
|
160
|
+
return createStreamingResponse([{ message: request.name }], {
|
|
161
|
+
code: Code.Ok,
|
|
162
|
+
message: "complete",
|
|
163
|
+
metadata: { trailer: "server-stream" },
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
async *bidiHello(requests) {
|
|
168
|
+
for await (const request of requests) yield { message: request.name };
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await server.serve();
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Client-streaming completion uses streaming wire semantics: exactly one response message followed by exactly one terminal status; it never uses unary `respond()`.
|
|
176
|
+
|
|
177
|
+
`NodeServerCall.signal` covers effective deadlines, server shutdown, local close, and cancellation observed by native I/O. The current native bridge cannot promptly report remote peer cancellation while a handler is idle and performs no call I/O; cancellation becomes observable when the handler next enters native call I/O.
|
|
178
|
+
|
|
179
|
+
## Native package support
|
|
180
|
+
|
|
181
|
+
The core tarball is platform-neutral. Native code is supplied by the exact optional package `@trevrpc/trevrpc-js-native-linux-x64-gnu@0.1.0`.
|
|
182
|
+
|
|
183
|
+
Initial support is Linux x86-64, glibc 2.42 or newer, and Node 24. Browser usage still installs and runs on Darwin, ARM, and musl. Requesting a native Node channel on an unsupported or incomplete installation throws `TrevRpcError(Code.Unavailable)` with the detected target, supported targets, expected optional package, whether loading was missing or failed, and the original loader error as `cause`.
|
|
184
|
+
|
|
185
|
+
All addon errors crossing public Node transport and server boundaries become `TrevRpcError` unless already normalized. `nativeCode` and `cause` preserve native provenance.
|
|
186
|
+
|
|
187
|
+
## Package resolution
|
|
188
|
+
|
|
189
|
+
Conditional ESM exports are authoritative; the package has no legacy top-level `main` or `types`. Use TypeScript NodeNext resolution for Node consumers and Bundler resolution with browser conditions for browser consumers.
|
|
190
|
+
|
|
191
|
+
## Native development
|
|
192
|
+
|
|
193
|
+
Installation never builds native code. In a source checkout:
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
npm run build:native:test
|
|
197
|
+
npm test
|
|
198
|
+
npm run build:native
|
|
199
|
+
npm run verify:native:production
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The development fallback is only `build/native/trevrpc_native.node` in a source checkout.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/nix/store/zvj0hl7rhh0ccr5vkcg3ijs3xm3sgyac-nodejs-24.16.0/bin/node
|
|
2
|
+
|
|
3
|
+
import { runGenerator } from "../src/generator.js";
|
|
4
|
+
|
|
5
|
+
const chunks = [];
|
|
6
|
+
|
|
7
|
+
for await (const chunk of process.stdin) {
|
|
8
|
+
chunks.push(chunk);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const output = runGenerator(Buffer.concat(chunks));
|
|
13
|
+
process.stdout.write(output);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(`protoc-gen-trevrpc-js: ${error.message}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Greeter example
|
|
2
|
+
|
|
3
|
+
This directory contains the generated Milestone 6 split:
|
|
4
|
+
|
|
5
|
+
- `greeter.trevrpc.js` and `.d.ts`: protobuf root, descriptors, typed clients, and factories; browser-safe.
|
|
6
|
+
- `greeter.node.trevrpc.js` and `.d.ts`: typed Node handler interfaces and `registerGreeterServer`.
|
|
7
|
+
|
|
8
|
+
Browser code must import only `greeter.trevrpc.js`. Node servers import registration from `greeter.node.trevrpc.js` and use `createUnaryResponse` or `createStreamingResponse` when returning metadata or a custom terminal status. A direct protobuf object such as `{ message: "hello" }` remains a protobuf response.
|
|
9
|
+
|
|
10
|
+
## Run the browser page
|
|
11
|
+
|
|
12
|
+
From `trevrpc-js`:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm run example:greeter
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Open `http://127.0.0.1:8080/examples/greeter/` and use a WebTransport greeter endpoint:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
https://127.0.0.1:50051/trevrpc # Go default
|
|
22
|
+
https://127.0.0.1:5000/trevrpc # Rust default
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The path selects the WebTransport session; TrevRPC routing remains inside the request frame. The example policies allow origin `http://127.0.0.1:8080` and use bearer token `trevrpc-example-token`.
|
|
26
|
+
|
|
27
|
+
Both example servers write their development certificate to `~/.config/trevrpc/trevrpc-example-cert.pem`. Override it with `TREVRPC_EXAMPLE_CERT`. The static server computes the SHA-256 DER hash and the browser client passes it through `serverCertificateHashes`.
|
|
28
|
+
|
|
29
|
+
For a temporary self-signed certificate, calculate the hash with:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
openssl x509 -in server.pem -outform der | openssl dgst -sha256 -binary | openssl base64
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The client fully consumes server streams, then reads terminal metadata from `stream.status`. It calls `close()` when abandoning streams. Client `timeoutMs` is absolute across setup, upload, and receive, while `streamIdleTimeoutMs` is a separate inactivity bound.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { TrevRpcError, connect } from "../../src/index.js";
|
|
2
|
+
import { GreeterClient } from "./greeter.trevrpc.js";
|
|
3
|
+
|
|
4
|
+
const form = document.querySelector("#client-form");
|
|
5
|
+
const output = document.querySelector("#output");
|
|
6
|
+
const submit = document.querySelector("#submit");
|
|
7
|
+
|
|
8
|
+
void loadExampleDefaults();
|
|
9
|
+
|
|
10
|
+
form.addEventListener("submit", (event) => {
|
|
11
|
+
event.preventDefault();
|
|
12
|
+
void runFromForm(new FormData(form));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
for (const preset of document.querySelectorAll("[data-url]")) {
|
|
16
|
+
preset.addEventListener("click", () => {
|
|
17
|
+
form.elements.url.value = preset.dataset.url;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function runFromForm(data) {
|
|
22
|
+
output.textContent = "";
|
|
23
|
+
submit.disabled = true;
|
|
24
|
+
|
|
25
|
+
const url = String(data.get("url") ?? "").trim();
|
|
26
|
+
const name = String(data.get("name") ?? "TrevRPC").trim() || "TrevRPC";
|
|
27
|
+
const token = String(data.get("token") ?? "").trim();
|
|
28
|
+
const certificateHash = String(data.get("certificate-hash") ?? "").trim();
|
|
29
|
+
|
|
30
|
+
let channel;
|
|
31
|
+
try {
|
|
32
|
+
log(`connecting to ${url}`);
|
|
33
|
+
channel = await connect(url, {
|
|
34
|
+
...browserConnectOptions(certificateHash),
|
|
35
|
+
timeoutMs: 10_000,
|
|
36
|
+
});
|
|
37
|
+
const client = new GreeterClient(channel, {
|
|
38
|
+
timeoutMs: 5_000,
|
|
39
|
+
metadata: token === "" ? {} : { authorization: `Bearer ${token}` },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const unary = await client.sayHello({ name });
|
|
43
|
+
log(`SayHello: ${unary.message}`);
|
|
44
|
+
|
|
45
|
+
const replies = await client.lotsOfReplies({ name });
|
|
46
|
+
for await (const reply of replies) {
|
|
47
|
+
log(`LotsOfReplies: ${reply.message}`);
|
|
48
|
+
}
|
|
49
|
+
const replyStatus = await replies.status;
|
|
50
|
+
log(
|
|
51
|
+
`LotsOfReplies status: ${replyStatus.code} ${replyStatus.message}; trailers=${Object.keys(replyStatus.metadata).join(",")}`,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const greetings = await client.lotsOfGreetings();
|
|
55
|
+
await greetings.send({ name: `${name} client stream 1` });
|
|
56
|
+
await greetings.send({ name: `${name} client stream 2` });
|
|
57
|
+
const summary = await greetings.closeAndRecvWithResponse();
|
|
58
|
+
log(
|
|
59
|
+
`LotsOfGreetings: ${summary.message.message}; trailers=${Object.keys(summary.metadata).join(",")}`,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const bidi = await client.bidiHello();
|
|
63
|
+
try {
|
|
64
|
+
for (const suffix of ["bidi 1", "bidi 2"]) {
|
|
65
|
+
await bidi.send({ name: `${name} ${suffix}` });
|
|
66
|
+
|
|
67
|
+
const reply = await bidi.recv();
|
|
68
|
+
if (reply == null) {
|
|
69
|
+
throw new Error("BidiHello response stream ended early");
|
|
70
|
+
}
|
|
71
|
+
log(`BidiHello: ${reply.message}`);
|
|
72
|
+
}
|
|
73
|
+
await bidi.closeSend();
|
|
74
|
+
|
|
75
|
+
for (;;) {
|
|
76
|
+
const reply = await bidi.recv();
|
|
77
|
+
if (reply == null) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
log(`BidiHello: ${reply.message}`);
|
|
81
|
+
}
|
|
82
|
+
} finally {
|
|
83
|
+
await bidi.close();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
log("complete");
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logError(error);
|
|
89
|
+
} finally {
|
|
90
|
+
submit.disabled = false;
|
|
91
|
+
channel?.close({ closeCode: 0, reason: "example complete" });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function browserConnectOptions(certificateHash) {
|
|
96
|
+
if (certificateHash === "") {
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
serverCertificateHashes: [
|
|
102
|
+
{
|
|
103
|
+
algorithm: "sha-256",
|
|
104
|
+
value: parseSha256Hash(certificateHash),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function loadExampleDefaults() {
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch("./certificate-hash.json", { cache: "no-store" });
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const config = await response.json();
|
|
118
|
+
if (typeof config.bearerToken === "string" && form.elements.token.value === "") {
|
|
119
|
+
form.elements.token.value = config.bearerToken;
|
|
120
|
+
}
|
|
121
|
+
if (config.exists && typeof config.sha256Base64 === "string") {
|
|
122
|
+
form.elements["certificate-hash"].value = config.sha256Base64;
|
|
123
|
+
log(`loaded certificate hash from ${config.path}`);
|
|
124
|
+
} else if (typeof config.path === "string") {
|
|
125
|
+
log(`certificate not found at ${config.path}; start a Go or Rust example server first`);
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
log(`could not load local certificate hash: ${error?.message ?? String(error)}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function parseSha256Hash(value) {
|
|
133
|
+
const normalized = value
|
|
134
|
+
.trim()
|
|
135
|
+
.replace(/^sha-?256[=:]/i, "")
|
|
136
|
+
.trim();
|
|
137
|
+
const hex = normalized.replace(/[:\s]/g, "");
|
|
138
|
+
|
|
139
|
+
if (/^[0-9a-fA-F]{64}$/.test(hex)) {
|
|
140
|
+
const bytes = new Uint8Array(32);
|
|
141
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
142
|
+
bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
|
|
143
|
+
}
|
|
144
|
+
return bytes;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const base64 = normalized.replace(/-/g, "+").replace(/_/g, "/");
|
|
148
|
+
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "=");
|
|
149
|
+
const binary = atob(padded);
|
|
150
|
+
if (binary.length !== 32) {
|
|
151
|
+
throw new Error("certificate hash must decode to 32 bytes of SHA-256 data");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const bytes = new Uint8Array(binary.length);
|
|
155
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
156
|
+
bytes[index] = binary.charCodeAt(index);
|
|
157
|
+
}
|
|
158
|
+
return bytes;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function log(message) {
|
|
162
|
+
output.textContent += `${message}\n`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function logError(error) {
|
|
166
|
+
if (error instanceof TrevRpcError) {
|
|
167
|
+
log(`RPC error: ${error.code}: ${error.statusMessage || error.message}`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
log(`error: ${error?.message ?? String(error)}`);
|
|
172
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Code generated by protoc-gen-trevrpc-js. DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
NodeServer,
|
|
5
|
+
NodeServerCall,
|
|
6
|
+
StreamingServerResponse,
|
|
7
|
+
UnaryServerResponse,
|
|
8
|
+
} from "../../src/node-generated.js";
|
|
9
|
+
import type { HelloRequest, HelloReply } from "./greeter.trevrpc.js";
|
|
10
|
+
|
|
11
|
+
/** Typed Node handlers for the Greeter service. */
|
|
12
|
+
export interface GreeterHandlers {
|
|
13
|
+
sayHello?(
|
|
14
|
+
request: HelloRequest,
|
|
15
|
+
call: NodeServerCall,
|
|
16
|
+
):
|
|
17
|
+
| HelloReply
|
|
18
|
+
| UnaryServerResponse<HelloReply>
|
|
19
|
+
| Promise<HelloReply | UnaryServerResponse<HelloReply>>;
|
|
20
|
+
lotsOfReplies?(
|
|
21
|
+
request: HelloRequest,
|
|
22
|
+
call: NodeServerCall,
|
|
23
|
+
):
|
|
24
|
+
| Iterable<HelloReply>
|
|
25
|
+
| AsyncIterable<HelloReply>
|
|
26
|
+
| StreamingServerResponse<HelloReply>
|
|
27
|
+
| Promise<
|
|
28
|
+
Iterable<HelloReply> | AsyncIterable<HelloReply> | StreamingServerResponse<HelloReply>
|
|
29
|
+
>;
|
|
30
|
+
lotsOfGreetings?(
|
|
31
|
+
requests: AsyncIterable<HelloRequest>,
|
|
32
|
+
call: NodeServerCall,
|
|
33
|
+
):
|
|
34
|
+
| HelloReply
|
|
35
|
+
| UnaryServerResponse<HelloReply>
|
|
36
|
+
| Promise<HelloReply | UnaryServerResponse<HelloReply>>;
|
|
37
|
+
bidiHello?(
|
|
38
|
+
requests: AsyncIterable<HelloRequest>,
|
|
39
|
+
call: NodeServerCall,
|
|
40
|
+
):
|
|
41
|
+
| Iterable<HelloReply>
|
|
42
|
+
| AsyncIterable<HelloReply>
|
|
43
|
+
| StreamingServerResponse<HelloReply>
|
|
44
|
+
| Promise<
|
|
45
|
+
Iterable<HelloReply> | AsyncIterable<HelloReply> | StreamingServerResponse<HelloReply>
|
|
46
|
+
>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Registers typed Node handlers for the Greeter service. */
|
|
50
|
+
export declare function registerGreeterServer(
|
|
51
|
+
server: NodeServer,
|
|
52
|
+
handlers: GreeterHandlers,
|
|
53
|
+
): NodeServer;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Code generated by protoc-gen-trevrpc-js. DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
import { registerTypedService } from "../../src/node-generated.js";
|
|
4
|
+
import { GreeterService, root } from "./greeter.trevrpc.js";
|
|
5
|
+
|
|
6
|
+
/** Registers typed Node handlers for the Greeter service. */
|
|
7
|
+
export function registerGreeterServer(server, handlers) {
|
|
8
|
+
return registerTypedService(server, root, GreeterService, handlers);
|
|
9
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package example.greeter;
|
|
4
|
+
|
|
5
|
+
service Greeter {
|
|
6
|
+
rpc SayHello(HelloRequest) returns (HelloReply);
|
|
7
|
+
rpc LotsOfReplies(HelloRequest) returns (stream HelloReply);
|
|
8
|
+
rpc LotsOfGreetings(stream HelloRequest) returns (HelloReply);
|
|
9
|
+
rpc BidiHello(stream HelloRequest) returns (stream HelloReply);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
message HelloRequest {
|
|
13
|
+
string name = 1;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
message HelloReply {
|
|
17
|
+
string message = 1;
|
|
18
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Code generated by protoc-gen-trevrpc-js. DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
import type { Long, Root } from "protobufjs";
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
BidirectionalStreamingCall,
|
|
7
|
+
CallOptions,
|
|
8
|
+
ClientStreamingCall,
|
|
9
|
+
ResponseAsyncIterable,
|
|
10
|
+
RpcServiceDescriptor,
|
|
11
|
+
Transport,
|
|
12
|
+
UnaryResponse,
|
|
13
|
+
} from "../../src/index.js";
|
|
14
|
+
|
|
15
|
+
export declare const root: Root;
|
|
16
|
+
|
|
17
|
+
export interface HelloRequest {
|
|
18
|
+
name?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface HelloReply {
|
|
22
|
+
message?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Service descriptor for the Greeter service. */
|
|
26
|
+
export declare const GreeterService: RpcServiceDescriptor;
|
|
27
|
+
|
|
28
|
+
/** Client for the Greeter service. */
|
|
29
|
+
export declare class GreeterClient {
|
|
30
|
+
/** Creates a client for the Greeter service. */
|
|
31
|
+
constructor(transport: Transport, options?: CallOptions);
|
|
32
|
+
|
|
33
|
+
/** Calls the SayHello RPC. */
|
|
34
|
+
sayHello(request: HelloRequest, options?: CallOptions): Promise<HelloReply>;
|
|
35
|
+
/** Calls the SayHello RPC and returns response metadata. */
|
|
36
|
+
sayHelloWithResponse(
|
|
37
|
+
request: HelloRequest,
|
|
38
|
+
options?: CallOptions,
|
|
39
|
+
): Promise<UnaryResponse<HelloReply>>;
|
|
40
|
+
/** Calls the LotsOfReplies RPC. */
|
|
41
|
+
lotsOfReplies(
|
|
42
|
+
request: HelloRequest,
|
|
43
|
+
options?: CallOptions,
|
|
44
|
+
): Promise<ResponseAsyncIterable<HelloReply>>;
|
|
45
|
+
/** Calls the LotsOfGreetings RPC. */
|
|
46
|
+
lotsOfGreetings(options?: CallOptions): Promise<ClientStreamingCall<HelloRequest, HelloReply>>;
|
|
47
|
+
/** Calls the BidiHello RPC. */
|
|
48
|
+
bidiHello(options?: CallOptions): Promise<BidirectionalStreamingCall<HelloRequest, HelloReply>>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Creates a client for the Greeter service. */
|
|
52
|
+
export declare function createGreeterClient(
|
|
53
|
+
transport: Transport,
|
|
54
|
+
options?: CallOptions,
|
|
55
|
+
): GreeterClient;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Code generated by protoc-gen-trevrpc-js. DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
import { createRoot, createServiceClient } from "../../src/index.js";
|
|
4
|
+
|
|
5
|
+
export const root = createRoot({
|
|
6
|
+
nested: {
|
|
7
|
+
example: {
|
|
8
|
+
nested: {
|
|
9
|
+
greeter: {
|
|
10
|
+
nested: {
|
|
11
|
+
HelloRequest: {
|
|
12
|
+
fields: {
|
|
13
|
+
name: {
|
|
14
|
+
type: "string",
|
|
15
|
+
id: 1,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
HelloReply: {
|
|
20
|
+
fields: {
|
|
21
|
+
message: {
|
|
22
|
+
type: "string",
|
|
23
|
+
id: 1,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** Service descriptor for the Greeter service. */
|
|
35
|
+
export const GreeterService = Object.freeze({
|
|
36
|
+
name: "Greeter",
|
|
37
|
+
fullName: "example.greeter.Greeter",
|
|
38
|
+
exportName: "GreeterService",
|
|
39
|
+
methods: {
|
|
40
|
+
sayHello: {
|
|
41
|
+
name: "SayHello",
|
|
42
|
+
kind: "unary",
|
|
43
|
+
inputType: "example.greeter.HelloRequest",
|
|
44
|
+
outputType: "example.greeter.HelloReply",
|
|
45
|
+
},
|
|
46
|
+
lotsOfReplies: {
|
|
47
|
+
name: "LotsOfReplies",
|
|
48
|
+
kind: "serverStreaming",
|
|
49
|
+
inputType: "example.greeter.HelloRequest",
|
|
50
|
+
outputType: "example.greeter.HelloReply",
|
|
51
|
+
},
|
|
52
|
+
lotsOfGreetings: {
|
|
53
|
+
name: "LotsOfGreetings",
|
|
54
|
+
kind: "clientStreaming",
|
|
55
|
+
inputType: "example.greeter.HelloRequest",
|
|
56
|
+
outputType: "example.greeter.HelloReply",
|
|
57
|
+
},
|
|
58
|
+
bidiHello: {
|
|
59
|
+
name: "BidiHello",
|
|
60
|
+
kind: "bidirectionalStreaming",
|
|
61
|
+
inputType: "example.greeter.HelloRequest",
|
|
62
|
+
outputType: "example.greeter.HelloReply",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/** Client for the Greeter service. */
|
|
68
|
+
export class GreeterClient {
|
|
69
|
+
/** Creates a client for the Greeter service. */
|
|
70
|
+
constructor(transport, options = {}) {
|
|
71
|
+
this._client = createServiceClient(transport, GreeterService, root, options);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Calls the SayHello RPC. */
|
|
75
|
+
sayHello(request, options = {}) {
|
|
76
|
+
return this._client.sayHello(request, options);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Calls the SayHello RPC and returns response metadata. */
|
|
80
|
+
sayHelloWithResponse(request, options = {}) {
|
|
81
|
+
return this._client.sayHelloWithResponse(request, options);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Calls the LotsOfReplies RPC. */
|
|
85
|
+
lotsOfReplies(request, options = {}) {
|
|
86
|
+
return this._client.lotsOfReplies(request, options);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Calls the LotsOfGreetings RPC. */
|
|
90
|
+
lotsOfGreetings(options = {}) {
|
|
91
|
+
return this._client.lotsOfGreetings(options);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Calls the BidiHello RPC. */
|
|
95
|
+
bidiHello(options = {}) {
|
|
96
|
+
return this._client.bidiHello(options);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Creates a client for the Greeter service. */
|
|
101
|
+
export function createGreeterClient(transport, options = {}) {
|
|
102
|
+
return new GreeterClient(transport, options);
|
|
103
|
+
}
|