-
Notifications
You must be signed in to change notification settings - Fork 400
Expand file tree
/
Copy pathrequest.ts
More file actions
220 lines (195 loc) · 6.41 KB
/
Copy pathrequest.ts
File metadata and controls
220 lines (195 loc) · 6.41 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
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/**
* Network request module.
*
* @internal
*/
import { CancellationController, TransportMethod, TransportRequest } from '../types/transport-request';
import { createMalformedResponseError, PubNubError } from '../../errors/pubnub-error';
import { TransportResponse } from '../types/transport-response';
import { PubNubAPIError } from '../../errors/pubnub-api-error';
import RequestOperation from '../constants/operations';
import { PubNubFileInterface } from '../types/file';
import { Request } from '../interfaces/request';
import { Query } from '../types/api';
import uuidGenerator from './uuid';
/**
* Base REST API request class.
*
* @internal
*/
export abstract class AbstractRequest<ResponseType, ServiceResponse extends object> implements Request<ResponseType> {
/**
* Service `ArrayBuffer` response decoder.
*/
protected static decoder = new TextDecoder();
/**
* Request cancellation controller.
*/
private _cancellationController: CancellationController | null;
/**
* Unique request identifier.
*/
requestIdentifier = uuidGenerator.createUUID();
/**
* Construct base request.
*
* Constructed request by default won't be cancellable and performed using `GET` HTTP method.
*
* @param params - Request configuration parameters.
*/
protected constructor(
private readonly params?: { method?: TransportMethod; cancellable?: boolean; compressible?: boolean },
) {
this._cancellationController = null;
}
/**
* Retrieve configured cancellation controller.
*
* @returns Cancellation controller.
*/
public get cancellationController(): CancellationController | null {
return this._cancellationController;
}
/**
* Update request cancellation controller.
*
* Controller itself provided by transport provider implementation and set only when request
* sending has been scheduled.
*
* @param controller - Cancellation controller or `null` to reset it.
*/
public set cancellationController(controller: CancellationController | null) {
this._cancellationController = controller;
}
/**
* Abort request if possible.
*
* @param [reason] Information about why request has been cancelled.
*/
abort(reason?: string): void {
if (this && this.cancellationController) this.cancellationController.abort(reason);
}
/**
* Target REST API endpoint operation type.
*/
operation(): RequestOperation {
throw Error('Should be implemented by subclass.');
}
/**
* Validate user-provided data before scheduling request.
*
* @returns Error message if request can't be sent without missing or malformed parameters.
*/
validate(): string | undefined {
return undefined;
}
/**
* Parse service response.
*
* @param response - Raw service response which should be parsed.
*/
async parse(response: TransportResponse): Promise<ResponseType> {
return this.deserializeResponse(response) as unknown as ResponseType;
}
/**
* Create platform-agnostic request object.
*
* @returns Request object which can be processed using platform-specific requirements.
*/
request(): TransportRequest {
const request: TransportRequest = {
method: this.params?.method ?? TransportMethod.GET,
path: this.path,
queryParameters: this.queryParameters,
cancellable: this.params?.cancellable ?? false,
compressible: this.params?.compressible ?? false,
timeout: 10,
identifier: this.requestIdentifier,
};
// Attach headers (if required).
const headers = this.headers;
if (headers) request.headers = headers;
// Attach body (if required).
if (
request.method === TransportMethod.POST ||
request.method === TransportMethod.PATCH ||
request.method === TransportMethod.PUT
) {
const [body, formData] = [this.body, this.formData];
if (formData) request.formData = formData;
if (body) request.body = body;
}
return request;
}
/**
* Target REST API endpoint request headers getter.
*
* @returns Key/value headers which should be used with request.
*/
protected get headers(): Record<string, string> | undefined {
return {
'Accept-Encoding': 'gzip, deflate',
...((this.params?.compressible ?? false) ? { 'Content-Encoding': 'deflate' } : {}),
};
}
/**
* Target REST API endpoint request path getter.
*
* @returns REST API path.
*/
protected get path(): string {
throw Error('`path` getter should be implemented by subclass.');
}
/**
* Target REST API endpoint request query parameters getter.
*
* @returns Key/value pairs which should be appended to the REST API path.
*/
protected get queryParameters(): Query {
return {};
}
protected get formData(): Record<string, string>[] | undefined {
return undefined;
}
/**
* Target REST API Request body payload getter.
*
* @returns Buffer of stringified data which should be sent with `POST` or `PATCH` request.
*/
protected get body(): ArrayBuffer | PubNubFileInterface | string | undefined {
return undefined;
}
/**
* Deserialize service response.
*
* @param response - Transparent response object with headers and body information.
*
* @returns Deserialized service response data.
*
* @throws {Error} if received service response can't be processed (has unexpected content-type or can't be parsed as
* JSON).
*/
protected deserializeResponse(response: TransportResponse): ServiceResponse {
const responseText = AbstractRequest.decoder.decode(response.body);
const contentType = response.headers['content-type'];
let parsedJson: ServiceResponse;
if (!contentType || (contentType.indexOf('javascript') === -1 && contentType.indexOf('json') === -1))
throw new PubNubError(
'Service response error, check status for details',
createMalformedResponseError(responseText, response.status),
);
try {
parsedJson = JSON.parse(responseText);
} catch (error) {
console.error('Error parsing JSON response:', error);
throw new PubNubError(
'Service response error, check status for details',
createMalformedResponseError(responseText, response.status),
);
}
// Throw and exception in case of client / server error.
if ('status' in parsedJson && typeof parsedJson.status === 'number' && parsedJson.status >= 400)
throw PubNubAPIError.create(response);
return parsedJson;
}
}