diff --git a/src/call-credentials-filter.ts b/src/call-credentials-filter.ts index a508e846..67812459 100644 --- a/src/call-credentials-filter.ts +++ b/src/call-credentials-filter.ts @@ -1,12 +1,12 @@ -import {promisify} from 'util' -import {Filter, BaseFilter, FilterFactory} from './filter' -import {CallCredentials} from './call-credentials' -import {Http2Channel} from './channel' -import {CallStream} from './call-stream' -import {Metadata} from './metadata' +import {promisify} from 'util'; + +import {CallCredentials} from './call-credentials'; +import {CallStream} from './call-stream'; +import {Http2Channel} from './channel'; +import {BaseFilter, Filter, FilterFactory} from './filter'; +import {Metadata} from './metadata'; export class CallCredentialsFilter extends BaseFilter implements Filter { - constructor(private readonly credentials: CallCredentials) { super(); } @@ -20,13 +20,15 @@ export class CallCredentialsFilter extends BaseFilter implements Filter { } } -export class CallCredentialsFilterFactory implements FilterFactory { +export class CallCredentialsFilterFactory implements + FilterFactory { private readonly credentials: CallCredentials; constructor(channel: Http2Channel) { this.credentials = channel.credentials.getCallCredentials(); } createFilter(callStream: CallStream): CallCredentialsFilter { - return new CallCredentialsFilter(this.credentials.compose(callStream.getCredentials())); + return new CallCredentialsFilter( + this.credentials.compose(callStream.getCredentials())); } } diff --git a/src/call-credentials.ts b/src/call-credentials.ts index 4b121836..5b322402 100644 --- a/src/call-credentials.ts +++ b/src/call-credentials.ts @@ -1,10 +1,10 @@ -import { Metadata } from './metadata'; -import {map, reduce} from 'lodash' +import {map, reduce} from 'lodash'; -export type CallMetadataGenerator = ( - options: Object, - cb: (err: Error | null, metadata?: Metadata) => void -) => void; +import {Metadata} from './metadata'; + +export type CallMetadataGenerator = + (options: Object, cb: (err: Error|null, metadata?: Metadata) => void) => + void; /** * A class that represents a generic method of adding authentication-related @@ -25,32 +25,13 @@ export interface CallCredentials { compose(callCredentials: CallCredentials): CallCredentials; } -export namespace CallCredentials { - /** - * Creates a new CallCredentials object from a given function that generates - * Metadata objects. - * @param metadataGenerator A function that accepts a set of options, and - * generates a Metadata object based on these options, which is passed back - * to the caller via a supplied (err, metadata) callback. - */ - export function createFromMetadataGenerator( - metadataGenerator: CallMetadataGenerator - ): CallCredentials { - return new SingleCallCredentials(metadataGenerator); - } - - export function createEmpty(): CallCredentials { - return new EmptyCallCredentials(); - } -} - class ComposedCallCredentials implements CallCredentials { constructor(private creds: CallCredentials[]) {} async generateMetadata(options: Object): Promise { let base: Metadata = new Metadata(); - let generated: Metadata[] = await Promise.all(map( - this.creds, (cred) => cred.generateMetadata(options))); + let generated: Metadata[] = await Promise.all( + map(this.creds, (cred) => cred.generateMetadata(options))); for (let gen of generated) { base.merge(gen); } @@ -62,7 +43,7 @@ class ComposedCallCredentials implements CallCredentials { } } -class SingleCallCredentials implements CallCredentials{ +class SingleCallCredentials implements CallCredentials { constructor(private metadataGenerator: CallMetadataGenerator) {} async generateMetadata(options: Object): Promise { @@ -83,13 +64,29 @@ class SingleCallCredentials implements CallCredentials{ } class EmptyCallCredentials implements CallCredentials { - constructor () {} - async generateMetadata(options: Object): Promise { return new Metadata(); } - compose(other:CallCredentials): CallCredentials { + compose(other: CallCredentials): CallCredentials { return other; } } + +export namespace CallCredentials { + /** + * Creates a new CallCredentials object from a given function that generates + * Metadata objects. + * @param metadataGenerator A function that accepts a set of options, and + * generates a Metadata object based on these options, which is passed back + * to the caller via a supplied (err, metadata) callback. + */ + export function createFromMetadataGenerator( + metadataGenerator: CallMetadataGenerator): CallCredentials { + return new SingleCallCredentials(metadataGenerator); + } + + export function createEmpty(): CallCredentials { + return new EmptyCallCredentials(); + } +} diff --git a/src/call-stream.ts b/src/call-stream.ts index 39e8343d..e8cb8753 100644 --- a/src/call-stream.ts +++ b/src/call-stream.ts @@ -1,18 +1,14 @@ -import * as stream from 'stream'; - -import * as http2 from 'http2'; +import * as http2 from 'http2'; +import {Duplex} from 'stream'; import {CallCredentials} from './call-credentials'; import {Status} from './constants'; +import {Filter} from './filter'; +import {FilterStackFactory} from './filter-stack'; import {Metadata} from './metadata'; import {ObjectDuplex} from './object-stream'; -import {Filter} from './filter' -import {FilterStackFactory} from './filter-stack'; -const { - HTTP2_HEADER_STATUS, - HTTP2_HEADER_CONTENT_TYPE -} = http2.constants; +const {HTTP2_HEADER_STATUS, HTTP2_HEADER_CONTENT_TYPE} = http2.constants; export type Deadline = Date | number; @@ -46,7 +42,7 @@ export interface CallStream extends ObjectDuplex { getCredentials(): CallCredentials; /* If the return value is null, the call has not ended yet. Otherwise, it has * ended with the specified status */ - getStatus(): StatusObject | null; + getStatus(): StatusObject|null; addListener(event: string, listener: Function): this; emit(event: string|symbol, ...args: any[]): boolean; @@ -85,35 +81,35 @@ enum ReadState { READING_MESSAGE } -export class Http2CallStream extends stream.Duplex implements CallStream { - +export class Http2CallStream extends Duplex implements CallStream { public filterStack: Filter; - private statusEmitted: boolean = false; - private http2Stream: http2.ClientHttp2Stream | null = null; - private pendingRead: boolean = false; - private pendingWrite: Buffer | null = null; - private pendingWriteCallback: Function | null = null; - private pendingFinalCallback: Function | null = null; + private statusEmitted = false; + private http2Stream: http2.ClientHttp2Stream|null = null; + private pendingRead = false; + private pendingWrite: Buffer|null = null; + private pendingWriteCallback: Function|null = null; + private pendingFinalCallback: Function|null = null; private readState: ReadState = ReadState.NO_DATA; - private readCompressFlag: boolean = false; + private readCompressFlag = false; private readPartialSize: Buffer = Buffer.alloc(4); - private readSizeRemaining: number = 4; - private readMessageSize: number = 0; + private readSizeRemaining = 4; + private readMessageSize = 0; private readPartialMessage: Buffer[] = []; private readMessageRemaining = 0; - private unpushedReadMessages: (Buffer | null)[] = []; + private unpushedReadMessages: (Buffer|null)[] = []; // Status code mapped from :status. To be used if grpc-status is not received private mappedStatusCode: Status = Status.UNKNOWN; // This is populated (non-null) if and only if the call has ended - private finalStatus: StatusObject | null = null; + private finalStatus: StatusObject|null = null; - constructor(private readonly methodName: string, - private readonly options: CallStreamOptions, - filterStackFactory: FilterStackFactory) { + constructor( + private readonly methodName: string, + private readonly options: CallStreamOptions, + filterStackFactory: FilterStackFactory) { super({objectMode: true}); this.filterStack = filterStackFactory.createFilter(this); } @@ -131,28 +127,28 @@ export class Http2CallStream extends stream.Duplex implements CallStream { } else { this.http2Stream = stream; stream.on('response', (headers) => { - switch(headers[HTTP2_HEADER_STATUS]) { + switch (headers[HTTP2_HEADER_STATUS]) { // TODO(murgatroid99): handle 100 and 101 - case '400': - this.mappedStatusCode = Status.INTERNAL; - break; - case '401': - this.mappedStatusCode = Status.UNAUTHENTICATED; - break; - case '403': - this.mappedStatusCode = Status.PERMISSION_DENIED; - break; - case '404': - this.mappedStatusCode = Status.UNIMPLEMENTED; - break; - case '429': - case '502': - case '503': - case '504': - this.mappedStatusCode = Status.UNAVAILABLE; - break; - default: - this.mappedStatusCode = Status.UNKNOWN; + case '400': + this.mappedStatusCode = Status.INTERNAL; + break; + case '401': + this.mappedStatusCode = Status.UNAUTHENTICATED; + break; + case '403': + this.mappedStatusCode = Status.PERMISSION_DENIED; + break; + case '404': + this.mappedStatusCode = Status.UNIMPLEMENTED; + break; + case '429': + case '502': + case '503': + case '504': + this.mappedStatusCode = Status.UNAVAILABLE; + break; + default: + this.mappedStatusCode = Status.UNKNOWN; } delete headers[HTTP2_HEADER_STATUS]; delete headers[HTTP2_HEADER_CONTENT_TYPE]; @@ -163,11 +159,14 @@ export class Http2CallStream extends stream.Duplex implements CallStream { this.cancelWithStatus(Status.UNKNOWN, e.message); return; } - this.filterStack.receiveMetadata(Promise.resolve(metadata)).then((finalMetadata) => { - this.emit('metadata', finalMetadata); - }, (error) => { - this.cancelWithStatus(Status.UNKNOWN, error.message); - }); + this.filterStack.receiveMetadata(Promise.resolve(metadata)) + .then( + (finalMetadata) => { + this.emit('metadata', finalMetadata); + }, + (error) => { + this.cancelWithStatus(Status.UNKNOWN, error.message); + }); }); stream.on('trailers', (headers) => { let code: Status = this.mappedStatusCode; @@ -180,7 +179,7 @@ export class Http2CallStream extends stream.Duplex implements CallStream { } delete headers['grpc-status']; } - let details: string = ''; + let details = ''; if (headers.hasOwnProperty('grpc-message')) { details = decodeURI(headers['grpc-message']); } @@ -190,65 +189,73 @@ export class Http2CallStream extends stream.Duplex implements CallStream { } catch (e) { metadata = new Metadata(); } - let status: StatusObject = { code, details, metadata }; - this.filterStack.receiveTrailers(Promise.resolve(status)).then((finalStatus) => { - this.endCall(finalStatus); - }, (error) => { - this.endCall({ - code: Status.INTERNAL, - details: 'Failed to process received status', - metadata: new Metadata() - }); - }); + let status: StatusObject = {code, details, metadata}; + this.filterStack.receiveTrailers(Promise.resolve(status)) + .then( + (finalStatus) => { + this.endCall(finalStatus); + }, + (error) => { + this.endCall({ + code: Status.INTERNAL, + details: 'Failed to process received status', + metadata: new Metadata() + }); + }); }); stream.on('read', (data) => { let readHead = 0; let canPush = true; let toRead: number; while (readHead < data.length) { - switch(this.readState) { - case ReadState.NO_DATA: - this.readCompressFlag = (data.readUInt8(readHead) !== 0); - readHead += 1; - this.readState = ReadState.READING_SIZE; - this.readPartialSize.fill(0); - this.readSizeRemaining = 4; - this.readMessageSize = 0; - this.readMessageRemaining = 0; - this.readPartialMessage = []; - break; - case ReadState.READING_SIZE: - toRead = Math.min(data.length - readHead, this.readSizeRemaining); - data.copy(this.readPartialSize, 4 - this.readSizeRemaining, readHead, readHead + toRead); - this.readSizeRemaining -= toRead; - readHead += toRead; - // readSizeRemaining >=0 here - if (this.readSizeRemaining === 0) { - this.readMessageSize = this.readPartialSize.readUInt32BE(0); - this.readMessageRemaining = this.readMessageSize; - this.readState = ReadState.READING_MESSAGE; - } - break; - case ReadState.READING_MESSAGE: - toRead = Math.min(data.length - readHead, this.readMessageRemaining); - this.readPartialMessage.push(data.slice(readHead, readHead + toRead)); - this.readMessageRemaining -= toRead; - readHead += toRead; - // readMessageRemaining >=0 here - if (this.readMessageRemaining === 0) { - // At this point, we have read a full message - const messageBytes = Buffer.concat(this.readPartialMessage, this.readMessageSize); - // TODO(murgatroid99): Add receive message filters - if (canPush) { - if (!this.push(messageBytes)) { - canPush = false; - (this.http2Stream as http2.ClientHttp2Stream).pause(); - } - } else { - this.unpushedReadMessages.push(messageBytes); + switch (this.readState) { + case ReadState.NO_DATA: + this.readCompressFlag = (data.readUInt8(readHead) !== 0); + readHead += 1; + this.readState = ReadState.READING_SIZE; + this.readPartialSize.fill(0); + this.readSizeRemaining = 4; + this.readMessageSize = 0; + this.readMessageRemaining = 0; + this.readPartialMessage = []; + break; + case ReadState.READING_SIZE: + toRead = Math.min(data.length - readHead, this.readSizeRemaining); + data.copy( + this.readPartialSize, 4 - this.readSizeRemaining, readHead, + readHead + toRead); + this.readSizeRemaining -= toRead; + readHead += toRead; + // readSizeRemaining >=0 here + if (this.readSizeRemaining === 0) { + this.readMessageSize = this.readPartialSize.readUInt32BE(0); + this.readMessageRemaining = this.readMessageSize; + this.readState = ReadState.READING_MESSAGE; + } + break; + case ReadState.READING_MESSAGE: + toRead = + Math.min(data.length - readHead, this.readMessageRemaining); + this.readPartialMessage.push( + data.slice(readHead, readHead + toRead)); + this.readMessageRemaining -= toRead; + readHead += toRead; + // readMessageRemaining >=0 here + if (this.readMessageRemaining === 0) { + // At this point, we have read a full message + const messageBytes = Buffer.concat( + this.readPartialMessage, this.readMessageSize); + // TODO(murgatroid99): Add receive message filters + if (canPush) { + if (!this.push(messageBytes)) { + canPush = false; + (this.http2Stream as http2.ClientHttp2Stream).pause(); + } + } else { + this.unpushedReadMessages.push(messageBytes); + } + this.readState = ReadState.NO_DATA; } - this.readState = ReadState.NO_DATA; - } } } }); @@ -261,30 +268,26 @@ export class Http2CallStream extends stream.Duplex implements CallStream { }); stream.on('streamClosed', (errorCode) => { let code: Status; - let details: string = ''; - switch(errorCode) { - case http2.constants.NGHTTP2_REFUSED_STREAM: - code = Status.UNAVAILABLE; - break; - case http2.constants.NGHTTP2_CANCEL: - code = Status.CANCELLED; - break; - case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM: - code = Status.RESOURCE_EXHAUSTED; - details = 'Bandwidth exhausted'; - break; - case http2.constants.NGHTTP2_INADEQUATE_SECURITY: - code = Status.PERMISSION_DENIED; - details = 'Protocol not secure enough'; - break; - default: - code = Status.INTERNAL; + let details = ''; + switch (errorCode) { + case http2.constants.NGHTTP2_REFUSED_STREAM: + code = Status.UNAVAILABLE; + break; + case http2.constants.NGHTTP2_CANCEL: + code = Status.CANCELLED; + break; + case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM: + code = Status.RESOURCE_EXHAUSTED; + details = 'Bandwidth exhausted'; + break; + case http2.constants.NGHTTP2_INADEQUATE_SECURITY: + code = Status.PERMISSION_DENIED; + details = 'Protocol not secure enough'; + break; + default: + code = Status.INTERNAL; } - this.endCall({ - code: code, - details: details, - metadata: new Metadata() - }); + this.endCall({code: code, details: details, metadata: new Metadata()}); }); stream.on('error', () => { this.endCall({ @@ -325,7 +328,7 @@ export class Http2CallStream extends stream.Duplex implements CallStream { return this.options.credentials; } - getStatus(): StatusObject | null { + getStatus(): StatusObject|null { return this.finalStatus; } diff --git a/src/channel-credentials.ts b/src/channel-credentials.ts index 49a93422..0d87d5de 100644 --- a/src/channel-credentials.ts +++ b/src/channel-credentials.ts @@ -1,5 +1,6 @@ -import { CallCredentials } from './call-credentials'; -import { createSecureContext, SecureContext } from 'tls'; +import {createSecureContext, SecureContext} from 'tls'; + +import {CallCredentials} from './call-credentials'; /** * A class that contains credentials for communicating over a channel, as well @@ -13,19 +14,69 @@ export interface ChannelCredentials { * @param callCredentials A CallCredentials object to associate with this * instance. */ - compose(callCredentials: CallCredentials) : ChannelCredentials; + compose(callCredentials: CallCredentials): ChannelCredentials; /** * Gets the set of per-call credentials associated with this instance. */ - getCallCredentials() : CallCredentials; + getCallCredentials(): CallCredentials; /** * Gets a SecureContext object generated from input parameters if this * instance was created with createSsl, or null if this instance was created * with createInsecure. */ - getSecureContext() : SecureContext | null; + getSecureContext(): SecureContext|null; +} + +abstract class ChannelCredentialsImpl implements ChannelCredentials { + protected callCredentials: CallCredentials; + + protected constructor(callCredentials?: CallCredentials) { + this.callCredentials = callCredentials || CallCredentials.createEmpty(); + } + + abstract compose(callCredentials: CallCredentials): ChannelCredentialsImpl; + + getCallCredentials(): CallCredentials { + return this.callCredentials; + } + + abstract getSecureContext(): SecureContext|null; +} + +class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl { + constructor(callCredentials?: CallCredentials) { + super(callCredentials); + } + + compose(callCredentials: CallCredentials): ChannelCredentialsImpl { + throw new Error('Cannot compose insecure credentials'); + } + + getSecureContext(): SecureContext|null { + return null; + } +} + +class SecureChannelCredentialsImpl extends ChannelCredentialsImpl { + secureContext: SecureContext; + + constructor(secureContext: SecureContext, callCredentials?: CallCredentials) { + super(callCredentials); + this.secureContext = secureContext; + } + + compose(callCredentials: CallCredentials): ChannelCredentialsImpl { + const combinedCallCredentials = + this.callCredentials.compose(callCredentials); + return new SecureChannelCredentialsImpl( + this.secureContext, combinedCallCredentials); + } + + getSecureContext(): SecureContext|null { + return this.secureContext; + } } export namespace ChannelCredentials { @@ -37,12 +88,16 @@ export namespace ChannelCredentials { * @param privateKey The client certificate private key, if available. * @param certChain The client certificate key chain, if available. */ - export function createSsl(rootCerts?: Buffer | null, privateKey?: Buffer | null, certChain?: Buffer | null) : ChannelCredentials { + export function createSsl( + rootCerts?: Buffer|null, privateKey?: Buffer|null, + certChain?: Buffer|null): ChannelCredentials { if (privateKey && !certChain) { - throw new Error('Private key must be given with accompanying certificate chain'); + throw new Error( + 'Private key must be given with accompanying certificate chain'); } if (!privateKey && certChain) { - throw new Error('Certificate chain must be given with accompanying private key'); + throw new Error( + 'Certificate chain must be given with accompanying private key'); } const secureContext = createSecureContext({ ca: rootCerts || undefined, @@ -55,60 +110,7 @@ export namespace ChannelCredentials { /** * Return a new ChannelCredentials instance with no credentials. */ - export function createInsecure() : ChannelCredentials { + export function createInsecure(): ChannelCredentials { return new InsecureChannelCredentialsImpl(); } } - - -abstract class ChannelCredentialsImpl implements ChannelCredentials { - protected callCredentials: CallCredentials; - - protected constructor(callCredentials?: CallCredentials) { - this.callCredentials = callCredentials || CallCredentials.createEmpty(); - } - - abstract compose(callCredentials: CallCredentials) : ChannelCredentialsImpl; - - getCallCredentials() : CallCredentials { - return this.callCredentials; - } - - abstract getSecureContext() : SecureContext | null; -} - -class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl { - constructor(callCredentials?: CallCredentials) { - super(callCredentials); - } - - compose(callCredentials: CallCredentials) : ChannelCredentialsImpl { - throw new Error("Cannot compose insecure credentials"); - } - - getSecureContext() : SecureContext | null { - return null; - } -} - -class SecureChannelCredentialsImpl extends ChannelCredentialsImpl { - secureContext: SecureContext; - - constructor( - secureContext: SecureContext, - callCredentials?: CallCredentials - ) { - super(callCredentials); - this.secureContext = secureContext; - } - - compose(callCredentials: CallCredentials) : ChannelCredentialsImpl { - const combinedCallCredentials = this.callCredentials.compose(callCredentials); - return new SecureChannelCredentialsImpl(this.secureContext, - combinedCallCredentials); - } - - getSecureContext() : SecureContext | null { - return this.secureContext; - } -} diff --git a/src/channel.ts b/src/channel.ts index 5e9cffbc..1b7fda5b 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -1,17 +1,17 @@ import {EventEmitter} from 'events'; -import {SecureContext} from 'tls'; import * as http2 from 'http2'; +import {SecureContext} from 'tls'; import * as url from 'url'; -import {CallOptions, CallStreamOptions, CallStream, Http2CallStream} from './call-stream'; -import {CallCredentials} from './call-credentials'; -import {ChannelCredentials} from './channel-credentials'; -import {Metadata, MetadataObject} from './metadata'; -import {Status} from './constants' -import {FilterStackFactory} from './filter-stack' -import {DeadlineFilterFactory} from './deadline-filter' -import {CallCredentialsFilterFactory} from './call-credentials-filter' -import {CompressionFilterFactory} from './compression-filter' +import {CallCredentials} from './call-credentials'; +import {CallCredentialsFilterFactory} from './call-credentials-filter'; +import {CallOptions, CallStream, CallStreamOptions, Http2CallStream} from './call-stream'; +import {ChannelCredentials} from './channel-credentials'; +import {CompressionFilterFactory} from './compression-filter'; +import {Status} from './constants'; +import {DeadlineFilterFactory} from './deadline-filter'; +import {FilterStackFactory} from './filter-stack'; +import {Metadata, MetadataObject} from './metadata'; const IDLE_TIMEOUT_MS = 300000; @@ -45,7 +45,8 @@ export enum ConnectivityState { * by a given address. */ export interface Channel extends EventEmitter { - createStream(methodName: string, metadata: Metadata, options: CallOptions): CallStream; + createStream(methodName: string, metadata: Metadata, options: CallOptions): + CallStream; connect(callback: () => void): void; getConnectivityState(): ConnectivityState; close(): void; @@ -61,11 +62,11 @@ export interface Channel extends EventEmitter { export class Http2Channel extends EventEmitter implements Channel { private connectivityState: ConnectivityState = ConnectivityState.IDLE; - private idleTimerId: NodeJS.Timer | null = null; + private idleTimerId: NodeJS.Timer|null = null; /* For now, we have up to one subchannel, which will exist as long as we are * connecting or trying to connect */ - private subChannel : http2.ClientHttp2Session | null; - private filterStackFactory : FilterStackFactory; + private subChannel: http2.ClientHttp2Session|null; + private filterStackFactory: FilterStackFactory; private transitionToState(newState: ConnectivityState): void { if (newState !== this.connectivityState) { @@ -94,7 +95,7 @@ export class Http2Channel extends EventEmitter implements Channel { private goIdle(): void { if (this.subChannel !== null) { - this.subChannel.shutdown({graceful: true}, () => {}); + this.subChannel.shutdown({graceful: true}, () => undefined); this.subChannel = null; } this.transitionToState(ConnectivityState.IDLE); @@ -106,9 +107,10 @@ export class Http2Channel extends EventEmitter implements Channel { } } - constructor(private readonly address: url.URL, - public readonly credentials: ChannelCredentials, - private readonly options: ChannelOptions) { + constructor( + private readonly address: url.URL, + public readonly credentials: ChannelCredentials, + private readonly options: ChannelOptions) { super(); if (credentials.getSecureContext() === null) { address.protocol = 'http'; @@ -117,41 +119,46 @@ export class Http2Channel extends EventEmitter implements Channel { } this.filterStackFactory = new FilterStackFactory([ new CompressionFilterFactory(this), - new CallCredentialsFilterFactory(this), - new DeadlineFilterFactory(this) + new CallCredentialsFilterFactory(this), new DeadlineFilterFactory(this) ]); } - private startHttp2Stream(methodName: string, stream: Http2CallStream, metadata: Metadata) { - let finalMetadata: Promise = stream.filterStack.sendMetadata(Promise.resolve(metadata)); + private startHttp2Stream( + methodName: string, stream: Http2CallStream, metadata: Metadata) { + let finalMetadata: Promise = + stream.filterStack.sendMetadata(Promise.resolve(metadata)); this.connect(() => { - finalMetadata.then((metadataValue) => { - let headers = metadataValue.toHttp2Headers(); - headers[HTTP2_HEADER_AUTHORITY] = this.address.hostname; - headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc'; - headers[HTTP2_HEADER_METHOD] = 'POST'; - headers[HTTP2_HEADER_PATH] = methodName; - headers[HTTP2_HEADER_TE] = 'trailers'; - if (stream.getStatus() === null) { - if (this.connectivityState === ConnectivityState.READY) { - let session: http2.ClientHttp2Session = - (this.subChannel as http2.ClientHttp2Session); - stream.attachHttp2Stream(session.request(headers)); - } else { - /* In this case, we lost the connection while finalizing metadata. - * That should be very unusual */ - setImmediate(() => { - this.startHttp2Stream(methodName, stream, metadata); - }); - } - } - }, (error) => { - stream.cancelWithStatus(Status.UNKNOWN, "Failed to generate metadata"); - }); + finalMetadata.then( + (metadataValue) => { + let headers = metadataValue.toHttp2Headers(); + headers[HTTP2_HEADER_AUTHORITY] = this.address.hostname; + headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc'; + headers[HTTP2_HEADER_METHOD] = 'POST'; + headers[HTTP2_HEADER_PATH] = methodName; + headers[HTTP2_HEADER_TE] = 'trailers'; + if (stream.getStatus() === null) { + if (this.connectivityState === ConnectivityState.READY) { + let session: http2.ClientHttp2Session = + (this.subChannel as http2.ClientHttp2Session); + stream.attachHttp2Stream(session.request(headers)); + } else { + /* In this case, we lost the connection while finalizing + * metadata. That should be very unusual */ + setImmediate(() => { + this.startHttp2Stream(methodName, stream, metadata); + }); + } + } + }, + (error) => { + stream.cancelWithStatus( + Status.UNKNOWN, 'Failed to generate metadata'); + }); }); } - createStream(methodName: string, metadata: Metadata, options: CallOptions): CallStream { + createStream(methodName: string, metadata: Metadata, options: CallOptions): + CallStream { if (this.connectivityState === ConnectivityState.SHUTDOWN) { throw new Error('Channel has been shut down'); } @@ -159,8 +166,9 @@ export class Http2Channel extends EventEmitter implements Channel { deadline: options.deadline === undefined ? Infinity : options.deadline, credentials: options.credentials || CallCredentials.createEmpty(), flags: options.flags || 0 - } - let stream: Http2CallStream = new Http2CallStream(methodName, finalOptions, this.filterStackFactory); + }; + let stream: Http2CallStream = + new Http2CallStream(methodName, finalOptions, this.filterStackFactory); this.startHttp2Stream(methodName, stream, metadata); return stream; } @@ -178,7 +186,7 @@ export class Http2Channel extends EventEmitter implements Channel { } } - getConnectivityState(): ConnectivityState{ + getConnectivityState(): ConnectivityState { return this.connectivityState; } diff --git a/src/client.ts b/src/client.ts index 883efbee..59a84d24 100644 --- a/src/client.ts +++ b/src/client.ts @@ -3,7 +3,7 @@ import {URL} from 'url'; import {ClientDuplexStream, ClientDuplexStreamImpl, ClientReadableStream, ClientReadableStreamImpl, ClientUnaryCall, ClientUnaryCallImpl, ClientWritableStream, ClientWritableStreamImpl, ServiceError, ServiceErrorImpl} from './call'; import {CallOptions, CallStream, StatusObject, WriteObject} from './call-stream'; -import {Channel, Http2Channel, ChannelOptions} from './channel'; +import {Channel, ChannelOptions, Http2Channel} from './channel'; import {ChannelCredentials} from './channel-credentials'; import {Status} from './constants'; import {Metadata} from './metadata'; @@ -32,13 +32,13 @@ export class Client { } waitForReady(deadline: Date|number, callback: (error: Error|null) => void): - void { - let cb : (error: Error|null) => void = once(callback); - let callbackCalled: boolean = false; + void { + let cb: (error: Error|null) => void = once(callback); + let callbackCalled = false; this.channel.connect(() => { cb(null); }); - if (deadline != Infinity) { + if (deadline !== Infinity) { let timeout: number; let now: number = (new Date).getTime(); if (deadline instanceof Date) { diff --git a/src/compression-filter.ts b/src/compression-filter.ts index 6ec5598e..134662dc 100644 --- a/src/compression-filter.ts +++ b/src/compression-filter.ts @@ -1,10 +1,9 @@ -import {CallStream} from './call-stream' -import {Channel} from './channel' -import {Filter, BaseFilter, FilterFactory} from './filter' -import {Metadata} from './metadata' +import {CallStream} from './call-stream'; +import {Channel} from './channel'; +import {BaseFilter, Filter, FilterFactory} from './filter'; +import {Metadata} from './metadata'; export class CompressionFilter extends BaseFilter implements Filter { - async sendMetadata(metadata: Promise): Promise { const headers: Metadata = await metadata; headers.set('grpc-encoding', 'identity'); @@ -20,8 +19,9 @@ export class CompressionFilter extends BaseFilter implements Filter { } } -export class CompressionFilterFactory implements FilterFactory { - constructor(channel: Channel) {} +export class CompressionFilterFactory implements + FilterFactory { + constructor(private readonly channel: Channel) {} createFilter(callStream: CallStream): CompressionFilter { return new CompressionFilter(); } diff --git a/src/deadline-filter.ts b/src/deadline-filter.ts index 5796162e..66b18107 100644 --- a/src/deadline-filter.ts +++ b/src/deadline-filter.ts @@ -1,19 +1,17 @@ -import {CallStream} from './call-stream' -import {Channel, Http2Channel} from './channel' -import {Filter, BaseFilter, FilterFactory} from './filter' -import {Status} from './constants' -import {Metadata} from './metadata' +import {CallStream} from './call-stream'; +import {Channel, Http2Channel} from './channel'; +import {Status} from './constants'; +import {BaseFilter, Filter, FilterFactory} from './filter'; +import {Metadata} from './metadata'; -const units: [string, number][] = [ - ['m', 1], - ['S', 1000], - ['M', 60 * 1000], - ['H', 60 * 60 * 1000] -] +const units: [string, number][] = + [['m', 1], ['S', 1000], ['M', 60 * 1000], ['H', 60 * 60 * 1000]]; export class DeadlineFilter extends BaseFilter implements Filter { private deadline: number; - constructor(private readonly channel: Http2Channel, private readonly callStream: CallStream) { + constructor( + private readonly channel: Http2Channel, + private readonly callStream: CallStream) { super(); let callDeadline = callStream.getDeadline(); if (callDeadline instanceof Date) { @@ -28,7 +26,8 @@ export class DeadlineFilter extends BaseFilter implements Filter { } if (this.deadline !== Infinity) { setTimeout(() => { - callStream.cancelWithStatus(Status.DEADLINE_EXCEEDED, 'Deadline exceeded'); + callStream.cancelWithStatus( + Status.DEADLINE_EXCEEDED, 'Deadline exceeded'); }, timeout); } } @@ -37,19 +36,20 @@ export class DeadlineFilter extends BaseFilter implements Filter { if (this.deadline === Infinity) { return await metadata; } - let timeoutString : Promise = new Promise((resolve, reject) => { - this.channel.connect(() => { - let now = (new Date()).getTime(); - let timeoutMs = this.deadline - now; - for (let [unit, factor] of units) { - let amount = timeoutMs / factor; - if (amount < 1e8) { - resolve(String(Math.ceil(amount)) + unit); - return; - } - } - }); - }); + let timeoutString: Promise = + new Promise((resolve, reject) => { + this.channel.connect(() => { + let now = (new Date()).getTime(); + let timeoutMs = this.deadline - now; + for (let [unit, factor] of units) { + let amount = timeoutMs / factor; + if (amount < 1e8) { + resolve(String(Math.ceil(amount)) + unit); + return; + } + } + }); + }); let finalMetadata = await metadata; finalMetadata.set('grpc-timeout', await timeoutString); return finalMetadata; diff --git a/src/filter-stack.ts b/src/filter-stack.ts index 97e94c5d..7a661692 100644 --- a/src/filter-stack.ts +++ b/src/filter-stack.ts @@ -1,21 +1,26 @@ import {flow, flowRight, map} from 'lodash'; -import {Metadata} from './metadata'; -import {CallStream, StatusObject} from './call-stream' + +import {CallStream, StatusObject} from './call-stream'; import {Filter, FilterFactory} from './filter'; +import {Metadata} from './metadata'; export class FilterStack implements Filter { constructor(private readonly filters: Filter[]) {} sendMetadata(metadata: Promise) { - return flow(map(this.filters, (filter) => filter.sendMetadata.bind(filter)))(metadata); + return flow(map( + this.filters, (filter) => filter.sendMetadata.bind(filter)))(metadata); } receiveMetadata(metadata: Promise) { - return flowRight(map(this.filters, (filter) => filter.receiveMetadata.bind(filter)))(metadata); + return flowRight( + map(this.filters, (filter) => filter.receiveMetadata.bind(filter)))( + metadata); } receiveTrailers(status: Promise): Promise { - return flowRight(map(this.filters, (filter) => filter.receiveTrailers.bind(filter)))(status); + return flowRight(map( + this.filters, (filter) => filter.receiveTrailers.bind(filter)))(status); } } @@ -23,6 +28,7 @@ export class FilterStackFactory implements FilterFactory { constructor(private readonly factories: FilterFactory[]) {} createFilter(callStream: CallStream): FilterStack { - return new FilterStack(map(this.factories, (factory) => factory.createFilter(callStream))); + return new FilterStack( + map(this.factories, (factory) => factory.createFilter(callStream))); } } diff --git a/src/filter.ts b/src/filter.ts index f0f2affe..bb4e0a93 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -1,9 +1,10 @@ -import {Metadata} from './metadata' -import {StatusObject, CallStream} from './call-stream' +import {CallStream, StatusObject} from './call-stream'; +import {Metadata} from './metadata'; /** * Filter classes represent related per-call logic and state that is primarily - * used to modify incoming and outgoing data */ + * used to modify incoming and outgoing data + */ export interface Filter { sendMetadata(metadata: Promise): Promise; diff --git a/src/metadata.ts b/src/metadata.ts index 7017a2f6..c4bb2568 100644 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -1,11 +1,9 @@ -import { forOwn } from 'lodash'; import * as http2 from 'http2'; +import {forOwn} from 'lodash'; export type MetadataValue = string | Buffer; -export interface MetadataObject { - [key: string]: Array; -} +export interface MetadataObject { [key: string]: Array; } function cloneMetadataObject(repr: MetadataObject): MetadataObject { const result: MetadataObject = {}; @@ -54,8 +52,9 @@ function validate(key: string, value?: MetadataValue): void { 'keys that don\'t end with \'-bin\' must have String values'); } if (!isLegalNonBinaryValue(value)) { - throw new Error('Metadata string value "' + value + - '" contains illegal characters'); + throw new Error( + 'Metadata string value "' + value + + '" contains illegal characters'); } } } @@ -66,7 +65,6 @@ function validate(key: string, value?: MetadataValue): void { */ export class Metadata { protected internalRepr: MetadataObject = {}; - constructor() {} /** * Sets the given value for the given key by replacing any other values @@ -130,10 +128,10 @@ export class Metadata { * This reflects the most common way that people will want to see metadata. * @return A key/value mapping of the metadata. */ - getMap(): { [key: string]: MetadataValue } { - const result: { [key: string]: MetadataValue } = {}; + getMap(): {[key: string]: MetadataValue} { + const result: {[key: string]: MetadataValue} = {}; forOwn(this.internalRepr, (values, key) => { - if(values.length > 0) { + if (values.length > 0) { const v = values[0]; result[key] = v instanceof Buffer ? v.slice() : v; } @@ -195,7 +193,7 @@ export class Metadata { if (Array.isArray(values)) { values.forEach((value) => { result.add(key, Buffer.from(value, 'base64')); - }) + }); } else { result.add(key, Buffer.from(values, 'base64')); } @@ -203,7 +201,7 @@ export class Metadata { if (Array.isArray(values)) { values.forEach((value) => { result.add(key, value); - }) + }); } else { result.add(key, values); } diff --git a/src/object-stream.ts b/src/object-stream.ts index 356dd6da..bed77f04 100644 --- a/src/object-stream.ts +++ b/src/object-stream.ts @@ -1,14 +1,14 @@ -import { Readable, Writable, Duplex } from 'stream'; +import {Duplex, Readable, Writable} from 'stream'; export interface IntermediateObjectReadable extends Readable { - read(size?: number): any & T; + read(size?: number): any&T; } export interface ObjectReadable extends IntermediateObjectReadable { read(size?: number): T; addListener(event: string, listener: Function): this; - emit(event: string | symbol, ...args: any[]): boolean; + emit(event: string|symbol, ...args: any[]): boolean; on(event: string, listener: Function): this; once(event: string, listener: Function): this; prependListener(event: string, listener: Function): this; @@ -25,13 +25,13 @@ export interface ObjectReadable extends IntermediateObjectReadable { } export interface IntermediateObjectWritable extends Writable { - _write(chunk: any & T, encoding: string, callback: Function): void; - write(chunk: any & T, cb?: Function): boolean; - write(chunk: any & T, encoding?: any, cb?: Function): boolean; + _write(chunk: any&T, encoding: string, callback: Function): void; + write(chunk: any&T, cb?: Function): boolean; + write(chunk: any&T, encoding?: any, cb?: Function): boolean; setDefaultEncoding(encoding: string): this; end(): void; - end(chunk: any & T, cb?: Function): void; - end(chunk: any & T, encoding?: any, cb?: Function): void; + end(chunk: any&T, cb?: Function): void; + end(chunk: any&T, encoding?: any, cb?: Function): void; } export interface ObjectWritable extends IntermediateObjectWritable { @@ -44,7 +44,8 @@ export interface ObjectWritable extends IntermediateObjectWritable { end(chunk: T, encoding?: any, cb?: Function): void; } -export interface ObjectDuplex extends Duplex, ObjectWritable, ObjectReadable { +export interface ObjectDuplex extends Duplex, ObjectWritable, + ObjectReadable { read(size?: number): U; _write(chunk: T, encoding: string, callback: Function): void; @@ -56,7 +57,7 @@ export interface ObjectDuplex extends Duplex, ObjectWritable, ObjectRea addListener(event: string, listener: Function): this; - emit(event: string | symbol, ...args: any[]): boolean; + emit(event: string|symbol, ...args: any[]): boolean; on(event: string, listener: Function): this; once(event: string, listener: Function): this; prependListener(event: string, listener: Function): this; diff --git a/test/common.ts b/test/common.ts index fbb6a2a1..7fbab022 100644 --- a/test/common.ts +++ b/test/common.ts @@ -9,8 +9,8 @@ export namespace assert2 { try { return fn(); } catch (e) { - assert.throws(() => { throw e }); - throw e; // for type safety only + assert.throws(() => {throw e}); + throw e; // for type safety only } } } diff --git a/test/test-call-credentials.ts b/test/test-call-credentials.ts index feed84c5..ab516ed9 100644 --- a/test/test-call-credentials.ts +++ b/test/test-call-credentials.ts @@ -1,11 +1,12 @@ -import { Metadata } from '../src/metadata'; -import { CallCredentials, CallMetadataGenerator } from '../src/call-credentials'; import * as assert from 'assert'; +import {CallCredentials, CallMetadataGenerator} from '../src/call-credentials'; +import {Metadata} from '../src/metadata'; + // Metadata generators function makeGenerator(props: Array): CallMetadataGenerator { - return (options: { [propName: string]: string }, cb) => { + return (options: {[propName: string]: string}, cb) => { const metadata: Metadata = new Metadata(); props.forEach((prop) => { if (options[prop]) { @@ -13,110 +14,111 @@ function makeGenerator(props: Array): CallMetadataGenerator { } }); cb(null, metadata); - } + }; } function makeAfterMsElapsedGenerator(ms: number): CallMetadataGenerator { - return (_options, cb) => { + return (options, cb) => { const metadata = new Metadata(); metadata.add('msElapsed', `${ms}`); setTimeout(() => cb(null, metadata), ms); }; -}; +} const generateFromName: CallMetadataGenerator = makeGenerator(['name']); -const generateWithError: CallMetadataGenerator = (_options, cb) => - cb(new Error()); +const generateWithError: CallMetadataGenerator = (options, cb) => + cb(new Error()); // Tests describe('CallCredentials', () => { describe('createFromMetadataGenerator', () => { it('should accept a metadata generator', () => { - assert.doesNotThrow(() => - CallCredentials.createFromMetadataGenerator(generateFromName)); + assert.doesNotThrow( + () => CallCredentials.createFromMetadataGenerator(generateFromName)); }); }); describe('compose', () => { it('should accept a CallCredentials object and return a new object', () => { - const callCredentials1 = CallCredentials.createFromMetadataGenerator(generateFromName); - const callCredentials2 = CallCredentials.createFromMetadataGenerator(generateFromName); + const callCredentials1 = + CallCredentials.createFromMetadataGenerator(generateFromName); + const callCredentials2 = + CallCredentials.createFromMetadataGenerator(generateFromName); const combinedCredentials = callCredentials1.compose(callCredentials2); assert.notEqual(combinedCredentials, callCredentials1); assert.notEqual(combinedCredentials, callCredentials2); }); it('should be chainable', () => { - const callCredentials1 = CallCredentials.createFromMetadataGenerator(generateFromName); - const callCredentials2 = CallCredentials.createFromMetadataGenerator(generateFromName); + const callCredentials1 = + CallCredentials.createFromMetadataGenerator(generateFromName); + const callCredentials2 = + CallCredentials.createFromMetadataGenerator(generateFromName); assert.doesNotThrow(() => { callCredentials1.compose(callCredentials2) - .compose(callCredentials2) - .compose(callCredentials2); + .compose(callCredentials2) + .compose(callCredentials2); }); }); }); describe('generateMetadata', () => { it('should call the function passed to createFromMetadataGenerator', - async () => { - const callCredentials = CallCredentials.createFromMetadataGenerator(generateFromName); - let metadata: Metadata; - try { - metadata = await callCredentials.generateMetadata({name: 'foo'}); - } catch (err) { - throw err; - } - assert.deepEqual(metadata.get('name'), ['foo']); - } - ); + async () => { + const callCredentials = + CallCredentials.createFromMetadataGenerator(generateFromName); + let metadata: Metadata; + try { + metadata = await callCredentials.generateMetadata({name: 'foo'}); + } catch (err) { + throw err; + } + assert.deepEqual(metadata.get('name'), ['foo']); + }); it('should emit an error if the associated metadataGenerator does', - async () => { - const callCredentials = CallCredentials.createFromMetadataGenerator( - generateWithError); - let metadata: Metadata | null = null; - try { - metadata = await callCredentials.generateMetadata({}); - } catch (err) { - assert.ok(err instanceof Error); - } - assert.strictEqual(metadata, null); - } - ); + async () => { + const callCredentials = + CallCredentials.createFromMetadataGenerator(generateWithError); + let metadata: Metadata|null = null; + try { + metadata = await callCredentials.generateMetadata({}); + } catch (err) { + assert.ok(err instanceof Error); + } + assert.strictEqual(metadata, null); + }); it('should combine metadata from multiple generators', async () => { const [callCreds1, callCreds2, callCreds3, callCreds4] = - [50, 100, 150, 200].map((ms) => { - const generator: CallMetadataGenerator = - makeAfterMsElapsedGenerator(ms); - return CallCredentials.createFromMetadataGenerator(generator); - }); - const testCases = [{ - credentials: callCreds1 - .compose(callCreds2) - .compose(callCreds3) - .compose(callCreds4), + [50, 100, 150, 200].map((ms) => { + const generator: CallMetadataGenerator = + makeAfterMsElapsedGenerator(ms); + return CallCredentials.createFromMetadataGenerator(generator); + }); + const testCases = [ + { + credentials: callCreds1.compose(callCreds2) + .compose(callCreds3) + .compose(callCreds4), expected: ['50', '100', '150', '200'] - }, { - credentials: callCreds4 - .compose(callCreds3 - .compose(callCreds2 - .compose(callCreds1))), + }, + { + credentials: callCreds4.compose( + callCreds3.compose(callCreds2.compose(callCreds1))), expected: ['200', '150', '100', '50'] - }, { - credentials: callCreds3 - .compose(callCreds4 - .compose(callCreds1) - .compose(callCreds2)), + }, + { + credentials: callCreds3.compose( + callCreds4.compose(callCreds1).compose(callCreds2)), expected: ['150', '200', '50', '100'] } ]; const options = {}; // Try each test case and make sure the msElapsed field is as expected await Promise.all(testCases.map(async (testCase) => { - const { credentials, expected } = testCase; + const {credentials, expected} = testCase; let metadata: Metadata; try { metadata = await credentials.generateMetadata(options); diff --git a/test/test-channel-credentials.ts b/test/test-channel-credentials.ts index 2dccce5b..abd188bc 100644 --- a/test/test-channel-credentials.ts +++ b/test/test-channel-credentials.ts @@ -1,9 +1,11 @@ import * as assert from 'assert'; -import { CallCredentials } from '../src/call-credentials'; -import { ChannelCredentials } from '../src/channel-credentials'; -import { mockFunction, assert2 } from './common'; import * as fs from 'fs'; -import { promisify } from 'util'; +import {promisify} from 'util'; + +import {CallCredentials} from '../src/call-credentials'; +import {ChannelCredentials} from '../src/channel-credentials'; + +import {assert2, mockFunction} from './common'; class CallCredentialsMock implements CallCredentials { child: CallCredentialsMock; @@ -32,66 +34,61 @@ class CallCredentialsMock implements CallCredentials { const readFile: (...args: any[]) => Promise = promisify(fs.readFile); // A promise which resolves to loaded files in the form { ca, key, cert } -const pFixtures = Promise.all([ - 'ca.pem', - 'server1.key', - 'server1.pem' - ].map((file) => readFile(`test/fixtures/${file}`)) -).then((result) => { - return { - ca: result[0], - key: result[1], - cert: result[2] - }; -}); +const pFixtures = Promise + .all(['ca.pem', 'server1.key', 'server1.pem'].map( + (file) => readFile(`test/fixtures/${file}`))) + .then((result) => { + return {ca: result[0], key: result[1], cert: result[2]}; + }); describe('ChannelCredentials Implementation', () => { describe('createInsecure', () => { - it('should return a ChannelCredentials object with no associated secure context', () => { - const creds = assert2.noThrowAndReturn( - () => ChannelCredentials.createInsecure()); - assert.ok(!creds.getSecureContext()); - }); + it('should return a ChannelCredentials object with no associated secure context', + () => { + const creds = assert2.noThrowAndReturn( + () => ChannelCredentials.createInsecure()); + assert.ok(!creds.getSecureContext()); + }); }); describe('createSsl', () => { it('should work when given no arguments', () => { - const creds: ChannelCredentials = assert2.noThrowAndReturn( - () => ChannelCredentials.createSsl()); + const creds: ChannelCredentials = + assert2.noThrowAndReturn(() => ChannelCredentials.createSsl()); assert.ok(!!creds.getSecureContext()); }); it('should work with just a CA override', async () => { - const { ca } = await pFixtures; - const creds = assert2.noThrowAndReturn( - () => ChannelCredentials.createSsl(ca)); + const {ca} = await pFixtures; + const creds = + assert2.noThrowAndReturn(() => ChannelCredentials.createSsl(ca)); assert.ok(!!creds.getSecureContext()); }); it('should work with just a private key and cert chain', async () => { - const { key, cert } = await pFixtures; + const {key, cert} = await pFixtures; const creds = assert2.noThrowAndReturn( - () => ChannelCredentials.createSsl(null, key, cert)); + () => ChannelCredentials.createSsl(null, key, cert)); assert.ok(!!creds.getSecureContext()); }); it('should work with all three parameters specified', async () => { - const { ca, key, cert } = await pFixtures; + const {ca, key, cert} = await pFixtures; const creds = assert2.noThrowAndReturn( - () => ChannelCredentials.createSsl(ca, key, cert)); + () => ChannelCredentials.createSsl(ca, key, cert)); assert.ok(!!creds.getSecureContext()); }); it('should throw if just one of private key and cert chain are missing', - async () => { - const { ca, key, cert } = await pFixtures; - assert.throws(() => ChannelCredentials.createSsl(ca, key)); - assert.throws(() => ChannelCredentials.createSsl(ca, key, null)); - assert.throws(() => ChannelCredentials.createSsl(ca, null, cert)); - assert.throws(() => ChannelCredentials.createSsl(null, key)); - assert.throws(() => ChannelCredentials.createSsl(null, key, null)); - assert.throws(() => ChannelCredentials.createSsl(null, null, cert)); - }); + async () => { + const {ca, key, cert} = await pFixtures; + assert.throws(() => ChannelCredentials.createSsl(ca, key)); + assert.throws(() => ChannelCredentials.createSsl(ca, key, null)); + assert.throws(() => ChannelCredentials.createSsl(ca, null, cert)); + assert.throws(() => ChannelCredentials.createSsl(null, key)); + assert.throws(() => ChannelCredentials.createSsl(null, key, null)); + assert.throws(() => ChannelCredentials.createSsl(null, null, cert)); + }); }); describe('compose', () => { @@ -99,8 +96,7 @@ describe('ChannelCredentials Implementation', () => { const channelCreds = ChannelCredentials.createSsl(); const callCreds = new CallCredentialsMock(); const composedChannelCreds = channelCreds.compose(callCreds); - assert.strictEqual(composedChannelCreds.getCallCredentials(), - callCreds); + assert.strictEqual(composedChannelCreds.getCallCredentials(), callCreds); }); it('should be chainable', () => { @@ -108,12 +104,12 @@ describe('ChannelCredentials Implementation', () => { const callCreds2 = new CallCredentialsMock(); // Associate both call credentials with channelCreds const composedChannelCreds = ChannelCredentials.createSsl() - .compose(callCreds1) - .compose(callCreds2); + .compose(callCreds1) + .compose(callCreds2); // Build a mock object that should be an identical copy const composedCallCreds = callCreds1.compose(callCreds2); assert.ok(composedCallCreds.isEqual( - composedChannelCreds.getCallCredentials() as CallCredentialsMock)); + composedChannelCreds.getCallCredentials() as CallCredentialsMock)); }); }); }); diff --git a/test/test-metadata.ts b/test/test-metadata.ts index f884c14d..6e227532 100644 --- a/test/test-metadata.ts +++ b/test/test-metadata.ts @@ -1,31 +1,30 @@ import * as assert from 'assert'; import * as http2 from 'http2'; -import { range } from 'lodash'; -import * as metadata from '../src/metadata'; +import {range} from 'lodash'; +import {Metadata} from '../src/metadata'; -class Metadata extends metadata.Metadata { +class TestMetadata extends Metadata { getInternalRepresentation() { return this.internalRepr; } - static fromHttp2Headers(headers: http2.IncomingHttpHeaders): Metadata { - const result = metadata.Metadata.fromHttp2Headers(headers) as Metadata; + static fromHttp2Headers(headers: http2.IncomingHttpHeaders): TestMetadata { + const result = Metadata.fromHttp2Headers(headers) as TestMetadata; result.getInternalRepresentation = - Metadata.prototype.getInternalRepresentation; + TestMetadata.prototype.getInternalRepresentation; return result; } } const validKeyChars = '0123456789abcdefghijklmnopqrstuvwxyz_-.'; -const validNonBinValueChars = range(0x20, 0x7f) - .map(code => String.fromCharCode(code)) - .join(''); +const validNonBinValueChars = + range(0x20, 0x7f).map(code => String.fromCharCode(code)).join(''); describe('Metadata', () => { - let metadata: Metadata; + let metadata: TestMetadata; beforeEach(() => { - metadata = new Metadata(); + metadata = new TestMetadata(); }); describe('set', () => { @@ -178,10 +177,8 @@ describe('Metadata', () => { metadata.add('Key2', 'value2'); metadata.add('KEY3', 'value3a'); metadata.add('KEY3', 'value3b'); - assert.deepEqual(metadata.getMap(), - {key1: 'value1', - key2: 'value2', - key3: 'value3a'}); + assert.deepEqual( + metadata.getMap(), {key1: 'value1', key2: 'value2', key3: 'value3a'}); }); }); @@ -213,7 +210,7 @@ describe('Metadata', () => { metadata.add('Key2', 'value2a'); metadata.add('KEY3', 'value3a'); metadata.add('key4', 'value4'); - const metadata2 = new Metadata(); + const metadata2 = new TestMetadata(); metadata2.add('KEY1', 'value1'); metadata2.add('key2', 'value2b'); metadata2.add('key3', 'value3b'); @@ -246,8 +243,7 @@ describe('Metadata', () => { key2: ['value2'], key3: ['value3a', 'value3b'], 'key-bin': [ - 'AAECAwQFBgcICQoLDA0ODw==', - 'EBESExQVFhcYGRobHB0eHw==', + 'AAECAwQFBgcICQoLDA0ODw==', 'EBESExQVFhcYGRobHB0eHw==', 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=' ] }); @@ -265,27 +261,25 @@ describe('Metadata', () => { key2: ['value2'], key3: ['value3a', 'value3b'], 'key-bin': [ - 'AAECAwQFBgcICQoLDA0ODw==', - 'EBESExQVFhcYGRobHB0eHw==', + 'AAECAwQFBgcICQoLDA0ODw==', 'EBESExQVFhcYGRobHB0eHw==', 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=' ] }; - const metadataFromHeaders = Metadata.fromHttp2Headers(headers); + const metadataFromHeaders = TestMetadata.fromHttp2Headers(headers); const internalRepr = metadataFromHeaders.getInternalRepresentation(); assert.deepEqual(internalRepr, { key1: ['value1'], key2: ['value2'], key3: ['value3a', 'value3b'], 'key-bin': [ - Buffer.from(range(0, 16)), - Buffer.from(range(16, 32)), + Buffer.from(range(0, 16)), Buffer.from(range(16, 32)), Buffer.from(range(0, 32)) ] }); }); it('creates an empty Metadata object from empty headers', () => { - const metadataFromHeaders = Metadata.fromHttp2Headers({}); + const metadataFromHeaders = TestMetadata.fromHttp2Headers({}); const internalRepr = metadataFromHeaders.getInternalRepresentation(); assert.deepEqual(internalRepr, {}); });