diff --git a/src/auth/spec/AuthManager.spec.ts b/src/auth/spec/AuthManager.spec.ts
index 71d5c2d..8af9cae 100644
--- a/src/auth/spec/AuthManager.spec.ts
+++ b/src/auth/spec/AuthManager.spec.ts
@@ -142,7 +142,10 @@ describe('AuthManager', () => {
})
)
mockedAxios.post.mockImplementation(() =>
- Promise.resolve({ data: mockLoginAuthoriseRequiredResponse })
+ Promise.resolve({
+ data: mockLoginAuthoriseRequiredResponse,
+ config: { url: 'https://test.com/SASLogon/login' }
+ })
)
await authManager.logIn(userName, password)
diff --git a/src/job-execution/ComputeJobExecutor.ts b/src/job-execution/ComputeJobExecutor.ts
index e6f5003..fa7a6f0 100644
--- a/src/job-execution/ComputeJobExecutor.ts
+++ b/src/job-execution/ComputeJobExecutor.ts
@@ -2,7 +2,6 @@ import { ServerType } from '@sasjs/utils/types'
import { ErrorResponse } from '..'
import { SASViyaApiClient } from '../SASViyaApiClient'
import { ComputeJobExecutionError, LoginRequiredError } from '../types'
-import { parseWeboutResponse } from '../utils'
import { BaseJobExecutor } from './JobExecutor'
export class ComputeJobExecutor extends BaseJobExecutor {
@@ -35,15 +34,7 @@ export class ComputeJobExecutor extends BaseJobExecutor {
this.appendRequest(response, sasJob, config.debug)
let responseJson
- try {
- if (typeof response!.result === 'string') {
- responseJson = JSON.parse(response!.result)
- } else {
- responseJson = response!.result
- }
- } catch {
- responseJson = JSON.parse(parseWeboutResponse(response!.result))
- }
+ return response.result
return responseJson
})
diff --git a/src/job-execution/JesJobExecutor.ts b/src/job-execution/JesJobExecutor.ts
index 0ce1d08..dad7b9e 100644
--- a/src/job-execution/JesJobExecutor.ts
+++ b/src/job-execution/JesJobExecutor.ts
@@ -2,7 +2,6 @@ import { ServerType } from '@sasjs/utils/types'
import { ErrorResponse } from '..'
import { SASViyaApiClient } from '../SASViyaApiClient'
import { JobExecutionError, LoginRequiredError } from '../types'
-import { parseWeboutResponse } from '../utils'
import { BaseJobExecutor } from './JobExecutor'
export class JesJobExecutor extends BaseJobExecutor {
@@ -23,19 +22,7 @@ export class JesJobExecutor extends BaseJobExecutor {
.then((response) => {
this.appendRequest(response, sasJob, config.debug)
- let responseJson
-
- try {
- if (typeof response!.result === 'string') {
- responseJson = JSON.parse(response!.result)
- } else {
- responseJson = response!.result
- }
- } catch {
- responseJson = JSON.parse(parseWeboutResponse(response!.result))
- }
-
- return responseJson
+ return response.result
})
.catch(async (e: Error) => {
if (e instanceof JobExecutionError) {
diff --git a/src/job-execution/JobExecutor.ts b/src/job-execution/JobExecutor.ts
index c195dcf..e8a77cf 100644
--- a/src/job-execution/JobExecutor.ts
+++ b/src/job-execution/JobExecutor.ts
@@ -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({
- logFile: response?.log || response?.result || response,
+ logFile: response?.log || stringifiedResult || response,
serviceLink: program,
timestamp: new Date(),
sourceCode,
diff --git a/src/job-execution/WebJobExecutor.ts b/src/job-execution/WebJobExecutor.ts
index c20c29b..ede976c 100644
--- a/src/job-execution/WebJobExecutor.ts
+++ b/src/job-execution/WebJobExecutor.ts
@@ -82,6 +82,13 @@ export class WebJobExecutor extends BaseJobExecutor {
return this.requestClient!.post(apiUrl, formData, undefined)
.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)
return res.result
})
@@ -99,6 +106,20 @@ export class WebJobExecutor extends BaseJobExecutor {
})
}
+ private parseSasViyaDebugResponse = async (response: string) => {
+ const iframeStart = response.split(
+ '')[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) {
if (!this.sasViyaApiClient) return ''
let uri = ''
diff --git a/src/request/RequestClient.ts b/src/request/RequestClient.ts
index 053b20c..acbc910 100644
--- a/src/request/RequestClient.ts
+++ b/src/request/RequestClient.ts
@@ -4,6 +4,7 @@ import { isAuthorizeFormRequired, isLogInRequired } from '../auth'
import { LoginRequiredError } from '../types'
import { AuthorizeError } from '../types/AuthorizeError'
import { NotFoundError } from '../types/NotFoundError'
+import { parseWeboutResponse } from '../utils/parseWeboutResponse'
export interface HttpClient {
get(
@@ -42,7 +43,7 @@ export class RequestClient implements HttpClient {
private fileUploadCsrfToken: CsrfToken | undefined
private httpClient: AxiosInstance
- constructor(baseUrl: string) {
+ constructor(private baseUrl: string) {
this.httpClient = axios.create({ baseURL: baseUrl })
}
@@ -79,35 +80,12 @@ export class RequestClient implements HttpClient {
.get(url, requestConfig)
.then((response) => {
throwIfError(response)
- const etag = response?.headers ? response.headers['etag'] : ''
-
- return {
- result: response.data as T,
- etag
- }
+ return this.parseResponse(response)
})
.catch(async (e) => {
- 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 this.get(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(url, accessToken, contentType, overrideHeaders)
- }
- throw e
- } else if (response?.status === 404) {
- throw new NotFoundError(url)
- }
- throw e
+ return await this.handleError(e, () =>
+ this.get(url, accessToken, contentType, overrideHeaders)
+ )
})
}
@@ -127,39 +105,12 @@ export class RequestClient implements HttpClient {
.post(url, data, { headers, withCredentials: true })
.then((response) => {
throwIfError(response)
- const etag = response?.headers ? response.headers['etag'] : ''
- return {
- result: response.data as T,
- etag
- }
+ return this.parseResponse(response)
})
.catch(async (e) => {
- 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 this.post(
- 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(url, data, accessToken)
- }
- throw e
- }
- throw e
+ return await this.handleError(e, () =>
+ this.post(url, data, accessToken, contentType, overrideHeaders)
+ )
})
}
@@ -174,36 +125,17 @@ export class RequestClient implements HttpClient {
...overrideHeaders
}
- try {
- const response = await this.httpClient.put(url, data, {
- headers,
- withCredentials: true
+ return this.httpClient
+ .put(url, data, { headers, withCredentials: true })
+ .then((response) => {
+ throwIfError(response)
+ return this.parseResponse(response)
+ })
+ .catch(async (e) => {
+ return await this.handleError(e, () =>
+ this.put(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(url, data, accessToken)
- }
- throw e
- }
-
- if (response?.status === 401) {
- throw new LoginRequiredError()
- }
- throw e
- }
}
public async delete(
@@ -212,33 +144,15 @@ export class RequestClient implements HttpClient {
): Promise<{ result: T; etag: string }> {
const headers = this.getHeaders(accessToken, 'application/json')
- const requestConfig: AxiosRequestConfig = {
- headers
- }
-
- try {
- const response = await this.httpClient.delete(url, requestConfig)
- 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.delete(url, accessToken)
- }
- throw e
- }
- throw e
- }
+ return this.httpClient
+ .delete(url, { headers, withCredentials: true })
+ .then((response) => {
+ throwIfError(response)
+ return this.parseResponse(response)
+ })
+ .catch(async (e) => {
+ return await this.handleError(e, () => this.delete(url, accessToken))
+ })
}
public async patch(
@@ -248,31 +162,17 @@ export class RequestClient implements HttpClient {
): Promise<{ result: T; etag: string }> {
const headers = this.getHeaders(accessToken, 'application/json')
- try {
- const response = await this.httpClient.patch(url, data, {
- headers,
- withCredentials: true
+ return this.httpClient
+ .patch(url, data, { headers, withCredentials: true })
+ .then((response) => {
+ throwIfError(response)
+ return this.parseResponse(response)
+ })
+ .catch(async (e) => {
+ return await this.handleError(e, () =>
+ this.patch(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(url, accessToken)
- }
- throw e
- }
- throw e
- }
}
public async uploadFile(
@@ -317,9 +217,7 @@ export class RequestClient implements HttpClient {
bodyElement.innerHTML = responseBody
const form = bodyElement.querySelector('#application_authorization')
- authUrl = form
- ? this.httpClient.defaults.baseURL! + form.getAttribute('action')
- : null
+ authUrl = form ? this.baseUrl + form.getAttribute('action') : null
const inputs: any = form?.querySelectorAll('input')
@@ -417,9 +315,60 @@ export class RequestClient implements HttpClient {
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(response: AxiosResponse) {
+ 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) => {
+ if (response.status === 401) {
+ throw new LoginRequiredError()
+ }
+
if (response.data?.entityID?.includes('login')) {
throw new LoginRequiredError()
}
@@ -427,7 +376,7 @@ const throwIfError = (response: AxiosResponse) => {
if (
typeof response.data === 'string' &&
isLogInRequired(response.data) &&
- !response.config.url?.includes('/SASLogon/login')
+ !response.config?.url?.includes('/SASLogon/login')
) {
throw new LoginRequiredError()
}