Migrate to ES6 classes

This commit is contained in:
Stanley Cheung 2020-06-17 10:58:32 -07:00 committed by Stanley Cheung
parent 8c96b8cee3
commit 670326124a
7 changed files with 1249 additions and 1263 deletions

View File

@ -7,53 +7,60 @@ goog.module.declareLegacyNamespace();
/** /**
* The collection of runtime options for a new RPC call. * The collection of runtime options for a new RPC call.
* @param {!Object<string, !Object>=} options * @unrestricted
* @constructor
*/ */
const CallOptions = function(options) { class CallOptions {
/**
* @param {!Object<string, !Object>=} options
*/
constructor(options) {
/** /**
* @const {!Object<string, !Object>} * @const {!Object<string, !Object>}
* @private * @private
*/ */
this.properties_ = options || {}; this.properties_ = options || {};
}; }
/** /**
* Add a new CallOption or override an existing one. * Add a new CallOption or override an existing one.
* *
* @param {string} name name of the CallOption that should be added/overridden. * @param {string} name name of the CallOption that should be
* added/overridden.
* @param {VALUE} value value of the CallOption * @param {VALUE} value value of the CallOption
* @template VALUE * @template VALUE
*/ */
CallOptions.prototype.setOption = function(name, value) { setOption(name, value) {
this.properties_[name] = value; this.properties_[name] = value;
}; }
/** /**
* Get the value of one CallOption. * Get the value of one CallOption.
* *
* @param {string} name name of the CallOption. * @param {string} name name of the CallOption.
* @return {!Object} value of the CallOption. If name doesn't exist, will return * @return {!Object} value of the CallOption. If name doesn't exist, will
* 'undefined'. * return 'undefined'.
*/ */
CallOptions.prototype.get = function(name) { get(name) {
return this.properties_[name]; return this.properties_[name];
}; }
/** /**
* Remove a CallOption. * Remove a CallOption.
* *
* @param {string} name name of the CallOption that shoud be removed. * @param {string} name name of the CallOption that shoud be removed.
*/ */
CallOptions.prototype.removeOption = function(name) { removeOption(name) {
delete this.properties_[name]; delete this.properties_[name];
}; }
/** /**
* @return {!Array<string>} * @return {!Array<string>}
*/ */
CallOptions.prototype.getKeys = function() { getKeys() {
return Object.keys(this.properties_); return Object.keys(this.properties_);
}; }
}
exports = CallOptions; exports = CallOptions;

View File

