@verdaccio/proxy 6.0.0-6-next.20 → 6.0.0-6-next.21

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.
@@ -1,28 +1,27 @@
1
- /* global AbortController */
2
1
  import JSONStream from 'JSONStream';
3
2
  import buildDebug from 'debug';
3
+ import got, { RequiredRetryOptions, Headers as gotHeaders } from 'got';
4
+ import type { Agents, Options } from 'got';
4
5
  import _ from 'lodash';
5
- import requestDeprecated from 'request';
6
6
  import Stream, { PassThrough, Readable } from 'stream';
7
7
  import { Headers, fetch as undiciFetch } from 'undici';
8
8
  import { URL } from 'url';
9
9
 
10
10
  import {
11
- CHARACTER_ENCODING,
11
+ API_ERROR,
12
12
  HEADERS,
13
- HEADER_TYPE,
14
13
  HTTP_STATUS,
15
14
  TOKEN_BASIC,
16
15
  TOKEN_BEARER,
17
16
  constants,
18
17
  errorUtils,
19
18
  searchUtils,
20
- validatioUtils,
21
19
  } from '@verdaccio/core';
22
- import { ReadTarball } from '@verdaccio/streams';
23
- import { Callback, Config, IReadTarball, Logger, UpLinkConf } from '@verdaccio/types';
20
+ import { Manifest } from '@verdaccio/types';
21
+ import { Config, Logger, UpLinkConf } from '@verdaccio/types';
24
22
  import { buildToken } from '@verdaccio/utils';
25
23
 
24
+ import CustomAgents, { AgentOptionsConf } from './agent';
26
25
  import { parseInterval } from './proxy-utils';
27
26
 
28
27
  const LoggerApi = require('@verdaccio/logger');
@@ -39,7 +38,7 @@ const contentTypeAccept = `${jsonContentType};`;
39
38
  /**
40
39
  * Just a helper (`config[key] || default` doesn't work because of zeroes)
41
40
  */
