Skip to content

Commit dd2cebc

Browse files
committed
Support HttpContext in LinkOptions and Context
1 parent d8306e5 commit dd2cebc

File tree

6 files changed

+131
-11
lines changed

6 files changed

+131
-11
lines changed

packages/apollo-angular/http/src/http-batch-link.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { print } from 'graphql';
22
import { Observable } from 'rxjs';
3-
import { HttpClient, HttpHeaders } from '@angular/common/http';
3+
import { HttpClient, HttpContext, HttpHeaders } from '@angular/common/http';
44
import { Injectable } from '@angular/core';
55
import { ApolloLink } from '@apollo/client';
66
import { BatchLink } from '@apollo/client/link/batch';
77
import type { HttpLink } from './http-link';
88
import { Body, Context, OperationPrinter, Request } from './types';
9-
import { createHeadersWithClientAwareness, fetch, mergeHeaders, prioritize } from './utils';
9+
import { createHeadersWithClientAwareness, fetch, mergeHeaders, mergeHttpContext, prioritize } from './utils';
1010

1111
export declare namespace HttpBatchLink {
1212
export type Options = {
@@ -61,6 +61,7 @@ export class HttpBatchLinkHandler extends ApolloLink {
6161
return new Observable((observer: any) => {
6262
const body = this.createBody(operations);
6363
const headers = this.createHeaders(operations);
64+
const context = this.createHttpContext(operations);
6465
const { method, uri, withCredentials } = this.createOptions(operations);
6566

6667
if (typeof uri === 'function') {
@@ -74,6 +75,7 @@ export class HttpBatchLinkHandler extends ApolloLink {
7475
options: {
7576
withCredentials,
7677
headers,
78+
context
7779
},
7880
};
7981

@@ -162,6 +164,16 @@ export class HttpBatchLinkHandler extends ApolloLink {
162164
);
163165
}
164166

167+
private createHttpContext(operations: ApolloLink.Operation[]): HttpContext {
168+
return operations.reduce(
169+
(context: HttpContext, operation: ApolloLink.Operation) => {
170+
const { httpContext } = operation.getContext();
171+
return httpContext ? mergeHttpContext(httpContext, context) : context;
172+
},
173+
mergeHttpContext(this.options.httpContext, new HttpContext()),
174+
)
175+
}
176+
165177
private createBatchKey(operation: ApolloLink.Operation): string {
166178
const context: Context & { skipBatching?: boolean } = operation.getContext();
167179

packages/apollo-angular/http/src/http-link.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { print } from 'graphql';
22
import { Observable } from 'rxjs';
3-
import { HttpClient } from '@angular/common/http';
3+
import { HttpClient, HttpContext } from '@angular/common/http';
44
import { Injectable } from '@angular/core';
55
import { ApolloLink } from '@apollo/client';
66
import { pick } from './http-batch-link';
@@ -13,7 +13,7 @@ import {
1313
OperationPrinter,
1414
Request,
1515
} from './types';
16-
import { createHeadersWithClientAwareness, fetch, mergeHeaders } from './utils';
16+
import { createHeadersWithClientAwareness, fetch, mergeHeaders, mergeHttpContext } from './utils';
1717

1818
export declare namespace HttpLink {
1919
export interface Options extends FetchOptions, HttpRequestOptions {
@@ -49,6 +49,7 @@ export class HttpLinkHandler extends ApolloLink {
4949
const withCredentials = pick(context, this.options, 'withCredentials');
5050
const useMultipart = pick(context, this.options, 'useMultipart');
5151
const useGETForQueries = this.options.useGETForQueries === true;
52+
const httpContext = mergeHttpContext(context.httpContext, mergeHttpContext(this.options.httpContext, new HttpContext()));
5253

5354
const isQuery = operation.query.definitions.some(
5455
def => def.kind === 'OperationDefinition' && def.operation === 'query',
@@ -69,6 +70,7 @@ export class HttpLinkHandler extends ApolloLink {
6970
withCredentials,
7071
useMultipart,
7172
headers: this.options.headers,
73+
context: httpContext
7274
},
7375
};
7476

packages/apollo-angular/http/src/types.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DocumentNode } from 'graphql';
2-
import { HttpHeaders } from '@angular/common/http';
2+
import { HttpContext, HttpHeaders } from '@angular/common/http';
33
import { ApolloLink } from '@apollo/client';
44

55
declare module '@apollo/client' {
@@ -10,6 +10,11 @@ export type HttpRequestOptions = {
1010
headers?: HttpHeaders;
1111
withCredentials?: boolean;
1212
useMultipart?: boolean;
13+
httpContext?: HttpContext;
14+
};
15+
16+
export type RequestOptions = Omit<HttpRequestOptions, 'httpContext'> & {
17+
context?: HttpContext;
1318
};
1419

1520
export type URIFunction = (operation: ApolloLink.Operation) => string;
@@ -36,7 +41,7 @@ export type Request = {
3641
method: string;
3742
url: string;
3843
body: Body | Body[];
39-
options: HttpRequestOptions;
44+
options: RequestOptions;
4045
};
4146

4247
export type ExtractedFiles = {

packages/apollo-angular/http/src/utils.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Observable } from 'rxjs';
2-
import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
2+
import { HttpClient, HttpContext, HttpHeaders, HttpResponse } from '@angular/common/http';
33
import { Body, ExtractedFiles, ExtractFiles, Request } from './types';
44

55
export const fetch = (
@@ -121,6 +121,18 @@ export const mergeHeaders = (
121121
return destination || source;
122122
};
123123

124+
export const mergeHttpContext = (
125+
source: HttpContext | undefined,
126+
destination: HttpContext,
127+
): HttpContext => {
128+
if (source && destination) {
129+
return [...source.keys()]
130+
.reduce((context, name) => context.set(name, source.get(name)), destination);
131+
}
132+
133+
return destination || source;
134+
};
135+
124136
export function prioritize<T>(
125137
...values: [NonNullable<T>, ...T[]] | [...T[], NonNullable<T>]
126138
): NonNullable<T> {

packages/apollo-angular/http/tests/http-batch-link.spec.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
2-
import { HttpHeaders, provideHttpClient } from '@angular/common/http';
1+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
2+
import { HttpClient, HttpContext, HttpContextToken, HttpHeaders, provideHttpClient } from '@angular/common/http';
33
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
44
import { TestBed } from '@angular/core/testing';
55
import { ApolloLink, gql } from '@apollo/client';
@@ -759,4 +759,58 @@ describe('HttpBatchLink', () => {
759759
done();
760760
}, 50);
761761
}));
762+
763+
test('should pass on httpContext from %s to HttpClient options', () =>
764+
new Promise<void>(done => {
765+
const requestSpy = vi.spyOn(TestBed.inject(HttpClient), 'request');
766+
const contextToken1 = new HttpContextToken(() => '');
767+
const contextToken2 = new HttpContextToken(() => '');
768+
const contextToken3 = new HttpContextToken(() => '');
769+
const link = httpLink.create({
770+
uri: 'graphql',
771+
httpContext: new HttpContext().set(contextToken1, 'options'),
772+
batchKey: () => 'batchKey',
773+
});
774+
775+
const op1 = {
776+
query: gql`
777+
query heroes {
778+
heroes {
779+
name
780+
}
781+
}
782+
`,
783+
context: {
784+
httpContext: new HttpContext().set(contextToken2, 'foo')
785+
},
786+
};
787+
788+
const op2 = {
789+
query: gql`
790+
query heroes {
791+
heroes {
792+
name
793+
}
794+
}
795+
`,
796+
context: {
797+
httpContext: new HttpContext().set(contextToken3, 'bar')
798+
},
799+
};
800+
801+
execute(link, op1).subscribe(noop);
802+
execute(link, op2).subscribe(noop);
803+
804+
setTimeout(() => {
805+
httpBackend.match(() => {
806+
const callOptions = requestSpy.mock.calls[0][2];
807+
expect(callOptions?.context?.get(contextToken1)).toBe('options');
808+
expect(callOptions?.context?.get(contextToken2)).toBe('foo');
809+
expect(callOptions?.context?.get(contextToken3)).toBe('bar');
810+
done();
811+
return true;
812+
});
813+
814+
}, 50);
815+
}));
762816
});

packages/apollo-angular/http/tests/http-link.spec.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { print, stripIgnoredCharacters } from 'graphql';
22
import { map, mergeMap } from 'rxjs/operators';
3-
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
4-
import { HttpHeaders, provideHttpClient } from '@angular/common/http';
3+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
4+
import { HttpClient, HttpContext, HttpContextToken, HttpHeaders, provideHttpClient } from '@angular/common/http';
55
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
66
import { TestBed } from '@angular/core/testing';
77
import { ApolloLink, gql, InMemoryCache } from '@apollo/client';
@@ -750,4 +750,39 @@ describe('HttpLink', () => {
750750

751751
expect(httpBackend.expectOne('graphql').cancelled).toBe(true);
752752
});
753+
754+
test('should merge httpContext from options and query context and pass it on to HttpClient', () =>
755+
new Promise<void>(done => {
756+
const requestSpy = vi.spyOn(TestBed.inject(HttpClient), 'request');
757+
const optionsToken = new HttpContextToken(() => '');
758+
const queryToken = new HttpContextToken(() => '');
759+
760+
const optionsContext = new HttpContext().set(optionsToken, 'foo');
761+
const queryContext = new HttpContext().set(queryToken, 'bar')
762+
763+
const link = httpLink.create({ uri: 'graphql', httpContext: optionsContext });
764+
const op = {
765+
query: gql`
766+
query heroes {
767+
heroes {
768+
name
769+
}
770+
}
771+
`,
772+
context: {
773+
httpContext: queryContext
774+
},
775+
};
776+
777+
execute(link, op).subscribe(() => {
778+
const callOptions = requestSpy.mock.calls[0][2];
779+
expect(callOptions?.context?.get(optionsToken)).toBe('foo');
780+
expect(callOptions?.context?.get(queryToken)).toBe('bar');
781+
expect(optionsContext.get(queryToken)).toBe('');
782+
expect(queryContext.get(optionsToken)).toBe('');
783+
done();
784+
});
785+
786+
httpBackend.expectOne('graphql').flush({ data: {} });
787+
}));
753788
});

0 commit comments

Comments
 (0)