1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-03 10:40:06 +00:00

Merge pull request #432 from sasjs/job-refresh-tokens

fix(job-execution): refresh access token if it has expired during job status checks
This commit is contained in:
Krishna Acondy
2021-06-30 11:10:34 +01:00
committed by GitHub
18 changed files with 36575 additions and 946 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

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. ')
})
@@ -367,7 +372,7 @@ export class SASViyaApiClient {
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. ')
})
@@ -397,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. ')
@@ -406,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
}'.`
@@ -417,7 +422,7 @@ export class SASViyaApiClient {
const jobStatus = await this.pollJobState(
postedJob,
etag,
accessToken,
authConfig,
pollOptions
).catch(async (err) => {
const error = err?.response?.data
@@ -430,7 +435,7 @@ export class SASViyaApiClient {
const logCount = 1000000
err.log = await fetchLogByChunks(
this.requestClient,
accessToken!,
access_token!,
sessionLogUrl,
logCount
)
@@ -441,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. ')
@@ -457,7 +462,7 @@ export class SASViyaApiClient {
const logCount = currentJob.logStatistics?.lineCount ?? 1000000
log = await fetchLogByChunks(
this.requestClient,
accessToken!,
access_token!,
logUrl,
logCount
)
@@ -477,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) {
@@ -485,7 +490,7 @@ export class SASViyaApiClient {
const logCount = currentJob.logStatistics?.lineCount ?? 1000000
log = await fetchLogByChunks(
this.requestClient,
accessToken!,
access_token!,
logUrl,
logCount
)
@@ -504,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. ')
})
@@ -516,7 +521,7 @@ export class SASViyaApiClient {
jobPath,
linesOfCode,
contextName,
accessToken,
authConfig,
data,
debug,
false,
@@ -603,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.')
}
@@ -610,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.`
)
@@ -622,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(
@@ -631,7 +637,7 @@ export class SASViyaApiClient {
undefined,
accessToken
)
console.log(
logger.info(
`Parent folder '${newFolderName}' has been successfully created.`
)
parentFolderUri = `/folders/folders/${parentFolder.id}`
@@ -873,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'
@@ -893,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. ')
})
@@ -907,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)
@@ -931,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. ')
@@ -951,7 +959,7 @@ export class SASViyaApiClient {
sasJob,
linesToExecute,
contextName,
accessToken,
authConfig,
data,
debug,
expectWebout,
@@ -975,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.'
@@ -989,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) {
@@ -1002,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) {
@@ -1014,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 } = {
@@ -1050,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
@@ -1072,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') {
@@ -1128,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
@@ -1147,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) {
@@ -1158,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
@@ -1186,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
@@ -1219,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
}
@@ -1410,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

@@ -287,7 +287,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'