From 755bf7d07c3b502d3537e3ae2d05c9c94976dabf Mon Sep 17 00:00:00 2001 From: Krishna Acondy Date: Tue, 1 Sep 2020 11:51:17 +0100 Subject: [PATCH] chore(*): change code style to remove semicolons --- .prettierrc | 2 +- src/FileUploader.ts | 58 +- src/SAS9ApiClient.ts | 12 +- src/SASViyaApiClient.ts | 588 ++++++++++---------- src/SASjs.spec.ts | 54 +- src/SASjs.ts | 656 +++++++++++------------ src/SessionManager.ts | 98 ++-- src/index.ts | 10 +- src/types/Context.ts | 8 +- src/types/CsrfToken.ts | 4 +- src/types/Folder.ts | 8 +- src/types/Job.ts | 20 +- src/types/JobDefinition.ts | 2 +- src/types/JobResult.ts | 2 +- src/types/Link.ts | 10 +- src/types/SASjsConfig.ts | 18 +- src/types/SASjsRequest.ts | 12 +- src/types/SASjsWaitingRequest.ts | 14 +- src/types/Session.ts | 14 +- src/types/UploadFile.ts | 4 +- src/types/index.ts | 22 +- src/utils/asyncForEach.ts | 2 +- src/utils/compareTimestamps.ts | 6 +- src/utils/convertToCsv.ts | 98 ++-- src/utils/formatDataForRequest.ts | 34 +- src/utils/index.ts | 30 +- src/utils/isAuthorizeFormRequired.ts | 4 +- src/utils/isIeOrEdge.ts | 22 +- src/utils/isLoginRequired.ts | 8 +- src/utils/isLoginSuccess.ts | 2 +- src/utils/makeRequest.ts | 80 +-- src/utils/needsRetry.ts | 4 +- src/utils/parseAndSubmitAuthorizeForm.ts | 34 +- src/utils/parseGeneratedCode.ts | 10 +- src/utils/parseSasViyaLog.ts | 12 +- src/utils/parseSourceCode.ts | 8 +- src/utils/parseWeboutResponse.ts | 12 +- src/utils/serialize.ts | 10 +- src/utils/splitChunks.ts | 12 +- 39 files changed, 1001 insertions(+), 1003 deletions(-) diff --git a/.prettierrc b/.prettierrc index fca24df..9954b43 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,6 @@ { "trailingComma": "none", "tabWidth": 2, - "semi": true, + "semi": false, "singleQuote": false } diff --git a/src/FileUploader.ts b/src/FileUploader.ts index 74d0703..3e509fe 100644 --- a/src/FileUploader.ts +++ b/src/FileUploader.ts @@ -1,8 +1,8 @@ -import { isLogInRequired, needsRetry } from "./utils"; -import { CsrfToken } from "./types/CsrfToken"; -import { UploadFile } from "./types/UploadFile"; +import { isLogInRequired, needsRetry } from "./utils" +import { CsrfToken } from "./types/CsrfToken" +import { UploadFile } from "./types/UploadFile" -const requestRetryLimit = 5; +const requestRetryLimit = 5 export class FileUploader { constructor( @@ -12,38 +12,38 @@ export class FileUploader { private setCsrfTokenWeb: any, private csrfToken: CsrfToken | null = null ) {} - private retryCount = 0; + private retryCount = 0 public uploadFile(sasJob: string, files: UploadFile[], params: any) { - if (files?.length < 1) throw new Error("Atleast one file must be provided"); + if (files?.length < 1) throw new Error("Atleast one file must be provided") - let paramsString = ""; + let paramsString = "" for (let param in params) { if (params.hasOwnProperty(param)) { - paramsString += `&${param}=${params[param]}`; + paramsString += `&${param}=${params[param]}` } } const program = this.appLoc ? this.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "") - : sasJob; + : sasJob const uploadUrl = `${this.serverUrl}${this.jobsPath}/?${ "_program=" + program - }${paramsString}`; + }${paramsString}` const headers = { "cache-control": "no-cache" - }; + } return new Promise((resolve, reject) => { - const formData = new FormData(); + const formData = new FormData() for (let file of files) { - formData.append("file", file.file, file.fileName); + formData.append("file", file.file, file.fileName) } - if (this.csrfToken) formData.append("_csrf", this.csrfToken.value); + if (this.csrfToken) formData.append("_csrf", this.csrfToken.value) fetch(uploadUrl, { method: "POST", @@ -54,47 +54,47 @@ export class FileUploader { .then(async (response) => { if (!response.ok) { if (response.status === 403) { - const tokenHeader = response.headers.get("X-CSRF-HEADER"); + const tokenHeader = response.headers.get("X-CSRF-HEADER") if (tokenHeader) { - const token = response.headers.get(tokenHeader); + const token = response.headers.get(tokenHeader) this.csrfToken = { headerName: tokenHeader, value: token || "" - }; + } - this.setCsrfTokenWeb(this.csrfToken); + this.setCsrfTokenWeb(this.csrfToken) } } } - return response.text(); + return response.text() }) .then((responseText) => { if (isLogInRequired(responseText)) - reject("You must be logged in to upload a fle"); + reject("You must be logged in to upload a fle") if (needsRetry(responseText)) { if (this.retryCount < requestRetryLimit) { - this.retryCount++; + this.retryCount++ this.uploadFile(sasJob, files, params).then( (res: any) => resolve(res), (err: any) => reject(err) - ); + ) } else { - this.retryCount = 0; - reject(responseText); + this.retryCount = 0 + reject(responseText) } } else { - this.retryCount = 0; + this.retryCount = 0 try { - resolve(JSON.parse(responseText)); + resolve(JSON.parse(responseText)) } catch (e) { - reject(e); + reject(e) } } - }); - }); + }) + }) } } diff --git a/src/SAS9ApiClient.ts b/src/SAS9ApiClient.ts index f37ed65..e5d4f99 100644 --- a/src/SAS9ApiClient.ts +++ b/src/SAS9ApiClient.ts @@ -11,7 +11,7 @@ export class SAS9ApiClient { public getConfig() { return { serverUrl: this.serverUrl - }; + } } /** @@ -19,7 +19,7 @@ export class SAS9ApiClient { * @param serverUrl - the URL of the server. */ public setConfig(serverUrl: string) { - if (serverUrl) this.serverUrl = serverUrl; + if (serverUrl) this.serverUrl = serverUrl } /** @@ -33,19 +33,19 @@ export class SAS9ApiClient { serverName: string, repositoryName: string ) { - const requestPayload = linesOfCode.join("\n"); + const requestPayload = linesOfCode.join("\n") const executeScriptRequest = { method: "PUT", headers: { Accept: "application/json" }, body: `command=${requestPayload}` - }; + } const executeScriptResponse = await fetch( `${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`, executeScriptRequest - ).then((res) => res.text()); + ).then((res) => res.text()) - return executeScriptResponse; + return executeScriptResponse } } diff --git a/src/SASViyaApiClient.ts b/src/SASViyaApiClient.ts index 60301a6..3d0113c 100644 --- a/src/SASViyaApiClient.ts +++ b/src/SASViyaApiClient.ts @@ -3,13 +3,13 @@ import { parseAndSubmitAuthorizeForm, convertToCSV, makeRequest -} from "./utils"; -import * as NodeFormData from "form-data"; -import * as path from "path"; -import { Job, Session, Context, Folder, CsrfToken } from "./types"; -import { JobDefinition } from "./types/JobDefinition"; -import { formatDataForRequest } from "./utils/formatDataForRequest"; -import { SessionManager } from "./SessionManager"; +} from "./utils" +import * as NodeFormData from "form-data" +import * as path from "path" +import { Job, Session, Context, Folder, CsrfToken } from "./types" +import { JobDefinition } from "./types/JobDefinition" +import { formatDataForRequest } from "./utils/formatDataForRequest" +import { SessionManager } from "./SessionManager" /** * A client for interfacing with the SAS Viya REST API @@ -24,27 +24,27 @@ export class SASViyaApiClient { private rootFolderMap = new Map() ) { if (!rootFolderName) { - throw new Error("Root folder must be provided."); + throw new Error("Root folder must be provided.") } } - private csrfToken: CsrfToken | null = null; - private rootFolder: Folder | null = null; + private csrfToken: CsrfToken | null = null + private rootFolder: Folder | null = null private sessionManager = new SessionManager( this.serverUrl, this.contextName, this.setCsrfToken - ); + ) /** * Returns a map containing the directory structure in the currently set root folder. */ public async getAppLocMap() { if (this.rootFolderMap.size) { - return this.rootFolderMap; + return this.rootFolderMap } - this.populateRootFolderMap(); - return this.rootFolderMap; + this.populateRootFolderMap() + return this.rootFolderMap } /** @@ -54,7 +54,7 @@ export class SASViyaApiClient { return { serverUrl: this.serverUrl, rootFolderName: this.rootFolderName - }; + } } /** @@ -63,8 +63,8 @@ export class SASViyaApiClient { * @param rootFolderName - the name for rootFolderName. */ public setConfig(serverUrl: string, rootFolderName: string) { - if (serverUrl) this.serverUrl = serverUrl; - if (rootFolderName) this.rootFolderName = rootFolderName; + if (serverUrl) this.serverUrl = serverUrl + if (rootFolderName) this.rootFolderName = rootFolderName } /** @@ -74,22 +74,22 @@ export class SASViyaApiClient { public async getAllContexts(accessToken?: string) { const headers: any = { "Content-Type": "application/json" - }; + } if (accessToken) { - headers.Authorization = `Bearer ${accessToken}`; + headers.Authorization = `Bearer ${accessToken}` } const { result: contexts } = await this.request<{ items: Context[] }>( `${this.serverUrl}/compute/contexts`, { headers } - ); - const contextsList = contexts && contexts.items ? contexts.items : []; + ) + const contextsList = contexts && contexts.items ? contexts.items : [] return contextsList.map((context: any) => ({ createdBy: context.createdBy, id: context.id, name: context.name, version: context.version, attributes: {} - })); + })) } /** @@ -99,37 +99,37 @@ export class SASViyaApiClient { public async getExecutableContexts(accessToken?: string) { const headers: any = { "Content-Type": "application/json" - }; + } if (accessToken) { - headers.Authorization = `Bearer ${accessToken}`; + headers.Authorization = `Bearer ${accessToken}` } const { result: contexts } = await this.request<{ items: Context[] }>( `${this.serverUrl}/compute/contexts`, { headers } - ); - const contextsList = contexts && contexts.items ? contexts.items : []; - const executableContexts: any[] = []; + ) + const contextsList = contexts && contexts.items ? contexts.items : [] + const executableContexts: any[] = [] const promises = contextsList.map((context: any) => { - const linesOfCode = ["%put &=sysuserid;"]; + const linesOfCode = ["%put &=sysuserid;"] return this.executeScript( `test-${context.name}`, linesOfCode, context.name, accessToken - ).catch(() => null); - }); - const results = await Promise.all(promises); + ).catch(() => null) + }) + const results = await Promise.all(promises) results.forEach((result: any, index: number) => { if (result && result.jobStatus === "completed") { - let sysUserId = ""; + let sysUserId = "" if (result && result.log && result.log.items) { const sysUserIdLog = result.log.items.find((i: any) => i.line.startsWith("SYSUSERID=") - ); + ) if (sysUserIdLog) { - sysUserId = sysUserIdLog.line.replace("SYSUSERID=", ""); + sysUserId = sysUserIdLog.line.replace("SYSUSERID=", "") } } @@ -141,11 +141,11 @@ export class SASViyaApiClient { attributes: { sysUserId } - }); + }) } - }); + }) - return executableContexts; + return executableContexts } /** @@ -156,22 +156,22 @@ export class SASViyaApiClient { public async createSession(contextName: string, accessToken?: string) { const headers: any = { "Content-Type": "application/json" - }; + } if (accessToken) { - headers.Authorization = `Bearer ${accessToken}`; + headers.Authorization = `Bearer ${accessToken}` } const { result: contexts } = await this.request<{ items: Context[] }>( `${this.serverUrl}/compute/contexts`, { headers } - ); + ) const executionContext = contexts.items && contexts.items.length ? contexts.items.find((c: any) => c.name === contextName) - : null; + : null if (!executionContext) { - throw new Error(`Execution context ${contextName} not found.`); + throw new Error(`Execution context ${contextName} not found.`) } const createSessionRequest = { @@ -180,13 +180,13 @@ export class SASViyaApiClient { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" } - }; + } const { result: createdSession } = await this.request( `${this.serverUrl}/compute/contexts/${executionContext.id}/sessions`, createSessionRequest - ); + ) - return createdSession; + return createdSession } /** @@ -207,19 +207,19 @@ export class SASViyaApiClient { data = null, debug = false ): Promise { - silent = !debug; + silent = !debug try { const headers: any = { "Content-Type": "application/json" - }; - - if (accessToken) { - headers.Authorization = `Bearer ${accessToken}`; } - let executionSessionId: string; - const session = await this.sessionManager.getSession(accessToken); - executionSessionId = session!.id; + if (accessToken) { + headers.Authorization = `Bearer ${accessToken}` + } + + let executionSessionId: string + const session = await this.sessionManager.getSession(accessToken) + executionSessionId = session!.id const jobArguments: { [key: string]: any } = { _contextName: contextName, @@ -228,37 +228,37 @@ export class SASViyaApiClient { _OMITSESSIONRESULTS: true, _OMITTEXTLISTING: true, _OMITTEXTLOG: true - }; + } if (debug) { - jobArguments["_OMITTEXTLOG"] = false; - jobArguments["_OMITSESSIONRESULTS"] = false; - jobArguments["_DEBUG"] = 131; + jobArguments["_OMITTEXTLOG"] = false + jobArguments["_OMITSESSIONRESULTS"] = false + jobArguments["_DEBUG"] = 131 } const fileName = `exec-${ jobName.includes("/") ? jobName.split("/")[1] : jobName - }`; + }` let jobVariables: any = { SYS_JES_JOB_URI: "", _program: this.rootFolderName + "/" + jobName - }; + } - let files: any[] = []; + let files: any[] = [] if (data) { if (JSON.stringify(data).includes(";")) { - files = await this.uploadTables(data, accessToken); - jobVariables["_webin_file_count"] = files.length; + files = await this.uploadTables(data, accessToken) + jobVariables["_webin_file_count"] = files.length files.forEach((fileInfo, index) => { jobVariables[ `_webin_fileuri${index + 1}` - ] = `/files/files/${fileInfo.file.id}`; - jobVariables[`_webin_name${index + 1}`] = fileInfo.tableName; - }); + ] = `/files/files/${fileInfo.file.id}` + jobVariables[`_webin_name${index + 1}`] = fileInfo.tableName + }) } else { - jobVariables = { ...jobVariables, ...formatDataForRequest(data) }; + jobVariables = { ...jobVariables, ...formatDataForRequest(data) } } } @@ -273,20 +273,20 @@ export class SASViyaApiClient { variables: jobVariables, arguments: jobArguments }) - }; + } const { result: postedJob, etag } = await this.request( `${this.serverUrl}/compute/sessions/${executionSessionId}/jobs`, postJobRequest - ); + ) if (!silent) { - console.log(`Job has been submitted for ${fileName}`); + console.log(`Job has been submitted for ${fileName}`) console.log( `You can monitor the job progress at ${this.serverUrl}${ postedJob.links.find((l: any) => l.rel === "state")!.href }` - ); + ) } const jobStatus = await this.pollJobState( @@ -294,16 +294,16 @@ export class SASViyaApiClient { etag, accessToken, silent - ); + ) const { result: currentJob } = await this.request( `${this.serverUrl}/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`, { headers } - ); + ) - let jobResult, log; + let jobResult, log - const logLink = currentJob.links.find((l) => l.rel === "log"); + const logLink = currentJob.links.find((l) => l.rel === "log") if (true && logLink) { log = await this.request( @@ -313,13 +313,13 @@ export class SASViyaApiClient { } ).then((res: any) => res.result.items.map((i: any) => i.line).join("\n") - ); + ) } if (jobStatus === "failed" || jobStatus === "error") { - return Promise.reject({ error: currentJob.error, log: log }); + return Promise.reject({ error: currentJob.error, log: log }) } - const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`; + const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content` if (resultLink) { jobResult = await this.request( @@ -328,12 +328,12 @@ export class SASViyaApiClient { "text" ).catch((e) => ({ result: JSON.stringify(e) - })); + })) } - await this.sessionManager.clearSession(executionSessionId, accessToken); + await this.sessionManager.clearSession(executionSessionId, accessToken) - return { result: jobResult?.result, log }; + return { result: jobResult?.result, log } } catch (e) { if (e && e.status === 404) { return this.executeScript( @@ -344,9 +344,9 @@ export class SASViyaApiClient { silent, data, debug - ); + ) } else { - throw e; + throw e } } } @@ -367,33 +367,33 @@ export class SASViyaApiClient { accessToken?: string ): Promise { if (!parentFolderPath && !parentFolderUri) { - throw new Error("Parent folder path or uri is required"); + throw new Error("Parent folder path or uri is required") } if (!parentFolderUri && parentFolderPath) { - parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken); + parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken) if (!parentFolderUri) { - console.log(`Parent folder is not present: ${parentFolderPath}`); + console.log(`Parent folder is not present: ${parentFolderPath}`) const newParentFolderPath = parentFolderPath.substring( 0, parentFolderPath.lastIndexOf("/") - ); - const newFolderName = `${parentFolderPath.split("/").pop()}`; + ) + const newFolderName = `${parentFolderPath.split("/").pop()}` if (newParentFolderPath === "") { - throw new Error("Root Folder should have been present on server"); + throw new Error("Root Folder should have been present on server") } console.log( `Creating Parent Folder:\n${newFolderName} in ${newParentFolderPath}` - ); + ) const parentFolder = await this.createFolder( newFolderName, newParentFolderPath, undefined, accessToken - ); - console.log(`Parent Folder "${newFolderName}" successfully created.`); - parentFolderUri = `/folders/folders/${parentFolder.id}`; + ) + console.log(`Parent Folder "${newFolderName}" successfully created.`) + parentFolderUri = `/folders/folders/${parentFolder.id}` } } @@ -403,21 +403,21 @@ export class SASViyaApiClient { name: folderName, type: "folder" }) - }; + } - createFolderRequest.headers = { "Content-Type": "application/json" }; + createFolderRequest.headers = { "Content-Type": "application/json" } if (accessToken) { - createFolderRequest.headers.Authorization = `Bearer ${accessToken}`; + createFolderRequest.headers.Authorization = `Bearer ${accessToken}` } const { result: createFolderResponse } = await this.request( `${this.serverUrl}/folders/folders?parentFolderUri=${parentFolderUri}`, createFolderRequest - ); + ) // update rootFolderMap with newly created folder. - await this.populateRootFolderMap(accessToken); - return createFolderResponse; + await this.populateRootFolderMap(accessToken) + return createFolderResponse } /** @@ -438,11 +438,11 @@ export class SASViyaApiClient { if (!parentFolderPath && !parentFolderUri) { throw new Error( "Either parentFolderPath or parentFolderUri must be provided" - ); + ) } if (!parentFolderUri && parentFolderPath) { - parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken); + parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken) } const createJobDefinitionRequest: RequestInit = { @@ -463,19 +463,19 @@ export class SASViyaApiClient { type: "Compute", code }) - }; + } if (accessToken) { createJobDefinitionRequest!.headers = { ...createJobDefinitionRequest.headers, Authorization: `Bearer ${accessToken}` - }; + } } return await this.request( `${this.serverUrl}/jobDefinitions/definitions?parentFolderUri=${parentFolderUri}`, createJobDefinitionRequest - ); + ) } /** @@ -483,7 +483,7 @@ export class SASViyaApiClient { * @param clientId - the client ID to authenticate with. */ public async getAuthCode(clientId: string) { - const authUrl = `${this.serverUrl}/SASLogon/oauth/authorize?client_id=${clientId}&response_type=code`; + const authUrl = `${this.serverUrl}/SASLogon/oauth/authorize?client_id=${clientId}&response_type=code` const authCode = await fetch(authUrl, { referrerPolicy: "same-origin", @@ -491,37 +491,37 @@ export class SASViyaApiClient { }) .then((response) => response.text()) .then(async (response) => { - let code = ""; + let code = "" if (isAuthorizeFormRequired(response)) { const formResponse: any = await parseAndSubmitAuthorizeForm( response, this.serverUrl - ); + ) const responseBody = formResponse .split("")[1] - .split("")[0]; - const bodyElement: any = document.createElement("div"); - bodyElement.innerHTML = responseBody; + .split("")[0] + const bodyElement: any = document.createElement("div") + bodyElement.innerHTML = responseBody - code = bodyElement.querySelector(".infobox h4").innerText; + code = bodyElement.querySelector(".infobox h4").innerText - return code; + return code } else { - const responseBody = response.split("")[1].split("")[0]; - const bodyElement: any = document.createElement("div"); - bodyElement.innerHTML = responseBody; + const responseBody = response.split("")[1].split("")[0] + const bodyElement: any = document.createElement("div") + bodyElement.innerHTML = responseBody if (bodyElement) { - code = bodyElement.querySelector(".infobox h4").innerText; + code = bodyElement.querySelector(".infobox h4").innerText } - return code; + return code } }) - .catch(() => null); + .catch(() => null) - return authCode; + return authCode } /** @@ -535,26 +535,26 @@ export class SASViyaApiClient { clientSecret: string, authCode: string ) { - const url = this.serverUrl + "/SASLogon/oauth/token"; - let token; + const url = this.serverUrl + "/SASLogon/oauth/token" + let token if (typeof Buffer === "undefined") { - token = btoa(clientId + ":" + clientSecret); + token = btoa(clientId + ":" + clientSecret) } else { - token = Buffer.from(clientId + ":" + clientSecret).toString("base64"); + token = Buffer.from(clientId + ":" + clientSecret).toString("base64") } const headers = { Authorization: "Basic " + token - }; + } - let formData; + let formData if (typeof FormData === "undefined") { - formData = new NodeFormData(); - formData.append("grant_type", "authorization_code"); - formData.append("code", authCode); + formData = new NodeFormData() + formData.append("grant_type", "authorization_code") + formData.append("code", authCode) } else { - formData = new FormData(); - formData.append("grant_type", "authorization_code"); - formData.append("code", authCode); + formData = new FormData() + formData.append("grant_type", "authorization_code") + formData.append("code", authCode) } const authResponse = await fetch(url, { @@ -563,9 +563,9 @@ export class SASViyaApiClient { headers, body: formData as any, referrerPolicy: "same-origin" - }).then((res) => res.json()); + }).then((res) => res.json()) - return authResponse; + return authResponse } /** @@ -579,26 +579,26 @@ export class SASViyaApiClient { clientSecret: string, refreshToken: string ) { - const url = this.serverUrl + "/SASLogon/oauth/token"; - let token; + const url = this.serverUrl + "/SASLogon/oauth/token" + let token if (typeof Buffer === "undefined") { - token = btoa(clientId + ":" + clientSecret); + token = btoa(clientId + ":" + clientSecret) } else { - token = Buffer.from(clientId + ":" + clientSecret).toString("base64"); + token = Buffer.from(clientId + ":" + clientSecret).toString("base64") } const headers = { Authorization: "Basic " + token - }; + } - let formData; + let formData if (typeof FormData === "undefined") { - formData = new NodeFormData(); - formData.append("grant_type", "refresh_token"); - formData.append("refresh_token", refreshToken); + formData = new NodeFormData() + formData.append("grant_type", "refresh_token") + formData.append("refresh_token", refreshToken) } else { - formData = new FormData(); - formData.append("grant_type", "refresh_token"); - formData.append("refresh_token", refreshToken); + formData = new FormData() + formData.append("grant_type", "refresh_token") + formData.append("refresh_token", refreshToken) } const authResponse = await fetch(url, { @@ -607,9 +607,9 @@ export class SASViyaApiClient { headers, body: formData as any, referrerPolicy: "same-origin" - }).then((res) => res.json()); + }).then((res) => res.json()) - return authResponse; + return authResponse } /** @@ -618,18 +618,18 @@ export class SASViyaApiClient { * @param accessToken - an access token for an authorized user. */ public async deleteClient(clientId: string, accessToken?: string) { - const url = this.serverUrl + `/oauth/clients/${clientId}`; - const headers: any = {}; + const url = this.serverUrl + `/oauth/clients/${clientId}` + const headers: any = {} if (accessToken) { - headers.Authorization = `Bearer ${accessToken}`; + headers.Authorization = `Bearer ${accessToken}` } const deleteResponse = await this.request(url, { method: "DELETE", credentials: "include", headers - }); + }) - return deleteResponse; + return deleteResponse } /** @@ -648,57 +648,55 @@ export class SASViyaApiClient { accessToken?: string ) { if (!this.rootFolder) { - await this.populateRootFolder(accessToken); + await this.populateRootFolder(accessToken) } if (!this.rootFolder) { - console.error("Root folder was not found"); - throw new Error("Root folder was not found"); + console.error("Root folder was not found") + throw new Error("Root folder was not found") } if (!this.rootFolderMap.size) { - await this.populateRootFolderMap(accessToken); + await this.populateRootFolderMap(accessToken) } if (!this.rootFolderMap.size) { - console.error( - `The job ${sasJob} was not found in ${this.rootFolderName}` - ); + console.error(`The job ${sasJob} was not found in ${this.rootFolderName}`) throw new Error( `The job ${sasJob} was not found in ${this.rootFolderName}` - ); + ) } - const headers: any = { "Content-Type": "application/json" }; + const headers: any = { "Content-Type": "application/json" } if (!!accessToken) { - headers.Authorization = `Bearer ${accessToken}`; + headers.Authorization = `Bearer ${accessToken}` } - const folderName = sasJob.split("/")[0]; - const jobName = sasJob.split("/")[1]; - const jobFolder = this.rootFolderMap.get(folderName); - const jobToExecute = jobFolder?.find((item) => item.name === jobName); + const folderName = sasJob.split("/")[0] + const jobName = sasJob.split("/")[1] + const jobFolder = this.rootFolderMap.get(folderName) + const jobToExecute = jobFolder?.find((item) => item.name === jobName) if (!jobToExecute) { - throw new Error("Job was not found."); + throw new Error("Job was not found.") } - let code = jobToExecute?.code; + let code = jobToExecute?.code if (!code) { const jobDefinitionLink = jobToExecute?.links.find( (l) => l.rel === "getResource" - ); + ) if (!jobDefinitionLink) { - console.error("Job definition URI was not found."); - throw new Error("Job definition URI was not found."); + console.error("Job definition URI was not found.") + throw new Error("Job definition URI was not found.") } const { result: jobDefinition } = await this.request( `${this.serverUrl}${jobDefinitionLink.href}`, headers - ); + ) - code = jobDefinition.code; + code = jobDefinition.code // Add code to existing job definition - jobToExecute.code = code; + jobToExecute.code = code } - const linesToExecute = code.replace(/\r\n/g, "\n").split("\n"); + const linesToExecute = code.replace(/\r\n/g, "\n").split("\n") return await this.executeScript( sasJob, linesToExecute, @@ -707,7 +705,7 @@ export class SASViyaApiClient { true, data, debug - ); + ) } /** @@ -726,46 +724,46 @@ export class SASViyaApiClient { accessToken?: string ) { if (!this.rootFolder) { - await this.populateRootFolder(accessToken); + await this.populateRootFolder(accessToken) } if (!this.rootFolder) { - throw new Error("Root folder was not found"); + throw new Error("Root folder was not found") } if (!this.rootFolderMap.size) { - await this.populateRootFolderMap(accessToken); + await this.populateRootFolderMap(accessToken) } if (!this.rootFolderMap.size) { throw new Error( `The job ${sasJob} was not found in ${this.rootFolderName}` - ); + ) } - let files: any[] = []; + let files: any[] = [] if (data && Object.keys(data).length) { - files = await this.uploadTables(data, accessToken); + files = await this.uploadTables(data, accessToken) } - const jobName = path.basename(sasJob); - const jobFolder = sasJob.replace(`/${jobName}`, ""); - const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace("/", "")); + const jobName = path.basename(sasJob) + const jobFolder = sasJob.replace(`/${jobName}`, "") + const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace("/", "")) if (allJobsInFolder) { - const jobSpec = allJobsInFolder.find((j: Job) => j.name === jobName); + const jobSpec = allJobsInFolder.find((j: Job) => j.name === jobName) const jobDefinitionLink = jobSpec?.links.find( (l) => l.rel === "getResource" - )?.href; + )?.href const requestInfo: any = { method: "GET" - }; - const headers: any = { "Content-Type": "application/json" }; - if (!!accessToken) { - headers.Authorization = `Bearer ${accessToken}`; } - requestInfo.headers = headers; + const headers: any = { "Content-Type": "application/json" } + if (!!accessToken) { + headers.Authorization = `Bearer ${accessToken}` + } + requestInfo.headers = headers const { result: jobDefinition } = await this.request( `${this.serverUrl}${jobDefinitionLink}`, requestInfo - ); + ) const jobArguments: { [key: string]: any } = { _contextName: contextName, @@ -776,20 +774,20 @@ export class SASViyaApiClient { _OMITSESSIONRESULTS: true, _OMITTEXTLISTING: true, _OMITTEXTLOG: true - }; + } if (debug) { - jobArguments["_OMITTEXTLOG"] = "false"; - jobArguments["_OMITSESSIONRESULTS"] = "false"; - jobArguments["_DEBUG"] = 131; + jobArguments["_OMITTEXTLOG"] = "false" + jobArguments["_OMITSESSIONRESULTS"] = "false" + jobArguments["_DEBUG"] = 131 } files.forEach((fileInfo, index) => { jobArguments[ `_webin_fileuri${index + 1}` - ] = `/files/files/${fileInfo.file.id}`; - jobArguments[`_webin_name${index + 1}`] = fileInfo.tableName; - }); + ] = `/files/files/${fileInfo.file.id}` + jobArguments[`_webin_name${index + 1}`] = fileInfo.tableName + }) const postJobRequest = { method: "POST", @@ -800,34 +798,34 @@ export class SASViyaApiClient { jobDefinition, arguments: jobArguments }) - }; + } const { result: postedJob, etag } = await this.request( `${this.serverUrl}/jobExecution/jobs?_action=wait`, postJobRequest - ); + ) const jobStatus = await this.pollJobState( postedJob, etag, accessToken, true - ); + ) const { result: currentJob } = await this.request( `${this.serverUrl}/jobExecution/jobs/${postedJob.id}`, { headers } - ); + ) - let jobResult, log; + let jobResult, log if (jobStatus === "failed") { - return Promise.reject(currentJob.error); + return Promise.reject(currentJob.error) } - const resultLink = currentJob.results["_webout.json"]; - const logLink = currentJob.links.find((l) => l.rel === "log"); + const resultLink = currentJob.results["_webout.json"] + const logLink = currentJob.links.find((l) => l.rel === "log") if (resultLink) { jobResult = await this.request( `${this.serverUrl}${resultLink}/content`, { headers }, "text" - ); + ) } if (debug && logLink) { log = await this.request( @@ -837,39 +835,39 @@ export class SASViyaApiClient { } ).then((res: any) => res.result.items.map((i: any) => i.line).join("\n") - ); + ) } - return { result: jobResult?.result, log }; + return { result: jobResult?.result, log } } else { throw new Error( `The job ${sasJob} was not found at the location ${this.rootFolderName}` - ); + ) } } private async populateRootFolderMap(accessToken?: string) { - const allItems = new Map(); - const url = "/folders/folders/@item?path=" + this.rootFolderName; + const allItems = new Map() + const url = "/folders/folders/@item?path=" + this.rootFolderName const requestInfo: any = { method: "GET" - }; + } if (accessToken) { - requestInfo.headers = { Authorization: `Bearer ${accessToken}` }; + requestInfo.headers = { Authorization: `Bearer ${accessToken}` } } const { result: folder } = await this.request( `${this.serverUrl}${url}`, requestInfo - ); + ) if (!folder) { - throw new Error("Cannot populate RootFolderMap unless rootFolder exists"); + throw new Error("Cannot populate RootFolderMap unless rootFolder exists") } const { result: members } = await this.request<{ items: any[] }>( `${this.serverUrl}/folders/folders/${folder.id}/members`, requestInfo - ); + ) - const itemsAtRoot = members.items; - allItems.set("", itemsAtRoot); + const itemsAtRoot = members.items + allItems.set("", itemsAtRoot) const subfolderRequests = members.items .filter((i: any) => i.contentType === "folder") .map(async (member: any) => { @@ -877,46 +875,46 @@ export class SASViyaApiClient { "/folders/folders/@item?path=" + this.rootFolderName + "/" + - member.name; + member.name const { result: memberDetail } = await this.request( `${this.serverUrl}${subFolderUrl}`, requestInfo - ); + ) const membersLink = memberDetail.links.find( (l: any) => l.rel === "members" - ); + ) const { result: memberContents } = await this.request<{ items: any[] }>( `${this.serverUrl}${membersLink!.href}`, requestInfo - ); - const itemsInFolder = memberContents.items as any[]; - allItems.set(member.name, itemsInFolder); - return itemsInFolder; - }); - await Promise.all(subfolderRequests); + ) + const itemsInFolder = memberContents.items as any[] + allItems.set(member.name, itemsInFolder) + return itemsInFolder + }) + await Promise.all(subfolderRequests) - this.rootFolderMap = allItems; + this.rootFolderMap = allItems } private async populateRootFolder(accessToken?: string) { - const url = "/folders/folders/@item?path=" + this.rootFolderName; + const url = "/folders/folders/@item?path=" + this.rootFolderName const requestInfo: RequestInit = { method: "GET" - }; - if (accessToken) { - requestInfo.headers = { Authorization: `Bearer ${accessToken}` }; } - let error; + if (accessToken) { + requestInfo.headers = { Authorization: `Bearer ${accessToken}` } + } + let error const rootFolder = await this.request( `${this.serverUrl}${url}`, requestInfo - ); + ) - this.rootFolder = rootFolder?.result || null; + this.rootFolder = rootFolder?.result || null if (error) { - throw new Error(JSON.stringify(error)); + throw new Error(JSON.stringify(error)) } } @@ -926,20 +924,20 @@ export class SASViyaApiClient { accessToken?: string, silent = false ) { - const MAX_POLL_COUNT = 1000; - const POLL_INTERVAL = 100; - let postedJobState = ""; - let pollCount = 0; + const MAX_POLL_COUNT = 1000 + const POLL_INTERVAL = 100 + let postedJobState = "" + let pollCount = 0 const headers: any = { "Content-Type": "application/json", "If-None-Match": etag - }; - if (accessToken) { - headers.Authorization = `Bearer ${accessToken}`; } - const stateLink = postedJob.links.find((l: any) => l.rel === "state"); + if (accessToken) { + headers.Authorization = `Bearer ${accessToken}` + } + const stateLink = postedJob.links.find((l: any) => l.rel === "state") if (!stateLink) { - Promise.reject("Job state link was not found."); + Promise.reject("Job state link was not found.") } const { result: state } = await this.request( @@ -948,11 +946,11 @@ export class SASViyaApiClient { headers }, "text" - ); + ) - const currentState = state.trim(); + const currentState = state.trim() if (currentState === "completed") { - return Promise.resolve(currentState); + return Promise.resolve(currentState) } return new Promise(async (resolve, _) => { @@ -964,7 +962,7 @@ export class SASViyaApiClient { ) { if (stateLink) { if (!silent) { - console.log("Polling job status... \n"); + console.log("Polling job status... \n") } const { result: jobState } = await this.request( `${this.serverUrl}${stateLink.href}?_action=wait&wait=30`, @@ -972,23 +970,23 @@ export class SASViyaApiClient { headers }, "text" - ); + ) - postedJobState = jobState.trim(); + postedJobState = jobState.trim() if (!silent) { - console.log(`Current state: ${postedJobState}\n`); + console.log(`Current state: ${postedJobState}\n`) } - pollCount++; + pollCount++ if (pollCount >= MAX_POLL_COUNT) { - resolve(postedJobState); + resolve(postedJobState) } } } else { - clearInterval(interval); - resolve(postedJobState); + clearInterval(interval) + resolve(postedJobState) } - }, POLL_INTERVAL); - }); + }, POLL_INTERVAL) + }) } private async waitForSession( @@ -997,21 +995,21 @@ export class SASViyaApiClient { accessToken?: string, silent = false ) { - let sessionState = session.state; - let pollCount = 0; + let sessionState = session.state + let pollCount = 0 const headers: any = { "Content-Type": "application/json", "If-None-Match": etag - }; - if (accessToken) { - headers.Authorization = `Bearer ${accessToken}`; } - const stateLink = session.links.find((l: any) => l.rel === "state"); + if (accessToken) { + headers.Authorization = `Bearer ${accessToken}` + } + const stateLink = session.links.find((l: any) => l.rel === "state") return new Promise(async (resolve, _) => { if (sessionState === "pending") { if (stateLink) { if (!silent) { - console.log("Polling session status... \n"); + console.log("Polling session status... \n") } const { result: state } = await this.request( `${this.serverUrl}${stateLink.href}?wait=30`, @@ -1019,77 +1017,77 @@ export class SASViyaApiClient { headers }, "text" - ); + ) - sessionState = state.trim(); + sessionState = state.trim() if (!silent) { - console.log(`Current state: ${sessionState}\n`); + console.log(`Current state: ${sessionState}\n`) } - pollCount++; - resolve(sessionState); + pollCount++ + resolve(sessionState) } } else { - resolve(sessionState); + resolve(sessionState) } - }); + }) } private async uploadTables(data: any, accessToken?: string) { - const uploadedFiles = []; + const uploadedFiles = [] const headers: any = { "Content-Type": "application/json" - }; + } if (accessToken) { - headers.Authorization = `Bearer ${accessToken}`; + headers.Authorization = `Bearer ${accessToken}` } for (const tableName in data) { - const csv = convertToCSV(data[tableName]); + const csv = convertToCSV(data[tableName]) if (csv === "ERROR: LARGE STRING LENGTH") { throw new Error( "The max length of a string value in SASjs is 32765 characters." - ); + ) } const createFileRequest = { method: "POST", body: csv, headers - }; + } const uploadResponse = await this.request( `${this.serverUrl}/files/files#rawUpload`, createFileRequest - ); + ) - uploadedFiles.push({ tableName, file: uploadResponse.result }); + uploadedFiles.push({ tableName, file: uploadResponse.result }) } - return uploadedFiles; + return uploadedFiles } private async getFolderUri(folderPath: string, accessToken?: string) { - const url = "/folders/folders/@item?path=" + folderPath; + const url = "/folders/folders/@item?path=" + folderPath const requestInfo: any = { method: "GET" - }; + } if (accessToken) { - requestInfo.headers = { Authorization: `Bearer ${accessToken}` }; + requestInfo.headers = { Authorization: `Bearer ${accessToken}` } } const { result: folder } = await this.request( `${this.serverUrl}${url}`, requestInfo ).catch((err) => { - return { result: null }; - }); + return { result: null } + }) - if (!folder) return undefined; - return `/folders/folders/${folder.id}`; + if (!folder) return undefined + return `/folders/folders/${folder.id}` } setCsrfTokenLocal = (csrfToken: CsrfToken) => { - this.csrfToken = csrfToken; - this.setCsrfToken(csrfToken); - }; + this.csrfToken = csrfToken + this.setCsrfToken(csrfToken) + } private async request( url: string, @@ -1100,13 +1098,13 @@ export class SASViyaApiClient { options.headers = { ...options.headers, [this.csrfToken.headerName]: this.csrfToken.value - }; + } } return await makeRequest( url, options, this.setCsrfTokenLocal, contentType - ); + ) } } diff --git a/src/SASjs.spec.ts b/src/SASjs.spec.ts index 60df4d5..10820b5 100644 --- a/src/SASjs.spec.ts +++ b/src/SASjs.spec.ts @@ -1,36 +1,36 @@ -import SASjs from "./index"; +import SASjs from "./index" -const adapter = new SASjs(); +const adapter = new SASjs() it("should parse SAS9 source code", async (done) => { - expect(sampleResponse).toBeTruthy(); - const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse); - expect(parsedSourceCode).toBeTruthy(); - const sourceCodeLines = parsedSourceCode.split("\r\n"); - expect(sourceCodeLines.length).toEqual(5); - expect(sourceCodeLines[0].startsWith("6")).toBeTruthy(); - expect(sourceCodeLines[1].startsWith("7")).toBeTruthy(); - expect(sourceCodeLines[2].startsWith("8")).toBeTruthy(); - expect(sourceCodeLines[3].startsWith("9")).toBeTruthy(); - expect(sourceCodeLines[4].startsWith("10")).toBeTruthy(); - done(); -}); + expect(sampleResponse).toBeTruthy() + const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse) + expect(parsedSourceCode).toBeTruthy() + const sourceCodeLines = parsedSourceCode.split("\r\n") + expect(sourceCodeLines.length).toEqual(5) + expect(sourceCodeLines[0].startsWith("6")).toBeTruthy() + expect(sourceCodeLines[1].startsWith("7")).toBeTruthy() + expect(sourceCodeLines[2].startsWith("8")).toBeTruthy() + expect(sourceCodeLines[3].startsWith("9")).toBeTruthy() + expect(sourceCodeLines[4].startsWith("10")).toBeTruthy() + done() +}) it("should parse generated code", async (done) => { - expect(sampleResponse).toBeTruthy(); + expect(sampleResponse).toBeTruthy() const parsedGeneratedCode = (adapter as any).parseGeneratedCode( sampleResponse - ); - expect(parsedGeneratedCode).toBeTruthy(); - const generatedCodeLines = parsedGeneratedCode.split("\r\n"); - expect(generatedCodeLines.length).toEqual(5); - expect(generatedCodeLines[0].startsWith("MPRINT(MM_WEBIN)")).toBeTruthy(); - expect(generatedCodeLines[1].startsWith("MPRINT(MM_WEBLEFT)")).toBeTruthy(); - expect(generatedCodeLines[2].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy(); - expect(generatedCodeLines[3].startsWith("MPRINT(MM_WEBRIGHT)")).toBeTruthy(); - expect(generatedCodeLines[4].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy(); - done(); -}); + ) + expect(parsedGeneratedCode).toBeTruthy() + const generatedCodeLines = parsedGeneratedCode.split("\r\n") + expect(generatedCodeLines.length).toEqual(5) + expect(generatedCodeLines[0].startsWith("MPRINT(MM_WEBIN)")).toBeTruthy() + expect(generatedCodeLines[1].startsWith("MPRINT(MM_WEBLEFT)")).toBeTruthy() + expect(generatedCodeLines[2].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy() + expect(generatedCodeLines[3].startsWith("MPRINT(MM_WEBRIGHT)")).toBeTruthy() + expect(generatedCodeLines[4].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy() + done() +}) /* tslint:disable */ const sampleResponse = ` @@ -44,5 +44,5 @@ MPRINT(MM_WEBLEFT): filename _temp temp lrecl=999999; MPRINT(MM_WEBOUT): data _null_; MPRINT(MM_WEBRIGHT): file _temp; MPRINT(MM_WEBOUT): if upcase(symget('_debug'))='LOG' then put '>>weboutBEGIN<<'; -`; +` /* tslint:enable */ diff --git a/src/SASjs.ts b/src/SASjs.ts index 6ea8d28..b238f0b 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -1,13 +1,13 @@ -import { isIEorEdgeOrOldFirefox } from "./utils/isIeOrEdge"; -import * as e6p from "es6-promise"; -(e6p as any).polyfill(); +import { isIEorEdgeOrOldFirefox } from "./utils/isIeOrEdge" +import * as e6p from "es6-promise" +;(e6p as any).polyfill() if (isIEorEdgeOrOldFirefox()) { if (window) { - window.fetch = undefined as any; // ensure the polyfill runs + window.fetch = undefined as any // ensure the polyfill runs } } // tslint:disable-next-line -require("isomorphic-fetch"); +require("isomorphic-fetch") import { convertToCSV, compareTimestamps, @@ -22,7 +22,7 @@ import { parseWeboutResponse, needsRetry, asyncForEach -} from "./utils"; +} from "./utils" import { SASjsConfig, SASjsRequest, @@ -30,10 +30,10 @@ import { ServerType, CsrfToken, UploadFile -} from "./types"; -import { SASViyaApiClient } from "./SASViyaApiClient"; -import { SAS9ApiClient } from "./SAS9ApiClient"; -import { FileUploader } from "./FileUploader"; +} from "./types" +import { SASViyaApiClient } from "./SASViyaApiClient" +import { SAS9ApiClient } from "./SAS9ApiClient" +import { FileUploader } from "./FileUploader" const defaultConfig: SASjsConfig = { serverUrl: "", @@ -44,38 +44,38 @@ const defaultConfig: SASjsConfig = { debug: true, contextName: "SAS Job Execution compute context", useComputeApi: false -}; +} -const requestRetryLimit = 5; +const requestRetryLimit = 5 /** * SASjs is a JavaScript adapter for SAS. * */ export default class SASjs { - private sasjsConfig: SASjsConfig = new SASjsConfig(); - private jobsPath: string = ""; - private logoutUrl: string = ""; - private loginUrl: string = ""; - private csrfTokenApi: CsrfToken | null = null; - private csrfTokenWeb: CsrfToken | null = null; - private retryCountWeb: number = 0; - private retryCountComputeApi: number = 0; - private retryCountJeseApi: number = 0; - private sasjsRequests: SASjsRequest[] = []; - private sasjsWaitingRequests: SASjsWaitingRequest[] = []; - private userName: string = ""; - private sasViyaApiClient: SASViyaApiClient | null = null; - private sas9ApiClient: SAS9ApiClient | null = null; - private fileUploader: FileUploader | null = null; + private sasjsConfig: SASjsConfig = new SASjsConfig() + private jobsPath: string = "" + private logoutUrl: string = "" + private loginUrl: string = "" + private csrfTokenApi: CsrfToken | null = null + private csrfTokenWeb: CsrfToken | null = null + private retryCountWeb: number = 0 + private retryCountComputeApi: number = 0 + private retryCountJeseApi: number = 0 + private sasjsRequests: SASjsRequest[] = [] + private sasjsWaitingRequests: SASjsWaitingRequest[] = [] + private userName: string = "" + private sasViyaApiClient: SASViyaApiClient | null = null + private sas9ApiClient: SAS9ApiClient | null = null + private fileUploader: FileUploader | null = null constructor(config?: any) { this.sasjsConfig = { ...defaultConfig, ...config - }; + } - this.setupConfiguration(); + this.setupConfiguration() } public async executeScriptSAS9( @@ -84,34 +84,34 @@ export default class SASjs { repositoryName: string ) { if (this.sasjsConfig.serverType !== ServerType.SAS9) { - throw new Error("This operation is only supported on SAS9 servers."); + throw new Error("This operation is only supported on SAS9 servers.") } return await this.sas9ApiClient?.executeScript( linesOfCode, serverName, repositoryName - ); + ) } public async getAllContexts(accessToken: string) { if (this.sasjsConfig.serverType !== ServerType.SASViya) { - throw new Error("This operation is only supported on SAS Viya servers."); + throw new Error("This operation is only supported on SAS Viya servers.") } - return await this.sasViyaApiClient!.getAllContexts(accessToken); + return await this.sasViyaApiClient!.getAllContexts(accessToken) } public async getExecutableContexts(accessToken: string) { if (this.sasjsConfig.serverType !== ServerType.SASViya) { - throw new Error("This operation is only supported on SAS Viya servers."); + throw new Error("This operation is only supported on SAS Viya servers.") } - return await this.sasViyaApiClient!.getExecutableContexts(accessToken); + return await this.sasViyaApiClient!.getExecutableContexts(accessToken) } public async createSession(contextName: string, accessToken: string) { if (this.sasjsConfig.serverType !== ServerType.SASViya) { - throw new Error("This operation is only supported on SAS Viya servers."); + throw new Error("This operation is only supported on SAS Viya servers.") } - return await this.sasViyaApiClient!.createSession(contextName, accessToken); + return await this.sasViyaApiClient!.createSession(contextName, accessToken) } public async executeScriptSASViya( @@ -123,7 +123,7 @@ export default class SASjs { silent = false ) { if (this.sasjsConfig.serverType !== ServerType.SASViya) { - throw new Error("This operation is only supported on SAS Viya servers."); + throw new Error("This operation is only supported on SAS Viya servers.") } return await this.sasViyaApiClient!.executeScript( fileName, @@ -131,7 +131,7 @@ export default class SASjs { contextName, accessToken, silent - ); + ) } public async createFolder( @@ -142,7 +142,7 @@ export default class SASjs { sasApiClient?: SASViyaApiClient ) { if (this.sasjsConfig.serverType !== ServerType.SASViya) { - throw new Error("This operation is only supported on SAS Viya servers."); + throw new Error("This operation is only supported on SAS Viya servers.") } if (sasApiClient) return await sasApiClient.createFolder( @@ -150,13 +150,13 @@ export default class SASjs { parentFolderPath, parentFolderUri, accessToken - ); + ) return await this.sasViyaApiClient!.createFolder( folderName, parentFolderPath, parentFolderUri, accessToken - ); + ) } public async createJobDefinition( @@ -168,7 +168,7 @@ export default class SASjs { sasApiClient?: SASViyaApiClient ) { if (this.sasjsConfig.serverType !== ServerType.SASViya) { - throw new Error("This operation is only supported on SAS Viya servers."); + throw new Error("This operation is only supported on SAS Viya servers.") } if (sasApiClient) return await sasApiClient!.createJobDefinition( @@ -177,21 +177,21 @@ export default class SASjs { parentFolderPath, parentFolderUri, accessToken - ); + ) return await this.sasViyaApiClient!.createJobDefinition( jobName, code, parentFolderPath, parentFolderUri, accessToken - ); + ) } public async getAuthCode(clientId: string) { if (this.sasjsConfig.serverType !== ServerType.SASViya) { - throw new Error("This operation is only supported on SAS Viya servers."); + throw new Error("This operation is only supported on SAS Viya servers.") } - return await this.sasViyaApiClient!.getAuthCode(clientId); + return await this.sasViyaApiClient!.getAuthCode(clientId) } public async getAccessToken( @@ -200,13 +200,13 @@ export default class SASjs { authCode: string ) { if (this.sasjsConfig.serverType !== ServerType.SASViya) { - throw new Error("This operation is only supported on SAS Viya servers."); + throw new Error("This operation is only supported on SAS Viya servers.") } return await this.sasViyaApiClient!.getAccessToken( clientId, clientSecret, authCode - ); + ) } public async refreshTokens( @@ -215,20 +215,20 @@ export default class SASjs { refreshToken: string ) { if (this.sasjsConfig.serverType !== ServerType.SASViya) { - throw new Error("This operation is only supported on SAS Viya servers."); + throw new Error("This operation is only supported on SAS Viya servers.") } return await this.sasViyaApiClient!.refreshTokens( clientId, clientSecret, refreshToken - ); + ) } public async deleteClient(clientId: string, accessToken: string) { if (this.sasjsConfig.serverType !== ServerType.SASViya) { - throw new Error("This operation is only supported on SAS Viya servers."); + throw new Error("This operation is only supported on SAS Viya servers.") } - return await this.sasViyaApiClient!.deleteClient(clientId, accessToken); + return await this.sasViyaApiClient!.deleteClient(clientId, accessToken) } /** @@ -236,7 +236,7 @@ export default class SASjs { * */ public getSasjsConfig() { - return this.sasjsConfig; + return this.sasjsConfig } /** @@ -244,7 +244,7 @@ export default class SASjs { * */ public getUserName() { - return this.userName; + return this.userName } /** @@ -252,7 +252,7 @@ export default class SASjs { * */ public getCsrfApi() { - return this.csrfTokenApi?.value; + return this.csrfTokenApi?.value } /** @@ -260,7 +260,7 @@ export default class SASjs { * */ public getCsrfWeb() { - return this.csrfTokenWeb?.value; + return this.csrfTokenWeb?.value } /** @@ -271,8 +271,8 @@ export default class SASjs { this.sasjsConfig = { ...this.sasjsConfig, ...config - }; - await this.setupConfiguration(); + } + await this.setupConfiguration() } /** @@ -281,7 +281,7 @@ export default class SASjs { * @param value - Boolean indicating debug state */ public setDebugState(value: boolean) { - this.sasjsConfig.debug = value; + this.sasjsConfig.debug = value } /** @@ -289,14 +289,14 @@ export default class SASjs { * @returns a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName` */ public async checkSession() { - const loginResponse = await fetch(this.loginUrl.replace(".do", "")); - const responseText = await loginResponse.text(); - const isLoggedIn = / response.text()) .then(async (responseText) => { - let authFormRes: any; - let loggedIn; + let authFormRes: any + let loggedIn if (isAuthorizeFormRequired(responseText)) { authFormRes = await parseAndSubmitAuthorizeForm( responseText, this.sasjsConfig.serverUrl - ); + ) } else { - loggedIn = isLogInSuccess(responseText); + loggedIn = isLogInSuccess(responseText) } if (!loggedIn) { - const currentSession = await this.checkSession(); - loggedIn = currentSession.isLoggedIn; + const currentSession = await this.checkSession() + loggedIn = currentSession.isLoggedIn } if (loggedIn) { - this.resendWaitingRequests(); + this.resendWaitingRequests() } return { isLoggedIn: loggedIn, userName: this.userName - }; + } }) - .catch((e) => Promise.reject(e)); + .catch((e) => Promise.reject(e)) } /** @@ -375,13 +375,13 @@ export default class SASjs { */ public logOut() { return new Promise((resolve, reject) => { - const logOutURL = `${this.sasjsConfig.serverUrl}${this.logoutUrl}`; + const logOutURL = `${this.sasjsConfig.serverUrl}${this.logoutUrl}` fetch(logOutURL) .then(() => { - resolve(true); + resolve(true) }) - .catch((err: Error) => reject(err)); - }); + .catch((err: Error) => reject(err)) + }) } /** @@ -401,9 +401,9 @@ export default class SASjs { this.jobsPath, this.setCsrfTokenWeb, this.csrfTokenWeb - ); + ) - return fileUploader.uploadFile(sasJob, files, params); + return fileUploader.uploadFile(sasJob, files, params) } /** @@ -431,14 +431,14 @@ export default class SASjs { loginRequiredCallback?: any, accessToken?: string ) { - let requestResponse; + let requestResponse config = { ...this.sasjsConfig, ...config - }; + } - sasJob = sasJob.startsWith("/") ? sasJob.replace("/", "") : sasJob; + sasJob = sasJob.startsWith("/") ? sasJob.replace("/", "") : sasJob if (config.serverType === ServerType.SASViya && config.contextName) { if (config.useComputeApi) { @@ -448,9 +448,9 @@ export default class SASjs { config, loginRequiredCallback, accessToken - ); + ) - this.retryCountComputeApi = 0; + this.retryCountComputeApi = 0 } else { requestResponse = await this.executeJobViaJesApi( sasJob, @@ -458,9 +458,9 @@ export default class SASjs { config, loginRequiredCallback, accessToken - ); + ) - this.retryCountJeseApi = 0; + this.retryCountJeseApi = 0 } } else { requestResponse = await this.executeJobViaWeb( @@ -468,10 +468,10 @@ export default class SASjs { data, config, loginRequiredCallback - ); + ) } - return requestResponse; + return requestResponse } /** @@ -492,16 +492,16 @@ export default class SASjs { accessToken?: string ) { if (this.sasjsConfig.serverType !== ServerType.SASViya) { - throw new Error("This operation is only supported on SAS Viya servers."); + throw new Error("This operation is only supported on SAS Viya servers.") } - let sasApiClient: any = null; + let sasApiClient: any = null if (serverUrl || appLoc) { if (!serverUrl) { - serverUrl = this.sasjsConfig.serverUrl; + serverUrl = this.sasjsConfig.serverUrl } if (!appLoc) { - appLoc = this.sasjsConfig.appLoc; + appLoc = this.sasjsConfig.appLoc } if (this.sasjsConfig.serverType === ServerType.SASViya) { sasApiClient = new SASViyaApiClient( @@ -509,39 +509,39 @@ export default class SASjs { appLoc, this.sasjsConfig.contextName, this.setCsrfTokenApi - ); + ) } else if (this.sasjsConfig.serverType === ServerType.SAS9) { - sasApiClient = new SAS9ApiClient(serverUrl); + sasApiClient = new SAS9ApiClient(serverUrl) } } else { - let sasClientConfig: any = null; + let sasClientConfig: any = null if (this.sasjsConfig.serverType === ServerType.SASViya) { - sasClientConfig = this.sasViyaApiClient!.getConfig(); + sasClientConfig = this.sasViyaApiClient!.getConfig() } else if (this.sasjsConfig.serverType === ServerType.SAS9) { - sasClientConfig = this.sas9ApiClient!.getConfig(); + sasClientConfig = this.sas9ApiClient!.getConfig() } - serverUrl = sasClientConfig.serverUrl; - appLoc = sasClientConfig.rootFolderName as string; + serverUrl = sasClientConfig.serverUrl + appLoc = sasClientConfig.rootFolderName as string } // members of type 'folder' should be processed first if (serviceJson.members[0].members) { serviceJson.members[0].members.sort((member: { type: string }) => member.type === "folder" ? -1 : 1 - ); + ) } const members = serviceJson.members[0].name === "services" ? serviceJson.members[0].members - : serviceJson.members; + : serviceJson.members await this.createFoldersAndServices( appLoc, members, accessToken, sasApiClient - ); + ) } private async executeJobViaComputeApi( @@ -559,7 +559,7 @@ export default class SASjs { }, SASjob: sasJob, data - }; + } sasjsWaitingRequest.requestPromise.promise = new Promise( async (resolve, reject) => { @@ -573,23 +573,23 @@ export default class SASjs { ) .then((response) => { if (!config.debug) { - this.appendSasjsRequest(null, sasJob, null); + this.appendSasjsRequest(null, sasJob, null) } else { - this.appendSasjsRequest(response, sasJob, null); + this.appendSasjsRequest(response, sasJob, null) } - let responseJson; + let responseJson try { - responseJson = JSON.parse(response!.result); + responseJson = JSON.parse(response!.result) } catch { - responseJson = JSON.parse(parseWeboutResponse(response!.result)); + responseJson = JSON.parse(parseWeboutResponse(response!.result)) } - resolve(responseJson); + resolve(responseJson) }) .catch(async (response) => { - let error = response.error || response; + let error = response.error || response if (needsRetry(JSON.stringify(error))) { if (this.retryCountComputeApi < requestRetryLimit) { @@ -599,32 +599,32 @@ export default class SASjs { config, loginRequiredCallback, accessToken - ); + ) - this.retryCountComputeApi++; + this.retryCountComputeApi++ - resolve(retryResponse); + resolve(retryResponse) } else { - this.retryCountComputeApi = 0; - reject({ MESSAGE: "Compute API retry requests limit reached" }); + this.retryCountComputeApi = 0 + reject({ MESSAGE: "Compute API retry requests limit reached" }) } } if (error && error.status === 401) { - if (loginRequiredCallback) loginRequiredCallback(true); - sasjsWaitingRequest.requestPromise.resolve = resolve; - sasjsWaitingRequest.requestPromise.reject = reject; - sasjsWaitingRequest.config = config; - this.sasjsWaitingRequests.push(sasjsWaitingRequest); + if (loginRequiredCallback) loginRequiredCallback(true) + sasjsWaitingRequest.requestPromise.resolve = resolve + sasjsWaitingRequest.requestPromise.reject = reject + sasjsWaitingRequest.config = config + this.sasjsWaitingRequests.push(sasjsWaitingRequest) } else { - reject({ MESSAGE: error || "Job execution failed" }); + reject({ MESSAGE: error || "Job execution failed" }) } - this.appendSasjsRequest(response.log, sasJob, null); - }); + this.appendSasjsRequest(response.log, sasJob, null) + }) } - ); - return sasjsWaitingRequest.requestPromise.promise; + ) + return sasjsWaitingRequest.requestPromise.promise } private async executeJobViaJesApi( @@ -642,18 +642,18 @@ export default class SASjs { }, SASjob: sasJob, data - }; + } sasjsWaitingRequest.requestPromise.promise = new Promise( async (resolve, reject) => { - const session = await this.checkSession(); + const session = await this.checkSession() if (!session.isLoggedIn) { - if (loginRequiredCallback) loginRequiredCallback(true); - sasjsWaitingRequest.requestPromise.resolve = resolve; - sasjsWaitingRequest.requestPromise.reject = reject; - sasjsWaitingRequest.config = config; - this.sasjsWaitingRequests.push(sasjsWaitingRequest); + if (loginRequiredCallback) loginRequiredCallback(true) + sasjsWaitingRequest.requestPromise.resolve = resolve + sasjsWaitingRequest.requestPromise.reject = reject + sasjsWaitingRequest.config = config + this.sasjsWaitingRequests.push(sasjsWaitingRequest) } else { resolve( await this.sasViyaApiClient @@ -666,22 +666,22 @@ export default class SASjs { ) .then((response) => { if (!config.debug) { - this.appendSasjsRequest(null, sasJob, null); + this.appendSasjsRequest(null, sasJob, null) } else { - this.appendSasjsRequest(response, sasJob, null); + this.appendSasjsRequest(response, sasJob, null) } - let responseJson; + let responseJson try { - responseJson = JSON.parse(response!.result); + responseJson = JSON.parse(response!.result) } catch { responseJson = JSON.parse( parseWeboutResponse(response!.result) - ); + ) } - return responseJson; + return responseJson }) .catch(async (e) => { if (needsRetry(JSON.stringify(e))) { @@ -692,24 +692,24 @@ export default class SASjs { config, loginRequiredCallback, accessToken - ); + ) - this.retryCountJeseApi++; + this.retryCountJeseApi++ - resolve(retryResponse); + resolve(retryResponse) } else { - this.retryCountJeseApi = 0; - reject({ MESSAGE: "Jes API retry requests limit reached" }); + this.retryCountJeseApi = 0 + reject({ MESSAGE: "Jes API retry requests limit reached" }) } } - reject({ MESSAGE: (e && e.message) || "Job execution failed" }); + reject({ MESSAGE: (e && e.message) || "Job execution failed" }) }) - ); + ) } } - ); - return sasjsWaitingRequest.requestPromise.promise; + ) + return sasjsWaitingRequest.requestPromise.promise } private async executeJobViaWeb( @@ -726,29 +726,29 @@ export default class SASjs { }, SASjob: sasJob, data - }; + } const program = config.appLoc ? config.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "") - : sasJob; + : sasJob const jobUri = - config.serverType === "SASVIYA" ? await this.getJobUri(sasJob) : ""; + config.serverType === "SASVIYA" ? await this.getJobUri(sasJob) : "" const apiUrl = `${config.serverUrl}${this.jobsPath}/?${ jobUri.length > 0 ? "__program=" + program + "&_job=" + jobUri : "_program=" + program - }`; + }` const requestParams = { ...this.getRequestParamsWeb(config) - }; + } - const formData = new FormData(); + const formData = new FormData() - let isError = false; - let errorMsg = ""; + let isError = false + let errorMsg = "" if (data) { - const stringifiedData = JSON.stringify(data); + const stringifiedData = JSON.stringify(data) if ( config.serverType === ServerType.SAS9 || stringifiedData.length > 500000 || @@ -757,69 +757,69 @@ export default class SASjs { // file upload approach for (const tableName in data) { if (isError) { - return; + return } - const name = tableName; - const csv = convertToCSV(data[tableName]); + const name = tableName + const csv = convertToCSV(data[tableName]) if (csv === "ERROR: LARGE STRING LENGTH") { - isError = true; + isError = true errorMsg = - "The max length of a string value in SASjs is 32765 characters."; + "The max length of a string value in SASjs is 32765 characters." } const file = new Blob([csv], { type: "application/csv" - }); + }) - formData.append(name, file, `${name}.csv`); + formData.append(name, file, `${name}.csv`) } } else { // param based approach - const sasjsTables = []; - let tableCounter = 0; + const sasjsTables = [] + let tableCounter = 0 for (const tableName in data) { if (isError) { - return; + return } - tableCounter++; - sasjsTables.push(tableName); - const csv = convertToCSV(data[tableName]); + tableCounter++ + sasjsTables.push(tableName) + const csv = convertToCSV(data[tableName]) if (csv === "ERROR: LARGE STRING LENGTH") { - isError = true; + isError = true errorMsg = - "The max length of a string value in SASjs is 32765 characters."; + "The max length of a string value in SASjs is 32765 characters." } // if csv has length more then 16k, send in chunks if (csv.length > 16000) { - const csvChunks = splitChunks(csv); + const csvChunks = splitChunks(csv) // append chunks to form data with same key csvChunks.map((chunk) => { - formData.append(`sasjs${tableCounter}data`, chunk); - }); + formData.append(`sasjs${tableCounter}data`, chunk) + }) } else { - requestParams[`sasjs${tableCounter}data`] = csv; + requestParams[`sasjs${tableCounter}data`] = csv } } - requestParams["sasjs_tables"] = sasjsTables.join(" "); + requestParams["sasjs_tables"] = sasjsTables.join(" ") } } for (const key in requestParams) { if (requestParams.hasOwnProperty(key)) { - formData.append(key, requestParams[key]); + formData.append(key, requestParams[key]) } } - let isRedirected = false; + let isRedirected = false sasjsWaitingRequest.requestPromise.promise = new Promise( (resolve, reject) => { if (isError) { - reject({ MESSAGE: errorMsg }); + reject({ MESSAGE: errorMsg }) } - const headers: any = {}; + const headers: any = {} if (this.csrfTokenWeb) { - headers[this.csrfTokenWeb.headerName] = this.csrfTokenWeb.value; + headers[this.csrfTokenWeb.headerName] = this.csrfTokenWeb.value } fetch(apiUrl, { method: "POST", @@ -830,23 +830,23 @@ export default class SASjs { .then(async (response) => { if (!response.ok) { if (response.status === 403) { - const tokenHeader = response.headers.get("X-CSRF-HEADER"); + const tokenHeader = response.headers.get("X-CSRF-HEADER") if (tokenHeader) { - const token = response.headers.get(tokenHeader); + const token = response.headers.get(tokenHeader) this.csrfTokenWeb = { headerName: tokenHeader, value: token || "" - }; + } } } } if (response.redirected && config.serverType === ServerType.SAS9) { - isRedirected = true; + isRedirected = true } - return response.text(); + return response.text() }) .then((responseText) => { if ( @@ -854,36 +854,36 @@ export default class SASjs { !isLogInRequired(responseText) ) { if (this.retryCountWeb < requestRetryLimit) { - this.retryCountWeb++; + this.retryCountWeb++ this.request(sasJob, data).then( (res: any) => resolve(res), (err: any) => reject(err) - ); + ) } else { - this.retryCountWeb = 0; - reject(responseText); + this.retryCountWeb = 0 + reject(responseText) } } else { - this.retryCountWeb = 0; - this.parseLogFromResponse(responseText, program); + this.retryCountWeb = 0 + this.parseLogFromResponse(responseText, program) if (isLogInRequired(responseText)) { - if (loginRequiredCallback) loginRequiredCallback(true); - sasjsWaitingRequest.requestPromise.resolve = resolve; - sasjsWaitingRequest.requestPromise.reject = reject; - sasjsWaitingRequest.config = config; - this.sasjsWaitingRequests.push(sasjsWaitingRequest); + if (loginRequiredCallback) loginRequiredCallback(true) + sasjsWaitingRequest.requestPromise.resolve = resolve + sasjsWaitingRequest.requestPromise.reject = reject + sasjsWaitingRequest.config = config + this.sasjsWaitingRequests.push(sasjsWaitingRequest) } else { if (config.serverType === ServerType.SAS9 && config.debug) { - this.updateUsername(responseText); - const jsonResponseText = parseWeboutResponse(responseText); + this.updateUsername(responseText) + const jsonResponseText = parseWeboutResponse(responseText) if (jsonResponseText !== "") { - resolve(JSON.parse(jsonResponseText)); + resolve(JSON.parse(jsonResponseText)) } else { reject({ MESSAGE: this.parseSAS9ErrorResponse(responseText) - }); + }) } } else if ( config.serverType === ServerType.SASViya && @@ -892,91 +892,91 @@ export default class SASjs { try { this.parseSASVIYADebugResponse(responseText).then( (resText: any) => { - this.updateUsername(resText); + this.updateUsername(resText) try { - resolve(JSON.parse(resText)); + resolve(JSON.parse(resText)) } catch (e) { - reject({ MESSAGE: resText }); + reject({ MESSAGE: resText }) } }, (err: any) => { - reject({ MESSAGE: err }); + reject({ MESSAGE: err }) } - ); + ) } catch (e) { - reject({ MESSAGE: responseText }); + reject({ MESSAGE: responseText }) } } else { - this.updateUsername(responseText); + this.updateUsername(responseText) try { - const parsedJson = JSON.parse(responseText); - resolve(parsedJson); + const parsedJson = JSON.parse(responseText) + resolve(parsedJson) } catch (e) { - reject({ MESSAGE: responseText }); + reject({ MESSAGE: responseText }) } } } } }) .catch((e: Error) => { - reject(e); - }); + reject(e) + }) } - ); + ) - return sasjsWaitingRequest.requestPromise.promise; + return sasjsWaitingRequest.requestPromise.promise } private setCsrfTokenWeb = (csrfToken: CsrfToken) => { - this.csrfTokenWeb = csrfToken; - }; + this.csrfTokenWeb = csrfToken + } private setCsrfTokenApi = (csrfToken: CsrfToken) => { - this.csrfTokenApi = csrfToken; - }; + this.csrfTokenApi = csrfToken + } private async resendWaitingRequests() { for (const sasjsWaitingRequest of this.sasjsWaitingRequests) { this.request(sasjsWaitingRequest.SASjob, sasjsWaitingRequest.data).then( (res: any) => { - sasjsWaitingRequest.requestPromise.resolve(res); + sasjsWaitingRequest.requestPromise.resolve(res) }, (err: any) => { - sasjsWaitingRequest.requestPromise.reject(err); + sasjsWaitingRequest.requestPromise.reject(err) } - ); + ) } - this.sasjsWaitingRequests = []; + this.sasjsWaitingRequests = [] } private getRequestParamsWeb(config: any): any { - const requestParams: any = {}; + const requestParams: any = {} if (this.csrfTokenWeb) { - requestParams["_csrf"] = this.csrfTokenWeb.value; + requestParams["_csrf"] = this.csrfTokenWeb.value } if (config.debug) { - requestParams["_omittextlog"] = "false"; - requestParams["_omitsessionresults"] = "false"; + requestParams["_omittextlog"] = "false" + requestParams["_omitsessionresults"] = "false" - requestParams["_debug"] = 131; + requestParams["_debug"] = 131 } - return requestParams; + return requestParams } private updateUsername(response: any) { try { - const responseJson = JSON.parse(response); + const responseJson = JSON.parse(response) if (this.sasjsConfig.serverType === ServerType.SAS9) { - this.userName = responseJson["_METAUSER"]; + this.userName = responseJson["_METAUSER"] } else { - this.userName = responseJson["SYSUSERID"]; + this.userName = responseJson["SYSUSERID"] } } catch (e) { - this.userName = ""; + this.userName = "" } } @@ -984,47 +984,47 @@ export default class SASjs { return new Promise((resolve, reject) => { const iframeStart = response.split( '')[0] : null; + )[1] + const jsonUrl = iframeStart ? iframeStart.split('">')[0] : null if (jsonUrl) { fetch(this.sasjsConfig.serverUrl + jsonUrl) .then((res) => res.text()) .then((resText) => { - resolve(resText); - }); + resolve(resText) + }) } else { - reject("No debug info in response"); + reject("No debug info in response") } - }); + }) } private async getJobUri(sasJob: string) { - if (!this.sasViyaApiClient) return ""; - const jobMap: any = await this.sasViyaApiClient.getAppLocMap(); - let uri = ""; + if (!this.sasViyaApiClient) return "" + const jobMap: any = await this.sasViyaApiClient.getAppLocMap() + let uri = "" if (jobMap.size) { - const jobKey = sasJob.split("/")[0]; - const jobName = sasJob.split("/")[1]; + const jobKey = sasJob.split("/")[0] + const jobName = sasJob.split("/")[1] - const locJobs = jobMap.get(jobKey); + const locJobs = jobMap.get(jobKey) if (locJobs) { const job = locJobs.find( (el: any) => el.name === jobName && el.contentType === "jobDefinition" - ); + ) if (job) { - uri = job.uri; + uri = job.uri } } } - return uri; + return uri } private parseSAS9ErrorResponse(response: string) { - const logLines = response.split("\n"); - const parsedLines: string[] = []; - let firstErrorLineIndex: number = -1; + const logLines = response.split("\n") + const parsedLines: string[] = [] + let firstErrorLineIndex: number = -1 logLines.map((line: string, index: number) => { if ( @@ -1032,25 +1032,25 @@ export default class SASjs { !line.toLowerCase().includes("this request completed with errors.") && firstErrorLineIndex === -1 ) { - firstErrorLineIndex = index; + firstErrorLineIndex = index } - }); + }) for (let i = firstErrorLineIndex - 10; i <= firstErrorLineIndex + 10; i++) { - parsedLines.push(logLines[i]); + parsedLines.push(logLines[i]) } - return parsedLines.join(", "); + return parsedLines.join(", ") } private parseLogFromResponse(response: any, program: string) { if (this.sasjsConfig.serverType === ServerType.SAS9) { - this.appendSasjsRequest(response, program, null); + this.appendSasjsRequest(response, program, null) } else { if (!this.sasjsConfig.debug) { - this.appendSasjsRequest(null, program, null); + this.appendSasjsRequest(null, program, null) } else { - this.appendSasjsRequest(response, program, null); + this.appendSasjsRequest(response, program, null) } } } @@ -1062,8 +1062,8 @@ export default class SASjs { }) .then((response: any) => response.text()) .then((response: any) => resolve(response)) - .catch((err: Error) => reject(err)); - }); + .catch((err: Error) => reject(err)) + }) } private async appendSasjsRequest( @@ -1071,28 +1071,28 @@ export default class SASjs { program: string, pgmData: any ) { - let sourceCode = ""; - let generatedCode = ""; - let sasWork = null; + let sourceCode = "" + let generatedCode = "" + let sasWork = null if (response && response.result && response.log) { - sourceCode = parseSourceCode(response.log); - generatedCode = parseGeneratedCode(response.log); + sourceCode = parseSourceCode(response.log) + generatedCode = parseGeneratedCode(response.log) if (this.sasjsConfig.debug) { if (response.log) { - sasWork = response.log; + sasWork = response.log } else { - sasWork = JSON.parse(parseWeboutResponse(response.result)).WORK; + sasWork = JSON.parse(parseWeboutResponse(response.result)).WORK } } else { - sasWork = JSON.parse(response.result).WORK; + sasWork = JSON.parse(response.result).WORK } } else { if (response) { - sourceCode = parseSourceCode(response); - generatedCode = parseGeneratedCode(response); - sasWork = await this.parseSasWork(response); + sourceCode = parseSourceCode(response) + generatedCode = parseGeneratedCode(response) + sasWork = await this.parseSasWork(response) } } @@ -1103,52 +1103,52 @@ export default class SASjs { sourceCode, generatedCode, SASWORK: sasWork - }); + }) if (this.sasjsRequests.length > 20) { - this.sasjsRequests.splice(0, 1); + this.sasjsRequests.splice(0, 1) } } private async parseSasWork(response: any) { if (this.sasjsConfig.debug) { - let jsonResponse; + let jsonResponse if (this.sasjsConfig.serverType === ServerType.SAS9) { try { - jsonResponse = JSON.parse(parseWeboutResponse(response)); + jsonResponse = JSON.parse(parseWeboutResponse(response)) } catch (e) { - console.error(e); + console.error(e) } } else { await this.parseSASVIYADebugResponse(response).then( (resText: any) => { try { - jsonResponse = JSON.parse(resText); + jsonResponse = JSON.parse(resText) } catch (e) { - console.error(e); + console.error(e) } }, (err: any) => { - console.error(err); + console.error(err) } - ); + ) } if (jsonResponse) { - return jsonResponse.WORK; + return jsonResponse.WORK } } - return null; + return null } public getSasRequests() { - const sortedRequests = this.sasjsRequests.sort(compareTimestamps); - return sortedRequests; + const sortedRequests = this.sasjsRequests.sort(compareTimestamps) + return sortedRequests } public clearSasRequests() { - this.sasjsRequests = []; + this.sasjsRequests = [] } private setupConfiguration() { @@ -1156,45 +1156,45 @@ export default class SASjs { this.sasjsConfig.serverUrl === undefined || this.sasjsConfig.serverUrl === "" ) { - let url = `${location.protocol}//${location.hostname}`; + let url = `${location.protocol}//${location.hostname}` if (location.port) { - url = `${url}:${location.port}`; + url = `${url}:${location.port}` } - this.sasjsConfig.serverUrl = url; + this.sasjsConfig.serverUrl = url } if (this.sasjsConfig.serverUrl.slice(-1) === "/") { - this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1); + this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1) } this.jobsPath = this.sasjsConfig.serverType === ServerType.SASViya ? this.sasjsConfig.pathSASViya - : this.sasjsConfig.pathSAS9; - this.loginUrl = `${this.sasjsConfig.serverUrl}/SASLogon/login`; + : this.sasjsConfig.pathSAS9 + this.loginUrl = `${this.sasjsConfig.serverUrl}/SASLogon/login` this.logoutUrl = this.sasjsConfig.serverType === ServerType.SAS9 ? "/SASLogon/logout?" - : "/SASLogon/logout.do?"; + : "/SASLogon/logout.do?" if (this.sasjsConfig.serverType === ServerType.SASViya) { if (this.sasViyaApiClient) this.sasViyaApiClient!.setConfig( this.sasjsConfig.serverUrl, this.sasjsConfig.appLoc - ); + ) else this.sasViyaApiClient = new SASViyaApiClient( this.sasjsConfig.serverUrl, this.sasjsConfig.appLoc, this.sasjsConfig.contextName, this.setCsrfTokenApi - ); + ) } if (this.sasjsConfig.serverType === ServerType.SAS9) { if (this.sas9ApiClient) - this.sas9ApiClient!.setConfig(this.sasjsConfig.serverUrl); - else this.sas9ApiClient = new SAS9ApiClient(this.sasjsConfig.serverUrl); + this.sas9ApiClient!.setConfig(this.sasjsConfig.serverUrl) + else this.sas9ApiClient = new SAS9ApiClient(this.sasjsConfig.serverUrl) } this.fileUploader = new FileUploader( @@ -1202,45 +1202,45 @@ export default class SASjs { this.sasjsConfig.serverUrl, this.jobsPath, this.setCsrfTokenWeb - ); + ) } private setLoginUrl = (matches: RegExpExecArray) => { - let parsedURL = matches[1].replace(/\?.*/, ""); + let parsedURL = matches[1].replace(/\?.*/, "") if (parsedURL[0] === "/") { - parsedURL = parsedURL.substr(1); + parsedURL = parsedURL.substr(1) const tempLoginLink = this.sasjsConfig.serverUrl ? `${this.sasjsConfig.serverUrl}/${parsedURL}` - : `${parsedURL}`; + : `${parsedURL}` - const loginUrl = tempLoginLink; + const loginUrl = tempLoginLink this.loginUrl = this.sasjsConfig.serverType === ServerType.SASViya ? tempLoginLink - : loginUrl.replace(".do", ""); + : loginUrl.replace(".do", "") } - }; + } private async getLoginForm() { - const pattern: RegExp = //; - const response = await fetch(this.loginUrl).then((r) => r.text()); - const matches = pattern.exec(response); - const formInputs: any = {}; + const pattern: RegExp = // + const response = await fetch(this.loginUrl).then((r) => r.text()) + const matches = pattern.exec(response) + const formInputs: any = {} if (matches && matches.length) { - this.setLoginUrl(matches); - const inputs = response.match(/]*>/g); + this.setLoginUrl(matches) + const inputs = response.match(/]*>/g) if (inputs) { inputs.forEach((inputStr: string) => { - const valueMatch = inputStr.match(/name="([^"]*)"\svalue="([^"]*)/); + const valueMatch = inputStr.match(/name="([^"]*)"\svalue="([^"]*)/) if (valueMatch && valueMatch.length) { - formInputs[valueMatch[1]] = valueMatch[2]; + formInputs[valueMatch[1]] = valueMatch[2] } - }); + }) } } - return Object.keys(formInputs).length ? formInputs : null; + return Object.keys(formInputs).length ? formInputs : null } private async createFoldersAndServices( @@ -1258,8 +1258,8 @@ export default class SASjs { undefined, accessToken, sasApiClient - ); - break; + ) + break case "service": await this.createJobDefinition( member.name, @@ -1268,10 +1268,10 @@ export default class SASjs { undefined, accessToken, sasApiClient - ); - break; + ) + break default: - throw new Error(`Unidenitied member present in Json: ${member.name}`); + throw new Error(`Unidenitied member present in Json: ${member.name}`) } if (member.type === "folder" && member.members && member.members.length) await this.createFoldersAndServices( @@ -1279,7 +1279,7 @@ export default class SASjs { member.members, accessToken, sasApiClient - ); - }); + ) + }) } } diff --git a/src/SessionManager.ts b/src/SessionManager.ts index 5f690df..63c0b94 100644 --- a/src/SessionManager.ts +++ b/src/SessionManager.ts @@ -1,7 +1,7 @@ -import { Session, Context, CsrfToken } from "./types"; -import { asyncForEach, makeRequest } from "./utils"; +import { Session, Context, CsrfToken } from "./types" +import { asyncForEach, makeRequest } from "./utils" -const MAX_SESSION_COUNT = 1; +const MAX_SESSION_COUNT = 1 export class SessionManager { constructor( @@ -9,49 +9,49 @@ export class SessionManager { private contextName: string, private setCsrfToken: (csrfToken: CsrfToken) => void ) {} - private sessions: Session[] = []; - private currentContext: Context | null = null; - private csrfToken: CsrfToken | null = null; + private sessions: Session[] = [] + private currentContext: Context | null = null + private csrfToken: CsrfToken | null = null async getSession(accessToken?: string) { - await this.createSessions(accessToken); - this.createAndWaitForSession(accessToken); - const session = this.sessions.pop(); + await this.createSessions(accessToken) + this.createAndWaitForSession(accessToken) + const session = this.sessions.pop() const secondsSinceSessionCreation = (new Date().getTime() - new Date(session!.creationTimeStamp).getTime()) / - 1000; + 1000 if ( secondsSinceSessionCreation >= session!.attributes.sessionInactiveTimeout ) { - await this.createSessions(accessToken); - const freshSession = this.sessions.pop(); - return freshSession; + await this.createSessions(accessToken) + const freshSession = this.sessions.pop() + return freshSession } - return session; + 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); - }); + this.sessions = this.sessions.filter((s) => s.id !== id) + }) } private async createSessions(accessToken?: string) { if (!this.sessions.length) { if (!this.currentContext) { - await this.setCurrentContext(accessToken); + await this.setCurrentContext(accessToken) } await asyncForEach(new Array(MAX_SESSION_COUNT), async () => { - const createdSession = await this.createAndWaitForSession(accessToken); - this.sessions.push(createdSession); - }); + const createdSession = await this.createAndWaitForSession(accessToken) + this.sessions.push(createdSession) + }) } } @@ -59,53 +59,53 @@ export class SessionManager { const createSessionRequest = { method: "POST", headers: this.getHeaders(accessToken) - }; + } const { result: createdSession, etag } = await this.request( `${this.serverUrl}/compute/contexts/${this.currentContext!.id}/sessions`, createSessionRequest - ); + ) - await this.waitForSession(createdSession, etag); - this.sessions.push(createdSession); - return createdSession; + await this.waitForSession(createdSession, etag) + this.sessions.push(createdSession) + return createdSession } private async setCurrentContext(accessToken?: string) { if (!this.currentContext) { const { result: contexts } = await this.request<{ - items: Context[]; + items: Context[] }>(`${this.serverUrl}/compute/contexts`, { headers: this.getHeaders(accessToken) - }); + }) const contextsList = contexts && contexts.items && contexts.items.length ? contexts.items - : []; + : [] const currentContext = contextsList.find( (c: any) => c.name === this.contextName - ); + ) if (!currentContext) { throw new Error( `The context ${this.contextName} was not found on the server ${this.serverUrl}` - ); + ) } - this.currentContext = currentContext; + this.currentContext = currentContext } } private getHeaders(accessToken?: string) { const headers: any = { "Content-Type": "application/json" - }; + } if (accessToken) { - headers.Authorization = `Bearer ${accessToken}`; + headers.Authorization = `Bearer ${accessToken}` } - return headers; + return headers } private async waitForSession( @@ -114,17 +114,17 @@ export class SessionManager { accessToken?: string, silent = false ) { - let sessionState = session.state; + let sessionState = session.state const headers: any = { ...this.getHeaders(accessToken), "If-None-Match": etag - }; - const stateLink = session.links.find((l: any) => l.rel === "state"); + } + const stateLink = session.links.find((l: any) => l.rel === "state") return new Promise(async (resolve, _) => { if (sessionState === "pending") { if (stateLink) { if (!silent) { - console.log("Polling session status... \n"); + console.log("Polling session status... \n") } const { result: state } = await this.request( `${this.serverUrl}${stateLink.href}?wait=30`, @@ -132,18 +132,18 @@ export class SessionManager { headers }, "text" - ); + ) - sessionState = state.trim(); + sessionState = state.trim() if (!silent) { - console.log(`Current state: ${sessionState}\n`); + console.log(`Current state: ${sessionState}\n`) } - resolve(sessionState); + resolve(sessionState) } } else { - resolve(sessionState); + resolve(sessionState) } - }); + }) } private async request( @@ -155,16 +155,16 @@ export class SessionManager { options.headers = { ...options.headers, [this.csrfToken.headerName]: this.csrfToken.value - }; + } } return await makeRequest( url, options, (token) => { - this.csrfToken = token; - this.setCsrfToken(token); + this.csrfToken = token + this.setCsrfToken(token) }, contentType - ); + ) } } diff --git a/src/index.ts b/src/index.ts index 39459c6..d646759 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ -import SASjs from "./SASjs"; -export * from "./types"; -export * from "./SASViyaApiClient"; -export * from "./SAS9ApiClient"; -export default SASjs; +import SASjs from "./SASjs" +export * from "./types" +export * from "./SASViyaApiClient" +export * from "./SAS9ApiClient" +export default SASjs diff --git a/src/types/Context.ts b/src/types/Context.ts index c5ca093..41be8d7 100644 --- a/src/types/Context.ts +++ b/src/types/Context.ts @@ -1,6 +1,6 @@ export interface Context { - name: string; - id: string; - createdBy: string; - version: number; + name: string + id: string + createdBy: string + version: number } diff --git a/src/types/CsrfToken.ts b/src/types/CsrfToken.ts index 7161982..79a5036 100644 --- a/src/types/CsrfToken.ts +++ b/src/types/CsrfToken.ts @@ -1,4 +1,4 @@ export interface CsrfToken { - headerName: string; - value: string; + headerName: string + value: string } diff --git a/src/types/Folder.ts b/src/types/Folder.ts index 849dc7c..109b50c 100644 --- a/src/types/Folder.ts +++ b/src/types/Folder.ts @@ -1,7 +1,7 @@ -import { Link } from "./Link"; +import { Link } from "./Link" export interface Folder { - id: string; - uri: string; - links: Link[]; + id: string + uri: string + links: Link[] } diff --git a/src/types/Job.ts b/src/types/Job.ts index cee59d6..3d99673 100644 --- a/src/types/Job.ts +++ b/src/types/Job.ts @@ -1,13 +1,13 @@ -import { Link } from "./Link"; -import { JobResult } from "./JobResult"; +import { Link } from "./Link" +import { JobResult } from "./JobResult" export interface Job { - id: string; - name: string; - uri: string; - createdBy: string; - code?: string; - links: Link[]; - results: JobResult; - error?: any; + id: string + name: string + uri: string + createdBy: string + code?: string + links: Link[] + results: JobResult + error?: any } diff --git a/src/types/JobDefinition.ts b/src/types/JobDefinition.ts index fc8fb59..74969ee 100644 --- a/src/types/JobDefinition.ts +++ b/src/types/JobDefinition.ts @@ -1,3 +1,3 @@ export interface JobDefinition { - code: string; + code: string } diff --git a/src/types/JobResult.ts b/src/types/JobResult.ts index 624ac08..edb8093 100644 --- a/src/types/JobResult.ts +++ b/src/types/JobResult.ts @@ -1,3 +1,3 @@ export interface JobResult { - "_webout.json": string; + "_webout.json": string } diff --git a/src/types/Link.ts b/src/types/Link.ts index 7f8c5a3..a60887e 100644 --- a/src/types/Link.ts +++ b/src/types/Link.ts @@ -1,7 +1,7 @@ export interface Link { - method: string; - rel: string; - href: string; - uri: string; - type: string; + method: string + rel: string + href: string + uri: string + type: string } diff --git a/src/types/SASjsConfig.ts b/src/types/SASjsConfig.ts index 9c434b4..94318bd 100644 --- a/src/types/SASjsConfig.ts +++ b/src/types/SASjsConfig.ts @@ -1,4 +1,4 @@ -import { ServerType } from "./ServerType"; +import { ServerType } from "./ServerType" /** * Specifies the configuration for the SASjs instance. @@ -10,22 +10,22 @@ export class SASjsConfig { * Can be omitted, eg if serving directly from the SAS Web Server or being * streamed. */ - serverUrl: string = ""; - pathSAS9: string = ""; - pathSASViya: string = ""; + serverUrl: string = "" + pathSAS9: string = "" + pathSASViya: string = "" /** * The appLoc is the parent folder under which the SAS services (STPs or Job * Execution Services) are stored. */ - appLoc: string = ""; + appLoc: string = "" /** * Can be SAS9 or SASVIYA */ - serverType: ServerType | null = null; + serverType: ServerType | null = null /** * Set to `true` to enable additional debugging. */ - debug: boolean = true; - contextName: string = ""; - useComputeApi = false; + debug: boolean = true + contextName: string = "" + useComputeApi = false } diff --git a/src/types/SASjsRequest.ts b/src/types/SASjsRequest.ts index 941b5ec..f0646bb 100644 --- a/src/types/SASjsRequest.ts +++ b/src/types/SASjsRequest.ts @@ -3,10 +3,10 @@ * */ export interface SASjsRequest { - serviceLink: string; - timestamp: Date; - sourceCode: string; - generatedCode: string; - logFile: string; - SASWORK: any; + serviceLink: string + timestamp: Date + sourceCode: string + generatedCode: string + logFile: string + SASWORK: any } diff --git a/src/types/SASjsWaitingRequest.ts b/src/types/SASjsWaitingRequest.ts index f47c832..68b1753 100644 --- a/src/types/SASjsWaitingRequest.ts +++ b/src/types/SASjsWaitingRequest.ts @@ -4,11 +4,11 @@ */ export interface SASjsWaitingRequest { requestPromise: { - promise: any; - resolve: any; - reject: any; - }; - SASjob: string; - data: any; - config?: any; + promise: any + resolve: any + reject: any + } + SASjob: string + data: any + config?: any } diff --git a/src/types/Session.ts b/src/types/Session.ts index a982063..452abd0 100644 --- a/src/types/Session.ts +++ b/src/types/Session.ts @@ -1,11 +1,11 @@ -import { Link } from "./Link"; +import { Link } from "./Link" export interface Session { - id: string; - state: string; - links: Link[]; + id: string + state: string + links: Link[] attributes: { - sessionInactiveTimeout: number; - }; - creationTimeStamp: string; + sessionInactiveTimeout: number + } + creationTimeStamp: string } diff --git a/src/types/UploadFile.ts b/src/types/UploadFile.ts index ddf5e69..419a992 100644 --- a/src/types/UploadFile.ts +++ b/src/types/UploadFile.ts @@ -3,6 +3,6 @@ * */ export interface UploadFile { - file: File; - fileName: string; + file: File + fileName: string } diff --git a/src/types/index.ts b/src/types/index.ts index dc7604c..b0902de 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,11 +1,11 @@ -export * from "./Context"; -export * from "./CsrfToken"; -export * from "./Folder"; -export * from "./Job"; -export * from "./Link"; -export * from "./SASjsConfig"; -export * from "./SASjsRequest"; -export * from "./SASjsWaitingRequest"; -export * from "./ServerType"; -export * from "./Session"; -export * from "./UploadFile"; +export * from "./Context" +export * from "./CsrfToken" +export * from "./Folder" +export * from "./Job" +export * from "./Link" +export * from "./SASjsConfig" +export * from "./SASjsRequest" +export * from "./SASjsWaitingRequest" +export * from "./ServerType" +export * from "./Session" +export * from "./UploadFile" diff --git a/src/utils/asyncForEach.ts b/src/utils/asyncForEach.ts index 9d71542..14fdcdc 100644 --- a/src/utils/asyncForEach.ts +++ b/src/utils/asyncForEach.ts @@ -1,5 +1,5 @@ export async function asyncForEach(array: any[], callback: any) { for (let index = 0; index < array.length; index++) { - await callback(array[index], index, array); + await callback(array[index], index, array) } } diff --git a/src/utils/compareTimestamps.ts b/src/utils/compareTimestamps.ts index b7a5800..301b1f1 100644 --- a/src/utils/compareTimestamps.ts +++ b/src/utils/compareTimestamps.ts @@ -1,9 +1,9 @@ -import { SASjsRequest } from "../types/SASjsRequest"; +import { SASjsRequest } from "../types/SASjsRequest" /** * Comparator for SASjs request timestamps * */ export const compareTimestamps = (a: SASjsRequest, b: SASjsRequest) => { - return b.timestamp.getTime() - a.timestamp.getTime(); -}; + return b.timestamp.getTime() - a.timestamp.getTime() +} diff --git a/src/utils/convertToCsv.ts b/src/utils/convertToCsv.ts index bfe0e01..704fc12 100644 --- a/src/utils/convertToCsv.ts +++ b/src/utils/convertToCsv.ts @@ -3,14 +3,14 @@ * @param data - the JSON object to convert. */ export const convertToCSV = (data: any) => { - const replacer = (key: any, value: any) => (value === null ? "" : value); - const headerFields = Object.keys(data[0]); - let csvTest; - let invalidString = false; + const replacer = (key: any, value: any) => (value === null ? "" : value) + const headerFields = Object.keys(data[0]) + let csvTest + let invalidString = false const headers = headerFields.map((field) => { - let firstFoundType: string | null = null; - let hasMixedTypes: boolean = false; - let rowNumError: number = -1; + let firstFoundType: string | null = null + let hasMixedTypes: boolean = false + let rowNumError: number = -1 const longestValueForField = data .map((row: any, index: number) => { @@ -19,46 +19,46 @@ export const convertToCSV = (data: any) => { let currentFieldType = row[field] === "" || typeof row[field] === "string" ? "chars" - : "number"; + : "number" if (!hasMixedTypes) { - hasMixedTypes = currentFieldType !== firstFoundType; - rowNumError = hasMixedTypes ? index + 1 : -1; + hasMixedTypes = currentFieldType !== firstFoundType + rowNumError = hasMixedTypes ? index + 1 : -1 } } else { if (row[field] === "") { - firstFoundType = "chars"; + firstFoundType = "chars" } else { firstFoundType = - typeof row[field] === "string" ? "chars" : "number"; + typeof row[field] === "string" ? "chars" : "number" } } - let byteSize; + let byteSize if (typeof row[field] === "string") { let doubleQuotesFound = row[field] .split("") - .filter((char: any) => char === '"'); + .filter((char: any) => char === '"') - byteSize = getByteSize(row[field]); + byteSize = getByteSize(row[field]) if (doubleQuotesFound.length > 0) { - byteSize += doubleQuotesFound.length; + byteSize += doubleQuotesFound.length } } - return byteSize; + return byteSize } }) - .sort((a: number, b: number) => b - a)[0]; + .sort((a: number, b: number) => b - a)[0] if (longestValueForField && longestValueForField > 32765) { - invalidString = true; + invalidString = true } if (hasMixedTypes) { console.error( `Row (${rowNumError}), Column (${field}) has mixed types: ERROR` - ); + ) } return `${field}:${firstFoundType === "chars" ? "$" : ""}${ @@ -67,30 +67,30 @@ export const convertToCSV = (data: any) => { : firstFoundType === "chars" ? "1" : "best" - }.`; - }); + }.` + }) if (invalidString) { - return "ERROR: LARGE STRING LENGTH"; + return "ERROR: LARGE STRING LENGTH" } csvTest = data.map((row: any) => { const fields = Object.keys(row).map((fieldName, index) => { - let value; - let containsSpecialChar = false; - const currentCell = row[fieldName]; + let value + let containsSpecialChar = false + const currentCell = row[fieldName] if (JSON.stringify(currentCell).search(/(\\t|\\n|\\r)/gm) > -1) { - value = currentCell.toString(); - containsSpecialChar = true; + value = currentCell.toString() + containsSpecialChar = true } else { - value = JSON.stringify(currentCell, replacer); + value = JSON.stringify(currentCell, replacer) } - value = value.replace(/\\\\/gm, "\\"); + value = value.replace(/\\\\/gm, "\\") if (containsSpecialChar) { if (value.includes(",") || value.includes('"')) { - value = '"' + value + '"'; + value = '"' + value + '"' } } else { if ( @@ -98,36 +98,36 @@ export const convertToCSV = (data: any) => { value.includes('"') && !value.includes('\\"') ) { - value = value.substring(1, value.length - 1); + value = value.substring(1, value.length - 1) } - value = value.replace(/\\"/gm, '""'); + value = value.replace(/\\"/gm, '""') } - value = value.replace(/\r\n/gm, "\n"); + value = value.replace(/\r\n/gm, "\n") if (value === "" && headers[index].includes("best")) { - value = "."; + value = "." } - return value; - }); - return fields.join(","); - }); + return value + }) + return fields.join(",") + }) let finalCSV = - headers.join(",").replace(/,/g, " ") + "\r\n" + csvTest.join("\r\n"); + headers.join(",").replace(/,/g, " ") + "\r\n" + csvTest.join("\r\n") - return finalCSV; -}; + return finalCSV +} const getByteSize = (str: string) => { - let byteSize = str.length; + let byteSize = str.length for (let i = str.length - 1; i >= 0; i--) { - const code = str.charCodeAt(i); - if (code > 0x7f && code <= 0x7ff) byteSize++; - else if (code > 0x7ff && code <= 0xffff) byteSize += 2; - if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate + const code = str.charCodeAt(i) + if (code > 0x7f && code <= 0x7ff) byteSize++ + else if (code > 0x7ff && code <= 0xffff) byteSize += 2 + if (code >= 0xdc00 && code <= 0xdfff) i-- //trail surrogate } - return byteSize; -}; + return byteSize +} diff --git a/src/utils/formatDataForRequest.ts b/src/utils/formatDataForRequest.ts index 4f62737..82c822f 100644 --- a/src/utils/formatDataForRequest.ts +++ b/src/utils/formatDataForRequest.ts @@ -1,33 +1,33 @@ -import { convertToCSV } from "./convertToCsv"; -import { splitChunks } from "./splitChunks"; +import { convertToCSV } from "./convertToCsv" +import { splitChunks } from "./splitChunks" export const formatDataForRequest = (data: any) => { - const sasjsTables = []; - let tableCounter = 0; - const result: any = {}; + const sasjsTables = [] + let tableCounter = 0 + const result: any = {} for (const tableName in data) { - tableCounter++; - sasjsTables.push(tableName); - const csv = convertToCSV(data[tableName]); + tableCounter++ + sasjsTables.push(tableName) + const csv = convertToCSV(data[tableName]) if (csv === "ERROR: LARGE STRING LENGTH") { throw new Error( "The max length of a string value in SASjs is 32765 characters." - ); + ) } // if csv has length more then 16k, send in chunks if (csv.length > 16000) { - const csvChunks = splitChunks(csv); + const csvChunks = splitChunks(csv) // append chunks to form data with same key - result[`sasjs${tableCounter}data0`] = csvChunks.length; + result[`sasjs${tableCounter}data0`] = csvChunks.length csvChunks.forEach((chunk, index) => { - result[`sasjs${tableCounter}data${index + 1}`] = chunk; - }); + result[`sasjs${tableCounter}data${index + 1}`] = chunk + }) } else { - result[`sasjs${tableCounter}data`] = csv; + result[`sasjs${tableCounter}data`] = csv } } - result["sasjs_tables"] = sasjsTables.join(" "); + result["sasjs_tables"] = sasjsTables.join(" ") - return result; -}; + return result +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 4fe6abc..95a3c81 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,15 +1,15 @@ -export * from "./asyncForEach"; -export * from "./compareTimestamps"; -export * from "./convertToCsv"; -export * from "./isAuthorizeFormRequired"; -export * from "./isLoginRequired"; -export * from "./isLoginSuccess"; -export * from "./makeRequest"; -export * from "./needsRetry"; -export * from "./parseAndSubmitAuthorizeForm"; -export * from "./parseGeneratedCode"; -export * from "./parseSourceCode"; -export * from "./parseSasViyaLog"; -export * from "./serialize"; -export * from "./splitChunks"; -export * from "./parseWeboutResponse"; +export * from "./asyncForEach" +export * from "./compareTimestamps" +export * from "./convertToCsv" +export * from "./isAuthorizeFormRequired" +export * from "./isLoginRequired" +export * from "./isLoginSuccess" +export * from "./makeRequest" +export * from "./needsRetry" +export * from "./parseAndSubmitAuthorizeForm" +export * from "./parseGeneratedCode" +export * from "./parseSourceCode" +export * from "./parseSasViyaLog" +export * from "./serialize" +export * from "./splitChunks" +export * from "./parseWeboutResponse" diff --git a/src/utils/isAuthorizeFormRequired.ts b/src/utils/isAuthorizeFormRequired.ts index a1d5b38..089322e 100644 --- a/src/utils/isAuthorizeFormRequired.ts +++ b/src/utils/isAuthorizeFormRequired.ts @@ -1,3 +1,3 @@ export const isAuthorizeFormRequired = (response: string): boolean => { - return //gm.test(response); -}; + return //gm.test(response) +} diff --git a/src/utils/isIeOrEdge.ts b/src/utils/isIeOrEdge.ts index 2a57ac3..36f3dc0 100644 --- a/src/utils/isIeOrEdge.ts +++ b/src/utils/isIeOrEdge.ts @@ -1,34 +1,34 @@ export function isIEorEdgeOrOldFirefox() { if (typeof window === "undefined") { - return false; + return false } - const ua = window.navigator.userAgent; + const ua = window.navigator.userAgent if (ua.indexOf("Firefox") > 0) { const version = parseInt( ua.substring(ua.lastIndexOf("Firefox/") + 8, ua.length), 10 - ); - return version <= 60; + ) + return version <= 60 } - const msie = ua.indexOf("MSIE "); + const msie = ua.indexOf("MSIE ") if (msie > 0) { // IE 10 or older => return version number - return true; + return true } - const trident = ua.indexOf("Trident/"); + const trident = ua.indexOf("Trident/") if (trident > 0) { - return true; + return true } - const edge = ua.indexOf("Edge/"); + const edge = ua.indexOf("Edge/") if (edge > 0) { // Edge (IE 12+) => return version number - return true; + return true } // other browser - return false; + return false } diff --git a/src/utils/isLoginRequired.ts b/src/utils/isLoginRequired.ts index 65448bc..b6d484a 100644 --- a/src/utils/isLoginRequired.ts +++ b/src/utils/isLoginRequired.ts @@ -1,5 +1,5 @@ export const isLogInRequired = (response: string): boolean => { - const pattern: RegExp = //gm; - const matches = pattern.test(response); - return matches; -}; + const pattern: RegExp = //gm + const matches = pattern.test(response) + return matches +} diff --git a/src/utils/isLoginSuccess.ts b/src/utils/isLoginSuccess.ts index 40d46d2..9704b23 100644 --- a/src/utils/isLoginSuccess.ts +++ b/src/utils/isLoginSuccess.ts @@ -1,2 +1,2 @@ export const isLogInSuccess = (response: string): boolean => - /You have signed in/gm.test(response); + /You have signed in/gm.test(response) diff --git a/src/utils/makeRequest.ts b/src/utils/makeRequest.ts index a7c942c..d25abaa 100644 --- a/src/utils/makeRequest.ts +++ b/src/utils/makeRequest.ts @@ -1,8 +1,8 @@ -import { CsrfToken } from "../types"; -import { needsRetry } from "./needsRetry"; +import { CsrfToken } from "../types" +import { needsRetry } from "./needsRetry" -let retryCount: number = 0; -let retryLimit: number = 5; +let retryCount: number = 0 +let retryLimit: number = 5 export async function makeRequest( url: string, @@ -10,98 +10,98 @@ export async function makeRequest( callback: (value: CsrfToken) => any, contentType: "text" | "json" = "json" ): Promise<{ result: T; etag: string | null }> { - let retryRequest: any = null; + let retryRequest: any = null const responseTransform = contentType === "json" ? (res: Response) => res.json() - : (res: Response) => res.text(); - let etag = null; + : (res: Response) => res.text() + let etag = null const result = await fetch(url, request).then(async (response) => { if (response.redirected && response.url.includes("SASLogon/login")) { - return Promise.reject({ status: 401 }); + return Promise.reject({ status: 401 }) } if (!response.ok) { if (response.status === 403) { - const tokenHeader = response.headers.get("X-CSRF-HEADER"); + const tokenHeader = response.headers.get("X-CSRF-HEADER") if (tokenHeader) { - const token = response.headers.get(tokenHeader); + const token = response.headers.get(tokenHeader) callback({ headerName: tokenHeader, value: token || "" - }); + }) retryRequest = { ...request, headers: { ...request.headers, [tokenHeader]: token } - }; + } return fetch(url, retryRequest).then((res) => { - etag = res.headers.get("ETag"); - return responseTransform(res); - }); + etag = res.headers.get("ETag") + return responseTransform(res) + }) } } else { - const body = await response.text(); + const body = await response.text() if (needsRetry(body)) { if (retryCount < retryLimit) { - retryCount++; + retryCount++ let retryResponse = await makeRequest( url, retryRequest || request, callback, contentType - ); - retryCount = 0; + ) + retryCount = 0 - etag = retryResponse.etag; - return retryResponse.result; + etag = retryResponse.etag + return retryResponse.result } else { - retryCount = 0; + retryCount = 0 - throw new Error("Request retry limit exceeded"); + throw new Error("Request retry limit exceeded") } } - return Promise.reject({ status: response.status, body }); + return Promise.reject({ status: response.status, body }) } } else { if (response.status === 204) { - return Promise.resolve(); + return Promise.resolve() } - const responseTransformed = await responseTransform(response); - let responseText = ""; + const responseTransformed = await responseTransform(response) + let responseText = "" if (typeof responseTransformed === "string") { - responseText = responseTransformed; + responseText = responseTransformed } else { - responseText = JSON.stringify(responseTransformed); + responseText = JSON.stringify(responseTransformed) } if (needsRetry(responseText)) { if (retryCount < retryLimit) { - retryCount++; + retryCount++ const retryResponse = await makeRequest( url, retryRequest || request, callback, contentType - ); - retryCount = 0; + ) + retryCount = 0 - etag = retryResponse.etag; - return retryResponse.result; + etag = retryResponse.etag + return retryResponse.result } else { - retryCount = 0; + retryCount = 0 - throw new Error("Request retry limit exceeded"); + throw new Error("Request retry limit exceeded") } } - etag = response.headers.get("ETag"); - return responseTransformed; + etag = response.headers.get("ETag") + return responseTransformed } - }); - return { result, etag }; + }) + return { result, etag } } diff --git a/src/utils/needsRetry.ts b/src/utils/needsRetry.ts index e912573..ab6c102 100644 --- a/src/utils/needsRetry.ts +++ b/src/utils/needsRetry.ts @@ -10,5 +10,5 @@ export const needsRetry = (responseText: string): boolean => { responseText.includes( "Authentication success, retry original request" ))) - ); -}; + ) +} diff --git a/src/utils/parseAndSubmitAuthorizeForm.ts b/src/utils/parseAndSubmitAuthorizeForm.ts index 122f6ee..d65fbac 100644 --- a/src/utils/parseAndSubmitAuthorizeForm.ts +++ b/src/utils/parseAndSubmitAuthorizeForm.ts @@ -2,31 +2,31 @@ export const parseAndSubmitAuthorizeForm = async ( response: string, serverUrl: string ) => { - let authUrl: string | null = null; - const params: any = {}; + let authUrl: string | null = null + const params: any = {} - const responseBody = response.split("")[1].split("")[0]; - const bodyElement = document.createElement("div"); - bodyElement.innerHTML = responseBody; + const responseBody = response.split("")[1].split("")[0] + const bodyElement = document.createElement("div") + bodyElement.innerHTML = responseBody - const form = bodyElement.querySelector("#application_authorization"); - authUrl = form ? serverUrl + form.getAttribute("action") : null; + const form = bodyElement.querySelector("#application_authorization") + authUrl = form ? serverUrl + form.getAttribute("action") : null - const inputs: any = form?.querySelectorAll("input"); + const inputs: any = form?.querySelectorAll("input") for (const input of inputs) { if (input.name === "user_oauth_approval") { - input.value = "true"; + input.value = "true" } - params[input.name] = input.value; + params[input.name] = input.value } - const formData = new FormData(); + const formData = new FormData() for (const key in params) { if (params.hasOwnProperty(key)) { - formData.append(key, params[key]); + formData.append(key, params[key]) } } @@ -40,10 +40,10 @@ export const parseAndSubmitAuthorizeForm = async ( }) .then((res) => res.text()) .then((res) => { - resolve(res); - }); + resolve(res) + }) } else { - reject("Auth form url is null"); + reject("Auth form url is null") } - }); -}; + }) +} diff --git a/src/utils/parseGeneratedCode.ts b/src/utils/parseGeneratedCode.ts index 74acce8..852b99b 100644 --- a/src/utils/parseGeneratedCode.ts +++ b/src/utils/parseGeneratedCode.ts @@ -1,7 +1,7 @@ export const parseGeneratedCode = (log: string) => { - const startsWith = "MPRINT"; + const startsWith = "MPRINT" const isGeneratedCodeLine = (line: string) => - line.trim().startsWith(startsWith); - const logLines = log.split("\n").filter(isGeneratedCodeLine); - return logLines.join("\r\n"); -}; + line.trim().startsWith(startsWith) + const logLines = log.split("\n").filter(isGeneratedCodeLine) + return logLines.join("\r\n") +} diff --git a/src/utils/parseSasViyaLog.ts b/src/utils/parseSasViyaLog.ts index 2a29d78..0727e63 100644 --- a/src/utils/parseSasViyaLog.ts +++ b/src/utils/parseSasViyaLog.ts @@ -1,12 +1,12 @@ export const parseSasViyaLog = (logResponse: { items: any[] }) => { - let log; + let log try { log = logResponse.items ? logResponse.items.map((i) => i.line).join("\n") - : JSON.stringify(logResponse); + : JSON.stringify(logResponse) } catch (e) { - console.error("An error has occurred while parsing the log response", e); - log = logResponse; + console.error("An error has occurred while parsing the log response", e) + log = logResponse } - return log; -}; + return log +} diff --git a/src/utils/parseSourceCode.ts b/src/utils/parseSourceCode.ts index d5b8ac6..c099333 100644 --- a/src/utils/parseSourceCode.ts +++ b/src/utils/parseSourceCode.ts @@ -1,6 +1,6 @@ export const parseSourceCode = (log: string): string => { const isSourceCodeLine = (line: string) => - line.trim().substring(0, 10).trimStart().match(/^\d/); - const logLines = log.split("\n").filter(isSourceCodeLine); - return logLines.join("\r\n"); -}; + line.trim().substring(0, 10).trimStart().match(/^\d/) + const logLines = log.split("\n").filter(isSourceCodeLine) + return logLines.join("\r\n") +} diff --git a/src/utils/parseWeboutResponse.ts b/src/utils/parseWeboutResponse.ts index 5adb178..2deae67 100644 --- a/src/utils/parseWeboutResponse.ts +++ b/src/utils/parseWeboutResponse.ts @@ -1,16 +1,16 @@ export const parseWeboutResponse = (response: string) => { - let sasResponse = ""; + let sasResponse = "" if (response.includes(">>weboutBEGIN<<")) { try { sasResponse = response .split(">>weboutBEGIN<<")[1] - .split(">>weboutEND<<")[0]; + .split(">>weboutEND<<")[0] } catch (e) { - sasResponse = ""; - console.error(e); + sasResponse = "" + console.error(e) } } - return sasResponse; -}; + return sasResponse +} diff --git a/src/utils/serialize.ts b/src/utils/serialize.ts index 4816a5a..fcb9783 100644 --- a/src/utils/serialize.ts +++ b/src/utils/serialize.ts @@ -1,15 +1,15 @@ export const serialize = (obj: any) => { - const str: any[] = []; + const str: any[] = [] for (const p in obj) { if (obj.hasOwnProperty(p)) { if (obj[p] instanceof Array) { for (let i = 0, n = obj[p].length; i < n; i++) { - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p][i])); + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p][i])) } } else { - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])) } } } - return str.join("&"); -}; + return str.join("&") +} diff --git a/src/utils/splitChunks.ts b/src/utils/splitChunks.ts index fa43fbc..9bdb578 100644 --- a/src/utils/splitChunks.ts +++ b/src/utils/splitChunks.ts @@ -1,12 +1,12 @@ export const splitChunks = (content: string) => { - const size = 16000; + const size = 16000 - const numChunks = Math.ceil(content.length / size); - const chunks = new Array(numChunks); + const numChunks = Math.ceil(content.length / size) + const chunks = new Array(numChunks) for (let i = 0, o = 0; i < numChunks; ++i, o += size) { - chunks[i] = content.substr(o, size); + chunks[i] = content.substr(o, size) } - return chunks; -}; + return chunks +}