diff --git a/src/SASViyaApiClient.ts b/src/SASViyaApiClient.ts index 167fbf6..5156a44 100644 --- a/src/SASViyaApiClient.ts +++ b/src/SASViyaApiClient.ts @@ -29,7 +29,11 @@ export class SASViyaApiClient { } private csrfToken: CsrfToken | null = null; private rootFolder: Folder | null = null; - private sessionManager = new SessionManager(this.serverUrl, this.contextName, this.setCsrfToken); + private sessionManager = new SessionManager( + this.serverUrl, + this.contextName, + this.setCsrfToken + ); /** * Returns a map containing the directory structure in the currently set root folder. @@ -113,9 +117,7 @@ export class SASViyaApiClient { `test-${context.name}`, linesOfCode, context.name, - accessToken, - undefined, - true + accessToken ).catch(() => null); }); const results = await Promise.all(promises); @@ -201,11 +203,11 @@ export class SASViyaApiClient { linesOfCode: string[], contextName: string, accessToken?: string, - sessionId = "", silent = false, data = null, debug = false ) { + silent = !debug; const headers: any = { "Content-Type": "application/json", }; @@ -313,7 +315,9 @@ export class SASViyaApiClient { } ).then((res: any) => res.result.items.map((i: any) => i.line).join("\n")); } - + + await this.sessionManager.clearSession(executionSessionId, accessToken); + return { result: jobResult?.result, log }; // } else { // console.error( @@ -623,12 +627,16 @@ export class SASViyaApiClient { await this.populateRootFolder(accessToken); } if (!this.rootFolder) { + console.error("Root folder was not found"); throw new Error("Root folder was not found"); } if (!this.rootFolderMap.size) { await this.populateRootFolderMap(accessToken); } if (!this.rootFolderMap.size) { + console.error( + `The job ${sasJob} was not found in ${this.rootFolderName}` + ); throw new Error( `The job ${sasJob} was not found in ${this.rootFolderName}` ); @@ -647,6 +655,7 @@ export class SASViyaApiClient { (l) => l.rel === "getResource" ); if (!jobDefinitionLink) { + console.error("Job definition URI was not found."); throw new Error("Job definition URI was not found."); } const { result: jobDefinition } = await this.request( @@ -661,7 +670,6 @@ export class SASViyaApiClient { linesToExecute, contextName, accessToken, - "", true, data, debug @@ -1019,9 +1027,9 @@ export class SASViyaApiClient { `${this.serverUrl}${url}`, requestInfo ).catch((err) => { - return {result: null}; - }) - + return { result: null }; + }); + if (!folder) return undefined; return `/folders/folders/${folder.id}`; } @@ -1042,6 +1050,11 @@ export class SASViyaApiClient { [this.csrfToken.headerName]: this.csrfToken.value, }; } - return await makeRequest(url, options, this.setCsrfTokenLocal, contentType); + return await makeRequest( + url, + options, + this.setCsrfTokenLocal, + contentType + ); } } diff --git a/src/SASjs.ts b/src/SASjs.ts index 928740d..4916d71 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -21,7 +21,7 @@ import { SASjsWaitingRequest, ServerType, CsrfToken, - UploadFile + UploadFile, } from "./types"; import { SASViyaApiClient } from "./SASViyaApiClient"; import { SAS9ApiClient } from "./SAS9ApiClient"; @@ -122,7 +122,6 @@ export default class SASjs { linesOfCode, contextName, accessToken, - sessionId, silent ); } @@ -546,52 +545,52 @@ export default class SASjs { sasjsWaitingRequest.requestPromise.promise = new Promise( async (resolve, reject) => { this.sasViyaApiClient - ?.executeComputeJob( - sasJob, - config.contextName, - config.debug, - data, - accessToken - ) - .then((response) => { - if (!config.debug) { - this.appendSasjsRequest(null, sasJob, null); + ?.executeComputeJob( + sasJob, + config.contextName, + config.debug, + data, + accessToken + ) + .then((response) => { + if (!config.debug) { + this.appendSasjsRequest(null, sasJob, null); + } else { + this.appendSasjsRequest(response, sasJob, null); + } + + resolve(JSON.parse(response!.result)); + }) + .catch(async (e) => { + if (needsRetry(JSON.stringify(e))) { + if (this.retryCountComputeApi < requestRetryLimit) { + let retryResponse = await this.executeJobViaComputeApi( + sasJob, + data, + config, + loginRequiredCallback, + accessToken + ); + + this.retryCountComputeApi++; + + resolve(retryResponse); } else { - this.appendSasjsRequest(response, sasJob, null); + this.retryCountComputeApi = 0; + reject({ MESSAGE: "Compute API retry requests limit reached" }); } + } - resolve(JSON.parse(response!.result)); - }) - .catch(async (e) => { - if (needsRetry(JSON.stringify(e))) { - if (this.retryCountComputeApi < requestRetryLimit) { - let retryResponse = await this.executeJobViaComputeApi( - sasJob, - data, - config, - loginRequiredCallback, - accessToken - ); - - this.retryCountComputeApi++; - - resolve(retryResponse); - } else { - this.retryCountComputeApi = 0; - reject({ MESSAGE: "Compute API retry requests limit reached" }); - } - } - - if (e && e.status === 401) { - if (loginRequiredCallback) loginRequiredCallback(true); - sasjsWaitingRequest.requestPromise.resolve = resolve; - sasjsWaitingRequest.requestPromise.reject = reject; - sasjsWaitingRequest.config = config; - this.sasjsWaitingRequests.push(sasjsWaitingRequest); - } else { - reject({ MESSAGE: e || "Job execution failed" }); - } - }) + if (e && e.status === 401) { + if (loginRequiredCallback) loginRequiredCallback(true); + sasjsWaitingRequest.requestPromise.resolve = resolve; + sasjsWaitingRequest.requestPromise.reject = reject; + sasjsWaitingRequest.config = config; + this.sasjsWaitingRequests.push(sasjsWaitingRequest); + } else { + reject({ MESSAGE: e || "Job execution failed" }); + } + }); } ); return sasjsWaitingRequest.requestPromise.promise; @@ -652,9 +651,9 @@ export default class SASjs { loginRequiredCallback, accessToken ); - + this.retryCountJeseApi++; - + resolve(retryResponse); } else { this.retryCountJeseApi = 0; @@ -662,7 +661,7 @@ export default class SASjs { } } - reject({ MESSAGE: (e && e.message) || "Job execution failed" }) + reject({ MESSAGE: (e && e.message) || "Job execution failed" }); }) ); } diff --git a/src/SessionManager.ts b/src/SessionManager.ts index 1a20b96..69b40a7 100644 --- a/src/SessionManager.ts +++ b/src/SessionManager.ts @@ -16,7 +16,21 @@ export class SessionManager { async getSession(accessToken?: string) { await this.createSessions(accessToken); this.createAndWaitForSession(accessToken); - return this.sessions.pop(); + const session = this.sessions.pop(); + return session; + } + + async clearSession(id: string, accessToken?: string) { + const deleteSessionRequest = { + method: "DELETE", + headers: this.getHeaders(accessToken), + }; + return await this.request( + `${this.serverUrl}/compute/sessions/${id}`, + deleteSessionRequest + ).then(() => { + this.sessions = this.sessions.filter((s) => s.id !== id); + }); } private async createSessions(accessToken?: string) { diff --git a/src/utils/makeRequest.ts b/src/utils/makeRequest.ts index 5bf8869..5fda9c8 100644 --- a/src/utils/makeRequest.ts +++ b/src/utils/makeRequest.ts @@ -44,24 +44,32 @@ export async function makeRequest( if (needsRetry(body)) { if (retryCount < retryLimit) { retryCount++; - let retryResponse = await makeRequest(url, retryRequest || request, callback, contentType); + let retryResponse = await makeRequest( + url, + retryRequest || request, + callback, + contentType + ); retryCount = 0; - + return retryResponse; } else { retryCount = 0; - - throw new Error('Request retry limit exceeded'); + + throw new Error("Request retry limit exceeded"); } } - + return Promise.reject({ status: response.status, body }); } } else { + if (response.status === 204) { + return Promise.resolve(); + } const responseTransformed = await responseTransform(response); - let responseText = ''; + let responseText = ""; - if (typeof responseTransformed === 'string') { + if (typeof responseTransformed === "string") { responseText = responseTransformed; } else { responseText = JSON.stringify(responseTransformed); @@ -70,18 +78,23 @@ export async function makeRequest( if (response.redirected && response.url.includes("SASLogon/login")) { return Promise.reject({ status: 401, responseTransformed }); } - + if (needsRetry(responseText)) { if (retryCount < retryLimit) { retryCount++; - let retryResponse = await makeRequest(url, retryRequest || request, callback, contentType); + const retryResponse = await makeRequest( + url, + retryRequest || request, + callback, + contentType + ); retryCount = 0; return retryResponse; } else { retryCount = 0; - - throw new Error('Request retry limit exceeded'); + + throw new Error("Request retry limit exceeded"); } } diff --git a/src/utils/needsRetry.ts b/src/utils/needsRetry.ts index cb70c6c..e912573 100644 --- a/src/utils/needsRetry.ts +++ b/src/utils/needsRetry.ts @@ -1,11 +1,14 @@ export const needsRetry = (responseText: string): boolean => { return ( - (responseText.includes('"errorCode":403') && + !!responseText && + ((responseText.includes('"errorCode":403') && responseText.includes("_csrf") && responseText.includes("X-CSRF-TOKEN")) || - (responseText.includes('"status":403') && - responseText.includes('"error":"Forbidden"')) || - (responseText.includes('"status":449') && - responseText.includes("Authentication success, retry original request")) + (responseText.includes('"status":403') && + responseText.includes('"error":"Forbidden"')) || + (responseText.includes('"status":449') && + responseText.includes( + "Authentication success, retry original request" + ))) ); };