UNPKG

@cerbos/http

Version:

Client library for interacting with the Cerbos policy decision point service over HTTP from browser-based applications

115 lines 3.9 kB
import { fromJson as bufFromJson } from "@bufbuild/protobuf"; import { NotOK, Status } from "@cerbos/core"; import { AbstractErrorResponse, cancelBody, isObject, methodName, } from "@cerbos/core/~internal"; import { endpoints } from "./endpoints.js"; export class Transport { baseUrl; userAgent; constructor(baseUrl, userAgent) { this.baseUrl = baseUrl; this.userAgent = userAgent; } async unary(method, request, headers, abortHandler) { const response = await this.fetch(method, request, headers, abortHandler); const json = (await response.json()); if (!response.ok) { throw new ErrorResponse(json); } return fromJson(method.output, json); } async *serverStream(method, request, headers, abortHandler) { const response = await this.fetch(method, request, headers, abortHandler); if (!response.body) { throw new Error("Missing response body"); } try { for await (const line of eachLine(response.body)) { const message = JSON.parse(line); if (!isObject(message)) { throw new Error(`Unexpected message: wanted object, got ${line}`); } const { result, error } = message; if (error) { throw new ErrorResponse(error); } if (!result) { throw new Error(`Missing result in ${line}`); } yield fromJson(method.output, result); } } finally { cancelBody(response); } } async fetch(method, request, headers, abortHandler) { const endpoint = endpoints.get(methodName(method)); if (!endpoint) { throw new NotOK(Status.UNIMPLEMENTED, "Unimplemented"); } headers.set("User-Agent", this.userAgent); const init = { url: this.baseUrl + endpoint.path, method: endpoint.method, headers, }; if (abortHandler.signal) { init.signal = abortHandler.signal; } const { url, ...rest } = endpoint.serialize(request, init); return await fetch(url, rest); } } export async function* eachLine(stream) { const utf8Decoder = new TextDecoder("utf-8", { fatal: true }); let buffer = ""; let start = 0; for await (const chunk of stream) { buffer += utf8Decoder.decode(chunk, { stream: true }); let end; while ((end = buffer.indexOf("\n", start)) >= 0) { yield buffer.slice(start, end); start = end + 1; } buffer = buffer.slice(start); start = 0; } if (buffer.length > 0) { yield buffer; } } class ErrorResponse extends AbstractErrorResponse { jsonDetails; constructor(json) { if (!isStatus(json)) { throw new Error(`Invalid error response: ${JSON.stringify(json)}`); } super(json.code, json.message); this.jsonDetails = json.details; } *details() { if (!this.jsonDetails) { return; } for (const details of this.jsonDetails) { if (isAny(details)) { const { "@type": typeUrl, ...value } = details; yield { typeUrl, value }; } } } parseDetails = fromJson; } function isStatus(json) { return (isObject(json) && typeof json["code"] === "number" && typeof json["message"] === "string" && (json["details"] === undefined || Array.isArray(json["details"]))); } function isAny(json) { return isObject(json) && typeof json["@type"] === "string"; } function fromJson(schema, json) { return bufFromJson(schema, json, { ignoreUnknownFields: true }); } //# sourceMappingURL=transport.js.map