mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-05 03:30:05 +00:00
chore(*): refactor, move all auth-related code to auth folder
This commit is contained in:
@@ -12,7 +12,7 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
|
||||
return adapter.startComputeJob("/Public/app/common/sendArr", data);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const expectedProperties = ["id", "applicationName", "attributes"]
|
||||
const expectedProperties = ["id", "applicationName", "attributes"];
|
||||
return validate(expectedProperties, res);
|
||||
}
|
||||
},
|
||||
@@ -21,11 +21,22 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
|
||||
description: "Should start a compute job and return the job",
|
||||
test: () => {
|
||||
const data: any = { table1: [{ col1: "first col value" }] };
|
||||
return adapter.startComputeJob("/Public/app/common/sendArr", data, {}, "", true);
|
||||
return adapter.startComputeJob(
|
||||
"/Public/app/common/sendArr",
|
||||
data,
|
||||
{},
|
||||
"",
|
||||
true
|
||||
);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const expectedProperties = ["id", "state", "creationTimeStamp", "jobConditionCode"]
|
||||
return validate(expectedProperties, res.result);
|
||||
const expectedProperties = [
|
||||
"id",
|
||||
"state",
|
||||
"creationTimeStamp",
|
||||
"jobConditionCode"
|
||||
];
|
||||
return validate(expectedProperties, res.job);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -38,19 +49,19 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
|
||||
`output;`,
|
||||
`end;`,
|
||||
`run;`
|
||||
]
|
||||
|
||||
];
|
||||
|
||||
return adapter.executeScriptSASViya(
|
||||
'sasCode.sas',
|
||||
"sasCode.sas",
|
||||
fileLines,
|
||||
'SAS Studio compute context',
|
||||
"SAS Studio compute context",
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n`
|
||||
|
||||
const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n`;
|
||||
|
||||
return validateLog(expectedLogContent, res.log);
|
||||
}
|
||||
},
|
||||
@@ -58,21 +69,21 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
|
||||
title: "Execute Script Viya - failed job",
|
||||
description: "Should execute sas file and return log",
|
||||
test: () => {
|
||||
const fileLines = [
|
||||
`%abort;`
|
||||
]
|
||||
|
||||
return adapter.executeScriptSASViya(
|
||||
'sasCode.sas',
|
||||
fileLines,
|
||||
'SAS Studio compute context',
|
||||
undefined,
|
||||
true
|
||||
).catch((err: any) => err )
|
||||
const fileLines = [`%abort;`];
|
||||
|
||||
return adapter
|
||||
.executeScriptSASViya(
|
||||
"sasCode.sas",
|
||||
fileLines,
|
||||
"SAS Studio compute context",
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
.catch((err: any) => err);
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const expectedLogContent = `1 %abort;\\nERROR: The %ABORT statement is not valid in open code.\\n`
|
||||
|
||||
const expectedLogContent = `1 %abort;\\nERROR: The %ABORT statement is not valid in open code.\\n`;
|
||||
|
||||
return validateLog(expectedLogContent, res.log);
|
||||
}
|
||||
}
|
||||
@@ -80,16 +91,16 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
|
||||
});
|
||||
|
||||
const validateLog = (text: string, log: string): boolean => {
|
||||
const isValid = JSON.stringify(log).includes(text)
|
||||
const isValid = JSON.stringify(log).includes(text);
|
||||
|
||||
return isValid
|
||||
}
|
||||
return isValid;
|
||||
};
|
||||
|
||||
const validate = (expectedProperties: string[], data: any): boolean => {
|
||||
const actualProperties = Object.keys(data);
|
||||
|
||||
const isValid = expectedProperties.every(
|
||||
(property) => actualProperties.includes(property)
|
||||
const isValid = expectedProperties.every((property) =>
|
||||
actualProperties.includes(property)
|
||||
);
|
||||
return isValid
|
||||
}
|
||||
return isValid;
|
||||
};
|
||||
|
||||
@@ -23,11 +23,12 @@ export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
|
||||
},
|
||||
{
|
||||
title: "Make error and capture log",
|
||||
description: "Should make an error and capture log, in the same time it is testing if debug override is working",
|
||||
description:
|
||||
"Should make an error and capture log, in the same time it is testing if debug override is working",
|
||||
test: async () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
adapter
|
||||
.request("common/makeErr", data, {debug: true})
|
||||
.request("common/makeErr", data, { debug: true })
|
||||
.then((res) => {
|
||||
//no action here, this request must throw error
|
||||
})
|
||||
@@ -38,9 +39,11 @@ export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
|
||||
req.serviceLink.includes("makeErr")
|
||||
) || null;
|
||||
|
||||
if (!makeErrRequest) resolve(false)
|
||||
if (!makeErrRequest) return resolve(false);
|
||||
|
||||
resolve(!!(makeErrRequest.logFile && makeErrRequest.logFile.length > 0));
|
||||
return resolve(
|
||||
!!(makeErrRequest.logFile && makeErrRequest.logFile.length > 0)
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { isLogInRequired, needsRetry, isUrl } from './utils'
|
||||
import { needsRetry, isUrl } from './utils'
|
||||
import { CsrfToken } from './types/CsrfToken'
|
||||
import { UploadFile } from './types/UploadFile'
|
||||
import { ErrorResponse } from './types'
|
||||
import axios, { AxiosInstance } from 'axios'
|
||||
import { isLogInRequired } from './auth'
|
||||
|
||||
const requestRetryLimit = 5
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import {
|
||||
isAuthorizeFormRequired,
|
||||
parseAndSubmitAuthorizeForm,
|
||||
convertToCSV,
|
||||
makeRequest,
|
||||
isRelativePath,
|
||||
isUri,
|
||||
isUrl
|
||||
@@ -24,12 +22,15 @@ import { SessionManager } from './SessionManager'
|
||||
import { ContextManager } from './ContextManager'
|
||||
import { timestampToYYYYMMDDHHMMSS } from '@sasjs/utils/time'
|
||||
import { Logger, LogLevel } from '@sasjs/utils/logger'
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired'
|
||||
|
||||
/**
|
||||
* A client for interfacing with the SAS Viya REST API.
|
||||
*
|
||||
*/
|
||||
export class SASViyaApiClient {
|
||||
private httpClient: AxiosInstance
|
||||
constructor(
|
||||
private serverUrl: string,
|
||||
private rootFolderName: string,
|
||||
@@ -37,6 +38,7 @@ export class SASViyaApiClient {
|
||||
private setCsrfToken: (csrfToken: CsrfToken) => void
|
||||
) {
|
||||
if (serverUrl) isUrl(serverUrl)
|
||||
this.httpClient = axios.create({ baseURL: serverUrl })
|
||||
}
|
||||
|
||||
private csrfToken: CsrfToken | null = null
|
||||
@@ -146,10 +148,11 @@ export class SASViyaApiClient {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
||||
`${this.serverUrl}/compute/contexts?limit=10000`,
|
||||
{ headers }
|
||||
const { result: contexts } = await this.get<{ items: Context[] }>(
|
||||
`/compute/contexts?limit=10000`,
|
||||
accessToken
|
||||
)
|
||||
|
||||
const executionContext =
|
||||
contexts.items && contexts.items.length
|
||||
? contexts.items.find((c: any) => c.name === contextName)
|
||||
@@ -165,9 +168,10 @@ export class SASViyaApiClient {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
const { result: createdSession } = await this.request<Session>(
|
||||
`${this.serverUrl}/compute/contexts/${executionContext.id}/sessions`,
|
||||
createSessionRequest
|
||||
const { result: createdSession } = await this.post<Session>(
|
||||
`/compute/contexts/${executionContext.id}/sessions`,
|
||||
{},
|
||||
accessToken
|
||||
)
|
||||
|
||||
return createdSession
|
||||
@@ -370,22 +374,18 @@ export class SASViyaApiClient {
|
||||
}
|
||||
|
||||
// Execute job in session
|
||||
const postJobRequest = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
name: fileName,
|
||||
description: 'Powered by SASjs',
|
||||
code: linesOfCode,
|
||||
variables: jobVariables,
|
||||
arguments: jobArguments
|
||||
})
|
||||
}
|
||||
|
||||
const { result: postedJob, etag } = await this.request<Job>(
|
||||
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs`,
|
||||
postJobRequest
|
||||
).catch((err) => {
|
||||
const jobRequestBody = JSON.stringify({
|
||||
name: fileName,
|
||||
description: 'Powered by SASjs',
|
||||
code: linesOfCode,
|
||||
variables: jobVariables,
|
||||
arguments: jobArguments
|
||||
})
|
||||
const { result: postedJob, etag } = await this.post<Job>(
|
||||
`/compute/sessions/${executionSessionId}/jobs`,
|
||||
jobRequestBody,
|
||||
accessToken
|
||||
).catch((err: any) => {
|
||||
throw err
|
||||
})
|
||||
|
||||
@@ -409,9 +409,9 @@ export class SASViyaApiClient {
|
||||
pollOptions
|
||||
)
|
||||
|
||||
const { result: currentJob } = await this.request<Job>(
|
||||
`${this.serverUrl}/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
|
||||
{ headers }
|
||||
const { result: currentJob } = await this.get<Job>(
|
||||
`/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
|
||||
accessToken
|
||||
).catch((err) => {
|
||||
throw err
|
||||
})
|
||||
@@ -422,11 +422,9 @@ export class SASViyaApiClient {
|
||||
const logLink = currentJob.links.find((l) => l.rel === 'log')
|
||||
|
||||
if (debug && logLink) {
|
||||
log = await this.request<any>(
|
||||
`${this.serverUrl}${logLink.href}/content?limit=10000`,
|
||||
{
|
||||
headers
|
||||
}
|
||||
log = await this.get<any>(
|
||||
`${logLink.href}/content?limit=10000`,
|
||||
accessToken
|
||||
)
|
||||
.then((res: any) =>
|
||||
res.result.items.map((i: any) => i.line).join('\n')
|
||||
@@ -449,18 +447,16 @@ export class SASViyaApiClient {
|
||||
}
|
||||
|
||||
if (resultLink) {
|
||||
jobResult = await this.request<any>(
|
||||
`${this.serverUrl}${resultLink}`,
|
||||
{ headers },
|
||||
'text'
|
||||
jobResult = await this.get<any>(
|
||||
resultLink,
|
||||
accessToken,
|
||||
'text/plain'
|
||||
).catch(async (e) => {
|
||||
if (e && e.status === 404) {
|
||||
if (logLink) {
|
||||
log = await this.request<any>(
|
||||
`${this.serverUrl}${logLink.href}/content?limit=10000`,
|
||||
{
|
||||
headers
|
||||
}
|
||||
log = await this.get<any>(
|
||||
`${logLink.href}/content?limit=10000`,
|
||||
accessToken
|
||||
)
|
||||
.then((res: any) =>
|
||||
res.result.items.map((i: any) => i.line).join('\n')
|
||||
@@ -471,7 +467,7 @@ export class SASViyaApiClient {
|
||||
|
||||
return Promise.reject({
|
||||
status: 500,
|
||||
log: log
|
||||
log
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -568,22 +564,13 @@ export class SASViyaApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
const createFolderRequest: RequestInit = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
const { result: createFolderResponse } = await this.post<Folder>(
|
||||
`/folders/folders?parentFolderUri=${parentFolderUri}`,
|
||||
JSON.stringify({
|
||||
name: folderName,
|
||||
type: 'folder'
|
||||
})
|
||||
}
|
||||
|
||||
createFolderRequest.headers = { 'Content-Type': 'application/json' }
|
||||
if (accessToken) {
|
||||
createFolderRequest.headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const { result: createFolderResponse } = await this.request<Folder>(
|
||||
`${this.serverUrl}/folders/folders?parentFolderUri=${parentFolderUri}`,
|
||||
createFolderRequest
|
||||
}),
|
||||
accessToken
|
||||
)
|
||||
|
||||
// update folder map with newly created folder.
|
||||
@@ -617,13 +604,9 @@ export class SASViyaApiClient {
|
||||
parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken)
|
||||
}
|
||||
|
||||
const createJobDefinitionRequest: RequestInit = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/vnd.sas.job.definition+json',
|
||||
Accept: 'application/vnd.sas.job.definition+json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
return await this.post<Job>(
|
||||
`${this.serverUrl}/jobDefinitions/definitions?parentFolderUri=${parentFolderUri}`,
|
||||
JSON.stringify({
|
||||
name: jobName,
|
||||
parameters: [
|
||||
{
|
||||
@@ -634,19 +617,8 @@ export class SASViyaApiClient {
|
||||
],
|
||||
type: 'Compute',
|
||||
code
|
||||
})
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
createJobDefinitionRequest!.headers = {
|
||||
...createJobDefinitionRequest.headers,
|
||||
Authorization: `Bearer ${accessToken}`
|
||||
}
|
||||
}
|
||||
|
||||
return await this.request<Job>(
|
||||
`${this.serverUrl}/jobDefinitions/definitions?parentFolderUri=${parentFolderUri}`,
|
||||
createJobDefinitionRequest
|
||||
}),
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
@@ -795,13 +767,10 @@ export class SASViyaApiClient {
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
const deleteResponse = await this.request(url, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include',
|
||||
headers
|
||||
})
|
||||
|
||||
return deleteResponse
|
||||
const deleteResponse = await this.delete(url, accessToken)
|
||||
|
||||
return deleteResponse.result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -871,9 +840,9 @@ export class SASViyaApiClient {
|
||||
throw new Error(`URI of job definition was not found.`)
|
||||
}
|
||||
|
||||
const { result: jobDefinition } = await this.request<JobDefinition>(
|
||||
const { result: jobDefinition } = await this.get<JobDefinition>(
|
||||
`${this.serverUrl}${jobDefinitionLink.href}`,
|
||||
{ headers }
|
||||
accessToken
|
||||
)
|
||||
|
||||
code = jobDefinition.code
|
||||
@@ -948,20 +917,10 @@ export class SASViyaApiClient {
|
||||
const jobDefinitionLink = jobToExecute?.links.find(
|
||||
(l) => l.rel === 'getResource'
|
||||
)?.href
|
||||
const requestInfo: any = {
|
||||
method: 'GET'
|
||||
}
|
||||
const headers: any = { 'Content-Type': 'application/json' }
|
||||
|
||||
if (!!accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
requestInfo.headers = headers
|
||||
|
||||
const { result: jobDefinition } = await this.request<Job>(
|
||||
const { result: jobDefinition } = await this.get<Job>(
|
||||
`${this.serverUrl}${jobDefinitionLink}`,
|
||||
requestInfo
|
||||
accessToken
|
||||
)
|
||||
|
||||
const jobArguments: { [key: string]: any } = {
|
||||
@@ -988,48 +947,43 @@ export class SASViyaApiClient {
|
||||
jobArguments[`_webin_name${index + 1}`] = fileInfo.tableName
|
||||
})
|
||||
|
||||
const postJobRequest = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
name: `exec-${jobName}`,
|
||||
description: 'Powered by SASjs',
|
||||
jobDefinition,
|
||||
arguments: jobArguments
|
||||
})
|
||||
}
|
||||
const { result: postedJob, etag } = await this.request<Job>(
|
||||
const postJobRequestBody = JSON.stringify({
|
||||
name: `exec-${jobName}`,
|
||||
description: 'Powered by SASjs',
|
||||
jobDefinition,
|
||||
arguments: jobArguments
|
||||
})
|
||||
const { result: postedJob, etag } = await this.post<Job>(
|
||||
`${this.serverUrl}/jobExecution/jobs?_action=wait`,
|
||||
postJobRequest
|
||||
postJobRequestBody
|
||||
)
|
||||
const jobStatus = await this.pollJobState(postedJob, etag, accessToken)
|
||||
const { result: currentJob } = await this.request<Job>(
|
||||
const { result: currentJob } = await this.get<Job>(
|
||||
`${this.serverUrl}/jobExecution/jobs/${postedJob.id}`,
|
||||
{ headers }
|
||||
accessToken
|
||||
)
|
||||
|
||||
let jobResult
|
||||
let log
|
||||
if (jobStatus === 'failed') {
|
||||
return Promise.reject(currentJob.error)
|
||||
}
|
||||
|
||||
const resultLink = currentJob.results['_webout.json']
|
||||
const logLink = currentJob.links.find((l) => l.rel === 'log')
|
||||
if (resultLink) {
|
||||
jobResult = await this.request<any>(
|
||||
jobResult = await this.get<any>(
|
||||
`${this.serverUrl}${resultLink}/content`,
|
||||
{ headers },
|
||||
'text'
|
||||
accessToken,
|
||||
'text/plain'
|
||||
)
|
||||
}
|
||||
if (debug && logLink) {
|
||||
log = await this.request<any>(
|
||||
log = await this.get<any>(
|
||||
`${this.serverUrl}${logLink.href}/content`,
|
||||
{
|
||||
headers
|
||||
}
|
||||
accessToken
|
||||
).then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
|
||||
}
|
||||
if (jobStatus === 'failed') {
|
||||
return Promise.reject({ error: currentJob.error, log })
|
||||
}
|
||||
return { result: jobResult?.result, log }
|
||||
}
|
||||
|
||||
@@ -1042,22 +996,13 @@ export class SASViyaApiClient {
|
||||
}
|
||||
|
||||
const url = '/folders/folders/@item?path=' + path
|
||||
const requestInfo: any = {
|
||||
method: 'GET'
|
||||
}
|
||||
if (accessToken) {
|
||||
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }
|
||||
}
|
||||
const { result: folder } = await this.request<Folder>(
|
||||
`${this.serverUrl}${url}`,
|
||||
requestInfo
|
||||
)
|
||||
const { result: folder } = await this.get<Folder>(`${url}`, accessToken)
|
||||
if (!folder) {
|
||||
throw new Error(`The path ${path} does not exist on ${this.serverUrl}`)
|
||||
}
|
||||
const { result: members } = await this.request<{ items: any[] }>(
|
||||
`${this.serverUrl}/folders/folders/${folder.id}/members?limit=${folder.memberCount}`,
|
||||
requestInfo
|
||||
const { result: members } = await this.get<{ items: any[] }>(
|
||||
`/folders/folders/${folder.id}/members?limit=${folder.memberCount}`,
|
||||
accessToken
|
||||
)
|
||||
|
||||
const itemsAtRoot = members.items
|
||||
@@ -1093,12 +1038,10 @@ export class SASViyaApiClient {
|
||||
Promise.reject(`Job state link was not found.`)
|
||||
}
|
||||
|
||||
const { result: state } = await this.request<string>(
|
||||
const { result: state } = await this.get<string>(
|
||||
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
|
||||
{
|
||||
headers
|
||||
},
|
||||
'text'
|
||||
accessToken,
|
||||
'text/plain'
|
||||
)
|
||||
|
||||
const currentState = state.trim()
|
||||
@@ -1116,12 +1059,10 @@ export class SASViyaApiClient {
|
||||
postedJobState === 'pending'
|
||||
) {
|
||||
if (stateLink) {
|
||||
const { result: jobState } = await this.request<string>(
|
||||
const { result: jobState } = await this.get<string>(
|
||||
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
|
||||
{
|
||||
headers
|
||||
},
|
||||
'text'
|
||||
accessToken,
|
||||
'text/plain'
|
||||
)
|
||||
|
||||
postedJobState = jobState.trim()
|
||||
@@ -1164,17 +1105,10 @@ export class SASViyaApiClient {
|
||||
)
|
||||
}
|
||||
|
||||
const createFileRequest = {
|
||||
method: 'POST',
|
||||
body: csv,
|
||||
headers
|
||||
}
|
||||
|
||||
const uploadResponse = await this.request<any>(
|
||||
const uploadResponse = await this.uploadFile(
|
||||
`${this.serverUrl}/files/files#rawUpload`,
|
||||
createFileRequest,
|
||||
'json',
|
||||
'fileUpload'
|
||||
csv,
|
||||
accessToken
|
||||
)
|
||||
|
||||
uploadedFiles.push({ tableName, file: uploadResponse.result })
|
||||
@@ -1184,16 +1118,10 @@ export class SASViyaApiClient {
|
||||
|
||||
private async getFolderUri(folderPath: string, accessToken?: string) {
|
||||
const url = '/folders/folders/@item?path=' + folderPath
|
||||
const requestInfo: any = {
|
||||
method: 'GET'
|
||||
}
|
||||
if (accessToken) {
|
||||
requestInfo.headers = { Authorization: `Bearer ${accessToken}` }
|
||||
}
|
||||
const { result: folder } = await this.request<Folder>(
|
||||
const { result: folder } = await this.get<Folder>(
|
||||
`${this.serverUrl}${url}`,
|
||||
requestInfo
|
||||
).catch((err) => {
|
||||
accessToken
|
||||
).catch(() => {
|
||||
return { result: null }
|
||||
})
|
||||
|
||||
@@ -1203,18 +1131,11 @@ export class SASViyaApiClient {
|
||||
|
||||
private async getRecycleBinUri(accessToken: string) {
|
||||
const url = '/folders/folders/@myRecycleBin'
|
||||
const requestInfo = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer ' + accessToken
|
||||
}
|
||||
}
|
||||
|
||||
const { result: folder } = await this.request<Folder>(
|
||||
const { result: folder } = await this.get<Folder>(
|
||||
`${this.serverUrl}${url}`,
|
||||
requestInfo
|
||||
).catch((err) => {
|
||||
accessToken
|
||||
).catch(() => {
|
||||
return { result: null }
|
||||
})
|
||||
|
||||
@@ -1280,22 +1201,14 @@ export class SASViyaApiClient {
|
||||
const sourceFolderId = sourceFolderUri?.split('/').pop()
|
||||
const url = sourceFolderUri
|
||||
|
||||
const requestInfo = {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer ' + accessToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
const { result: folder } = await this.patch<Folder>(
|
||||
`${this.serverUrl}${url}`,
|
||||
JSON.stringify({
|
||||
id: sourceFolderId,
|
||||
name: targetFolderName,
|
||||
parentFolderUri: targetParentFolderUri
|
||||
})
|
||||
}
|
||||
|
||||
const { result: folder } = await this.request<Folder>(
|
||||
`${this.serverUrl}${url}`,
|
||||
requestInfo
|
||||
}),
|
||||
accessToken
|
||||
).catch((err) => {
|
||||
if (err.code && err.code === 'ENOTFOUND') {
|
||||
const notFoundError = {
|
||||
@@ -1346,32 +1259,165 @@ export class SASViyaApiClient {
|
||||
this.fileUploadCsrfToken = csrfToken
|
||||
}
|
||||
|
||||
private async request<T>(
|
||||
private get<T>(
|
||||
url: string,
|
||||
options: RequestInit,
|
||||
contentType: 'text' | 'json' = 'json',
|
||||
type: 'fileUpload' | 'other' = 'other'
|
||||
accessToken?: string,
|
||||
contentType = 'application/json'
|
||||
) {
|
||||
const callback =
|
||||
type === 'fileUpload'
|
||||
? this.setFileUploadCsrfToken
|
||||
: this.setCsrfTokenLocal
|
||||
|
||||
if (type === 'other') {
|
||||
if (this.csrfToken) {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
[this.csrfToken.headerName]: this.csrfToken.value
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.fileUploadCsrfToken) {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
[this.fileUploadCsrfToken.headerName]: this.fileUploadCsrfToken.value
|
||||
}
|
||||
}
|
||||
const headers: any = {
|
||||
'Content-Type': contentType
|
||||
}
|
||||
return await makeRequest<T>(url, options, callback, contentType)
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const requestConfig: AxiosRequestConfig = {
|
||||
headers,
|
||||
responseType: contentType === 'text/plain' ? 'text' : 'json',
|
||||
withCredentials: true
|
||||
}
|
||||
if (contentType === 'text/plain') {
|
||||
requestConfig.headers.Accept = '*/*'
|
||||
requestConfig.transformResponse = undefined
|
||||
}
|
||||
|
||||
return this.httpClient.get<T>(url, requestConfig).then((response) => ({
|
||||
result: response.data,
|
||||
etag: response.headers['etag']
|
||||
}))
|
||||
}
|
||||
|
||||
private post<T>(
|
||||
url: string,
|
||||
data: any = {},
|
||||
accessToken?: string
|
||||
): Promise<{ result: T; etag: string }> {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
if (this.csrfToken?.value) {
|
||||
headers[this.csrfToken.headerName] = this.csrfToken.value
|
||||
}
|
||||
|
||||
return this.httpClient
|
||||
.post<T>(url, data, { headers, withCredentials: true })
|
||||
.then((response) => {
|
||||
return {
|
||||
result: response.data as T,
|
||||
etag: response.headers['etag'] as string
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
const response = e.response as AxiosResponse
|
||||
if (response.status === 403 || response.status === 449) {
|
||||
const tokenHeader = (response.headers[
|
||||
'x-csrf-header'
|
||||
] as string)?.toLowerCase()
|
||||
|
||||
if (tokenHeader) {
|
||||
const token = response.headers[tokenHeader]
|
||||
this.setCsrfTokenLocal({
|
||||
headerName: tokenHeader,
|
||||
value: token || ''
|
||||
})
|
||||
return this.post<T>(url, data, accessToken)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
throw e
|
||||
})
|
||||
}
|
||||
|
||||
private delete<T>(url: string, accessToken?: string) {
|
||||
const headers: any = {}
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
if (this.csrfToken?.value) {
|
||||
headers[this.csrfToken.headerName] = this.csrfToken.value
|
||||
}
|
||||
|
||||
const requestConfig: AxiosRequestConfig = {
|
||||
headers
|
||||
}
|
||||
|
||||
return this.httpClient.delete<T>(url, requestConfig).then((response) => ({
|
||||
result: response.data,
|
||||
etag: response.headers['etag']
|
||||
}))
|
||||
}
|
||||
|
||||
private patch<T>(url: string, data: any = {}, accessToken?: string) {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
if (this.csrfToken?.value) {
|
||||
headers[this.csrfToken.headerName] = this.csrfToken.value
|
||||
}
|
||||
|
||||
return this.httpClient
|
||||
.patch<T>(url, data, { headers, withCredentials: true })
|
||||
.then((response) => {
|
||||
return {
|
||||
result: response.data as T,
|
||||
etag: response.headers['etag'] as string
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private uploadFile(
|
||||
url: string,
|
||||
content: string,
|
||||
accessToken?: string
|
||||
): Promise<any> {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
if (this.fileUploadCsrfToken?.value) {
|
||||
headers[
|
||||
this.fileUploadCsrfToken.headerName
|
||||
] = this.fileUploadCsrfToken.value
|
||||
}
|
||||
|
||||
return this.httpClient
|
||||
.post(url, content, { headers, withCredentials: true })
|
||||
.then((response) => {
|
||||
return {
|
||||
result: response.data,
|
||||
etag: response.headers['etag'] as string
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
const response = e.response as AxiosResponse
|
||||
if (response.status === 403 || response.status === 449) {
|
||||
const tokenHeader = (response.headers[
|
||||
'x-csrf-header'
|
||||
] as string)?.toLowerCase()
|
||||
|
||||
if (tokenHeader) {
|
||||
const token = response.headers[tokenHeader]
|
||||
this.setFileUploadCsrfToken({
|
||||
headerName: tokenHeader,
|
||||
value: token || ''
|
||||
})
|
||||
return this.uploadFile(url, content, accessToken)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
throw e
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
15
src/SASjs.ts
15
src/SASjs.ts
@@ -2,7 +2,6 @@ import {
|
||||
convertToCSV,
|
||||
compareTimestamps,
|
||||
splitChunks,
|
||||
isLogInRequired,
|
||||
parseSourceCode,
|
||||
parseGeneratedCode,
|
||||
parseWeboutResponse,
|
||||
@@ -24,7 +23,7 @@ import {
|
||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||
import { FileUploader } from './FileUploader'
|
||||
import { AuthManager } from './auth/auth'
|
||||
import { isLogInRequired, AuthManager } from './auth'
|
||||
|
||||
const defaultConfig: SASjsConfig = {
|
||||
serverUrl: '',
|
||||
@@ -732,7 +731,11 @@ export default class SASjs {
|
||||
let responseJson
|
||||
|
||||
try {
|
||||
responseJson = JSON.parse(response!.result)
|
||||
if (typeof response!.result === 'string') {
|
||||
responseJson = JSON.parse(response!.result)
|
||||
} else {
|
||||
responseJson = response!.result
|
||||
}
|
||||
} catch {
|
||||
responseJson = JSON.parse(parseWeboutResponse(response!.result))
|
||||
}
|
||||
@@ -837,7 +840,11 @@ export default class SASjs {
|
||||
let responseJson
|
||||
|
||||
try {
|
||||
responseJson = JSON.parse(response!.result)
|
||||
if (typeof response!.result === 'string') {
|
||||
responseJson = JSON.parse(response!.result)
|
||||
} else {
|
||||
responseJson = response!.result
|
||||
}
|
||||
} catch {
|
||||
responseJson = JSON.parse(
|
||||
parseWeboutResponse(response!.result)
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import axios, { AxiosInstance } from 'axios'
|
||||
import { isAuthorizeFormRequired, parseAndSubmitAuthorizeForm } from '.'
|
||||
import { ServerType } from '../types'
|
||||
import {
|
||||
serialize,
|
||||
isAuthorizeFormRequired,
|
||||
parseAndSubmitAuthorizeForm,
|
||||
isLogInSuccess
|
||||
} from '../utils'
|
||||
import { serialize } from '../utils'
|
||||
|
||||
export class AuthManager {
|
||||
public userName = ''
|
||||
@@ -15,7 +11,7 @@ export class AuthManager {
|
||||
constructor(
|
||||
private serverUrl: string,
|
||||
private serverType: ServerType,
|
||||
private loginCallback: Function
|
||||
private loginCallback: () => Promise<void>
|
||||
) {
|
||||
this.httpClient = axios.create({ baseURL: this.serverUrl })
|
||||
this.loginUrl = `/SASLogon/login`
|
||||
@@ -41,7 +37,7 @@ export class AuthManager {
|
||||
|
||||
const { isLoggedIn, loginForm } = await this.checkSession()
|
||||
if (isLoggedIn) {
|
||||
this.loginCallback()
|
||||
await this.loginCallback()
|
||||
|
||||
return {
|
||||
isLoggedIn,
|
||||
@@ -58,7 +54,10 @@ export class AuthManager {
|
||||
.post<string>(this.loginUrl, loginParamsStr, {
|
||||
withCredentials: true,
|
||||
responseType: 'text',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: '*/*'
|
||||
}
|
||||
})
|
||||
.then((response) => response.data)
|
||||
|
||||
@@ -159,3 +158,6 @@ export class AuthManager {
|
||||
return this.httpClient.get(this.logoutUrl).then(() => true)
|
||||
}
|
||||
}
|
||||
|
||||
const isLogInSuccess = (response: string): boolean =>
|
||||
/You have signed in/gm.test(response)
|
||||
6
src/auth/index.ts
Normal file
6
src/auth/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { AuthManager } from './AuthManager'
|
||||
|
||||
export * from './AuthManager'
|
||||
export * from './isAuthorizeFormRequired'
|
||||
export * from './isLoginRequired'
|
||||
export * from './parseAndSubmitAuthorizeForm'
|
||||
@@ -37,6 +37,12 @@ export const parseAndSubmitAuthorizeForm = async (
|
||||
}
|
||||
|
||||
return await axios
|
||||
.post(authUrl, formData, { withCredentials: true, responseType: 'text' })
|
||||
.then((response) => response.data)
|
||||
.post(authUrl, formData, {
|
||||
withCredentials: true,
|
||||
responseType: 'text',
|
||||
headers: {
|
||||
Accept: '*/*'
|
||||
}
|
||||
})
|
||||
.then((res) => res.data)
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
export * from './asyncForEach'
|
||||
export * from './compareTimestamps'
|
||||
export * from './convertToCsv'
|
||||
export * from './isAuthorizeFormRequired'
|
||||
export * from './isLoginRequired'
|
||||
export * from './isLoginSuccess'
|
||||
export * from './isRelativePath'
|
||||
export * from './isUri'
|
||||
export * from './isUrl'
|
||||
export * from './makeRequest'
|
||||
export * from './needsRetry'
|
||||
export * from './parseAndSubmitAuthorizeForm'
|
||||
export * from './parseGeneratedCode'
|
||||
export * from './parseSourceCode'
|
||||
export * from './parseSasViyaLog'
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export const isLogInSuccess = (response: string): boolean =>
|
||||
/You have signed in/gm.test(response)
|
||||
Reference in New Issue
Block a user