private https()

in src/package-sources/npmjs/couch-changes.lambda-shared.ts [79:150]


  private https(method: string, url: URL, body?: { [key: string]: unknown }, attempt = 1): Promise<{ [key: string]: unknown }> {
    return new Promise((ok, ko) => {
      const retry = () => setTimeout(
        () => {
          console.log(`Retrying ${method.toUpperCase()} ${url}`);
          this.https(method, url, body, attempt + 1).then(ok, ko);
        },
        Math.min(500 * attempt, 5_000),
      );

      const headers: OutgoingHttpHeaders = {
        'Accept': 'application/json',
        'Accept-Encoding': 'gzip',
      };
      if (body) {
        headers['Content-Type'] = 'application/json';
      }
      console.log(`Request: ${method.toUpperCase()} ${url}`);
      const req = request(
        url,
        {
          agent: this.agent,
          headers,
          method,
          port: 443,
          servername: url.hostname,
        },
        (res) => {
          if (res.statusCode == null) {
            const error = new Error(`[FATAL] Request failed: ${method.toUpperCase()} ${url}`);
            Error.captureStackTrace(error);
            return ko(error);
          }

          console.log(`Response: ${method.toUpperCase()} ${url} => HTTP ${res.statusCode} (${res.statusMessage})`);

          // Transient (server) errors:
          if (res.statusCode >= 500 && res.statusCode < 600) {
            console.error(`[RETRYABLE] HTTP ${res.statusCode} (${res.statusMessage}) - ${method.toUpperCase()} ${url}`);
            // Call again after a short back-off
            return retry();
          }
          // Permanent (client) errors:
          if (res.statusCode >= 400 && res.statusCode < 500) {
            const error = new Error(`[FATAL] HTTP ${res.statusCode} (${res.statusMessage}) - ${method.toUpperCase()} ${url}`);
            Error.captureStackTrace(error);
            return ko(error);
          }

          const onError = (err: Error & { code?: string }) => {
            if (err.code === 'ECONNRESET') {
              // Transient networking problem?
              console.error(`[RETRYABLE] ${err.code} - ${method.toUpperCase()} ${url}`);
              retry();
            } else {
              ko(err);
            }
          };

          res.once('error', onError);

          const json = JSONStream.parse(true);
          json.once('data', ok);
          json.once('error', onError);

          const plainPayload = res.headers['content-encoding'] === 'gzip' ? gunzip(res) : res;
          plainPayload.pipe(json, { end: true });
        },
      );
      req.end(body && JSON.stringify(body, null, 2));
    });
  }