UNPKG

@esm-js/jira.js

Version:

A comprehensive JavaScript/TypeScript library designed for both Node.JS and browsers, facilitating seamless interaction with the Atlassian Jira API.

169 lines (136 loc) 5.42 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ import type { AxiosInstance, AxiosResponse } from 'axios'; import axios from 'axios'; import type { Callback } from '../callback'; import type { Client } from './client'; import type { Config, JiraError } from '../config'; import { ConfigSchema } from '../config'; import { getAuthenticationToken } from '../services/authenticationService'; import type { RequestConfig } from '../requestConfig'; import { HttpException, isObject } from './httpException'; import { ZodError } from 'zod'; const STRICT_GDPR_FLAG = 'x-atlassian-force-account-id'; const ATLASSIAN_TOKEN_CHECK_FLAG = 'X-Atlassian-Token'; const ATLASSIAN_TOKEN_CHECK_NOCHECK_VALUE = 'no-check'; export class BaseClient implements Client { private instance: AxiosInstance; constructor(protected readonly config: Config) { try { this.config = ConfigSchema.parse(config); } catch (e) { if (e instanceof ZodError && e.errors[0].message === 'Invalid url') { throw new Error( 'Couldn\'t parse the host URL. Perhaps you forgot to add \'http://\' or \'https://\' at the beginning of the URL?', ); } throw e; } this.instance = axios.create({ paramsSerializer: this.paramSerializer.bind(this), ...config.baseRequestConfig, baseURL: config.host, headers: this.removeUndefinedProperties({ [STRICT_GDPR_FLAG]: config.strictGDPR, [ATLASSIAN_TOKEN_CHECK_FLAG]: config.noCheckAtlassianToken ? ATLASSIAN_TOKEN_CHECK_NOCHECK_VALUE : undefined, ...config.baseRequestConfig?.headers, }), }); } protected paramSerializer(parameters: Record<string, any>): string { const parts: string[] = []; Object.entries(parameters).forEach(([key, value]) => { if (value === null || typeof value === 'undefined') { return; } if (Array.isArray(value)) { value = value.join(','); } if (value instanceof Date) { value = value.toISOString(); } else if (value !== null && typeof value === 'object') { value = JSON.stringify(value); } else if (value instanceof Function) { const part = value(); return part && parts.push(part); } parts.push(`${this.encode(key)}=${this.encode(value)}`); }); return parts.join('&'); } protected encode(value: string) { return encodeURIComponent(value) .replace(/%3A/gi, ':') .replace(/%24/g, '$') .replace(/%2C/gi, ',') .replace(/%20/g, '+') .replace(/%5B/gi, '[') .replace(/%5D/gi, ']'); } protected removeUndefinedProperties(obj: Record<string, any>): Record<string, any> { return Object.entries(obj) .filter(([, value]) => typeof value !== 'undefined') .reduce((accumulator, [key, value]) => ({ ...accumulator, [key]: value }), {}); } async sendRequest<T>(requestConfig: RequestConfig, callback: never): Promise<T>; async sendRequest<T>(requestConfig: RequestConfig, callback: Callback<T>): Promise<void>; async sendRequest<T>(requestConfig: RequestConfig, callback: Callback<T> | never): Promise<void | T> { try { const response = await this.sendRequestFullResponse<T>(requestConfig); return this.handleSuccessResponse(response.data, callback); } catch (e: unknown) { return this.handleFailedResponse(e, callback); } } async sendRequestFullResponse<T>(requestConfig: RequestConfig): Promise<AxiosResponse<T>> { const modifiedRequestConfig = { ...requestConfig, headers: this.removeUndefinedProperties({ Authorization: await getAuthenticationToken(this.config.authentication), ...requestConfig.headers, }), }; return this.instance.request<T>(modifiedRequestConfig); } handleSuccessResponse<T>(response: any, callback?: Callback<T> | never): T | void { const callbackResponseHandler = callback && ((data: T): void => callback(null, data)); const defaultResponseHandler = (data: T): T => data; const responseHandler = callbackResponseHandler ?? defaultResponseHandler; this.config.middlewares?.onResponse?.(response.data); return responseHandler(response); } handleFailedResponse<T>(e: unknown, callback?: Callback<T> | never): void { const err = this.buildErrorHandlingResponse(e); const callbackErrorHandler = callback && ((error: JiraError) => callback(error)); const defaultErrorHandler = (error: JiraError) => { throw error; }; const errorHandler = callbackErrorHandler ?? defaultErrorHandler; this.config.middlewares?.onError?.(err); return errorHandler(err); } private buildErrorHandlingResponse(e: unknown): JiraError { if (axios.isAxiosError(e) && e.response) { return new HttpException( { code: e.code, message: e.message, data: e.response.data, status: e.response.status, statusText: e.response.statusText, }, e.response.status, { cause: e }, ); } if (axios.isAxiosError(e)) { return e; } if (isObject(e) && isObject((e as Record<string, any>).response)) { return new HttpException((e as Record<string, any>).response); } if (e instanceof Error) { return new HttpException(e); } return new HttpException('Unknown error occurred.', 500, { cause: e }); } }