UNPKG

nice-grpc-web

Version:

A Browser gRPC library that is nice to you

101 lines 3.95 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebsocketTransport = WebsocketTransport; const abort_controller_x_1 = require("abort-controller-x"); const isomorphic_ws_1 = __importDefault(require("isomorphic-ws")); const js_base64_1 = require("js-base64"); const AsyncSink_1 = require("../../utils/AsyncSink"); /** * Transport based on WebSockets. Works only with `grpcwebproxy`. */ function WebsocketTransport() { return async function* ({ url, body, metadata, signal }) { if (signal.aborted) { throw new abort_controller_x_1.AbortError(); } const frames = new AsyncSink_1.AsyncSink(); signal.addEventListener('abort', () => { frames.error(new abort_controller_x_1.AbortError()); }); const websocketUrl = new URL(url); websocketUrl.protocol = websocketUrl.protocol.replace('http', 'ws'); const webSocket = new isomorphic_ws_1.default(websocketUrl, ['grpc-websockets']); webSocket.binaryType = 'arraybuffer'; webSocket.addEventListener('message', event => { if (event.data instanceof ArrayBuffer) { frames.write({ type: 'data', data: new Uint8Array(event.data), }); } else { frames.error(new Error(`Unexpected message type: ${typeof event.data}`)); } }); webSocket.addEventListener('close', event => { if (event.wasClean) { frames.end(); } else { frames.error(new Error(`WebSocket closed with code ${event.code}` + (event.reason && `: ${event.reason}`))); } }); const pipeAbortController = new AbortController(); pipeBody(pipeAbortController.signal, metadata, body, webSocket).catch(err => { if (!(0, abort_controller_x_1.isAbortError)(err)) { frames.error(err); } }); try { return yield* frames; } finally { pipeAbortController.abort(); webSocket.close(); } }; } async function pipeBody(signal, metadata, body, webSocket) { if (webSocket.readyState == isomorphic_ws_1.default.CONNECTING) { await (0, abort_controller_x_1.waitForEvent)(signal, webSocket, 'open'); } webSocket.send(encodeMetadata(metadata)); for await (const chunk of body) { (0, abort_controller_x_1.throwIfAborted)(signal); const data = new Uint8Array(chunk.length + 1); data.set([0], 0); data.set(chunk, 1); webSocket.send(data); } webSocket.send(new Uint8Array([1])); } function encodeMetadata(metadata) { let result = ''; for (const [key, values] of metadata) { for (const value of values) { const valueString = typeof value === 'string' ? value : js_base64_1.Base64.fromUint8Array(value); const pairString = `${key}: ${valueString}\r\n`; for (let i = 0; i < pairString.length; i++) { const charCode = pairString.charCodeAt(i); if (!isValidCharCode(charCode)) { throw new Error(`Metadata contains invalid characters: '${pairString}'`); } } result += pairString; } } return new TextEncoder().encode(result); } /** * Checks whether the given number represents a valid character code. * It returns true if the number is 0x9 (tab), 0xa (line feed), 0xd (carriage * return), or any printable character. */ function isValidCharCode(val) { return (val === 0x9 || val === 0xa || val === 0xd || (val >= 0x20 && val <= 0x7e)); } //# sourceMappingURL=websocket.js.map