mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-16 00:20:06 +00:00
chore(*): clean up, handle debug responses
This commit is contained in:
@@ -142,7 +142,10 @@ describe('AuthManager', () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
mockedAxios.post.mockImplementation(() =>
|
mockedAxios.post.mockImplementation(() =>
|
||||||
Promise.resolve({ data: mockLoginAuthoriseRequiredResponse })
|
Promise.resolve({
|
||||||
|
data: mockLoginAuthoriseRequiredResponse,
|
||||||
|
config: { url: 'https://test.com/SASLogon/login' }
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
await authManager.logIn(userName, password)
|
await authManager.logIn(userName, password)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ServerType } from '@sasjs/utils/types'
|
|||||||
import { ErrorResponse } from '..'
|
import { ErrorResponse } from '..'
|
||||||
import { SASViyaApiClient } from '../SASViyaApiClient'
|
import { SASViyaApiClient } from '../SASViyaApiClient'
|
||||||
import { ComputeJobExecutionError, LoginRequiredError } from '../types'
|
import { ComputeJobExecutionError, LoginRequiredError } from '../types'
|
||||||
import { parseWeboutResponse } from '../utils'
|
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
|
|
||||||
export class ComputeJobExecutor extends BaseJobExecutor {
|
export class ComputeJobExecutor extends BaseJobExecutor {
|
||||||
@@ -35,15 +34,7 @@ export class ComputeJobExecutor extends BaseJobExecutor {
|
|||||||
this.appendRequest(response, sasJob, config.debug)
|
this.appendRequest(response, sasJob, config.debug)
|
||||||
let responseJson
|
let responseJson
|
||||||
|
|
||||||
try {
|
return response.result
|
||||||
if (typeof response!.result === 'string') {
|
|
||||||
responseJson = JSON.parse(response!.result)
|
|
||||||
} else {
|
|
||||||
responseJson = response!.result
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
responseJson = JSON.parse(parseWeboutResponse(response!.result))
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseJson
|
return responseJson
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ServerType } from '@sasjs/utils/types'
|
|||||||
import { ErrorResponse } from '..'
|
import { ErrorResponse } from '..'
|
||||||
import { SASViyaApiClient } from '../SASViyaApiClient'
|
import { SASViyaApiClient } from '../SASViyaApiClient'
|
||||||
import { JobExecutionError, LoginRequiredError } from '../types'
|
import { JobExecutionError, LoginRequiredError } from '../types'
|
||||||
import { parseWeboutResponse } from '../utils'
|
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
|
|
||||||
export class JesJobExecutor extends BaseJobExecutor {
|
export class JesJobExecutor extends BaseJobExecutor {
|
||||||
@@ -23,19 +22,7 @@ export class JesJobExecutor extends BaseJobExecutor {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
this.appendRequest(response, sasJob, config.debug)
|
this.appendRequest(response, sasJob, config.debug)
|
||||||
|
|
||||||
let responseJson
|
return response.result
|
||||||
|
|
||||||
try {
|
|
||||||
if (typeof response!.result === 'string') {
|
|
||||||
responseJson = JSON.parse(response!.result)
|
|
||||||
} else {
|
|
||||||
responseJson = response!.result
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
responseJson = JSON.parse(parseWeboutResponse(response!.result))
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseJson
|
|
||||||
})
|
})
|
||||||
.catch(async (e: Error) => {
|
.catch(async (e: Error) => {
|
||||||
if (e instanceof JobExecutionError) {
|
if (e instanceof JobExecutionError) {
|
||||||
|
|||||||
@@ -90,8 +90,13 @@ export abstract class BaseJobExecutor implements JobExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stringifiedResult =
|
||||||
|
typeof response?.result === 'string'
|
||||||
|
? response?.result
|
||||||
|
: JSON.stringify(response?.result, null, 2)
|
||||||
|
|
||||||
this.requests.push({
|
this.requests.push({
|
||||||
logFile: response?.log || response?.result || response,
|
logFile: response?.log || stringifiedResult || response,
|
||||||
serviceLink: program,
|
serviceLink: program,
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
sourceCode,
|
sourceCode,
|
||||||
|
|||||||
@@ -82,6 +82,13 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
|
|
||||||
return this.requestClient!.post(apiUrl, formData, undefined)
|
return this.requestClient!.post(apiUrl, formData, undefined)
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
|
if (this.serverType === ServerType.SasViya && config.debug) {
|
||||||
|
const jsonResponse = await this.parseSasViyaDebugResponse(
|
||||||
|
res.result as string
|
||||||
|
)
|
||||||
|
this.appendRequest(res, sasJob, config.debug)
|
||||||
|
return jsonResponse
|
||||||
|
}
|
||||||
this.appendRequest(res, sasJob, config.debug)
|
this.appendRequest(res, sasJob, config.debug)
|
||||||
return res.result
|
return res.result
|
||||||
})
|
})
|
||||||
@@ -99,6 +106,20 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parseSasViyaDebugResponse = async (response: string) => {
|
||||||
|
const iframeStart = response.split(
|
||||||
|
'<iframe style="width: 99%; height: 500px" src="'
|
||||||
|
)[1]
|
||||||
|
const jsonUrl = iframeStart ? iframeStart.split('"></iframe>')[0] : null
|
||||||
|
if (!jsonUrl) {
|
||||||
|
throw new Error('Unable to find webout file URL.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.requestClient
|
||||||
|
.get(this.serverUrl + jsonUrl, undefined)
|
||||||
|
.then((res) => res.result)
|
||||||
|
}
|
||||||
|
|
||||||
private async getJobUri(sasJob: string) {
|
private async getJobUri(sasJob: string) {
|
||||||
if (!this.sasViyaApiClient) return ''
|
if (!this.sasViyaApiClient) return ''
|
||||||
let uri = ''
|
let uri = ''
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { isAuthorizeFormRequired, isLogInRequired } from '../auth'
|
|||||||
import { LoginRequiredError } from '../types'
|
import { LoginRequiredError } from '../types'
|
||||||
import { AuthorizeError } from '../types/AuthorizeError'
|
import { AuthorizeError } from '../types/AuthorizeError'
|
||||||
import { NotFoundError } from '../types/NotFoundError'
|
import { NotFoundError } from '../types/NotFoundError'
|
||||||
|
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||||
|
|
||||||
export interface HttpClient {
|
export interface HttpClient {
|
||||||
get<T>(
|
get<T>(
|
||||||
@@ -42,7 +43,7 @@ export class RequestClient implements HttpClient {
|
|||||||
private fileUploadCsrfToken: CsrfToken | undefined
|
private fileUploadCsrfToken: CsrfToken | undefined
|
||||||
private httpClient: AxiosInstance
|
private httpClient: AxiosInstance
|
||||||
|
|
||||||
constructor(baseUrl: string) {
|
constructor(private baseUrl: string) {
|
||||||
this.httpClient = axios.create({ baseURL: baseUrl })
|
this.httpClient = axios.create({ baseURL: baseUrl })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,35 +80,12 @@ export class RequestClient implements HttpClient {
|
|||||||
.get<T>(url, requestConfig)
|
.get<T>(url, requestConfig)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
throwIfError(response)
|
throwIfError(response)
|
||||||
const etag = response?.headers ? response.headers['etag'] : ''
|
return this.parseResponse<T>(response)
|
||||||
|
|
||||||
return {
|
|
||||||
result: response.data as T,
|
|
||||||
etag
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(async (e) => {
|
.catch(async (e) => {
|
||||||
const response = e.response as AxiosResponse
|
return await this.handleError(e, () =>
|
||||||
if (e instanceof AuthorizeError) {
|
this.get<T>(url, accessToken, contentType, overrideHeaders)
|
||||||
const res = await this.get(e.confirmUrl, undefined, 'text/plain')
|
)
|
||||||
if (isAuthorizeFormRequired(res.result as string)) {
|
|
||||||
await this.authorize(res.result as string)
|
|
||||||
}
|
|
||||||
return this.get<T>(url, accessToken, contentType, overrideHeaders)
|
|
||||||
}
|
|
||||||
if (e instanceof LoginRequiredError) {
|
|
||||||
this.clearCsrfTokens()
|
|
||||||
}
|
|
||||||
if (response?.status === 403 || response?.status === 449) {
|
|
||||||
this.parseAndSetCsrfToken(response)
|
|
||||||
if (this.csrfToken.headerName && this.csrfToken.value) {
|
|
||||||
return this.get<T>(url, accessToken, contentType, overrideHeaders)
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
} else if (response?.status === 404) {
|
|
||||||
throw new NotFoundError(url)
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,39 +105,12 @@ export class RequestClient implements HttpClient {
|
|||||||
.post<T>(url, data, { headers, withCredentials: true })
|
.post<T>(url, data, { headers, withCredentials: true })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
throwIfError(response)
|
throwIfError(response)
|
||||||
const etag = response?.headers ? response.headers['etag'] : ''
|
return this.parseResponse<T>(response)
|
||||||
return {
|
|
||||||
result: response.data as T,
|
|
||||||
etag
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(async (e) => {
|
.catch(async (e) => {
|
||||||
const response = e.response as AxiosResponse
|
return await this.handleError(e, () =>
|
||||||
if (e instanceof AuthorizeError) {
|
this.post<T>(url, data, accessToken, contentType, overrideHeaders)
|
||||||
const res = await this.get(e.confirmUrl, undefined, 'text/plain')
|
)
|
||||||
if (isAuthorizeFormRequired(res.result as string)) {
|
|
||||||
await this.authorize(res.result as string)
|
|
||||||
}
|
|
||||||
return this.post<T>(
|
|
||||||
url,
|
|
||||||
data,
|
|
||||||
accessToken,
|
|
||||||
contentType,
|
|
||||||
overrideHeaders
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (e instanceof LoginRequiredError) {
|
|
||||||
this.clearCsrfTokens()
|
|
||||||
}
|
|
||||||
if (response?.status === 403 || response?.status === 449) {
|
|
||||||
this.parseAndSetCsrfToken(response)
|
|
||||||
|
|
||||||
if (this.csrfToken.headerName && this.csrfToken.value) {
|
|
||||||
return this.post<T>(url, data, accessToken)
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,36 +125,17 @@ export class RequestClient implements HttpClient {
|
|||||||
...overrideHeaders
|
...overrideHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return this.httpClient
|
||||||
const response = await this.httpClient.put<T>(url, data, {
|
.put<T>(url, data, { headers, withCredentials: true })
|
||||||
headers,
|
.then((response) => {
|
||||||
withCredentials: true
|
throwIfError(response)
|
||||||
|
return this.parseResponse<T>(response)
|
||||||
|
})
|
||||||
|
.catch(async (e) => {
|
||||||
|
return await this.handleError(e, () =>
|
||||||
|
this.put<T>(url, data, accessToken, overrideHeaders)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
const etag = response?.headers ? response.headers['etag'] : ''
|
|
||||||
return {
|
|
||||||
result: response.data as T,
|
|
||||||
etag
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
const response = e.response as AxiosResponse
|
|
||||||
if (e instanceof LoginRequiredError) {
|
|
||||||
this.clearCsrfTokens()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response?.status === 403 || response?.status === 449) {
|
|
||||||
this.parseAndSetCsrfToken(response)
|
|
||||||
|
|
||||||
if (this.csrfToken.headerName && this.csrfToken.value) {
|
|
||||||
return this.put<T>(url, data, accessToken)
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response?.status === 401) {
|
|
||||||
throw new LoginRequiredError()
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async delete<T>(
|
public async delete<T>(
|
||||||
@@ -212,33 +144,15 @@ export class RequestClient implements HttpClient {
|
|||||||
): Promise<{ result: T; etag: string }> {
|
): Promise<{ result: T; etag: string }> {
|
||||||
const headers = this.getHeaders(accessToken, 'application/json')
|
const headers = this.getHeaders(accessToken, 'application/json')
|
||||||
|
|
||||||
const requestConfig: AxiosRequestConfig = {
|
return this.httpClient
|
||||||
headers
|
.delete<T>(url, { headers, withCredentials: true })
|
||||||
}
|
.then((response) => {
|
||||||
|
throwIfError(response)
|
||||||
try {
|
return this.parseResponse<T>(response)
|
||||||
const response = await this.httpClient.delete<T>(url, requestConfig)
|
})
|
||||||
const etag = response?.headers ? response.headers['etag'] : ''
|
.catch(async (e) => {
|
||||||
return {
|
return await this.handleError(e, () => this.delete<T>(url, accessToken))
|
||||||
result: response.data as T,
|
})
|
||||||
etag
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
const response = e.response as AxiosResponse
|
|
||||||
if (e instanceof LoginRequiredError) {
|
|
||||||
this.clearCsrfTokens()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response?.status === 403 || response?.status === 449) {
|
|
||||||
this.parseAndSetCsrfToken(response)
|
|
||||||
|
|
||||||
if (this.csrfToken.headerName && this.csrfToken.value) {
|
|
||||||
return this.delete<T>(url, accessToken)
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async patch<T>(
|
public async patch<T>(
|
||||||
@@ -248,31 +162,17 @@ export class RequestClient implements HttpClient {
|
|||||||
): Promise<{ result: T; etag: string }> {
|
): Promise<{ result: T; etag: string }> {
|
||||||
const headers = this.getHeaders(accessToken, 'application/json')
|
const headers = this.getHeaders(accessToken, 'application/json')
|
||||||
|
|
||||||
try {
|
return this.httpClient
|
||||||
const response = await this.httpClient.patch<T>(url, data, {
|
.patch<T>(url, data, { headers, withCredentials: true })
|
||||||
headers,
|
.then((response) => {
|
||||||
withCredentials: true
|
throwIfError(response)
|
||||||
|
return this.parseResponse<T>(response)
|
||||||
|
})
|
||||||
|
.catch(async (e) => {
|
||||||
|
return await this.handleError(e, () =>
|
||||||
|
this.patch<T>(url, data, accessToken)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
return {
|
|
||||||
result: response.data as T,
|
|
||||||
etag: response.headers['etag'] as string
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
const response = e.response as AxiosResponse
|
|
||||||
if (e instanceof LoginRequiredError) {
|
|
||||||
this.clearCsrfTokens()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response?.status === 403 || response?.status === 449) {
|
|
||||||
this.parseAndSetCsrfToken(response)
|
|
||||||
|
|
||||||
if (this.csrfToken.headerName && this.csrfToken.value) {
|
|
||||||
return this.patch<T>(url, accessToken)
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async uploadFile(
|
public async uploadFile(
|
||||||
@@ -317,9 +217,7 @@ export class RequestClient implements HttpClient {
|
|||||||
bodyElement.innerHTML = responseBody
|
bodyElement.innerHTML = responseBody
|
||||||
|
|
||||||
const form = bodyElement.querySelector('#application_authorization')
|
const form = bodyElement.querySelector('#application_authorization')
|
||||||
authUrl = form
|
authUrl = form ? this.baseUrl + form.getAttribute('action') : null
|
||||||
? this.httpClient.defaults.baseURL! + form.getAttribute('action')
|
|
||||||
: null
|
|
||||||
|
|
||||||
const inputs: any = form?.querySelectorAll('input')
|
const inputs: any = form?.querySelectorAll('input')
|
||||||
|
|
||||||
@@ -417,9 +315,60 @@ export class RequestClient implements HttpClient {
|
|||||||
return csrfToken
|
return csrfToken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleError = async (e: any, callback: any) => {
|
||||||
|
const response = e.response as AxiosResponse
|
||||||
|
if (e instanceof AuthorizeError) {
|
||||||
|
const res = await this.get(e.confirmUrl, undefined, 'text/plain')
|
||||||
|
if (isAuthorizeFormRequired(res.result as string)) {
|
||||||
|
await this.authorize(res.result as string)
|
||||||
|
}
|
||||||
|
return await callback()
|
||||||
|
}
|
||||||
|
if (e instanceof LoginRequiredError) {
|
||||||
|
this.clearCsrfTokens()
|
||||||
|
}
|
||||||
|
if (response?.status === 403 || response?.status === 449) {
|
||||||
|
this.parseAndSetCsrfToken(response)
|
||||||
|
if (this.csrfToken.headerName && this.csrfToken.value) {
|
||||||
|
return await callback()
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
} else if (response?.status === 404) {
|
||||||
|
throw new NotFoundError(response.config.url!)
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
private async parseResponse<T>(response: AxiosResponse<any>) {
|
||||||
|
const etag = response?.headers ? response.headers['etag'] : ''
|
||||||
|
let parsedResponse
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof response.data === 'string') {
|
||||||
|
parsedResponse = JSON.parse(response.data)
|
||||||
|
} else {
|
||||||
|
parsedResponse = response.data
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
parsedResponse = JSON.parse(parseWeboutResponse(response.data))
|
||||||
|
} catch {
|
||||||
|
parsedResponse = response.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
result: parsedResponse as T,
|
||||||
|
etag
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const throwIfError = (response: AxiosResponse) => {
|
const throwIfError = (response: AxiosResponse) => {
|
||||||
|
if (response.status === 401) {
|
||||||
|
throw new LoginRequiredError()
|
||||||
|
}
|
||||||
|
|
||||||
if (response.data?.entityID?.includes('login')) {
|
if (response.data?.entityID?.includes('login')) {
|
||||||
throw new LoginRequiredError()
|
throw new LoginRequiredError()
|
||||||
}
|
}
|
||||||
@@ -427,7 +376,7 @@ const throwIfError = (response: AxiosResponse) => {
|
|||||||
if (
|
if (
|
||||||
typeof response.data === 'string' &&
|
typeof response.data === 'string' &&
|
||||||
isLogInRequired(response.data) &&
|
isLogInRequired(response.data) &&
|
||||||
!response.config.url?.includes('/SASLogon/login')
|
!response.config?.url?.includes('/SASLogon/login')
|
||||||
) {
|
) {
|
||||||
throw new LoginRequiredError()
|
throw new LoginRequiredError()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user