@trevrpc/trevrpc-js 1.5.0 → 1.6.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 +33 -42
- package/package.json +9 -13
- package/src/call.js +1 -1
- package/src/channel.js +44 -6
- package/src/client.js +30 -38
- package/src/framing.js +40 -12
- package/src/index.d.ts +55 -198
- package/src/index.js +1 -15
- package/src/method-options.js +170 -0
- package/src/qmux/session.js +17 -0
- package/src/wire.js +400 -312
- package/bin/protoc-gen-trevrpc-js.js +0 -17
- package/src/generator.js +0 -1152
- package/src/int64.js +0 -288
- package/src/wkt.js +0 -137
package/src/index.d.ts
CHANGED
|
@@ -1,139 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export const protobuf: typeof import("protobufjs");
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* How decoded messages represent 64-bit integer fields: `bigint`, `number` (exact up to 2^53),
|
|
7
|
-
* decimal `string`, or protobuf.js's `long` (a Long object). 64-bit map keys are decimal strings
|
|
8
|
-
* in every representation.
|
|
9
|
-
*/
|
|
10
|
-
export type Int64Representation = "bigint" | "number" | "string" | "long";
|
|
11
|
-
|
|
12
|
-
export interface CreateRootOptions {
|
|
13
|
-
/**
|
|
14
|
-
* How calls decode the 64-bit integers of the root's messages; a field's `jstype` option
|
|
15
|
-
* (`JS_STRING` or `JS_NUMBER`) overrides it. Without it they stay Long objects. Generated code
|
|
16
|
-
* passes the generator's `int64` option.
|
|
17
|
-
*/
|
|
18
|
-
int64?: Int64Representation;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Creates a protobuf root from a JSON namespace definition. */
|
|
22
|
-
export function createRoot(json: INamespace, options?: CreateRootOptions): Root;
|
|
23
|
-
|
|
24
|
-
/** A 64-bit integer in any representation, including a Long. */
|
|
25
|
-
type Int64Value = bigint | number | string | { low: number; high: number; unsigned?: boolean };
|
|
26
|
-
|
|
27
|
-
/** A decoded google.protobuf.Timestamp message. */
|
|
28
|
-
export interface Timestamp {
|
|
29
|
-
seconds: bigint;
|
|
30
|
-
nanos: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** A google.protobuf.Timestamp message to send. */
|
|
34
|
-
export interface TimestampInit {
|
|
35
|
-
seconds?: bigint | number | string;
|
|
36
|
-
nanos?: number;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** A decoded google.protobuf.Duration message. */
|
|
40
|
-
export interface Duration {
|
|
41
|
-
seconds: bigint;
|
|
42
|
-
nanos: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** A google.protobuf.Duration message to send. */
|
|
46
|
-
export interface DurationInit {
|
|
47
|
-
seconds?: bigint | number | string;
|
|
48
|
-
nanos?: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** A decoded google.protobuf.Empty message. */
|
|
52
|
-
export interface Empty {}
|
|
53
|
-
|
|
54
|
-
/** A google.protobuf.Empty message to send. */
|
|
55
|
-
export interface EmptyInit {}
|
|
56
|
-
|
|
57
|
-
/** Values of the google.protobuf.NullValue enum. */
|
|
58
|
-
export const NullValue: Readonly<{ NULL_VALUE: 0 }>;
|
|
59
|
-
/** A value of the google.protobuf.NullValue enum. */
|
|
60
|
-
export type NullValue = (typeof NullValue)[keyof typeof NullValue];
|
|
61
|
-
|
|
62
|
-
/** A decoded google.protobuf.Struct message: a JSON object; see `structToJson`. */
|
|
63
|
-
export interface Struct {
|
|
64
|
-
fields: Record<string, Value>;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** A google.protobuf.Struct message to send; see `structFromJson`. */
|
|
68
|
-
export interface StructInit {
|
|
69
|
-
fields?: Record<string, ValueInit>;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* A decoded google.protobuf.Value message: a JSON value, which sets one member of the `kind`
|
|
74
|
-
* oneof; see `valueToJson`.
|
|
75
|
-
*/
|
|
76
|
-
export interface Value {
|
|
77
|
-
nullValue: NullValue;
|
|
78
|
-
numberValue: number;
|
|
79
|
-
stringValue: string;
|
|
80
|
-
boolValue: boolean;
|
|
81
|
-
structValue: Struct | null;
|
|
82
|
-
listValue: ListValue | null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** A google.protobuf.Value message to send; see `valueFromJson`. */
|
|
86
|
-
export interface ValueInit {
|
|
87
|
-
nullValue?: NullValue;
|
|
88
|
-
numberValue?: number;
|
|
89
|
-
stringValue?: string;
|
|
90
|
-
boolValue?: boolean;
|
|
91
|
-
structValue?: StructInit | null;
|
|
92
|
-
listValue?: ListValueInit | null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** A decoded google.protobuf.ListValue message: a JSON array. */
|
|
96
|
-
export interface ListValue {
|
|
97
|
-
values: Value[];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** A google.protobuf.ListValue message to send. */
|
|
101
|
-
export interface ListValueInit {
|
|
102
|
-
values?: ValueInit[];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export type JsonValue = null | boolean | number | string | JsonValue[] | JsonObject;
|
|
106
|
-
export type JsonObject = { [key: string]: JsonValue };
|
|
107
|
-
|
|
108
|
-
/** Returns the Timestamp of a Date, to the millisecond. */
|
|
109
|
-
export function timestampFromDate(date: Date): Timestamp;
|
|
110
|
-
/** Returns the Timestamp of milliseconds since the Unix epoch. */
|
|
111
|
-
export function timestampFromMs(ms: number): Timestamp;
|
|
112
|
-
/** Returns the Date of a Timestamp, whatever the representation of its seconds. */
|
|
113
|
-
export function timestampDate(timestamp: {
|
|
114
|
-
seconds?: Int64Value | null;
|
|
115
|
-
nanos?: number | null;
|
|
116
|
-
}): Date;
|
|
117
|
-
/** Returns the milliseconds since the Unix epoch of a Timestamp, rounded down. */
|
|
118
|
-
export function timestampMs(timestamp: {
|
|
119
|
-
seconds?: Int64Value | null;
|
|
120
|
-
nanos?: number | null;
|
|
121
|
-
}): number;
|
|
122
|
-
/** Returns the Duration of a number of milliseconds. */
|
|
123
|
-
export function durationFromMs(ms: number): Duration;
|
|
124
|
-
/** Returns the milliseconds of a Duration, rounded towards zero. */
|
|
125
|
-
export function durationMs(duration: {
|
|
126
|
-
seconds?: Int64Value | null;
|
|
127
|
-
nanos?: number | null;
|
|
128
|
-
}): number;
|
|
129
|
-
/** Returns the Struct of a JSON object. */
|
|
130
|
-
export function structFromJson(json: JsonObject): StructInit;
|
|
131
|
-
/** Returns the JSON object of a Struct. */
|
|
132
|
-
export function structToJson(struct: StructInit): JsonObject;
|
|
133
|
-
/** Returns the Value of a JSON value. */
|
|
134
|
-
export function valueFromJson(json: JsonValue): ValueInit;
|
|
135
|
-
/** Returns the JSON value of a Value; one with no member of `kind` set is null. */
|
|
136
|
-
export function valueToJson(value: ValueInit): JsonValue;
|
|
1
|
+
import type { DescMessage, DescService, MessageInitShape, MessageShape } from "@bufbuild/protobuf";
|
|
137
2
|
|
|
138
3
|
export const Code: Readonly<{
|
|
139
4
|
Ok: 0;
|
|
@@ -197,7 +62,7 @@ export interface CallOptions {
|
|
|
197
62
|
onResponseMetadata?(metadata: Metadata): void;
|
|
198
63
|
}
|
|
199
64
|
|
|
200
|
-
/** The kind of an RPC
|
|
65
|
+
/** The kind of an RPC. */
|
|
201
66
|
export type CallKind = "unary" | "serverStreaming" | "clientStreaming" | "bidirectionalStreaming";
|
|
202
67
|
|
|
203
68
|
/** What a call interceptor sees of a call before it starts. */
|
|
@@ -334,55 +199,6 @@ export interface Transport {
|
|
|
334
199
|
}>;
|
|
335
200
|
}
|
|
336
201
|
|
|
337
|
-
export type RpcMethodKind =
|
|
338
|
-
| "unary"
|
|
339
|
-
| "clientStreaming"
|
|
340
|
-
| "serverStreaming"
|
|
341
|
-
| "bidirectionalStreaming";
|
|
342
|
-
|
|
343
|
-
export interface RpcMethodDescriptor {
|
|
344
|
-
name: string;
|
|
345
|
-
kind: RpcMethodKind;
|
|
346
|
-
inputType: string;
|
|
347
|
-
outputType: string;
|
|
348
|
-
/**
|
|
349
|
-
* The method marks its streamed request messages unreliable (`unreliable_requests` in the
|
|
350
|
-
* `trevrpc.method` option). They are sent as WebTransport datagrams when they fit and the
|
|
351
|
-
* session has datagrams, and may be lost or reordered; otherwise they travel on the stream.
|
|
352
|
-
*/
|
|
353
|
-
unreliableRequests?: boolean;
|
|
354
|
-
/**
|
|
355
|
-
* The method marks its streamed response messages unreliable (`unreliable_responses`). The
|
|
356
|
-
* client accepts them as datagrams on any session that has them.
|
|
357
|
-
*/
|
|
358
|
-
unreliableResponses?: boolean;
|
|
359
|
-
/**
|
|
360
|
-
* The method's request messages may be stream-compressed (`compression: COMPRESSION_STREAM` in
|
|
361
|
-
* the `trevrpc.method` option, on a method that streams requests, whose request type has no
|
|
362
|
-
* field marked `debug_redact`). Calls on a transport with compression then compress them with one zstd
|
|
363
|
-
* context per call, once the server agrees.
|
|
364
|
-
*/
|
|
365
|
-
compressStreamRequests?: boolean;
|
|
366
|
-
/** The method's response messages may be stream-compressed; see `compressStreamRequests`. */
|
|
367
|
-
compressStreamResponses?: boolean;
|
|
368
|
-
/**
|
|
369
|
-
* The method's request messages may be compressed without a dictionary: one at a time, from 256
|
|
370
|
-
* bytes, where no dictionary applies to their type and the server decodes it. The generator
|
|
371
|
-
* sets it for `COMPRESSION_MESSAGE` and `COMPRESSION_STREAM`, except for message types with a
|
|
372
|
-
* field marked `debug_redact`.
|
|
373
|
-
*/
|
|
374
|
-
compressRequests?: boolean;
|
|
375
|
-
/** The method's response messages may be compressed without a dictionary; see `compressRequests`. */
|
|
376
|
-
compressResponses?: boolean;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
export interface RpcServiceDescriptor {
|
|
380
|
-
name: string;
|
|
381
|
-
fullName: string;
|
|
382
|
-
exportName: string;
|
|
383
|
-
methods: Record<string, RpcMethodDescriptor>;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
202
|
/** The final status of a call that ended with OK. */
|
|
387
203
|
export interface StreamStatus {
|
|
388
204
|
readonly code: StatusCode;
|
|
@@ -442,8 +258,8 @@ export interface ClientStreamingCall<TRequest, TResponse> {
|
|
|
442
258
|
}
|
|
443
259
|
|
|
444
260
|
/**
|
|
445
|
-
* On a method
|
|
446
|
-
* `
|
|
261
|
+
* On a method whose `trevrpc.method` option marks a direction unreliable (`unreliable_requests` or
|
|
262
|
+
* `unreliable_responses`), a call on a session with datagrams may send its request messages, and
|
|
447
263
|
* receive its response messages, as datagrams: they can be lost or reordered, never duplicated.
|
|
448
264
|
* Response datagrams are delivered after the response headers, merged with the stream's messages
|
|
449
265
|
* in arrival order, and dropped when the call's queue is full (32 messages or 1 MiB) or the call
|
|
@@ -498,21 +314,62 @@ export interface BidirectionalStreamingCall<TRequest, TResponse> extends AsyncIt
|
|
|
498
314
|
close(reason?: unknown): Promise<void>;
|
|
499
315
|
}
|
|
500
316
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
317
|
+
/** A unary method of a client: sends one request and resolves with the response. */
|
|
318
|
+
export type UnaryMethod<I extends DescMessage, O extends DescMessage> = (
|
|
319
|
+
request: MessageInitShape<I>,
|
|
320
|
+
options?: CallOptions,
|
|
321
|
+
) => Promise<MessageShape<O>>;
|
|
322
|
+
|
|
323
|
+
/** A server-streaming method of a client: sends one request and resolves with the responses. */
|
|
324
|
+
export type ServerStreamingMethod<I extends DescMessage, O extends DescMessage> = (
|
|
325
|
+
request: MessageInitShape<I>,
|
|
326
|
+
options?: CallOptions,
|
|
327
|
+
) => Promise<ResponseStream<MessageShape<O>>>;
|
|
328
|
+
|
|
329
|
+
/** A client-streaming method of a client: starts a call to send requests on. */
|
|
330
|
+
export type ClientStreamingMethod<I extends DescMessage, O extends DescMessage> = (
|
|
331
|
+
options?: CallOptions,
|
|
332
|
+
) => Promise<ClientStreamingCall<MessageInitShape<I>, MessageShape<O>>>;
|
|
333
|
+
|
|
334
|
+
/** A bidirectional-streaming method of a client: starts a call to send and receive on. */
|
|
335
|
+
export type BidirectionalStreamingMethod<I extends DescMessage, O extends DescMessage> = (
|
|
336
|
+
options?: CallOptions,
|
|
337
|
+
) => Promise<BidirectionalStreamingCall<MessageInitShape<I>, MessageShape<O>>>;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* The client of a protobuf-es service descriptor: one function per method, named by its
|
|
341
|
+
* `localName`. Requests take a message or the init object `create()` takes; responses are
|
|
342
|
+
* messages.
|
|
343
|
+
*/
|
|
344
|
+
export type Client<S extends DescService> = {
|
|
345
|
+
[K in keyof S["method"]]: S["method"][K] extends {
|
|
346
|
+
methodKind: infer Kind;
|
|
347
|
+
input: infer I extends DescMessage;
|
|
348
|
+
output: infer O extends DescMessage;
|
|
349
|
+
}
|
|
350
|
+
? Kind extends "unary"
|
|
351
|
+
? UnaryMethod<I, O>
|
|
352
|
+
: Kind extends "server_streaming"
|
|
353
|
+
? ServerStreamingMethod<I, O>
|
|
354
|
+
: Kind extends "client_streaming"
|
|
355
|
+
? ClientStreamingMethod<I, O>
|
|
356
|
+
: Kind extends "bidi_streaming"
|
|
357
|
+
? BidirectionalStreamingMethod<I, O>
|
|
358
|
+
: never
|
|
359
|
+
: never;
|
|
360
|
+
};
|
|
505
361
|
|
|
506
362
|
/**
|
|
507
|
-
* Creates a
|
|
508
|
-
*
|
|
363
|
+
* Creates a client for `service`, a service descriptor that protoc-gen-es generates (such as
|
|
364
|
+
* `GreeterService` from `greeter_pb.js`). The `trevrpc.method` option of each method decides
|
|
365
|
+
* what its calls send as datagrams and compress; one the method's kind cannot use throws
|
|
366
|
+
* InvalidArgument. Calls run the transport's interceptors, then those of `options.interceptors`.
|
|
509
367
|
*/
|
|
510
|
-
export function
|
|
368
|
+
export function createClient<S extends DescService>(
|
|
369
|
+
service: S,
|
|
511
370
|
transport: Transport,
|
|
512
|
-
service: RpcServiceDescriptor,
|
|
513
|
-
root: Root,
|
|
514
371
|
options?: ClientOptions,
|
|
515
|
-
):
|
|
372
|
+
): Client<S>;
|
|
516
373
|
|
|
517
374
|
export interface WebTransportCertificateHash {
|
|
518
375
|
algorithm: string;
|
package/src/index.js
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
1
|
export { fetchServerCertificateHashes } from "./certificate-hashes.js";
|
|
2
2
|
export { Channel, connect, createChannel } from "./channel.js";
|
|
3
|
-
export {
|
|
3
|
+
export { createClient } from "./client.js";
|
|
4
4
|
export { Code, TrevRpcError, codeName } from "./status.js";
|
|
5
|
-
export { createRoot, protobuf } from "./wire.js";
|
|
6
|
-
export {
|
|
7
|
-
NullValue,
|
|
8
|
-
durationFromMs,
|
|
9
|
-
durationMs,
|
|
10
|
-
structFromJson,
|
|
11
|
-
structToJson,
|
|
12
|
-
timestampDate,
|
|
13
|
-
timestampFromDate,
|
|
14
|
-
timestampFromMs,
|
|
15
|
-
timestampMs,
|
|
16
|
-
valueFromJson,
|
|
17
|
-
valueToJson,
|
|
18
|
-
} from "./wkt.js";
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// How a client calls each method of a protobuf-es service descriptor: the method's kind, and what
|
|
2
|
+
// its `(trevrpc.method)` option (proto/trevrpc/options.proto) lets it send as datagrams and
|
|
3
|
+
// compress. protoc-gen-es keeps the option in the descriptors it embeds, where protobuf-es leaves
|
|
4
|
+
// it as an unknown field of the method's options, so it is read from there without the generated
|
|
5
|
+
// extension.
|
|
6
|
+
import { BinaryReader, WireType } from "@bufbuild/protobuf/wire";
|
|
7
|
+
|
|
8
|
+
import { invalidArgument } from "./status.js";
|
|
9
|
+
|
|
10
|
+
/** The field number of the `trevrpc.method` extension of google.protobuf.MethodOptions. */
|
|
11
|
+
const MethodOptionField = 58247;
|
|
12
|
+
|
|
13
|
+
/** `trevrpc.Compression` values. */
|
|
14
|
+
const Compression = Object.freeze({ Unspecified: 0, Message: 1, Stream: 2 });
|
|
15
|
+
|
|
16
|
+
/** The kind of each protobuf-es `methodKind`, as calls and interceptors name it. */
|
|
17
|
+
const Kinds = Object.freeze({
|
|
18
|
+
__proto__: null,
|
|
19
|
+
unary: "unary",
|
|
20
|
+
server_streaming: "serverStreaming",
|
|
21
|
+
client_streaming: "clientStreaming",
|
|
22
|
+
bidi_streaming: "bidirectionalStreaming",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The call settings of `method`, a protobuf-es DescMethod: `{ kind, unreliableRequests,
|
|
27
|
+
* unreliableResponses, compressRequests, compressResponses, compressStreamRequests,
|
|
28
|
+
* compressStreamResponses }`, with each flag true only where the option sets it for a direction
|
|
29
|
+
* that allows it. An option the method's kind cannot use throws InvalidArgument.
|
|
30
|
+
*/
|
|
31
|
+
export function methodSettings(method) {
|
|
32
|
+
const kind = Kinds[method.methodKind];
|
|
33
|
+
const name = method.parent?.typeName ? `${method.parent.typeName}.${method.name}` : method.name;
|
|
34
|
+
if (kind === undefined) {
|
|
35
|
+
throw invalidArgument(`${name}: unsupported RPC kind ${JSON.stringify(method.methodKind)}`);
|
|
36
|
+
}
|
|
37
|
+
const options = trevrpcMethodOptions(method, name);
|
|
38
|
+
return {
|
|
39
|
+
kind,
|
|
40
|
+
...unreliableDirections(name, kind, options),
|
|
41
|
+
...compressionDirections(name, kind, options, method),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The method's `(trevrpc.method)` option, with unset fields false or zero. */
|
|
46
|
+
function trevrpcMethodOptions(method, name) {
|
|
47
|
+
const options = { unreliableRequests: false, unreliableResponses: false, compression: 0 };
|
|
48
|
+
for (const field of method.proto?.options?.$unknown ?? []) {
|
|
49
|
+
if (field.no !== MethodOptionField) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
if (field.wireType !== WireType.LengthDelimited) {
|
|
54
|
+
throw new Error(`wire type ${field.wireType}`);
|
|
55
|
+
}
|
|
56
|
+
// A message-typed option may be split over several fields, which merge in order.
|
|
57
|
+
const reader = new BinaryReader(new BinaryReader(field.data).bytes());
|
|
58
|
+
while (reader.pos < reader.len) {
|
|
59
|
+
const [number, wireType] = reader.tag();
|
|
60
|
+
if (number === 1 || number === 2 || number === 5) {
|
|
61
|
+
if (wireType !== WireType.Varint) {
|
|
62
|
+
throw new Error(`field ${number} has wire type ${wireType}`);
|
|
63
|
+
}
|
|
64
|
+
if (number === 5) {
|
|
65
|
+
options.compression = reader.int32();
|
|
66
|
+
} else {
|
|
67
|
+
options[number === 1 ? "unreliableRequests" : "unreliableResponses"] = reader.bool();
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
reader.skip(wireType, number);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw invalidArgument(`${name}: malformed trevrpc.method option: ${error.message}`, error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return options;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The `unreliable_*` flags that are set; one on a direction that does not stream throws. */
|
|
81
|
+
function unreliableDirections(name, kind, options) {
|
|
82
|
+
const directions = {};
|
|
83
|
+
if (options.unreliableRequests) {
|
|
84
|
+
if (kind !== "clientStreaming" && kind !== "bidirectionalStreaming") {
|
|
85
|
+
throw invalidArgument(
|
|
86
|
+
`${name}: unreliable_requests requires a client-streaming or bidirectional method`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
directions.unreliableRequests = true;
|
|
90
|
+
}
|
|
91
|
+
if (options.unreliableResponses) {
|
|
92
|
+
if (kind !== "serverStreaming" && kind !== "bidirectionalStreaming") {
|
|
93
|
+
throw invalidArgument(
|
|
94
|
+
`${name}: unreliable_responses requires a server-streaming or bidirectional method`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
directions.unreliableResponses = true;
|
|
98
|
+
}
|
|
99
|
+
return directions;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The directions `compression` lets compress, leaving out message types with a field marked
|
|
104
|
+
* `debug_redact`. `COMPRESSION_MESSAGE` compresses messages without a dictionary in both
|
|
105
|
+
* directions. `COMPRESSION_STREAM` does too, for the messages outside the stream, and
|
|
106
|
+
* stream-compresses the directions that stream; on a unary method it throws, as does a value this
|
|
107
|
+
* runtime does not know.
|
|
108
|
+
*/
|
|
109
|
+
function compressionDirections(name, kind, { compression }, method) {
|
|
110
|
+
if (compression === Compression.Unspecified) {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
if (compression !== Compression.Message && compression !== Compression.Stream) {
|
|
114
|
+
throw invalidArgument(`${name}: unknown compression ${compression}`);
|
|
115
|
+
}
|
|
116
|
+
if (compression === Compression.Stream && kind === "unary") {
|
|
117
|
+
throw invalidArgument(`${name}: COMPRESSION_STREAM requires a streaming method`);
|
|
118
|
+
}
|
|
119
|
+
const requests = !hasRedactedField(method.input);
|
|
120
|
+
const responses = !hasRedactedField(method.output);
|
|
121
|
+
const directions = {};
|
|
122
|
+
if (requests) {
|
|
123
|
+
directions.compressRequests = true;
|
|
124
|
+
}
|
|
125
|
+
if (responses) {
|
|
126
|
+
directions.compressResponses = true;
|
|
127
|
+
}
|
|
128
|
+
if (compression === Compression.Stream) {
|
|
129
|
+
if (requests && (kind === "clientStreaming" || kind === "bidirectionalStreaming")) {
|
|
130
|
+
directions.compressStreamRequests = true;
|
|
131
|
+
}
|
|
132
|
+
if (responses && (kind === "serverStreaming" || kind === "bidirectionalStreaming")) {
|
|
133
|
+
directions.compressStreamResponses = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return directions;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Whether each message type checked so far has a redacted field. */
|
|
140
|
+
const redacted = new WeakMap();
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Whether `message`, a DescMessage, has a field marked `debug_redact`, directly or in a
|
|
144
|
+
* message-typed field (singular, repeated or a map value), however deeply nested.
|
|
145
|
+
*/
|
|
146
|
+
export function hasRedactedField(message) {
|
|
147
|
+
let found = redacted.get(message);
|
|
148
|
+
if (found !== undefined) {
|
|
149
|
+
return found;
|
|
150
|
+
}
|
|
151
|
+
// Every message type reachable from this one; a cycle is visited once.
|
|
152
|
+
const reachable = new Set([message]);
|
|
153
|
+
found = false;
|
|
154
|
+
for (const type of reachable) {
|
|
155
|
+
for (const field of type.fields) {
|
|
156
|
+
if (field.proto?.options?.debugRedact === true) {
|
|
157
|
+
found = true;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
if (field.message !== undefined) {
|
|
161
|
+
reachable.add(field.message);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (found) {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
redacted.set(message, found);
|
|
169
|
+
return found;
|
|
170
|
+
}
|
package/src/qmux/session.js
CHANGED
|
@@ -69,6 +69,8 @@ export class QMuxWebSocketSession {
|
|
|
69
69
|
/** The highest QX_PING request sequence received, -1 before the first: a peer may start at 0. */
|
|
70
70
|
#pingReceived = -1;
|
|
71
71
|
#closedError = null;
|
|
72
|
+
/** Whether the WebSocket opened with the QMux subprotocol. */
|
|
73
|
+
#open = false;
|
|
72
74
|
#lastActivity = Date.now();
|
|
73
75
|
#idleTimer;
|
|
74
76
|
#handshakeTimer;
|
|
@@ -110,6 +112,20 @@ export class QMuxWebSocketSession {
|
|
|
110
112
|
);
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
/**
|
|
116
|
+
* How far the session got: "opening" until the WebSocket opens, "handshaking" until the server's
|
|
117
|
+
* transport parameters arrive, "ready" after them, and "closed" once the session ended.
|
|
118
|
+
*/
|
|
119
|
+
get stage() {
|
|
120
|
+
if (this.#closedError) {
|
|
121
|
+
return "closed";
|
|
122
|
+
}
|
|
123
|
+
if (this.#peer !== null) {
|
|
124
|
+
return "ready";
|
|
125
|
+
}
|
|
126
|
+
return this.#open ? "handshaking" : "opening";
|
|
127
|
+
}
|
|
128
|
+
|
|
113
129
|
/** Opens a bidirectional stream, waiting for the server's stream limit. */
|
|
114
130
|
async createBidirectionalStream() {
|
|
115
131
|
await this.ready;
|
|
@@ -154,6 +170,7 @@ export class QMuxWebSocketSession {
|
|
|
154
170
|
);
|
|
155
171
|
return;
|
|
156
172
|
}
|
|
173
|
+
this.#open = true;
|
|
157
174
|
// The transport parameters must be the first frame on the wire.
|
|
158
175
|
writeTransportParameters(this.#pending, {
|
|
159
176
|
initialMaxData: this.#window,
|