UNPKG

34.1 kBJavaScriptView Raw
1/*! firebase-admin v10.0.0 */
2"use strict";
3/*!
4 * @license
5 * Copyright 2017 Google Inc.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19var __extends = (this && this.__extends) || (function () {
20 var extendStatics = function (d, b) {
21 extendStatics = Object.setPrototypeOf ||
22 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
23 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
24 return extendStatics(d, b);
25 };
26 return function (d, b) {
27 extendStatics(d, b);
28 function __() { this.constructor = d; }
29 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
30 };
31})();
32Object.defineProperty(exports, "__esModule", { value: true });
33exports.ExponentialBackoffPoller = exports.ApiSettings = exports.AuthorizedHttpClient = exports.parseHttpResponse = exports.HttpClient = exports.defaultRetryConfig = exports.HttpError = void 0;
34var error_1 = require("./error");
35var validator = require("./validator");
36var http = require("http");
37var https = require("https");
38var url = require("url");
39var events_1 = require("events");
40var DefaultHttpResponse = /** @class */ (function () {
41 /**
42 * Constructs a new HttpResponse from the given LowLevelResponse.
43 */
44 function DefaultHttpResponse(resp) {
45 this.status = resp.status;
46 this.headers = resp.headers;
47 this.text = resp.data;
48 try {
49 if (!resp.data) {
50 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INTERNAL_ERROR, 'HTTP response missing data.');
51 }
52 this.parsedData = JSON.parse(resp.data);
53 }
54 catch (err) {
55 this.parsedData = undefined;
56 this.parseError = err;
57 }
58 this.request = resp.config.method + " " + resp.config.url;
59 }
60 Object.defineProperty(DefaultHttpResponse.prototype, "data", {
61 get: function () {
62 if (this.isJson()) {
63 return this.parsedData;
64 }
65 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.UNABLE_TO_PARSE_RESPONSE, "Error while parsing response data: \"" + this.parseError.toString() + "\". Raw server " +
66 ("response: \"" + this.text + "\". Status code: \"" + this.status + "\". Outgoing ") +
67 ("request: \"" + this.request + ".\""));
68 },
69 enumerable: false,
70 configurable: true
71 });
72 DefaultHttpResponse.prototype.isJson = function () {
73 return typeof this.parsedData !== 'undefined';
74 };
75 return DefaultHttpResponse;
76}());
77/**
78 * Represents a multipart HTTP response. Parts that constitute the response body can be accessed
79 * via the multipart getter. Getters for text and data throw errors.
80 */
81var MultipartHttpResponse = /** @class */ (function () {
82 function MultipartHttpResponse(resp) {
83 this.status = resp.status;
84 this.headers = resp.headers;
85 this.multipart = resp.multipart;
86 }
87 Object.defineProperty(MultipartHttpResponse.prototype, "text", {
88 get: function () {
89 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.UNABLE_TO_PARSE_RESPONSE, 'Unable to parse multipart payload as text');
90 },
91 enumerable: false,
92 configurable: true
93 });
94 Object.defineProperty(MultipartHttpResponse.prototype, "data", {
95 get: function () {
96 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.UNABLE_TO_PARSE_RESPONSE, 'Unable to parse multipart payload as JSON');
97 },
98 enumerable: false,
99 configurable: true
100 });
101 MultipartHttpResponse.prototype.isJson = function () {
102 return false;
103 };
104 return MultipartHttpResponse;
105}());
106var HttpError = /** @class */ (function (_super) {
107 __extends(HttpError, _super);
108 function HttpError(response) {
109 var _this = _super.call(this, "Server responded with status " + response.status + ".") || this;
110 _this.response = response;
111 // Set the prototype so that instanceof checks will work correctly.
112 // See: https://github.com/Microsoft/TypeScript/issues/13965
113 Object.setPrototypeOf(_this, HttpError.prototype);
114 return _this;
115 }
116 return HttpError;
117}(Error));
118exports.HttpError = HttpError;
119/**
120 * Default retry configuration for HTTP requests. Retries up to 4 times on connection reset and timeout errors
121 * as well as HTTP 503 errors. Exposed as a function to ensure that every HttpClient gets its own RetryConfig
122 * instance.
123 */
124function defaultRetryConfig() {
125 return {
126 maxRetries: 4,
127 statusCodes: [503],
128 ioErrorCodes: ['ECONNRESET', 'ETIMEDOUT'],
129 backOffFactor: 0.5,
130 maxDelayInMillis: 60 * 1000,
131 };
132}
133exports.defaultRetryConfig = defaultRetryConfig;
134/**
135 * Ensures that the given RetryConfig object is valid.
136 *
137 * @param retry - The configuration to be validated.
138 */
139function validateRetryConfig(retry) {
140 if (!validator.isNumber(retry.maxRetries) || retry.maxRetries < 0) {
141 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_ARGUMENT, 'maxRetries must be a non-negative integer');
142 }
143 if (typeof retry.backOffFactor !== 'undefined') {
144 if (!validator.isNumber(retry.backOffFactor) || retry.backOffFactor < 0) {
145 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_ARGUMENT, 'backOffFactor must be a non-negative number');
146 }
147 }
148 if (!validator.isNumber(retry.maxDelayInMillis) || retry.maxDelayInMillis < 0) {
149 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_ARGUMENT, 'maxDelayInMillis must be a non-negative integer');
150 }
151 if (typeof retry.statusCodes !== 'undefined' && !validator.isArray(retry.statusCodes)) {
152 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_ARGUMENT, 'statusCodes must be an array');
153 }
154 if (typeof retry.ioErrorCodes !== 'undefined' && !validator.isArray(retry.ioErrorCodes)) {
155 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INVALID_ARGUMENT, 'ioErrorCodes must be an array');
156 }
157}
158var HttpClient = /** @class */ (function () {
159 function HttpClient(retry) {
160 if (retry === void 0) { retry = defaultRetryConfig(); }
161 this.retry = retry;
162 if (this.retry) {
163 validateRetryConfig(this.retry);
164 }
165 }
166 /**
167 * Sends an HTTP request to a remote server. If the server responds with a successful response (2xx), the returned
168 * promise resolves with an HttpResponse. If the server responds with an error (3xx, 4xx, 5xx), the promise rejects
169 * with an HttpError. In case of all other errors, the promise rejects with a FirebaseAppError. If a request fails
170 * due to a low-level network error, transparently retries the request once before rejecting the promise.
171 *
172 * If the request data is specified as an object, it will be serialized into a JSON string. The application/json
173 * content-type header will also be automatically set in this case. For all other payload types, the content-type
174 * header should be explicitly set by the caller. To send a JSON leaf value (e.g. "foo", 5), parse it into JSON,
175 * and pass as a string or a Buffer along with the appropriate content-type header.
176 *
177 * @param config - HTTP request to be sent.
178 * @returns A promise that resolves with the response details.
179 */
180 HttpClient.prototype.send = function (config) {
181 return this.sendWithRetry(config);
182 };
183 /**
184 * Sends an HTTP request. In the event of an error, retries the HTTP request according to the
185 * RetryConfig set on the HttpClient.
186 *
187 * @param config - HTTP request to be sent.
188 * @param retryAttempts - Number of retries performed up to now.
189 * @returns A promise that resolves with the response details.
190 */
191 HttpClient.prototype.sendWithRetry = function (config, retryAttempts) {
192 var _this = this;
193 if (retryAttempts === void 0) { retryAttempts = 0; }
194 return AsyncHttpCall.invoke(config)
195 .then(function (resp) {
196 return _this.createHttpResponse(resp);
197 })
198 .catch(function (err) {
199 var _a = _this.getRetryDelayMillis(retryAttempts, err), delayMillis = _a[0], canRetry = _a[1];
200 if (canRetry && _this.retry && delayMillis <= _this.retry.maxDelayInMillis) {
201 return _this.waitForRetry(delayMillis).then(function () {
202 return _this.sendWithRetry(config, retryAttempts + 1);
203 });
204 }
205 if (err.response) {
206 throw new HttpError(_this.createHttpResponse(err.response));
207 }
208 if (err.code === 'ETIMEDOUT') {
209 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.NETWORK_TIMEOUT, "Error while making request: " + err.message + ".");
210 }
211 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.NETWORK_ERROR, "Error while making request: " + err.message + ". Error code: " + err.code);
212 });
213 };
214 HttpClient.prototype.createHttpResponse = function (resp) {
215 if (resp.multipart) {
216 return new MultipartHttpResponse(resp);
217 }
218 return new DefaultHttpResponse(resp);
219 };
220 HttpClient.prototype.waitForRetry = function (delayMillis) {
221 if (delayMillis > 0) {
222 return new Promise(function (resolve) {
223 setTimeout(resolve, delayMillis);
224 });
225 }
226 return Promise.resolve();
227 };
228 /**
229 * Checks if a failed request is eligible for a retry, and if so returns the duration to wait before initiating
230 * the retry.
231 *
232 * @param retryAttempts - Number of retries completed up to now.
233 * @param err - The last encountered error.
234 * @returns A 2-tuple where the 1st element is the duration to wait before another retry, and the
235 * 2nd element is a boolean indicating whether the request is eligible for a retry or not.
236 */
237 HttpClient.prototype.getRetryDelayMillis = function (retryAttempts, err) {
238 if (!this.isRetryEligible(retryAttempts, err)) {
239 return [0, false];
240 }
241 var response = err.response;
242 if (response && response.headers['retry-after']) {
243 var delayMillis = this.parseRetryAfterIntoMillis(response.headers['retry-after']);
244 if (delayMillis > 0) {
245 return [delayMillis, true];
246 }
247 }
248 return [this.backOffDelayMillis(retryAttempts), true];
249 };
250 HttpClient.prototype.isRetryEligible = function (retryAttempts, err) {
251 if (!this.retry) {
252 return false;
253 }
254 if (retryAttempts >= this.retry.maxRetries) {
255 return false;
256 }
257 if (err.response) {
258 var statusCodes = this.retry.statusCodes || [];
259 return statusCodes.indexOf(err.response.status) !== -1;
260 }
261 if (err.code) {
262 var retryCodes = this.retry.ioErrorCodes || [];
263 return retryCodes.indexOf(err.code) !== -1;
264 }
265 return false;
266 };
267 /**
268 * Parses the Retry-After HTTP header as a milliseconds value. Return value is negative if the Retry-After header
269 * contains an expired timestamp or otherwise malformed.
270 */
271 HttpClient.prototype.parseRetryAfterIntoMillis = function (retryAfter) {
272 var delaySeconds = parseInt(retryAfter, 10);
273 if (!isNaN(delaySeconds)) {
274 return delaySeconds * 1000;
275 }
276 var date = new Date(retryAfter);
277 if (!isNaN(date.getTime())) {
278 return date.getTime() - Date.now();
279 }
280 return -1;
281 };
282 HttpClient.prototype.backOffDelayMillis = function (retryAttempts) {
283 if (retryAttempts === 0) {
284 return 0;
285 }
286 if (!this.retry) {
287 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INTERNAL_ERROR, 'Expected this.retry to exist.');
288 }
289 var backOffFactor = this.retry.backOffFactor || 0;
290 var delayInSeconds = (Math.pow(2, retryAttempts)) * backOffFactor;
291 return Math.min(delayInSeconds * 1000, this.retry.maxDelayInMillis);
292 };
293 return HttpClient;
294}());
295exports.HttpClient = HttpClient;
296/**
297 * Parses a full HTTP response message containing both a header and a body.
298 *
299 * @param response - The HTTP response to be parsed.
300 * @param config - The request configuration that resulted in the HTTP response.
301 * @returns An object containing the parsed HTTP status, headers and the body.
302 */
303function parseHttpResponse(response, config) {
304 var responseText = validator.isBuffer(response) ?
305 response.toString('utf-8') : response;
306 var endOfHeaderPos = responseText.indexOf('\r\n\r\n');
307 var headerLines = responseText.substring(0, endOfHeaderPos).split('\r\n');
308 var statusLine = headerLines[0];
309 var status = statusLine.trim().split(/\s/)[1];
310 var headers = {};
311 headerLines.slice(1).forEach(function (line) {
312 var colonPos = line.indexOf(':');
313 var name = line.substring(0, colonPos).trim().toLowerCase();
314 var value = line.substring(colonPos + 1).trim();
315 headers[name] = value;
316 });
317 var data = responseText.substring(endOfHeaderPos + 4);
318 if (data.endsWith('\n')) {
319 data = data.slice(0, -1);
320 }
321 if (data.endsWith('\r')) {
322 data = data.slice(0, -1);
323 }
324 var lowLevelResponse = {
325 status: parseInt(status, 10),
326 headers: headers,
327 data: data,
328 config: config,
329 request: null,
330 };
331 if (!validator.isNumber(lowLevelResponse.status)) {
332 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INTERNAL_ERROR, 'Malformed HTTP status line.');
333 }
334 return new DefaultHttpResponse(lowLevelResponse);
335}
336exports.parseHttpResponse = parseHttpResponse;
337/**
338 * A helper class for sending HTTP requests over the wire. This is a wrapper around the standard
339 * http and https packages of Node.js, providing content processing, timeouts and error handling.
340 * It also wraps the callback API of the Node.js standard library in a more flexible Promise API.
341 */
342var AsyncHttpCall = /** @class */ (function () {
343 function AsyncHttpCall(config) {
344 var _this = this;
345 try {
346 this.config = new HttpRequestConfigImpl(config);
347 this.options = this.config.buildRequestOptions();
348 this.entity = this.config.buildEntity(this.options.headers);
349 this.promise = new Promise(function (resolve, reject) {
350 _this.resolve = resolve;
351 _this.reject = reject;
352 _this.execute();
353 });
354 }
355 catch (err) {
356 this.promise = Promise.reject(this.enhanceError(err, null));
357 }
358 }
359 /**
360 * Sends an HTTP request based on the provided configuration.
361 */
362 AsyncHttpCall.invoke = function (config) {
363 return new AsyncHttpCall(config).promise;
364 };
365 AsyncHttpCall.prototype.execute = function () {
366 var _this = this;
367 var transport = this.options.protocol === 'https:' ? https : http;
368 var req = transport.request(this.options, function (res) {
369 _this.handleResponse(res, req);
370 });
371 // Handle errors
372 req.on('error', function (err) {
373 if (req.aborted) {
374 return;
375 }
376 _this.enhanceAndReject(err, null, req);
377 });
378 var timeout = this.config.timeout;
379 var timeoutCallback = function () {
380 req.abort();
381 _this.rejectWithError("timeout of " + timeout + "ms exceeded", 'ETIMEDOUT', req);
382 };
383 if (timeout) {
384 // Listen to timeouts and throw an error.
385 req.setTimeout(timeout, timeoutCallback);
386 req.on('socket', function (socket) {
387 socket.setTimeout(timeout, timeoutCallback);
388 });
389 }
390 // Send the request
391 req.end(this.entity);
392 };
393 AsyncHttpCall.prototype.handleResponse = function (res, req) {
394 if (req.aborted) {
395 return;
396 }
397 if (!res.statusCode) {
398 throw new error_1.FirebaseAppError(error_1.AppErrorCodes.INTERNAL_ERROR, 'Expected a statusCode on the response from a ClientRequest');
399 }
400 var response = {
401 status: res.statusCode,
402 headers: res.headers,
403 request: req,
404 data: undefined,
405 config: this.config,
406 };
407 var boundary = this.getMultipartBoundary(res.headers);
408 var respStream = this.uncompressResponse(res);
409 if (boundary) {
410 this.handleMultipartResponse(response, respStream, boundary);
411 }
412 else {
413 this.handleRegularResponse(response, respStream);
414 }
415 };
416 /**
417 * Extracts multipart boundary from the HTTP header. The content-type header of a multipart
418 * response has the form 'multipart/subtype; boundary=string'.
419 *
420 * If the content-type header does not exist, or does not start with
421 * 'multipart/', then null will be returned.
422 */
423 AsyncHttpCall.prototype.getMultipartBoundary = function (headers) {
424 var contentType = headers['content-type'];
425 if (!contentType || !contentType.startsWith('multipart/')) {
426 return null;
427 }
428 var segments = contentType.split(';');
429 var emptyObject = {};
430 var headerParams = segments.slice(1)
431 .map(function (segment) { return segment.trim().split('='); })
432 .reduce(function (curr, params) {
433 // Parse key=value pairs in the content-type header into properties of an object.
434 if (params.length === 2) {
435 var keyValuePair = {};
436 keyValuePair[params[0]] = params[1];
437 return Object.assign(curr, keyValuePair);
438 }
439 return curr;
440 }, emptyObject);
441 return headerParams.boundary;
442 };
443 AsyncHttpCall.prototype.uncompressResponse = function (res) {
444 // Uncompress the response body transparently if required.
445 var respStream = res;
446 var encodings = ['gzip', 'compress', 'deflate'];
447 if (res.headers['content-encoding'] && encodings.indexOf(res.headers['content-encoding']) !== -1) {
448 // Add the unzipper to the body stream processing pipeline.
449 var zlib = require('zlib'); // eslint-disable-line @typescript-eslint/no-var-requires
450 respStream = respStream.pipe(zlib.createUnzip());
451 // Remove the content-encoding in order to not confuse downstream operations.
452 delete res.headers['content-encoding'];
453 }
454 return respStream;
455 };
456 AsyncHttpCall.prototype.handleMultipartResponse = function (response, respStream, boundary) {
457 var _this = this;
458 var dicer = require('dicer'); // eslint-disable-line @typescript-eslint/no-var-requires
459 var multipartParser = new dicer({ boundary: boundary });
460 var responseBuffer = [];
461 multipartParser.on('part', function (part) {
462 var tempBuffers = [];
463 part.on('data', function (partData) {
464 tempBuffers.push(partData);
465 });
466 part.on('end', function () {
467 responseBuffer.push(Buffer.concat(tempBuffers));
468 });
469 });
470 multipartParser.on('finish', function () {
471 response.data = undefined;
472 response.multipart = responseBuffer;
473 _this.finalizeResponse(response);
474 });
475 respStream.pipe(multipartParser);
476 };
477 AsyncHttpCall.prototype.handleRegularResponse = function (response, respStream) {
478 var _this = this;
479 var responseBuffer = [];
480 respStream.on('data', function (chunk) {
481 responseBuffer.push(chunk);
482 });
483 respStream.on('error', function (err) {
484 var req = response.request;
485 if (req && req.aborted) {
486 return;
487 }
488 _this.enhanceAndReject(err, null, req);
489 });
490 respStream.on('end', function () {
491 response.data = Buffer.concat(responseBuffer).toString();
492 _this.finalizeResponse(response);
493 });
494 };
495 /**
496 * Finalizes the current HTTP call in-flight by either resolving or rejecting the associated
497 * promise. In the event of an error, adds additional useful information to the returned error.
498 */
499 AsyncHttpCall.prototype.finalizeResponse = function (response) {
500 if (response.status >= 200 && response.status < 300) {
501 this.resolve(response);
502 }
503 else {
504 this.rejectWithError('Request failed with status code ' + response.status, null, response.request, response);
505 }
506 };
507 /**
508 * Creates a new error from the given message, and enhances it with other information available.
509 * Then the promise associated with this HTTP call is rejected with the resulting error.
510 */
511 AsyncHttpCall.prototype.rejectWithError = function (message, code, request, response) {
512 var error = new Error(message);
513 this.enhanceAndReject(error, code, request, response);
514 };
515 AsyncHttpCall.prototype.enhanceAndReject = function (error, code, request, response) {
516 this.reject(this.enhanceError(error, code, request, response));
517 };
518 /**
519 * Enhances the given error by adding more information to it. Specifically, the HttpRequestConfig,
520 * the underlying request and response will be attached to the error.
521 */
522 AsyncHttpCall.prototype.enhanceError = function (error, code, request, response) {
523 error.config = this.config;
524 if (code) {
525 error.code = code;
526 }
527 error.request = request;
528 error.response = response;
529 return error;
530 };
531 return AsyncHttpCall;
532}());
533/**
534 * An adapter class for extracting options and entity data from an HttpRequestConfig.
535 */
536var HttpRequestConfigImpl = /** @class */ (function () {
537 function HttpRequestConfigImpl(config) {
538 this.config = config;
539 }
540 Object.defineProperty(HttpRequestConfigImpl.prototype, "method", {
541 get: function () {
542 return this.config.method;
543 },
544 enumerable: false,
545 configurable: true
546 });
547 Object.defineProperty(HttpRequestConfigImpl.prototype, "url", {
548 get: function () {
549 return this.config.url;
550 },
551 enumerable: false,
552 configurable: true
553 });
554 Object.defineProperty(HttpRequestConfigImpl.prototype, "headers", {
555 get: function () {
556 return this.config.headers;
557 },
558 enumerable: false,
559 configurable: true
560 });
561 Object.defineProperty(HttpRequestConfigImpl.prototype, "data", {
562 get: function () {
563 return this.config.data;
564 },
565 enumerable: false,
566 configurable: true
567 });
568 Object.defineProperty(HttpRequestConfigImpl.prototype, "timeout", {
569 get: function () {
570 return this.config.timeout;
571 },
572 enumerable: false,
573 configurable: true
574 });
575 Object.defineProperty(HttpRequestConfigImpl.prototype, "httpAgent", {
576 get: function () {
577 return this.config.httpAgent;
578 },
579 enumerable: false,
580 configurable: true
581 });
582 HttpRequestConfigImpl.prototype.buildRequestOptions = function () {
583 var parsed = this.buildUrl();
584 var protocol = parsed.protocol;
585 var port = parsed.port;
586 if (!port) {
587 var isHttps = protocol === 'https:';
588 port = isHttps ? '443' : '80';
589 }
590 return {
591 protocol: protocol,
592 hostname: parsed.hostname,
593 port: port,
594 path: parsed.path,
595 method: this.method,
596 agent: this.httpAgent,
597 headers: Object.assign({}, this.headers),
598 };
599 };
600 HttpRequestConfigImpl.prototype.buildEntity = function (headers) {
601 var data;
602 if (!this.hasEntity() || !this.isEntityEnclosingRequest()) {
603 return data;
604 }
605 if (validator.isBuffer(this.data)) {
606 data = this.data;
607 }
608 else if (validator.isObject(this.data)) {
609 data = Buffer.from(JSON.stringify(this.data), 'utf-8');
610 if (typeof headers['content-type'] === 'undefined') {
611 headers['content-type'] = 'application/json;charset=utf-8';
612 }
613 }
614 else if (validator.isString(this.data)) {
615 data = Buffer.from(this.data, 'utf-8');
616 }
617 else {
618 throw new Error('Request data must be a string, a Buffer or a json serializable object');
619 }
620 // Add Content-Length header if data exists.
621 headers['Content-Length'] = data.length.toString();
622 return data;
623 };
624 HttpRequestConfigImpl.prototype.buildUrl = function () {
625 var fullUrl = this.urlWithProtocol();
626 if (!this.hasEntity() || this.isEntityEnclosingRequest()) {
627 return url.parse(fullUrl);
628 }
629 if (!validator.isObject(this.data)) {
630 throw new Error(this.method + " requests cannot have a body");
631 }
632 // Parse URL and append data to query string.
633 var parsedUrl = new url.URL(fullUrl);
634 var dataObj = this.data;
635 for (var key in dataObj) {
636 if (Object.prototype.hasOwnProperty.call(dataObj, key)) {
637 parsedUrl.searchParams.append(key, dataObj[key]);
638 }
639 }
640 return url.parse(parsedUrl.toString());
641 };
642 HttpRequestConfigImpl.prototype.urlWithProtocol = function () {
643 var fullUrl = this.url;
644 if (fullUrl.startsWith('http://') || fullUrl.startsWith('https://')) {
645 return fullUrl;
646 }
647 return "https://" + fullUrl;
648 };
649 HttpRequestConfigImpl.prototype.hasEntity = function () {
650 return !!this.data;
651 };
652 HttpRequestConfigImpl.prototype.isEntityEnclosingRequest = function () {
653 // GET and HEAD requests do not support entity (body) in request.
654 return this.method !== 'GET' && this.method !== 'HEAD';
655 };
656 return HttpRequestConfigImpl;
657}());
658var AuthorizedHttpClient = /** @class */ (function (_super) {
659 __extends(AuthorizedHttpClient, _super);
660 function AuthorizedHttpClient(app) {
661 var _this = _super.call(this) || this;
662 _this.app = app;
663 return _this;
664 }
665 AuthorizedHttpClient.prototype.send = function (request) {
666 var _this = this;
667 return this.getToken().then(function (token) {
668 var requestCopy = Object.assign({}, request);
669 requestCopy.headers = Object.assign({}, request.headers);
670 var authHeader = 'Authorization';
671 requestCopy.headers[authHeader] = "Bearer " + token;
672 if (!requestCopy.httpAgent && _this.app.options.httpAgent) {
673 requestCopy.httpAgent = _this.app.options.httpAgent;
674 }
675 return _super.prototype.send.call(_this, requestCopy);
676 });
677 };
678 AuthorizedHttpClient.prototype.getToken = function () {
679 return this.app.INTERNAL.getToken()
680 .then(function (accessTokenObj) {
681 return accessTokenObj.accessToken;
682 });
683 };
684 return AuthorizedHttpClient;
685}(HttpClient));
686exports.AuthorizedHttpClient = AuthorizedHttpClient;
687/**
688 * Class that defines all the settings for the backend API endpoint.
689 *
690 * @param endpoint - The Firebase Auth backend endpoint.
691 * @param httpMethod - The http method for that endpoint.
692 * @constructor
693 */
694var ApiSettings = /** @class */ (function () {
695 function ApiSettings(endpoint, httpMethod) {
696 if (httpMethod === void 0) { httpMethod = 'POST'; }
697 this.endpoint = endpoint;
698 this.httpMethod = httpMethod;
699 this.setRequestValidator(null)
700 .setResponseValidator(null);
701 }
702 /** @returns The backend API endpoint. */
703 ApiSettings.prototype.getEndpoint = function () {
704 return this.endpoint;
705 };
706 /** @returns The request HTTP method. */
707 ApiSettings.prototype.getHttpMethod = function () {
708 return this.httpMethod;
709 };
710 /**
711 * @param requestValidator - The request validator.
712 * @returns The current API settings instance.
713 */
714 ApiSettings.prototype.setRequestValidator = function (requestValidator) {
715 var nullFunction = function () { return undefined; };
716 this.requestValidator = requestValidator || nullFunction;
717 return this;
718 };
719 /** @returns The request validator. */
720 ApiSettings.prototype.getRequestValidator = function () {
721 return this.requestValidator;
722 };
723 /**
724 * @param responseValidator - The response validator.
725 * @returns The current API settings instance.
726 */
727 ApiSettings.prototype.setResponseValidator = function (responseValidator) {
728 var nullFunction = function () { return undefined; };
729 this.responseValidator = responseValidator || nullFunction;
730 return this;
731 };
732 /** @returns The response validator. */
733 ApiSettings.prototype.getResponseValidator = function () {
734 return this.responseValidator;
735 };
736 return ApiSettings;
737}());
738exports.ApiSettings = ApiSettings;
739/**
740 * Class used for polling an endpoint with exponential backoff.
741 *
742 * Example usage:
743 * ```
744 * const poller = new ExponentialBackoffPoller();
745 * poller
746 * .poll(() => {
747 * return myRequestToPoll()
748 * .then((responseData: any) => {
749 * if (!isValid(responseData)) {
750 * // Continue polling.
751 * return null;
752 * }
753 *
754 * // Polling complete. Resolve promise with final response data.
755 * return responseData;
756 * });
757 * })
758 * .then((responseData: any) => {
759 * console.log(`Final response: ${responseData}`);
760 * });
761 * ```
762 */
763var ExponentialBackoffPoller = /** @class */ (function (_super) {
764 __extends(ExponentialBackoffPoller, _super);
765 function ExponentialBackoffPoller(initialPollingDelayMillis, maxPollingDelayMillis, masterTimeoutMillis) {
766 if (initialPollingDelayMillis === void 0) { initialPollingDelayMillis = 1000; }
767 if (maxPollingDelayMillis === void 0) { maxPollingDelayMillis = 10000; }
768 if (masterTimeoutMillis === void 0) { masterTimeoutMillis = 60000; }
769 var _this = _super.call(this) || this;
770 _this.initialPollingDelayMillis = initialPollingDelayMillis;
771 _this.maxPollingDelayMillis = maxPollingDelayMillis;
772 _this.masterTimeoutMillis = masterTimeoutMillis;
773 _this.numTries = 0;
774 _this.completed = false;
775 return _this;
776 }
777 /**
778 * Poll the provided callback with exponential backoff.
779 *
780 * @param callback - The callback to be called for each poll. If the
781 * callback resolves to a falsey value, polling will continue. Otherwise, the truthy
782 * resolution will be used to resolve the promise returned by this method.
783 * @returns A Promise which resolves to the truthy value returned by the provided
784 * callback when polling is complete.
785 */
786 ExponentialBackoffPoller.prototype.poll = function (callback) {
787 var _this = this;
788 if (this.pollCallback) {
789 throw new Error('poll() can only be called once per instance of ExponentialBackoffPoller');
790 }
791 this.pollCallback = callback;
792 this.on('poll', this.repoll);
793 this.masterTimer = setTimeout(function () {
794 if (_this.completed) {
795 return;
796 }
797 _this.markCompleted();
798 _this.reject(new Error('ExponentialBackoffPoller deadline exceeded - Master timeout reached'));
799 }, this.masterTimeoutMillis);
800 return new Promise(function (resolve, reject) {
801 _this.resolve = resolve;
802 _this.reject = reject;
803 _this.repoll();
804 });
805 };
806 ExponentialBackoffPoller.prototype.repoll = function () {
807 var _this = this;
808 this.pollCallback()
809 .then(function (result) {
810 if (_this.completed) {
811 return;
812 }
813 if (!result) {
814 _this.repollTimer =
815 setTimeout(function () { return _this.emit('poll'); }, _this.getPollingDelayMillis());
816 _this.numTries++;
817 return;
818 }
819 _this.markCompleted();
820 _this.resolve(result);
821 })
822 .catch(function (err) {
823 if (_this.completed) {
824 return;
825 }
826 _this.markCompleted();
827 _this.reject(err);
828 });
829 };
830 ExponentialBackoffPoller.prototype.getPollingDelayMillis = function () {
831 var increasedPollingDelay = Math.pow(2, this.numTries) * this.initialPollingDelayMillis;
832 return Math.min(increasedPollingDelay, this.maxPollingDelayMillis);
833 };
834 ExponentialBackoffPoller.prototype.markCompleted = function () {
835 this.completed = true;
836 if (this.masterTimer) {
837 clearTimeout(this.masterTimer);
838 }
839 if (this.repollTimer) {
840 clearTimeout(this.repollTimer);
841 }
842 };
843 return ExponentialBackoffPoller;
844}(events_1.EventEmitter));
845exports.ExponentialBackoffPoller = ExponentialBackoffPoller;