@protobuf-ts/twirp-transport
Version:
Twirp transport for clients generated by the protoc plugin "protobuf-ts".
131 lines (130 loc) • 7.32 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TwirpFetchTransport = void 0;
const twirp_format_1 = require("./twirp-format");
const runtime_rpc_1 = require("@protobuf-ts/runtime-rpc");
const runtime_1 = require("@protobuf-ts/runtime");
const twitch_twirp_error_code_1 = require("./twitch-twirp-error-code");
/**
* Implements the Twirp protocol, supporting JSON or binary format on
* the wire. Uses the fetch API to do the HTTP requests.
*
* See https://twitchtv.github.io/twirp/docs/spec_v5.html
*/
class TwirpFetchTransport {
constructor(options) {
this.defaultOptions = options;
}
mergeOptions(options) {
return runtime_rpc_1.mergeRpcOptions(this.defaultOptions, options);
}
unary(method, input, options) {
var _a, _b, _c;
let opt = options, url = this.makeUrl(method, opt), fetchInit = (_a = opt.fetchInit) !== null && _a !== void 0 ? _a : {}, requestBody = opt.sendJson ? method.I.toJsonString(input, opt.jsonOptions) : method.I.toBinary(input, opt.binaryOptions), defHeader = new runtime_rpc_1.Deferred(), defMessage = new runtime_rpc_1.Deferred(), defStatus = new runtime_rpc_1.Deferred(), defTrailer = new runtime_rpc_1.Deferred();
globalThis.fetch(url, Object.assign(Object.assign({}, fetchInit), { method: 'POST', headers: twirp_format_1.createTwirpRequestHeader(new globalThis.Headers(), !!opt.sendJson, opt.meta), body: requestBody, signal: (_b = options.abort) !== null && _b !== void 0 ? _b : null // node-fetch@3.0.0-beta.9 rejects `undefined`
}))
.then(fetchResponse => {
defHeader.resolve(twirp_format_1.parseMetadataFromResponseHeaders(fetchResponse.headers));
// Cloudflare Workers throw when the type property of a fetch response
// is accessed, so wrap access with try/catch. See:
// * https://developers.cloudflare.com/workers/runtime-apis/response/#properties
// * https://github.com/cloudflare/miniflare/blob/72f046e/packages/core/src/standards/http.ts#L646
let responseType;
try {
responseType = fetchResponse.type;
}
catch (_a) { }
switch (responseType) {
case "error":
case "opaque":
case "opaqueredirect":
// see https://developer.mozilla.org/en-US/docs/Web/API/Response/type
throw new runtime_rpc_1.RpcError(`fetch response type ${fetchResponse.type}`, twitch_twirp_error_code_1.TwirpErrorCode[twitch_twirp_error_code_1.TwirpErrorCode.unknown]);
}
if (!fetchResponse.ok) {
return fetchResponse.json().then(value => {
throw twirp_format_1.parseTwirpErrorResponse(value);
}, () => {
throw new runtime_rpc_1.RpcError('received HTTP ' + fetchResponse.status + ', unable to read response body as json', twitch_twirp_error_code_1.TwirpErrorCode[twitch_twirp_error_code_1.TwirpErrorCode.internal]);
});
}
if (opt.sendJson) {
return fetchResponse.json().then(value => method.O.fromJson(value, opt.jsonOptions), () => {
throw new runtime_rpc_1.RpcError('unable to read response body as json', twitch_twirp_error_code_1.TwirpErrorCode[twitch_twirp_error_code_1.TwirpErrorCode.dataloss]);
});
}
return fetchResponse.arrayBuffer().then(value => method.O.fromBinary(new Uint8Array(value), opt.binaryOptions), () => {
throw new runtime_rpc_1.RpcError('unable to read response body', twitch_twirp_error_code_1.TwirpErrorCode[twitch_twirp_error_code_1.TwirpErrorCode.dataloss]);
});
}, (reason) => {
// failed to fetch, aborted, wrong url or network problem
if (reason instanceof Error && reason.name === 'AbortError')
throw new runtime_rpc_1.RpcError(reason.message, twitch_twirp_error_code_1.TwirpErrorCode[twitch_twirp_error_code_1.TwirpErrorCode.cancelled]);
throw new runtime_rpc_1.RpcError(reason instanceof Error ? reason.message : reason);
})
.then(message => {
defMessage.resolve(message);
defStatus.resolve({ code: 'OK', detail: '' });
defTrailer.resolve({});
})
.catch((reason) => {
// RpcErrors are thrown by us, everything else is an internal error
let error = reason instanceof runtime_rpc_1.RpcError ? reason
: new runtime_rpc_1.RpcError(reason instanceof Error ? reason.message : reason, twitch_twirp_error_code_1.TwirpErrorCode[twitch_twirp_error_code_1.TwirpErrorCode.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, (_c = opt.meta) !== null && _c !== void 0 ? _c : {}, input, defHeader.promise, defMessage.promise, defStatus.promise, defTrailer.promise);
}
/**
* Create an URI for a RPC 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.
*
* The method name is CamelCased just as it would be in Go, unless the
* option `useProtoMethodName` is `true`.
*/
makeUrl(method, options) {
let base = options.baseUrl;
if (base.endsWith('/'))
base = base.substring(0, base.length - 1);
let methodName = method.name;
if (options.useProtoMethodName !== true) {
methodName = runtime_1.lowerCamelCase(methodName);
methodName = methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
}
return `${base}/${method.service.typeName}/${methodName}`;
}
clientStreaming(method /*, options: RpcOptions*/) {
const e = new runtime_rpc_1.RpcError('Client streaming is not supported by Twirp', twitch_twirp_error_code_1.TwirpErrorCode[twitch_twirp_error_code_1.TwirpErrorCode.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 Twirp', twitch_twirp_error_code_1.TwirpErrorCode[twitch_twirp_error_code_1.TwirpErrorCode.unimplemented]);
e.methodName = method.name;
e.serviceName = method.service.typeName;
throw e;
}
serverStreaming(method /*, input: I, options?: RpcOptions*/) {
const e = new runtime_rpc_1.RpcError('Server streaming is not supported by Twirp', twitch_twirp_error_code_1.TwirpErrorCode[twitch_twirp_error_code_1.TwirpErrorCode.unimplemented]);
e.methodName = method.name;
e.serviceName = method.service.typeName;
throw e;
}
}
exports.TwirpFetchTransport = TwirpFetchTransport;