@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
text/typescript
/* 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 });
}
}