@trpc-proto/runtime 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 +19 -0
- package/dist/batch/grpcweb/direct_dispatcher.d.ts +10 -0
- package/dist/batch/grpcweb/direct_dispatcher.d.ts.map +1 -0
- package/dist/batch/grpcweb/direct_dispatcher.js +71 -0
- package/dist/batch/grpcweb/direct_dispatcher.js.map +1 -0
- package/dist/batch/grpcweb/forwarding_dispatcher.d.ts +5 -0
- package/dist/batch/grpcweb/forwarding_dispatcher.d.ts.map +1 -0
- package/dist/batch/grpcweb/forwarding_dispatcher.js +62 -0
- package/dist/batch/grpcweb/forwarding_dispatcher.js.map +1 -0
- package/dist/batch/index.d.ts +5 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/batch/index.js +4 -0
- package/dist/batch/index.js.map +1 -0
- package/dist/batch/link/web_link.d.ts +18 -0
- package/dist/batch/link/web_link.d.ts.map +1 -0
- package/dist/batch/link/web_link.js +216 -0
- package/dist/batch/link/web_link.js.map +1 -0
- package/dist/batch/proto/batch_router.d.ts +41 -0
- package/dist/batch/proto/batch_router.d.ts.map +1 -0
- package/dist/batch/proto/batch_router.js +59 -0
- package/dist/batch/proto/batch_router.js.map +1 -0
- package/dist/batch/proto/generated/schema.d.ts +262 -0
- package/dist/batch/proto/generated/schema.d.ts.map +1 -0
- package/dist/batch/proto/generated/schema.js +327 -0
- package/dist/batch/proto/generated/schema.js.map +1 -0
- package/dist/batch/proto/wire.d.ts +42 -0
- package/dist/batch/proto/wire.d.ts.map +1 -0
- package/dist/batch/proto/wire.js +52 -0
- package/dist/batch/proto/wire.js.map +1 -0
- package/dist/grpc/client.d.ts +10 -0
- package/dist/grpc/client.d.ts.map +1 -0
- package/dist/grpc/client.js +62 -0
- package/dist/grpc/client.js.map +1 -0
- package/dist/grpc/link.d.ts +11 -0
- package/dist/grpc/link.d.ts.map +1 -0
- package/dist/grpc/link.js +10 -0
- package/dist/grpc/link.js.map +1 -0
- package/dist/grpc/proto_link.d.ts +56 -0
- package/dist/grpc/proto_link.d.ts.map +1 -0
- package/dist/grpc/proto_link.js +112 -0
- package/dist/grpc/proto_link.js.map +1 -0
- package/dist/grpc/server.d.ts +28 -0
- package/dist/grpc/server.d.ts.map +1 -0
- package/dist/grpc/server.js +113 -0
- package/dist/grpc/server.js.map +1 -0
- package/dist/grpc/status.d.ts +5 -0
- package/dist/grpc/status.d.ts.map +1 -0
- package/dist/grpc/status.js +42 -0
- package/dist/grpc/status.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/noop.d.ts +10 -0
- package/dist/noop.d.ts.map +1 -0
- package/dist/noop.js +12 -0
- package/dist/noop.js.map +1 -0
- package/dist/proto_codec/index.d.ts +3 -0
- package/dist/proto_codec/index.d.ts.map +1 -0
- package/dist/proto_codec/index.js +2 -0
- package/dist/proto_codec/index.js.map +1 -0
- package/dist/proto_codec/proto_codec.d.ts +24 -0
- package/dist/proto_codec/proto_codec.d.ts.map +1 -0
- package/dist/proto_codec/proto_codec.js +327 -0
- package/dist/proto_codec/proto_codec.js.map +1 -0
- package/dist/router_invoker.d.ts +12 -0
- package/dist/router_invoker.d.ts.map +1 -0
- package/dist/router_invoker.js +79 -0
- package/dist/router_invoker.js.map +1 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/web/codec/base64_codec.d.ts +7 -0
- package/dist/web/codec/base64_codec.d.ts.map +1 -0
- package/dist/web/codec/base64_codec.js +57 -0
- package/dist/web/codec/base64_codec.js.map +1 -0
- package/dist/web/codec/codec.d.ts +33 -0
- package/dist/web/codec/codec.d.ts.map +1 -0
- package/dist/web/codec/codec.js +11 -0
- package/dist/web/codec/codec.js.map +1 -0
- package/dist/web/codec/compression.d.ts +15 -0
- package/dist/web/codec/compression.d.ts.map +1 -0
- package/dist/web/codec/compression.js +32 -0
- package/dist/web/codec/compression.js.map +1 -0
- package/dist/web/codec/frame_codec.d.ts +20 -0
- package/dist/web/codec/frame_codec.d.ts.map +1 -0
- package/dist/web/codec/frame_codec.js +109 -0
- package/dist/web/codec/frame_codec.js.map +1 -0
- package/dist/web/codec/protocol_codec.d.ts +40 -0
- package/dist/web/codec/protocol_codec.d.ts.map +1 -0
- package/dist/web/codec/protocol_codec.js +162 -0
- package/dist/web/codec/protocol_codec.js.map +1 -0
- package/dist/web/content_type.d.ts +19 -0
- package/dist/web/content_type.d.ts.map +1 -0
- package/dist/web/content_type.js +42 -0
- package/dist/web/content_type.js.map +1 -0
- package/dist/web/dispatchers/direct.d.ts +10 -0
- package/dist/web/dispatchers/direct.d.ts.map +1 -0
- package/dist/web/dispatchers/direct.js +72 -0
- package/dist/web/dispatchers/direct.js.map +1 -0
- package/dist/web/dispatchers/forward.d.ts +5 -0
- package/dist/web/dispatchers/forward.d.ts.map +1 -0
- package/dist/web/dispatchers/forward.js +99 -0
- package/dist/web/dispatchers/forward.js.map +1 -0
- package/dist/web/dispatchers/types.d.ts +21 -0
- package/dist/web/dispatchers/types.d.ts.map +1 -0
- package/dist/web/dispatchers/types.js +2 -0
- package/dist/web/dispatchers/types.js.map +1 -0
- package/dist/web/fetch_call.d.ts +13 -0
- package/dist/web/fetch_call.d.ts.map +1 -0
- package/dist/web/fetch_call.js +126 -0
- package/dist/web/fetch_call.js.map +1 -0
- package/dist/web/http_handler.d.ts +65 -0
- package/dist/web/http_handler.d.ts.map +1 -0
- package/dist/web/http_handler.js +295 -0
- package/dist/web/http_handler.js.map +1 -0
- package/dist/web/index.d.ts +8 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +4 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/link.d.ts +26 -0
- package/dist/web/link.d.ts.map +1 -0
- package/dist/web/link.js +24 -0
- package/dist/web/link.js.map +1 -0
- package/dist/web/server.d.ts +33 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +77 -0
- package/dist/web/server.js.map +1 -0
- package/package.json +90 -0
- package/src/batch/proto/generated/trpc_batch_v1.proto +40 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as grpc from '@grpc/grpc-js';
|
|
2
|
+
import { createForwardingBatchDispatcher } from '../../batch/grpcweb/forwarding_dispatcher.js';
|
|
3
|
+
function trailerRecord(metadata) {
|
|
4
|
+
if (!metadata)
|
|
5
|
+
return {};
|
|
6
|
+
const result = {};
|
|
7
|
+
for (const [key, value] of Object.entries(metadata.getMap())) {
|
|
8
|
+
if (value == null || Buffer.isBuffer(value))
|
|
9
|
+
continue;
|
|
10
|
+
result[key] = String(value);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
const identity = (value) => value;
|
|
15
|
+
function forwardingRoutes(schema) {
|
|
16
|
+
const routes = new Map();
|
|
17
|
+
for (const service of schema.services) {
|
|
18
|
+
for (const method of service.methods) {
|
|
19
|
+
const path = `/${schema.package}.${service.name}/${method.name}`;
|
|
20
|
+
routes.set(path, {
|
|
21
|
+
path,
|
|
22
|
+
requestStream: false,
|
|
23
|
+
responseStream: method.isResponseStreaming,
|
|
24
|
+
requestSerialize: identity,
|
|
25
|
+
requestDeserialize: identity,
|
|
26
|
+
responseSerialize: identity,
|
|
27
|
+
responseDeserialize: identity,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return routes;
|
|
32
|
+
}
|
|
33
|
+
function completionForCall({ call, signal, writes, unaryError, }) {
|
|
34
|
+
const completed = Promise.withResolvers();
|
|
35
|
+
let callEnded = false;
|
|
36
|
+
const cancelCall = () => {
|
|
37
|
+
if (!callEnded)
|
|
38
|
+
call.cancel();
|
|
39
|
+
};
|
|
40
|
+
if (signal.aborted)
|
|
41
|
+
cancelCall();
|
|
42
|
+
else
|
|
43
|
+
signal.addEventListener('abort', cancelCall, { once: true });
|
|
44
|
+
call.on('status', (status) => {
|
|
45
|
+
callEnded = true;
|
|
46
|
+
signal.removeEventListener('abort', cancelCall);
|
|
47
|
+
const result = unaryError?.() ?? status;
|
|
48
|
+
void writes().then(() => completed.resolve({
|
|
49
|
+
code: result.code,
|
|
50
|
+
message: result.details,
|
|
51
|
+
metadata: trailerRecord(result.metadata),
|
|
52
|
+
}), completed.reject);
|
|
53
|
+
});
|
|
54
|
+
return completed.promise;
|
|
55
|
+
}
|
|
56
|
+
export function createForwardingDispatcher(client, schema) {
|
|
57
|
+
const routes = forwardingRoutes(schema);
|
|
58
|
+
const dispatch = ({ grpcPath, message, metadata, signal }, emit) => {
|
|
59
|
+
const route = routes.get(grpcPath);
|
|
60
|
+
if (!route) {
|
|
61
|
+
return Promise.resolve({
|
|
62
|
+
code: grpc.status.UNIMPLEMENTED,
|
|
63
|
+
message: `no gRPC mapping for ${grpcPath}`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const grpcMetadata = new grpc.Metadata();
|
|
67
|
+
for (const [key, value] of metadata) {
|
|
68
|
+
grpcMetadata.set(key, value);
|
|
69
|
+
}
|
|
70
|
+
// Unary calls.
|
|
71
|
+
if (!route.responseStream) {
|
|
72
|
+
let writes = Promise.resolve();
|
|
73
|
+
let callbackError = null;
|
|
74
|
+
const call = client.makeUnaryRequest(route.path, route.requestSerialize, route.responseDeserialize, Buffer.from(message), grpcMetadata, (error, response) => {
|
|
75
|
+
callbackError = error;
|
|
76
|
+
if (!error && response !== undefined) {
|
|
77
|
+
writes = writes.then(() => emit(new Uint8Array(response)));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return completionForCall({
|
|
81
|
+
call,
|
|
82
|
+
signal,
|
|
83
|
+
writes: () => writes,
|
|
84
|
+
unaryError: () => callbackError,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const call = client.makeServerStreamRequest(route.path, route.requestSerialize, route.responseDeserialize, Buffer.from(message), grpcMetadata);
|
|
88
|
+
let writes = Promise.resolve();
|
|
89
|
+
call.on('data', (response) => {
|
|
90
|
+
writes = writes.then(() => emit(new Uint8Array(response)));
|
|
91
|
+
});
|
|
92
|
+
call.on('error', () => {
|
|
93
|
+
// `status` always follows and carries the canonical result.
|
|
94
|
+
});
|
|
95
|
+
return completionForCall({ call, signal, writes: () => writes });
|
|
96
|
+
};
|
|
97
|
+
return createForwardingBatchDispatcher(dispatch, schema);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=forward.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forward.js","sourceRoot":"","sources":["../../../src/web/dispatchers/forward.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Protocol-neutral input for one gRPC-Web dispatcher invocation. */
|
|
2
|
+
export interface GrpcWebDispatchRequest {
|
|
3
|
+
grpcPath: string;
|
|
4
|
+
message: Uint8Array;
|
|
5
|
+
metadata: ReadonlyMap<string, string>;
|
|
6
|
+
signal: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
/** Final gRPC status returned by a dispatcher. */
|
|
9
|
+
export interface GrpcWebDispatchStatus {
|
|
10
|
+
code: number;
|
|
11
|
+
message: string;
|
|
12
|
+
metadata?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
export type EmitGrpcWebMessage = (message: Uint8Array) => Promise<void>;
|
|
15
|
+
/** Per-invocation state consumed by dispatcher implementations. */
|
|
16
|
+
export interface GrpcWebDispatchOptions {
|
|
17
|
+
/** Pre-created tRPC context used only by direct dispatchers. */
|
|
18
|
+
directContext?: unknown;
|
|
19
|
+
}
|
|
20
|
+
export type GrpcWebDispatcher = (request: GrpcWebDispatchRequest, emit: EmitGrpcWebMessage, dispatchOptions?: GrpcWebDispatchOptions) => Promise<GrpcWebDispatchStatus>;
|
|
21
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/web/dispatchers/types.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,kDAAkD;AAClD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AACxE,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACrC,gEAAgE;IAChE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC9B,OAAO,EAAE,sBAAsB,EAC/B,IAAI,EAAE,kBAAkB,EACxB,eAAe,CAAC,EAAE,sBAAsB,KACrC,OAAO,CAAC,qBAAqB,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/web/dispatchers/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { StubCall } from '../grpc/proto_link.js';
|
|
2
|
+
import { type GrpcWebEncoding } from './content_type.js';
|
|
3
|
+
/** Configuration for the browser or Node Fetch gRPC-Web transport. */
|
|
4
|
+
export interface GrpcWebFetchCallOptions {
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** HTTP body representation. Defaults to base64 gRPC-Web. */
|
|
7
|
+
encoding?: GrpcWebEncoding;
|
|
8
|
+
/** Compresses the request message with gzip. Defaults to false. */
|
|
9
|
+
compress?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Creates a Fetch transport for binary or base64 gRPC-Web calls. */
|
|
12
|
+
export declare function createGrpcWebFetchCall(options?: GrpcWebFetchCallOptions): StubCall<Uint8Array | AsyncIterable<Uint8Array>>;
|
|
13
|
+
//# sourceMappingURL=fetch_call.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch_call.d.ts","sourceRoot":"","sources":["../../src/web/fetch_call.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,mBAAmB,CAAC;AAE3B,sEAAsE;AACtE,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAiED,qEAAqE;AACrE,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,uBAA4B,GACpC,QAAQ,CAAC,UAAU,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC,CAsElD"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Status as grpcStatus } from '@grpc/grpc-js/build/src/constants.js';
|
|
2
|
+
import { assumeExhaustive } from '@trpc-proto/utility';
|
|
3
|
+
import { GRPC_GZIP_ENCODING } from './codec/compression.js';
|
|
4
|
+
import { GrpcWebError, GrpcWebProtocolCodec } from './codec/protocol_codec.js';
|
|
5
|
+
import { grpcWebContentType, grpcWebEncoding, } from './content_type.js';
|
|
6
|
+
const HTTP_STATUS_TO_GRPC_STATUS = {
|
|
7
|
+
400: grpcStatus.INTERNAL,
|
|
8
|
+
401: grpcStatus.UNAUTHENTICATED,
|
|
9
|
+
403: grpcStatus.PERMISSION_DENIED,
|
|
10
|
+
404: grpcStatus.UNIMPLEMENTED,
|
|
11
|
+
429: grpcStatus.UNAVAILABLE,
|
|
12
|
+
502: grpcStatus.UNAVAILABLE,
|
|
13
|
+
503: grpcStatus.UNAVAILABLE,
|
|
14
|
+
504: grpcStatus.UNAVAILABLE,
|
|
15
|
+
};
|
|
16
|
+
/** Given the assumption is 200 with grpc-status trailers in the response, anything else is considered an error. */
|
|
17
|
+
function missingGrpcStatus(res) {
|
|
18
|
+
return new GrpcWebError(HTTP_STATUS_TO_GRPC_STATUS[res.status] ?? grpcStatus.UNKNOWN, `missing grpc-status (HTTP ${res.status}) from gRPC-Web response: ${res.statusText}`);
|
|
19
|
+
}
|
|
20
|
+
function responseEncoding(res, fallback) {
|
|
21
|
+
return grpcWebEncoding(res.headers.get('content-type')) ?? fallback;
|
|
22
|
+
}
|
|
23
|
+
async function* readResponseBody(res) {
|
|
24
|
+
const reader = res.body?.getReader();
|
|
25
|
+
if (!reader)
|
|
26
|
+
throw new GrpcWebError(13, 'empty gRPC-Web body');
|
|
27
|
+
while (true) {
|
|
28
|
+
const { done, value } = await reader.read();
|
|
29
|
+
if (value && value.byteLength > 0)
|
|
30
|
+
yield value;
|
|
31
|
+
if (done)
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function* readGrpcWebStream(res, encoding, codec) {
|
|
36
|
+
for await (const value of codec.decode(readResponseBody(res), {
|
|
37
|
+
encoding,
|
|
38
|
+
compression: res.headers.get('grpc-encoding'),
|
|
39
|
+
})) {
|
|
40
|
+
switch (value.kind) {
|
|
41
|
+
case 'message':
|
|
42
|
+
yield value.payload;
|
|
43
|
+
break;
|
|
44
|
+
case 'trailers':
|
|
45
|
+
if (value.status !== grpcStatus.OK) {
|
|
46
|
+
throw new GrpcWebError(value.status, value.message || `grpc-status ${value.status}`);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
default:
|
|
50
|
+
return assumeExhaustive(value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
throw missingGrpcStatus(res);
|
|
54
|
+
}
|
|
55
|
+
/** Creates a Fetch transport for binary or base64 gRPC-Web calls. */
|
|
56
|
+
export function createGrpcWebFetchCall(options = {}) {
|
|
57
|
+
const { baseUrl = '', encoding = 'base64', compress = false } = options;
|
|
58
|
+
const codec = new GrpcWebProtocolCodec();
|
|
59
|
+
return async (request) => {
|
|
60
|
+
const path = request.grpcPath;
|
|
61
|
+
if (!path)
|
|
62
|
+
throw new Error('grpcPath required');
|
|
63
|
+
const contentType = grpcWebContentType(encoding);
|
|
64
|
+
const compression = compress ? GRPC_GZIP_ENCODING : undefined;
|
|
65
|
+
const body = await codec.encode({
|
|
66
|
+
kind: 'message',
|
|
67
|
+
payload: request.bytes ?? new Uint8Array(),
|
|
68
|
+
}, { encoding, compress, compression });
|
|
69
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
...(request.metadata ?? {}),
|
|
73
|
+
accept: contentType,
|
|
74
|
+
'content-type': contentType,
|
|
75
|
+
'grpc-accept-encoding': GRPC_GZIP_ENCODING,
|
|
76
|
+
...(compression ? { 'grpc-encoding': compression } : {}),
|
|
77
|
+
'x-grpc-web': '1',
|
|
78
|
+
'x-user-agent': 'grpc-web-javascript/0.1',
|
|
79
|
+
},
|
|
80
|
+
body,
|
|
81
|
+
signal: request.signal,
|
|
82
|
+
});
|
|
83
|
+
if (res.status !== 200)
|
|
84
|
+
throw missingGrpcStatus(res);
|
|
85
|
+
const encodingFromResponse = responseEncoding(res, encoding);
|
|
86
|
+
switch (request.type) {
|
|
87
|
+
case 'subscription':
|
|
88
|
+
return readGrpcWebStream(res, encodingFromResponse, codec);
|
|
89
|
+
case 'query':
|
|
90
|
+
case 'mutation':
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
return assumeExhaustive(request.type);
|
|
94
|
+
}
|
|
95
|
+
// Unary gRPC-Web response → single message or error.
|
|
96
|
+
let message;
|
|
97
|
+
let status;
|
|
98
|
+
let statusMessage = '';
|
|
99
|
+
for await (const value of codec.decode(readResponseBody(res), {
|
|
100
|
+
encoding: encodingFromResponse,
|
|
101
|
+
compression: res.headers.get('grpc-encoding'),
|
|
102
|
+
})) {
|
|
103
|
+
switch (value.kind) {
|
|
104
|
+
case 'message':
|
|
105
|
+
message ??= value.payload;
|
|
106
|
+
break;
|
|
107
|
+
case 'trailers':
|
|
108
|
+
status = value.status;
|
|
109
|
+
statusMessage = value.message || `grpc-status ${status}`;
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
assumeExhaustive(value);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (status === undefined)
|
|
116
|
+
throw missingGrpcStatus(res);
|
|
117
|
+
if (status !== grpcStatus.OK) {
|
|
118
|
+
throw new GrpcWebError(status, statusMessage);
|
|
119
|
+
}
|
|
120
|
+
if (message === undefined) {
|
|
121
|
+
throw new GrpcWebError(grpcStatus.INTERNAL, 'empty gRPC-Web response');
|
|
122
|
+
}
|
|
123
|
+
return message;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=fetch_call.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch_call.js","sourceRoot":"","sources":["../../src/web/fetch_call.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EACL,kBAAkB,EAClB,eAAe,GAEhB,MAAM,mBAAmB,CAAC;AAU3B,MAAM,0BAA0B,GAA+B;IAC7D,GAAG,EAAE,UAAU,CAAC,QAAQ;IACxB,GAAG,EAAE,UAAU,CAAC,eAAe;IAC/B,GAAG,EAAE,UAAU,CAAC,iBAAiB;IACjC,GAAG,EAAE,UAAU,CAAC,aAAa;IAC7B,GAAG,EAAE,UAAU,CAAC,WAAW;IAC3B,GAAG,EAAE,UAAU,CAAC,WAAW;IAC3B,GAAG,EAAE,UAAU,CAAC,WAAW;IAC3B,GAAG,EAAE,UAAU,CAAC,WAAW;CAC5B,CAAC;AAEF,mHAAmH;AACnH,SAAS,iBAAiB,CAAC,GAAa;IACtC,OAAO,IAAI,YAAY,CACrB,0BAA0B,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,UAAU,CAAC,OAAO,EAC5D,6BAA6B,GAAG,CAAC,MAAM,6BAA6B,GAAG,CAAC,UAAU,EAAE,CACrF,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CACvB,GAAa,EACb,QAAyB;IAEzB,OAAO,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,IAAI,QAAQ,CAAC;AACtE,CAAC;AAED,KAAK,SAAS,CAAC,CAAC,gBAAgB,CAAC,GAAa;IAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC;IACrC,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,YAAY,CAAC,EAAE,EAAE,qBAAqB,CAAC,CAAC;IAC/D,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAC5C,IAAI,KAAK,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC;YAAE,MAAM,KAAK,CAAC;QAC/C,IAAI,IAAI;YAAE,OAAO;IACnB,CAAC;AACH,CAAC;AAED,KAAK,SAAS,CAAC,CAAC,iBAAiB,CAC/B,GAAa,EACb,QAAyB,EACzB,KAA2B;IAE3B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE;QAC5D,QAAQ;QACR,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;KAC9C,CAAC,EAAE,CAAC;QACH,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,SAAS;gBACZ,MAAM,KAAK,CAAC,OAAO,CAAC;gBACpB,MAAM;YACR,KAAK,UAAU;gBACb,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,CAAC,EAAE,EAAE,CAAC;oBACnC,MAAM,IAAI,YAAY,CACpB,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,OAAO,IAAI,eAAe,KAAK,CAAC,MAAM,EAAE,CAC/C,CAAC;gBACJ,CAAC;gBACD,OAAO;YACT;gBACE,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IACD,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,sBAAsB,CACpC,UAAmC,EAAE;IAErC,MAAM,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,QAAQ,EAAE,QAAQ,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IACxE,MAAM,KAAK,GAAG,IAAI,oBAAoB,EAAE,CAAC;IACzC,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAChD,MAAM,WAAW,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QACjD,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9D,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,MAAM,CAC7B;YACE,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,UAAU,EAAE;SAC3C,EACD,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,CACpC,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,GAAG,IAAI,EAAE,EAAE;YAC3C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;gBAC3B,MAAM,EAAE,WAAW;gBACnB,cAAc,EAAE,WAAW;gBAC3B,sBAAsB,EAAE,kBAAkB;gBAC1C,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACxD,YAAY,EAAE,GAAG;gBACjB,cAAc,EAAE,yBAAyB;aAC1C;YACD,IAAI;YACJ,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;YAAE,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACrD,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC7D,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;YACrB,KAAK,cAAc;gBACjB,OAAO,iBAAiB,CAAC,GAAG,EAAE,oBAAoB,EAAE,KAAK,CAAC,CAAC;YAC7D,KAAK,OAAO,CAAC;YACb,KAAK,UAAU;gBACb,MAAM;YACR;gBACE,OAAO,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC;QAED,qDAAqD;QACrD,IAAI,OAA+B,CAAC;QACpC,IAAI,MAA0B,CAAC;QAC/B,IAAI,aAAa,GAAG,EAAE,CAAC;QACvB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE;YAC5D,QAAQ,EAAE,oBAAoB;YAC9B,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;SAC9C,CAAC,EAAE,CAAC;YACH,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;gBACnB,KAAK,SAAS;oBACZ,OAAO,KAAK,KAAK,CAAC,OAAO,CAAC;oBAC1B,MAAM;gBACR,KAAK,UAAU;oBACb,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;oBACtB,aAAa,GAAG,KAAK,CAAC,OAAO,IAAI,eAAe,MAAM,EAAE,CAAC;oBACzD,MAAM;gBACR;oBACE,gBAAgB,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QACD,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACvD,IAAI,MAAM,KAAK,UAAU,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,IAAI,YAAY,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QAChD,CAAC;QACD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,IAAI,YAAY,CAAC,UAAU,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { AnyRouter } from '@trpc/server';
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
import type { ProtoSchema } from '@trpc-proto/schema_ir';
|
|
4
|
+
/** CORS policy applied to browser requests handled by the gRPC-Web HTTP handler. */
|
|
5
|
+
export interface GrpcWebCorsOptions {
|
|
6
|
+
/** Exact serialized origins allowed to call the handler. */
|
|
7
|
+
readonly allowedOrigins: readonly string[];
|
|
8
|
+
/** Request headers allowed in addition to the gRPC-Web defaults. */
|
|
9
|
+
readonly additionalAllowedHeaders?: readonly string[];
|
|
10
|
+
/** Browser preflight cache lifetime in seconds. Defaults to 600. */
|
|
11
|
+
readonly maxAgeSeconds?: number;
|
|
12
|
+
}
|
|
13
|
+
/** TLS configuration for the native upstream gRPC channel. */
|
|
14
|
+
export interface MtlsConfig {
|
|
15
|
+
type: 'mtls';
|
|
16
|
+
/** CA bundle used to verify the upstream gRPC server's certificate. */
|
|
17
|
+
caCertPath: string;
|
|
18
|
+
/**
|
|
19
|
+
* Expected SAN or hostname on the upstream server certificate. Maps to
|
|
20
|
+
* `grpc.ssl_target_name_override` in grpc-js.
|
|
21
|
+
*/
|
|
22
|
+
serverNameOverride?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Client identity presented during TLS. When omitted, the channel uses
|
|
25
|
+
* one-way TLS.
|
|
26
|
+
*/
|
|
27
|
+
clientIdentity?: {
|
|
28
|
+
certPath: string;
|
|
29
|
+
keyPath: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Explicit transport security for the native upstream gRPC channel. */
|
|
33
|
+
export type GrpcWebUpstreamCredentials = {
|
|
34
|
+
type: 'insecure';
|
|
35
|
+
} | MtlsConfig;
|
|
36
|
+
/** Forwarding handler with an owned upstream client lifecycle. */
|
|
37
|
+
export interface ForwardingGrpcWebHttpHandler {
|
|
38
|
+
(request: IncomingMessage, response: ServerResponse): Promise<boolean>;
|
|
39
|
+
close(): void;
|
|
40
|
+
}
|
|
41
|
+
/** Configuration for forwarding browser gRPC-Web calls to a gRPC backend. */
|
|
42
|
+
export interface ForwardingGrpcWebHttpHandlerOptions {
|
|
43
|
+
/** Schema used to select unary or server-streaming grpc-js calls. */
|
|
44
|
+
schema: ProtoSchema;
|
|
45
|
+
/** Native gRPC backend address. Defaults to `127.0.0.1:50051`. */
|
|
46
|
+
address?: string;
|
|
47
|
+
/** Transport security for the upstream channel. Defaults to insecure. */
|
|
48
|
+
credentials?: GrpcWebUpstreamCredentials;
|
|
49
|
+
/** Optional cross-origin request policy. CORS is disabled when omitted. */
|
|
50
|
+
cors?: GrpcWebCorsOptions;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates one Node HTTP request handler that forwards gRPC-Web calls to a
|
|
54
|
+
* native gRPC backend. CORS is disabled unless configured.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createForwardingGrpcWebHttpHandler(options: ForwardingGrpcWebHttpHandlerOptions): ForwardingGrpcWebHttpHandler;
|
|
57
|
+
/** Configuration for dispatching gRPC-Web calls directly to a tRPC router. */
|
|
58
|
+
export interface DirectGrpcWebHttpHandlerOptions {
|
|
59
|
+
schema: ProtoSchema;
|
|
60
|
+
createContext?: () => unknown | Promise<unknown>;
|
|
61
|
+
cors?: GrpcWebCorsOptions;
|
|
62
|
+
}
|
|
63
|
+
/** Creates a gRPC-Web handler that dispatches directly to a tRPC router. */
|
|
64
|
+
export declare function createDirectGrpcWebHttpHandler(router: AnyRouter, options: DirectGrpcWebHttpHandlerOptions): (request: IncomingMessage, response: ServerResponse) => Promise<boolean>;
|
|
65
|
+
//# sourceMappingURL=http_handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http_handler.d.ts","sourceRoot":"","sources":["../../src/web/http_handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9C,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AA+BzD,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,oEAAoE;IACpE,QAAQ,CAAC,wBAAwB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtD,oEAAoE;IACpE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,8DAA8D;AAC9D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,cAAc,CAAC,EAAE;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,wEAAwE;AACxE,MAAM,MAAM,0BAA0B,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG,UAAU,CAAC;AAE3E,kEAAkE;AAClE,MAAM,WAAW,4BAA4B;IAC3C,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,KAAK,IAAI,IAAI,CAAC;CACf;AAED,6EAA6E;AAC7E,MAAM,WAAW,mCAAmC;IAClD,qEAAqE;IACrE,MAAM,EAAE,WAAW,CAAC;IACpB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,WAAW,CAAC,EAAE,0BAA0B,CAAC;IACzC,2EAA2E;IAC3E,IAAI,CAAC,EAAE,kBAAkB,CAAC;CAC3B;AAuUD;;;GAGG;AACH,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,mCAAmC,GAC3C,4BAA4B,CAe9B;AAED,8EAA8E;AAC9E,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,WAAW,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,IAAI,CAAC,EAAE,kBAAkB,CAAC;CAC3B;AAED,4EAA4E;AAC5E,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,+BAA+B,aA5L7B,eAAe,YACd,cAAc,KACvB,OAAO,CAAC,OAAO,CAAC,CAgMpB"}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { assumeExhaustive } from '@trpc-proto/utility';
|
|
2
|
+
import * as grpc from '@grpc/grpc-js';
|
|
3
|
+
import { once } from 'node:events';
|
|
4
|
+
import { GrpcWebProtocolCodec } from './codec/protocol_codec.js';
|
|
5
|
+
import { grpcWebContentType, grpcWebEncoding, negotiateGrpcWebResponseEncoding, } from './content_type.js';
|
|
6
|
+
import { createDirectDispatcher } from './dispatchers/direct.js';
|
|
7
|
+
import { createForwardingDispatcher } from './dispatchers/forward.js';
|
|
8
|
+
const DEFAULT_ADDRESS = '127.0.0.1:50051';
|
|
9
|
+
const DEFAULT_CORS_MAX_AGE_SECONDS = 600;
|
|
10
|
+
const DEFAULT_CORS_REQUEST_HEADERS = [
|
|
11
|
+
'accept',
|
|
12
|
+
'authorization',
|
|
13
|
+
'content-type',
|
|
14
|
+
'grpc-accept-encoding',
|
|
15
|
+
'grpc-encoding',
|
|
16
|
+
'grpc-timeout',
|
|
17
|
+
'x-grpc-web',
|
|
18
|
+
'x-user-agent',
|
|
19
|
+
];
|
|
20
|
+
const CORS_EXPOSE_HEADERS = 'grpc-status, grpc-message, grpc-accept-encoding, grpc-encoding, x-grpc-web';
|
|
21
|
+
/** Do not set these request headers as gRPC metadata. */
|
|
22
|
+
const SKIP_METADATA_HEADERS = {
|
|
23
|
+
accept: true,
|
|
24
|
+
'accept-encoding': true,
|
|
25
|
+
connection: true,
|
|
26
|
+
'content-length': true,
|
|
27
|
+
'content-type': true,
|
|
28
|
+
'grpc-accept-encoding': true,
|
|
29
|
+
'grpc-encoding': true,
|
|
30
|
+
host: true,
|
|
31
|
+
'keep-alive': true,
|
|
32
|
+
origin: true,
|
|
33
|
+
'proxy-authenticate': true,
|
|
34
|
+
'proxy-authorization': true,
|
|
35
|
+
'proxy-connection': true,
|
|
36
|
+
te: true,
|
|
37
|
+
trailer: true,
|
|
38
|
+
'transfer-encoding': true,
|
|
39
|
+
upgrade: true,
|
|
40
|
+
'user-agent': true,
|
|
41
|
+
'x-grpc-web': true,
|
|
42
|
+
'x-user-agent': true,
|
|
43
|
+
};
|
|
44
|
+
function requestHeader(req, name) {
|
|
45
|
+
const value = req.headers[name];
|
|
46
|
+
return typeof value === 'string' ? value : undefined;
|
|
47
|
+
}
|
|
48
|
+
function requestEncoding(req) {
|
|
49
|
+
return grpcWebEncoding(requestHeader(req, 'content-type'));
|
|
50
|
+
}
|
|
51
|
+
async function readBody(req) {
|
|
52
|
+
const chunks = [];
|
|
53
|
+
for await (const chunk of req) {
|
|
54
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
55
|
+
}
|
|
56
|
+
return Buffer.concat(chunks);
|
|
57
|
+
}
|
|
58
|
+
function metadataFromRequest(request) {
|
|
59
|
+
const metadata = new Map();
|
|
60
|
+
for (const [key, value] of Object.entries(request.headers)) {
|
|
61
|
+
if (value == null)
|
|
62
|
+
continue;
|
|
63
|
+
const name = key.toLowerCase();
|
|
64
|
+
if (SKIP_METADATA_HEADERS[name] || name.startsWith(':'))
|
|
65
|
+
continue;
|
|
66
|
+
const text = Array.isArray(value) ? value.join(',') : value;
|
|
67
|
+
if (text)
|
|
68
|
+
metadata.set(name, text);
|
|
69
|
+
}
|
|
70
|
+
return metadata;
|
|
71
|
+
}
|
|
72
|
+
function corsHeaders(origin) {
|
|
73
|
+
return {
|
|
74
|
+
'access-control-allow-origin': origin,
|
|
75
|
+
'access-control-expose-headers': CORS_EXPOSE_HEADERS,
|
|
76
|
+
vary: 'Origin',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function grpcWebHeaders(encoding, origin) {
|
|
80
|
+
return {
|
|
81
|
+
'content-type': grpcWebContentType(encoding),
|
|
82
|
+
'grpc-accept-encoding': 'gzip',
|
|
83
|
+
...(origin ? corsHeaders(origin) : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function createCorsPolicy(cors) {
|
|
87
|
+
const maxAgeSeconds = cors.maxAgeSeconds ?? DEFAULT_CORS_MAX_AGE_SECONDS;
|
|
88
|
+
if (!Number.isSafeInteger(maxAgeSeconds) || maxAgeSeconds < 0) {
|
|
89
|
+
throw new RangeError('cors.maxAgeSeconds must be a non-negative integer');
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
allowedOrigins: new Set(cors.allowedOrigins),
|
|
93
|
+
allowedHeaders: new Set([
|
|
94
|
+
...DEFAULT_CORS_REQUEST_HEADERS,
|
|
95
|
+
...(cors.additionalAllowedHeaders ?? []),
|
|
96
|
+
].map((name) => name.toLowerCase())),
|
|
97
|
+
maxAgeSeconds,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function requestedCorsHeaders(req) {
|
|
101
|
+
return (requestHeader(req, 'access-control-request-headers') ?? '')
|
|
102
|
+
.split(',')
|
|
103
|
+
.map((name) => name.trim().toLowerCase())
|
|
104
|
+
.filter(Boolean);
|
|
105
|
+
}
|
|
106
|
+
function handleCorsPreflight(req, res, cors) {
|
|
107
|
+
if (req.method !== 'OPTIONS' || !cors)
|
|
108
|
+
return false;
|
|
109
|
+
const origin = requestHeader(req, 'origin');
|
|
110
|
+
if (!origin)
|
|
111
|
+
return false;
|
|
112
|
+
const vary = 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers';
|
|
113
|
+
if (!cors.allowedOrigins.has(origin)) {
|
|
114
|
+
res.writeHead(403, { vary, 'content-length': '0' });
|
|
115
|
+
res.end();
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
const method = requestHeader(req, 'access-control-request-method');
|
|
119
|
+
if (method?.toUpperCase() !== 'POST') {
|
|
120
|
+
res.writeHead(405, {
|
|
121
|
+
...corsHeaders(origin),
|
|
122
|
+
vary,
|
|
123
|
+
allow: 'POST',
|
|
124
|
+
'access-control-allow-methods': 'POST',
|
|
125
|
+
'content-length': '0',
|
|
126
|
+
});
|
|
127
|
+
res.end();
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
const requestedHeaders = requestedCorsHeaders(req);
|
|
131
|
+
if (requestedHeaders.some((name) => !cors.allowedHeaders.has(name))) {
|
|
132
|
+
res.writeHead(403, {
|
|
133
|
+
...corsHeaders(origin),
|
|
134
|
+
vary,
|
|
135
|
+
'content-length': '0',
|
|
136
|
+
});
|
|
137
|
+
res.end();
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
res.writeHead(204, {
|
|
141
|
+
...corsHeaders(origin),
|
|
142
|
+
vary,
|
|
143
|
+
'access-control-allow-methods': 'POST',
|
|
144
|
+
'access-control-allow-headers': [...cors.allowedHeaders].join(', '),
|
|
145
|
+
'access-control-max-age': String(cors.maxAgeSeconds),
|
|
146
|
+
'content-length': '0',
|
|
147
|
+
});
|
|
148
|
+
res.end();
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
async function writeGrpcWebBody(response, body, signal) {
|
|
152
|
+
if (!response.write(body))
|
|
153
|
+
await once(response, 'drain', { signal });
|
|
154
|
+
}
|
|
155
|
+
function createGrpcWebRequestHandler(corsPolicy, dispatch) {
|
|
156
|
+
const codec = new GrpcWebProtocolCodec();
|
|
157
|
+
return async function handleGrpcWebHttpRequest(request, response) {
|
|
158
|
+
if (handleCorsPreflight(request, response, corsPolicy))
|
|
159
|
+
return true;
|
|
160
|
+
const requestBodyEncoding = requestEncoding(request);
|
|
161
|
+
if (request.method !== 'POST' || !requestBodyEncoding)
|
|
162
|
+
return false;
|
|
163
|
+
const origin = requestHeader(request, 'origin');
|
|
164
|
+
if (origin && corsPolicy && !corsPolicy.allowedOrigins.has(origin)) {
|
|
165
|
+
response.writeHead(403, { vary: 'Origin', 'content-length': '0' });
|
|
166
|
+
response.end();
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
const responseOrigin = origin && corsPolicy?.allowedOrigins.has(origin) ? origin : undefined;
|
|
170
|
+
const responseBodyEncoding = negotiateGrpcWebResponseEncoding(requestHeader(request, 'accept'), requestBodyEncoding);
|
|
171
|
+
try {
|
|
172
|
+
const messages = [];
|
|
173
|
+
const encodedBody = await readBody(request);
|
|
174
|
+
for await (const value of codec.decode(encodedBody, {
|
|
175
|
+
encoding: requestBodyEncoding,
|
|
176
|
+
compression: requestHeader(request, 'grpc-encoding'),
|
|
177
|
+
})) {
|
|
178
|
+
switch (value.kind) {
|
|
179
|
+
case 'message':
|
|
180
|
+
messages.push(value.payload);
|
|
181
|
+
break;
|
|
182
|
+
case 'trailers':
|
|
183
|
+
break;
|
|
184
|
+
default:
|
|
185
|
+
assumeExhaustive(value);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const message = messages.length === 1 ? messages[0] : undefined;
|
|
189
|
+
if (!message)
|
|
190
|
+
throw new Error('unary gRPC-Web expects one data frame');
|
|
191
|
+
const abortController = new AbortController();
|
|
192
|
+
let callEnded = false;
|
|
193
|
+
const cancelCall = () => {
|
|
194
|
+
if (!callEnded)
|
|
195
|
+
abortController.abort();
|
|
196
|
+
};
|
|
197
|
+
const detachBrowserConnectionListeners = () => {
|
|
198
|
+
request.off('aborted', cancelCall);
|
|
199
|
+
response.off('close', cancelCall);
|
|
200
|
+
};
|
|
201
|
+
request.once('aborted', cancelCall);
|
|
202
|
+
response.once('close', cancelCall);
|
|
203
|
+
response.writeHead(200, grpcWebHeaders(responseBodyEncoding, responseOrigin));
|
|
204
|
+
let status;
|
|
205
|
+
try {
|
|
206
|
+
const url = new URL(request.url ?? '/', 'http://grpc-web.invalid');
|
|
207
|
+
status = await dispatch({
|
|
208
|
+
grpcPath: url.pathname,
|
|
209
|
+
message,
|
|
210
|
+
metadata: metadataFromRequest(request),
|
|
211
|
+
signal: abortController.signal,
|
|
212
|
+
}, async (payload) => {
|
|
213
|
+
if (abortController.signal.aborted ||
|
|
214
|
+
response.writableEnded ||
|
|
215
|
+
response.destroyed) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const body = await codec.encode({ kind: 'message', payload }, { encoding: responseBodyEncoding });
|
|
219
|
+
await writeGrpcWebBody(response, body, abortController.signal);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
callEnded = true;
|
|
224
|
+
detachBrowserConnectionListeners();
|
|
225
|
+
}
|
|
226
|
+
if (!abortController.signal.aborted &&
|
|
227
|
+
!response.writableEnded &&
|
|
228
|
+
!response.destroyed) {
|
|
229
|
+
const trailer = await codec.encode({
|
|
230
|
+
kind: 'trailers',
|
|
231
|
+
status: status.code,
|
|
232
|
+
message: status.message,
|
|
233
|
+
metadata: status.metadata,
|
|
234
|
+
}, { encoding: responseBodyEncoding });
|
|
235
|
+
response.end(trailer);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (!response.writableEnded && !response.destroyed) {
|
|
240
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
241
|
+
const trailer = await codec.encode({
|
|
242
|
+
kind: 'trailers',
|
|
243
|
+
status: grpc.status.INTERNAL,
|
|
244
|
+
message,
|
|
245
|
+
}, { encoding: responseBodyEncoding });
|
|
246
|
+
if (!response.headersSent) {
|
|
247
|
+
response.writeHead(200, {
|
|
248
|
+
...grpcWebHeaders(responseBodyEncoding, responseOrigin),
|
|
249
|
+
'content-length': String(trailer.byteLength),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
response.end(trailer);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function createForwardingClient(address, credentials) {
|
|
259
|
+
if (credentials.type === 'insecure') {
|
|
260
|
+
return new grpc.Client(address, grpc.credentials.createInsecure(), {});
|
|
261
|
+
}
|
|
262
|
+
const mtls = credentials;
|
|
263
|
+
const identity = mtls.clientIdentity;
|
|
264
|
+
const certificateProvider = new grpc.experimental.FileWatcherCertificateProvider({
|
|
265
|
+
caCertificateFile: mtls.caCertPath,
|
|
266
|
+
certificateFile: identity?.certPath,
|
|
267
|
+
privateKeyFile: identity?.keyPath,
|
|
268
|
+
refreshIntervalMs: 1_000,
|
|
269
|
+
});
|
|
270
|
+
const channelCredentials = grpc.experimental.createCertificateProviderChannelCredentials(certificateProvider, identity ? certificateProvider : null);
|
|
271
|
+
const channelOptions = mtls.serverNameOverride
|
|
272
|
+
? {
|
|
273
|
+
'grpc.ssl_target_name_override': mtls.serverNameOverride,
|
|
274
|
+
'grpc.default_authority': mtls.serverNameOverride,
|
|
275
|
+
}
|
|
276
|
+
: {};
|
|
277
|
+
return new grpc.Client(address, channelCredentials, channelOptions);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Creates one Node HTTP request handler that forwards gRPC-Web calls to a
|
|
281
|
+
* native gRPC backend. CORS is disabled unless configured.
|
|
282
|
+
*/
|
|
283
|
+
export function createForwardingGrpcWebHttpHandler(options) {
|
|
284
|
+
const { schema, address = DEFAULT_ADDRESS, credentials = { type: 'insecure' }, cors, } = options;
|
|
285
|
+
const client = createForwardingClient(address, credentials);
|
|
286
|
+
const handler = createGrpcWebRequestHandler(cors ? createCorsPolicy(cors) : undefined, createForwardingDispatcher(client, schema));
|
|
287
|
+
return Object.assign(handler, {
|
|
288
|
+
close: () => client.close(),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/** Creates a gRPC-Web handler that dispatches directly to a tRPC router. */
|
|
292
|
+
export function createDirectGrpcWebHttpHandler(router, options) {
|
|
293
|
+
return createGrpcWebRequestHandler(options.cors ? createCorsPolicy(options.cors) : undefined, createDirectDispatcher(router, options));
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=http_handler.js.map
|