1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-10 17:04:36 +00:00

chore(*): change code style to use single quote

This commit is contained in:
Yury Shkoda
2020-09-01 14:28:15 +03:00
parent 82b14fad14
commit c626c57662
28 changed files with 404 additions and 404 deletions

View File

@@ -2,5 +2,5 @@
"trailingComma": "none", "trailingComma": "none",
"tabWidth": 2, "tabWidth": 2,
"semi": false, "semi": false,
"singleQuote": false "singleQuote": true
} }

View File

@@ -1,6 +1,6 @@
import { isLogInRequired, needsRetry } from "./utils" import { isLogInRequired, needsRetry } from './utils'
import { CsrfToken } from "./types/CsrfToken" import { CsrfToken } from './types/CsrfToken'
import { UploadFile } from "./types/UploadFile" import { UploadFile } from './types/UploadFile'
const requestRetryLimit = 5 const requestRetryLimit = 5
@@ -15,9 +15,9 @@ export class FileUploader {
private retryCount = 0 private retryCount = 0
public uploadFile(sasJob: string, files: UploadFile[], params: any) { 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) { for (let param in params) {
if (params.hasOwnProperty(param)) { if (params.hasOwnProperty(param)) {
@@ -26,41 +26,41 @@ export class FileUploader {
} }
const program = this.appLoc const program = this.appLoc
? this.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "") ? this.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
: sasJob : sasJob
const uploadUrl = `${this.serverUrl}${this.jobsPath}/?${ const uploadUrl = `${this.serverUrl}${this.jobsPath}/?${
"_program=" + program '_program=' + program
}${paramsString}` }${paramsString}`
const headers = { const headers = {
"cache-control": "no-cache" 'cache-control': 'no-cache'
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const formData = new FormData() const formData = new FormData()
for (let file of files) { 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, { fetch(uploadUrl, {
method: "POST", method: 'POST',
body: formData, body: formData,
referrerPolicy: "same-origin", referrerPolicy: 'same-origin',
headers headers
}) })
.then(async (response) => { .then(async (response) => {
if (!response.ok) { if (!response.ok) {
if (response.status === 403) { if (response.status === 403) {
const tokenHeader = response.headers.get("X-CSRF-HEADER") const tokenHeader = response.headers.get('X-CSRF-HEADER')
if (tokenHeader) { if (tokenHeader) {
const token = response.headers.get(tokenHeader) const token = response.headers.get(tokenHeader)
this.csrfToken = { this.csrfToken = {
headerName: tokenHeader, headerName: tokenHeader,
value: token || "" value: token || ''
} }
this.setCsrfTokenWeb(this.csrfToken) this.setCsrfTokenWeb(this.csrfToken)
@@ -72,7 +72,7 @@ export class FileUploader {
}) })
.then((responseText) => { .then((responseText) => {
if (isLogInRequired(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 (needsRetry(responseText)) {
if (this.retryCount < requestRetryLimit) { if (this.retryCount < requestRetryLimit) {

View File

@@ -33,11 +33,11 @@ export class SAS9ApiClient {
serverName: string, serverName: string,
repositoryName: string repositoryName: string
) { ) {
const requestPayload = linesOfCode.join("\n") const requestPayload = linesOfCode.join('\n')
const executeScriptRequest = { const executeScriptRequest = {
method: "PUT", method: 'PUT',
headers: { headers: {
Accept: "application/json" Accept: 'application/json'
}, },
body: `command=${requestPayload}` body: `command=${requestPayload}`
} }

View File

@@ -3,13 +3,13 @@ import {
parseAndSubmitAuthorizeForm, parseAndSubmitAuthorizeForm,
convertToCSV, convertToCSV,
makeRequest makeRequest
} from "./utils" } from './utils'
import * as NodeFormData from "form-data" import * as NodeFormData from 'form-data'
import * as path from "path" import * as path from 'path'
import { Job, Session, Context, Folder, CsrfToken } from "./types" import { Job, Session, Context, Folder, CsrfToken } from './types'
import { JobDefinition } from "./types/JobDefinition" import { JobDefinition } from './types/JobDefinition'
import { formatDataForRequest } from "./utils/formatDataForRequest" import { formatDataForRequest } from './utils/formatDataForRequest'
import { SessionManager } from "./SessionManager" import { SessionManager } from './SessionManager'
/** /**
* A client for interfacing with the SAS Viya REST API * A client for interfacing with the SAS Viya REST API
@@ -24,7 +24,7 @@ export class SASViyaApiClient {
private rootFolderMap = new Map<string, Job[]>() private rootFolderMap = new Map<string, Job[]>()
) { ) {
if (!rootFolderName) { if (!rootFolderName) {
throw new Error("Root folder must be provided.") throw new Error('Root folder must be provided.')
} }
} }
private csrfToken: CsrfToken | null = null private csrfToken: CsrfToken | null = null
@@ -73,7 +73,7 @@ export class SASViyaApiClient {
*/ */
public async getAllContexts(accessToken?: string) { public async getAllContexts(accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json" 'Content-Type': 'application/json'
} }
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}` headers.Authorization = `Bearer ${accessToken}`
@@ -98,7 +98,7 @@ export class SASViyaApiClient {
*/ */
public async getExecutableContexts(accessToken?: string) { public async getExecutableContexts(accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json" 'Content-Type': 'application/json'
} }
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}` headers.Authorization = `Bearer ${accessToken}`
@@ -112,7 +112,7 @@ export class SASViyaApiClient {
const executableContexts: any[] = [] const executableContexts: any[] = []
const promises = contextsList.map((context: any) => { const promises = contextsList.map((context: any) => {
const linesOfCode = ["%put &=sysuserid;"] const linesOfCode = ['%put &=sysuserid;']
return this.executeScript( return this.executeScript(
`test-${context.name}`, `test-${context.name}`,
linesOfCode, linesOfCode,
@@ -122,14 +122,14 @@ export class SASViyaApiClient {
}) })
const results = await Promise.all(promises) const results = await Promise.all(promises)
results.forEach((result: any, index: number) => { results.forEach((result: any, index: number) => {
if (result && result.jobStatus === "completed") { if (result && result.jobStatus === 'completed') {
let sysUserId = "" let sysUserId = ''
if (result && result.log && result.log.items) { if (result && result.log && result.log.items) {
const sysUserIdLog = result.log.items.find((i: any) => const sysUserIdLog = result.log.items.find((i: any) =>
i.line.startsWith("SYSUSERID=") i.line.startsWith('SYSUSERID=')
) )
if (sysUserIdLog) { 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) { public async createSession(contextName: string, accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json" 'Content-Type': 'application/json'
} }
if (accessToken) { if (accessToken) {
@@ -175,10 +175,10 @@ export class SASViyaApiClient {
} }
const createSessionRequest = { const createSessionRequest = {
method: "POST", method: 'POST',
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json" 'Content-Type': 'application/json'
} }
} }
const { result: createdSession } = await this.request<Session>( const { result: createdSession } = await this.request<Session>(
@@ -210,7 +210,7 @@ export class SASViyaApiClient {
silent = !debug silent = !debug
try { try {
const headers: any = { const headers: any = {
"Content-Type": "application/json" 'Content-Type': 'application/json'
} }
if (accessToken) { if (accessToken) {
@@ -231,26 +231,26 @@ export class SASViyaApiClient {
} }
if (debug) { if (debug) {
jobArguments["_OMITTEXTLOG"] = false jobArguments['_OMITTEXTLOG'] = false
jobArguments["_OMITSESSIONRESULTS"] = false jobArguments['_OMITSESSIONRESULTS'] = false
jobArguments["_DEBUG"] = 131 jobArguments['_DEBUG'] = 131
} }
const fileName = `exec-${ const fileName = `exec-${
jobName.includes("/") ? jobName.split("/")[1] : jobName jobName.includes('/') ? jobName.split('/')[1] : jobName
}` }`
let jobVariables: any = { let jobVariables: any = {
SYS_JES_JOB_URI: "", SYS_JES_JOB_URI: '',
_program: this.rootFolderName + "/" + jobName _program: this.rootFolderName + '/' + jobName
} }
let files: any[] = [] let files: any[] = []
if (data) { if (data) {
if (JSON.stringify(data).includes(";")) { if (JSON.stringify(data).includes(';')) {
files = await this.uploadTables(data, accessToken) files = await this.uploadTables(data, accessToken)
jobVariables["_webin_file_count"] = files.length jobVariables['_webin_file_count'] = files.length
files.forEach((fileInfo, index) => { files.forEach((fileInfo, index) => {
jobVariables[ jobVariables[
`_webin_fileuri${index + 1}` `_webin_fileuri${index + 1}`
@@ -264,11 +264,11 @@ export class SASViyaApiClient {
// Execute job in session // Execute job in session
const postJobRequest = { const postJobRequest = {
method: "POST", method: 'POST',
headers, headers,
body: JSON.stringify({ body: JSON.stringify({
name: fileName, name: fileName,
description: "Powered by SASjs", description: 'Powered by SASjs',
code: linesOfCode, code: linesOfCode,
variables: jobVariables, variables: jobVariables,
arguments: jobArguments arguments: jobArguments
@@ -284,7 +284,7 @@ export class SASViyaApiClient {
console.log(`Job has been submitted for ${fileName}`) console.log(`Job has been submitted for ${fileName}`)
console.log( console.log(
`You can monitor the job progress at ${this.serverUrl}${ `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 let jobResult, log
const logLink = currentJob.links.find((l) => l.rel === "log") const logLink = currentJob.links.find((l) => l.rel === 'log')
if (true && logLink) { if (true && logLink) {
log = await this.request<any>( log = await this.request<any>(
@@ -312,11 +312,11 @@ export class SASViyaApiClient {
headers headers
} }
).then((res: any) => ).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 }) return Promise.reject({ error: currentJob.error, log: log })
} }
const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content` const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`
@@ -325,7 +325,7 @@ export class SASViyaApiClient {
jobResult = await this.request<any>( jobResult = await this.request<any>(
`${this.serverUrl}${resultLink}`, `${this.serverUrl}${resultLink}`,
{ headers }, { headers },
"text" 'text'
).catch((e) => ({ ).catch((e) => ({
result: JSON.stringify(e) result: JSON.stringify(e)
})) }))
@@ -367,7 +367,7 @@ export class SASViyaApiClient {
accessToken?: string accessToken?: string
): Promise<Folder> { ): Promise<Folder> {
if (!parentFolderPath && !parentFolderUri) { 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) { if (!parentFolderUri && parentFolderPath) {
@@ -377,11 +377,11 @@ export class SASViyaApiClient {
const newParentFolderPath = parentFolderPath.substring( const newParentFolderPath = parentFolderPath.substring(
0, 0,
parentFolderPath.lastIndexOf("/") parentFolderPath.lastIndexOf('/')
) )
const newFolderName = `${parentFolderPath.split("/").pop()}` const newFolderName = `${parentFolderPath.split('/').pop()}`
if (newParentFolderPath === "") { 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( console.log(
`Creating Parent Folder:\n${newFolderName} in ${newParentFolderPath}` `Creating Parent Folder:\n${newFolderName} in ${newParentFolderPath}`
@@ -398,14 +398,14 @@ export class SASViyaApiClient {
} }
const createFolderRequest: RequestInit = { const createFolderRequest: RequestInit = {
method: "POST", method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
name: folderName, name: folderName,
type: "folder" type: 'folder'
}) })
} }
createFolderRequest.headers = { "Content-Type": "application/json" } createFolderRequest.headers = { 'Content-Type': 'application/json' }
if (accessToken) { if (accessToken) {
createFolderRequest.headers.Authorization = `Bearer ${accessToken}` createFolderRequest.headers.Authorization = `Bearer ${accessToken}`
} }
@@ -437,7 +437,7 @@ export class SASViyaApiClient {
) { ) {
if (!parentFolderPath && !parentFolderUri) { if (!parentFolderPath && !parentFolderUri) {
throw new Error( 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 = { const createJobDefinitionRequest: RequestInit = {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "application/vnd.sas.job.definition+json", 'Content-Type': 'application/vnd.sas.job.definition+json',
Accept: "application/vnd.sas.job.definition+json" Accept: 'application/vnd.sas.job.definition+json'
}, },
body: JSON.stringify({ body: JSON.stringify({
name: jobName, name: jobName,
parameters: [ parameters: [
{ {
name: "_addjesbeginendmacros", name: '_addjesbeginendmacros',
type: "CHARACTER", type: 'CHARACTER',
defaultValue: "false" defaultValue: 'false'
} }
], ],
type: "Compute", type: 'Compute',
code code
}) })
} }
@@ -486,12 +486,12 @@ export class SASViyaApiClient {
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, { const authCode = await fetch(authUrl, {
referrerPolicy: "same-origin", referrerPolicy: 'same-origin',
credentials: "include" credentials: 'include'
}) })
.then((response) => response.text()) .then((response) => response.text())
.then(async (response) => { .then(async (response) => {
let code = "" let code = ''
if (isAuthorizeFormRequired(response)) { if (isAuthorizeFormRequired(response)) {
const formResponse: any = await parseAndSubmitAuthorizeForm( const formResponse: any = await parseAndSubmitAuthorizeForm(
response, response,
@@ -499,21 +499,21 @@ export class SASViyaApiClient {
) )
const responseBody = formResponse const responseBody = formResponse
.split("<body>")[1] .split('<body>')[1]
.split("</body>")[0] .split('</body>')[0]
const bodyElement: any = document.createElement("div") const bodyElement: any = document.createElement('div')
bodyElement.innerHTML = responseBody bodyElement.innerHTML = responseBody
code = bodyElement.querySelector(".infobox h4").innerText code = bodyElement.querySelector('.infobox h4').innerText
return code return code
} else { } else {
const responseBody = response.split("<body>")[1].split("</body>")[0] const responseBody = response.split('<body>')[1].split('</body>')[0]
const bodyElement: any = document.createElement("div") const bodyElement: any = document.createElement('div')
bodyElement.innerHTML = responseBody bodyElement.innerHTML = responseBody
if (bodyElement) { if (bodyElement) {
code = bodyElement.querySelector(".infobox h4").innerText code = bodyElement.querySelector('.infobox h4').innerText
} }
return code return code
@@ -535,34 +535,34 @@ export class SASViyaApiClient {
clientSecret: string, clientSecret: string,
authCode: string authCode: string
) { ) {
const url = this.serverUrl + "/SASLogon/oauth/token" const url = this.serverUrl + '/SASLogon/oauth/token'
let token let token
if (typeof Buffer === "undefined") { if (typeof Buffer === 'undefined') {
token = btoa(clientId + ":" + clientSecret) token = btoa(clientId + ':' + clientSecret)
} else { } else {
token = Buffer.from(clientId + ":" + clientSecret).toString("base64") token = Buffer.from(clientId + ':' + clientSecret).toString('base64')
} }
const headers = { const headers = {
Authorization: "Basic " + token Authorization: 'Basic ' + token
} }
let formData let formData
if (typeof FormData === "undefined") { if (typeof FormData === 'undefined') {
formData = new NodeFormData() formData = new NodeFormData()
formData.append("grant_type", "authorization_code") formData.append('grant_type', 'authorization_code')
formData.append("code", authCode) formData.append('code', authCode)
} else { } else {
formData = new FormData() formData = new FormData()
formData.append("grant_type", "authorization_code") formData.append('grant_type', 'authorization_code')
formData.append("code", authCode) formData.append('code', authCode)
} }
const authResponse = await fetch(url, { const authResponse = await fetch(url, {
method: "POST", method: 'POST',
credentials: "include", credentials: 'include',
headers, headers,
body: formData as any, body: formData as any,
referrerPolicy: "same-origin" referrerPolicy: 'same-origin'
}).then((res) => res.json()) }).then((res) => res.json())
return authResponse return authResponse
@@ -579,34 +579,34 @@ export class SASViyaApiClient {
clientSecret: string, clientSecret: string,
refreshToken: string refreshToken: string
) { ) {
const url = this.serverUrl + "/SASLogon/oauth/token" const url = this.serverUrl + '/SASLogon/oauth/token'
let token let token
if (typeof Buffer === "undefined") { if (typeof Buffer === 'undefined') {
token = btoa(clientId + ":" + clientSecret) token = btoa(clientId + ':' + clientSecret)
} else { } else {
token = Buffer.from(clientId + ":" + clientSecret).toString("base64") token = Buffer.from(clientId + ':' + clientSecret).toString('base64')
} }
const headers = { const headers = {
Authorization: "Basic " + token Authorization: 'Basic ' + token
} }
let formData let formData
if (typeof FormData === "undefined") { if (typeof FormData === 'undefined') {
formData = new NodeFormData() formData = new NodeFormData()
formData.append("grant_type", "refresh_token") formData.append('grant_type', 'refresh_token')
formData.append("refresh_token", refreshToken) formData.append('refresh_token', refreshToken)
} else { } else {
formData = new FormData() formData = new FormData()
formData.append("grant_type", "refresh_token") formData.append('grant_type', 'refresh_token')
formData.append("refresh_token", refreshToken) formData.append('refresh_token', refreshToken)
} }
const authResponse = await fetch(url, { const authResponse = await fetch(url, {
method: "POST", method: 'POST',
credentials: "include", credentials: 'include',
headers, headers,
body: formData as any, body: formData as any,
referrerPolicy: "same-origin" referrerPolicy: 'same-origin'
}).then((res) => res.json()) }).then((res) => res.json())
return authResponse return authResponse
@@ -624,8 +624,8 @@ export class SASViyaApiClient {
headers.Authorization = `Bearer ${accessToken}` headers.Authorization = `Bearer ${accessToken}`
} }
const deleteResponse = await this.request(url, { const deleteResponse = await this.request(url, {
method: "DELETE", method: 'DELETE',
credentials: "include", credentials: 'include',
headers headers
}) })
@@ -651,8 +651,8 @@ export class SASViyaApiClient {
await this.populateRootFolder(accessToken) await this.populateRootFolder(accessToken)
} }
if (!this.rootFolder) { if (!this.rootFolder) {
console.error("Root folder was not found") console.error('Root folder was not found')
throw new Error("Root folder was not found") throw new Error('Root folder was not found')
} }
if (!this.rootFolderMap.size) { if (!this.rootFolderMap.size) {
await this.populateRootFolderMap(accessToken) 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) { if (!!accessToken) {
headers.Authorization = `Bearer ${accessToken}` headers.Authorization = `Bearer ${accessToken}`
} }
const folderName = sasJob.split("/")[0] const folderName = sasJob.split('/')[0]
const jobName = sasJob.split("/")[1] const jobName = sasJob.split('/')[1]
const jobFolder = this.rootFolderMap.get(folderName) const jobFolder = this.rootFolderMap.get(folderName)
const jobToExecute = jobFolder?.find((item) => item.name === jobName) const jobToExecute = jobFolder?.find((item) => item.name === jobName)
if (!jobToExecute) { 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) { if (!code) {
const jobDefinitionLink = jobToExecute?.links.find( const jobDefinitionLink = jobToExecute?.links.find(
(l) => l.rel === "getResource" (l) => l.rel === 'getResource'
) )
if (!jobDefinitionLink) { if (!jobDefinitionLink) {
console.error("Job definition URI was not found.") console.error('Job definition URI was not found.')
throw new Error("Job definition URI was not found.") throw new Error('Job definition URI was not found.')
} }
const { result: jobDefinition } = await this.request<JobDefinition>( const { result: jobDefinition } = await this.request<JobDefinition>(
`${this.serverUrl}${jobDefinitionLink.href}`, `${this.serverUrl}${jobDefinitionLink.href}`,
@@ -696,7 +696,7 @@ export class SASViyaApiClient {
// Add code to existing job definition // 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( return await this.executeScript(
sasJob, sasJob,
linesToExecute, linesToExecute,
@@ -728,7 +728,7 @@ export class SASViyaApiClient {
} }
if (!this.rootFolder) { if (!this.rootFolder) {
throw new Error("Root folder was not found") throw new Error('Root folder was not found')
} }
if (!this.rootFolderMap.size) { if (!this.rootFolderMap.size) {
await this.populateRootFolderMap(accessToken) await this.populateRootFolderMap(accessToken)
@@ -745,17 +745,17 @@ export class SASViyaApiClient {
} }
const jobName = path.basename(sasJob) const jobName = path.basename(sasJob)
const jobFolder = sasJob.replace(`/${jobName}`, "") const jobFolder = sasJob.replace(`/${jobName}`, '')
const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace("/", "")) const allJobsInFolder = this.rootFolderMap.get(jobFolder.replace('/', ''))
if (allJobsInFolder) { 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( const jobDefinitionLink = jobSpec?.links.find(
(l) => l.rel === "getResource" (l) => l.rel === 'getResource'
)?.href )?.href
const requestInfo: any = { const requestInfo: any = {
method: "GET" method: 'GET'
} }
const headers: any = { "Content-Type": "application/json" } const headers: any = { 'Content-Type': 'application/json' }
if (!!accessToken) { if (!!accessToken) {
headers.Authorization = `Bearer ${accessToken}` headers.Authorization = `Bearer ${accessToken}`
} }
@@ -777,9 +777,9 @@ export class SASViyaApiClient {
} }
if (debug) { if (debug) {
jobArguments["_OMITTEXTLOG"] = "false" jobArguments['_OMITTEXTLOG'] = 'false'
jobArguments["_OMITSESSIONRESULTS"] = "false" jobArguments['_OMITSESSIONRESULTS'] = 'false'
jobArguments["_DEBUG"] = 131 jobArguments['_DEBUG'] = 131
} }
files.forEach((fileInfo, index) => { files.forEach((fileInfo, index) => {
@@ -790,11 +790,11 @@ export class SASViyaApiClient {
}) })
const postJobRequest = { const postJobRequest = {
method: "POST", method: 'POST',
headers, headers,
body: JSON.stringify({ body: JSON.stringify({
name: `exec-${jobName}`, name: `exec-${jobName}`,
description: "Powered by SASjs", description: 'Powered by SASjs',
jobDefinition, jobDefinition,
arguments: jobArguments arguments: jobArguments
}) })
@@ -815,16 +815,16 @@ export class SASViyaApiClient {
) )
let jobResult, log let jobResult, log
if (jobStatus === "failed") { if (jobStatus === 'failed') {
return Promise.reject(currentJob.error) return Promise.reject(currentJob.error)
} }
const resultLink = currentJob.results["_webout.json"] const resultLink = currentJob.results['_webout.json']
const logLink = currentJob.links.find((l) => l.rel === "log") const logLink = currentJob.links.find((l) => l.rel === 'log')
if (resultLink) { if (resultLink) {
jobResult = await this.request<any>( jobResult = await this.request<any>(
`${this.serverUrl}${resultLink}/content`, `${this.serverUrl}${resultLink}/content`,
{ headers }, { headers },
"text" 'text'
) )
} }
if (debug && logLink) { if (debug && logLink) {
@@ -834,7 +834,7 @@ export class SASViyaApiClient {
headers headers
} }
).then((res: any) => ).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 } return { result: jobResult?.result, log }
@@ -847,9 +847,9 @@ export class SASViyaApiClient {
private async populateRootFolderMap(accessToken?: string) { private async populateRootFolderMap(accessToken?: string) {
const allItems = new Map<string, Job[]>() const allItems = new Map<string, Job[]>()
const url = "/folders/folders/@item?path=" + this.rootFolderName const url = '/folders/folders/@item?path=' + this.rootFolderName
const requestInfo: any = { const requestInfo: any = {
method: "GET" method: 'GET'
} }
if (accessToken) { if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` } requestInfo.headers = { Authorization: `Bearer ${accessToken}` }
@@ -859,7 +859,7 @@ export class SASViyaApiClient {
requestInfo requestInfo
) )
if (!folder) { 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[] }>( const { result: members } = await this.request<{ items: any[] }>(
`${this.serverUrl}/folders/folders/${folder.id}/members`, `${this.serverUrl}/folders/folders/${folder.id}/members`,
@@ -867,14 +867,14 @@ export class SASViyaApiClient {
) )
const itemsAtRoot = members.items const itemsAtRoot = members.items
allItems.set("", itemsAtRoot) allItems.set('', itemsAtRoot)
const subfolderRequests = members.items const subfolderRequests = members.items
.filter((i: any) => i.contentType === "folder") .filter((i: any) => i.contentType === 'folder')
.map(async (member: any) => { .map(async (member: any) => {
const subFolderUrl = const subFolderUrl =
"/folders/folders/@item?path=" + '/folders/folders/@item?path=' +
this.rootFolderName + this.rootFolderName +
"/" + '/' +
member.name member.name
const { result: memberDetail } = await this.request<Folder>( const { result: memberDetail } = await this.request<Folder>(
`${this.serverUrl}${subFolderUrl}`, `${this.serverUrl}${subFolderUrl}`,
@@ -882,7 +882,7 @@ export class SASViyaApiClient {
) )
const membersLink = memberDetail.links.find( const membersLink = memberDetail.links.find(
(l: any) => l.rel === "members" (l: any) => l.rel === 'members'
) )
const { result: memberContents } = await this.request<{ items: any[] }>( const { result: memberContents } = await this.request<{ items: any[] }>(
@@ -899,9 +899,9 @@ export class SASViyaApiClient {
} }
private async populateRootFolder(accessToken?: string) { private async populateRootFolder(accessToken?: string) {
const url = "/folders/folders/@item?path=" + this.rootFolderName const url = '/folders/folders/@item?path=' + this.rootFolderName
const requestInfo: RequestInit = { const requestInfo: RequestInit = {
method: "GET" method: 'GET'
} }
if (accessToken) { if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` } requestInfo.headers = { Authorization: `Bearer ${accessToken}` }
@@ -926,18 +926,18 @@ export class SASViyaApiClient {
) { ) {
const MAX_POLL_COUNT = 1000 const MAX_POLL_COUNT = 1000
const POLL_INTERVAL = 100 const POLL_INTERVAL = 100
let postedJobState = "" let postedJobState = ''
let pollCount = 0 let pollCount = 0
const headers: any = { const headers: any = {
"Content-Type": "application/json", 'Content-Type': 'application/json',
"If-None-Match": etag 'If-None-Match': etag
} }
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${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) { if (!stateLink) {
Promise.reject("Job state link was not found.") Promise.reject('Job state link was not found.')
} }
const { result: state } = await this.request<string>( const { result: state } = await this.request<string>(
@@ -945,31 +945,31 @@ export class SASViyaApiClient {
{ {
headers headers
}, },
"text" 'text'
) )
const currentState = state.trim() const currentState = state.trim()
if (currentState === "completed") { if (currentState === 'completed') {
return Promise.resolve(currentState) return Promise.resolve(currentState)
} }
return new Promise(async (resolve, _) => { return new Promise(async (resolve, _) => {
const interval = setInterval(async () => { const interval = setInterval(async () => {
if ( if (
postedJobState === "running" || postedJobState === 'running' ||
postedJobState === "" || postedJobState === '' ||
postedJobState === "pending" postedJobState === 'pending'
) { ) {
if (stateLink) { if (stateLink) {
if (!silent) { if (!silent) {
console.log("Polling job status... \n") console.log('Polling job status... \n')
} }
const { result: jobState } = await this.request<string>( const { result: jobState } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?_action=wait&wait=30`, `${this.serverUrl}${stateLink.href}?_action=wait&wait=30`,
{ {
headers headers
}, },
"text" 'text'
) )
postedJobState = jobState.trim() postedJobState = jobState.trim()
@@ -998,25 +998,25 @@ export class SASViyaApiClient {
let sessionState = session.state let sessionState = session.state
let pollCount = 0 let pollCount = 0
const headers: any = { const headers: any = {
"Content-Type": "application/json", 'Content-Type': 'application/json',
"If-None-Match": etag 'If-None-Match': etag
} }
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${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, _) => { return new Promise(async (resolve, _) => {
if (sessionState === "pending") { if (sessionState === 'pending') {
if (stateLink) { if (stateLink) {
if (!silent) { if (!silent) {
console.log("Polling session status... \n") console.log('Polling session status... \n')
} }
const { result: state } = await this.request<string>( const { result: state } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?wait=30`, `${this.serverUrl}${stateLink.href}?wait=30`,
{ {
headers headers
}, },
"text" 'text'
) )
sessionState = state.trim() sessionState = state.trim()
@@ -1035,7 +1035,7 @@ export class SASViyaApiClient {
private async uploadTables(data: any, accessToken?: string) { private async uploadTables(data: any, accessToken?: string) {
const uploadedFiles = [] const uploadedFiles = []
const headers: any = { const headers: any = {
"Content-Type": "application/json" 'Content-Type': 'application/json'
} }
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}` headers.Authorization = `Bearer ${accessToken}`
@@ -1043,14 +1043,14 @@ export class SASViyaApiClient {
for (const tableName in data) { for (const tableName in data) {
const csv = convertToCSV(data[tableName]) const csv = convertToCSV(data[tableName])
if (csv === "ERROR: LARGE STRING LENGTH") { if (csv === 'ERROR: LARGE STRING LENGTH') {
throw new Error( 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 = { const createFileRequest = {
method: "POST", method: 'POST',
body: csv, body: csv,
headers headers
} }
@@ -1066,9 +1066,9 @@ export class SASViyaApiClient {
} }
private async getFolderUri(folderPath: string, accessToken?: string) { private async getFolderUri(folderPath: string, accessToken?: string) {
const url = "/folders/folders/@item?path=" + folderPath const url = '/folders/folders/@item?path=' + folderPath
const requestInfo: any = { const requestInfo: any = {
method: "GET" method: 'GET'
} }
if (accessToken) { if (accessToken) {
requestInfo.headers = { Authorization: `Bearer ${accessToken}` } requestInfo.headers = { Authorization: `Bearer ${accessToken}` }
@@ -1092,7 +1092,7 @@ export class SASViyaApiClient {
private async request<T>( private async request<T>(
url: string, url: string,
options: RequestInit, options: RequestInit,
contentType: "text" | "json" = "json" contentType: 'text' | 'json' = 'json'
) { ) {
if (this.csrfToken) { if (this.csrfToken) {
options.headers = { options.headers = {

View File

@@ -1,34 +1,34 @@
import SASjs from "./index" import SASjs from './index'
const adapter = new SASjs() const adapter = new SASjs()
it("should parse SAS9 source code", async (done) => { it('should parse SAS9 source code', async (done) => {
expect(sampleResponse).toBeTruthy() expect(sampleResponse).toBeTruthy()
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse) const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse)
expect(parsedSourceCode).toBeTruthy() expect(parsedSourceCode).toBeTruthy()
const sourceCodeLines = parsedSourceCode.split("\r\n") const sourceCodeLines = parsedSourceCode.split('\r\n')
expect(sourceCodeLines.length).toEqual(5) expect(sourceCodeLines.length).toEqual(5)
expect(sourceCodeLines[0].startsWith("6")).toBeTruthy() expect(sourceCodeLines[0].startsWith('6')).toBeTruthy()
expect(sourceCodeLines[1].startsWith("7")).toBeTruthy() expect(sourceCodeLines[1].startsWith('7')).toBeTruthy()
expect(sourceCodeLines[2].startsWith("8")).toBeTruthy() expect(sourceCodeLines[2].startsWith('8')).toBeTruthy()
expect(sourceCodeLines[3].startsWith("9")).toBeTruthy() expect(sourceCodeLines[3].startsWith('9')).toBeTruthy()
expect(sourceCodeLines[4].startsWith("10")).toBeTruthy() expect(sourceCodeLines[4].startsWith('10')).toBeTruthy()
done() done()
}) })
it("should parse generated code", async (done) => { it('should parse generated code', async (done) => {
expect(sampleResponse).toBeTruthy() expect(sampleResponse).toBeTruthy()
const parsedGeneratedCode = (adapter as any).parseGeneratedCode( const parsedGeneratedCode = (adapter as any).parseGeneratedCode(
sampleResponse sampleResponse
) )
expect(parsedGeneratedCode).toBeTruthy() expect(parsedGeneratedCode).toBeTruthy()
const generatedCodeLines = parsedGeneratedCode.split("\r\n") const generatedCodeLines = parsedGeneratedCode.split('\r\n')
expect(generatedCodeLines.length).toEqual(5) expect(generatedCodeLines.length).toEqual(5)
expect(generatedCodeLines[0].startsWith("MPRINT(MM_WEBIN)")).toBeTruthy() expect(generatedCodeLines[0].startsWith('MPRINT(MM_WEBIN)')).toBeTruthy()
expect(generatedCodeLines[1].startsWith("MPRINT(MM_WEBLEFT)")).toBeTruthy() expect(generatedCodeLines[1].startsWith('MPRINT(MM_WEBLEFT)')).toBeTruthy()
expect(generatedCodeLines[2].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy() expect(generatedCodeLines[2].startsWith('MPRINT(MM_WEBOUT)')).toBeTruthy()
expect(generatedCodeLines[3].startsWith("MPRINT(MM_WEBRIGHT)")).toBeTruthy() expect(generatedCodeLines[3].startsWith('MPRINT(MM_WEBRIGHT)')).toBeTruthy()
expect(generatedCodeLines[4].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy() expect(generatedCodeLines[4].startsWith('MPRINT(MM_WEBOUT)')).toBeTruthy()
done() done()
}) })

View File

@@ -1,5 +1,5 @@
import { isIEorEdgeOrOldFirefox } from "./utils/isIeOrEdge" import { isIEorEdgeOrOldFirefox } from './utils/isIeOrEdge'
import * as e6p from "es6-promise" import * as e6p from 'es6-promise'
;(e6p as any).polyfill() ;(e6p as any).polyfill()
if (isIEorEdgeOrOldFirefox()) { if (isIEorEdgeOrOldFirefox()) {
if (window) { if (window) {
@@ -7,7 +7,7 @@ if (isIEorEdgeOrOldFirefox()) {
} }
} }
// tslint:disable-next-line // tslint:disable-next-line
require("isomorphic-fetch") require('isomorphic-fetch')
import { import {
convertToCSV, convertToCSV,
compareTimestamps, compareTimestamps,
@@ -22,7 +22,7 @@ import {
parseWeboutResponse, parseWeboutResponse,
needsRetry, needsRetry,
asyncForEach asyncForEach
} from "./utils" } from './utils'
import { import {
SASjsConfig, SASjsConfig,
SASjsRequest, SASjsRequest,
@@ -30,19 +30,19 @@ import {
ServerType, ServerType,
CsrfToken, CsrfToken,
UploadFile UploadFile
} from "./types" } from './types'
import { SASViyaApiClient } from "./SASViyaApiClient" import { SASViyaApiClient } from './SASViyaApiClient'
import { SAS9ApiClient } from "./SAS9ApiClient" import { SAS9ApiClient } from './SAS9ApiClient'
import { FileUploader } from "./FileUploader" import { FileUploader } from './FileUploader'
const defaultConfig: SASjsConfig = { const defaultConfig: SASjsConfig = {
serverUrl: "", serverUrl: '',
pathSAS9: "/SASStoredProcess/do", pathSAS9: '/SASStoredProcess/do',
pathSASViya: "/SASJobExecution", pathSASViya: '/SASJobExecution',
appLoc: "/Public/seedapp", appLoc: '/Public/seedapp',
serverType: ServerType.SASViya, serverType: ServerType.SASViya,
debug: true, debug: true,
contextName: "SAS Job Execution compute context", contextName: 'SAS Job Execution compute context',
useComputeApi: false useComputeApi: false
} }
@@ -54,9 +54,9 @@ const requestRetryLimit = 5
*/ */
export default class SASjs { export default class SASjs {
private sasjsConfig: SASjsConfig = new SASjsConfig() private sasjsConfig: SASjsConfig = new SASjsConfig()
private jobsPath: string = "" private jobsPath: string = ''
private logoutUrl: string = "" private logoutUrl: string = ''
private loginUrl: string = "" private loginUrl: string = ''
private csrfTokenApi: CsrfToken | null = null private csrfTokenApi: CsrfToken | null = null
private csrfTokenWeb: CsrfToken | null = null private csrfTokenWeb: CsrfToken | null = null
private retryCountWeb: number = 0 private retryCountWeb: number = 0
@@ -64,7 +64,7 @@ export default class SASjs {
private retryCountJeseApi: number = 0 private retryCountJeseApi: number = 0
private sasjsRequests: SASjsRequest[] = [] private sasjsRequests: SASjsRequest[] = []
private sasjsWaitingRequests: SASjsWaitingRequest[] = [] private sasjsWaitingRequests: SASjsWaitingRequest[] = []
private userName: string = "" private userName: string = ''
private sasViyaApiClient: SASViyaApiClient | null = null private sasViyaApiClient: SASViyaApiClient | null = null
private sas9ApiClient: SAS9ApiClient | null = null private sas9ApiClient: SAS9ApiClient | null = null
private fileUploader: FileUploader | null = null private fileUploader: FileUploader | null = null
@@ -84,7 +84,7 @@ export default class SASjs {
repositoryName: string repositoryName: string
) { ) {
if (this.sasjsConfig.serverType !== ServerType.SAS9) { 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( return await this.sas9ApiClient?.executeScript(
linesOfCode, linesOfCode,
@@ -95,21 +95,21 @@ export default class SASjs {
public async getAllContexts(accessToken: string) { public async getAllContexts(accessToken: string) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) { 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) { public async getExecutableContexts(accessToken: string) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) { 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) { public async createSession(contextName: string, accessToken: string) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) { 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)
} }
@@ -119,11 +119,11 @@ export default class SASjs {
linesOfCode: string[], linesOfCode: string[],
contextName: string, contextName: string,
accessToken?: string, accessToken?: string,
sessionId = "", sessionId = '',
silent = false silent = false
) { ) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) { 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( return await this.sasViyaApiClient!.executeScript(
fileName, fileName,
@@ -142,7 +142,7 @@ export default class SASjs {
sasApiClient?: SASViyaApiClient sasApiClient?: SASViyaApiClient
) { ) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) { 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) if (sasApiClient)
return await sasApiClient.createFolder( return await sasApiClient.createFolder(
@@ -168,7 +168,7 @@ export default class SASjs {
sasApiClient?: SASViyaApiClient sasApiClient?: SASViyaApiClient
) { ) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) { 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) if (sasApiClient)
return await sasApiClient!.createJobDefinition( return await sasApiClient!.createJobDefinition(
@@ -189,7 +189,7 @@ export default class SASjs {
public async getAuthCode(clientId: string) { public async getAuthCode(clientId: string) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) { 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)
} }
@@ -200,7 +200,7 @@ export default class SASjs {
authCode: string authCode: string
) { ) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) { 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( return await this.sasViyaApiClient!.getAccessToken(
clientId, clientId,
@@ -215,7 +215,7 @@ export default class SASjs {
refreshToken: string refreshToken: string
) { ) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) { 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( return await this.sasViyaApiClient!.refreshTokens(
clientId, clientId,
@@ -226,7 +226,7 @@ export default class SASjs {
public async deleteClient(clientId: string, accessToken: string) { public async deleteClient(clientId: string, accessToken: string) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) { 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)
} }
@@ -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` * @returns a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`
*/ */
public async checkSession() { 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 responseText = await loginResponse.text()
const isLoggedIn = /<button.+onClick.+logout/gm.test(responseText) const isLoggedIn = /<button.+onClick.+logout/gm.test(responseText)
@@ -306,7 +306,7 @@ export default class SASjs {
*/ */
public async logIn(username: string, password: string) { public async logIn(username: string, password: string) {
const loginParams: any = { const loginParams: any = {
_service: "default", _service: 'default',
username, username,
password password
} }
@@ -331,12 +331,12 @@ export default class SASjs {
const loginParamsStr = serialize(loginParams) const loginParamsStr = serialize(loginParams)
return fetch(this.loginUrl, { return fetch(this.loginUrl, {
method: "post", method: 'post',
credentials: "include", credentials: 'include',
referrerPolicy: "same-origin", referrerPolicy: 'same-origin',
body: loginParamsStr, body: loginParamsStr,
headers: new Headers({ headers: new Headers({
"Content-Type": "application/x-www-form-urlencoded" 'Content-Type': 'application/x-www-form-urlencoded'
}) })
}) })
.then((response) => response.text()) .then((response) => response.text())
@@ -438,7 +438,7 @@ export default class SASjs {
...config ...config
} }
sasJob = sasJob.startsWith("/") ? sasJob.replace("/", "") : sasJob sasJob = sasJob.startsWith('/') ? sasJob.replace('/', '') : sasJob
if (config.serverType === ServerType.SASViya && config.contextName) { if (config.serverType === ServerType.SASViya && config.contextName) {
if (config.useComputeApi) { if (config.useComputeApi) {
@@ -492,7 +492,7 @@ export default class SASjs {
accessToken?: string accessToken?: string
) { ) {
if (this.sasjsConfig.serverType !== ServerType.SASViya) { 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
@@ -527,12 +527,12 @@ export default class SASjs {
// members of type 'folder' should be processed first // members of type 'folder' should be processed first
if (serviceJson.members[0].members) { if (serviceJson.members[0].members) {
serviceJson.members[0].members.sort((member: { type: string }) => serviceJson.members[0].members.sort((member: { type: string }) =>
member.type === "folder" ? -1 : 1 member.type === 'folder' ? -1 : 1
) )
} }
const members = const members =
serviceJson.members[0].name === "services" serviceJson.members[0].name === 'services'
? serviceJson.members[0].members ? serviceJson.members[0].members
: serviceJson.members : serviceJson.members
@@ -606,7 +606,7 @@ export default class SASjs {
resolve(retryResponse) resolve(retryResponse)
} else { } else {
this.retryCountComputeApi = 0 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 sasjsWaitingRequest.config = config
this.sasjsWaitingRequests.push(sasjsWaitingRequest) this.sasjsWaitingRequests.push(sasjsWaitingRequest)
} else { } 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)
@@ -699,11 +699,11 @@ export default class SASjs {
resolve(retryResponse) resolve(retryResponse)
} else { } else {
this.retryCountJeseApi = 0 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 data
} }
const program = config.appLoc const program = config.appLoc
? config.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "") ? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
: sasJob : sasJob
const jobUri = const jobUri =
config.serverType === "SASVIYA" ? await this.getJobUri(sasJob) : "" config.serverType === 'SASVIYA' ? await this.getJobUri(sasJob) : ''
const apiUrl = `${config.serverUrl}${this.jobsPath}/?${ const apiUrl = `${config.serverUrl}${this.jobsPath}/?${
jobUri.length > 0 jobUri.length > 0
? "__program=" + program + "&_job=" + jobUri ? '__program=' + program + '&_job=' + jobUri
: "_program=" + program : '_program=' + program
}` }`
const requestParams = { const requestParams = {
@@ -745,14 +745,14 @@ export default class SASjs {
const formData = new FormData() const formData = new FormData()
let isError = false let isError = false
let errorMsg = "" let errorMsg = ''
if (data) { if (data) {
const stringifiedData = JSON.stringify(data) const stringifiedData = JSON.stringify(data)
if ( if (
config.serverType === ServerType.SAS9 || config.serverType === ServerType.SAS9 ||
stringifiedData.length > 500000 || stringifiedData.length > 500000 ||
stringifiedData.includes(";") stringifiedData.includes(';')
) { ) {
// file upload approach // file upload approach
for (const tableName in data) { for (const tableName in data) {
@@ -761,14 +761,14 @@ export default class SASjs {
} }
const name = tableName const name = tableName
const csv = convertToCSV(data[tableName]) const csv = convertToCSV(data[tableName])
if (csv === "ERROR: LARGE STRING LENGTH") { if (csv === 'ERROR: LARGE STRING LENGTH') {
isError = true isError = true
errorMsg = 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], { const file = new Blob([csv], {
type: "application/csv" type: 'application/csv'
}) })
formData.append(name, file, `${name}.csv`) formData.append(name, file, `${name}.csv`)
@@ -784,10 +784,10 @@ export default class SASjs {
tableCounter++ tableCounter++
sasjsTables.push(tableName) sasjsTables.push(tableName)
const csv = convertToCSV(data[tableName]) const csv = convertToCSV(data[tableName])
if (csv === "ERROR: LARGE STRING LENGTH") { if (csv === 'ERROR: LARGE STRING LENGTH') {
isError = true isError = true
errorMsg = 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 has length more then 16k, send in chunks
if (csv.length > 16000) { if (csv.length > 16000) {
@@ -800,7 +800,7 @@ export default class SASjs {
requestParams[`sasjs${tableCounter}data`] = csv 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 headers[this.csrfTokenWeb.headerName] = this.csrfTokenWeb.value
} }
fetch(apiUrl, { fetch(apiUrl, {
method: "POST", method: 'POST',
body: formData, body: formData,
referrerPolicy: "same-origin", referrerPolicy: 'same-origin',
headers headers
}) })
.then(async (response) => { .then(async (response) => {
if (!response.ok) { if (!response.ok) {
if (response.status === 403) { if (response.status === 403) {
const tokenHeader = response.headers.get("X-CSRF-HEADER") const tokenHeader = response.headers.get('X-CSRF-HEADER')
if (tokenHeader) { if (tokenHeader) {
const token = response.headers.get(tokenHeader) const token = response.headers.get(tokenHeader)
this.csrfTokenWeb = { this.csrfTokenWeb = {
headerName: tokenHeader, headerName: tokenHeader,
value: token || "" value: token || ''
} }
} }
} }
@@ -878,7 +878,7 @@ export default class SASjs {
this.updateUsername(responseText) this.updateUsername(responseText)
const jsonResponseText = parseWeboutResponse(responseText) const jsonResponseText = parseWeboutResponse(responseText)
if (jsonResponseText !== "") { if (jsonResponseText !== '') {
resolve(JSON.parse(jsonResponseText)) resolve(JSON.parse(jsonResponseText))
} else { } else {
reject({ reject({
@@ -954,14 +954,14 @@ export default class SASjs {
const requestParams: any = {} const requestParams: any = {}
if (this.csrfTokenWeb) { if (this.csrfTokenWeb) {
requestParams["_csrf"] = this.csrfTokenWeb.value requestParams['_csrf'] = this.csrfTokenWeb.value
} }
if (config.debug) { if (config.debug) {
requestParams["_omittextlog"] = "false" requestParams['_omittextlog'] = 'false'
requestParams["_omitsessionresults"] = "false" requestParams['_omitsessionresults'] = 'false'
requestParams["_debug"] = 131 requestParams['_debug'] = 131
} }
return requestParams return requestParams
@@ -971,12 +971,12 @@ export default class SASjs {
try { try {
const responseJson = JSON.parse(response) const responseJson = JSON.parse(response)
if (this.sasjsConfig.serverType === ServerType.SAS9) { if (this.sasjsConfig.serverType === ServerType.SAS9) {
this.userName = responseJson["_METAUSER"] this.userName = responseJson['_METAUSER']
} else { } else {
this.userName = responseJson["SYSUSERID"] this.userName = responseJson['SYSUSERID']
} }
} catch (e) { } catch (e) {
this.userName = "" this.userName = ''
} }
} }
@@ -994,24 +994,24 @@ export default class SASjs {
resolve(resText) resolve(resText)
}) })
} else { } else {
reject("No debug info in response") reject('No debug info in response')
} }
}) })
} }
private async getJobUri(sasJob: string) { private async getJobUri(sasJob: string) {
if (!this.sasViyaApiClient) return "" if (!this.sasViyaApiClient) return ''
const jobMap: any = await this.sasViyaApiClient.getAppLocMap() const jobMap: any = await this.sasViyaApiClient.getAppLocMap()
let uri = "" let uri = ''
if (jobMap.size) { if (jobMap.size) {
const jobKey = sasJob.split("/")[0] const jobKey = sasJob.split('/')[0]
const jobName = sasJob.split("/")[1] const jobName = sasJob.split('/')[1]
const locJobs = jobMap.get(jobKey) const locJobs = jobMap.get(jobKey)
if (locJobs) { if (locJobs) {
const job = locJobs.find( const job = locJobs.find(
(el: any) => el.name === jobName && el.contentType === "jobDefinition" (el: any) => el.name === jobName && el.contentType === 'jobDefinition'
) )
if (job) { if (job) {
uri = job.uri uri = job.uri
@@ -1022,14 +1022,14 @@ export default class SASjs {
} }
private parseSAS9ErrorResponse(response: string) { private parseSAS9ErrorResponse(response: string) {
const logLines = response.split("\n") const logLines = response.split('\n')
const parsedLines: string[] = [] const parsedLines: string[] = []
let firstErrorLineIndex: number = -1 let firstErrorLineIndex: number = -1
logLines.map((line: string, index: number) => { logLines.map((line: string, index: number) => {
if ( if (
line.toLowerCase().includes("error") && line.toLowerCase().includes('error') &&
!line.toLowerCase().includes("this request completed with errors.") && !line.toLowerCase().includes('this request completed with errors.') &&
firstErrorLineIndex === -1 firstErrorLineIndex === -1
) { ) {
firstErrorLineIndex = index firstErrorLineIndex = index
@@ -1040,7 +1040,7 @@ export default class SASjs {
parsedLines.push(logLines[i]) parsedLines.push(logLines[i])
} }
return parsedLines.join(", ") return parsedLines.join(', ')
} }
private parseLogFromResponse(response: any, program: string) { private parseLogFromResponse(response: any, program: string) {
@@ -1058,7 +1058,7 @@ export default class SASjs {
private fetchLogFileContent(logLink: string) { private fetchLogFileContent(logLink: string) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch(logLink, { fetch(logLink, {
method: "GET" method: 'GET'
}) })
.then((response: any) => response.text()) .then((response: any) => response.text())
.then((response: any) => resolve(response)) .then((response: any) => resolve(response))
@@ -1071,8 +1071,8 @@ export default class SASjs {
program: string, program: string,
pgmData: any pgmData: any
) { ) {
let sourceCode = "" let sourceCode = ''
let generatedCode = "" let generatedCode = ''
let sasWork = null let sasWork = null
if (response && response.result && response.log) { if (response && response.result && response.log) {
@@ -1154,7 +1154,7 @@ export default class SASjs {
private setupConfiguration() { private setupConfiguration() {
if ( if (
this.sasjsConfig.serverUrl === undefined || this.sasjsConfig.serverUrl === undefined ||
this.sasjsConfig.serverUrl === "" this.sasjsConfig.serverUrl === ''
) { ) {
let url = `${location.protocol}//${location.hostname}` let url = `${location.protocol}//${location.hostname}`
if (location.port) { if (location.port) {
@@ -1163,7 +1163,7 @@ export default class SASjs {
this.sasjsConfig.serverUrl = url 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) 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.loginUrl = `${this.sasjsConfig.serverUrl}/SASLogon/login`
this.logoutUrl = this.logoutUrl =
this.sasjsConfig.serverType === ServerType.SAS9 this.sasjsConfig.serverType === ServerType.SAS9
? "/SASLogon/logout?" ? '/SASLogon/logout?'
: "/SASLogon/logout.do?" : '/SASLogon/logout.do?'
if (this.sasjsConfig.serverType === ServerType.SASViya) { if (this.sasjsConfig.serverType === ServerType.SASViya) {
if (this.sasViyaApiClient) if (this.sasViyaApiClient)
@@ -1206,8 +1206,8 @@ export default class SASjs {
} }
private setLoginUrl = (matches: RegExpExecArray) => { private setLoginUrl = (matches: RegExpExecArray) => {
let parsedURL = matches[1].replace(/\?.*/, "") let parsedURL = matches[1].replace(/\?.*/, '')
if (parsedURL[0] === "/") { if (parsedURL[0] === '/') {
parsedURL = parsedURL.substr(1) parsedURL = parsedURL.substr(1)
const tempLoginLink = this.sasjsConfig.serverUrl const tempLoginLink = this.sasjsConfig.serverUrl
@@ -1219,7 +1219,7 @@ export default class SASjs {
this.loginUrl = this.loginUrl =
this.sasjsConfig.serverType === ServerType.SASViya this.sasjsConfig.serverType === ServerType.SASViya
? tempLoginLink ? tempLoginLink
: loginUrl.replace(".do", "") : loginUrl.replace('.do', '')
} }
} }
@@ -1251,7 +1251,7 @@ export default class SASjs {
) { ) {
await asyncForEach(membersJson, async (member: any) => { await asyncForEach(membersJson, async (member: any) => {
switch (member.type) { switch (member.type) {
case "folder": case 'folder':
await this.createFolder( await this.createFolder(
member.name, member.name,
parentFolder, parentFolder,
@@ -1260,7 +1260,7 @@ export default class SASjs {
sasApiClient sasApiClient
) )
break break
case "service": case 'service':
await this.createJobDefinition( await this.createJobDefinition(
member.name, member.name,
member.code, member.code,
@@ -1273,7 +1273,7 @@ export default class SASjs {
default: 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) if (member.type === 'folder' && member.members && member.members.length)
await this.createFoldersAndServices( await this.createFoldersAndServices(
`${parentFolder}/${member.name}`, `${parentFolder}/${member.name}`,
member.members, member.members,

View File

@@ -1,5 +1,5 @@
import { Session, Context, CsrfToken } from "./types" import { Session, Context, CsrfToken } from './types'
import { asyncForEach, makeRequest } from "./utils" import { asyncForEach, makeRequest } from './utils'
const MAX_SESSION_COUNT = 1 const MAX_SESSION_COUNT = 1
@@ -32,7 +32,7 @@ export class SessionManager {
async clearSession(id: string, accessToken?: string) { async clearSession(id: string, accessToken?: string) {
const deleteSessionRequest = { const deleteSessionRequest = {
method: "DELETE", method: 'DELETE',
headers: this.getHeaders(accessToken) headers: this.getHeaders(accessToken)
} }
return await this.request<Session>( return await this.request<Session>(
@@ -57,7 +57,7 @@ export class SessionManager {
private async createAndWaitForSession(accessToken?: string) { private async createAndWaitForSession(accessToken?: string) {
const createSessionRequest = { const createSessionRequest = {
method: "POST", method: 'POST',
headers: this.getHeaders(accessToken) headers: this.getHeaders(accessToken)
} }
const { result: createdSession, etag } = await this.request<Session>( const { result: createdSession, etag } = await this.request<Session>(
@@ -99,7 +99,7 @@ export class SessionManager {
private getHeaders(accessToken?: string) { private getHeaders(accessToken?: string) {
const headers: any = { const headers: any = {
"Content-Type": "application/json" 'Content-Type': 'application/json'
} }
if (accessToken) { if (accessToken) {
headers.Authorization = `Bearer ${accessToken}` headers.Authorization = `Bearer ${accessToken}`
@@ -117,21 +117,21 @@ export class SessionManager {
let sessionState = session.state let sessionState = session.state
const headers: any = { const headers: any = {
...this.getHeaders(accessToken), ...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, _) => { return new Promise(async (resolve, _) => {
if (sessionState === "pending") { if (sessionState === 'pending') {
if (stateLink) { if (stateLink) {
if (!silent) { if (!silent) {
console.log("Polling session status... \n") console.log('Polling session status... \n')
} }
const { result: state } = await this.request<string>( const { result: state } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?wait=30`, `${this.serverUrl}${stateLink.href}?wait=30`,
{ {
headers headers
}, },
"text" 'text'
) )
sessionState = state.trim() sessionState = state.trim()
@@ -149,7 +149,7 @@ export class SessionManager {
private async request<T>( private async request<T>(
url: string, url: string,
options: RequestInit, options: RequestInit,
contentType: "text" | "json" = "json" contentType: 'text' | 'json' = 'json'
) { ) {
if (this.csrfToken) { if (this.csrfToken) {
options.headers = { options.headers = {

View File

@@ -1,5 +1,5 @@
import SASjs from "./SASjs" import SASjs from './SASjs'
export * from "./types" export * from './types'
export * from "./SASViyaApiClient" export * from './SASViyaApiClient'
export * from "./SAS9ApiClient" export * from './SAS9ApiClient'
export default SASjs export default SASjs

View File

@@ -1,4 +1,4 @@
import { Link } from "./Link" import { Link } from './Link'
export interface Folder { export interface Folder {
id: string id: string

View File

@@ -1,5 +1,5 @@
import { Link } from "./Link" import { Link } from './Link'
import { JobResult } from "./JobResult" import { JobResult } from './JobResult'
export interface Job { export interface Job {
id: string id: string

View File

@@ -1,3 +1,3 @@
export interface JobResult { export interface JobResult {
"_webout.json": string '_webout.json': string
} }

View File

@@ -1,4 +1,4 @@
import { ServerType } from "./ServerType" import { ServerType } from './ServerType'
/** /**
* Specifies the configuration for the SASjs instance. * 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 * Can be omitted, eg if serving directly from the SAS Web Server or being
* streamed. * streamed.
*/ */
serverUrl: string = "" serverUrl: string = ''
pathSAS9: string = "" pathSAS9: string = ''
pathSASViya: string = "" pathSASViya: string = ''
/** /**
* The appLoc is the parent folder under which the SAS services (STPs or Job * The appLoc is the parent folder under which the SAS services (STPs or Job
* Execution Services) are stored. * Execution Services) are stored.
*/ */
appLoc: string = "" appLoc: string = ''
/** /**
* Can be SAS9 or SASVIYA * Can be SAS9 or SASVIYA
*/ */
@@ -26,6 +26,6 @@ export class SASjsConfig {
* Set to `true` to enable additional debugging. * Set to `true` to enable additional debugging.
*/ */
debug: boolean = true debug: boolean = true
contextName: string = "" contextName: string = ''
useComputeApi = false useComputeApi = false
} }

View File

@@ -3,6 +3,6 @@
* *
*/ */
export enum ServerType { export enum ServerType {
SASViya = "SASVIYA", SASViya = 'SASVIYA',
SAS9 = "SAS9" SAS9 = 'SAS9'
} }

View File

@@ -1,4 +1,4 @@
import { Link } from "./Link" import { Link } from './Link'
export interface Session { export interface Session {
id: string id: string

View File

@@ -1,11 +1,11 @@
export * from "./Context" export * from './Context'
export * from "./CsrfToken" export * from './CsrfToken'
export * from "./Folder" export * from './Folder'
export * from "./Job" export * from './Job'
export * from "./Link" export * from './Link'
export * from "./SASjsConfig" export * from './SASjsConfig'
export * from "./SASjsRequest" export * from './SASjsRequest'
export * from "./SASjsWaitingRequest" export * from './SASjsWaitingRequest'
export * from "./ServerType" export * from './ServerType'
export * from "./Session" export * from './Session'
export * from "./UploadFile" export * from './UploadFile'

View File

@@ -1,4 +1,4 @@
import { SASjsRequest } from "../types/SASjsRequest" import { SASjsRequest } from '../types/SASjsRequest'
/** /**
* Comparator for SASjs request timestamps * Comparator for SASjs request timestamps

View File

@@ -3,7 +3,7 @@
* @param data - the JSON object to convert. * @param data - the JSON object to convert.
*/ */
export const convertToCSV = (data: any) => { 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]) const headerFields = Object.keys(data[0])
let csvTest let csvTest
let invalidString = false let invalidString = false
@@ -14,31 +14,31 @@ export const convertToCSV = (data: any) => {
const longestValueForField = data const longestValueForField = data
.map((row: any, index: number) => { .map((row: any, index: number) => {
if (row[field] || row[field] === "") { if (row[field] || row[field] === '') {
if (firstFoundType) { if (firstFoundType) {
let currentFieldType = let currentFieldType =
row[field] === "" || typeof row[field] === "string" row[field] === '' || typeof row[field] === 'string'
? "chars" ? 'chars'
: "number" : 'number'
if (!hasMixedTypes) { if (!hasMixedTypes) {
hasMixedTypes = currentFieldType !== firstFoundType hasMixedTypes = currentFieldType !== firstFoundType
rowNumError = hasMixedTypes ? index + 1 : -1 rowNumError = hasMixedTypes ? index + 1 : -1
} }
} else { } else {
if (row[field] === "") { if (row[field] === '') {
firstFoundType = "chars" firstFoundType = 'chars'
} else { } else {
firstFoundType = firstFoundType =
typeof row[field] === "string" ? "chars" : "number" typeof row[field] === 'string' ? 'chars' : 'number'
} }
} }
let byteSize let byteSize
if (typeof row[field] === "string") { if (typeof row[field] === 'string') {
let doubleQuotesFound = row[field] let doubleQuotesFound = row[field]
.split("") .split('')
.filter((char: any) => char === '"') .filter((char: any) => char === '"')
byteSize = getByteSize(row[field]) byteSize = getByteSize(row[field])
@@ -61,17 +61,17 @@ export const convertToCSV = (data: any) => {
) )
} }
return `${field}:${firstFoundType === "chars" ? "$" : ""}${ return `${field}:${firstFoundType === 'chars' ? '$' : ''}${
longestValueForField longestValueForField
? longestValueForField ? longestValueForField
: firstFoundType === "chars" : firstFoundType === 'chars'
? "1" ? '1'
: "best" : 'best'
}.` }.`
}) })
if (invalidString) { if (invalidString) {
return "ERROR: LARGE STRING LENGTH" return 'ERROR: LARGE STRING LENGTH'
} }
csvTest = data.map((row: any) => { csvTest = data.map((row: any) => {
const fields = Object.keys(row).map((fieldName, index) => { const fields = Object.keys(row).map((fieldName, index) => {
@@ -86,15 +86,15 @@ export const convertToCSV = (data: any) => {
value = JSON.stringify(currentCell, replacer) value = JSON.stringify(currentCell, replacer)
} }
value = value.replace(/\\\\/gm, "\\") value = value.replace(/\\\\/gm, '\\')
if (containsSpecialChar) { if (containsSpecialChar) {
if (value.includes(",") || value.includes('"')) { if (value.includes(',') || value.includes('"')) {
value = '"' + value + '"' value = '"' + value + '"'
} }
} else { } else {
if ( if (
!value.includes(",") && !value.includes(',') &&
value.includes('"') && value.includes('"') &&
!value.includes('\\"') !value.includes('\\"')
) { ) {
@@ -104,19 +104,19 @@ export const convertToCSV = (data: any) => {
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")) { if (value === '' && headers[index].includes('best')) {
value = "." value = '.'
} }
return value return value
}) })
return fields.join(",") return fields.join(',')
}) })
let finalCSV = 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
} }

View File

@@ -1,5 +1,5 @@
import { convertToCSV } from "./convertToCsv" import { convertToCSV } from './convertToCsv'
import { splitChunks } from "./splitChunks" import { splitChunks } from './splitChunks'
export const formatDataForRequest = (data: any) => { export const formatDataForRequest = (data: any) => {
const sasjsTables = [] const sasjsTables = []
@@ -10,9 +10,9 @@ export const formatDataForRequest = (data: any) => {
tableCounter++ tableCounter++
sasjsTables.push(tableName) sasjsTables.push(tableName)
const csv = convertToCSV(data[tableName]) const csv = convertToCSV(data[tableName])
if (csv === "ERROR: LARGE STRING LENGTH") { if (csv === 'ERROR: LARGE STRING LENGTH') {
throw new Error( 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 // 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${tableCounter}data`] = csv
} }
} }
result["sasjs_tables"] = sasjsTables.join(" ") result['sasjs_tables'] = sasjsTables.join(' ')
return result return result
} }

View File

@@ -1,15 +1,15 @@
export * from "./asyncForEach" export * from './asyncForEach'
export * from "./compareTimestamps" export * from './compareTimestamps'
export * from "./convertToCsv" export * from './convertToCsv'
export * from "./isAuthorizeFormRequired" export * from './isAuthorizeFormRequired'
export * from "./isLoginRequired" export * from './isLoginRequired'
export * from "./isLoginSuccess" export * from './isLoginSuccess'
export * from "./makeRequest" export * from './makeRequest'
export * from "./needsRetry" export * from './needsRetry'
export * from "./parseAndSubmitAuthorizeForm" export * from './parseAndSubmitAuthorizeForm'
export * from "./parseGeneratedCode" export * from './parseGeneratedCode'
export * from "./parseSourceCode" export * from './parseSourceCode'
export * from "./parseSasViyaLog" export * from './parseSasViyaLog'
export * from "./serialize" export * from './serialize'
export * from "./splitChunks" export * from './splitChunks'
export * from "./parseWeboutResponse" export * from './parseWeboutResponse'

View File

@@ -1,29 +1,29 @@
export function isIEorEdgeOrOldFirefox() { export function isIEorEdgeOrOldFirefox() {
if (typeof window === "undefined") { if (typeof window === 'undefined') {
return false return false
} }
const ua = window.navigator.userAgent const ua = window.navigator.userAgent
if (ua.indexOf("Firefox") > 0) { if (ua.indexOf('Firefox') > 0) {
const version = parseInt( const version = parseInt(
ua.substring(ua.lastIndexOf("Firefox/") + 8, ua.length), ua.substring(ua.lastIndexOf('Firefox/') + 8, ua.length),
10 10
) )
return version <= 60 return version <= 60
} }
const msie = ua.indexOf("MSIE ") const msie = ua.indexOf('MSIE ')
if (msie > 0) { if (msie > 0) {
// IE 10 or older => return version number // IE 10 or older => return version number
return true return true
} }
const trident = ua.indexOf("Trident/") const trident = ua.indexOf('Trident/')
if (trident > 0) { if (trident > 0) {
return true return true
} }
const edge = ua.indexOf("Edge/") const edge = ua.indexOf('Edge/')
if (edge > 0) { if (edge > 0) {
// Edge (IE 12+) => return version number // Edge (IE 12+) => return version number
return true return true

View File

@@ -1,5 +1,5 @@
import { CsrfToken } from "../types" import { CsrfToken } from '../types'
import { needsRetry } from "./needsRetry" import { needsRetry } from './needsRetry'
let retryCount: number = 0 let retryCount: number = 0
let retryLimit: number = 5 let retryLimit: number = 5
@@ -8,28 +8,28 @@ export async function makeRequest<T>(
url: string, url: string,
request: RequestInit, request: RequestInit,
callback: (value: CsrfToken) => any, callback: (value: CsrfToken) => any,
contentType: "text" | "json" = "json" contentType: 'text' | 'json' = 'json'
): Promise<{ result: T; etag: string | null }> { ): Promise<{ result: T; etag: string | null }> {
let retryRequest: any = null let retryRequest: any = null
const responseTransform = const responseTransform =
contentType === "json" contentType === 'json'
? (res: Response) => res.json() ? (res: Response) => res.json()
: (res: Response) => res.text() : (res: Response) => res.text()
let etag = null let etag = null
const result = await fetch(url, request).then(async (response) => { 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 }) return Promise.reject({ status: 401 })
} }
if (!response.ok) { if (!response.ok) {
if (response.status === 403) { if (response.status === 403) {
const tokenHeader = response.headers.get("X-CSRF-HEADER") const tokenHeader = response.headers.get('X-CSRF-HEADER')
if (tokenHeader) { if (tokenHeader) {
const token = response.headers.get(tokenHeader) const token = response.headers.get(tokenHeader)
callback({ callback({
headerName: tokenHeader, headerName: tokenHeader,
value: token || "" value: token || ''
}) })
retryRequest = { retryRequest = {
@@ -37,7 +37,7 @@ export async function makeRequest<T>(
headers: { ...request.headers, [tokenHeader]: token } headers: { ...request.headers, [tokenHeader]: token }
} }
return fetch(url, retryRequest).then((res) => { return fetch(url, retryRequest).then((res) => {
etag = res.headers.get("ETag") etag = res.headers.get('ETag')
return responseTransform(res) return responseTransform(res)
}) })
} }
@@ -60,7 +60,7 @@ export async function makeRequest<T>(
} else { } else {
retryCount = 0 retryCount = 0
throw new Error("Request retry limit exceeded") throw new Error('Request retry limit exceeded')
} }
} }
@@ -71,9 +71,9 @@ export async function makeRequest<T>(
return Promise.resolve() return Promise.resolve()
} }
const responseTransformed = await responseTransform(response) const responseTransformed = await responseTransform(response)
let responseText = "" let responseText = ''
if (typeof responseTransformed === "string") { if (typeof responseTransformed === 'string') {
responseText = responseTransformed responseText = responseTransformed
} else { } else {
responseText = JSON.stringify(responseTransformed) responseText = JSON.stringify(responseTransformed)
@@ -95,11 +95,11 @@ export async function makeRequest<T>(
} else { } else {
retryCount = 0 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 return responseTransformed
} }
}) })

View File

@@ -2,13 +2,13 @@ export const needsRetry = (responseText: string): boolean => {
return ( return (
!!responseText && !!responseText &&
((responseText.includes('"errorCode":403') && ((responseText.includes('"errorCode":403') &&
responseText.includes("_csrf") && responseText.includes('_csrf') &&
responseText.includes("X-CSRF-TOKEN")) || responseText.includes('X-CSRF-TOKEN')) ||
(responseText.includes('"status":403') && (responseText.includes('"status":403') &&
responseText.includes('"error":"Forbidden"')) || responseText.includes('"error":"Forbidden"')) ||
(responseText.includes('"status":449') && (responseText.includes('"status":449') &&
responseText.includes( responseText.includes(
"Authentication success, retry original request" 'Authentication success, retry original request'
))) )))
) )
} }

View File

@@ -5,18 +5,18 @@ export const parseAndSubmitAuthorizeForm = async (
let authUrl: string | null = null let authUrl: string | null = null
const params: any = {} const params: any = {}
const responseBody = response.split("<body>")[1].split("</body>")[0] const responseBody = response.split('<body>')[1].split('</body>')[0]
const bodyElement = document.createElement("div") const bodyElement = document.createElement('div')
bodyElement.innerHTML = responseBody bodyElement.innerHTML = responseBody
const form = bodyElement.querySelector("#application_authorization") const form = bodyElement.querySelector('#application_authorization')
authUrl = form ? serverUrl + form.getAttribute("action") : null authUrl = form ? serverUrl + form.getAttribute('action') : null
const inputs: any = form?.querySelectorAll("input") const inputs: any = form?.querySelectorAll('input')
for (const input of inputs) { for (const input of inputs) {
if (input.name === "user_oauth_approval") { if (input.name === 'user_oauth_approval') {
input.value = "true" input.value = 'true'
} }
params[input.name] = input.value params[input.name] = input.value
@@ -33,17 +33,17 @@ export const parseAndSubmitAuthorizeForm = async (
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (authUrl) { if (authUrl) {
fetch(authUrl, { fetch(authUrl, {
method: "POST", method: 'POST',
credentials: "include", credentials: 'include',
body: formData, body: formData,
referrerPolicy: "same-origin" referrerPolicy: 'same-origin'
}) })
.then((res) => res.text()) .then((res) => res.text())
.then((res) => { .then((res) => {
resolve(res) resolve(res)
}) })
} else { } else {
reject("Auth form url is null") reject('Auth form url is null')
} }
}) })
} }

View File

@@ -1,7 +1,7 @@
export const parseGeneratedCode = (log: string) => { export const parseGeneratedCode = (log: string) => {
const startsWith = "MPRINT" const startsWith = 'MPRINT'
const isGeneratedCodeLine = (line: string) => const isGeneratedCodeLine = (line: string) =>
line.trim().startsWith(startsWith) line.trim().startsWith(startsWith)
const logLines = log.split("\n").filter(isGeneratedCodeLine) const logLines = log.split('\n').filter(isGeneratedCodeLine)
return logLines.join("\r\n") return logLines.join('\r\n')
} }

View File

@@ -2,10 +2,10 @@ export const parseSasViyaLog = (logResponse: { items: any[] }) => {
let log let log
try { try {
log = logResponse.items log = logResponse.items
? logResponse.items.map((i) => i.line).join("\n") ? logResponse.items.map((i) => i.line).join('\n')
: JSON.stringify(logResponse) : JSON.stringify(logResponse)
} catch (e) { } 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 log = logResponse
} }
return log return log

View File

@@ -1,6 +1,6 @@
export const parseSourceCode = (log: string): string => { export const parseSourceCode = (log: string): string => {
const isSourceCodeLine = (line: string) => const isSourceCodeLine = (line: string) =>
line.trim().substring(0, 10).trimStart().match(/^\d/) line.trim().substring(0, 10).trimStart().match(/^\d/)
const logLines = log.split("\n").filter(isSourceCodeLine) const logLines = log.split('\n').filter(isSourceCodeLine)
return logLines.join("\r\n") return logLines.join('\r\n')
} }

View File

@@ -1,13 +1,13 @@
export const parseWeboutResponse = (response: string) => { export const parseWeboutResponse = (response: string) => {
let sasResponse = "" let sasResponse = ''
if (response.includes(">>weboutBEGIN<<")) { if (response.includes('>>weboutBEGIN<<')) {
try { try {
sasResponse = response sasResponse = response
.split(">>weboutBEGIN<<")[1] .split('>>weboutBEGIN<<')[1]
.split(">>weboutEND<<")[0] .split('>>weboutEND<<')[0]
} catch (e) { } catch (e) {
sasResponse = "" sasResponse = ''
console.error(e) console.error(e)
} }
} }

View File

@@ -4,12 +4,12 @@ export const serialize = (obj: any) => {
if (obj.hasOwnProperty(p)) { if (obj.hasOwnProperty(p)) {
if (obj[p] instanceof Array) { if (obj[p] instanceof Array) {
for (let i = 0, n = obj[p].length; i < n; i++) { 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 { } else {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])) str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]))
} }
} }
} }
return str.join("&") return str.join('&')
} }