diff --git a/.prettierrc b/.prettierrc index 9954b43..c50384f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,5 +2,5 @@ "trailingComma": "none", "tabWidth": 2, "semi": false, - "singleQuote": false + "singleQuote": true } diff --git a/src/FileUploader.ts b/src/FileUploader.ts index 3e509fe..8bd62be 100644 --- a/src/FileUploader.ts +++ b/src/FileUploader.ts @@ -1,6 +1,6 @@ -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 @@ -15,9 +15,9 @@ export class FileUploader { 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)) { @@ -26,41 +26,41 @@ export class FileUploader { } const program = this.appLoc - ? this.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "") + ? this.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '') : sasJob const uploadUrl = `${this.serverUrl}${this.jobsPath}/?${ - "_program=" + program + '_program=' + program }${paramsString}` const headers = { - "cache-control": "no-cache" + 'cache-control': 'no-cache' } return new Promise((resolve, reject) => { 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", + method: 'POST', body: formData, - referrerPolicy: "same-origin", + referrerPolicy: 'same-origin', headers }) .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) this.csrfToken = { headerName: tokenHeader, - value: token || "" + value: token || '' } this.setCsrfTokenWeb(this.csrfToken) @@ -72,7 +72,7 @@ export class FileUploader { }) .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) { diff --git a/src/SAS9ApiClient.ts b/src/SAS9ApiClient.ts index e5d4f99..010d63a 100644 --- a/src/SAS9ApiClient.ts +++ b/src/SAS9ApiClient.ts @@ -33,11 +33,11 @@ export class SAS9ApiClient { serverName: string, repositoryName: string ) { - const requestPayload = linesOfCode.join("\n") + const requestPayload = linesOfCode.join('\n') const executeScriptRequest = { - method: "PUT", + method: 'PUT', headers: { - Accept: "application/json" + Accept: 'application/json' }, body: `command=${requestPayload}` } diff --git a/src/SASViyaApiClient.ts b/src/SASViyaApiClient.ts index 3d0113c..226fb8c 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,7 +24,7 @@ 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 @@ -73,7 +73,7 @@ export class SASViyaApiClient { */ public async getAllContexts(accessToken?: string) { const headers: any = { - "Content-Type": "application/json" + 'Content-Type': 'application/json' } if (accessToken) { headers.Authorization = `Bearer ${accessToken}` @@ -98,7 +98,7 @@ export class SASViyaApiClient { */ public async getExecutableContexts(accessToken?: string) { const headers: any = { - "Content-Type": "application/json" + 'Content-Type': 'application/json' } if (accessToken) { headers.Authorization = `Bearer ${accessToken}` @@ -112,7 +112,7 @@ export class SASViyaApiClient { const executableContexts: any[] = [] const promises = contextsList.map((context: any) => { - const linesOfCode = ["%put &=sysuserid;"] + const linesOfCode = ['%put &=sysuserid;'] return this.executeScript( `test-${context.name}`, linesOfCode, @@ -122,14 +122,14 @@ export class SASViyaApiClient { }) const results = await Promise.all(promises) results.forEach((result: any, index: number) => { - if (result && result.jobStatus === "completed") { - let sysUserId = "" + if (result && result.jobStatus === 'completed') { + let sysUserId = '' if (result && result.log && result.log.items) { const sysUserIdLog = result.log.items.find((i: any) => - i.line.startsWith("SYSUSERID=") + i.line.startsWith('SYSUSERID=') ) if (sysUserIdLog) { - sysUserId = sysUserIdLog.line.replace("SYSUSERID=", "") + sysUserId = sysUserIdLog.line.replace('SYSUSERID=', '') } } @@ -155,7 +155,7 @@ export class SASViyaApiClient { */ public async createSession(contextName: string, accessToken?: string) { const headers: any = { - "Content-Type": "application/json" + 'Content-Type': 'application/json' } if (accessToken) { @@ -175,10 +175,10 @@ export class SASViyaApiClient { } const createSessionRequest = { - method: "POST", + method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, - "Content-Type": "application/json" + 'Content-Type': 'application/json' } } const { result: createdSession } = await this.request( @@ -210,7 +210,7 @@ export class SASViyaApiClient { silent = !debug try { const headers: any = { - "Content-Type": "application/json" + 'Content-Type': 'application/json' } if (accessToken) { @@ -231,26 +231,26 @@ export class SASViyaApiClient { } 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 + jobName.includes('/') ? jobName.split('/')[1] : jobName }` let jobVariables: any = { - SYS_JES_JOB_URI: "", - _program: this.rootFolderName + "/" + jobName + SYS_JES_JOB_URI: '', + _program: this.rootFolderName + '/' + jobName } let files: any[] = [] if (data) { - if (JSON.stringify(data).includes(";")) { + if (JSON.stringify(data).includes(';')) { files = await this.uploadTables(data, accessToken) - jobVariables["_webin_file_count"] = files.length + jobVariables['_webin_file_count'] = files.length files.forEach((fileInfo, index) => { jobVariables[ `_webin_fileuri${index + 1}` @@ -264,11 +264,11 @@ export class SASViyaApiClient { // Execute job in session const postJobRequest = { - method: "POST", + method: 'POST', headers, body: JSON.stringify({ name: fileName, - description: "Powered by SASjs", + description: 'Powered by SASjs', code: linesOfCode, variables: jobVariables, arguments: jobArguments @@ -284,7 +284,7 @@ export class SASViyaApiClient { 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 + postedJob.links.find((l: any) => l.rel === 'state')!.href }` ) } @@ -303,7 +303,7 @@ export class SASViyaApiClient { 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( @@ -312,11 +312,11 @@ export class SASViyaApiClient { headers } ).then((res: any) => - res.result.items.map((i: any) => i.line).join("\n") + res.result.items.map((i: any) => i.line).join('\n') ) } - if (jobStatus === "failed" || jobStatus === "error") { + if (jobStatus === 'failed' || jobStatus === 'error') { return Promise.reject({ error: currentJob.error, log: log }) } const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content` @@ -325,7 +325,7 @@ export class SASViyaApiClient { jobResult = await this.request( `${this.serverUrl}${resultLink}`, { headers }, - "text" + 'text' ).catch((e) => ({ result: JSON.stringify(e) })) @@ -367,7 +367,7 @@ 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) { @@ -377,11 +377,11 @@ export class SASViyaApiClient { const newParentFolderPath = parentFolderPath.substring( 0, - parentFolderPath.lastIndexOf("/") + parentFolderPath.lastIndexOf('/') ) - const newFolderName = `${parentFolderPath.split("/").pop()}` - if (newParentFolderPath === "") { - throw new Error("Root Folder should have been present on server") + const newFolderName = `${parentFolderPath.split('/').pop()}` + if (newParentFolderPath === '') { + throw new Error('Root Folder should have been present on server') } console.log( `Creating Parent Folder:\n${newFolderName} in ${newParentFolderPath}` @@ -398,14 +398,14 @@ export class SASViyaApiClient { } const createFolderRequest: RequestInit = { - method: "POST", + method: 'POST', body: JSON.stringify({ name: folderName, - type: "folder" + type: 'folder' }) } - createFolderRequest.headers = { "Content-Type": "application/json" } + createFolderRequest.headers = { 'Content-Type': 'application/json' } if (accessToken) { createFolderRequest.headers.Authorization = `Bearer ${accessToken}` } @@ -437,7 +437,7 @@ export class SASViyaApiClient { ) { if (!parentFolderPath && !parentFolderUri) { throw new Error( - "Either parentFolderPath or parentFolderUri must be provided" + 'Either parentFolderPath or parentFolderUri must be provided' ) } @@ -446,21 +446,21 @@ export class SASViyaApiClient { } const createJobDefinitionRequest: RequestInit = { - method: "POST", + method: 'POST', headers: { - "Content-Type": "application/vnd.sas.job.definition+json", - Accept: "application/vnd.sas.job.definition+json" + 'Content-Type': 'application/vnd.sas.job.definition+json', + Accept: 'application/vnd.sas.job.definition+json' }, body: JSON.stringify({ name: jobName, parameters: [ { - name: "_addjesbeginendmacros", - type: "CHARACTER", - defaultValue: "false" + name: '_addjesbeginendmacros', + type: 'CHARACTER', + defaultValue: 'false' } ], - type: "Compute", + type: 'Compute', code }) } @@ -486,12 +486,12 @@ export class SASViyaApiClient { const authUrl = `${this.serverUrl}/SASLogon/oauth/authorize?client_id=${clientId}&response_type=code` const authCode = await fetch(authUrl, { - referrerPolicy: "same-origin", - credentials: "include" + referrerPolicy: 'same-origin', + credentials: 'include' }) .then((response) => response.text()) .then(async (response) => { - let code = "" + let code = '' if (isAuthorizeFormRequired(response)) { const formResponse: any = await parseAndSubmitAuthorizeForm( response, @@ -499,21 +499,21 @@ export class SASViyaApiClient { ) const responseBody = formResponse - .split("")[1] - .split("")[0] - const bodyElement: any = document.createElement("div") + .split('')[1] + .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 } else { - const responseBody = response.split("")[1].split("")[0] - const bodyElement: any = document.createElement("div") + 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 @@ -535,34 +535,34 @@ export class SASViyaApiClient { clientSecret: string, authCode: string ) { - const url = this.serverUrl + "/SASLogon/oauth/token" + const url = this.serverUrl + '/SASLogon/oauth/token' let token - if (typeof Buffer === "undefined") { - token = btoa(clientId + ":" + clientSecret) + if (typeof Buffer === 'undefined') { + token = btoa(clientId + ':' + clientSecret) } else { - token = Buffer.from(clientId + ":" + clientSecret).toString("base64") + token = Buffer.from(clientId + ':' + clientSecret).toString('base64') } const headers = { - Authorization: "Basic " + token + Authorization: 'Basic ' + token } let formData - if (typeof FormData === "undefined") { + if (typeof FormData === 'undefined') { formData = new NodeFormData() - formData.append("grant_type", "authorization_code") - formData.append("code", authCode) + 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.append('grant_type', 'authorization_code') + formData.append('code', authCode) } const authResponse = await fetch(url, { - method: "POST", - credentials: "include", + method: 'POST', + credentials: 'include', headers, body: formData as any, - referrerPolicy: "same-origin" + referrerPolicy: 'same-origin' }).then((res) => res.json()) return authResponse @@ -579,34 +579,34 @@ export class SASViyaApiClient { clientSecret: string, refreshToken: string ) { - const url = this.serverUrl + "/SASLogon/oauth/token" + const url = this.serverUrl + '/SASLogon/oauth/token' let token - if (typeof Buffer === "undefined") { - token = btoa(clientId + ":" + clientSecret) + if (typeof Buffer === 'undefined') { + token = btoa(clientId + ':' + clientSecret) } else { - token = Buffer.from(clientId + ":" + clientSecret).toString("base64") + token = Buffer.from(clientId + ':' + clientSecret).toString('base64') } const headers = { - Authorization: "Basic " + token + Authorization: 'Basic ' + token } let formData - if (typeof FormData === "undefined") { + if (typeof FormData === 'undefined') { formData = new NodeFormData() - formData.append("grant_type", "refresh_token") - formData.append("refresh_token", refreshToken) + 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.append('grant_type', 'refresh_token') + formData.append('refresh_token', refreshToken) } const authResponse = await fetch(url, { - method: "POST", - credentials: "include", + method: 'POST', + credentials: 'include', headers, body: formData as any, - referrerPolicy: "same-origin" + referrerPolicy: 'same-origin' }).then((res) => res.json()) return authResponse @@ -624,8 +624,8 @@ export class SASViyaApiClient { headers.Authorization = `Bearer ${accessToken}` } const deleteResponse = await this.request(url, { - method: "DELETE", - credentials: "include", + method: 'DELETE', + credentials: 'include', headers }) @@ -651,8 +651,8 @@ export class SASViyaApiClient { await this.populateRootFolder(accessToken) } if (!this.rootFolder) { - console.error("Root folder was not found") - throw new Error("Root folder was not found") + console.error('Root folder was not found') + throw new Error('Root folder was not found') } if (!this.rootFolderMap.size) { await this.populateRootFolderMap(accessToken) @@ -664,27 +664,27 @@ export class SASViyaApiClient { ) } - const headers: any = { "Content-Type": "application/json" } + const headers: any = { 'Content-Type': 'application/json' } if (!!accessToken) { headers.Authorization = `Bearer ${accessToken}` } - const folderName = sasJob.split("/")[0] - const jobName = sasJob.split("/")[1] + 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 if (!code) { const jobDefinitionLink = jobToExecute?.links.find( - (l) => l.rel === "getResource" + (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}`, @@ -696,7 +696,7 @@ export class SASViyaApiClient { // Add code to existing job definition 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, @@ -728,7 +728,7 @@ export class SASViyaApiClient { } 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) @@ -745,17 +745,17 @@ export class SASViyaApiClient { } const jobName = path.basename(sasJob) - const jobFolder = sasJob.replace(`/${jobName}`, "") - const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace("/", "")) + const jobFolder = sasJob.replace(`/${jobName}`, '') + const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace('/', '')) if (allJobsInFolder) { const jobSpec = allJobsInFolder.find((j: Job) => j.name === jobName) const jobDefinitionLink = jobSpec?.links.find( - (l) => l.rel === "getResource" + (l) => l.rel === 'getResource' )?.href const requestInfo: any = { - method: "GET" + method: 'GET' } - const headers: any = { "Content-Type": "application/json" } + const headers: any = { 'Content-Type': 'application/json' } if (!!accessToken) { headers.Authorization = `Bearer ${accessToken}` } @@ -777,9 +777,9 @@ export class SASViyaApiClient { } if (debug) { - jobArguments["_OMITTEXTLOG"] = "false" - jobArguments["_OMITSESSIONRESULTS"] = "false" - jobArguments["_DEBUG"] = 131 + jobArguments['_OMITTEXTLOG'] = 'false' + jobArguments['_OMITSESSIONRESULTS'] = 'false' + jobArguments['_DEBUG'] = 131 } files.forEach((fileInfo, index) => { @@ -790,11 +790,11 @@ export class SASViyaApiClient { }) const postJobRequest = { - method: "POST", + method: 'POST', headers, body: JSON.stringify({ name: `exec-${jobName}`, - description: "Powered by SASjs", + description: 'Powered by SASjs', jobDefinition, arguments: jobArguments }) @@ -815,16 +815,16 @@ export class SASViyaApiClient { ) let jobResult, log - if (jobStatus === "failed") { + if (jobStatus === 'failed') { 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" + 'text' ) } if (debug && logLink) { @@ -834,7 +834,7 @@ export class SASViyaApiClient { headers } ).then((res: any) => - res.result.items.map((i: any) => i.line).join("\n") + res.result.items.map((i: any) => i.line).join('\n') ) } return { result: jobResult?.result, log } @@ -847,9 +847,9 @@ export class SASViyaApiClient { private async populateRootFolderMap(accessToken?: string) { const allItems = new Map() - const url = "/folders/folders/@item?path=" + this.rootFolderName + const url = '/folders/folders/@item?path=' + this.rootFolderName const requestInfo: any = { - method: "GET" + method: 'GET' } if (accessToken) { requestInfo.headers = { Authorization: `Bearer ${accessToken}` } @@ -859,7 +859,7 @@ export class SASViyaApiClient { 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`, @@ -867,14 +867,14 @@ export class SASViyaApiClient { ) const itemsAtRoot = members.items - allItems.set("", itemsAtRoot) + allItems.set('', itemsAtRoot) const subfolderRequests = members.items - .filter((i: any) => i.contentType === "folder") + .filter((i: any) => i.contentType === 'folder') .map(async (member: any) => { const subFolderUrl = - "/folders/folders/@item?path=" + + '/folders/folders/@item?path=' + this.rootFolderName + - "/" + + '/' + member.name const { result: memberDetail } = await this.request( `${this.serverUrl}${subFolderUrl}`, @@ -882,7 +882,7 @@ export class SASViyaApiClient { ) const membersLink = memberDetail.links.find( - (l: any) => l.rel === "members" + (l: any) => l.rel === 'members' ) const { result: memberContents } = await this.request<{ items: any[] }>( @@ -899,9 +899,9 @@ export class SASViyaApiClient { } 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" + method: 'GET' } if (accessToken) { requestInfo.headers = { Authorization: `Bearer ${accessToken}` } @@ -926,18 +926,18 @@ export class SASViyaApiClient { ) { const MAX_POLL_COUNT = 1000 const POLL_INTERVAL = 100 - let postedJobState = "" + let postedJobState = '' let pollCount = 0 const headers: any = { - "Content-Type": "application/json", - "If-None-Match": etag + 'Content-Type': 'application/json', + 'If-None-Match': etag } if (accessToken) { headers.Authorization = `Bearer ${accessToken}` } - const stateLink = postedJob.links.find((l: any) => l.rel === "state") + 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( @@ -945,31 +945,31 @@ export class SASViyaApiClient { { headers }, - "text" + 'text' ) const currentState = state.trim() - if (currentState === "completed") { + if (currentState === 'completed') { return Promise.resolve(currentState) } return new Promise(async (resolve, _) => { const interval = setInterval(async () => { if ( - postedJobState === "running" || - postedJobState === "" || - postedJobState === "pending" + postedJobState === 'running' || + postedJobState === '' || + postedJobState === 'pending' ) { 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`, { headers }, - "text" + 'text' ) postedJobState = jobState.trim() @@ -998,25 +998,25 @@ export class SASViyaApiClient { let sessionState = session.state let pollCount = 0 const headers: any = { - "Content-Type": "application/json", - "If-None-Match": etag + 'Content-Type': 'application/json', + 'If-None-Match': etag } if (accessToken) { headers.Authorization = `Bearer ${accessToken}` } - 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 (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`, { headers }, - "text" + 'text' ) sessionState = state.trim() @@ -1035,7 +1035,7 @@ export class SASViyaApiClient { private async uploadTables(data: any, accessToken?: string) { const uploadedFiles = [] const headers: any = { - "Content-Type": "application/json" + 'Content-Type': 'application/json' } if (accessToken) { headers.Authorization = `Bearer ${accessToken}` @@ -1043,14 +1043,14 @@ export class SASViyaApiClient { for (const tableName in data) { const csv = convertToCSV(data[tableName]) - if (csv === "ERROR: LARGE STRING LENGTH") { + if (csv === 'ERROR: LARGE STRING LENGTH') { throw new Error( - "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 createFileRequest = { - method: "POST", + method: 'POST', body: csv, headers } @@ -1066,9 +1066,9 @@ export class SASViyaApiClient { } 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" + method: 'GET' } if (accessToken) { requestInfo.headers = { Authorization: `Bearer ${accessToken}` } @@ -1092,7 +1092,7 @@ export class SASViyaApiClient { private async request( url: string, options: RequestInit, - contentType: "text" | "json" = "json" + contentType: 'text' | 'json' = 'json' ) { if (this.csrfToken) { options.headers = { diff --git a/src/SASjs.spec.ts b/src/SASjs.spec.ts index 10820b5..99c58fd 100644 --- a/src/SASjs.spec.ts +++ b/src/SASjs.spec.ts @@ -1,34 +1,34 @@ -import SASjs from "./index" +import SASjs from './index' const adapter = new SASjs() -it("should parse SAS9 source code", async (done) => { +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") + 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() + 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) => { +it('should parse generated code', async (done) => { expect(sampleResponse).toBeTruthy() const parsedGeneratedCode = (adapter as any).parseGeneratedCode( sampleResponse ) expect(parsedGeneratedCode).toBeTruthy() - const generatedCodeLines = parsedGeneratedCode.split("\r\n") + 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() + 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() }) diff --git a/src/SASjs.ts b/src/SASjs.ts index b238f0b..02eecc6 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -1,5 +1,5 @@ -import { isIEorEdgeOrOldFirefox } from "./utils/isIeOrEdge" -import * as e6p from "es6-promise" +import { isIEorEdgeOrOldFirefox } from './utils/isIeOrEdge' +import * as e6p from 'es6-promise' ;(e6p as any).polyfill() if (isIEorEdgeOrOldFirefox()) { if (window) { @@ -7,7 +7,7 @@ if (isIEorEdgeOrOldFirefox()) { } } // 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,19 +30,19 @@ 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: "", - pathSAS9: "/SASStoredProcess/do", - pathSASViya: "/SASJobExecution", - appLoc: "/Public/seedapp", + serverUrl: '', + pathSAS9: '/SASStoredProcess/do', + pathSASViya: '/SASJobExecution', + appLoc: '/Public/seedapp', serverType: ServerType.SASViya, debug: true, - contextName: "SAS Job Execution compute context", + contextName: 'SAS Job Execution compute context', useComputeApi: false } @@ -54,9 +54,9 @@ const requestRetryLimit = 5 */ export default class SASjs { private sasjsConfig: SASjsConfig = new SASjsConfig() - private jobsPath: string = "" - private logoutUrl: string = "" - private loginUrl: string = "" + private jobsPath: string = '' + private logoutUrl: string = '' + private loginUrl: string = '' private csrfTokenApi: CsrfToken | null = null private csrfTokenWeb: CsrfToken | null = null private retryCountWeb: number = 0 @@ -64,7 +64,7 @@ export default class SASjs { private retryCountJeseApi: number = 0 private sasjsRequests: SASjsRequest[] = [] private sasjsWaitingRequests: SASjsWaitingRequest[] = [] - private userName: string = "" + private userName: string = '' private sasViyaApiClient: SASViyaApiClient | null = null private sas9ApiClient: SAS9ApiClient | null = null private fileUploader: FileUploader | null = null @@ -84,7 +84,7 @@ 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, @@ -95,21 +95,21 @@ export default class SASjs { 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) } 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) } 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) } @@ -119,11 +119,11 @@ export default class SASjs { linesOfCode: string[], contextName: string, accessToken?: string, - sessionId = "", + sessionId = '', 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, @@ -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( @@ -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( @@ -189,7 +189,7 @@ export default class SASjs { 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) } @@ -200,7 +200,7 @@ 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, @@ -215,7 +215,7 @@ 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, @@ -226,7 +226,7 @@ export default class SASjs { 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) } @@ -289,7 +289,7 @@ 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 loginResponse = await fetch(this.loginUrl.replace('.do', '')) const responseText = await loginResponse.text() const isLoggedIn = / response.text()) @@ -438,7 +438,7 @@ export default class SASjs { ...config } - sasJob = sasJob.startsWith("/") ? sasJob.replace("/", "") : sasJob + sasJob = sasJob.startsWith('/') ? sasJob.replace('/', '') : sasJob if (config.serverType === ServerType.SASViya && config.contextName) { if (config.useComputeApi) { @@ -492,7 +492,7 @@ 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 @@ -527,12 +527,12 @@ export default class SASjs { // 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 + member.type === 'folder' ? -1 : 1 ) } const members = - serviceJson.members[0].name === "services" + serviceJson.members[0].name === 'services' ? serviceJson.members[0].members : serviceJson.members @@ -606,7 +606,7 @@ export default class SASjs { resolve(retryResponse) } else { this.retryCountComputeApi = 0 - reject({ MESSAGE: "Compute API retry requests limit reached" }) + reject({ MESSAGE: 'Compute API retry requests limit reached' }) } } @@ -617,7 +617,7 @@ export default class SASjs { 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) @@ -699,11 +699,11 @@ export default class SASjs { resolve(retryResponse) } else { this.retryCountJeseApi = 0 - reject({ MESSAGE: "Jes API retry requests limit reached" }) + reject({ MESSAGE: 'Jes API retry requests limit reached' }) } } - reject({ MESSAGE: (e && e.message) || "Job execution failed" }) + reject({ MESSAGE: (e && e.message) || 'Job execution failed' }) }) ) } @@ -728,14 +728,14 @@ export default class SASjs { data } const program = config.appLoc - ? config.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "") + ? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '') : 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 + ? '__program=' + program + '&_job=' + jobUri + : '_program=' + program }` const requestParams = { @@ -745,14 +745,14 @@ export default class SASjs { const formData = new FormData() let isError = false - let errorMsg = "" + let errorMsg = '' if (data) { const stringifiedData = JSON.stringify(data) if ( config.serverType === ServerType.SAS9 || stringifiedData.length > 500000 || - stringifiedData.includes(";") + stringifiedData.includes(';') ) { // file upload approach for (const tableName in data) { @@ -761,14 +761,14 @@ export default class SASjs { } const name = tableName const csv = convertToCSV(data[tableName]) - if (csv === "ERROR: LARGE STRING LENGTH") { + if (csv === 'ERROR: LARGE STRING LENGTH') { 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" + type: 'application/csv' }) formData.append(name, file, `${name}.csv`) @@ -784,10 +784,10 @@ export default class SASjs { tableCounter++ sasjsTables.push(tableName) const csv = convertToCSV(data[tableName]) - if (csv === "ERROR: LARGE STRING LENGTH") { + if (csv === 'ERROR: LARGE STRING LENGTH') { 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) { @@ -800,7 +800,7 @@ export default class SASjs { requestParams[`sasjs${tableCounter}data`] = csv } } - requestParams["sasjs_tables"] = sasjsTables.join(" ") + requestParams['sasjs_tables'] = sasjsTables.join(' ') } } @@ -822,21 +822,21 @@ export default class SASjs { headers[this.csrfTokenWeb.headerName] = this.csrfTokenWeb.value } fetch(apiUrl, { - method: "POST", + method: 'POST', body: formData, - referrerPolicy: "same-origin", + referrerPolicy: 'same-origin', headers }) .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) this.csrfTokenWeb = { headerName: tokenHeader, - value: token || "" + value: token || '' } } } @@ -878,7 +878,7 @@ export default class SASjs { this.updateUsername(responseText) const jsonResponseText = parseWeboutResponse(responseText) - if (jsonResponseText !== "") { + if (jsonResponseText !== '') { resolve(JSON.parse(jsonResponseText)) } else { reject({ @@ -954,14 +954,14 @@ export default class SASjs { 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 @@ -971,12 +971,12 @@ export default class SASjs { try { 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 = '' } } @@ -994,24 +994,24 @@ export default class SASjs { resolve(resText) }) } else { - reject("No debug info in response") + reject('No debug info in response') } }) } private async getJobUri(sasJob: string) { - if (!this.sasViyaApiClient) return "" + if (!this.sasViyaApiClient) return '' const jobMap: any = await this.sasViyaApiClient.getAppLocMap() - let uri = "" + 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) if (locJobs) { const job = locJobs.find( - (el: any) => el.name === jobName && el.contentType === "jobDefinition" + (el: any) => el.name === jobName && el.contentType === 'jobDefinition' ) if (job) { uri = job.uri @@ -1022,14 +1022,14 @@ export default class SASjs { } private parseSAS9ErrorResponse(response: string) { - const logLines = response.split("\n") + const logLines = response.split('\n') const parsedLines: string[] = [] let firstErrorLineIndex: number = -1 logLines.map((line: string, index: number) => { if ( - line.toLowerCase().includes("error") && - !line.toLowerCase().includes("this request completed with errors.") && + line.toLowerCase().includes('error') && + !line.toLowerCase().includes('this request completed with errors.') && firstErrorLineIndex === -1 ) { firstErrorLineIndex = index @@ -1040,7 +1040,7 @@ export default class SASjs { parsedLines.push(logLines[i]) } - return parsedLines.join(", ") + return parsedLines.join(', ') } private parseLogFromResponse(response: any, program: string) { @@ -1058,7 +1058,7 @@ export default class SASjs { private fetchLogFileContent(logLink: string) { return new Promise((resolve, reject) => { fetch(logLink, { - method: "GET" + method: 'GET' }) .then((response: any) => response.text()) .then((response: any) => resolve(response)) @@ -1071,8 +1071,8 @@ export default class SASjs { program: string, pgmData: any ) { - let sourceCode = "" - let generatedCode = "" + let sourceCode = '' + let generatedCode = '' let sasWork = null if (response && response.result && response.log) { @@ -1154,7 +1154,7 @@ export default class SASjs { private setupConfiguration() { if ( this.sasjsConfig.serverUrl === undefined || - this.sasjsConfig.serverUrl === "" + this.sasjsConfig.serverUrl === '' ) { let url = `${location.protocol}//${location.hostname}` if (location.port) { @@ -1163,7 +1163,7 @@ export default class SASjs { this.sasjsConfig.serverUrl = url } - if (this.sasjsConfig.serverUrl.slice(-1) === "/") { + if (this.sasjsConfig.serverUrl.slice(-1) === '/') { this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1) } @@ -1174,8 +1174,8 @@ export default class SASjs { this.loginUrl = `${this.sasjsConfig.serverUrl}/SASLogon/login` this.logoutUrl = this.sasjsConfig.serverType === ServerType.SAS9 - ? "/SASLogon/logout?" - : "/SASLogon/logout.do?" + ? '/SASLogon/logout?' + : '/SASLogon/logout.do?' if (this.sasjsConfig.serverType === ServerType.SASViya) { if (this.sasViyaApiClient) @@ -1206,8 +1206,8 @@ export default class SASjs { } private setLoginUrl = (matches: RegExpExecArray) => { - let parsedURL = matches[1].replace(/\?.*/, "") - if (parsedURL[0] === "/") { + let parsedURL = matches[1].replace(/\?.*/, '') + if (parsedURL[0] === '/') { parsedURL = parsedURL.substr(1) const tempLoginLink = this.sasjsConfig.serverUrl @@ -1219,7 +1219,7 @@ export default class SASjs { this.loginUrl = this.sasjsConfig.serverType === ServerType.SASViya ? tempLoginLink - : loginUrl.replace(".do", "") + : loginUrl.replace('.do', '') } } @@ -1251,7 +1251,7 @@ export default class SASjs { ) { await asyncForEach(membersJson, async (member: any) => { switch (member.type) { - case "folder": + case 'folder': await this.createFolder( member.name, parentFolder, @@ -1260,7 +1260,7 @@ export default class SASjs { sasApiClient ) break - case "service": + case 'service': await this.createJobDefinition( member.name, member.code, @@ -1273,7 +1273,7 @@ export default class SASjs { default: throw new Error(`Unidenitied member present in Json: ${member.name}`) } - if (member.type === "folder" && member.members && member.members.length) + if (member.type === 'folder' && member.members && member.members.length) await this.createFoldersAndServices( `${parentFolder}/${member.name}`, member.members, diff --git a/src/SessionManager.ts b/src/SessionManager.ts index 63c0b94..5a782c2 100644 --- a/src/SessionManager.ts +++ b/src/SessionManager.ts @@ -1,5 +1,5 @@ -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 @@ -32,7 +32,7 @@ export class SessionManager { async clearSession(id: string, accessToken?: string) { const deleteSessionRequest = { - method: "DELETE", + method: 'DELETE', headers: this.getHeaders(accessToken) } return await this.request( @@ -57,7 +57,7 @@ export class SessionManager { private async createAndWaitForSession(accessToken?: string) { const createSessionRequest = { - method: "POST", + method: 'POST', headers: this.getHeaders(accessToken) } const { result: createdSession, etag } = await this.request( @@ -99,7 +99,7 @@ export class SessionManager { private getHeaders(accessToken?: string) { const headers: any = { - "Content-Type": "application/json" + 'Content-Type': 'application/json' } if (accessToken) { headers.Authorization = `Bearer ${accessToken}` @@ -117,21 +117,21 @@ export class SessionManager { let sessionState = session.state const headers: any = { ...this.getHeaders(accessToken), - "If-None-Match": etag + '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 (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`, { headers }, - "text" + 'text' ) sessionState = state.trim() @@ -149,7 +149,7 @@ export class SessionManager { private async request( url: string, options: RequestInit, - contentType: "text" | "json" = "json" + contentType: 'text' | 'json' = 'json' ) { if (this.csrfToken) { options.headers = { diff --git a/src/index.ts b/src/index.ts index d646759..709febe 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" +import SASjs from './SASjs' +export * from './types' +export * from './SASViyaApiClient' +export * from './SAS9ApiClient' export default SASjs diff --git a/src/types/Folder.ts b/src/types/Folder.ts index 109b50c..4e7ec02 100644 --- a/src/types/Folder.ts +++ b/src/types/Folder.ts @@ -1,4 +1,4 @@ -import { Link } from "./Link" +import { Link } from './Link' export interface Folder { id: string diff --git a/src/types/Job.ts b/src/types/Job.ts index 3d99673..71b72dd 100644 --- a/src/types/Job.ts +++ b/src/types/Job.ts @@ -1,5 +1,5 @@ -import { Link } from "./Link" -import { JobResult } from "./JobResult" +import { Link } from './Link' +import { JobResult } from './JobResult' export interface Job { id: string diff --git a/src/types/JobResult.ts b/src/types/JobResult.ts index edb8093..ec2c63c 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/SASjsConfig.ts b/src/types/SASjsConfig.ts index 94318bd..70a2a18 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,14 +10,14 @@ 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 */ @@ -26,6 +26,6 @@ export class SASjsConfig { * Set to `true` to enable additional debugging. */ debug: boolean = true - contextName: string = "" + contextName: string = '' useComputeApi = false } diff --git a/src/types/ServerType.ts b/src/types/ServerType.ts index 82f1e5a..6cee82b 100644 --- a/src/types/ServerType.ts +++ b/src/types/ServerType.ts @@ -3,6 +3,6 @@ * */ export enum ServerType { - SASViya = "SASVIYA", - SAS9 = "SAS9" + SASViya = 'SASVIYA', + SAS9 = 'SAS9' } diff --git a/src/types/Session.ts b/src/types/Session.ts index 452abd0..4aa13ee 100644 --- a/src/types/Session.ts +++ b/src/types/Session.ts @@ -1,4 +1,4 @@ -import { Link } from "./Link" +import { Link } from './Link' export interface Session { id: string diff --git a/src/types/index.ts b/src/types/index.ts index b0902de..5235df3 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/compareTimestamps.ts b/src/utils/compareTimestamps.ts index 301b1f1..c862623 100644 --- a/src/utils/compareTimestamps.ts +++ b/src/utils/compareTimestamps.ts @@ -1,4 +1,4 @@ -import { SASjsRequest } from "../types/SASjsRequest" +import { SASjsRequest } from '../types/SASjsRequest' /** * Comparator for SASjs request timestamps diff --git a/src/utils/convertToCsv.ts b/src/utils/convertToCsv.ts index 704fc12..08a33b1 100644 --- a/src/utils/convertToCsv.ts +++ b/src/utils/convertToCsv.ts @@ -3,7 +3,7 @@ * @param data - the JSON object to convert. */ export const convertToCSV = (data: any) => { - const replacer = (key: any, value: any) => (value === null ? "" : value) + const replacer = (key: any, value: any) => (value === null ? '' : value) const headerFields = Object.keys(data[0]) let csvTest let invalidString = false @@ -14,31 +14,31 @@ export const convertToCSV = (data: any) => { const longestValueForField = data .map((row: any, index: number) => { - if (row[field] || row[field] === "") { + if (row[field] || row[field] === '') { if (firstFoundType) { let currentFieldType = - row[field] === "" || typeof row[field] === "string" - ? "chars" - : "number" + row[field] === '' || typeof row[field] === 'string' + ? 'chars' + : 'number' if (!hasMixedTypes) { hasMixedTypes = currentFieldType !== firstFoundType rowNumError = hasMixedTypes ? index + 1 : -1 } } else { - if (row[field] === "") { - firstFoundType = "chars" + if (row[field] === '') { + firstFoundType = 'chars' } else { firstFoundType = - typeof row[field] === "string" ? "chars" : "number" + typeof row[field] === 'string' ? 'chars' : 'number' } } let byteSize - if (typeof row[field] === "string") { + if (typeof row[field] === 'string') { let doubleQuotesFound = row[field] - .split("") + .split('') .filter((char: any) => char === '"') byteSize = getByteSize(row[field]) @@ -61,17 +61,17 @@ export const convertToCSV = (data: any) => { ) } - return `${field}:${firstFoundType === "chars" ? "$" : ""}${ + return `${field}:${firstFoundType === 'chars' ? '$' : ''}${ longestValueForField ? longestValueForField - : firstFoundType === "chars" - ? "1" - : "best" + : 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) => { @@ -86,15 +86,15 @@ export const convertToCSV = (data: any) => { value = JSON.stringify(currentCell, replacer) } - value = value.replace(/\\\\/gm, "\\") + value = value.replace(/\\\\/gm, '\\') if (containsSpecialChar) { - if (value.includes(",") || value.includes('"')) { + if (value.includes(',') || value.includes('"')) { value = '"' + value + '"' } } else { if ( - !value.includes(",") && + !value.includes(',') && value.includes('"') && !value.includes('\\"') ) { @@ -104,19 +104,19 @@ export const convertToCSV = (data: any) => { 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 = "." + if (value === '' && headers[index].includes('best')) { + value = '.' } return value }) - return fields.join(",") + 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 } diff --git a/src/utils/formatDataForRequest.ts b/src/utils/formatDataForRequest.ts index 82c822f..caf157d 100644 --- a/src/utils/formatDataForRequest.ts +++ b/src/utils/formatDataForRequest.ts @@ -1,5 +1,5 @@ -import { convertToCSV } from "./convertToCsv" -import { splitChunks } from "./splitChunks" +import { convertToCSV } from './convertToCsv' +import { splitChunks } from './splitChunks' export const formatDataForRequest = (data: any) => { const sasjsTables = [] @@ -10,9 +10,9 @@ export const formatDataForRequest = (data: any) => { tableCounter++ sasjsTables.push(tableName) const csv = convertToCSV(data[tableName]) - if (csv === "ERROR: LARGE STRING LENGTH") { + if (csv === 'ERROR: LARGE STRING LENGTH') { throw new Error( - "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 @@ -27,7 +27,7 @@ export const formatDataForRequest = (data: any) => { result[`sasjs${tableCounter}data`] = csv } } - result["sasjs_tables"] = sasjsTables.join(" ") + result['sasjs_tables'] = sasjsTables.join(' ') return result } diff --git a/src/utils/index.ts b/src/utils/index.ts index 95a3c81..6a15042 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/isIeOrEdge.ts b/src/utils/isIeOrEdge.ts index 36f3dc0..c136b60 100644 --- a/src/utils/isIeOrEdge.ts +++ b/src/utils/isIeOrEdge.ts @@ -1,29 +1,29 @@ export function isIEorEdgeOrOldFirefox() { - if (typeof window === "undefined") { + if (typeof window === 'undefined') { return false } const ua = window.navigator.userAgent - if (ua.indexOf("Firefox") > 0) { + if (ua.indexOf('Firefox') > 0) { const version = parseInt( - ua.substring(ua.lastIndexOf("Firefox/") + 8, ua.length), + ua.substring(ua.lastIndexOf('Firefox/') + 8, ua.length), 10 ) 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 } - const trident = ua.indexOf("Trident/") + const trident = ua.indexOf('Trident/') if (trident > 0) { return true } - const edge = ua.indexOf("Edge/") + const edge = ua.indexOf('Edge/') if (edge > 0) { // Edge (IE 12+) => return version number return true diff --git a/src/utils/makeRequest.ts b/src/utils/makeRequest.ts index d25abaa..a566251 100644 --- a/src/utils/makeRequest.ts +++ b/src/utils/makeRequest.ts @@ -1,5 +1,5 @@ -import { CsrfToken } from "../types" -import { needsRetry } from "./needsRetry" +import { CsrfToken } from '../types' +import { needsRetry } from './needsRetry' let retryCount: number = 0 let retryLimit: number = 5 @@ -8,28 +8,28 @@ export async function makeRequest( url: string, request: RequestInit, callback: (value: CsrfToken) => any, - contentType: "text" | "json" = "json" + contentType: 'text' | 'json' = 'json' ): Promise<{ result: T; etag: string | null }> { let retryRequest: any = null const responseTransform = - contentType === "json" + contentType === 'json' ? (res: Response) => res.json() : (res: Response) => res.text() let etag = null const result = await fetch(url, request).then(async (response) => { - if (response.redirected && response.url.includes("SASLogon/login")) { + if (response.redirected && response.url.includes('SASLogon/login')) { 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) callback({ headerName: tokenHeader, - value: token || "" + value: token || '' }) retryRequest = { @@ -37,7 +37,7 @@ export async function makeRequest( headers: { ...request.headers, [tokenHeader]: token } } return fetch(url, retryRequest).then((res) => { - etag = res.headers.get("ETag") + etag = res.headers.get('ETag') return responseTransform(res) }) } @@ -60,7 +60,7 @@ export async function makeRequest( } else { retryCount = 0 - throw new Error("Request retry limit exceeded") + throw new Error('Request retry limit exceeded') } } @@ -71,9 +71,9 @@ export async function makeRequest( return Promise.resolve() } const responseTransformed = await responseTransform(response) - let responseText = "" + let responseText = '' - if (typeof responseTransformed === "string") { + if (typeof responseTransformed === 'string') { responseText = responseTransformed } else { responseText = JSON.stringify(responseTransformed) @@ -95,11 +95,11 @@ export async function makeRequest( } else { retryCount = 0 - throw new Error("Request retry limit exceeded") + throw new Error('Request retry limit exceeded') } } - etag = response.headers.get("ETag") + etag = response.headers.get('ETag') return responseTransformed } }) diff --git a/src/utils/needsRetry.ts b/src/utils/needsRetry.ts index ab6c102..b08c4cc 100644 --- a/src/utils/needsRetry.ts +++ b/src/utils/needsRetry.ts @@ -2,13 +2,13 @@ export const needsRetry = (responseText: string): boolean => { return ( !!responseText && ((responseText.includes('"errorCode":403') && - responseText.includes("_csrf") && - responseText.includes("X-CSRF-TOKEN")) || + responseText.includes('_csrf') && + responseText.includes('X-CSRF-TOKEN')) || (responseText.includes('"status":403') && responseText.includes('"error":"Forbidden"')) || (responseText.includes('"status":449') && responseText.includes( - "Authentication success, retry original request" + 'Authentication success, retry original request' ))) ) } diff --git a/src/utils/parseAndSubmitAuthorizeForm.ts b/src/utils/parseAndSubmitAuthorizeForm.ts index d65fbac..5a77090 100644 --- a/src/utils/parseAndSubmitAuthorizeForm.ts +++ b/src/utils/parseAndSubmitAuthorizeForm.ts @@ -5,18 +5,18 @@ export const parseAndSubmitAuthorizeForm = async ( let authUrl: string | null = null const params: any = {} - const responseBody = response.split("")[1].split("")[0] - const bodyElement = document.createElement("div") + 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" + if (input.name === 'user_oauth_approval') { + input.value = 'true' } params[input.name] = input.value @@ -33,17 +33,17 @@ export const parseAndSubmitAuthorizeForm = async ( return new Promise((resolve, reject) => { if (authUrl) { fetch(authUrl, { - method: "POST", - credentials: "include", + method: 'POST', + credentials: 'include', body: formData, - referrerPolicy: "same-origin" + referrerPolicy: 'same-origin' }) .then((res) => res.text()) .then((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 852b99b..01d4e31 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") + 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 0727e63..b406f59 100644 --- a/src/utils/parseSasViyaLog.ts +++ b/src/utils/parseSasViyaLog.ts @@ -2,10 +2,10 @@ export const parseSasViyaLog = (logResponse: { items: any[] }) => { let log try { log = logResponse.items - ? logResponse.items.map((i) => i.line).join("\n") + ? logResponse.items.map((i) => i.line).join('\n') : JSON.stringify(logResponse) } catch (e) { - console.error("An error has occurred while parsing the log response", e) + console.error('An error has occurred while parsing the log response', e) log = logResponse } return log diff --git a/src/utils/parseSourceCode.ts b/src/utils/parseSourceCode.ts index c099333..d3d5fdc 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") + 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 2deae67..f9bc4db 100644 --- a/src/utils/parseWeboutResponse.ts +++ b/src/utils/parseWeboutResponse.ts @@ -1,13 +1,13 @@ export const parseWeboutResponse = (response: string) => { - let sasResponse = "" + let sasResponse = '' - if (response.includes(">>weboutBEGIN<<")) { + if (response.includes('>>weboutBEGIN<<')) { try { sasResponse = response - .split(">>weboutBEGIN<<")[1] - .split(">>weboutEND<<")[0] + .split('>>weboutBEGIN<<')[1] + .split('>>weboutEND<<')[0] } catch (e) { - sasResponse = "" + sasResponse = '' console.error(e) } } diff --git a/src/utils/serialize.ts b/src/utils/serialize.ts index fcb9783..824ff13 100644 --- a/src/utils/serialize.ts +++ b/src/utils/serialize.ts @@ -4,12 +4,12 @@ export const serialize = (obj: any) => { 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('&') }