@ -47,17 +47,19 @@ const {StreamInterceptor, UnaryInterceptor} = goog.require('grpc.web.Interceptor
/** /**
* Base class for gRPC web client using the application/grpc-web wire format * Base class for gRPC web client using the application/grpc-web wire format
* @param {?Object=} opt_options
* @constructor
* @implements {AbstractClientBase} * @implements {AbstractClientBase}
* @unrestricted
*/ */
const GrpcWebClientBase = function(opt_options) { class GrpcWebClientBase {
/**
* @param {?Object=} opt_options
*/
constructor(opt_options) {
/** /**
* @const * @const
* @private {string} * @private {string}
*/ */
this.format_ = this.format_ = goog.getObjectByName('format', opt_options) || 'text';
goog.getObjectByName('format', opt_options) || "text";
/** /**
* @const * @const
@ -87,15 +89,13 @@ const GrpcWebClientBase = function(opt_options) {
*/ */
this.unaryInterceptors_ = this.unaryInterceptors_ =
goog.getObjectByName('unaryInterceptors', opt_options) || []; goog.getObjectByName('unaryInterceptors', opt_options) || [];
}; }
/** /**
* @override * @override
* @export * @export
*/ */
GrpcWebClientBase.prototype.rpcCall = function( rpcCall(method, requestMessage, metadata, methodDescriptor, callback) {
method, requestMessage, metadata, methodDescriptor, callback) {
methodDescriptor = AbstractClientBase.ensureMethodDescriptor( methodDescriptor = AbstractClientBase.ensureMethodDescriptor(
method, requestMessage, MethodType.UNARY, methodDescriptor); method, requestMessage, MethodType.UNARY, methodDescriptor);
var hostname = AbstractClientBase.getHostname(method, methodDescriptor); var hostname = AbstractClientBase.getHostname(method, methodDescriptor);
@ -106,15 +106,13 @@ GrpcWebClientBase.prototype.rpcCall = function(
this, methodDescriptor.createRequest(requestMessage, metadata))); this, methodDescriptor.createRequest(requestMessage, metadata)));
GrpcWebClientBase.setCallback_(stream, callback, false); GrpcWebClientBase.setCallback_(stream, callback, false);
return stream; return stream;
}; }
/** /**
* @override * @override
* @export * @export
*/ */
GrpcWebClientBase.prototype.thenableCall = function( thenableCall(method, requestMessage, metadata, methodDescriptor) {
method, requestMessage, metadata, methodDescriptor) {
methodDescriptor = AbstractClientBase.ensureMethodDescriptor( methodDescriptor = AbstractClientBase.ensureMethodDescriptor(
method, requestMessage, MethodType.UNARY, methodDescriptor); method, requestMessage, MethodType.UNARY, methodDescriptor);
var hostname = AbstractClientBase.getHostname(method, methodDescriptor); var hostname = AbstractClientBase.getHostname(method, methodDescriptor);
@ -143,7 +141,7 @@ GrpcWebClientBase.prototype.thenableCall = function(
var unaryResponse = /** @type {!Promise<?>} */ (invoker.call( var unaryResponse = /** @type {!Promise<?>} */ (invoker.call(
this, methodDescriptor.createRequest(requestMessage, metadata))); this, methodDescriptor.createRequest(requestMessage, metadata)));
return unaryResponse.then((response) => response.getResponseMessage()); return unaryResponse.then((response) => response.getResponseMessage());
}; }
/** /**
* @export * @export
@ -156,19 +154,16 @@ GrpcWebClientBase.prototype.thenableCall = function(
* @return {!Promise<RESPONSE>} * @return {!Promise<RESPONSE>}
* @template REQUEST, RESPONSE * @template REQUEST, RESPONSE
*/ */
GrpcWebClientBase.prototype.unaryCall = function( unaryCall(method, requestMessage, metadata, methodDescriptor) {
method, requestMessage, metadata, methodDescriptor) {
return /** @type {!Promise<RESPONSE>}*/ ( return /** @type {!Promise<RESPONSE>}*/ (
this.thenableCall(method, requestMessage, metadata, methodDescriptor)); this.thenableCall(method, requestMessage, metadata, methodDescriptor));
}; }
/** /**
* @override * @override
* @export * @export
*/ */
GrpcWebClientBase.prototype.serverStreaming = function( serverStreaming(method, requestMessage, metadata, methodDescriptor) {
method, requestMessage, metadata, methodDescriptor) {
methodDescriptor = AbstractClientBase.ensureMethodDescriptor( methodDescriptor = AbstractClientBase.ensureMethodDescriptor(
method, requestMessage, MethodType.SERVER_STREAMING, methodDescriptor); method, requestMessage, MethodType.SERVER_STREAMING, methodDescriptor);
var hostname = AbstractClientBase.getHostname(method, methodDescriptor); var hostname = AbstractClientBase.getHostname(method, methodDescriptor);
@ -177,8 +172,7 @@ GrpcWebClientBase.prototype.serverStreaming = function(
this.streamInterceptors_); this.streamInterceptors_);
return /** @type {!ClientReadableStream<?>} */ (invoker.call( return /** @type {!ClientReadableStream<?>} */ (invoker.call(
this, methodDescriptor.createRequest(requestMessage, metadata))); this, methodDescriptor.createRequest(requestMessage, metadata)));
}; }
/** /**
* @private * @private
@ -187,7 +181,7 @@ GrpcWebClientBase.prototype.serverStreaming = function(
* @param {string} hostname * @param {string} hostname
* @return {!ClientReadableStream<RESPONSE>} * @return {!ClientReadableStream<RESPONSE>}
*/ */
GrpcWebClientBase.prototype.startStream_ = function(request, hostname) { startStream_(request, hostname) {
var methodDescriptor = request.getMethodDescriptor(); var methodDescriptor = request.getMethodDescriptor();
var path = hostname + methodDescriptor.name; var path = hostname + methodDescriptor.name;
@ -218,8 +212,7 @@ GrpcWebClientBase.prototype.startStream_ = function(request, hostname) {
} }
xhr.send(path, 'POST', payload); xhr.send(path, 'POST', payload);
return stream; return stream;
}; }
/** /**
* @private * @private
@ -230,7 +223,7 @@ GrpcWebClientBase.prototype.startStream_ = function(request, hostname) {
* function(?Error,?RESPONSE)} callback * function(?Error,?RESPONSE)} callback
* @param {boolean} useUnaryResponse * @param {boolean} useUnaryResponse
*/ */
GrpcWebClientBase.setCallback_ = function(stream, callback, useUnaryResponse) { static setCallback_(stream, callback, useUnaryResponse) {
var responseReceived = null; var responseReceived = null;
var errorEmitted = false; var errorEmitted = false;
@ -274,7 +267,7 @@ GrpcWebClientBase.setCallback_ = function(stream, callback, useUnaryResponse) {
callback(null, null); // trigger unaryResponse callback(null, null); // trigger unaryResponse
} }
}); });
}; }
/** /**
* Create a new XhrIo object * Create a new XhrIo object
@ -282,9 +275,9 @@ GrpcWebClientBase.setCallback_ = function(stream, callback, useUnaryResponse) {
* @private * @private
* @return {!XhrIo} The created XhrIo object * @return {!XhrIo} The created XhrIo object
*/ */
GrpcWebClientBase.prototype.newXhr_ = function() { newXhr_() {
return new XhrIo(); return new XhrIo();
}; }
/** /**
* Encode the grpc-web request * Encode the grpc-web request
@ -293,7 +286,7 @@ GrpcWebClientBase.prototype.newXhr_ = function() {
* @param {!Uint8Array} serialized The serialized proto payload * @param {!Uint8Array} serialized The serialized proto payload
* @return {!Uint8Array} The application/grpc-web padded request * @return {!Uint8Array} The application/grpc-web padded request
*/ */
GrpcWebClientBase.prototype.encodeRequest_ = function(serialized) { encodeRequest_(serialized) {
var len = serialized.length; var len = serialized.length;
var bytesArray = [0, 0, 0, 0]; var bytesArray = [0, 0, 0, 0];
var payload = new Uint8Array(5 + len); var payload = new Uint8Array(5 + len);
@ -304,14 +297,14 @@ GrpcWebClientBase.prototype.encodeRequest_ = function(serialized) {
payload.set(new Uint8Array(bytesArray), 1); payload.set(new Uint8Array(bytesArray), 1);
payload.set(serialized, 5); payload.set(serialized, 5);
return payload; return payload;
}; }
/** /**
* @private * @private
* @param {!XhrIo} xhr The xhr object * @param {!XhrIo} xhr The xhr object
*/ */
GrpcWebClientBase.prototype.processHeaders_ = function(xhr) { processHeaders_(xhr) {
if (this.format_ == "text") { if (this.format_ == 'text') {
xhr.headers.set('Content-Type', 'application/grpc-web-text'); xhr.headers.set('Content-Type', 'application/grpc-web-text');
xhr.headers.set('Accept', 'application/grpc-web-text'); xhr.headers.set('Accept', 'application/grpc-web-text');
} else { } else {
@ -332,7 +325,7 @@ GrpcWebClientBase.prototype.processHeaders_ = function(xhr) {
xhr.headers.set('grpc-timeout', timeout + 'm'); xhr.headers.set('grpc-timeout', timeout + 'm');
} }
} }
}; }
/** /**
* @private * @private
@ -341,10 +334,10 @@ GrpcWebClientBase.prototype.processHeaders_ = function(xhr) {
* @param {!Object<string,string>} headerObject The xhr headers * @param {!Object<string,string>} headerObject The xhr headers
* @return {string} The URI object or a string path with headers * @return {string} The URI object or a string path with headers
*/ */
GrpcWebClientBase.setCorsOverride_ = function(method, headerObject) { static setCorsOverride_(method, headerObject) {
return /** @type {string} */ (HttpCors.setHttpHeadersWithOverwriteParam( return /** @type {string} */ (HttpCors.setHttpHeadersWithOverwriteParam(
method, HttpCors.HTTP_HEADERS_PARAM_NAME, headerObject)); method, HttpCors.HTTP_HEADERS_PARAM_NAME, headerObject));
}; }
/** /**
* @private * @private
@ -357,12 +350,16 @@ GrpcWebClientBase.setCorsOverride_ = function(method, headerObject) {
* @return {function(!Request<REQUEST,RESPONSE>): * @return {function(!Request<REQUEST,RESPONSE>):
* (!Promise<RESPONSE>|!ClientReadableStream<RESPONSE>)} * (!Promise<RESPONSE>|!ClientReadableStream<RESPONSE>)}
*/ */
GrpcWebClientBase.runInterceptors_ = function(invoker, interceptors) { static runInterceptors_(invoker, interceptors) {
let curInvoker = invoker; let curInvoker = invoker;
interceptors.forEach((interceptor) => { interceptors.forEach((interceptor) => {
const lastInvoker = curInvoker; const lastInvoker = curInvoker;
curInvoker = (request) => interceptor.intercept(request, lastInvoker); curInvoker = (request) => interceptor.intercept(request, lastInvoker);
}); });
return curInvoker; return curInvoker;
}; }
}
exports = GrpcWebClientBase; exports = GrpcWebClientBase;

