1 | ;
|
2 | // Copyright 2014 Google LLC
|
3 | //
|
4 | // Licensed under the Apache License, Version 2.0 (the "License");
|
5 | // you may not use this file except in compliance with the License.
|
6 | // You may obtain a copy of the License at
|
7 | //
|
8 | // http://www.apache.org/licenses/LICENSE-2.0
|
9 | //
|
10 | // Unless required by applicable law or agreed to in writing, software
|
11 | // distributed under the License is distributed on an "AS IS" BASIS,
|
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13 | // See the License for the specific language governing permissions and
|
14 | // limitations under the License.
|
15 | Object.defineProperty(exports, "__esModule", { value: true });
|
16 | exports.util = exports.Util = exports.PartialFailureError = exports.ApiError = void 0;
|
17 | /*!
|
18 | * @module common/util
|
19 | */
|
20 | const projectify_1 = require("@google-cloud/projectify");
|
21 | const ent = require("ent");
|
22 | const extend = require("extend");
|
23 | const google_auth_library_1 = require("google-auth-library");
|
24 | const retryRequest = require("retry-request");
|
25 | const stream_1 = require("stream");
|
26 | const teeny_request_1 = require("teeny-request");
|
27 | const service_1 = require("./service");
|
28 | // eslint-disable-next-line @typescript-eslint/no-var-requires
|
29 | const duplexify = require('duplexify');
|
30 | const requestDefaults = {
|
31 | timeout: 60000,
|
32 | gzip: true,
|
33 | forever: true,
|
34 | pool: {
|
35 | maxSockets: Infinity,
|
36 | },
|
37 | };
|
38 | /**
|
39 | * Default behavior: Automatically retry retriable server errors.
|
40 | *
|
41 | * @const {boolean}
|
42 | * @private
|
43 | */
|
44 | const AUTO_RETRY_DEFAULT = true;
|
45 | /**
|
46 | * Default behavior: Only attempt to retry retriable errors 3 times.
|
47 | *
|
48 | * @const {number}
|
49 | * @private
|
50 | */
|
51 | const MAX_RETRY_DEFAULT = 3;
|
52 | /**
|
53 | * Custom error type for API errors.
|
54 | *
|
55 | * @param {object} errorBody - Error object.
|
56 | */
|
57 | class ApiError extends Error {
|
58 | constructor(errorBodyOrMessage) {
|
59 | super();
|
60 | if (typeof errorBodyOrMessage !== 'object') {
|
61 | this.message = errorBodyOrMessage || '';
|
62 | return;
|
63 | }
|
64 | const errorBody = errorBodyOrMessage;
|
65 | this.code = errorBody.code;
|
66 | this.errors = errorBody.errors;
|
67 | this.response = errorBody.response;
|
68 | try {
|
69 | this.errors = JSON.parse(this.response.body).error.errors;
|
70 | }
|
71 | catch (e) {
|
72 | this.errors = errorBody.errors;
|
73 | }
|
74 | this.message = ApiError.createMultiErrorMessage(errorBody, this.errors);
|
75 | Error.captureStackTrace(this);
|
76 | }
|
77 | /**
|
78 | * Pieces together an error message by combining all unique error messages
|
79 | * returned from a single GoogleError
|
80 | *
|
81 | * @private
|
82 | *
|
83 | * @param {GoogleErrorBody} err The original error.
|
84 | * @param {GoogleInnerError[]} [errors] Inner errors, if any.
|
85 | * @returns {string}
|
86 | */
|
87 | static createMultiErrorMessage(err, errors) {
|
88 | const messages = new Set();
|
89 | if (err.message) {
|
90 | messages.add(err.message);
|
91 | }
|
92 | if (errors && errors.length) {
|
93 | errors.forEach(({ message }) => messages.add(message));
|
94 | }
|
95 | else if (err.response && err.response.body) {
|
96 | messages.add(ent.decode(err.response.body.toString()));
|
97 | }
|
98 | else if (!err.message) {
|
99 | messages.add('A failure occurred during this request.');
|
100 | }
|
101 | let messageArr = Array.from(messages);
|
102 | if (messageArr.length > 1) {
|
103 | messageArr = messageArr.map((message, i) => ` ${i + 1}. ${message}`);
|
104 | messageArr.unshift('Multiple errors occurred during the request. Please see the `errors` array for complete details.\n');
|
105 | messageArr.push('\n');
|
106 | }
|
107 | return messageArr.join('\n');
|
108 | }
|
109 | }
|
110 | exports.ApiError = ApiError;
|
111 | /**
|
112 | * Custom error type for partial errors returned from the API.
|
113 | *
|
114 | * @param {object} b - Error object.
|
115 | */
|
116 | class PartialFailureError extends Error {
|
117 | constructor(b) {
|
118 | super();
|
119 | const errorObject = b;
|
120 | this.errors = errorObject.errors;
|
121 | this.name = 'PartialFailureError';
|
122 | this.response = errorObject.response;
|
123 | this.message = ApiError.createMultiErrorMessage(errorObject, this.errors);
|
124 | }
|
125 | }
|
126 | exports.PartialFailureError = PartialFailureError;
|
127 | class Util {
|
128 | constructor() {
|
129 | this.ApiError = ApiError;
|
130 | this.PartialFailureError = PartialFailureError;
|
131 | }
|
132 | /**
|
133 | * No op.
|
134 | *
|
135 | * @example
|
136 | * function doSomething(callback) {
|
137 | * callback = callback || noop;
|
138 | * }
|
139 | */
|
140 | noop() { }
|
141 | /**
|
142 | * Uniformly process an API response.
|
143 | *
|
144 | * @param {*} err - Error value.
|
145 | * @param {*} resp - Response value.
|
146 | * @param {*} body - Body value.
|
147 | * @param {function} callback - The callback function.
|
148 | */
|
149 | handleResp(err, resp, body, callback) {
|
150 | callback = callback || util.noop;
|
151 | const parsedResp = extend(true, { err: err || null }, resp && util.parseHttpRespMessage(resp), body && util.parseHttpRespBody(body));
|
152 | // Assign the parsed body to resp.body, even if { json: false } was passed
|
153 | // as a request option.
|
154 | // We assume that nobody uses the previously unparsed value of resp.body.
|
155 | if (!parsedResp.err && resp && typeof parsedResp.body === 'object') {
|
156 | parsedResp.resp.body = parsedResp.body;
|
157 | }
|
158 | if (parsedResp.err && resp) {
|
159 | parsedResp.err.response = resp;
|
160 | }
|
161 | callback(parsedResp.err, parsedResp.body, parsedResp.resp);
|
162 | }
|
163 | /**
|
164 | * Sniff an incoming HTTP response message for errors.
|
165 | *
|
166 | * @param {object} httpRespMessage - An incoming HTTP response message from `request`.
|
167 | * @return {object} parsedHttpRespMessage - The parsed response.
|
168 | * @param {?error} parsedHttpRespMessage.err - An error detected.
|
169 | * @param {object} parsedHttpRespMessage.resp - The original response object.
|
170 | */
|
171 | parseHttpRespMessage(httpRespMessage) {
|
172 | const parsedHttpRespMessage = {
|
173 | resp: httpRespMessage,
|
174 | };
|
175 | if (httpRespMessage.statusCode < 200 || httpRespMessage.statusCode > 299) {
|
176 | // Unknown error. Format according to ApiError standard.
|
177 | parsedHttpRespMessage.err = new ApiError({
|
178 | errors: new Array(),
|
179 | code: httpRespMessage.statusCode,
|
180 | message: httpRespMessage.statusMessage,
|
181 | response: httpRespMessage,
|
182 | });
|
183 | }
|
184 | return parsedHttpRespMessage;
|
185 | }
|
186 | /**
|
187 | * Parse the response body from an HTTP request.
|
188 | *
|
189 | * @param {object} body - The response body.
|
190 | * @return {object} parsedHttpRespMessage - The parsed response.
|
191 | * @param {?error} parsedHttpRespMessage.err - An error detected.
|
192 | * @param {object} parsedHttpRespMessage.body - The original body value provided
|
193 | * will try to be JSON.parse'd. If it's successful, the parsed value will
|
194 | * be returned here, otherwise the original value and an error will be returned.
|
195 | */
|
196 | parseHttpRespBody(body) {
|
197 | const parsedHttpRespBody = {
|
198 | body,
|
199 | };
|
200 | if (typeof body === 'string') {
|
201 | try {
|
202 | parsedHttpRespBody.body = JSON.parse(body);
|
203 | }
|
204 | catch (err) {
|
205 | parsedHttpRespBody.body = body;
|
206 | }
|
207 | }
|
208 | if (parsedHttpRespBody.body && parsedHttpRespBody.body.error) {
|
209 | // Error from JSON API.
|
210 | parsedHttpRespBody.err = new ApiError(parsedHttpRespBody.body.error);
|
211 | }
|
212 | return parsedHttpRespBody;
|
213 | }
|
214 | /**
|
215 | * Take a Duplexify stream, fetch an authenticated connection header, and
|
216 | * create an outgoing writable stream.
|
217 | *
|
218 | * @param {Duplexify} dup - Duplexify stream.
|
219 | * @param {object} options - Configuration object.
|
220 | * @param {module:common/connection} options.connection - A connection instance used to get a token with and send the request through.
|
221 | * @param {object} options.metadata - Metadata to send at the head of the request.
|
222 | * @param {object} options.request - Request object, in the format of a standard Node.js http.request() object.
|
223 | * @param {string=} options.request.method - Default: "POST".
|
224 | * @param {string=} options.request.qs.uploadType - Default: "multipart".
|
225 | * @param {string=} options.streamContentType - Default: "application/octet-stream".
|
226 | * @param {function} onComplete - Callback, executed after the writable Request stream has completed.
|
227 | */
|
228 | makeWritableStream(dup, options, onComplete) {
|
229 | onComplete = onComplete || util.noop;
|
230 | const writeStream = new ProgressStream();
|
231 | writeStream.on('progress', evt => dup.emit('progress', evt));
|
232 | dup.setWritable(writeStream);
|
233 | const defaultReqOpts = {
|
234 | method: 'POST',
|
235 | qs: {
|
236 | uploadType: 'multipart',
|
237 | },
|
238 | timeout: 0,
|
239 | maxRetries: 0,
|
240 | };
|
241 | const metadata = options.metadata || {};
|
242 | const reqOpts = extend(true, defaultReqOpts, options.request, {
|
243 | multipart: [
|
244 | {
|
245 | 'Content-Type': 'application/json',
|
246 | body: JSON.stringify(metadata),
|
247 | },
|
248 | {
|
249 | 'Content-Type': metadata.contentType || 'application/octet-stream',
|
250 | body: writeStream,
|
251 | },
|
252 | ],
|
253 | });
|
254 | options.makeAuthenticatedRequest(reqOpts, {
|
255 | onAuthenticated(err, authenticatedReqOpts) {
|
256 | if (err) {
|
257 | dup.destroy(err);
|
258 | return;
|
259 | }
|
260 | const request = teeny_request_1.teenyRequest.defaults(requestDefaults);
|
261 | request(authenticatedReqOpts, (err, resp, body) => {
|
262 | util.handleResp(err, resp, body, (err, data) => {
|
263 | if (err) {
|
264 | dup.destroy(err);
|
265 | return;
|
266 | }
|
267 | dup.emit('response', resp);
|
268 | onComplete(data);
|
269 | });
|
270 | });
|
271 | },
|
272 | });
|
273 | }
|
274 | /**
|
275 | * Returns true if the API request should be retried, given the error that was
|
276 | * given the first time the request was attempted. This is used for rate limit
|
277 | * related errors as well as intermittent server errors.
|
278 | *
|
279 | * @param {error} err - The API error to check if it is appropriate to retry.
|
280 | * @return {boolean} True if the API request should be retried, false otherwise.
|
281 | */
|
282 | shouldRetryRequest(err) {
|
283 | if (err) {
|
284 | if ([408, 429, 500, 502, 503, 504].indexOf(err.code) !== -1) {
|
285 | return true;
|
286 | }
|
287 | if (err.errors) {
|
288 | for (const e of err.errors) {
|
289 | const reason = e.reason;
|
290 | if (reason === 'rateLimitExceeded') {
|
291 | return true;
|
292 | }
|
293 | if (reason === 'userRateLimitExceeded') {
|
294 | return true;
|
295 | }
|
296 | if (reason && reason.includes('EAI_AGAIN')) {
|
297 | return true;
|
298 | }
|
299 | }
|
300 | }
|
301 | }
|
302 | return false;
|
303 | }
|
304 | /**
|
305 | * Get a function for making authenticated requests.
|
306 | *
|
307 | * @param {object} config - Configuration object.
|
308 | * @param {boolean=} config.autoRetry - Automatically retry requests if the
|
309 | * response is related to rate limits or certain intermittent server
|
310 | * errors. We will exponentially backoff subsequent requests by default.
|
311 | * (default: true)
|
312 | * @param {object=} config.credentials - Credentials object.
|
313 | * @param {boolean=} config.customEndpoint - If true, just return the provided request options. Default: false.
|
314 | * @param {boolean=} config.useAuthWithCustomEndpoint - If true, will authenticate when using a custom endpoint. Default: false.
|
315 | * @param {string=} config.email - Account email address, required for PEM/P12 usage.
|
316 | * @param {number=} config.maxRetries - Maximum number of automatic retries attempted before returning the error. (default: 3)
|
317 | * @param {string=} config.keyFile - Path to a .json, .pem, or .p12 keyfile.
|
318 | * @param {array} config.scopes - Array of scopes required for the API.
|
319 | */
|
320 | makeAuthenticatedRequestFactory(config) {
|
321 | const googleAutoAuthConfig = extend({}, config);
|
322 | if (googleAutoAuthConfig.projectId === service_1.DEFAULT_PROJECT_ID_TOKEN) {
|
323 | delete googleAutoAuthConfig.projectId;
|
324 | }
|
325 | let authClient;
|
326 | if (googleAutoAuthConfig.authClient instanceof google_auth_library_1.GoogleAuth) {
|
327 | // Use an existing `GoogleAuth`
|
328 | authClient = googleAutoAuthConfig.authClient;
|
329 | }
|
330 | else {
|
331 | // Pass an `AuthClient` to `GoogleAuth`, if available
|
332 | const config = {
|
333 | ...googleAutoAuthConfig,
|
334 | authClient: googleAutoAuthConfig.authClient,
|
335 | };
|
336 | authClient = new google_auth_library_1.GoogleAuth(config);
|
337 | }
|
338 | function makeAuthenticatedRequest(reqOpts, optionsOrCallback) {
|
339 | let stream;
|
340 | let projectId;
|
341 | const reqConfig = extend({}, config);
|
342 | let activeRequest_;
|
343 | if (!optionsOrCallback) {
|
344 | stream = duplexify();
|
345 | reqConfig.stream = stream;
|
346 | }
|
347 | const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : undefined;
|
348 | const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : undefined;
|
349 | async function setProjectId() {
|
350 | projectId = await authClient.getProjectId();
|
351 | }
|
352 | const onAuthenticated = async (err, authenticatedReqOpts) => {
|
353 | const authLibraryError = err;
|
354 | const autoAuthFailed = err &&
|
355 | err.message.indexOf('Could not load the default credentials') > -1;
|
356 | if (autoAuthFailed) {
|
357 | // Even though authentication failed, the API might not actually
|
358 | // care.
|
359 | authenticatedReqOpts = reqOpts;
|
360 | }
|
361 | if (!err || autoAuthFailed) {
|
362 | try {
|
363 | // Try with existing `projectId` value
|
364 | authenticatedReqOpts = util.decorateRequest(authenticatedReqOpts, projectId);
|
365 | err = null;
|
366 | }
|
367 | catch (e) {
|
368 | if (e instanceof projectify_1.MissingProjectIdError) {
|
369 | // A `projectId` was required, but we don't have one.
|
370 | try {
|
371 | // Attempt to get the `projectId`
|
372 | await setProjectId();
|
373 | authenticatedReqOpts = util.decorateRequest(authenticatedReqOpts, projectId);
|
374 | err = null;
|
375 | }
|
376 | catch (e) {
|
377 | // Re-use the "Could not load the default credentials error" if
|
378 | // auto auth failed.
|
379 | err = err || e;
|
380 | }
|
381 | }
|
382 | else {
|
383 | // Some other error unrelated to missing `projectId`
|
384 | err = err || e;
|
385 | }
|
386 | }
|
387 | }
|
388 | if (err) {
|
389 | if (stream) {
|
390 | stream.destroy(err);
|
391 | }
|
392 | else {
|
393 | const fn = options && options.onAuthenticated
|
394 | ? options.onAuthenticated
|
395 | : callback;
|
396 | fn(err);
|
397 | }
|
398 | return;
|
399 | }
|
400 | if (options && options.onAuthenticated) {
|
401 | options.onAuthenticated(null, authenticatedReqOpts);
|
402 | }
|
403 | else {
|
404 | activeRequest_ = util.makeRequest(authenticatedReqOpts, reqConfig, (apiResponseError, ...params) => {
|
405 | if (apiResponseError &&
|
406 | apiResponseError.code === 401 &&
|
407 | authLibraryError) {
|
408 | // Re-use the "Could not load the default credentials error" if
|
409 | // the API request failed due to missing credentials.
|
410 | apiResponseError = authLibraryError;
|
411 | }
|
412 | callback(apiResponseError, ...params);
|
413 | });
|
414 | }
|
415 | };
|
416 | const prepareRequest = async () => {
|
417 | try {
|
418 | const getProjectId = async () => {
|
419 | if (config.projectId &&
|
420 | config.projectId !== service_1.DEFAULT_PROJECT_ID_TOKEN) {
|
421 | // The user provided a project ID. We don't need to check with the
|
422 | // auth client, it could be incorrect.
|
423 | return config.projectId;
|
424 | }
|
425 | if (config.projectIdRequired === false) {
|
426 | // A projectId is not required. Return the default.
|
427 | return service_1.DEFAULT_PROJECT_ID_TOKEN;
|
428 | }
|
429 | return setProjectId();
|
430 | };
|
431 | const authorizeRequest = async () => {
|
432 | if (reqConfig.customEndpoint &&
|
433 | !reqConfig.useAuthWithCustomEndpoint) {
|
434 | // Using a custom API override. Do not use `google-auth-library` for
|
435 | // authentication. (ex: connecting to a local Datastore server)
|
436 | return reqOpts;
|
437 | }
|
438 | else {
|
439 | return authClient.authorizeRequest(reqOpts);
|
440 | }
|
441 | };
|
442 | const [_projectId, authorizedReqOpts] = await Promise.all([
|
443 | getProjectId(),
|
444 | authorizeRequest(),
|
445 | ]);
|
446 | if (_projectId) {
|
447 | projectId = _projectId;
|
448 | }
|
449 | return onAuthenticated(null, authorizedReqOpts);
|
450 | }
|
451 | catch (e) {
|
452 | return onAuthenticated(e);
|
453 | }
|
454 | };
|
455 | prepareRequest();
|
456 | if (stream) {
|
457 | return stream;
|
458 | }
|
459 | return {
|
460 | abort() {
|
461 | setImmediate(() => {
|
462 | if (activeRequest_) {
|
463 | activeRequest_.abort();
|
464 | activeRequest_ = null;
|
465 | }
|
466 | });
|
467 | },
|
468 | };
|
469 | }
|
470 | const mar = makeAuthenticatedRequest;
|
471 | mar.getCredentials = authClient.getCredentials.bind(authClient);
|
472 | mar.authClient = authClient;
|
473 | return mar;
|
474 | }
|
475 | /**
|
476 | * Make a request through the `retryRequest` module with built-in error
|
477 | * handling and exponential back off.
|
478 | *
|
479 | * @param {object} reqOpts - Request options in the format `request` expects.
|
480 | * @param {object=} config - Configuration object.
|
481 | * @param {boolean=} config.autoRetry - Automatically retry requests if the
|
482 | * response is related to rate limits or certain intermittent server
|
483 | * errors. We will exponentially backoff subsequent requests by default.
|
484 | * (default: true)
|
485 | * @param {number=} config.maxRetries - Maximum number of automatic retries
|
486 | * attempted before returning the error. (default: 3)
|
487 | * @param {object=} config.request - HTTP module for request calls.
|
488 | * @param {function} callback - The callback function.
|
489 | */
|
490 | makeRequest(reqOpts, config, callback) {
|
491 | var _a, _b, _c, _d, _e, _f, _g;
|
492 | let autoRetryValue = AUTO_RETRY_DEFAULT;
|
493 | if (config.autoRetry !== undefined &&
|
494 | ((_a = config.retryOptions) === null || _a === void 0 ? void 0 : _a.autoRetry) !== undefined) {
|
495 | throw new ApiError('autoRetry is deprecated. Use retryOptions.autoRetry instead.');
|
496 | }
|
497 | else if (config.autoRetry !== undefined) {
|
498 | autoRetryValue = config.autoRetry;
|
499 | }
|
500 | else if (((_b = config.retryOptions) === null || _b === void 0 ? void 0 : _b.autoRetry) !== undefined) {
|
501 | autoRetryValue = config.retryOptions.autoRetry;
|
502 | }
|
503 | let maxRetryValue = MAX_RETRY_DEFAULT;
|
504 | if (config.maxRetries && ((_c = config.retryOptions) === null || _c === void 0 ? void 0 : _c.maxRetries)) {
|
505 | throw new ApiError('maxRetries is deprecated. Use retryOptions.maxRetries instead.');
|
506 | }
|
507 | else if (config.maxRetries) {
|
508 | maxRetryValue = config.maxRetries;
|
509 | }
|
510 | else if ((_d = config.retryOptions) === null || _d === void 0 ? void 0 : _d.maxRetries) {
|
511 | maxRetryValue = config.retryOptions.maxRetries;
|
512 | }
|
513 | const options = {
|
514 | request: teeny_request_1.teenyRequest.defaults(requestDefaults),
|
515 | retries: autoRetryValue !== false ? maxRetryValue : 0,
|
516 | noResponseRetries: autoRetryValue !== false ? maxRetryValue : 0,
|
517 | shouldRetryFn(httpRespMessage) {
|
518 | var _a, _b;
|
519 | const err = util.parseHttpRespMessage(httpRespMessage).err;
|
520 | if ((_a = config.retryOptions) === null || _a === void 0 ? void 0 : _a.retryableErrorFn) {
|
521 | return err && ((_b = config.retryOptions) === null || _b === void 0 ? void 0 : _b.retryableErrorFn(err));
|
522 | }
|
523 | return err && util.shouldRetryRequest(err);
|
524 | },
|
525 | maxRetryDelay: (_e = config.retryOptions) === null || _e === void 0 ? void 0 : _e.maxRetryDelay,
|
526 | retryDelayMultiplier: (_f = config.retryOptions) === null || _f === void 0 ? void 0 : _f.retryDelayMultiplier,
|
527 | totalTimeout: (_g = config.retryOptions) === null || _g === void 0 ? void 0 : _g.totalTimeout,
|
528 | };
|
529 | if (typeof reqOpts.maxRetries === 'number') {
|
530 | options.retries = reqOpts.maxRetries;
|
531 | }
|
532 | if (!config.stream) {
|
533 | return retryRequest(reqOpts, options, (err, response, body) => {
|
534 | util.handleResp(err, response, body, callback);
|
535 | });
|
536 | }
|
537 | const dup = config.stream;
|
538 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
539 | let requestStream;
|
540 | const isGetRequest = (reqOpts.method || 'GET').toUpperCase() === 'GET';
|
541 | if (isGetRequest) {
|
542 | requestStream = retryRequest(reqOpts, options);
|
543 | dup.setReadable(requestStream);
|
544 | }
|
545 | else {
|
546 | // Streaming writable HTTP requests cannot be retried.
|
547 | requestStream = options.request(reqOpts);
|
548 | dup.setWritable(requestStream);
|
549 | }
|
550 | // Replay the Request events back to the stream.
|
551 | requestStream
|
552 | .on('error', dup.destroy.bind(dup))
|
553 | .on('response', dup.emit.bind(dup, 'response'))
|
554 | .on('complete', dup.emit.bind(dup, 'complete'));
|
555 | dup.abort = requestStream.abort;
|
556 | return dup;
|
557 | }
|
558 | /**
|
559 | * Decorate the options about to be made in a request.
|
560 | *
|
561 | * @param {object} reqOpts - The options to be passed to `request`.
|
562 | * @param {string} projectId - The project ID.
|
563 | * @return {object} reqOpts - The decorated reqOpts.
|
564 | */
|
565 | decorateRequest(reqOpts, projectId) {
|
566 | delete reqOpts.autoPaginate;
|
567 | delete reqOpts.autoPaginateVal;
|
568 | delete reqOpts.objectMode;
|
569 | if (reqOpts.qs !== null && typeof reqOpts.qs === 'object') {
|
570 | delete reqOpts.qs.autoPaginate;
|
571 | delete reqOpts.qs.autoPaginateVal;
|
572 | reqOpts.qs = (0, projectify_1.replaceProjectIdToken)(reqOpts.qs, projectId);
|
573 | }
|
574 | if (Array.isArray(reqOpts.multipart)) {
|
575 | reqOpts.multipart = reqOpts.multipart.map(part => {
|
576 | return (0, projectify_1.replaceProjectIdToken)(part, projectId);
|
577 | });
|
578 | }
|
579 | if (reqOpts.json !== null && typeof reqOpts.json === 'object') {
|
580 | delete reqOpts.json.autoPaginate;
|
581 | delete reqOpts.json.autoPaginateVal;
|
582 | reqOpts.json = (0, projectify_1.replaceProjectIdToken)(reqOpts.json, projectId);
|
583 | }
|
584 | reqOpts.uri = (0, projectify_1.replaceProjectIdToken)(reqOpts.uri, projectId);
|
585 | return reqOpts;
|
586 | }
|
587 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
588 | isCustomType(unknown, module) {
|
589 | function getConstructorName(obj) {
|
590 | return obj.constructor && obj.constructor.name.toLowerCase();
|
591 | }
|
592 | const moduleNameParts = module.split('/');
|
593 | const parentModuleName = moduleNameParts[0] && moduleNameParts[0].toLowerCase();
|
594 | const subModuleName = moduleNameParts[1] && moduleNameParts[1].toLowerCase();
|
595 | if (subModuleName && getConstructorName(unknown) !== subModuleName) {
|
596 | return false;
|
597 | }
|
598 | let walkingModule = unknown;
|
599 | // eslint-disable-next-line no-constant-condition
|
600 | while (true) {
|
601 | if (getConstructorName(walkingModule) === parentModuleName) {
|
602 | return true;
|
603 | }
|
604 | walkingModule = walkingModule.parent;
|
605 | if (!walkingModule) {
|
606 | return false;
|
607 | }
|
608 | }
|
609 | }
|
610 | /**
|
611 | * Create a properly-formatted User-Agent string from a package.json file.
|
612 | *
|
613 | * @param {object} packageJson - A module's package.json file.
|
614 | * @return {string} userAgent - The formatted User-Agent string.
|
615 | */
|
616 | getUserAgentFromPackageJson(packageJson) {
|
617 | const hyphenatedPackageName = packageJson.name
|
618 | .replace('@google-cloud', 'gcloud-node') // For legacy purposes.
|
619 | .replace('/', '-'); // For UA spec-compliance purposes.
|
620 | return hyphenatedPackageName + '/' + packageJson.version;
|
621 | }
|
622 | /**
|
623 | * Given two parameters, figure out if this is either:
|
624 | * - Just a callback function
|
625 | * - An options object, and then a callback function
|
626 | * @param optionsOrCallback An options object or callback.
|
627 | * @param cb A potentially undefined callback.
|
628 | */
|
629 | maybeOptionsOrCallback(optionsOrCallback, cb) {
|
630 | return typeof optionsOrCallback === 'function'
|
631 | ? [{}, optionsOrCallback]
|
632 | : [optionsOrCallback, cb];
|
633 | }
|
634 | }
|
635 | exports.Util = Util;
|
636 | /**
|
637 | * Basic Passthrough Stream that records the number of bytes read
|
638 | * every time the cursor is moved.
|
639 | */
|
640 | class ProgressStream extends stream_1.Transform {
|
641 | constructor() {
|
642 | super(...arguments);
|
643 | this.bytesRead = 0;
|
644 | }
|
645 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
646 | _transform(chunk, encoding, callback) {
|
647 | this.bytesRead += chunk.length;
|
648 | this.emit('progress', { bytesWritten: this.bytesRead, contentLength: '*' });
|
649 | this.push(chunk);
|
650 | callback();
|
651 | }
|
652 | }
|
653 | const util = new Util();
|
654 | exports.util = util;
|
655 | //# sourceMappingURL=util.js.map |
\ | No newline at end of file |