@protobuf-ts/grpcweb-transport
Version:
gRPC-web transport for clients generated by the protoc plugin "protobuf-ts"
198 lines (197 loc) • 12.7 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.GrpcWebFetchTransport = void 0;
const runtime_rpc_1 = require("@protobuf-ts/runtime-rpc");
const grpc_web_format_1 = require("./grpc-web-format");
const goog_grpc_status_code_1 = require("./goog-grpc-status-code");
/**
* Implements the grpc-web protocol, supporting text format or binary
* format on the wire. Uses the fetch API to do the HTTP requests.
*
* Does not support client streaming or duplex calls because grpc-web
* does not support them.
*/
class GrpcWebFetchTransport {
constructor(defaultOptions) {
this.defaultOptions = defaultOptions;
}
mergeOptions(options) {
return runtime_rpc_1.mergeRpcOptions(this.defaultOptions, options);
}
/**
* Create an URI for a gRPC web call.
*
* Takes the `baseUrl` option and appends:
* - slash "/"
* - package name
* - dot "."
* - service name
* - slash "/"
* - method name
*
* If the service was declared without a package, the package name and dot
* are omitted.
*
* All names are used exactly like declared in .proto.
*/
makeUrl(method, options) {
let base = options.baseUrl;
if (base.endsWith('/'))
base = base.substring(0, base.length - 1);
return `${base}/${method.service.typeName}/${method.name}`;
}
clientStreaming(method /*, options: RpcOptions*/) {
const e = new runtime_rpc_1.RpcError('Client streaming is not supported by grpc-web', goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.UNIMPLEMENTED]);
e.methodName = method.name;
e.serviceName = method.service.typeName;
throw e;
}
duplex(method /*, options: RpcOptions*/) {
const e = new runtime_rpc_1.RpcError('Duplex streaming is not supported by grpc-web', goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.UNIMPLEMENTED]);
e.methodName = method.name;
e.serviceName = method.service.typeName;
throw e;
}
serverStreaming(method, input, options) {
var _a, _b, _c, _d, _e;
let opt = options, format = (_a = opt.format) !== null && _a !== void 0 ? _a : 'text', fetch = (_b = opt.fetch) !== null && _b !== void 0 ? _b : globalThis.fetch, fetchInit = (_c = opt.fetchInit) !== null && _c !== void 0 ? _c : {}, url = this.makeUrl(method, opt), inputBytes = method.I.toBinary(input, opt.binaryOptions), defHeader = new runtime_rpc_1.Deferred(), responseStream = new runtime_rpc_1.RpcOutputStreamController(), responseEmptyBody = true, maybeStatus, defStatus = new runtime_rpc_1.Deferred(), maybeTrailer, defTrailer = new runtime_rpc_1.Deferred();
fetch(url, Object.assign(Object.assign({}, fetchInit), { method: 'POST', headers: grpc_web_format_1.createGrpcWebRequestHeader(new globalThis.Headers(), format, opt.timeout, opt.meta), body: grpc_web_format_1.createGrpcWebRequestBody(inputBytes, format), signal: (_d = options.abort) !== null && _d !== void 0 ? _d : null // node-fetch@3.0.0-beta.9 rejects `undefined`
}))
.then(fetchResponse => {
let [code, detail, meta] = grpc_web_format_1.readGrpcWebResponseHeader(fetchResponse);
defHeader.resolve(meta);
if (code != null && code !== goog_grpc_status_code_1.GrpcStatusCode.OK)
throw new runtime_rpc_1.RpcError(detail !== null && detail !== void 0 ? detail : goog_grpc_status_code_1.GrpcStatusCode[code], goog_grpc_status_code_1.GrpcStatusCode[code], meta);
if (code != null)
maybeStatus = {
code: goog_grpc_status_code_1.GrpcStatusCode[code],
detail: detail !== null && detail !== void 0 ? detail : goog_grpc_status_code_1.GrpcStatusCode[code]
};
return fetchResponse;
})
.then(fetchResponse => {
if (!fetchResponse.body)
throw new runtime_rpc_1.RpcError('missing response body', goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.INTERNAL]);
return grpc_web_format_1.readGrpcWebResponseBody(fetchResponse.body, fetchResponse.headers.get('content-type'), (type, data) => {
switch (type) {
case grpc_web_format_1.GrpcWebFrame.DATA:
responseStream.notifyMessage(method.O.fromBinary(data, opt.binaryOptions));
responseEmptyBody = false;
break;
case grpc_web_format_1.GrpcWebFrame.TRAILER:
let code, detail;
[code, detail, maybeTrailer] = grpc_web_format_1.readGrpcWebResponseTrailer(data);
maybeStatus = {
code: goog_grpc_status_code_1.GrpcStatusCode[code],
detail: detail !== null && detail !== void 0 ? detail : goog_grpc_status_code_1.GrpcStatusCode[code]
};
break;
}
});
})
.then(() => {
if (!maybeTrailer && !responseEmptyBody)
throw new runtime_rpc_1.RpcError(`missing trailers`, goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.DATA_LOSS]);
// istanbul ignore if - this should be impossible and only here to satisfy TypeScript
if (!maybeStatus)
throw new runtime_rpc_1.RpcError(`missing status`, goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.INTERNAL]);
if (maybeStatus.code !== 'OK')
throw new runtime_rpc_1.RpcError(maybeStatus.detail, maybeStatus.code, maybeTrailer);
responseStream.notifyComplete();
defStatus.resolve(maybeStatus);
defTrailer.resolve(maybeTrailer || {});
})
.catch(reason => {
let error;
if (reason instanceof runtime_rpc_1.RpcError)
error = reason;
else if (reason instanceof Error && reason.name === 'AbortError')
// aborted
error = new runtime_rpc_1.RpcError(reason.message, goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.CANCELLED]);
else
// RpcErrors are thrown by us, everything else is an internal error
error = new runtime_rpc_1.RpcError(reason instanceof Error ? reason.message : "" + reason, goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.INTERNAL]);
error.methodName = method.name;
error.serviceName = method.service.typeName;
defHeader.rejectPending(error);
responseStream.notifyError(error);
defStatus.rejectPending(error);
defTrailer.rejectPending(error);
});
return new runtime_rpc_1.ServerStreamingCall(method, (_e = opt.meta) !== null && _e !== void 0 ? _e : {}, input, defHeader.promise, responseStream, defStatus.promise, defTrailer.promise);
}
unary(method, input, options) {
var _a, _b, _c, _d, _e;
let opt = options, format = (_a = opt.format) !== null && _a !== void 0 ? _a : 'text', fetch = (_b = opt.fetch) !== null && _b !== void 0 ? _b : globalThis.fetch, fetchInit = (_c = opt.fetchInit) !== null && _c !== void 0 ? _c : {}, url = this.makeUrl(method, opt), inputBytes = method.I.toBinary(input, opt.binaryOptions), defHeader = new runtime_rpc_1.Deferred(), maybeMessage, defMessage = new runtime_rpc_1.Deferred(), maybeStatus, defStatus = new runtime_rpc_1.Deferred(), maybeTrailer, defTrailer = new runtime_rpc_1.Deferred();
fetch(url, Object.assign(Object.assign({}, fetchInit), { method: 'POST', headers: grpc_web_format_1.createGrpcWebRequestHeader(new globalThis.Headers(), format, opt.timeout, opt.meta), body: grpc_web_format_1.createGrpcWebRequestBody(inputBytes, format), signal: (_d = options.abort) !== null && _d !== void 0 ? _d : null // node-fetch@3.0.0-beta.9 rejects `undefined`
}))
.then(fetchResponse => {
let [code, detail, meta] = grpc_web_format_1.readGrpcWebResponseHeader(fetchResponse);
defHeader.resolve(meta);
if (code != null && code !== goog_grpc_status_code_1.GrpcStatusCode.OK)
throw new runtime_rpc_1.RpcError(detail !== null && detail !== void 0 ? detail : goog_grpc_status_code_1.GrpcStatusCode[code], goog_grpc_status_code_1.GrpcStatusCode[code], meta);
if (code != null)
maybeStatus = {
code: goog_grpc_status_code_1.GrpcStatusCode[code],
detail: detail !== null && detail !== void 0 ? detail : goog_grpc_status_code_1.GrpcStatusCode[code]
};
return fetchResponse;
})
.then(fetchResponse => {
if (!fetchResponse.body)
throw new runtime_rpc_1.RpcError('missing response body', goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.INTERNAL]);
return grpc_web_format_1.readGrpcWebResponseBody(fetchResponse.body, fetchResponse.headers.get('content-type'), (type, data) => {
switch (type) {
case grpc_web_format_1.GrpcWebFrame.DATA:
if (maybeMessage)
throw new runtime_rpc_1.RpcError(`unary call received 2nd message`, goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.DATA_LOSS]);
maybeMessage = method.O.fromBinary(data, opt.binaryOptions);
break;
case grpc_web_format_1.GrpcWebFrame.TRAILER:
let code, detail;
[code, detail, maybeTrailer] = grpc_web_format_1.readGrpcWebResponseTrailer(data);
maybeStatus = {
code: goog_grpc_status_code_1.GrpcStatusCode[code],
detail: detail !== null && detail !== void 0 ? detail : goog_grpc_status_code_1.GrpcStatusCode[code]
};
break;
}
});
})
.then(() => {
if (!maybeTrailer && maybeMessage)
throw new runtime_rpc_1.RpcError(`missing trailers`, goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.DATA_LOSS]);
// istanbul ignore if - this should be impossible and only here to satisfy TypeScript
if (!maybeStatus)
throw new runtime_rpc_1.RpcError(`missing status`, goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.INTERNAL]);
if (!maybeMessage && maybeStatus.code === 'OK')
throw new runtime_rpc_1.RpcError('expected error status', goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.DATA_LOSS]);
if (!maybeMessage)
throw new runtime_rpc_1.RpcError(maybeStatus.detail, maybeStatus.code, maybeTrailer);
defMessage.resolve(maybeMessage);
if (maybeStatus.code !== 'OK')
throw new runtime_rpc_1.RpcError(maybeStatus.detail, maybeStatus.code, maybeTrailer);
defStatus.resolve(maybeStatus);
defTrailer.resolve(maybeTrailer || {});
})
.catch(reason => {
let error;
if (reason instanceof runtime_rpc_1.RpcError)
error = reason;
else if (reason instanceof Error && reason.name === 'AbortError')
// aborted
error = new runtime_rpc_1.RpcError(reason.message, goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.CANCELLED]);
else
// RpcErrors are thrown by us, everything else is an internal error
error = new runtime_rpc_1.RpcError(reason instanceof Error ? reason.message : "" + reason, goog_grpc_status_code_1.GrpcStatusCode[goog_grpc_status_code_1.GrpcStatusCode.INTERNAL]);
error.methodName = method.name;
error.serviceName = method.service.typeName;
defHeader.rejectPending(error);
defMessage.rejectPending(error);
defStatus.rejectPending(error);
defTrailer.rejectPending(error);
});
return new runtime_rpc_1.UnaryCall(method, (_e = opt.meta) !== null && _e !== void 0 ? _e : {}, input, defHeader.promise, defMessage.promise, defStatus.promise, defTrailer.promise);
}
}
exports.GrpcWebFetchTransport = GrpcWebFetchTransport;
;