Skip to content

Commit

Permalink
Adding customization for "events" processing to Http client
Browse files Browse the repository at this point in the history
  • Loading branch information
quinnjr committed Nov 5, 2024
1 parent 8c75368 commit b8276d1
Show file tree
Hide file tree
Showing 10 changed files with 376 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/clean-dragons-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'apollo-angular': minor
---

Adding additional configurable support for the underlying Angular Http Client
6 changes: 3 additions & 3 deletions .devcontainer/base.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Update the VARIANT arg in devcontainer.json to pick a Node.js version: 14, 12, 10
ARG VARIANT=14
# Update the VARIANT arg in devcontainer.json to pick a Node.js version: 14, 12, 10
ARG VARIANT=18
FROM node:${VARIANT}

# Options for setup scripts
Expand All @@ -10,7 +10,7 @@ ARG USER_UID=1000
ARG USER_GID=$USER_UID

ENV NVM_DIR=/usr/local/share/nvm
ENV NVM_SYMLINK_CURRENT=true \
ENV NVM_SYMLINK_CURRENT=true \
PATH=${NVM_DIR}/current/bin:${PATH}

# Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies.
Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"build": {
"dockerfile": "Dockerfile",
// Update 'VARIANT' to pick a Node version: 10, 12, 14
"args": { "VARIANT": "14" }
"args": { "VARIANT": "18" }
},

// Set *default* container specific settings.json values on container create.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
]
},
"engines": {
"node": ">=16"
"node": ">=18"
},
"scripts": {
"build": "yarn workspaces run build",
Expand Down
10 changes: 8 additions & 2 deletions packages/apollo-angular/http/src/http-batch-link.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { print } from 'graphql';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import {
ApolloLink,
Expand Down Expand Up @@ -75,7 +75,13 @@ export class HttpBatchLinkHandler extends ApolloLink {
const sub = fetch(req, this.httpClient, () => {
throw new Error('File upload is not available when combined with Batching');
}).subscribe({
next: result => observer.next(result.body),
next: result => {
if (result instanceof HttpResponse) {
observer.next(result.body);
} else {
observer.next(result);
}
},
error: err => observer.error(err),
complete: () => observer.complete(),
});
Expand Down
63 changes: 58 additions & 5 deletions packages/apollo-angular/http/src/http-link.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { print } from 'graphql';
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpResponse, HttpEvent, HttpEventType } from '@angular/common/http';
import { Injectable } from '@angular/core';
import {
ApolloLink,
FetchResult,
Observable as LinkObservable,
Operation,
Operation
} from '@apollo/client/core';
import { pick } from './http-batch-link';
import { Body, Context, OperationPrinter, Options, Request } from './types';
import { Body, Context, HttpClientReturn, OperationPrinter, Options, Request } from './types';
import { createHeadersWithClientAwareness, fetch, mergeHeaders } from './utils';

// XXX find a better name for it
Expand Down Expand Up @@ -57,6 +57,9 @@ export class HttpLinkHandler extends ApolloLink {
withCredentials,
useMultipart,
headers: this.options.headers,
observe: context.observe,
reportProgress: context.reportProgress,
responseType: context.responseType
},
};

Expand All @@ -73,9 +76,23 @@ export class HttpLinkHandler extends ApolloLink {
req.options.headers = mergeHeaders(req.options.headers, headers);

const sub = fetch(req, this.httpClient, this.options.extractFiles).subscribe({
next: response => {
next: (response: HttpClientReturn) => {
operation.setContext({ response });
observer.next(response.body);

if (context.responseType === 'blob' ||
context.responseType === 'arraybuffer' ||
context.responseType === 'text') {
observer.next(response);
return;
}

if (response instanceof HttpResponse) {
observer.next(response.body);
} else if (this.isHttpEvent(response)) {
this.handleHttpEvent(response, observer);
} else {
observer.next(response);
}
},
error: err => observer.error(err),
complete: () => observer.complete(),
Expand All @@ -92,6 +109,42 @@ export class HttpLinkHandler extends ApolloLink {
public request(op: Operation): LinkObservable<FetchResult> | null {
return this.requester(op);
}

private isHttpEvent(response: HttpClientReturn): response is HttpEvent<any> {
return typeof response === 'object' && response !== null && 'type' in response;
}

private handleHttpEvent(event: HttpEvent<any>, observer: any) {
switch (event.type) {
case HttpEventType.Response:
if (event instanceof HttpResponse) {
observer.next(event.body);
}
break;
case HttpEventType.DownloadProgress:
case HttpEventType.UploadProgress:
observer.next({
data: null,
extensions: {
httpEvent: {
type: event.type,
loaded: 'loaded' in event ? event.loaded : undefined,
total: 'total' in event ? event.total : undefined
}
}
});
break;
default:
observer.next({
data: null,
extensions: {
httpEvent: {
type: event.type
}
}
});
}
}
}

@Injectable({
Expand Down
18 changes: 16 additions & 2 deletions packages/apollo-angular/http/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import { DocumentNode } from 'graphql';
import { HttpHeaders } from '@angular/common/http';
import { Operation } from '@apollo/client/core';
import { HttpHeaders, HttpContext, HttpResponse, HttpEvent } from '@angular/common/http';
import { Operation, FetchResult } from '@apollo/client/core';

export type HttpRequestOptions = {
headers?: HttpHeaders;
context?: HttpContext;
withCredentials?: boolean;
useMultipart?: boolean;
observe?: 'body' | 'events' | 'response';
reportProgress?: boolean;
responseType?: 'json' | 'arraybuffer' | 'blob' | 'text';
params?: any;
body?: any;
};

export type HttpClientReturn =
| Object
| ArrayBuffer
| Blob
| string
| HttpResponse<Object | ArrayBuffer | Blob | string>
| HttpEvent<Object | ArrayBuffer | Blob | string>;

export type URIFunction = (operation: Operation) => string;

export type FetchOptions = {
Expand Down
35 changes: 26 additions & 9 deletions packages/apollo-angular/http/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { Observable } from 'rxjs';
import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Body, ExtractedFiles, ExtractFiles, Request } from './types';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {
Body,
Context,
ExtractedFiles,
ExtractFiles,
HttpRequestOptions,
Request,
HttpClientReturn
} from './types';

export const fetch = (
req: Request,
httpClient: HttpClient,
extractFiles?: ExtractFiles,
): Observable<HttpResponse<Object>> => {
): Observable<HttpClientReturn> => {
const context: Context = req.options || {};
const shouldUseBody = ['POST', 'PUT', 'PATCH'].indexOf(req.method.toUpperCase()) !== -1;
const shouldStringify = (param: string) =>
['variables', 'extensions'].indexOf(param.toLowerCase()) !== -1;
Expand Down Expand Up @@ -96,13 +105,21 @@ export const fetch = (
(bodyOrParams as any).body = form;
}

// create a request
return httpClient.request<Object>(req.method, req.url, {
observe: 'response',
responseType: 'json',
reportProgress: false,
const baseOptions: HttpRequestOptions = {
reportProgress: context.reportProgress ?? false,
withCredentials: context.withCredentials,
headers: context.headers,
...bodyOrParams,
...req.options,
...req.options
};

const observe = context.observe || 'response';
const responseType = context.responseType || 'json';

return httpClient.request(req.method, req.url, {
...baseOptions,
observe,
responseType: responseType as 'json' | 'text' | 'blob' | 'arraybuffer'
});
};

Expand Down
Loading

0 comments on commit b8276d1

Please sign in to comment.