1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-11 01:14:36 +00:00

Merge branch 'master' into issue-409

This commit is contained in:
Sabir Hassan
2021-06-30 15:35:43 +05:00
committed by GitHub
19 changed files with 36579 additions and 947 deletions

View File

@@ -6,7 +6,7 @@ GREEN="\033[1;32m"
# temporary file which holds the message).
commit_message=$(cat "$1")
if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 \-]+\))?!?: .+$") then
if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 \-\*]+\))?!?: .+$") then
echo "${GREEN} ✔ Commit message meets Conventional Commit standards"
exit 0
fi

View File

@@ -189,6 +189,8 @@ In this setup, all requests are routed through the JES web app, at `YOURSERVER/S
}
```
Note - to use the web approach, the `useComputeApi` property must be `undefined` or `null`.
### Using the JES API
Here we are running Jobs using the Job Execution Service except this time we are making the requests directly using the REST API instead of through the JES Web App. This is helpful when we need to call web services outside of a browser (eg with the SASjs CLI or other commandline tools). To save one network request, the adapter prefetches the JOB URIs and passes them in the `__job` parameter. Depending on your network bandwidth, it may or may not be faster than the JES Web approach.

15148
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -43,32 +43,33 @@
"@types/tough-cookie": "^4.0.0",
"cp": "^0.2.0",
"dotenv": "^10.0.0",
"jest": "^27.0.4",
"jest": "^27.0.6",
"jest-extended": "^0.11.5",
"mime": "^2.5.2",
"path": "^0.12.7",
"process": "^0.11.10",
"rimraf": "^3.0.2",
"semantic-release": "^17.4.4",
"terser-webpack-plugin": "^5.1.3",
"terser-webpack-plugin": "^5.1.4",
"ts-jest": "^27.0.3",
"ts-loader": "^9.2.2",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typedoc": "^0.20.36",
"typedoc": "^0.21.2",
"typedoc-neo-theme": "^1.1.1",
"typedoc-plugin-external-module-name": "^4.0.6",
"typescript": "^4.3.2",
"webpack": "^5.38.1",
"typescript": "^4.3.4",
"webpack": "^5.41.1",
"webpack-cli": "^4.7.2"
},
"main": "index.js",
"dependencies": {
"@sasjs/utils": "^2.20.1",
"@sasjs/utils": "^2.21.0",
"axios": "^0.21.1",
"axios-cookiejar-support": "^1.0.1",
"form-data": "^4.0.0",
"https": "^1.0.0",
"jwt-decode": "^3.1.2",
"tough-cookie": "^4.0.0",
"url": "^0.11.0"
}

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
'/Public/app/common/sendArr',
data,
{},
'',
undefined,
true
)
},

View File

