import * as https from 'https' import { AxiosRequestConfig } from 'axios' import axiosCookieJarSupport from 'axios-cookiejar-support' import * as tough from 'tough-cookie' import { prefixMessage } from '@sasjs/utils/error' import { RequestClient, throwIfError } from './RequestClient' import { JobExecutionError } from '../types/errors' /** * Specific request client for SAS9 in Node.js environments. * Handles redirects and cookie management. */ export class Sas9RequestClient extends RequestClient { constructor(baseUrl: string, httpsAgentOptions?: https.AgentOptions) { super(baseUrl, httpsAgentOptions) this.httpClient.defaults.maxRedirects = 0 this.httpClient.defaults.validateStatus = (status) => status >= 200 && status < 303 if (axiosCookieJarSupport) { axiosCookieJarSupport(this.httpClient) this.httpClient.defaults.jar = new tough.CookieJar() } } public async login(username: string, password: string, jobsPath: string) { const codeInjectorPath = `/User Folders/${username}/My Folder/sasjs/runner` if (this.httpClient.defaults.jar) { ;(this.httpClient.defaults.jar as tough.CookieJar).removeAllCookies() await this.get( `${jobsPath}?_program=${codeInjectorPath}&_username=${username}&_password=${password}`, undefined, 'text/plain' ) } } public async get( url: string, accessToken: string | undefined, contentType: string = 'application/json', overrideHeaders: { [key: string]: string | number } = {}, debug: boolean = false ): Promise<{ result: T; etag: string; status: number }> { const headers = { ...this.getHeaders(accessToken, contentType), ...overrideHeaders } const requestConfig: AxiosRequestConfig = { headers, responseType: contentType === 'text/plain' ? 'text' : 'json', withCredentials: true } if (contentType === 'text/plain') { requestConfig.transformResponse = undefined } return this.httpClient .get(url, requestConfig) .then((response) => { if (response.status === 302) { return this.get( response.headers['location'], accessToken, contentType ) } throwIfError(response) return this.parseResponse(response) }) .catch(async (e: any) => { if (e instanceof JobExecutionError) throw e return await this.handleError( e, () => this.get(url, accessToken, contentType, overrideHeaders).catch( (err) => { throw prefixMessage( err, 'Error while executing handle error callback. ' ) } ), debug ).catch((err) => { throw prefixMessage(err, 'Error while handling error. ') }) }) } public async post( url: string, data: any, accessToken: string | undefined, contentType = 'application/json', overrideHeaders: { [key: string]: string | number } = {} ): Promise<{ result: T; etag: string }> { const headers = { ...this.getHeaders(accessToken, contentType), ...overrideHeaders } return this.httpClient .post(url, data, { headers, withCredentials: true }) .then(async (response) => { if (response.status === 302) { return await this.get( response.headers['location'], undefined, contentType, overrideHeaders ) } throwIfError(response) return this.parseResponse(response) }) .catch(async (e: any) => { return await this.handleError(e, () => this.post(url, data, accessToken, contentType, overrideHeaders) ) }) } }