View File

@ -29,16 +29,16 @@ const {StreamInterceptor} = goog.require('grpc.web.Interceptor');
goog.require('goog.testing.jsunit'); goog.require('goog.testing.jsunit');
var REQUEST_BYTES = [1, 2, 3]; var REQUEST_BYTES = [1, 2, 3];
var FAKE_METHOD = "fake-method"; var FAKE_METHOD = 'fake-method';
var PROTO_FIELD_VALUE = "meow"; var PROTO_FIELD_VALUE = 'meow';
var EXPECTED_HEADERS; var EXPECTED_HEADERS;
var EXPECTED_HEADER_VALUES; var EXPECTED_HEADER_VALUES;
var EXPECTED_UNARY_HEADERS = ['Content-Type', 'Accept', var EXPECTED_UNARY_HEADERS =
'X-User-Agent', 'X-Grpc-Web']; ['Content-Type', 'Accept', 'X-User-Agent', 'X-Grpc-Web'];
var EXPECTED_UNARY_HEADER_VALUES = ['application/grpc-web-text', var EXPECTED_UNARY_HEADER_VALUES = [
'application/grpc-web-text', 'application/grpc-web-text', 'application/grpc-web-text',
'grpc-web-javascript/0.1', 'grpc-web-javascript/0.1', '1'
'1']; ];
var dataCallback; var dataCallback;
@ -62,22 +62,23 @@ testSuite({
client.newXhr_ = function() { client.newXhr_ = function() {
return new MockXhr({ return new MockXhr({
// This parses to [ { DATA: [4,5,6] }, { TRAILER: "a: b" } ] // This parses to [ { DATA: [4,5,6] }, { TRAILER: "a: b" } ]
response: googCrypt.encodeByteArray(new Uint8Array([ response: googCrypt.encodeByteArray(new Uint8Array(
0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98 [0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98])),
])),
}); });
}; };
expectUnaryHeaders(); expectUnaryHeaders();
client.rpcCall(FAKE_METHOD, {}, {}, { client.rpcCall(
FAKE_METHOD, {}, {}, {
requestSerializeFn: function(request) { requestSerializeFn: function(request) {
return REQUEST_BYTES; return REQUEST_BYTES;
}, },
responseDeserializeFn: function(bytes) { responseDeserializeFn: function(bytes) {
assertElementsEquals([4, 5, 6], [].slice.call(bytes)); assertElementsEquals([4, 5, 6], [].slice.call(bytes));
return {"field1": PROTO_FIELD_VALUE}; return {'field1': PROTO_FIELD_VALUE};
} }
}, function(error, response) { },
function(error, response) {
assertNull(error); assertNull(error);
assertEquals(PROTO_FIELD_VALUE, response['field1']); assertEquals(PROTO_FIELD_VALUE, response['field1']);
}); });
@ -119,20 +120,23 @@ testSuite({
return new MockXhr({ return new MockXhr({
// This decodes to "grpc-status: 3" // This decodes to "grpc-status: 3"
response: googCrypt.encodeByteArray(new Uint8Array([ response: googCrypt.encodeByteArray(new Uint8Array([
128, 0, 0, 0, 14, 103, 114, 112, 99, 45, 115, 116, 97, 116, 117, 115, 58, 32, 51 128, 0, 0, 0, 14, 103, 114, 112, 99, 45, 115, 116, 97, 116, 117, 115,
58, 32, 51
])), ])),
}); });
}; };
expectUnaryHeaders(); expectUnaryHeaders();
client.rpcCall(FAKE_METHOD, {}, {}, { client.rpcCall(
FAKE_METHOD, {}, {}, {
requestSerializeFn: function(request) { requestSerializeFn: function(request) {
return REQUEST_BYTES; return REQUEST_BYTES;
}, },
responseDeserializeFn: function(bytes) { responseDeserializeFn: function(bytes) {
return {}; return {};
} }
}, function(error, response) { },
function(error, response) {
assertNull(response); assertNull(response);
assertEquals(3, error.code); assertEquals(3, error.code);
}); });
@ -144,28 +148,29 @@ testSuite({
client.newXhr_ = function() { client.newXhr_ = function() {
return new MockXhr({ return new MockXhr({
// This parses to [ { DATA: [4,5,6] }, { TRAILER: "a: b" } ] // This parses to [ { DATA: [4,5,6] }, { TRAILER: "a: b" } ]
response: googCrypt.encodeByteArray(new Uint8Array([ response: googCrypt.encodeByteArray(new Uint8Array(
0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98 [0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98])),
])),
}); });
}; };
expectUnaryHeaders(); expectUnaryHeaders();
var call = client.rpcCall(FAKE_METHOD, {}, {}, { var call = client.rpcCall(
FAKE_METHOD, {}, {}, {
requestSerializeFn: function(request) { requestSerializeFn: function(request) {
return REQUEST_BYTES; return REQUEST_BYTES;
}, },
responseDeserializeFn: function(bytes) { responseDeserializeFn: function(bytes) {
assertElementsEquals([4, 5, 6], [].slice.call(bytes)); assertElementsEquals([4, 5, 6], [].slice.call(bytes));
return {"field1": PROTO_FIELD_VALUE}; return {'field1': PROTO_FIELD_VALUE};
} }
}, function(error, response) { },
function(error, response) {
assertNull(error); assertNull(error);
assertEquals(PROTO_FIELD_VALUE, response['field1']); assertEquals(PROTO_FIELD_VALUE, response['field1']);
}); });
call.on('metadata', (metadata) => { call.on('metadata', (metadata) => {
assertEquals(metadata['sample-initial-metadata-1'], assertEquals(
'sample-initial-metadata-val'); metadata['sample-initial-metadata-1'], 'sample-initial-metadata-val');
}); });
dataCallback(); dataCallback();
} }
@ -180,104 +185,102 @@ function expectUnaryHeaders() {
} }
/** @unrestricted */
class MockXhr {
/** /**
* @constructor
* @param {?Object} mockValues * @param {?Object} mockValues
* Mock XhrIO object to test the outgoing values * Mock XhrIO object to test the outgoing values
*/ */
function MockXhr(mockValues) { constructor(mockValues) {
this.mockValues = mockValues; this.mockValues = mockValues;
this.headers = new Map(); this.headers = new Map();
} }
/** /**
* @param {string} url * @param {string} url
* @param {string=} opt_method * @param {string=} opt_method
* @param {string=} opt_content * @param {string=} opt_content
* @param {string=} opt_headers * @param {string=} opt_headers
*/ */
MockXhr.prototype.send = function(url, opt_method, opt_content, opt_headers) { send(url, opt_method, opt_content, opt_headers) {
assertEquals(FAKE_METHOD, url); assertEquals(FAKE_METHOD, url);
assertEquals("POST", opt_method); assertEquals('POST', opt_method);
assertElementsEquals(googCrypt.encodeByteArray(new Uint8Array([0, 0, 0, 0, 3, 1, 2, 3])), opt_content); assertElementsEquals(
googCrypt.encodeByteArray(new Uint8Array([0, 0, 0, 0, 3, 1, 2, 3])),
opt_content);
assertElementsEquals(EXPECTED_HEADERS, this.headers.getKeys()); assertElementsEquals(EXPECTED_HEADERS, this.headers.getKeys());
assertElementsEquals(EXPECTED_HEADER_VALUES, this.headers.getValues()); assertElementsEquals(EXPECTED_HEADER_VALUES, this.headers.getValues());
}; }
/** /**
* @param {boolean} withCredentials * @param {boolean} withCredentials
*/ */
MockXhr.prototype.setWithCredentials = function(withCredentials) { setWithCredentials(withCredentials) {
return; return;
}; }
/** /**
* @return {string} response * @return {string} response
*/ */
MockXhr.prototype.getResponseText = function() { getResponseText() {
return this.mockValues.response; return this.mockValues.response;
}; }
/** /**
* @param {string} key header key * @param {string} key header key
* @return {string} content-type * @return {string} content-type
*/ */
MockXhr.prototype.getStreamingResponseHeader = function(key) { getStreamingResponseHeader(key) {
return 'application/grpc-web-text'; return 'application/grpc-web-text';
}; }
/** /**
* @return {string} response * @return {string} response
*/ */
MockXhr.prototype.getResponseHeaders = function() { getResponseHeaders() {
return {'sample-initial-metadata-1': 'sample-initial-metadata-val'}; return {'sample-initial-metadata-1': 'sample-initial-metadata-val'};
}; }
/** /**
* @return {number} xhr state * @return {number} xhr state
*/ */
MockXhr.prototype.getReadyState = function() { getReadyState() {
return 0; return 0;
}; }
/** /**
* @return {number} lastErrorCode * @return {number} lastErrorCode
*/ */
MockXhr.prototype.getLastErrorCode = function() { getLastErrorCode() {
return 0; return 0;
}; }
/** /**
* @return {string} lastError * @return {string} lastError
*/ */
MockXhr.prototype.getLastError = function() { getLastError() {
return 'server not responding'; return 'server not responding';
}; }
/** /**
* @param {string} responseType * @param {string} responseType
*/ */
MockXhr.prototype.setResponseType = function(responseType) { setResponseType(responseType) {
return; return;
}; }
}
/** /**
* @constructor
* @implements {StreamInterceptor} * @implements {StreamInterceptor}
* @unrestricted
*/ */
const StreamResponseInterceptor = function() {}; class StreamResponseInterceptor {
constructor() {}
/** @override */ /** @override */
StreamResponseInterceptor.prototype.intercept = function(request, invoker) { intercept(request, invoker) {
/** /**
* @implements {ClientReadableStream} * @implements {ClientReadableStream}
* @constructor * @constructor
@ -309,4 +312,5 @@ StreamResponseInterceptor.prototype.intercept = function(request, invoker) {
}; };
return new InterceptedStream(invoker(request)); return new InterceptedStream(invoker(request));
}; }
}

View File

@ -45,28 +45,27 @@ const {Status} = goog.require('grpc.web.Status');
const GRPC_STATUS = "grpc-status"; const GRPC_STATUS = 'grpc-status';
const GRPC_STATUS_MESSAGE = "grpc-message"; const GRPC_STATUS_MESSAGE = 'grpc-message';
/** @type {!Array<string>} */ /** @type {!Array<string>} */
const EXCLUDED_RESPONSE_HEADERS = [ const EXCLUDED_RESPONSE_HEADERS =
'content-type', ['content-type', GRPC_STATUS, GRPC_STATUS_MESSAGE];
GRPC_STATUS,
GRPC_STATUS_MESSAGE
];
/** /**
* A stream that the client can read from. Used for calls that are streaming * A stream that the client can read from. Used for calls that are streaming
* from the server side. * from the server side.
*
* @template RESPONSE * @template RESPONSE
* @constructor
* @implements {ClientReadableStream} * @implements {ClientReadableStream}
* @final * @final
* @unrestricted
*/
class GrpcWebClientReadableStream {
/**
* @param {!GenericTransportInterface} genericTransportInterface The * @param {!GenericTransportInterface} genericTransportInterface The
* GenericTransportInterface * GenericTransportInterface
*/ */
const GrpcWebClientReadableStream = function(genericTransportInterface) { constructor(genericTransportInterface) {
/** /**
* @const * @const
* @private * @private
@ -135,8 +134,7 @@ const GrpcWebClientReadableStream = function(genericTransportInterface) {
this.parser_ = new GrpcWebStreamParser(); this.parser_ = new GrpcWebStreamParser();
var self = this; var self = this;
events.listen(this.xhr_, EventType.READY_STATE_CHANGE, events.listen(this.xhr_, EventType.READY_STATE_CHANGE, function(e) {
function(e) {
var contentType = self.xhr_.getStreamingResponseHeader('Content-Type'); var contentType = self.xhr_.getStreamingResponseHeader('Content-Type');
if (!contentType) return; if (!contentType) return;
contentType = contentType.toLowerCase(); contentType = contentType.toLowerCase();
@ -172,8 +170,8 @@ const GrpcWebClientReadableStream = function(genericTransportInterface) {
var trailerString = ''; var trailerString = '';
for (var pos = 0; pos < messages[i][FrameType.TRAILER].length; for (var pos = 0; pos < messages[i][FrameType.TRAILER].length;
pos++) { pos++) {
trailerString += String.fromCharCode( trailerString +=
messages[i][FrameType.TRAILER][pos]); String.fromCharCode(messages[i][FrameType.TRAILER][pos]);
} }
var trailers = self.parseHttp1Headers_(trailerString); var trailers = self.parseHttp1Headers_(trailerString);
var grpcStatusCode = StatusCode.OK; var grpcStatusCode = StatusCode.OK;
@ -265,15 +263,13 @@ const GrpcWebClientReadableStream = function(genericTransportInterface) {
self.sendEndCallbacks_(); self.sendEndCallbacks_();
} }
}); });
}; }
/** /**
* @override * @override
* @export * @export
*/ */
GrpcWebClientReadableStream.prototype.on = function( on(eventType, callback) {
eventType, callback) {
// TODO(stanleycheung): change eventType to @enum type // TODO(stanleycheung): change eventType to @enum type
if (eventType == 'data') { if (eventType == 'data') {
this.onDataCallbacks_.push(callback); this.onDataCallbacks_.push(callback);
@ -287,29 +283,25 @@ GrpcWebClientReadableStream.prototype.on = function(
this.onErrorCallbacks_.push(callback); this.onErrorCallbacks_.push(callback);
} }
return this; return this;
}; }
/** /**
* @private * @private
* @param {!Array<function(?)>} callbacks the internal list of callbacks * @param {!Array<function(?)>} callbacks the internal list of callbacks
* @param {function(?)} callback the callback to remove * @param {function(?)} callback the callback to remove
*/ */
GrpcWebClientReadableStream.prototype.removeListenerFromCallbacks_ = function( removeListenerFromCallbacks_(callbacks, callback) {
callbacks, callback) {
const index = callbacks.indexOf(callback); const index = callbacks.indexOf(callback);
if (index > -1) { if (index > -1) {
callbacks.splice(index, 1); callbacks.splice(index, 1);
} }
}; }
/** /**
* @export * @export
* @override * @override
*/ */
GrpcWebClientReadableStream.prototype.removeListener = function( removeListener(eventType, callback) {
eventType, callback) {
if (eventType == 'data') { if (eventType == 'data') {
this.removeListenerFromCallbacks_(this.onDataCallbacks_, callback); this.removeListenerFromCallbacks_(this.onDataCallbacks_, callback);
} else if (eventType == 'status') { } else if (eventType == 'status') {
@ -322,8 +314,7 @@ GrpcWebClientReadableStream.prototype.removeListener = function(
this.removeListenerFromCallbacks_(this.onErrorCallbacks_, callback); this.removeListenerFromCallbacks_(this.onErrorCallbacks_, callback);
} }
return this; return this;
}; }
/** /**
* Register a callbackl to parse the response * Register a callbackl to parse the response
@ -331,21 +322,18 @@ GrpcWebClientReadableStream.prototype.removeListener = function(
* @param {function(?):!RESPONSE} responseDeserializeFn The deserialize * @param {function(?):!RESPONSE} responseDeserializeFn The deserialize
* function for the proto * function for the proto
*/ */
GrpcWebClientReadableStream.prototype.setResponseDeserializeFn = setResponseDeserializeFn(responseDeserializeFn) {
function(responseDeserializeFn) {
this.responseDeserializeFn_ = responseDeserializeFn; this.responseDeserializeFn_ = responseDeserializeFn;
}; }
/** /**
* @override * @override
* @export * @export
*/ */
GrpcWebClientReadableStream.prototype.cancel = function() { cancel() {
this.aborted_ = true; this.aborted_ = true;
this.xhr_.abort(); this.xhr_.abort();
}; }
/** /**
* Parse HTTP headers * Parse HTTP headers
@ -354,72 +342,67 @@ GrpcWebClientReadableStream.prototype.cancel = function() {
* @param {string} str The raw http header string * @param {string} str The raw http header string
* @return {!Object} The header:value pairs * @return {!Object} The header:value pairs
*/ */
GrpcWebClientReadableStream.prototype.parseHttp1Headers_ = parseHttp1Headers_(str) {
function(str) { var chunks = str.trim().split('\r\n');
var chunks = str.trim().split("\r\n");
var headers = {}; var headers = {};
for (var i = 0; i < chunks.length; i++) { for (var i = 0; i < chunks.length; i++) {
var pos = chunks[i].indexOf(":"); var pos = chunks[i].indexOf(':');
headers[chunks[i].substring(0, pos).trim()] = headers[chunks[i].substring(0, pos).trim()] =
chunks[i].substring(pos + 1).trim(); chunks[i].substring(pos + 1).trim();
} }
return headers; return headers;
}; }
/** /**
* @private * @private
* @param {!RESPONSE} data The data to send back * @param {!RESPONSE} data The data to send back
*/ */
GrpcWebClientReadableStream.prototype.sendDataCallbacks_ = function(data) { sendDataCallbacks_(data) {
for (var i = 0; i < this.onDataCallbacks_.length; i++) { for (var i = 0; i < this.onDataCallbacks_.length; i++) {
this.onDataCallbacks_[i](data); this.onDataCallbacks_[i](data);
} }
}; }
/** /**
* @private * @private
* @param {!Status} status The status to send back * @param {!Status} status The status to send back
*/ */
GrpcWebClientReadableStream.prototype.sendStatusCallbacks_ = function(status) { sendStatusCallbacks_(status) {
for (var i = 0; i < this.onStatusCallbacks_.length; i++) { for (var i = 0; i < this.onStatusCallbacks_.length; i++) {
this.onStatusCallbacks_[i](status); this.onStatusCallbacks_[i](status);
} }
}; }
/** /**
* @private * @private
* @param {!Metadata} metadata The metadata to send back * @param {!Metadata} metadata The metadata to send back
*/ */
GrpcWebClientReadableStream.prototype.sendMetadataCallbacks_ = sendMetadataCallbacks_(metadata) {
function(metadata) {
for (var i = 0; i < this.onMetadataCallbacks_.length; i++) { for (var i = 0; i < this.onMetadataCallbacks_.length; i++) {
this.onMetadataCallbacks_[i](metadata); this.onMetadataCallbacks_[i](metadata);
} }
}; }
/** /**
* @private * @private
* @param {?} error The error to send back * @param {?} error The error to send back
*/ */
GrpcWebClientReadableStream.prototype.sendErrorCallbacks_ = function(error) { sendErrorCallbacks_(error) {
for (var i = 0; i < this.onErrorCallbacks_.length; i++) { for (var i = 0; i < this.onErrorCallbacks_.length; i++) {
this.onErrorCallbacks_[i](error); this.onErrorCallbacks_[i](error);
} }
}; }
/** /**
* @private * @private
*/ */
GrpcWebClientReadableStream.prototype.sendEndCallbacks_ = function() { sendEndCallbacks_() {
for (var i = 0; i < this.onEndCallbacks_.length; i++) { for (var i = 0; i < this.onEndCallbacks_.length; i++) {
this.onEndCallbacks_[i](); this.onEndCallbacks_[i]();
} }
}; }
}
exports = GrpcWebClientReadableStream; exports = GrpcWebClientReadableStream;

View File

@ -49,13 +49,11 @@ const asserts = goog.require('goog.asserts');
/** /**
* The default grpc-web stream parser. * The default grpc-web stream parser.
*
* @constructor
* @struct
* @implements {StreamParser} * @implements {StreamParser}
* @final * @final
*/ */
const GrpcWebStreamParser = function() { class GrpcWebStreamParser {
constructor() {
/** /**
* The current error message, if any. * The current error message, if any.
* @private {?string} * @private {?string}
@ -99,8 +97,8 @@ const GrpcWebStreamParser = function() {
this.countLengthBytes_ = 0; this.countLengthBytes_ = 0;
/** /**
* Raw bytes of the current message. Uses Uint8Array by default. Falls back to * Raw bytes of the current message. Uses Uint8Array by default. Falls back
* native array when Uint8Array is unsupported. * to native array when Uint8Array is unsupported.
* @private {?Uint8Array|?Array<number>} * @private {?Uint8Array|?Array<number>}
*/ */
this.messageBuffer_ = null; this.messageBuffer_ = null;
@ -110,83 +108,38 @@ const GrpcWebStreamParser = function() {
* @private {number} * @private {number}
*/ */
this.countMessageBytes_ = 0; this.countMessageBytes_ = 0;
}; }
const Parser = GrpcWebStreamParser;
/**
* The parser state.
* @private @enum {number}
*/
Parser.State_ = {
INIT: 0, // expecting the next frame byte
LENGTH: 1, // expecting 4 bytes of length
MESSAGE: 2, // expecting more message bytes
INVALID: 3
};
/**
* Possible frame byte
* @enum {number}
*/
GrpcWebStreamParser.FrameType = {
DATA: 0x00, // expecting a data frame
TRAILER: 0x80, // expecting a trailer frame
};
var FrameType = GrpcWebStreamParser.FrameType;
/** /**
* @override * @override
*/ */
GrpcWebStreamParser.prototype.isInputValid = function() { isInputValid() {
return this.state_ != Parser.State_.INVALID; return this.state_ != Parser.State_.INVALID;
}; }
/** /**
* @override * @override
*/ */
GrpcWebStreamParser.prototype.getErrorMessage = function() { getErrorMessage() {
return this.errorMessage_; return this.errorMessage_;
}; }
/**
* @param {!Uint8Array|!Array<number>} inputBytes The current input buffer
* @param {number} pos The position in the current input that triggers the error
* @param {string} errorMsg Additional error message
* @throws {!Error} Throws an error indicating where the stream is broken
* @private
*/
Parser.prototype.error_ = function(inputBytes, pos, errorMsg) {
this.state_ = Parser.State_.INVALID;
this.errorMessage_ = 'The stream is broken @' + this.streamPos_ + '/' + pos +
'. ' +
'Error: ' + errorMsg + '. ' +
'With input:\n' + inputBytes;
throw new Error(this.errorMessage_);
};
/** /**
* Parse the new input. * Parse the new input.
* *
* Note that there is no Parser state to indicate the end of a stream. * Note that there is no Parser state to indicate the end of a stream.
* *
* @param {string|!ArrayBuffer|!Uint8Array|!Array<number>} input The input data * @param {string|!ArrayBuffer|!Uint8Array|!Array<number>} input The input
* data
* @throws {!Error} Throws an error message if the input is invalid. * @throws {!Error} Throws an error message if the input is invalid.
* @return {?Array<string|!Object>} any parsed objects (atomic messages) * @return {?Array<string|!Object>} any parsed objects (atomic messages)
* in an array, or null if more data needs be read to parse any new object. * in an array, or null if more data needs be read to parse any new object.
* @override * @override
*/ */
GrpcWebStreamParser.prototype.parse = function(input) { parse(input) {
asserts.assert(input instanceof Array || input instanceof ArrayBuffer || input instanceof Uint8Array); asserts.assert(
input instanceof Array || input instanceof ArrayBuffer ||
input instanceof Uint8Array);
var parser = this; var parser = this;
var inputBytes; var inputBytes;
@ -216,7 +169,9 @@ GrpcWebStreamParser.prototype.parse = function(input) {
processMessageByte(inputBytes[pos]); processMessageByte(inputBytes[pos]);
break; break;
} }
default: { throw new Error('unexpected parser state: ' + parser.state_); } default: {
throw new Error('unexpected parser state: ' + parser.state_);
}
} }
parser.streamPos_++; parser.streamPos_++;
@ -285,6 +240,53 @@ GrpcWebStreamParser.prototype.parse = function(input) {
parser.result_.push(message); parser.result_.push(message);
parser.state_ = Parser.State_.INIT; parser.state_ = Parser.State_.INIT;
} }
}
}
const Parser = GrpcWebStreamParser;
/**
* The parser state.
* @private @enum {number}
*/
Parser.State_ = {
INIT: 0, // expecting the next frame byte
LENGTH: 1, // expecting 4 bytes of length
MESSAGE: 2, // expecting more message bytes
INVALID: 3
};
/**
* Possible frame byte
* @enum {number}
*/
GrpcWebStreamParser.FrameType = {
DATA: 0x00, // expecting a data frame
TRAILER: 0x80, // expecting a trailer frame
};
var FrameType = GrpcWebStreamParser.FrameType;
/**
* @param {!Uint8Array|!Array<number>} inputBytes The current input buffer
* @param {number} pos The position in the current input that triggers the error
* @param {string} errorMsg Additional error message
* @throws {!Error} Throws an error indicating where the stream is broken
* @private
*/
Parser.prototype.error_ = function(inputBytes, pos, errorMsg) {
this.state_ = Parser.State_.INVALID;
this.errorMessage_ = 'The stream is broken @' + this.streamPos_ + '/' + pos +
'. ' +
'Error: ' + errorMsg + '. ' +
'With input:\n' + inputBytes;
throw new Error(this.errorMessage_);
}; };

View File

@ -13,10 +13,9 @@ const MethodType = goog.require('grpc.web.MethodType');
const Request = goog.require('grpc.web.Request'); const Request = goog.require('grpc.web.Request');
const RequestInternal = goog.require('grpc.web.RequestInternal'); const RequestInternal = goog.require('grpc.web.RequestInternal');
/** @template REQUEST, RESPONSE */
class MethodDescriptor {
/** /**
* @constructor
* @struct
* @template REQUEST, RESPONSE
* @param {string} name * @param {string} name
* @param {!MethodType} methodType * @param {!MethodType} methodType
* @param {function(new: REQUEST, ...)} requestType * @param {function(new: REQUEST, ...)} requestType
@ -24,7 +23,7 @@ const RequestInternal = goog.require('grpc.web.RequestInternal');
* @param {function(REQUEST): ?} requestSerializeFn * @param {function(REQUEST): ?} requestSerializeFn
* @param {function(?): RESPONSE} responseDeserializeFn * @param {function(?): RESPONSE} responseDeserializeFn
*/ */
const MethodDescriptor = function( constructor(
name, methodType, requestType, responseType, requestSerializeFn, name, methodType, requestType, responseType, requestSerializeFn,
responseDeserializeFn) { responseDeserializeFn) {
/** @const */ /** @const */
@ -39,7 +38,7 @@ const MethodDescriptor = function(
this.requestSerializeFn = requestSerializeFn; this.requestSerializeFn = requestSerializeFn;
/** @const */ /** @const */
this.responseDeserializeFn = responseDeserializeFn; this.responseDeserializeFn = responseDeserializeFn;
}; }
/** /**
* @template REQUEST, RESPONSE * @template REQUEST, RESPONSE
@ -48,9 +47,12 @@ const MethodDescriptor = function(
* @param {!CallOptions=} callOptions * @param {!CallOptions=} callOptions
* @return {!Request<REQUEST, RESPONSE>} * @return {!Request<REQUEST, RESPONSE>}
*/ */
MethodDescriptor.prototype.createRequest = function( createRequest(
requestMessage, metadata = {}, callOptions = new CallOptions()) { requestMessage, metadata = {}, callOptions = new CallOptions()) {
return new RequestInternal(requestMessage, this, metadata, callOptions); return new RequestInternal(requestMessage, this, metadata, callOptions);
}; }
}
exports = MethodDescriptor; exports = MethodDescriptor;

View File

@ -44,15 +44,17 @@ const {Status} = goog.require('grpc.web.Status');
/** /**
* A stream that the client can read from. Used for calls that are streaming * A stream that the client can read from. Used for calls that are streaming
* from the server side. * from the server side.
*
* @template RESPONSE * @template RESPONSE
* @constructor
* @implements {ClientReadableStream} * @implements {ClientReadableStream}
* @final * @final
* @unrestricted
*/
class StreamBodyClientReadableStream {
/**
* @param {!GenericTransportInterface} genericTransportInterface The * @param {!GenericTransportInterface} genericTransportInterface The
* GenericTransportInterface * GenericTransportInterface
*/ */
const StreamBodyClientReadableStream = function(genericTransportInterface) { constructor(genericTransportInterface) {
/** /**
* @const * @const
* @private * @private
@ -109,13 +111,12 @@ const StreamBodyClientReadableStream = function(genericTransportInterface) {
this.rpcStatusParseFn_ = null; this.rpcStatusParseFn_ = null;
this.setStreamCallback_(); this.setStreamCallback_();
}
};
/** /**
* @private * @private
*/ */
StreamBodyClientReadableStream.prototype.setStreamCallback_ = function() { setStreamCallback_() {
// Add the callback to the underlying stream // Add the callback to the underlying stream
var self = this; var self = this;
this.xhrNodeReadableStream_.on('data', function(data) { this.xhrNodeReadableStream_.on('data', function(data) {
@ -167,14 +168,13 @@ StreamBodyClientReadableStream.prototype.setStreamCallback_ = function() {
message: ErrorCode.getDebugMessage(lastErrorCode) message: ErrorCode.getDebugMessage(lastErrorCode)
}); });
}); });
}; }
/** /**
* @override * @override
* @export * @export
*/ */
StreamBodyClientReadableStream.prototype.on = function( on(eventType, callback) {
eventType, callback) {
// TODO(stanleycheung): change eventType to @enum type // TODO(stanleycheung): change eventType to @enum type
if (eventType == 'data') { if (eventType == 'data') {
this.onDataCallbacks_.push(callback); this.onDataCallbacks_.push(callback);
@ -186,29 +186,25 @@ StreamBodyClientReadableStream.prototype.on = function(
this.onErrorCallbacks_.push(callback); this.onErrorCallbacks_.push(callback);
} }
return this; return this;
}; }
/** /**
* @private * @private
* @param {!Array<function(?)>} callbacks the internal list of callbacks * @param {!Array<function(?)>} callbacks the internal list of callbacks
* @param {function(?)} callback the callback to remove * @param {function(?)} callback the callback to remove
*/ */
StreamBodyClientReadableStream.prototype.removeListenerFromCallbacks_ = function( removeListenerFromCallbacks_(callbacks, callback) {
callbacks, callback) {
const index = callbacks.indexOf(callback); const index = callbacks.indexOf(callback);
if (index > -1) { if (index > -1) {
callbacks.splice(index, 1); callbacks.splice(index, 1);
} }
}; }
/** /**
* @export * @export
* @override * @override
*/ */
StreamBodyClientReadableStream.prototype.removeListener = function( removeListener(eventType, callback) {
eventType, callback) {
if (eventType == 'data') { if (eventType == 'data') {
this.removeListenerFromCallbacks_(this.onDataCallbacks_, callback); this.removeListenerFromCallbacks_(this.onDataCallbacks_, callback);
} else if (eventType == 'status') { } else if (eventType == 'status') {
@ -219,8 +215,7 @@ StreamBodyClientReadableStream.prototype.removeListener = function(
this.removeListenerFromCallbacks_(this.onErrorCallbacks_, callback); this.removeListenerFromCallbacks_(this.onErrorCallbacks_, callback);
} }
return this; return this;
}; }
/** /**
* Register a callbackl to parse the response * Register a callbackl to parse the response
@ -228,10 +223,9 @@ StreamBodyClientReadableStream.prototype.removeListener = function(
* @param {function(?): RESPONSE} responseDeserializeFn The deserialize * @param {function(?): RESPONSE} responseDeserializeFn The deserialize
* function for the proto * function for the proto
*/ */
StreamBodyClientReadableStream.prototype.setResponseDeserializeFn = setResponseDeserializeFn(responseDeserializeFn) {
function(responseDeserializeFn) {
this.responseDeserializeFn_ = responseDeserializeFn; this.responseDeserializeFn_ = responseDeserializeFn;
}; }
/** /**
* Register a function to parse RPC status response * Register a function to parse RPC status response
@ -239,61 +233,58 @@ StreamBodyClientReadableStream.prototype.setResponseDeserializeFn =
* @param {function(?):!Status} rpcStatusParseFn A function to parse * @param {function(?):!Status} rpcStatusParseFn A function to parse
* the RPC status response * the RPC status response
*/ */
StreamBodyClientReadableStream.prototype.setRpcStatusParseFn = function(rpcStatusParseFn) { setRpcStatusParseFn(rpcStatusParseFn) {
this.rpcStatusParseFn_ = rpcStatusParseFn; this.rpcStatusParseFn_ = rpcStatusParseFn;
}; }
/** /**
* @override * @override
* @export * @export
*/ */
StreamBodyClientReadableStream.prototype.cancel = function() { cancel() {
this.xhr_.abort(); this.xhr_.abort();
}; }
/** /**
* @private * @private
* @param {!RESPONSE} data The data to send back * @param {!RESPONSE} data The data to send back
*/ */
StreamBodyClientReadableStream.prototype.sendDataCallbacks_ = function(data) { sendDataCallbacks_(data) {
for (var i = 0; i < this.onDataCallbacks_.length; i++) { for (var i = 0; i < this.onDataCallbacks_.length; i++) {
this.onDataCallbacks_[i](data); this.onDataCallbacks_[i](data);
} }
}; }
/** /**
* @private * @private
* @param {!Status} status The status to send back * @param {!Status} status The status to send back
*/ */
StreamBodyClientReadableStream.prototype.sendStatusCallbacks_ = function(status) { sendStatusCallbacks_(status) {
for (var i = 0; i < this.onStatusCallbacks_.length; i++) { for (var i = 0; i < this.onStatusCallbacks_.length; i++) {
this.onStatusCallbacks_[i](status); this.onStatusCallbacks_[i](status);
} }
}; }
/** /**
* @private * @private
* @param {?} error The error to send back * @param {?} error The error to send back
*/ */
StreamBodyClientReadableStream.prototype.sendErrorCallbacks_ = function(error) { sendErrorCallbacks_(error) {
for (var i = 0; i < this.onErrorCallbacks_.length; i++) { for (var i = 0; i < this.onErrorCallbacks_.length; i++) {
this.onErrorCallbacks_[i](error); this.onErrorCallbacks_[i](error);
} }
}; }
/** /**
* @private * @private
*/ */
StreamBodyClientReadableStream.prototype.sendEndCallbacks_ = function() { sendEndCallbacks_() {
for (var i = 0; i < this.onEndCallbacks_.length; i++) { for (var i = 0; i < this.onEndCallbacks_.length; i++) {
this.onEndCallbacks_[i](); this.onEndCallbacks_[i]();
} }
}; }
}
exports = StreamBodyClientReadableStream; exports = StreamBodyClientReadableStream;