nice-grpc-web
Version:
A Browser gRPC library that is nice to you
101 lines • 3.95 kB
JavaScript
;
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