@trevrpc/trevrpc-js 1.6.0 → 1.7.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/README.md +4 -0
- package/package.json +2 -2
- package/src/call.js +64 -25
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ TrevRPC is an RPC framework like gRPC, but uses QUIC (and HTTP/3 / WebTransport)
|
|
|
4
4
|
|
|
5
5
|
`trevrpc-js` is the **browser client**: a WebTransport runtime with a QMux over WebSocket fallback, for the messages and service descriptors that [protobuf-es](https://github.com/bufbuild/protobuf-es) generates. It has no Node.js runtime; servers are written in Go or Rust, native QUIC clients in Go or Rust, and HTTP/3 clients in Kotlin.
|
|
6
6
|
|
|
7
|
+
> **Compatibility:** every TrevRPC package shares one version, and packages of the same version are compatible
|
|
8
|
+
> with each other: `trevrpc-rust` 1.7.0 works with `trevrpc-go` 1.7.0, `trevrpc-js` 1.7.0, and `trevrpc-kotlin`
|
|
9
|
+
> 1.7.0. Compatibility across different versions isn't guaranteed yet; that's a milestone for 2.0.
|
|
10
|
+
|
|
7
11
|
Full documentation: https://trev.zip/llc/TrevRPC/wiki
|
|
8
12
|
|
|
9
13
|
## Protobuf
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trevrpc/trevrpc-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Browser WebTransport client for TrevRPC, on protobuf-es",
|
|
5
5
|
"homepage": "https://trev.zip/llc/TrevRPC",
|
|
6
6
|
"bugs": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@connectrpc/connect": "^2.3.0",
|
|
49
49
|
"@connectrpc/connect-web": "^2.3.0",
|
|
50
50
|
"playwright": "1.64.1",
|
|
51
|
-
"rolldown": "^1.3.
|
|
51
|
+
"rolldown": "^1.3.1",
|
|
52
52
|
"typescript": "^7.0.2"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
package/src/call.js
CHANGED
|
@@ -769,7 +769,8 @@ export class Call {
|
|
|
769
769
|
/**
|
|
770
770
|
* Opens a bidirectional stream. A call whose setup depends on its session passes `session`, which
|
|
771
771
|
* receives that session's `key` and `datagrams` object, so the compression state and datagram flow
|
|
772
|
-
* belong to the connection that carries the call and not to one a reconnect swapped in later.
|
|
772
|
+
* belong to the connection that carries the call and not to one a reconnect swapped in later. A
|
|
773
|
+
* stream the browser refuses for want of stream credit is opened again (see StreamRefusalRetryMs).
|
|
773
774
|
*/
|
|
774
775
|
function openStream(transport, signal, session) {
|
|
775
776
|
return new Promise((resolve, reject) => {
|
|
@@ -777,34 +778,72 @@ function openStream(transport, signal, session) {
|
|
|
777
778
|
reject(signal.reason);
|
|
778
779
|
return;
|
|
779
780
|
}
|
|
780
|
-
|
|
781
|
+
let retryTimer;
|
|
782
|
+
const onAbort = () => {
|
|
783
|
+
clearTimeout(retryTimer);
|
|
784
|
+
reject(signal.reason);
|
|
785
|
+
};
|
|
781
786
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
opening
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
787
|
+
const giveUpAt = Date.now() + StreamRefusalRetryMs;
|
|
788
|
+
|
|
789
|
+
const attempt = (delay) => {
|
|
790
|
+
let opening;
|
|
791
|
+
try {
|
|
792
|
+
opening = Promise.resolve(startStream(transport, session));
|
|
793
|
+
} catch (error) {
|
|
794
|
+
opening = Promise.reject(error);
|
|
795
|
+
}
|
|
796
|
+
opening.then(
|
|
797
|
+
(stream) => {
|
|
798
|
+
signal.removeEventListener("abort", onAbort);
|
|
799
|
+
if (signal.aborted) {
|
|
800
|
+
// The caller gave up while the stream was opening; do not leak it.
|
|
801
|
+
stream.writable.abort(signal.reason).catch(() => {});
|
|
802
|
+
stream.readable.cancel(signal.reason).catch(() => {});
|
|
803
|
+
} else {
|
|
804
|
+
resolve(stream);
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
(error) => {
|
|
808
|
+
if (signal.aborted) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (isStreamRefusal(error) && Date.now() + delay <= giveUpAt) {
|
|
812
|
+
// Nothing reached the server, so opening the stream again is safe.
|
|
813
|
+
retryTimer = setTimeout(
|
|
814
|
+
() => attempt(Math.min(delay * 2, StreamRefusalMaxDelayMs)),
|
|
815
|
+
delay,
|
|
816
|
+
);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
signal.removeEventListener("abort", onAbort);
|
|
820
|
+
reject(statusFromTransportError(error));
|
|
821
|
+
},
|
|
822
|
+
);
|
|
823
|
+
};
|
|
824
|
+
attempt(StreamRefusalFirstDelayMs);
|
|
805
825
|
});
|
|
806
826
|
}
|
|
807
827
|
|
|
828
|
+
/**
|
|
829
|
+
* How long a call keeps opening a stream the browser refused, and how long it waits between
|
|
830
|
+
* attempts. Chromium refuses a new WebTransport stream at once while the server's stream credit is
|
|
831
|
+
* used up, where the WebTransport spec has it wait for more. Credit comes back about two round
|
|
832
|
+
* trips after a stream opens, so the call waits for it itself, doubling the delay from 1 ms.
|
|
833
|
+
*/
|
|
834
|
+
const StreamRefusalRetryMs = 2000;
|
|
835
|
+
const StreamRefusalFirstDelayMs = 1;
|
|
836
|
+
const StreamRefusalMaxDelayMs = 64;
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Whether opening a stream failed because the browser refused it for now: Chromium's NetworkError
|
|
840
|
+
* "Failed to create bidirectional stream". A closed session fails differently, and a channel
|
|
841
|
+
* without a session fails with a status.
|
|
842
|
+
*/
|
|
843
|
+
function isStreamRefusal(error) {
|
|
844
|
+
return !(error instanceof TrevRpcError) && error?.name === "NetworkError";
|
|
845
|
+
}
|
|
846
|
+
|
|
808
847
|
function messageOf(item) {
|
|
809
848
|
return item.message;
|
|
810
849
|
}
|