@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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>TrevRPC JavaScript Greeter Client</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: light dark;
|
|
10
|
+
font-family:
|
|
11
|
+
Inter,
|
|
12
|
+
ui-sans-serif,
|
|
13
|
+
system-ui,
|
|
14
|
+
-apple-system,
|
|
15
|
+
BlinkMacSystemFont,
|
|
16
|
+
"Segoe UI",
|
|
17
|
+
sans-serif;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
body {
|
|
21
|
+
margin: 0;
|
|
22
|
+
min-height: 100vh;
|
|
23
|
+
background:
|
|
24
|
+
radial-gradient(circle at top left, rgb(80 80 180 / 30%), transparent 35rem), Canvas;
|
|
25
|
+
color: CanvasText;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
main {
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
width: min(54rem, 100%);
|
|
31
|
+
margin: 0 auto;
|
|
32
|
+
padding: 3rem 1rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
form,
|
|
36
|
+
pre {
|
|
37
|
+
border: 1px solid color-mix(in srgb, CanvasText 18%, transparent);
|
|
38
|
+
border-radius: 1rem;
|
|
39
|
+
background: color-mix(in srgb, Canvas 92%, CanvasText 8%);
|
|
40
|
+
box-shadow: 0 1rem 3rem rgb(0 0 0 / 12%);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
form {
|
|
44
|
+
display: grid;
|
|
45
|
+
gap: 1rem;
|
|
46
|
+
padding: 1rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
label {
|
|
50
|
+
display: grid;
|
|
51
|
+
gap: 0.35rem;
|
|
52
|
+
font-weight: 700;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
input,
|
|
56
|
+
button {
|
|
57
|
+
border: 1px solid color-mix(in srgb, CanvasText 22%, transparent);
|
|
58
|
+
border-radius: 0.7rem;
|
|
59
|
+
font: inherit;
|
|
60
|
+
padding: 0.75rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
button {
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
font-weight: 800;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.actions {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-wrap: wrap;
|
|
71
|
+
gap: 0.75rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#submit {
|
|
75
|
+
background: #5b5bd6;
|
|
76
|
+
color: white;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
small {
|
|
80
|
+
opacity: 0.75;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pre {
|
|
84
|
+
min-height: 14rem;
|
|
85
|
+
overflow: auto;
|
|
86
|
+
padding: 1rem;
|
|
87
|
+
white-space: pre-wrap;
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
90
|
+
<script type="importmap">
|
|
91
|
+
{
|
|
92
|
+
"imports": {
|
|
93
|
+
"protobufjs": "./vendor/protobufjs.js"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
</script>
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
99
|
+
<main>
|
|
100
|
+
<h1>TrevRPC JavaScript Greeter Client</h1>
|
|
101
|
+
<p>
|
|
102
|
+
Connect this page to a WebTransport-enabled TrevRPC Go or Rust greeter server and run all
|
|
103
|
+
four RPC shapes from the browser.
|
|
104
|
+
</p>
|
|
105
|
+
|
|
106
|
+
<form id="client-form">
|
|
107
|
+
<label>
|
|
108
|
+
WebTransport URL
|
|
109
|
+
<input name="url" required value="https://127.0.0.1:50051/trevrpc" />
|
|
110
|
+
</label>
|
|
111
|
+
<div class="actions">
|
|
112
|
+
<button type="button" data-url="https://127.0.0.1:50051/trevrpc">Go default URL</button>
|
|
113
|
+
<button type="button" data-url="https://127.0.0.1:5000/trevrpc">Rust default URL</button>
|
|
114
|
+
</div>
|
|
115
|
+
<label>
|
|
116
|
+
Name
|
|
117
|
+
<input name="name" value="TrevRPC" />
|
|
118
|
+
</label>
|
|
119
|
+
<label>
|
|
120
|
+
Bearer token
|
|
121
|
+
<input name="token" value="trevrpc-example-token" />
|
|
122
|
+
</label>
|
|
123
|
+
<label>
|
|
124
|
+
Server certificate SHA-256 hash
|
|
125
|
+
<input name="certificate-hash" placeholder="optional hex or base64 hash" />
|
|
126
|
+
<small>
|
|
127
|
+
The local static server loads this from
|
|
128
|
+
<code>~/.config/trevrpc/trevrpc-example-cert.pem</code> when available. Leave empty when
|
|
129
|
+
the browser already trusts the server certificate.
|
|
130
|
+
</small>
|
|
131
|
+
</label>
|
|
132
|
+
<button id="submit" type="submit">Run RPCs</button>
|
|
133
|
+
</form>
|
|
134
|
+
|
|
135
|
+
<h2>Output</h2>
|
|
136
|
+
<pre id="output" aria-live="polite"></pre>
|
|
137
|
+
</main>
|
|
138
|
+
<script type="module" src="./client.js"></script>
|
|
139
|
+
</body>
|
|
140
|
+
</html>
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { extname, join, normalize, resolve, sep } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
|
|
10
|
+
const port = Number.parseInt(process.env.PORT ?? "8080", 10);
|
|
11
|
+
const authToken = "trevrpc-example-token";
|
|
12
|
+
const protobufBundlePath = fileURLToPath(import.meta.resolve("protobufjs/dist/protobuf.min.js"));
|
|
13
|
+
|
|
14
|
+
const contentTypes = new Map([
|
|
15
|
+
[".html", "text/html; charset=utf-8"],
|
|
16
|
+
[".js", "text/javascript; charset=utf-8"],
|
|
17
|
+
[".json", "application/json; charset=utf-8"],
|
|
18
|
+
[".md", "text/markdown; charset=utf-8"],
|
|
19
|
+
[".proto", "text/plain; charset=utf-8"],
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const server = createServer(async (request, response) => {
|
|
23
|
+
try {
|
|
24
|
+
const path = requestPath(request.url ?? "/");
|
|
25
|
+
|
|
26
|
+
if (path === "/favicon.ico") {
|
|
27
|
+
response.writeHead(204);
|
|
28
|
+
response.end();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (path === "/examples/greeter/certificate-hash.json") {
|
|
33
|
+
await writeCertificateHash(response);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (path === "/examples/greeter/vendor/protobufjs.js") {
|
|
38
|
+
writeProtobufModule(response);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (path === "/examples/greeter/vendor/protobuf.min.js") {
|
|
43
|
+
await writeProtobufBundle(response);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const file = path.endsWith("/") ? join(path, "index.html") : path;
|
|
48
|
+
await writeStaticFile(response, safePath(file));
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error?.code === "ENOENT") {
|
|
51
|
+
notFound(response);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
response.writeHead(500, { "content-type": "text/plain; charset=utf-8" });
|
|
56
|
+
response.end(error?.message ?? String(error));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
server.listen(port, "127.0.0.1", () => {
|
|
61
|
+
console.log(`serving trevrpc-js from http://127.0.0.1:${port}/examples/greeter/`);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
function requestPath(url) {
|
|
65
|
+
return decodeURIComponent(new URL(url, "http://127.0.0.1").pathname);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function writeStaticFile(response, absolute) {
|
|
69
|
+
const info = await stat(absolute);
|
|
70
|
+
|
|
71
|
+
if (!info.isFile()) {
|
|
72
|
+
notFound(response);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
response.writeHead(200, {
|
|
77
|
+
"content-type": contentTypes.get(extname(absolute)) ?? "application/octet-stream",
|
|
78
|
+
});
|
|
79
|
+
createReadStream(absolute).pipe(response);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function safePath(path) {
|
|
83
|
+
const absolute = normalize(join(root, path));
|
|
84
|
+
if (absolute !== root && !absolute.startsWith(`${root}${sep}`)) {
|
|
85
|
+
throw new Error("path escapes example root");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return absolute;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function writeCertificateHash(response) {
|
|
92
|
+
const path = certificatePath();
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const certificate = await readFile(path);
|
|
96
|
+
const der = certificateDer(certificate);
|
|
97
|
+
const hash = createHash("sha256").update(der).digest();
|
|
98
|
+
|
|
99
|
+
response.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
100
|
+
response.end(
|
|
101
|
+
JSON.stringify({
|
|
102
|
+
exists: true,
|
|
103
|
+
path,
|
|
104
|
+
bearerToken: authToken,
|
|
105
|
+
sha256Base64: hash.toString("base64"),
|
|
106
|
+
sha256Hex: hash.toString("hex"),
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
response.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
111
|
+
response.end(
|
|
112
|
+
JSON.stringify({
|
|
113
|
+
exists: false,
|
|
114
|
+
path,
|
|
115
|
+
bearerToken: authToken,
|
|
116
|
+
error: error?.message ?? String(error),
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function writeProtobufModule(response) {
|
|
123
|
+
response.writeHead(200, { "content-type": "text/javascript; charset=utf-8" });
|
|
124
|
+
response.end(`await new Promise((resolve, reject) => {
|
|
125
|
+
if (globalThis.protobuf != null) {
|
|
126
|
+
resolve();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const script = document.createElement("script");
|
|
131
|
+
script.src = new URL("./protobuf.min.js", import.meta.url).href;
|
|
132
|
+
script.onload = resolve;
|
|
133
|
+
script.onerror = () => reject(new Error("failed to load protobuf.js browser bundle"));
|
|
134
|
+
document.head.append(script);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export default globalThis.protobuf;
|
|
138
|
+
export const protobuf = globalThis.protobuf;
|
|
139
|
+
`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function writeProtobufBundle(response) {
|
|
143
|
+
await writeStaticFile(response, protobufBundlePath);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function certificatePath() {
|
|
147
|
+
return (
|
|
148
|
+
process.env.TREVRPC_EXAMPLE_CERT ??
|
|
149
|
+
join(homedir(), ".config", "trevrpc", "trevrpc-example-cert.pem")
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function certificateDer(certificate) {
|
|
154
|
+
const text = certificate.toString("utf8");
|
|
155
|
+
const match = /-----BEGIN CERTIFICATE-----([A-Za-z0-9+/=\s]+)-----END CERTIFICATE-----/.exec(
|
|
156
|
+
text,
|
|
157
|
+
);
|
|
158
|
+
if (match == null) {
|
|
159
|
+
return certificate;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return Buffer.from(match[1].replace(/\s/g, ""), "base64");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function notFound(response) {
|
|
166
|
+
response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
167
|
+
response.end("not found");
|
|
168
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@trevrpc/trevrpc-js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "JavaScript WebTransport runtime and protobuf.js code generator for TrevRPC",
|
|
5
|
+
"homepage": "https://trev.zip/llc/TrevRPC",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://trev.zip/llc/TrevRPC/issues"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://trev.zip/llc/TrevRPC.git"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"protoc-gen-trevrpc-js": "bin/protoc-gen-trevrpc-js.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src/**",
|
|
19
|
+
"bin/protoc-gen-trevrpc-js.js",
|
|
20
|
+
"examples/greeter/**",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"node": {
|
|
29
|
+
"types": "./src/index.node.d.ts",
|
|
30
|
+
"import": "./src/index.node.js"
|
|
31
|
+
},
|
|
32
|
+
"browser": {
|
|
33
|
+
"types": "./src/index.d.ts",
|
|
34
|
+
"import": "./src/index.js"
|
|
35
|
+
},
|
|
36
|
+
"types": "./src/index.d.ts",
|
|
37
|
+
"import": "./src/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./node": {
|
|
40
|
+
"types": "./src/node.d.ts",
|
|
41
|
+
"import": "./src/node-index.js"
|
|
42
|
+
},
|
|
43
|
+
"./advanced": {
|
|
44
|
+
"types": "./src/advanced.d.ts",
|
|
45
|
+
"import": "./src/advanced.js"
|
|
46
|
+
},
|
|
47
|
+
"./node/advanced": {
|
|
48
|
+
"types": "./src/node-advanced.d.ts",
|
|
49
|
+
"import": "./src/node-advanced.js"
|
|
50
|
+
},
|
|
51
|
+
"./node/generated": {
|
|
52
|
+
"types": "./src/node-generated.d.ts",
|
|
53
|
+
"import": "./src/node-generated.js"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public",
|
|
58
|
+
"provenance": true
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build:native": "TREVRPC_NODE_TEST_HOOKS=OFF npm run build:native:cmake",
|
|
62
|
+
"build:native:test": "TREVRPC_NODE_TEST_HOOKS=ON npm run build:native:cmake",
|
|
63
|
+
"build:native:cmake": "cmake -S native -B build/native -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} -DTREVRPC_NODE_TEST_HOOKS=${TREVRPC_NODE_TEST_HOOKS:?} ${TREVRPC_C_ROOT:+-DTREVRPC_C_ROOT=$TREVRPC_C_ROOT} ${NODE_INCLUDE_DIR:+-DNODE_INCLUDE_DIR=$NODE_INCLUDE_DIR} && cmake --build build/native --config ${CMAKE_BUILD_TYPE:-Release}",
|
|
64
|
+
"check": "npm run lint && npm run typecheck && npm run build:native:test && npm test && npm run build:native && npm run verify:native:production",
|
|
65
|
+
"example:greeter": "node examples/greeter/static-server.js",
|
|
66
|
+
"format": "oxfmt --write",
|
|
67
|
+
"format:check": "oxfmt --check",
|
|
68
|
+
"lint": "oxlint --deny-warnings",
|
|
69
|
+
"test": "node --test --test-concurrency=1 test/*.test.js",
|
|
70
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.browser.json && tsc -p tsconfig.node.json",
|
|
71
|
+
"verify:native:production": "node -e 'const { readFileSync } = require(\"node:fs\"); const path = \"./build/native/trevrpc_native.node\"; const expectedExports = [\"connectMsQuic\", \"createCancellation\", \"listenMsQuic\"]; const actualExports = Object.getOwnPropertyNames(require(path)).sort(); const exportsMatch = JSON.stringify(actualExports) === JSON.stringify(expectedExports); const embedsDebugNames = readFileSync(path).includes(Buffer.from(\"_debug\")); if (!exportsMatch || embedsDebugNames) throw new Error(`production addon exports ${JSON.stringify(actualExports)}; expected exactly ${JSON.stringify(expectedExports)}${embedsDebugNames ? \"; binary contains _debug\" : \"\"}`);'"
|
|
72
|
+
},
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"protobufjs": "^8.7.1"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@types/node": "^24.13.3",
|
|
78
|
+
"esbuild": "^0.28.1",
|
|
79
|
+
"playwright": "^1.62.1",
|
|
80
|
+
"typescript": "^7.0.2"
|
|
81
|
+
},
|
|
82
|
+
"optionalDependencies": {
|
|
83
|
+
"@trevrpc/trevrpc-js-native-linux-x64-gnu": "0.1.0"
|
|
84
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=24 <25"
|
|
87
|
+
},
|
|
88
|
+
"packageManager": "npm@11.19.0"
|
|
89
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ResolvedCallOptions,
|
|
3
|
+
RpcRequestMessage,
|
|
4
|
+
RpcResponseMessage,
|
|
5
|
+
RpcStreamFrameMessage,
|
|
6
|
+
Transport,
|
|
7
|
+
WebTransportBidirectionalStreamLike,
|
|
8
|
+
WebTransportCloseInfoLike,
|
|
9
|
+
WebTransportOptions,
|
|
10
|
+
WebTransportSessionLike,
|
|
11
|
+
} from "./index.js";
|
|
12
|
+
|
|
13
|
+
/** Raw single-session TrevRPC transport for browser WebTransport. */
|
|
14
|
+
export class RawWebTransport implements Transport {
|
|
15
|
+
session: WebTransportSessionLike;
|
|
16
|
+
maxFrameSize: number;
|
|
17
|
+
|
|
18
|
+
constructor(session: WebTransportSessionLike, options?: WebTransportOptions);
|
|
19
|
+
static connect(url: string | URL, options?: WebTransportOptions): Promise<RawWebTransport>;
|
|
20
|
+
ready(): Promise<void>;
|
|
21
|
+
close(closeInfo?: WebTransportCloseInfoLike): void;
|
|
22
|
+
call(request: RpcRequestMessage, options?: ResolvedCallOptions): Promise<RpcResponseMessage>;
|
|
23
|
+
streamingCall(
|
|
24
|
+
request: RpcRequestMessage,
|
|
25
|
+
requestBody: AsyncIterable<Uint8Array>,
|
|
26
|
+
options?: ResolvedCallOptions,
|
|
27
|
+
): Promise<AsyncIterableIterator<RpcStreamFrameMessage>>;
|
|
28
|
+
openBidirectionalStream(): Promise<WebTransportBidirectionalStreamLike>;
|
|
29
|
+
}
|
package/src/advanced.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RawWebTransport } from "./webtransport.js";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ChannelStateMachine, stripChannelOptions, waitForInitialReady } from "./channel.js";
|
|
2
|
+
import { unavailable } from "./status.js";
|
|
3
|
+
import { RawWebTransport, createWebTransportSession } from "./webtransport.js";
|
|
4
|
+
|
|
5
|
+
/** Browser channel with background session reconnection. */
|
|
6
|
+
export class Channel extends ChannelStateMachine {
|
|
7
|
+
constructor(url, options = {}) {
|
|
8
|
+
const retainedUrl = url instanceof URL ? url.href : url;
|
|
9
|
+
const retainedOptions = Object.freeze({ ...options });
|
|
10
|
+
const transportOptions = stripChannelOptions(retainedOptions);
|
|
11
|
+
super(
|
|
12
|
+
(signal) => connectWebTransport(retainedUrl, transportOptions, signal),
|
|
13
|
+
retainedOptions,
|
|
14
|
+
(transport) => transport.session.closed,
|
|
15
|
+
);
|
|
16
|
+
this.url = retainedUrl;
|
|
17
|
+
this.options = retainedOptions;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Creates a channel and waits for its first ready generation. */
|
|
21
|
+
static async connect(url, options = {}) {
|
|
22
|
+
const channel = new Channel(url, options);
|
|
23
|
+
await waitForInitialReady(channel, options);
|
|
24
|
+
return channel;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function connectWebTransport(url, options, signal) {
|
|
29
|
+
const session = createWebTransportSession(url, options);
|
|
30
|
+
const onAbort = () => session.close?.();
|
|
31
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await session.ready;
|
|
35
|
+
if (signal.aborted) {
|
|
36
|
+
throw unavailable("channel is closed");
|
|
37
|
+
}
|
|
38
|
+
return new RawWebTransport(session, options);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
session.close?.();
|
|
41
|
+
throw error;
|
|
42
|
+
} finally {
|
|
43
|
+
signal.removeEventListener("abort", onAbort);
|
|
44
|
+
}
|
|
45
|
+
}
|