Skip to content

Commit

Permalink
Allow GrpcWebFetchTransport to use a custom fetch function
Browse files Browse the repository at this point in the history
  • Loading branch information
jcready committed Aug 10, 2023
1 parent 3a7ce47 commit c308a30
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 2 deletions.
150 changes: 150 additions & 0 deletions packages/grpcweb-transport/spec/grpc-web-transport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,72 @@ describe('GrpcWebFetchTransport', () => {
globalThis.fetch = fetch;
});

describe('with custom fetch() function', () => {
describe('in the call options', () => {
it('calls the custom fetch function instead of globalThis.fetch()', async () => {
const optFetch = jasmine.createSpy('optFetch', (((...args: any[]): any => Promise.resolve() as any) as typeof originalFetch));
const { next, body } = makeWhatWgStream();
const res = getResponse({ body });
const ret = new Deferred<typeof res>(true);
fetch.and.returnValue(ret.promise);
optFetch.and.returnValue(ret.promise);
const call = transport.unary(methodInfo, callInput, { ...callOptions, fetch: optFetch });

// Attach promise handlers
const spy = {
headers: jasmine.createSpy('call.headers'),
response: jasmine.createSpy('call.response'),
status: jasmine.createSpy('call.status'),
trailers: jasmine.createSpy('call.trailers'),
};

for (let k in spy) {
// @ts-ignore
call[k].then(spy[k]);
}

const callPromiseSpy = jasmine.createSpy('call.then');
call.then(callPromiseSpy);

// custom fetch called with expected parameters
expect(fetch).toHaveBeenCalledTimes(0);
expect(optFetch).toHaveBeenCalledTimes(1);
});
});
describe('in the GrpcWebFetchTransport constructor options', () => {
it('calls the custom fetch function instead of globalThis.fetch()', async () => {
const optFetch = jasmine.createSpy('optFetch', (((...args: any[]): any => Promise.resolve() as any) as typeof originalFetch));
const transport = new GrpcWebFetchTransport({...base, fetch: optFetch});
const { next, body } = makeWhatWgStream();
const res = getResponse({ body });
const ret = new Deferred<typeof res>(true);
fetch.and.returnValue(ret.promise);
optFetch.and.returnValue(ret.promise);
const call = transport.unary(methodInfo, callInput, transport.mergeOptions(callOptions));

// Attach promise handlers
const spy = {
headers: jasmine.createSpy('call.headers'),
response: jasmine.createSpy('call.response'),
status: jasmine.createSpy('call.status'),
trailers: jasmine.createSpy('call.trailers'),
};

for (let k in spy) {
// @ts-ignore
call[k].then(spy[k]);
}

const callPromiseSpy = jasmine.createSpy('call.then');
call.then(callPromiseSpy);

// custom fetch called with expected parameters
expect(fetch).toHaveBeenCalledTimes(0);
expect(optFetch).toHaveBeenCalledTimes(1);
});
});
});

