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