@@ -2,6 +2,7 @@ import { Context, EditContextInput, ContextAllAttributes } from './types'
import { isUrl } from './utils'
import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from './request/RequestClient'
import { AuthConfig } from '@sasjs/utils/types'
export class ContextManager {
private defaultComputeContexts = [
@@ -328,12 +329,12 @@ export class ContextManager {
public async getExecutableContexts(
executeScript: Function,
accessToken?: string
authConfig?: AuthConfig
) {
const { result: contexts } = await this.requestClient
.get<{ items: Context[] }>(
`${this.serverUrl}/compute/contexts?limit=10000`,
accessToken
authConfig?.access_token
)
.catch((err) => {
throw prefixMessage(err, 'Error while fetching compute contexts.')
@@ -350,7 +351,7 @@ export class ContextManager {
`test-${context.name}`,
linesOfCode,
context.name,
accessToken,
authConfig,
null,
false,
true,

View File

@@ -3,7 +3,9 @@ import {
isRelativePath,
isUri,
isUrl,
fetchLogByChunks
fetchLogByChunks,
isAccessTokenExpiring,
isRefreshTokenExpiring
} from './utils'
import * as NodeFormData from 'form-data'
import {
@@ -29,7 +31,7 @@ import { timestampToYYYYMMDDHHMMSS } from '@sasjs/utils/time'
import { Logger, LogLevel } from '@sasjs/utils/logger'
import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired'
import { RequestClient } from './request/RequestClient'
import { SasAuthResponse, MacroVar } from '@sasjs/utils/types'
import { SasAuthResponse, MacroVar, AuthConfig } from '@sasjs/utils/types'
import { prefixMessage } from '@sasjs/utils/error'
import * as mime from 'mime'
@@ -130,14 +132,14 @@ export class SASViyaApiClient {
/**
* Returns all compute contexts on this server that the user has access to.
* @param accessToken - an access token for an authorized user.
* @param authConfig - an access token, refresh token, client and secret for an authorized user.
*/
public async getExecutableContexts(accessToken?: string) {
public async getExecutableContexts(authConfig?: AuthConfig) {
const bindedExecuteScript = this.executeScript.bind(this)
return await this.contextManager.getExecutableContexts(
bindedExecuteScript,
accessToken
authConfig
)
}
@@ -266,7 +268,7 @@ export class SASViyaApiClient {
* @param jobPath - the path to the file being submitted for execution.
* @param linesOfCode - an array of code lines to execute.
* @param contextName - the context to execute the code in.
* @param accessToken - an access token for an authorized user.
* @param authConfig - an object containing an access token, refresh token, client ID and secret.
* @param data - execution data.
* @param debug - when set to true, the log will be returned.
* @param expectWebout - when set to true, the automatic _webout fileref will be checked for content, and that content returned. This fileref is used when the Job contains a SASjs web request (as opposed to executing arbitrary SAS code).
@@ -279,7 +281,7 @@ export class SASViyaApiClient {
jobPath: string,
linesOfCode: string[],
contextName: string,
accessToken?: string,
authConfig?: AuthConfig,
data = null,
debug: boolean = false,
expectWebout = false,
@@ -288,17 +290,20 @@ export class SASViyaApiClient {
printPid = false,
variables?: MacroVar
): Promise<any> {
const { access_token } = authConfig || {}
const logger = process.logger || console
try {
const headers: any = {
'Content-Type': 'application/json'
}
if (accessToken) headers.Authorization = `Bearer ${accessToken}`
if (access_token) headers.Authorization = `Bearer ${access_token}`
let executionSessionId: string
const session = await this.sessionManager
.getSession(accessToken)
.getSession(access_token)
.catch((err) => {
throw prefixMessage(err, 'Error while getting session. ')
})
@@ -307,7 +312,7 @@ export class SASViyaApiClient {
if (printPid) {
const { result: jobIdVariable } = await this.sessionManager
.getVariable(executionSessionId, 'SYSJOBID', accessToken)
.getVariable(executionSessionId, 'SYSJOBID', access_token)
.catch((err) => {
throw prefixMessage(err, 'Error while getting session variable. ')
})
@@ -339,7 +344,6 @@ export class SASViyaApiClient {
if (debug) {
jobArguments['_OMITTEXTLOG'] = false
jobArguments['_OMITSESSIONRESULTS'] = false
jobArguments['_DEBUG'] = 131
}
let fileName
@@ -362,11 +366,13 @@ export class SASViyaApiClient {
if (variables) jobVariables = { ...jobVariables, ...variables }
if (debug) jobVariables = { ...jobVariables, _DEBUG: 131 }
let files: any[] = []
if (data) {
if (JSON.stringify(data).includes(';')) {
files = await this.uploadTables(data, accessToken).catch((err) => {
files = await this.uploadTables(data, access_token).catch((err) => {
throw prefixMessage(err, 'Error while uploading tables. ')
})
@@ -396,7 +402,7 @@ export class SASViyaApiClient {
.post<Job>(
`/compute/sessions/${executionSessionId}/jobs`,
jobRequestBody,
accessToken
access_token
)
.catch((err) => {
throw prefixMessage(err, 'Error while posting job. ')
@@ -405,8 +411,8 @@ export class SASViyaApiClient {
if (!waitForResult) return session
if (debug) {
console.log(`Job has been submitted for '${fileName}'.`)
console.log(
logger.info(`Job has been submitted for '${fileName}'.`)
logger.info(
`You can monitor the job progress at '${this.serverUrl}${
postedJob.links.find((l: any) => l.rel === 'state')!.href
}'.`
@@ -416,7 +422,7 @@ export class SASViyaApiClient {
const jobStatus = await this.pollJobState(
postedJob,
etag,
accessToken,
authConfig,
pollOptions
).catch(async (err) => {
const error = err?.response?.data
@@ -429,7 +435,7 @@ export class SASViyaApiClient {
const logCount = 1000000
err.log = await fetchLogByChunks(
this.requestClient,
accessToken!,
access_token!,
sessionLogUrl,
logCount
)
@@ -440,7 +446,7 @@ export class SASViyaApiClient {
const { result: currentJob } = await this.requestClient
.get<Job>(
`/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
accessToken
access_token
)
.catch((err) => {
throw prefixMessage(err, 'Error while getting job. ')
@@ -456,7 +462,7 @@ export class SASViyaApiClient {
const logCount = currentJob.logStatistics?.lineCount ?? 1000000
log = await fetchLogByChunks(
this.requestClient,
accessToken!,
access_token!,
logUrl,
logCount
)
@@ -476,7 +482,7 @@ export class SASViyaApiClient {
if (resultLink) {
jobResult = await this.requestClient
.get<any>(resultLink, accessToken, 'text/plain')
.get<any>(resultLink, access_token, 'text/plain')
.catch(async (e) => {
if (e instanceof NotFoundError) {
if (logLink) {
@@ -484,7 +490,7 @@ export class SASViyaApiClient {
const logCount = currentJob.logStatistics?.lineCount ?? 1000000
log = await fetchLogByChunks(
this.requestClient,
accessToken!,
access_token!,
logUrl,
logCount
)
@@ -503,7 +509,7 @@ export class SASViyaApiClient {
}
await this.sessionManager
.clearSession(executionSessionId, accessToken)
.clearSession(executionSessionId, access_token)
.catch((err) => {
throw prefixMessage(err, 'Error while clearing session. ')
})
@@ -515,7 +521,7 @@ export class SASViyaApiClient {
jobPath,
linesOfCode,
contextName,
accessToken,
authConfig,
data,
debug,
false,
@@ -602,6 +608,7 @@ export class SASViyaApiClient {
accessToken?: string,
isForced?: boolean
): Promise<Folder> {
const logger = process.logger || console
if (!parentFolderPath && !parentFolderUri) {
throw new Error('Path or URI of the parent folder is required.')
}
@@ -609,7 +616,7 @@ export class SASViyaApiClient {
if (!parentFolderUri && parentFolderPath) {
parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken)
if (!parentFolderUri) {
console.log(
logger.info(
`Parent folder at path '${parentFolderPath}' is not present.`
)
@@ -621,7 +628,7 @@ export class SASViyaApiClient {
if (newParentFolderPath === '') {
throw new Error('Root folder has to be present on the server.')
}
console.log(
logger.info(
`Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'`
)
const parentFolder = await this.createFolder(
@@ -630,7 +637,7 @@ export class SASViyaApiClient {
undefined,
accessToken
)
console.log(
logger.info(
`Parent folder '${newFolderName}' has been successfully created.`
)
parentFolderUri = `/folders/folders/${parentFolder.id}`
@@ -872,13 +879,15 @@ export class SASViyaApiClient {
contextName: string,
debug?: boolean,
data?: any,
accessToken?: string,
authConfig?: AuthConfig,
waitForResult = true,
expectWebout = false,
pollOptions?: PollOptions,
printPid = false,
variables?: MacroVar
) {
let { access_token } = authConfig || {}
if (isRelativePath(sasJob) && !this.rootFolderName) {
throw new Error(
'Relative paths cannot be used without specifying a root folder name'
@@ -892,7 +901,7 @@ export class SASViyaApiClient {
? `${this.rootFolderName}/${folderPath}`
: folderPath
await this.populateFolderMap(fullFolderPath, accessToken).catch((err) => {
await this.populateFolderMap(fullFolderPath, access_token).catch((err) => {
throw prefixMessage(err, 'Error while populating folder map. ')
})
@@ -906,8 +915,8 @@ export class SASViyaApiClient {
const headers: any = { 'Content-Type': 'application/json' }
if (!!accessToken) {
headers.Authorization = `Bearer ${accessToken}`
if (!!access_token) {
headers.Authorization = `Bearer ${access_token}`
}
const jobToExecute = jobFolder?.find((item) => item.name === jobName)
@@ -930,7 +939,7 @@ export class SASViyaApiClient {
const { result: jobDefinition } = await this.requestClient
.get<JobDefinition>(
`${this.serverUrl}${jobDefinitionLink.href}`,
accessToken
access_token
)
.catch((err) => {
throw prefixMessage(err, 'Error while getting job definition. ')
@@ -950,7 +959,7 @@ export class SASViyaApiClient {
sasJob,
linesToExecute,
contextName,
accessToken,
authConfig,
data,
debug,
expectWebout,
@@ -974,8 +983,9 @@ export class SASViyaApiClient {
contextName: string,
debug: boolean,
data?: any,
accessToken?: string
authConfig?: AuthConfig
) {
let { access_token } = authConfig || {}
if (isRelativePath(sasJob) && !this.rootFolderName) {
throw new Error(
'Relative paths cannot be used without specifying a root folder name.'
@@ -988,7 +998,7 @@ export class SASViyaApiClient {
const fullFolderPath = isRelativePath(sasJob)
? `${this.rootFolderName}/${folderPath}`
: folderPath
await this.populateFolderMap(fullFolderPath, accessToken)
await this.populateFolderMap(fullFolderPath, access_token)
const jobFolder = this.folderMap.get(fullFolderPath)
if (!jobFolder) {
@@ -1001,7 +1011,7 @@ export class SASViyaApiClient {
let files: any[] = []
if (data && Object.keys(data).length) {
files = await this.uploadTables(data, accessToken)
files = await this.uploadTables(data, access_token)
}
if (!jobToExecute) {
@@ -1013,7 +1023,7 @@ export class SASViyaApiClient {
const { result: jobDefinition } = await this.requestClient.get<Job>(
`${this.serverUrl}${jobDefinitionLink}`,
accessToken
access_token
)
const jobArguments: { [key: string]: any } = {
@@ -1049,18 +1059,18 @@ export class SASViyaApiClient {
const { result: postedJob, etag } = await this.requestClient.post<Job>(
`${this.serverUrl}/jobExecution/jobs?_action=wait`,
postJobRequestBody,
accessToken
access_token
)
const jobStatus = await this.pollJobState(
postedJob,
etag,
accessToken
authConfig
).catch((err) => {
throw prefixMessage(err, 'Error while polling job status. ')
})
const { result: currentJob } = await this.requestClient.get<Job>(
`${this.serverUrl}/jobExecution/jobs/${postedJob.id}`,
accessToken
access_token
)
let jobResult
@@ -1071,13 +1081,13 @@ export class SASViyaApiClient {
if (resultLink) {
jobResult = await this.requestClient.get<any>(
`${this.serverUrl}${resultLink}/content`,
accessToken,
access_token,
'text/plain'
)
}
if (debug && logLink) {
log = await this.requestClient
.get<any>(`${this.serverUrl}${logLink.href}/content`, accessToken)
.get<any>(`${this.serverUrl}${logLink.href}/content`, access_token)
.then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
}
if (jobStatus === 'failed') {
@@ -1127,12 +1137,30 @@ export class SASViyaApiClient {
private async pollJobState(
postedJob: any,
etag: string | null,
accessToken?: string,
authConfig?: AuthConfig,
pollOptions?: PollOptions
) {
const logger = process.logger || console
let POLL_INTERVAL = 300
let MAX_POLL_COUNT = 1000
let MAX_ERROR_COUNT = 5
let { access_token, refresh_token, client, secret } = authConfig || {}
if (access_token && refresh_token) {
if (
client &&
secret &&
refresh_token &&
(isAccessTokenExpiring(access_token) ||
isRefreshTokenExpiring(refresh_token))
) {
;({ access_token, refresh_token } = await this.refreshTokens(
client,
secret,
refresh_token
))
}
}
if (pollOptions) {
POLL_INTERVAL = pollOptions.POLL_INTERVAL || POLL_INTERVAL
@@ -1146,8 +1174,8 @@ export class SASViyaApiClient {
'Content-Type': 'application/json',
'If-None-Match': etag
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`
if (access_token) {
headers.Authorization = `Bearer ${access_token}`
}
const stateLink = postedJob.links.find((l: any) => l.rel === 'state')
if (!stateLink) {
@@ -1157,7 +1185,7 @@ export class SASViyaApiClient {
const { result: state } = await this.requestClient
.get<string>(
`${this.serverUrl}${stateLink.href}?_action=wait&wait=300`,
accessToken,
access_token,
'text/plain',
{},
this.debug
@@ -1185,11 +1213,27 @@ export class SASViyaApiClient {
postedJobState === 'pending' ||
postedJobState === 'unavailable'
) {
if (access_token && refresh_token) {
if (
client &&
secret &&
refresh_token &&
(isAccessTokenExpiring(access_token) ||
isRefreshTokenExpiring(refresh_token))
) {
;({ access_token, refresh_token } = await this.refreshTokens(
client,
secret,
refresh_token
))
}
}
if (stateLink) {
const { result: jobState } = await this.requestClient
.get<string>(
`${this.serverUrl}${stateLink.href}?_action=wait&wait=300`,
accessToken,
access_token,
'text/plain',
{},
this.debug
@@ -1218,8 +1262,8 @@ export class SASViyaApiClient {
}
if (this.debug && printedState !== postedJobState) {
console.log('Polling job status...')
console.log(`Current job state: ${postedJobState}`)
logger.info('Polling job status...')
logger.info(`Current job state: ${postedJobState}`)
printedState = postedJobState
}
@@ -1409,6 +1453,9 @@ export class SASViyaApiClient {
accessToken
)
if (!sourceFolderUri) {
return undefined
}
const sourceFolderId = sourceFolderUri?.split('/').pop()
const { result: folder } = await this.requestClient

View File

@@ -4,7 +4,7 @@ import { SASViyaApiClient } from './SASViyaApiClient'
import { SAS9ApiClient } from './SAS9ApiClient'
import { FileUploader } from './FileUploader'
import { AuthManager } from './auth'
import { ServerType, MacroVar } from '@sasjs/utils/types'
import { ServerType, MacroVar, AuthConfig } from '@sasjs/utils/types'
import { RequestClient } from './request/RequestClient'
import {
JobExecutor,
@@ -103,12 +103,12 @@ export default class SASjs {
/**
* Gets executable compute contexts.
* @param accessToken - an access token for an authorized user.
* @param authConfig - an access token, refresh token, client and secret for an authorized user.
*/
public async getExecutableContexts(accessToken: string) {
public async getExecutableContexts(authConfig: AuthConfig) {
this.isMethodSupported('getExecutableContexts', ServerType.SasViya)
return await this.sasViyaApiClient!.getExecutableContexts(accessToken)
return await this.sasViyaApiClient!.getExecutableContexts(authConfig)
}
/**
@@ -240,14 +240,14 @@ export default class SASjs {
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
* @param linesOfCode - lines of sas code from the file to run.
* @param contextName - context name on which code will be run on the server.
* @param accessToken - (optional) the access token for authorizing the request.
* @param authConfig - (optional) the access token, refresh token, client and secret for authorizing the request.
* @param debug - (optional) if true, global debug config will be overriden
*/
public async executeScriptSASViya(
fileName: string,
linesOfCode: string[],
contextName: string,
accessToken?: string,
authConfig?: AuthConfig,
debug?: boolean
) {
this.isMethodSupported('executeScriptSASViya', ServerType.SasViya)
@@ -261,7 +261,7 @@ export default class SASjs {
fileName,
linesOfCode,
contextName,
accessToken,
authConfig,
null,
debug ? debug : this.sasjsConfig.debug
)
@@ -579,7 +579,7 @@ export default class SASjs {
data: { [key: string]: any } | null,
config: { [key: string]: any } = {},
loginRequiredCallback?: () => any,
accessToken?: string,
authConfig?: AuthConfig,
extraResponseAttributes: ExtraResponseAttributes[] = []
) {
config = {
@@ -601,7 +601,7 @@ export default class SASjs {
data,
config,
loginRequiredCallback,
accessToken
authConfig
)
} else {
return await this.jesJobExecutor!.execute(
@@ -609,7 +609,7 @@ export default class SASjs {
data,
config,
loginRequiredCallback,
accessToken,
authConfig,
extraResponseAttributes
)
}
@@ -625,7 +625,7 @@ export default class SASjs {
data,
config,
loginRequiredCallback,
accessToken,
authConfig,
extraResponseAttributes
)
}
@@ -776,7 +776,7 @@ export default class SASjs {
* @param config - provide any changes to the config here, for instance to
* enable/disable `debug`. Any change provided will override the global config,
* for that particular function call.
* @param accessToken - a valid access token that is authorised to execute compute jobs.
* @param authConfig - a valid client, secret, refresh and access tokens that are authorised to execute compute jobs.
* The access token is not required when the user is authenticated via the browser.
* @param waitForResult - a boolean that indicates whether the function needs to wait for execution to complete.
* @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { MAX_POLL_COUNT: 24 * 60 * 60, POLL_INTERVAL: 1000 }.
@@ -787,7 +787,7 @@ export default class SASjs {
sasJob: string,
data: any,
config: any = {},
accessToken?: string,
authConfig?: AuthConfig,
waitForResult?: boolean,
pollOptions?: PollOptions,
printPid = false,
@@ -810,7 +810,7 @@ export default class SASjs {
config.contextName,
config.debug,
data,
accessToken,
authConfig,
!!waitForResult,
false,
pollOptions,

View File

@@ -158,6 +158,8 @@ export class SessionManager {
etag: string | null,
accessToken?: string
) {
const logger = process.logger || console
let sessionState = session.state
const stateLink = session.links.find((l: any) => l.rel === 'state')
@@ -170,7 +172,7 @@ export class SessionManager {
) {
if (stateLink) {
if (this.debug && !this.printedSessionState.printed) {
console.log('Polling session status...')
logger.info('Polling session status...')
this.printedSessionState.printed = true
}
@@ -186,7 +188,7 @@ export class SessionManager {
sessionState = state.trim()
if (this.debug && this.printedSessionState.state !== sessionState) {
console.log(`Current session state is '${sessionState}'`)
logger.info(`Current session state is '${sessionState}'`)
this.printedSessionState.state = sessionState
this.printedSessionState.printed = false

View File

@@ -1,5 +1,4 @@
import { ServerType } from '@sasjs/utils/types'
import { isAuthorizeFormRequired } from '.'
import { RequestClient } from '../request/RequestClient'
import { serialize } from '../utils'

View File

@@ -1,4 +1,4 @@
import { ServerType } from '@sasjs/utils/types'
import { AuthConfig, ServerType } from '@sasjs/utils/types'
import { SASViyaApiClient } from '../SASViyaApiClient'
import {
ErrorResponse,
@@ -17,7 +17,7 @@ export class ComputeJobExecutor extends BaseJobExecutor {
data: any,
config: any,
loginRequiredCallback?: any,
accessToken?: string
authConfig?: AuthConfig
) {
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
const waitForResult = true
@@ -30,7 +30,7 @@ export class ComputeJobExecutor extends BaseJobExecutor {
config.contextName,
config.debug,
data,
accessToken,
authConfig,
waitForResult,
expectWebout
)

View File

@@ -1,4 +1,4 @@
import { ServerType } from '@sasjs/utils/types'
import { AuthConfig, ServerType } from '@sasjs/utils/types'
import { SASViyaApiClient } from '../SASViyaApiClient'
import {
ErrorResponse,
@@ -18,20 +18,14 @@ export class JesJobExecutor extends BaseJobExecutor {
data: any,
config: any,
loginRequiredCallback?: any,
accessToken?: string,
authConfig?: AuthConfig,
extraResponseAttributes: ExtraResponseAttributes[] = []
) {
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
const requestPromise = new Promise((resolve, reject) => {
this.sasViyaApiClient
?.executeJob(
sasJob,
config.contextName,
config.debug,
data,
accessToken
)
?.executeJob(sasJob, config.contextName, config.debug, data, authConfig)
.then((response: any) => {
this.appendRequest(response, sasJob, config.debug)
@@ -69,7 +63,7 @@ export class JesJobExecutor extends BaseJobExecutor {
data,
config,
loginRequiredCallback,
accessToken,
authConfig,
extraResponseAttributes
).then(
(res: any) => {

View File

@@ -1,4 +1,4 @@
import { ServerType } from '@sasjs/utils/types'
import { AuthConfig, ServerType } from '@sasjs/utils/types'
import { SASjsRequest } from '../types'
import { ExtraResponseAttributes } from '@sasjs/utils/types'
import { asyncForEach, parseGeneratedCode, parseSourceCode } from '../utils'
@@ -11,7 +11,7 @@ export interface JobExecutor {
data: any,
config: any,
loginRequiredCallback?: any,
accessToken?: string,
authConfig?: AuthConfig,
extraResponseAttributes?: ExtraResponseAttributes[]
) => Promise<any>
resendWaitingRequests: () => Promise<void>
@@ -30,7 +30,7 @@ export abstract class BaseJobExecutor implements JobExecutor {
data: any,
config: any,
loginRequiredCallback?: any,
accessToken?: string | undefined,
authConfig?: AuthConfig | undefined,
extraResponseAttributes?: ExtraResponseAttributes[]
): Promise<any>

View File

@@ -288,7 +288,8 @@ export class RequestClient implements HttpClient {
})
.then((res) => res.data)
.catch((error) => {
console.log(error)
const logger = process.logger || console
logger.error(error)
})
}

5
src/types/Process.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare namespace NodeJS {
export interface Process {
logger?: import('@sasjs/utils/logger').Logger
}
}

35
src/utils/auth.ts Normal file
View File

@@ -0,0 +1,35 @@
import jwtDecode from 'jwt-decode'
/**
* Checks if the Access Token is expired or is expiring in 1 hour. A default Access Token
* lasts 12 hours. If the Access Token expires, the Refresh Token is used to fetch a new
* Access Token. In the case that the Refresh Token is expired, 1 hour is enough to let
* most jobs finish.
* @param {string} token- token string that will be evaluated
*/
export function isAccessTokenExpiring(token: string): boolean {
if (!token) {
return true
}
const payload = jwtDecode<{ exp: number }>(token)
const timeToLive = payload.exp - new Date().valueOf() / 1000
return timeToLive <= 60 * 60 // 1 hour
}
/**
* Checks if the Refresh Token is expired or expiring in 30 secs. A default Refresh Token
* lasts 30 days. Once the Refresh Token expires, the user must re-authenticate (provide
* credentials in a browser to obtain an authorisation code). 30 seconds is enough time
* to make a request for a final Access Token.
* @param {string} token- token string that will be evaluated
*/
export function isRefreshTokenExpiring(token?: string): boolean {
if (!token) {
return true
}
const payload = jwtDecode<{ exp: number }>(token)
const timeToLive = payload.exp - new Date().valueOf() / 1000
return timeToLive <= 30 // 30 seconds
}

View File

@@ -15,12 +15,14 @@ export const fetchLogByChunks = async (
logUrl: string,
logCount: number
): Promise<string> => {
const logger = process.logger || console
let log: string = ''
const loglimit = logCount < 10000 ? logCount : 10000
let start = 0
do {
console.log(
logger.info(
`Fetching logs from line no: ${start + 1} to ${
start + loglimit
} of ${logCount}.`

View File

@@ -1,4 +1,5 @@
export * from './asyncForEach'
export * from './auth'
export * from './compareTimestamps'
export * from './convertToCsv'
export * from './isRelativePath'