This repository has been archived by the owner on Apr 14, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 347
/
httpLink.ts
201 lines (177 loc) · 5.89 KB
/
httpLink.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import { ApolloLink, Observable, RequestHandler } from 'apollo-link';
import { print } from 'graphql/language/printer';
// types
import { ExecutionResult } from 'graphql';
import { ApolloFetch } from 'apollo-fetch';
// XXX replace with actual typings when available
declare var AbortController: any;
type ResponseError = Error & {
response?: Response;
parseError: Error;
statusCode?: number;
};
const parseAndCheckResponse = request => (response: Response) => {
return response
.json()
.then(result => {
if (response.status >= 300)
throw new Error(
`Response not successful: Received status code ${response.status}`,
);
if (!result.hasOwnProperty('data') && !result.hasOwnProperty('errors')) {
throw new Error(
`Server response was missing for query '${request.operationName}'.`,
);
}
return result;
})
.catch(e => {
const httpError = new Error(
`Network request failed with status ${response.status} - "${response.statusText}"`,
) as ResponseError;
httpError.response = response;
httpError.parseError = e;
httpError.statusCode = response.status;
throw httpError;
});
};
const checkFetcher = (fetcher: ApolloFetch | GlobalFetch['fetch']) => {
if (
(fetcher as ApolloFetch).use &&
(fetcher as ApolloFetch).useAfter &&
(fetcher as ApolloFetch).batchUse &&
(fetcher as ApolloFetch).batchUseAfter
) {
throw new Error(`
It looks like you're using apollo-fetch! Apollo Link now uses the native fetch
implementation, so apollo-fetch is not needed. If you want to use your existing
apollo-fetch middleware, please check this guide to upgrade:
https://github.com/apollographql/apollo-link/blob/master/docs/implementation.md
`);
}
};
const warnIfNoFetch = fetcher => {
if (!fetcher && typeof fetch === 'undefined') {
let library: string = 'unfetch';
if (typeof window === 'undefined') library = 'node-fetch';
throw new Error(
`fetch is not found globally and no fetcher passed, to fix pass a fetch for
your environment like https://www.npmjs.com/package/${library}.
For example:
import fetch from '${library}';
import { createHttpLink } from 'apollo-link-http';
const link = createFetchLink({ uri: '/graphql', fetch: fetch });
`,
);
}
};
const createSignalIfSupported = () => {
if (typeof AbortController === 'undefined')
return { controller: false, signal: false };
const controller = new AbortController();
const signal = controller.signal;
return { controller, signal };
};
export interface FetchOptions {
uri?: string;
fetch?: GlobalFetch['fetch'];
includeExtensions?: boolean;
credentials?: string;
headers?: any;
fetchOptions?: any;
}
export const createHttpLink = (
{
uri,
fetch: fetcher,
includeExtensions,
...requestOptions,
}: FetchOptions = {},
) => {
// dev warnings to ensure fetch is present
warnIfNoFetch(fetcher);
if (fetcher) checkFetcher(fetcher);
// use default global fetch is nothing passed in
if (!fetcher) fetcher = fetch;
if (!uri) uri = '/graphql';
return new ApolloLink(
operation =>
new Observable(observer => {
const {
headers,
credentials,
fetchOptions = {},
} = operation.getContext();
const { operationName, extensions, variables, query } = operation;
const body = {
operationName,
variables,
query: print(query),
};
if (includeExtensions) (body as any).extensions = extensions;
let serializedBody;
try {
serializedBody = JSON.stringify(body);
} catch (e) {
const parseError = new Error(
`Network request failed. Payload is not serializable: ${e.message}`,
) as ResponseError;
parseError.parseError = e;
throw parseError;
}
let options = fetchOptions;
if (requestOptions.fetchOptions)
options = { ...requestOptions.fetchOptions, ...options };
const fetcherOptions = {
method: 'POST',
...options,
headers: {
// headers are case insensitive (https://stackoverflow.com/a/5259004)
accept: '*/*',
'content-type': 'application/json',
},
body: serializedBody,
};
if (requestOptions.credentials)
fetcherOptions.credentials = requestOptions.credentials;
if (credentials) fetcherOptions.credentials = credentials;
if (requestOptions.headers)
fetcherOptions.headers = {
...fetcherOptions.headers,
...requestOptions.headers,
};
if (headers)
fetcherOptions.headers = { ...fetcherOptions.headers, ...headers };
const { controller, signal } = createSignalIfSupported();
if (controller) fetcherOptions.signal = signal;
fetcher(uri, fetcherOptions)
.then(parseAndCheckResponse(operation))
.then(result => {
// we have data and can send it to back up the link chain
observer.next(result);
observer.complete();
return result;
})
.catch(err => {
// fetch was cancelled so its already been cleaned up in the unsubscribe
if (err.name === 'AbortError') return;
observer.error(err);
});
return () => {
// XXX support canceling this request
// https://developers.google.com/web/updates/2017/09/abortable-fetch
if (controller) controller.abort();
};
}),
);
};
export class HttpLink extends ApolloLink {
public requester: RequestHandler;
constructor(opts: FetchOptions) {
super();
this.requester = createHttpLink(opts).request;
}
public request(op): Observable<ExecutionResult> | null {
return this.requester(op);
}
}