-
Notifications
You must be signed in to change notification settings - Fork 86
Expand file tree
/
Copy pathrequest_handler.browser.ts
More file actions
132 lines (116 loc) · 4.16 KB
/
request_handler.browser.ts
File metadata and controls
132 lines (116 loc) · 4.16 KB
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
/**
* Copyright 2022, 2024 Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AbortableRequest, Headers, RequestHandler, Response } from './http';
import { LoggerFacade, LogLevel } from '../../logging/logger';
import { REQUEST_TIMEOUT_MS } from '../enums';
import { REQUEST_ERROR, REQUEST_TIMEOUT, UNABLE_TO_PARSE_AND_SKIPPED_HEADER } from 'error_message';
import { OptimizelyError } from '../../error/optimizly_error';
/**
* Handles sending requests and receiving responses over HTTP via XMLHttpRequest
*/
export class BrowserRequestHandler implements RequestHandler {
private logger?: LoggerFacade;
private timeout: number;
public constructor(opt: { logger?: LoggerFacade, timeout?: number } = {}) {
this.logger = opt.logger;
this.timeout = opt.timeout ?? REQUEST_TIMEOUT_MS;
}
/**
* Builds an XMLHttpRequest
* @param requestUrl Fully-qualified URL to which to send the request
* @param headers List of headers to include in the request
* @param method HTTP method to use
* @param data?? stringified version of data to POST, PUT, etc
* @returns AbortableRequest contains both the response Promise and capability to abort()
*/
public makeRequest(requestUrl: string, headers: Headers, method: string, data?: string): AbortableRequest {
const request = new XMLHttpRequest();
const responsePromise: Promise<Response> = new Promise((resolve, reject) => {
request.open(method, requestUrl, true);
this.setHeadersInXhr(headers, request);
request.onreadystatechange = (): void => {
if (request.readyState === XMLHttpRequest.DONE) {
const statusCode = request.status;
if (statusCode === 0) {
reject(new OptimizelyError(REQUEST_ERROR));
return;
}
const headers = this.parseHeadersFromXhr(request);
const response: Response = {
statusCode: request.status,
body: request.responseText,
headers,
};
resolve(response);
}
};
request.timeout = this.timeout;
request.ontimeout = (): void => {
this.logger?.warn(REQUEST_TIMEOUT);
};
request.send(data);
});
return {
responsePromise,
abort(): void {
request.abort();
},
};
}
/**
* Sets the header collection for an XHR
* @param headers Headers to set
* @param request Request into which headers are to be set
* @private
*/
private setHeadersInXhr(headers: Headers, request: XMLHttpRequest): void {
Object.keys(headers).forEach(headerName => {
const header = headers[headerName];
if (typeof header === 'string') {
request.setRequestHeader(headerName, header);
}
});
}
/**
* Parses headers from an XHR
* @param request Request containing headers to be retrieved
* @private
* @returns List of headers without duplicates
*/
private parseHeadersFromXhr(request: XMLHttpRequest): Headers {
const allHeadersString = request.getAllResponseHeaders();
if (allHeadersString === null) {
return {};
}
const headerLines = allHeadersString.split('\r\n');
const headers: Headers = {};
headerLines.forEach(headerLine => {
try {
const separatorIndex = headerLine.indexOf(': ');
if (separatorIndex > -1) {
const headerName = headerLine.slice(0, separatorIndex);
const headerValue = headerLine.slice(separatorIndex + 2);
if (headerName && headerValue) {
headers[headerName] = headerValue;
}
}
} catch {
this.logger?.warn(UNABLE_TO_PARSE_AND_SKIPPED_HEADER, headerLine);
}
});
return headers;
}
}