@bufbuild/connect-web
Advanced tools
Comparing version 0.0.2-alpha.2 to 0.0.2-alpha.3
@@ -24,2 +24,3 @@ "use strict"; | ||
Object.setPrototypeOf(this, new.target.prototype); | ||
this.rawMessage = message; | ||
this.code = code; | ||
@@ -26,0 +27,0 @@ this.details = details ?? []; |
@@ -106,3 +106,4 @@ "use strict"; | ||
handler.onHeader?.(response.headers); | ||
const err = extractDetailsError(response.headers) ?? | ||
const err = extractContentTypeError(response.headers) ?? | ||
extractDetailsError(response.headers) ?? | ||
extractHeadersError(response.headers) ?? | ||
@@ -156,4 +157,13 @@ extractHttpStatusError(response); | ||
const header = new Headers({ | ||
// We provide the most explicit description for our content type. | ||
// Note that we do not support the grpc-web-text format. | ||
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2 | ||
"Content-Type": "application/grpc-web+proto", | ||
// Some servers may rely on the request header `X-Grpc-Web` to identify | ||
// gRPC-web requests. For example the proxy by improbable: | ||
// https://github.com/improbable-eng/grpc-web/blob/53aaf4cdc0fede7103c1b06f0cfc560c003a5c41/go/grpcweb/wrapper.go#L231 | ||
"X-Grpc-Web": "1", | ||
// Note that we do not comply with recommended structure for the | ||
// user-agent string. | ||
// https://github.com/grpc/grpc/blob/c462bb8d485fc1434ecfae438823ca8d14cf3154/doc/PROTOCOL-HTTP2.md#user-agents | ||
"X-User-Agent": "@bufbuild/connect-web", | ||
@@ -163,3 +173,3 @@ }); | ||
if (callOptions.timeout !== undefined) { | ||
header.set("grpc-timeout", `${callOptions.timeout}m`); | ||
header.set("Grpc-Timeout", `${callOptions.timeout}m`); | ||
} | ||
@@ -180,2 +190,17 @@ return header; | ||
} | ||
function extractContentTypeError(header) { | ||
const type = header.get("Content-Type"); | ||
switch (type?.toLowerCase()) { | ||
case "application/grpc-web": | ||
case "application/grpc-web+proto": | ||
return undefined; | ||
case "application/grpc-web-text": | ||
case "application/grpc-web-text+proto": | ||
return new connect_error_js_1.ConnectError("grpc-web-text is not supported", status_code_js_1.StatusCode.Internal); | ||
case undefined: | ||
case null: | ||
default: | ||
return new connect_error_js_1.ConnectError(`unexpected content type: ${String(type)}`, status_code_js_1.StatusCode.Internal); | ||
} | ||
} | ||
function extractHttpStatusError(response) { | ||
@@ -186,3 +211,3 @@ const code = (0, status_code_js_1.codeFromHttpStatus)(response.status); | ||
} | ||
return new connect_error_js_1.ConnectError((0, http_headers_js_1.percentDecodeHeader)(response.headers.get("grpc-message") ?? ""), code); | ||
return new connect_error_js_1.ConnectError(decodeURIComponent(response.headers.get("grpc-message") ?? ""), code); | ||
} | ||
@@ -202,3 +227,3 @@ function extractHeadersError(header) { | ||
} | ||
return new connect_error_js_1.ConnectError((0, http_headers_js_1.percentDecodeHeader)(header.get("grpc-message") ?? ""), code); | ||
return new connect_error_js_1.ConnectError(decodeURIComponent(header.get("grpc-message") ?? ""), code); | ||
} | ||
@@ -211,3 +236,3 @@ function extractDetailsError(header) { | ||
try { | ||
const status = (0, http_headers_js_1.parseBinaryHeader)(grpcStatusDetailsBin, status_pb_js_1.Status); | ||
const status = (0, http_headers_js_1.decodeBinaryHeader)(grpcStatusDetailsBin, status_pb_js_1.Status); | ||
// Prefer the protobuf-encoded data to the headers. | ||
@@ -214,0 +239,0 @@ if (status.code === status_code_js_1.StatusCode.Ok) { |
@@ -16,15 +16,36 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.parseBinaryHeader = exports.percentDecodeHeader = void 0; | ||
const base64_js_1 = require("./base64.js"); | ||
function percentDecodeHeader(value) { | ||
return decodeURIComponent(value); | ||
exports.decodeBinaryHeader = exports.encodeBinaryHeader = void 0; | ||
const protobuf_1 = require("@bufbuild/protobuf"); | ||
const connect_error_1 = require("./connect-error"); | ||
const status_code_1 = require("./status-code"); | ||
/** | ||
* Encode a single binary header value according to the gRPC | ||
* specification. | ||
* | ||
* Binary headers names end with `-bin`, and can contain arbitrary | ||
* base64-encoded binary data. | ||
*/ | ||
function encodeBinaryHeader(value) { | ||
let bytes; | ||
if (value instanceof protobuf_1.Message) { | ||
bytes = value.toBinary(); | ||
} | ||
else { | ||
bytes = value instanceof Uint8Array ? value : new Uint8Array(value); | ||
} | ||
return protobuf_1.protoBase64.enc(bytes); | ||
} | ||
exports.percentDecodeHeader = percentDecodeHeader; | ||
function parseBinaryHeader(value, type, options) { | ||
const bytes = (0, base64_js_1.base64decode)(value); | ||
if (type) { | ||
return type.fromBinary(bytes, options); | ||
exports.encodeBinaryHeader = encodeBinaryHeader; | ||
function decodeBinaryHeader(value, type, options) { | ||
try { | ||
const bytes = protobuf_1.protoBase64.dec(value); | ||
if (type) { | ||
return type.fromBinary(bytes, options); | ||
} | ||
return bytes; | ||
} | ||
return bytes; | ||
catch (e) { | ||
throw new connect_error_1.ConnectError(e instanceof Error ? e.message : String(e), status_code_1.StatusCode.DataLoss); | ||
} | ||
} | ||
exports.parseBinaryHeader = parseBinaryHeader; | ||
exports.decodeBinaryHeader = decodeBinaryHeader; |
@@ -16,3 +16,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.percentDecodeHeader = exports.parseBinaryHeader = exports.createConnectTransport = exports.StatusCode = exports.ConnectError = exports.createClientTransportCalls = exports.chainClientInterceptors = exports.makePromiseClient = exports.makeCallbackClient = void 0; | ||
exports.encodeBinaryHeader = exports.decodeBinaryHeader = exports.createConnectTransport = exports.StatusCode = exports.ConnectError = exports.createClientTransportCalls = exports.chainClientInterceptors = exports.makePromiseClient = exports.makeCallbackClient = void 0; | ||
var callback_client_js_1 = require("./callback-client.js"); | ||
@@ -33,3 +33,3 @@ Object.defineProperty(exports, "makeCallbackClient", { enumerable: true, get: function () { return callback_client_js_1.makeCallbackClient; } }); | ||
var http_headers_js_1 = require("./http-headers.js"); | ||
Object.defineProperty(exports, "parseBinaryHeader", { enumerable: true, get: function () { return http_headers_js_1.parseBinaryHeader; } }); | ||
Object.defineProperty(exports, "percentDecodeHeader", { enumerable: true, get: function () { return http_headers_js_1.percentDecodeHeader; } }); | ||
Object.defineProperty(exports, "decodeBinaryHeader", { enumerable: true, get: function () { return http_headers_js_1.decodeBinaryHeader; } }); | ||
Object.defineProperty(exports, "encodeBinaryHeader", { enumerable: true, get: function () { return http_headers_js_1.encodeBinaryHeader; } }); |
@@ -105,3 +105,3 @@ "use strict"; | ||
case 400: | ||
return StatusCode.InvalidArgument; | ||
return StatusCode.Internal; | ||
case 401: | ||
@@ -112,5 +112,5 @@ return StatusCode.Unauthenticated; | ||
case 404: | ||
return StatusCode.NotFound; | ||
return StatusCode.Unimplemented; | ||
case 429: | ||
return StatusCode.ResourceExhausted; | ||
return StatusCode.Unavailable; | ||
case 502: | ||
@@ -117,0 +117,0 @@ return StatusCode.Unavailable; |
@@ -21,2 +21,3 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
Object.setPrototypeOf(this, new.target.prototype); | ||
this.rawMessage = message; | ||
this.code = code; | ||
@@ -23,0 +24,0 @@ this.details = details ?? []; |
@@ -16,3 +16,3 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
import { codeFromHttpStatus, StatusCode } from "./status-code.js"; | ||
import { parseBinaryHeader, percentDecodeHeader } from "./http-headers.js"; | ||
import { decodeBinaryHeader } from "./http-headers.js"; | ||
import { Status } from "./grpc/status/v1/status_pb.js"; | ||
@@ -103,3 +103,4 @@ import { chainClientInterceptors, } from "./client-interceptor.js"; | ||
handler.onHeader?.(response.headers); | ||
const err = extractDetailsError(response.headers) ?? | ||
const err = extractContentTypeError(response.headers) ?? | ||
extractDetailsError(response.headers) ?? | ||
extractHeadersError(response.headers) ?? | ||
@@ -153,4 +154,13 @@ extractHttpStatusError(response); | ||
const header = new Headers({ | ||
// We provide the most explicit description for our content type. | ||
// Note that we do not support the grpc-web-text format. | ||
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2 | ||
"Content-Type": "application/grpc-web+proto", | ||
// Some servers may rely on the request header `X-Grpc-Web` to identify | ||
// gRPC-web requests. For example the proxy by improbable: | ||
// https://github.com/improbable-eng/grpc-web/blob/53aaf4cdc0fede7103c1b06f0cfc560c003a5c41/go/grpcweb/wrapper.go#L231 | ||
"X-Grpc-Web": "1", | ||
// Note that we do not comply with recommended structure for the | ||
// user-agent string. | ||
// https://github.com/grpc/grpc/blob/c462bb8d485fc1434ecfae438823ca8d14cf3154/doc/PROTOCOL-HTTP2.md#user-agents | ||
"X-User-Agent": "@bufbuild/connect-web", | ||
@@ -160,3 +170,3 @@ }); | ||
if (callOptions.timeout !== undefined) { | ||
header.set("grpc-timeout", `${callOptions.timeout}m`); | ||
header.set("Grpc-Timeout", `${callOptions.timeout}m`); | ||
} | ||
@@ -177,2 +187,17 @@ return header; | ||
} | ||
function extractContentTypeError(header) { | ||
const type = header.get("Content-Type"); | ||
switch (type?.toLowerCase()) { | ||
case "application/grpc-web": | ||
case "application/grpc-web+proto": | ||
return undefined; | ||
case "application/grpc-web-text": | ||
case "application/grpc-web-text+proto": | ||
return new ConnectError("grpc-web-text is not supported", StatusCode.Internal); | ||
case undefined: | ||
case null: | ||
default: | ||
return new ConnectError(`unexpected content type: ${String(type)}`, StatusCode.Internal); | ||
} | ||
} | ||
function extractHttpStatusError(response) { | ||
@@ -183,3 +208,3 @@ const code = codeFromHttpStatus(response.status); | ||
} | ||
return new ConnectError(percentDecodeHeader(response.headers.get("grpc-message") ?? ""), code); | ||
return new ConnectError(decodeURIComponent(response.headers.get("grpc-message") ?? ""), code); | ||
} | ||
@@ -199,3 +224,3 @@ function extractHeadersError(header) { | ||
} | ||
return new ConnectError(percentDecodeHeader(header.get("grpc-message") ?? ""), code); | ||
return new ConnectError(decodeURIComponent(header.get("grpc-message") ?? ""), code); | ||
} | ||
@@ -208,3 +233,3 @@ function extractDetailsError(header) { | ||
try { | ||
const status = parseBinaryHeader(grpcStatusDetailsBin, Status); | ||
const status = decodeBinaryHeader(grpcStatusDetailsBin, Status); | ||
// Prefer the protobuf-encoded data to the headers. | ||
@@ -211,0 +236,0 @@ if (status.code === StatusCode.Ok) { |
@@ -14,12 +14,33 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
// limitations under the License. | ||
import { base64decode } from "./base64.js"; | ||
export function percentDecodeHeader(value) { | ||
return decodeURIComponent(value); | ||
import { Message, protoBase64 } from "@bufbuild/protobuf"; | ||
import { ConnectError } from "./connect-error"; | ||
import { StatusCode } from "./status-code"; | ||
/** | ||
* Encode a single binary header value according to the gRPC | ||
* specification. | ||
* | ||
* Binary headers names end with `-bin`, and can contain arbitrary | ||
* base64-encoded binary data. | ||
*/ | ||
export function encodeBinaryHeader(value) { | ||
let bytes; | ||
if (value instanceof Message) { | ||
bytes = value.toBinary(); | ||
} | ||
else { | ||
bytes = value instanceof Uint8Array ? value : new Uint8Array(value); | ||
} | ||
return protoBase64.enc(bytes); | ||
} | ||
export function parseBinaryHeader(value, type, options) { | ||
const bytes = base64decode(value); | ||
if (type) { | ||
return type.fromBinary(bytes, options); | ||
export function decodeBinaryHeader(value, type, options) { | ||
try { | ||
const bytes = protoBase64.dec(value); | ||
if (type) { | ||
return type.fromBinary(bytes, options); | ||
} | ||
return bytes; | ||
} | ||
return bytes; | ||
catch (e) { | ||
throw new ConnectError(e instanceof Error ? e.message : String(e), StatusCode.DataLoss); | ||
} | ||
} |
@@ -21,2 +21,2 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
export { createConnectTransport } from "./connect-transport.js"; | ||
export { parseBinaryHeader, percentDecodeHeader } from "./http-headers.js"; | ||
export { decodeBinaryHeader, encodeBinaryHeader } from "./http-headers.js"; |
@@ -102,3 +102,3 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
case 400: | ||
return StatusCode.InvalidArgument; | ||
return StatusCode.Internal; | ||
case 401: | ||
@@ -109,5 +109,5 @@ return StatusCode.Unauthenticated; | ||
case 404: | ||
return StatusCode.NotFound; | ||
return StatusCode.Unimplemented; | ||
case 429: | ||
return StatusCode.ResourceExhausted; | ||
return StatusCode.Unavailable; | ||
case 502: | ||
@@ -114,0 +114,0 @@ return StatusCode.Unavailable; |
@@ -22,6 +22,6 @@ import type { MethodInfoServerStreaming, MethodInfoUnary, PartialMessage, ServiceType } from "@bufbuild/protobuf"; | ||
export declare type CallbackClient<T extends ServiceType> = { | ||
[P in keyof T["methods"]]: T["methods"][P] extends MethodInfoUnary<infer I, infer O> ? (request: PartialMessage<I>, callback: (error: ConnectError | undefined, response: O) => void, options?: ClientCallOptions) => CancelFn : T["methods"][P] extends MethodInfoServerStreaming<infer I, infer O> ? (request: PartialMessage<I>, responseCallback: (response: O) => void, messageCallback: (error: ConnectError | undefined) => void, options?: ClientCallOptions) => CancelFn : never; | ||
[P in keyof T["methods"]]: T["methods"][P] extends MethodInfoUnary<infer I, infer O> ? (request: PartialMessage<I>, callback: (error: ConnectError | undefined, response: O) => void, options?: ClientCallOptions) => CancelFn : T["methods"][P] extends MethodInfoServerStreaming<infer I, infer O> ? (request: PartialMessage<I>, messageCallback: (response: O) => void, closeCallback: (error: ConnectError | undefined) => void, options?: ClientCallOptions) => CancelFn : never; | ||
}; | ||
export declare type CallbackClientWithExactRequest<T extends ServiceType> = { | ||
[P in keyof T["methods"]]: T["methods"][P] extends MethodInfoUnary<infer I, infer O> ? (request: I, callback: (error: ConnectError | undefined, response: O) => void, options?: ClientCallOptions) => CancelFn : T["methods"][P] extends MethodInfoServerStreaming<infer I, infer O> ? (request: I, responseCallback: (response: O) => void, messageCallback: (error: ConnectError | undefined) => void, options?: ClientCallOptions) => CancelFn : never; | ||
[P in keyof T["methods"]]: T["methods"][P] extends MethodInfoUnary<infer I, infer O> ? (request: I, callback: (error: ConnectError | undefined, response: O) => void, options?: ClientCallOptions) => CancelFn : T["methods"][P] extends MethodInfoServerStreaming<infer I, infer O> ? (request: I, messageCallback: (response: O) => void, closeCallback: (error: ConnectError | undefined) => void, options?: ClientCallOptions) => CancelFn : never; | ||
}; | ||
@@ -28,0 +28,0 @@ declare type CancelFn = () => void; |
@@ -7,2 +7,3 @@ import { StatusCode } from "./status-code.js"; | ||
readonly details: Any[]; | ||
readonly rawMessage: string; | ||
name: string; | ||
@@ -9,0 +10,0 @@ constructor(message: string, code?: ErrorCode, details?: Any[]); |
@@ -1,9 +0,27 @@ | ||
import type { Message } from "@bufbuild/protobuf"; | ||
import type { MessageType } from "@bufbuild/protobuf"; | ||
import type { BinaryReadOptions } from "@bufbuild/protobuf"; | ||
export declare function percentDecodeHeader(value: string): string; | ||
import type { BinaryReadOptions, MessageType } from "@bufbuild/protobuf"; | ||
import { Message } from "@bufbuild/protobuf"; | ||
/** | ||
* Throws on invalid base-64 data, or on message parsing. | ||
* Encode a single binary header value according to the gRPC | ||
* specification. | ||
* | ||
* Binary headers names end with `-bin`, and can contain arbitrary | ||
* base64-encoded binary data. | ||
*/ | ||
export declare function parseBinaryHeader(value: string): Uint8Array; | ||
export declare function parseBinaryHeader<T extends Message<T>>(value: string, type: MessageType<T>, options?: Partial<BinaryReadOptions>): T; | ||
export declare function encodeBinaryHeader(value: Uint8Array | ArrayBufferLike | Message): string; | ||
/** | ||
* Decode a single binary header value according to the gRPC | ||
* specification. | ||
* | ||
* Binary headers names end with `-bin`, and can contain arbitrary | ||
* base64-encoded binary data. | ||
* | ||
* Note that duplicate header names may have their values joined | ||
* with a `,` as the delimiter, so you most likely will want to | ||
* split by `,` first. | ||
* | ||
* If this function detects invalid base-64 encoding, or invalid | ||
* binary message data, it throws a ConnectError with status | ||
* DataLoss. | ||
*/ | ||
export declare function decodeBinaryHeader(value: string): Uint8Array; | ||
export declare function decodeBinaryHeader<T extends Message<T>>(value: string, type: MessageType<T>, options?: Partial<BinaryReadOptions>): T; |
@@ -8,2 +8,2 @@ export { makeCallbackClient, CallbackClient, CallbackClientWithExactRequest, } from "./callback-client.js"; | ||
export { createConnectTransport } from "./connect-transport.js"; | ||
export { parseBinaryHeader, percentDecodeHeader } from "./http-headers.js"; | ||
export { decodeBinaryHeader, encodeBinaryHeader } from "./http-headers.js"; |
{ | ||
"name": "@bufbuild/connect-web", | ||
"version": "0.0.2-alpha.2", | ||
"version": "0.0.2-alpha.3", | ||
"license": "Apache-2.0", | ||
@@ -24,3 +24,3 @@ "repository": { | ||
"dependencies": { | ||
"@bufbuild/protobuf": "^0.0.2-alpha.2" | ||
"@bufbuild/protobuf": "^0.0.2-alpha.3" | ||
}, | ||
@@ -27,0 +27,0 @@ "devDependencies": { |
85438
31
2169