firebase-admin 9.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +90 -0
- package/lib/app-check/app-check-api-client-internal.js +197 -0
- package/lib/app-check/app-check.js +79 -0
- package/lib/app-check/index.d.ts +160 -0
- package/lib/app-check/index.js +19 -0
- package/lib/app-check/token-generator.js +161 -0
- package/lib/app-check/token-verifier.js +152 -0
- package/lib/auth/action-code-settings-builder.js +118 -0
- package/lib/auth/auth-api-request.js +1856 -0
- package/lib/auth/auth-config.js +636 -0
- package/lib/auth/auth.js +836 -0
- package/lib/auth/identifier.js +40 -0
- package/lib/auth/index.d.ts +1927 -0
- package/lib/auth/index.js +18 -0
- package/lib/auth/tenant-manager.js +140 -0
- package/lib/auth/tenant.js +171 -0
- package/lib/auth/token-generator.js +200 -0
- package/lib/auth/token-verifier.js +259 -0
- package/lib/auth/user-import-builder.js +387 -0
- package/lib/auth/user-record.js +346 -0
- package/lib/credential/credential-internal.js +391 -0
- package/lib/credential/credential.js +44 -0
- package/lib/credential/index.d.ts +169 -0
- package/lib/credential/index.js +23 -0
- package/lib/database/database-internal.js +266 -0
- package/lib/database/index.d.ts +89 -0
- package/lib/database/index.js +31 -0
- package/lib/default-namespace.js +31 -0
- package/lib/firebase-app.js +349 -0
- package/lib/firebase-namespace-api.d.ts +243 -0
- package/lib/firebase-namespace-api.js +18 -0
- package/lib/firebase-namespace.d.ts +31 -0
- package/lib/firebase-namespace.js +417 -0
- package/lib/firestore/firestore-internal.js +105 -0
- package/lib/firestore/index.d.ts +50 -0
- package/lib/firestore/index.js +47 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.js +27 -0
- package/lib/installations/index.d.ts +81 -0
- package/lib/installations/index.js +18 -0
- package/lib/installations/installations-request-handler.js +117 -0
- package/lib/installations/installations.js +62 -0
- package/lib/instance-id/index.d.ts +83 -0
- package/lib/instance-id/index.js +18 -0
- package/lib/instance-id/instance-id.js +87 -0
- package/lib/machine-learning/index.d.ts +249 -0
- package/lib/machine-learning/index.js +18 -0
- package/lib/machine-learning/machine-learning-api-client.js +304 -0
- package/lib/machine-learning/machine-learning-utils.js +62 -0
- package/lib/machine-learning/machine-learning.js +364 -0
- package/lib/messaging/batch-request-internal.js +129 -0
- package/lib/messaging/index.d.ts +1174 -0
- package/lib/messaging/index.js +18 -0
- package/lib/messaging/messaging-api-request-internal.js +128 -0
- package/lib/messaging/messaging-errors-internal.js +106 -0
- package/lib/messaging/messaging-internal.js +484 -0
- package/lib/messaging/messaging.js +846 -0
- package/lib/project-management/android-app.js +176 -0
- package/lib/project-management/index.d.ts +363 -0
- package/lib/project-management/index.js +41 -0
- package/lib/project-management/ios-app.js +88 -0
- package/lib/project-management/project-management-api-request-internal.js +273 -0
- package/lib/project-management/project-management.js +254 -0
- package/lib/remote-config/index.d.ts +369 -0
- package/lib/remote-config/index.js +18 -0
- package/lib/remote-config/remote-config-api-client-internal.js +407 -0
- package/lib/remote-config/remote-config.js +304 -0
- package/lib/security-rules/index.d.ts +216 -0
- package/lib/security-rules/index.js +18 -0
- package/lib/security-rules/security-rules-api-client-internal.js +237 -0
- package/lib/security-rules/security-rules-internal.js +41 -0
- package/lib/security-rules/security-rules.js +310 -0
- package/lib/storage/index.d.ts +60 -0
- package/lib/storage/index.js +18 -0
- package/lib/storage/storage.js +123 -0
- package/lib/utils/api-request.js +845 -0
- package/lib/utils/crypto-signer.js +237 -0
- package/lib/utils/deep-copy.js +78 -0
- package/lib/utils/error.js +1063 -0
- package/lib/utils/index.js +217 -0
- package/lib/utils/jwt.js +355 -0
- package/lib/utils/validator.js +271 -0
- package/package.json +122 -0
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
/*! firebase-admin v9.12.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
|
+
*/
|
|
19
|
+
var __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
|
+
})();
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.ExponentialBackoffPoller = exports.ApiSettings = exports.AuthorizedHttpClient = exports.parseHttpResponse = exports.HttpClient = exports.defaultRetryConfig = exports.HttpError = void 0;
|
|
34
|
+
var error_1 = require("./error");
|
|
35
|
+
var validator = require("./validator");
|
|
36
|
+
var http = require("http");
|
|
37
|
+
var https = require("https");
|
|
38
|
+
var url = require("url");
|
|
39
|
+
var events_1 = require("events");
|
|
40
|
+
var 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
|
+
*/
|
|
81
|
+
var 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
|
+
}());
|
|
106
|
+
var 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));
|
|
118
|
+
exports.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
|
+
*/
|
|
124
|
+
function defaultRetryConfig() {
|
|
125
|
+
return {
|
|
126
|
+
maxRetries: 4,
|
|
127
|
+
statusCodes: [503],
|
|
128
|
+
ioErrorCodes: ['ECONNRESET', 'ETIMEDOUT'],
|
|
129
|
+
backOffFactor: 0.5,
|
|
130
|
+
maxDelayInMillis: 60 * 1000,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
exports.defaultRetryConfig = defaultRetryConfig;
|
|
134
|
+
/**
|
|
135
|
+
* Ensures that the given RetryConfig object is valid.
|
|
136
|
+
*
|
|
137
|
+
* @param retry The configuration to be validated.
|
|
138
|
+
*/
|
|
139
|
+
function 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
|
+
}
|
|
158
|
+
var 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 {HttpRequest} config HTTP request to be sent.
|
|
178
|
+
* @return {Promise<HttpResponse>} 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 {HttpRequestConfig} config HTTP request to be sent.
|
|
188
|
+
* @param {number} retryAttempts Number of retries performed up to now.
|
|
189
|
+
* @return {Promise<HttpResponse>} 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 {number} retryAttempts Number of retries completed up to now.
|
|
233
|
+
* @param {LowLevelError} err The last encountered error.
|
|
234
|
+
* @returns {[number, boolean]} 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
|
+
}());
|
|
295
|
+
exports.HttpClient = HttpClient;
|
|
296
|
+
/**
|
|
297
|
+
* Parses a full HTTP response message containing both a header and a body.
|
|
298
|
+
*
|
|
299
|
+
* @param {string|Buffer} response The HTTP response to be parsed.
|
|
300
|
+
* @param {HttpRequestConfig} config The request configuration that resulted in the HTTP response.
|
|
301
|
+
* @return {HttpResponse} An object containing the parsed HTTP status, headers and the body.
|
|
302
|
+
*/
|
|
303
|
+
function 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
|
+
}
|
|
336
|
+
exports.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
|
+
*/
|
|
342
|
+
var 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
|
+
*/
|
|
536
|
+
var 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
|
+
}());
|
|
658
|
+
var 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));
|
|
686
|
+
exports.AuthorizedHttpClient = AuthorizedHttpClient;
|
|
687
|
+
/**
|
|
688
|
+
* Class that defines all the settings for the backend API endpoint.
|
|
689
|
+
*
|
|
690
|
+
* @param {string} endpoint The Firebase Auth backend endpoint.
|
|
691
|
+
* @param {HttpMethod} httpMethod The http method for that endpoint.
|
|
692
|
+
* @constructor
|
|
693
|
+
*/
|
|
694
|
+
var 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
|
+
/** @return {string} The backend API endpoint. */
|
|
703
|
+
ApiSettings.prototype.getEndpoint = function () {
|
|
704
|
+
return this.endpoint;
|
|
705
|
+
};
|
|
706
|
+
/** @return {HttpMethod} The request HTTP method. */
|
|
707
|
+
ApiSettings.prototype.getHttpMethod = function () {
|
|
708
|
+
return this.httpMethod;
|
|
709
|
+
};
|
|
710
|
+
/**
|
|
711
|
+
* @param {ApiCallbackFunction} requestValidator The request validator.
|
|
712
|
+
* @return {ApiSettings} 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
|
+
/** @return {ApiCallbackFunction} The request validator. */
|
|
720
|
+
ApiSettings.prototype.getRequestValidator = function () {
|
|
721
|
+
return this.requestValidator;
|
|
722
|
+
};
|
|
723
|
+
/**
|
|
724
|
+
* @param {ApiCallbackFunction} responseValidator The response validator.
|
|
725
|
+
* @return {ApiSettings} 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
|
+
/** @return {ApiCallbackFunction} The response validator. */
|
|
733
|
+
ApiSettings.prototype.getResponseValidator = function () {
|
|
734
|
+
return this.responseValidator;
|
|
735
|
+
};
|
|
736
|
+
return ApiSettings;
|
|
737
|
+
}());
|
|
738
|
+
exports.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
|
+
*/
|
|
763
|
+
var 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 {() => Promise<T>} 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
|
+
* @return {Promise<T>} 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));
|
|
845
|
+
exports.ExponentialBackoffPoller = ExponentialBackoffPoller;
|