it('handles a successful response', async () => {
const { next, body } = makeWhatWgStream();
const res = getResponse({ body });
Expand Down Expand Up @@ -459,6 +525,90 @@ describe('GrpcWebFetchTransport', () => {
globalThis.fetch = fetch;
});

describe('with custom fetch() function', () => {
describe('in the call options', () => {
it('calls the custom fetch function instead of globalThis.fetch()', async () => {
const optFetch = jasmine.createSpy('optFetch', (((...args: any[]): any => Promise.resolve() as any) as typeof originalFetch));
const { next, body } = makeWhatWgStream();
const res = getResponse({ body });
const ret = new Deferred<typeof res>(true);
fetch.and.returnValue(ret.promise);
optFetch.and.returnValue(ret.promise);
const call = transport.serverStreaming(methodInfo, callInput, { ...callOptions, fetch: optFetch });

const responseIterator = call.responses[Symbol.asyncIterator]();

// Attach promise handlers
const spy = {
headers: jasmine.createSpy('call.headers'),
status: jasmine.createSpy('call.status'),
trailers: jasmine.createSpy('call.trailers'),
};

for (let k in spy) {
// @ts-ignore
call[k].then(spy[k]);
}

const responsesIteratorSpy = jasmine.createSpy<(res: IteratorResult<ResponseMessage>) => void>('call.responses.iterator');

let nextIteratorResult = responseIterator.next().then(function chunkThen(res) {
responsesIteratorSpy(res);
nextIteratorResult = responseIterator.next().then(chunkThen);
return res;
});

const callPromiseSpy = jasmine.createSpy('call.then');
call.then(callPromiseSpy);

// Fetch called with expected parameters
expect(fetch).toHaveBeenCalledTimes(0);
expect(optFetch).toHaveBeenCalledTimes(1);
});
});
describe('in the GrpcWebFetchTransport constructor options', () => {
it('calls the custom fetch function instead of globalThis.fetch()', async () => {
const optFetch = jasmine.createSpy('optFetch', (((...args: any[]): any => Promise.resolve() as any) as typeof originalFetch));
const transport = new GrpcWebFetchTransport({...base, fetch: optFetch});
const { next, body } = makeWhatWgStream();
const res = getResponse({ body });
const ret = new Deferred<typeof res>(true);
fetch.and.returnValue(ret.promise);
optFetch.and.returnValue(ret.promise);
const call = transport.serverStreaming(methodInfo, callInput, transport.mergeOptions(callOptions));

const responseIterator = call.responses[Symbol.asyncIterator]();

// Attach promise handlers
const spy = {
headers: jasmine.createSpy('call.headers'),
status: jasmine.createSpy('call.status'),
trailers: jasmine.createSpy('call.trailers'),
};

for (let k in spy) {
// @ts-ignore
call[k].then(spy[k]);
}

const responsesIteratorSpy = jasmine.createSpy<(res: IteratorResult<ResponseMessage>) => void>('call.responses.iterator');

let nextIteratorResult = responseIterator.next().then(function chunkThen(res) {
responsesIteratorSpy(res);
nextIteratorResult = responseIterator.next().then(chunkThen);
return res;
});

const callPromiseSpy = jasmine.createSpy('call.then');
call.then(callPromiseSpy);

// Fetch called with expected parameters
expect(fetch).toHaveBeenCalledTimes(0);
expect(optFetch).toHaveBeenCalledTimes(1);
});
});
});

it('handles a successful serverStreaming call', async () => {
const { next, body } = makeWhatWgStream();
const res = getResponse({ body });
Expand Down
4 changes: 4 additions & 0 deletions packages/grpcweb-transport/src/grpc-web-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ export interface GrpcWebOptions extends RpcOptions {
*/
fetchInit?: Omit<RequestInit, 'body' | 'headers' | 'method' | 'signal'>;

/**
* A `fetch` function to use in place of `globalThis.fetch`
*/
fetch?: typeof fetch;
}
6 changes: 4 additions & 2 deletions packages/grpcweb-transport/src/grpc-web-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export class GrpcWebFetchTransport implements RpcTransport {
let
opt = options as GrpcWebOptions,
format = opt.format ?? 'text',
fetch = opt.fetch ?? globalThis.fetch,
fetchInit = opt.fetchInit ?? {},
url = this.makeUrl(method, opt),
inputBytes = method.I.toBinary(input, opt.binaryOptions),
Expand All @@ -100,7 +101,7 @@ export class GrpcWebFetchTransport implements RpcTransport {
maybeTrailer: RpcMetadata | undefined,
defTrailer = new Deferred<RpcMetadata>();

globalThis.fetch(url, {
fetch(url, {
...fetchInit,
method: 'POST',
headers: createGrpcWebRequestHeader(new globalThis.Headers(), format, opt.timeout, opt.meta),
Expand Down Expand Up @@ -197,6 +198,7 @@ export class GrpcWebFetchTransport implements RpcTransport {
let
opt = options as GrpcWebOptions,
format = opt.format ?? 'text',
fetch = opt.fetch ?? globalThis.fetch,
fetchInit = opt.fetchInit ?? {},
url = this.makeUrl(method, opt),
inputBytes = method.I.toBinary(input, opt.binaryOptions),
Expand All @@ -208,7 +210,7 @@ export class GrpcWebFetchTransport implements RpcTransport {
maybeTrailer: RpcMetadata | undefined,
defTrailer = new Deferred<RpcMetadata>();

globalThis.fetch(url, {
fetch(url, {
...fetchInit,
method: 'POST',
headers: createGrpcWebRequestHeader(new globalThis.Headers(), format, opt.timeout, opt.meta),
Expand Down

0 comments on commit c308a30

Please sign in to comment.