Skip to content

Commit 51238da

Browse files
committed
Clean up http client headers
1 parent d41aec5 commit 51238da

4 files changed

Lines changed: 105 additions & 60 deletions

File tree

services/http.ts

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
export type FetchConfig = Omit<RequestInit, 'headers'> & {
2+
headers?: Record<string, string>;
3+
};
4+
5+
export type HttpBody = BodyInit | object;
6+
17
export class BaseHttpClientError extends Error {
28
response: Response;
39

@@ -25,27 +31,27 @@ export abstract class BaseHttpClient {
2531
return this._baseUrl + rest
2632
}
2733

28-
_get(url: string, config?: object): Promise<Response> {
34+
_get(url: string, config?: FetchConfig): Promise<Response> {
2935
return this._send(url, 'GET', undefined, config);
3036
}
3137

32-
_post(url: string, body?: any, config?: object): Promise<Response> {
38+
_post(url: string, body?: HttpBody, config?: FetchConfig): Promise<Response> {
3339
return this._send(url, 'POST', body, config);
3440
}
3541

36-
_put(url: string, body?: any, config?: object): Promise<Response> {
42+
_put(url: string, body?: HttpBody, config?: FetchConfig): Promise<Response> {
3743
return this._send(url, 'PUT', body, config);
3844
}
3945

40-
_patch(url: string, body?: any, config?: object): Promise<Response> {
46+
_patch(url: string, body?: HttpBody, config?: FetchConfig): Promise<Response> {
4147
return this._send(url, 'PATCH', body, config);
4248
}
4349

44-
_delete(url: string, config?: object): Promise<Response> {
50+
_delete(url: string, config?: FetchConfig): Promise<Response> {
4551
return this._send(url, 'DELETE', undefined, config);
4652
}
4753

48-
async _sendTest(url: string, method: string, body?: any): Promise<Response> {
54+
async _sendTest(url: string, method: string, body?: HttpBody): Promise<Response> {
4955
const response = await fetch(this.url(url), {
5056
method,
5157
body,
@@ -62,22 +68,46 @@ export abstract class BaseHttpClient {
6268
return response;
6369
}
6470

65-
async _send(url: string, method: string, body?: any, config?: object): Promise<Response> {
71+
async _send(
72+
url: string,
73+
method: string,
74+
body?: HttpBody,
75+
config?: FetchConfig,
76+
): Promise<Response> {
77+
const { headers: configHeaders, ...restConfig } = config ?? {};
78+
const mergedHeaders: Record<string, string> = {
79+
...this._requestHeaders,
80+
...configHeaders,
81+
};
6682
const requestOptions = {
6783
method,
68-
body,
69-
headers: this._requestHeaders,
84+
body: undefined as BodyInit | undefined,
85+
headers: mergedHeaders,
7086
signal: this._abortSignal,
71-
...config
87+
...restConfig,
7288
};
7389

74-
if (requestOptions.headers['Content-Type'] === 'application/json'
75-
&& !(body instanceof FormData)
76-
&& typeof body !== 'undefined'
77-
&& body !== null
78-
) {
90+
// Let the browser set Content-Type (and multipart boundary) for FormData:
91+
if (body instanceof FormData) {
92+
delete mergedHeaders['Content-Type'];
93+
requestOptions.body = body;
94+
}
95+
else if (body !== null && body !== undefined && (
96+
mergedHeaders['Content-Type'] === 'application/json'
97+
|| (
98+
typeof body === 'object'
99+
&& !(body instanceof Blob)
100+
&& !(body instanceof ArrayBuffer)
101+
&& !ArrayBuffer.isView(body)
102+
&& !(body instanceof URLSearchParams)
103+
&& !(body instanceof ReadableStream)
104+
)
105+
)) {
79106
requestOptions.body = JSON.stringify(body);
80107
}
108+
else {
109+
requestOptions.body = body as BodyInit;
110+
}
81111

82112
const response = await fetch(this.url(url), requestOptions);
83113

services/osm.ts

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import parseOsmChangeXml from '@osmcha/osmchange-parser';
22
import type { FeatureCollection, Point } from 'geojson';
33

4-
import { BaseHttpClient, BaseHttpClientError } from "~/services/http";
4+
import {
5+
BaseHttpClient,
6+
BaseHttpClientError,
7+
type FetchConfig,
8+
type HttpBody,
9+
} from '~/services/http';
510
import * as xml from '~/util/xml';
611

712
import type { ICancelableClient } from '~/services/loading';
@@ -14,7 +19,6 @@ import type {
1419
OsmElement,
1520
OsmNode,
1621
OsmNote,
17-
OsmTags,
1822
OsmWay,
1923
} from '~/types/osm';
2024
import type { WorkspaceId } from '~/types/workspaces';
@@ -207,7 +211,9 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
207211
this.#webUrl = webUrl;
208212
this.#tdeiClient = tdeiClient;
209213
this.#setAuthHeader();
210-
this._requestHeaders['Accept'] = 'text/plain';
214+
215+
// OSM API can return XML or JSON based on the header or file extension:
216+
this._requestHeaders['Accept'] = '*/*';
211217
this._requestHeaders['Content-Type'] = 'text/plain';
212218
}
213219

@@ -234,8 +240,8 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
234240
display_name: this.auth.displayName
235241
};
236242

237-
await this._put(`user/${this.auth.subject}`, JSON.stringify(body), {
238-
headers: { ...this._requestHeaders, 'Content-Type': 'application/json' }
243+
await this._put(`user/${this.auth.subject}`, body, {
244+
headers: { 'Content-Type': 'application/json' }
239245
});
240246
}
241247

@@ -286,7 +292,6 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
286292
): Promise<OsmElement> {
287293
const response = await this._get(`${type}/${id}/${version}`, {
288294
headers: {
289-
...this._requestHeaders,
290295
'Accept': 'application/json',
291296
'X-Workspace': workspaceId,
292297
},
@@ -301,7 +306,6 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
301306
async getNodes(workspaceId: WorkspaceId, nodeIds: (number | string)[]): Promise<OsmNode[]> {
302307
const response = await this._get(`nodes?nodes=${nodeIds.join(',')}`, {
303308
headers: {
304-
...this._requestHeaders,
305309
'Accept': 'application/json',
306310
'X-Workspace': workspaceId,
307311
},
@@ -319,7 +323,6 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
319323
async getWays(workspaceId: WorkspaceId, wayIds: (number | string)[]): Promise<OsmWay[]> {
320324
const response = await this._get(`ways?ways=${wayIds.join(',')}`, {
321325
headers: {
322-
...this._requestHeaders,
323326
'Accept': 'application/json',
324327
'X-Workspace': workspaceId,
325328
},
@@ -337,7 +340,6 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
337340
async getWaysForNode(workspaceId: WorkspaceId, nodeId: number): Promise<OsmElement[]> {
338341
const response = await this._get(`node/${nodeId}/ways`, {
339342
headers: {
340-
...this._requestHeaders,
341343
'Accept': 'application/json',
342344
'X-Workspace': workspaceId,
343345
},
@@ -348,7 +350,7 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
348350

349351
async listChangesets(workspaceId: WorkspaceId): Promise<OsmChangeset[]> {
350352
const response = await this._get(`changesets.json`, {
351-
headers: { ...this._requestHeaders, 'X-Workspace': workspaceId },
353+
headers: { 'X-Workspace': workspaceId },
352354
});
353355

354356
const changesets = (await response.json())?.changesets ?? [];
@@ -374,7 +376,6 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
374376

375377
const response = await this._get(url, {
376378
headers: {
377-
...this._requestHeaders,
378379
'Accept': 'application/json',
379380
'X-Workspace': workspaceId,
380381
},
@@ -401,7 +402,6 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
401402
{
402403
const response = await this._get(`changeset/${changesetId}/download`, {
403404
headers: {
404-
...this._requestHeaders,
405405
'Accept': 'application/xml',
406406
'X-Workspace': workspaceId,
407407
},
@@ -427,7 +427,7 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
427427

428428
const body = xml.serialize(doc);
429429
const response = await this._put('changeset/create', body, {
430-
headers: { ...this._requestHeaders, 'X-Workspace': workspaceId },
430+
headers: { 'X-Workspace': workspaceId },
431431
});
432432

433433
return Number(await response.text());
@@ -441,7 +441,6 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
441441
await this._post(`changeset/${changesetId}/upload`, changesetXml, {
442442
headers: {
443443
'Content-Type': 'application/xml',
444-
'Authorization': this._requestHeaders['Authorization'],
445444
'X-Workspace': workspaceId,
446445
},
447446
});
@@ -468,10 +467,7 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
468467
body.append('text', message);
469468

470469
await this._post(`changeset/${changesetId}/comment`, body, {
471-
headers: {
472-
'Authorization': this._requestHeaders['Authorization'],
473-
'X-Workspace': workspaceId,
474-
},
470+
headers: { 'X-Workspace': workspaceId },
475471
});
476472
}
477473

@@ -484,7 +480,6 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
484480

485481
const response = await this._get(`notes/search.json?${params}`, {
486482
headers: {
487-
...this._requestHeaders,
488483
'Accept': 'application/json',
489484
'X-Workspace': workspaceId,
490485
},
@@ -497,7 +492,6 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
497492
const bboxParam = await this.getExportBbox(workspaceId);
498493
const response = await this._get(`map.json?bbox=${bboxParam}`, {
499494
headers: {
500-
...this._requestHeaders,
501495
'Accept': 'application/json',
502496
'X-Workspace': workspaceId
503497
}
@@ -510,7 +504,6 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
510504
const bboxParam = await this.getExportBbox(workspaceId);
511505
const response = await this._get(`map?bbox=${bboxParam}`, {
512506
headers: {
513-
...this._requestHeaders,
514507
'Accept': 'application/xml',
515508
'X-Workspace': workspaceId
516509
}
@@ -525,17 +518,23 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
525518
}
526519
}
527520

528-
async _send(url: string, method: string, body?: any, config?: object): Promise<Response> {
521+
override async _send(
522+
url: string,
523+
method: string,
524+
body?: HttpBody,
525+
config?: FetchConfig,
526+
): Promise<Response> {
529527
try {
530528
await this.#tdeiClient.tryRefreshAuth();
531529
this.#setAuthHeader();
532530

533-
const requestOptions = {
534-
credentials: 'include'
535-
}
531+
const requestOptions: FetchConfig = {
532+
credentials: 'include',
533+
};
536534

537535
return await super._send(url, method, body, { ...requestOptions, ...config });
538-
} catch (e: any) {
536+
}
537+
catch (e: unknown) {
539538
if (e instanceof BaseHttpClientError) {
540539
throw new OsmApiClientError(e.response);
541540
}

services/tdei.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { BlobReader, BlobWriter, ZipReader } from '@zip.js/zip.js';
22

3-
import { BaseHttpClient, BaseHttpClientError } from '~/services/http';
3+
import {
4+
BaseHttpClient,
5+
BaseHttpClientError,
6+
type FetchConfig,
7+
type HttpBody,
8+
} from '~/services/http';
49
import type { ICancelableClient } from '~/services/loading';
510
import type {
611
TdeiFeedback,
@@ -284,9 +289,7 @@ export class TdeiClient extends BaseHttpClient implements ICancelableClient {
284289
resource += '?derived_from_dataset_id=' + tdeiRecordId;
285290
}
286291

287-
const response = await this._post(resource, body, {
288-
headers: { 'Authorization': this._requestHeaders['Authorization'] }
289-
});
292+
const response = await this._post(resource, body);
290293

291294
return await response.text();
292295
}
@@ -308,9 +311,7 @@ export class TdeiClient extends BaseHttpClient implements ICancelableClient {
308311
resource += '?derived_from_dataset_id=' + tdeiRecordId;
309312
}
310313

311-
const response = await this._post(resource, body, {
312-
headers: { 'Authorization': this._requestHeaders['Authorization'] }
313-
});
314+
const response = await this._post(resource, body);
314315

315316
return await response.text();
316317
}
@@ -335,10 +336,7 @@ export class TdeiClient extends BaseHttpClient implements ICancelableClient {
335336
await new Promise(resolve => setTimeout(resolve, 4000));
336337

337338
const statusResponse = await this._get(`jobs?job_id=${jobId}&tdei_project_group_id=${projectGroupId}`, {
338-
headers: {
339-
'Accept': 'application/text',
340-
'Authorization': this._requestHeaders['Authorization']
341-
}
339+
headers: { 'Accept': 'application/text' }
342340
});
343341
const statusBody = (await statusResponse.json())[0];
344342
const statusText = statusBody.status.toLowerCase();
@@ -424,14 +422,20 @@ export class TdeiClient extends BaseHttpClient implements ICancelableClient {
424422
}
425423
}
426424

427-
override async _send(url: string, method: string, body?: any, config?: object): Promise<Response> {
425+
override async _send(
426+
url: string,
427+
method: string,
428+
body?: HttpBody,
429+
config?: FetchConfig,
430+
): Promise<Response> {
428431
try {
429432
if (this.#auth.needsRefresh) {
430433
await this.refreshToken();
431434
}
432435

433436
return await super._send(url, method, body, config);
434-
} catch (e: any) {
437+
}
438+
catch (e: unknown) {
435439
if (e instanceof BaseHttpClientError) {
436440
throw new TdeiClientError(e.response);
437441
}
@@ -515,13 +519,19 @@ export class TdeiUserClient extends BaseHttpClient implements ICancelableClient
515519
}
516520
}
517521

518-
override async _send(url: string, method: string, body?: any, config?: object): Promise<Response> {
522+
override async _send(
523+
url: string,
524+
method: string,
525+
body?: HttpBody,
526+
config?: FetchConfig,
527+
): Promise<Response> {
519528
try {
520529
await this.#tdeiClient.tryRefreshAuth();
521530
this.#setAuthHeader();
522531

523532
return await super._send(url, method, body, config);
524-
} catch (e: any) {
533+
}
534+
catch (e: unknown) {
525535
if (e instanceof BaseHttpClientError) {
526536
throw new TdeiUserClientError(e.response);
527537
}

0 commit comments

Comments
 (0)