42
- const setConfig = (config, key, def): string => {
41
+ const setConfig = (config: UpLinkConfLocal, key: string, def): string => {
43
42
  return _.isNil(config[key]) === false ? config[key] : def;
44
43
  };
45
44
 
@@ -70,9 +69,21 @@ export interface IProxy {
70
69
  max_fails: number;
71
70
  fail_timeout: number;
72
71
  upname: string;
73
- fetchTarball(url: string): IReadTarball;
74
72
  search(options: ProxySearchParams): Promise<Stream.Readable>;
75
- getRemoteMetadata(name: string, options: any, callback: Callback): void;
73
+ getRemoteMetadataNext(name: string, options: ISyncUplinksOptions): Promise<[Manifest, string]>;
74
+ fetchTarballNext(
75
+ url: string,
76
+ options: Pick<ISyncUplinksOptions, 'remoteAddress' | 'etag' | 'retry'>
77
+ ): PassThrough;
78
+ }
79
+
80
+ // this type is need it by storage
81
+ export { Options as FetchOptions };
82
+
83
+ export interface ISyncUplinksOptions extends Options {
84
+ uplinksLook?: boolean;
85
+ etag?: string;
86
+ remoteAddress?: string;
76
87
  }
77
88
 
78
89
  /**
@@ -91,33 +102,33 @@ class ProxyStorage implements IProxy {
91
102
  public timeout: number;
92
103
  public max_fails: number;
93
104
  public fail_timeout: number;
94
- public agent_options: any;
105
+ public agent_options: AgentOptionsConf;
95
106
  // FIXME: upname is assigned to each instance
96
107
  // @ts-ignore
97
108
  public upname: string;
98
- // FIXME: proxy can be boolean or object, something smells here
99
- // @ts-ignore
100
- public proxy: any;
109
+ public proxy: string | undefined;
110
+ private agent: Agents;
101
111
  // @ts-ignore
102
112
  public last_request_time: number | null;
103
113
  public strict_ssl: boolean;
114
+ private retry: Partial<RequiredRetryOptions> | number;
104
115
 
105
- /**
106
- * Constructor
107
- * @param {*} config
108
- * @param {*} mainConfig
109
- */
110
- public constructor(config: UpLinkConfLocal, mainConfig: Config) {
116
+ public constructor(config: UpLinkConfLocal, mainConfig: Config, agent?: Agents) {
111
117
  this.config = config;
112
118
  this.failed_requests = 0;
113
119
  this.userAgent = mainConfig.user_agent;
114
120
  this.ca = config.ca;
115
121
  this.logger = LoggerApi.logger.child({ sub: 'out' });
116
122
  this.server_id = mainConfig.server_id;
117
-
123
+ this.agent_options = setConfig(this.config, 'agent_options', {
124
+ keepAlive: true,
125
+ maxSockets: 40,
126
+ maxFreeSockets: 10,
127
+ }) as AgentOptionsConf;
118
128
  this.url = new URL(this.config.url);
119
- this._setupProxy(this.url.hostname, config, mainConfig, this.url.protocol === 'https:');
120
-
129
+ const isHTTPS = this.url.protocol === 'https:';
130
+ this._setupProxy(this.url.hostname, config, mainConfig, isHTTPS);
131
+ this.agent = agent ?? this.getAgent();
121
132
  this.config.url = this.config.url.replace(/\/$/, '');
122
133
 
123
134
  if (this.config.timeout && Number(this.config.timeout) >= 1000) {
@@ -133,192 +144,25 @@ class ProxyStorage implements IProxy {
133
144
 
134
145
  // a bunch of different configurable timers
135
146
  this.maxage = parseInterval(setConfig(this.config, 'maxage', '2m'));
147
+ // https://github.com/sindresorhus/got/blob/main/documentation/6-timeout.md
136
148
  this.timeout = parseInterval(setConfig(this.config, 'timeout', '30s'));
137
- this.max_fails = Number(setConfig(this.config, 'max_fails', 2));
149
+ this.max_fails = Number(setConfig(this.config, 'max_fails', this.config.max_fails ?? 2));
138
150
  this.fail_timeout = parseInterval(setConfig(this.config, 'fail_timeout', '5m'));
139
151
  this.strict_ssl = Boolean(setConfig(this.config, 'strict_ssl', true));
140
- this.agent_options = setConfig(this.config, 'agent_options', {
141
- keepAlive: true,
142
- maxSockets: 40,
143
- maxFreeSockets: 10,
144
- });
152
+ this.retry = { limit: this.max_fails ?? 2 };
145
153
  }
146
154
 
147
- /**
148
- * Fetch an asset.
149
- * @param {*} options
150
- * @param {*} cb
151
- * @return {Request}
152
- */
153
- private request(options: any, cb?: Callback): Stream.Readable {
154
- let json;
155
-
156
- if (this._statusCheck() === false) {
157
- const streamRead = new Stream.Readable();
158
-
159
- process.nextTick(function (): void {
160
- if (cb) {
161
- cb(errorUtils.getInternalError(errorUtils.API_ERROR.UPLINK_OFFLINE));
162
- }
163
- streamRead.emit('error', errorUtils.getInternalError(errorUtils.API_ERROR.UPLINK_OFFLINE));
164
- });
165
- streamRead._read = function (): void {};
166
- // preventing 'Uncaught, unspecified "error" event'
167
- streamRead.on('error', function (): void {});
168
- return streamRead;
169
- }
170
-
171
- const self = this;
172
- const headers: Headers = this._setHeaders(options);
173
-
174
- this._addProxyHeaders(options.req, headers);
175
- this._overrideWithUpLinkConfLocaligHeaders(headers);
176
-
177
- const method = options.method || 'GET';
178
- const uri = options.uri_full || this.config.url + options.uri;
179
-
180
- self.logger.info(
181
- {
182
- method: method,
183
- headers: headers,
184
- uri: uri,
185
- },
186
- "making request: '@{method} @{uri}'"
187
- );
188
-
189
- if (validatioUtils.isObject(options.json)) {
190
- json = JSON.stringify(options.json);
191
- headers['Content-Type'] = headers['Content-Type'] || HEADERS.JSON;
192
- }
193
-
194
- const requestCallback = cb
195
- ? function (err, res, body): void {
196
- let error;
197
- const responseLength = err ? 0 : body.length;
198
- // $FlowFixMe
199
- processBody();
200
- logActivity();
201
- // $FlowFixMe
202
- cb(err, res, body);
203
-
204
- /**
205
- * Perform a decode.
206
- */
207
- function processBody(): void {
208
- if (err) {
209
- error = err.message;
210
- return;
211
- }
212
-
213
- if (options.json && res.statusCode < 300) {
214
- try {
215
- // $FlowFixMe
216
- body = JSON.parse(body.toString(CHARACTER_ENCODING.UTF8));
217
- } catch (_err: any) {
218
- body = {};
219
- err = _err;
220
- error = err.message;
221
- }
222
- }
223
-
224
- if (!err && validatioUtils.isObject(body)) {
225
- if (_.isString(body.error)) {
226
- error = body.error;
227
- }
228
- }
229
- }
230
- /**
231
- * Perform a log.
232
- */
233
- function logActivity(): void {
234
- let message = "@{!status}, req: '@{request.method} @{request.url}'";
235
- // FIXME: use LOG_VERDACCIO_BYTES
236
- message += error ? ', error: @{!error}' : ', bytes: @{bytes.in}/@{bytes.out}';
237
- self.logger.http(
238
- {
239
- // if error is null/false change this to undefined so it wont log
240
- err: err || undefined,
241
- request: { method: method, url: uri },
242
- status: res != null ? res.statusCode : 'ERR',
243
- error: error,
244
- bytes: {
245
- in: json ? json.length : 0,
246
- out: responseLength || 0,
247
- },
248
- },
249
- message
250
- );
251
- }
252
- }
253
- : undefined;
254
-
255
- let requestOptions = {
256
- url: uri,
257
- method: method,
258
- headers: headers,
259
- body: json,
260
- proxy: this.proxy,
261
- encoding: null,
262
- gzip: true,
263
- timeout: this.timeout,
264
- strictSSL: this.strict_ssl,
265
- agentOptions: this.agent_options,
266
- };
267
-
268
- if (this.ca) {
269
- requestOptions = Object.assign({}, requestOptions, {
270
- ca: this.ca,
271
- });
155
+ private getAgent() {
156
+ if (!this.agent) {
157
+ // TODO: the config.ca (certificates) is not yet injected here
158
+ const agentInstance = new CustomAgents(this.config.url, this.proxy, this.agent_options);
159
+ return agentInstance.get();
160
+ } else {
161
+ return this.agent;
272
162
  }
273
-
274
- const req = requestDeprecated(requestOptions, requestCallback);
275
-
276
- let statusCalled = false;
277
- req.on('response', function (res): void {
278
- // FIXME: _verdaccio_aborted seems not used
279
- // @ts-ignore
280
- if (!req._verdaccio_aborted && !statusCalled) {
281
- statusCalled = true;
282
- self._statusCheck(true);
283
- }
284
-
285
- if (_.isNil(requestCallback) === false) {
286
- (function do_log(): void {
287
- const message = "@{!status}, req: '@{request.method} @{request.url}' (streaming)";
288
- self.logger.http(
289
- {
290
- request: {
291
- method: method,
292
- url: uri,
293
- },
294
- status: _.isNull(res) === false ? res.statusCode : 'ERR',
295
- },
296
- message
297
- );
298
- })();
299
- }
300
- });
301
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
302
- req.on('error', function (_err): void {
303
- // FIXME: _verdaccio_aborted seems not used
304
- // @ts-ignore
305
- if (!req._verdaccio_aborted && !statusCalled) {
306
- statusCalled = true;
307
- self._statusCheck(false);
308
- }
309
- });
310
- // @ts-ignore
311
- return req;
312
163
  }
313
164
 
314
- /**
315
- * Set default headers.
316
- * @param {Object} options
317
- * @return {Object}
318
- * @private
319
- */
320
- private _setHeaders(options: any): Headers {
321
- const headers = options.headers || {};
165
+ public getHeadersNext(headers = {}): gotHeaders {
322
166
  const accept = HEADERS.ACCEPT;
323
167
  const acceptEncoding = HEADERS.ACCEPT_ENCODING;
324
168
  const userAgent = HEADERS.USER_AGENT;
@@ -327,8 +171,7 @@ class ProxyStorage implements IProxy {
327
171
  headers[acceptEncoding] = headers[acceptEncoding] || 'gzip';
328
172
  // registry.npmjs.org will only return search result if user-agent include string 'npm'
329
173
  headers[userAgent] = headers[userAgent] || `npm (${this.userAgent})`;
330
-
331
- return this._setAuth(headers);
174
+ return this.setAuthNext(headers);
332
175
  }
333
176
 
334
177
  /**
@@ -337,10 +180,9 @@ class ProxyStorage implements IProxy {
337
180
  * @return {Object}
338
181
  * @private
339
182
  */
340
- private _setAuth(headers: any): Headers {
183
+ private setAuthNext(headers: gotHeaders): gotHeaders {
341
184
  const { auth } = this.config;
342
-
343
- if (_.isNil(auth) || headers[HEADERS.AUTHORIZATION]) {
185
+ if (typeof auth === 'undefined' || typeof headers[HEADERS.AUTHORIZATION] === 'string') {
344
186
  return headers;
345
187
  }
346
188
 
@@ -353,13 +195,12 @@ class ProxyStorage implements IProxy {
353
195
  // https://github.com/verdaccio/verdaccio/releases/tag/v2.5.0
354
196
  let token: any;
355
197
  const tokenConf: any = auth;
356
-
357
198
  if (_.isNil(tokenConf.token) === false && _.isString(tokenConf.token)) {
358
199
  token = tokenConf.token;
359
200
  } else if (_.isNil(tokenConf.token_env) === false) {
360
- if (_.isString(tokenConf.token_env)) {
201
+ if (typeof tokenConf.token_env === 'string') {
361
202
  token = process.env[tokenConf.token_env];
362
- } else if (_.isBoolean(tokenConf.token_env) && tokenConf.token_env) {
203
+ } else if (typeof tokenConf.token_env === 'boolean' && tokenConf.token_env) {
363
204
  token = process.env.NPM_TOKEN;
364
205
  } else {
365
206
  this.logger.error(constants.ERROR_CODE.token_required);
@@ -369,7 +210,7 @@ class ProxyStorage implements IProxy {
369
210
  token = process.env.NPM_TOKEN;
370
211
  }
371
212
 
372
- if (_.isNil(token)) {
213
+ if (typeof token === 'undefined') {
373
214
  this._throwErrorAuth(constants.ERROR_CODE.token_required);
374
215
  }
375
216
 
@@ -426,6 +267,7 @@ class ProxyStorage implements IProxy {
426
267
 
427
268
  * @param {Object} headers
428
269
  * @private
270
+ * @deprecated use applyUplinkHeaders
429
271
  */
430
272
  private _overrideWithUpLinkConfLocaligHeaders(headers: Headers): any {
431
273
  if (!this.config.headers) {
@@ -439,98 +281,200 @@ class ProxyStorage implements IProxy {
439
281
  }
440
282
  }
441
283
 
442
- /**
443
- * Get a remote package metadata
444
- * @param {*} name package name
445
- * @param {*} options request options, eg: eTag.
446
- * @param {*} callback
447
- */
448
- public getRemoteMetadata(name: string, options: any, callback: Callback): void {
449
- const headers = {};
284
+ private applyUplinkHeaders(headers: gotHeaders): gotHeaders {
285
+ if (!this.config.headers) {
286
+ return headers;
287
+ }
288
+
289
+ // add/override headers specified in the config
290
+ /* eslint guard-for-in: 0 */
291
+ for (const key in this.config.headers) {
292
+ headers[key] = this.config.headers[key];
293
+ }
294
+ return headers;
295
+ }
296
+
297
+ public async getRemoteMetadataNext(
298
+ name: string,
299
+ options: ISyncUplinksOptions
300
+ ): Promise<[Manifest, string]> {
301
+ if (this._ifRequestFailure()) {
302
+ throw errorUtils.getInternalError(API_ERROR.UPLINK_OFFLINE);
303
+ }
304
+
305
+ // FUTURE: allow mix headers that comes from the client
306
+ debug('get metadata for %s', name);
307
+ let headers = this.getHeadersNext(options?.headers);
308
+ headers = this.addProxyHeaders(headers, options.remoteAddress);
309
+ headers = this.applyUplinkHeaders(headers);
310
+ // the following headers cannot be overwritten
450
311
  if (_.isNil(options.etag) === false) {
451
- headers['If-None-Match'] = options.etag;
312
+ headers[HEADERS.NONE_MATCH] = options.etag;
452
313
  headers[HEADERS.ACCEPT] = contentTypeAccept;
453
314
  }
315
+ const method = options.method || 'GET';
316
+ const uri = this.config.url + `/${encode(name)}`;
317
+ debug('request uri for %s retry %s', uri);
318
+ let response;
319
+ let responseLength = 0;
320
+ try {
321
+ const retry = options?.retry ?? this.retry;
322
+ debug('retry times %s for %s', retry, uri);
323
+ response = await got(uri, {
324
+ headers,
325
+ responseType: 'json',
326
+ method,
327
+ agent: this.agent,
328
+ retry,
329
+ // @ts-ignore
330
+ timeout: { request: options?.timeout ?? this.timeout },
331
+ hooks: {
332
+ afterResponse: [
333
+ (afterResponse) => {
334
+ const code = afterResponse.statusCode;
335
+ debug('code response %s', code);
336
+ if (code >= HTTP_STATUS.OK && code < HTTP_STATUS.MULTIPLE_CHOICES) {
337
+ if (this.failed_requests >= this.max_fails) {
338
+ this.failed_requests = 0;
339
+ this.logger.warn(
340
+ {
341
+ host: this.url.host,
342
+ },
343
+ 'host @{host} is now online'
344
+ );
345
+ }
346
+ }
454
347
 
455
- this.request(
456
- {
457
- uri: `/${encode(name)}`,
458
- json: true,
459
- headers: headers,
460
- req: options.req,
461
- },
462
- (err, res, body): void => {
463
- if (err) {
464
- return callback(err);
465
- }
466
- if (res.statusCode === HTTP_STATUS.NOT_FOUND) {
467
- return callback(errorUtils.getNotFound(errorUtils.API_ERROR.NOT_PACKAGE_UPLINK));
348
+ return afterResponse;
349
+ },
350
+ ],
351
+ beforeRetry: [
352
+ // FUTURE: got 12.0.0, the option arg should be removed
353
+ (_options, error: any, count) => {
354
+ this.failed_requests = count ?? 0;
355
+ this.logger.info(
356
+ {
357
+ request: {
358
+ method: method,
359
+ url: uri,
360
+ },
361
+ error: error.message,
362
+ retryCount: this.failed_requests,
363
+ },
364
+ "retry @{retryCount} req: '@{request.method} @{request.url}'"
365
+ );
366
+ if (this.failed_requests >= this.max_fails) {
367
+ this.logger.warn(
368
+ {
369
+ host: this.url.host,
370
+ },
371
+ 'host @{host} is now offline'
372
+ );
373
+ }
374
+ },
375
+ ],
376
+ },
377
+ })
378
+ .on('request', () => {
379
+ this.last_request_time = Date.now();
380
+ })
381
+ .on('response', (eventResponse) => {
382
+ const message = "@{!status}, req: '@{request.method} @{request.url}' (streaming)";
383
+ this.logger.http(
384
+ {
385
+ request: {
386
+ method: method,
387
+ url: uri,
388
+ },
389
+ status: _.isNull(eventResponse) === false ? eventResponse.statusCode : 'ERR',
390
+ },
391
+ message
392
+ );
393
+ })
394
+ .on('downloadProgress', (progress) => {
395
+ if (progress.total) {
396
+ debug('responseLength %s', progress.total);
397
+ responseLength = progress.total;
398
+ }
399
+ });
400
+ const etag = response.headers.etag as string;
401
+ const data = response.body;
402
+
403
+ // not modified status (304) registry does not return any payload
404
+ // it is handled as an error
405
+ if (response?.statusCode === HTTP_STATUS.NOT_MODIFIED) {
406
+ throw errorUtils.getCode(HTTP_STATUS.NOT_MODIFIED, API_ERROR.NOT_MODIFIED_NO_DATA);
407
+ }
408
+
409
+ debug('uri %s success', uri);
410
+ const message = "@{!status}, req: '@{request.method} @{request.url}'";
411
+ this.logger.http(
412
+ {
413
+ // if error is null/false change this to undefined so it wont log
414
+ request: { method: method, url: uri },
415
+ status: response.statusCode,
416
+ bytes: {
417
+ in: options?.json ? JSON.stringify(options?.json).length : 0,
418
+ out: responseLength || 0,
419
+ },
420
+ },
421
+ message
422
+ );
423
+ return [data, etag];
424
+ } catch (err: any) {
425
+ debug('uri %s fail', uri);
426
+ if (err.code === 'ERR_NON_2XX_3XX_RESPONSE') {
427
+ const code = err.response.statusCode;
428
+ if (code === HTTP_STATUS.NOT_FOUND) {
429
+ throw errorUtils.getNotFound(errorUtils.API_ERROR.NOT_PACKAGE_UPLINK);
468
430
  }
469
- if (!(res.statusCode >= HTTP_STATUS.OK && res.statusCode < HTTP_STATUS.MULTIPLE_CHOICES)) {
431
+
432
+ if (!(code >= HTTP_STATUS.OK && code < HTTP_STATUS.MULTIPLE_CHOICES)) {
470
433
  const error = errorUtils.getInternalError(
471
- `${errorUtils.API_ERROR.BAD_STATUS_CODE}: ${res.statusCode}`
434
+ `${errorUtils.API_ERROR.BAD_STATUS_CODE}: ${code}`
472
435
  );
473
-
474
- error.remoteStatus = res.statusCode;
475
- return callback(error);
436
+ // we need this code to identify outside which status code triggered the error
437
+ error.remoteStatus = code;
438
+ throw error;
476
439
  }
477
- callback(null, body, res.headers.etag);
478
440
  }
479
- );
441
+ throw err;
442
+ }
480
443
  }
481
444
 
482
- /**
483
- * Fetch a tarball from the uplink.
484
- * @param {String} url
485
- * @return {Stream}
486
- */
487
- public fetchTarball(url: string) {
488
- const stream = new ReadTarball({});
489
- let current_length = 0;
490
- let expected_length;
491
-
492
- stream.abort = () => {};
493
- const readStream = this.request({
494
- uri_full: url,
495
- encoding: null,
496
- headers: {
497
- Accept: contentTypeAccept,
498
- },
499
- });
500
-
501
- readStream.on('response', function (res: any) {
502
- if (res.statusCode === HTTP_STATUS.NOT_FOUND) {
503
- return stream.emit('error', errorUtils.getNotFound(errorUtils.API_ERROR.NOT_FILE_UPLINK));
504
- }
505
- if (!(res.statusCode >= HTTP_STATUS.OK && res.statusCode < HTTP_STATUS.MULTIPLE_CHOICES)) {
506
- return stream.emit(
507
- 'error',
508
- errorUtils.getInternalError(`bad uplink status code: ${res.statusCode}`)
509
- );
510
- }
511
- if (res.headers[HEADER_TYPE.CONTENT_LENGTH]) {
512
- expected_length = res.headers[HEADER_TYPE.CONTENT_LENGTH];
513
- stream.emit(HEADER_TYPE.CONTENT_LENGTH, res.headers[HEADER_TYPE.CONTENT_LENGTH]);
514
- }
445
+ // FIXME: handle stream and retry
446
+ public fetchTarballNext(
447
+ url: string,
448
+ overrideOptions: Pick<ISyncUplinksOptions, 'remoteAddress' | 'etag' | 'retry'>
449
+ ): any {
450
+ debug('fetching url for %s', url);
451
+ const options = { ...this.config, ...overrideOptions };
452
+ let headers = this.getHeadersNext(options?.headers);
453
+ headers = this.addProxyHeaders(headers, options.remoteAddress);
454
+ headers = this.applyUplinkHeaders(headers);
455
+ // the following headers cannot be overwritten
456
+ if (_.isNil(options.etag) === false) {
457
+ headers[HEADERS.NONE_MATCH] = options.etag;
458
+ headers[HEADERS.ACCEPT] = contentTypeAccept;
459
+ }
460
+ const method = 'GET';
461
+ // const uri = this.config.url + `/${encode(name)}`;
462
+ debug('request uri for %s', url);
463
+
464
+ const readStream = got
465
+ .stream(url, {
466
+ headers,
467
+ method,
468
+ agent: this.agent,
469
+ // FIXME: this should be taken from construtor as priority
470
+ retry: this.retry ?? options?.retry,
471
+ timeout: this.timeout,
472
+ })
473
+ .on('request', () => {
474
+ this.last_request_time = Date.now();
475
+ });
515
476
 
516
- readStream.pipe(stream);
517
- });
518
-
519
- readStream.on('error', function (err) {
520
- stream.emit('error', err);
521
- });
522
- readStream.on('data', function (data) {
523
- current_length += data.length;
524
- });
525
- readStream.on('end', function (data) {
526
- if (data) {
527
- current_length += data.length;
528
- }
529
- if (expected_length && current_length != expected_length) {
530
- stream.emit('error', errorUtils.getInternalError(errorUtils.API_ERROR.CONTENT_MISMATCH));
531
- }
532
- });
533
- return stream;
477
+ return readStream;
534
478
  }
535
479
 
536
480
  /**
@@ -577,65 +521,21 @@ class ProxyStorage implements IProxy {
577
521
  }
578
522
  }
579
523
 
580
- /**
581
- * Add proxy headers.
582
- * FIXME: object mutations, it should return an new object
583
- * @param {*} req the http request
584
- * @param {*} headers the request headers
585
- */
586
- private _addProxyHeaders(req: any, headers: any): void {
587
- if (req) {
588
- // Only submit X-Forwarded-For field if we don't have a proxy selected
589
- // in the config file.
590
- //
591
- // Otherwise misconfigured proxy could return 407:
592
- // https://github.com/rlidwka/sinopia/issues/254
593
- // @ts-ignore
594
- if (!this.proxy) {
595
- headers[HEADERS.FORWARDED_FOR] =
596
- (req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'] + ', ' : '') +
597
- req.connection.remoteAddress;
598
- }
524
+ private addProxyHeaders(headers: gotHeaders, remoteAddress?: string): gotHeaders {
525
+ // Only submit X-Forwarded-For field if we don't have a proxy selected
526
+ // in the config file.
527
+ //
528
+ // Otherwise misconfigured proxy could return 407
529
+ if (!this.proxy) {
530
+ headers[HEADERS.FORWARDED_FOR] =
531
+ (headers['x-forwarded-for'] ? headers['x-forwarded-for'] + ', ' : '') + remoteAddress;
599
532
  }
600
533
 
601
534
  // always attach Via header to avoid loops, even if we're not proxying
602
- headers['Via'] = req?.headers['via'] ? req.headers['via'] + ', ' : '';
603
-
604
- headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)';
605
- }
535
+ headers['via'] = headers['via'] ? headers['via'] + ', ' : '';
536
+ headers['via'] += '1.1 ' + this.server_id + ' (Verdaccio)';
606
537
 
607
- /**
608
- * Check whether the remote host is available.
609
- * @param {*} alive
610
- * @return {Boolean}
611
- */
612
- private _statusCheck(alive?: boolean): boolean | void {
613
- if (arguments.length === 0) {
614
- return this._ifRequestFailure() === false;
615
- }
616
- if (alive) {
617
- if (this.failed_requests >= this.max_fails) {
618
- this.logger.warn(
619
- {
620
- host: this.url.host,
621
- },
622
- 'host @{host} is back online'
623
- );
624
- }
625
- this.failed_requests = 0;
626
- } else {
627
- this.failed_requests++;
628
- if (this.failed_requests === this.max_fails) {
629
- this.logger.warn(
630
- {
631
- host: this.url.host,
632
- },
633
- 'host @{host} is now offline'
634
- );
635
- }
636
- }
637
-
638
- this.last_request_time = Date.now();
538
+ return headers;
639
539
  }
640
540
 
641
541
  /**
@@ -693,24 +593,20 @@ class ProxyStorage implements IProxy {
693
593
  if (noProxyItem[0] !== '.') {
694
594
  noProxyItem = '.' + noProxyItem;
695
595
  }
696
- if (hostname.lastIndexOf(noProxyItem) === hostname.length - noProxyItem.length) {
596
+ if (hostname.endsWith(noProxyItem)) {
697
597
  if (this.proxy) {
698
598
  this.logger.debug(
699
599
  { url: this.url.href, rule: noProxyItem },
700
600
  'not using proxy for @{url}, excluded by @{rule} rule'
701
601
  );
702
- // @ts-ignore
703
- this.proxy = false;
602
+ this.proxy = undefined;
704
603
  }
705
604
  break;
706
605
  }
707
606
  }
708
607
  }
709
608
 
710
- // if it's non-string (i.e. "false"), don't use it
711
- if (_.isString(this.proxy) === false) {
712
- delete this.proxy;
713
- } else {
609
+ if (typeof this.proxy === 'string') {
714
610
  this.logger.debug(
715
611
  { url: this.url.href, proxy: this.proxy },
716
612
  'using proxy @{proxy} for @{url}'