All files / src/models send-to-api.js

89.16% Statements 107/120
77.27% Branches 17/22
100% Functions 6/6
89.16% Lines 107/120

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121193x 193x 193x 193x 193x 193x 193x 193x 193x 193x         193x 193x 141x 141x 193x 193x 8x 8x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 8x 8x 8x 8x 8x 8x 8x 8x 8x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 157x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 41x 41x     41x     41x 12x 12x 12x 12x 12x 29x           29x 41x 193x 193x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x  
import { addOauthHeader } from '@clevercloud/client/esm/oauth.js';
import { prefixUrl } from '@clevercloud/client/esm/prefix-url.js';
import { request } from '@clevercloud/client/esm/request.fetch.js';
import { subtle as cryptoSubtle } from 'node:crypto';
import { config } from '../config/config.js';
import { styleText } from '../lib/style-text.js';
import { Logger } from '../logger.js';
 
// Required for @clevercloud/client with "old" Node.js
if (globalThis.crypto == null) {
  globalThis.crypto = {
    subtle: cryptoSubtle,
  };
}
 
export async function sendToApi(requestParams) {
  return executeRequest(requestParams);
}
 
export async function sendToAuthBridge(requestParams) {
  return executeRequest(requestParams, { API_HOST: config.AUTH_BRIDGE_HOST });
}
 
/**
 * Creates an API request function with a custom config.
 * @param {object} customConfig
 * @param {string} customConfig.token - OAuth token
 * @param {string} customConfig.secret - OAuth secret
 * @param {string} customConfig.apiHost - API host
 * @param {string} customConfig.consumerKey - OAuth consumer key
 * @param {string} customConfig.consumerSecret - OAuth consumer secret
 * @returns {(requestParams: object) => Promise<unknown>}
 */
export function sendToApiWithConfig(customConfig) {
  return (requestParams) =>
    executeRequest(requestParams, {
      API_HOST: customConfig.apiHost,
      OAUTH_CONSUMER_KEY: customConfig.consumerKey,
      OAUTH_CONSUMER_SECRET: customConfig.consumerSecret,
      API_OAUTH_TOKEN: customConfig.token,
      API_OAUTH_TOKEN_SECRET: customConfig.secret,
    });
}
 
/**
 * Executes the request pipeline with the given configuration.
 * @param {object} requestParams - The request parameters
 * @param {object} [customConfig] - Optional configuration overrides
 * @param {string} [customConfig.API_HOST] - API host URL
 * @param {string} [customConfig.OAUTH_CONSUMER_KEY] - OAuth consumer key
 * @param {string} [customConfig.OAUTH_CONSUMER_SECRET] - OAuth consumer secret
 * @param {string} [customConfig.API_OAUTH_TOKEN] - OAuth token
 * @param {string} [customConfig.API_OAUTH_TOKEN_SECRET] - OAuth token secret
 * @returns {Promise<unknown>}
 */
async function executeRequest(requestParams, customConfig = {}) {
  const host = customConfig.API_HOST ?? config.API_HOST;
  const tokens = {
    OAUTH_CONSUMER_KEY: customConfig.OAUTH_CONSUMER_KEY ?? config.OAUTH_CONSUMER_KEY,
    OAUTH_CONSUMER_SECRET: customConfig.OAUTH_CONSUMER_SECRET ?? config.OAUTH_CONSUMER_SECRET,
    API_OAUTH_TOKEN: customConfig.API_OAUTH_TOKEN ?? config.token,
    API_OAUTH_TOKEN_SECRET: customConfig.API_OAUTH_TOKEN_SECRET ?? config.secret,
  };
 
  return Promise.resolve(requestParams)
    .then(prefixUrl(host))
    .then(addOauthHeader(tokens))
    .then((requestParams) => {
      Logger.debug(
        `${requestParams.method.toUpperCase()} ${requestParams.url} ? ${JSON.stringify(requestParams.queryParams)}`,
      );
      return requestParams;
    })
    .then(request)
    .catch(processError);
}
 
// OpenSSL error codes raised when the server certificate chain isn't trusted
// (corporate TLS-intercepting proxy, private or self-signed CA…).
const TLS_ERROR_CODES = [
  'SELF_SIGNED_CERT_IN_CHAIN',
  'DEPTH_ZERO_SELF_SIGNED_CERT',
  'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
  'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
];
 
export function processError(error) {
  const code = error.code ?? error?.cause?.code;
  if (code === 'EAI_AGAIN') {
    throw new Error('Cannot reach the Clever Cloud API, please check your internet connection.', { cause: error });
  }
  if (code === 'ECONNRESET') {
    throw new Error('The connection to the Clever Cloud API was closed abruptly, please try again.', { cause: error });
  }
  if (error?.response?.status === 401) {
    throw new Error(
      `You're not logged in, use ${styleText('red', 'clever login')} command to connect to your Clever Cloud account`,
      { cause: error },
    );
  }
  if (TLS_ERROR_CODES.includes(code)) {
    throw new Error(
      `TLS certificate verification failed (${code}). If you're behind a corporate proxy or using a private/self-signed Certificate Authority, trust your CA and follow the "TLS certificates" section of the documentation.`,
      { cause: error },
    );
  }
  throw error;
}
 
export function getHostAndTokens() {
  return {
    apiHost: config.API_HOST,
    tokens: {
      OAUTH_CONSUMER_KEY: config.OAUTH_CONSUMER_KEY,
      OAUTH_CONSUMER_SECRET: config.OAUTH_CONSUMER_SECRET,
      API_OAUTH_TOKEN: config.token,
      API_OAUTH_TOKEN_SECRET: config.secret,
    },
  };
}