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

Compare commits

..

34 Commits

Author SHA1 Message Date
Krishna Acondy
5a7b4a1de4 Merge pull request #447 from sasjs/token-expiry-utils
fix(auth-utils): move auth functions to utils library, fix webpack config
2021-07-05 08:17:59 +01:00
Krishna Acondy
5a35237de5 fix(build): add node polyfill plugin and stub fs and readline when building for the browser 2021-07-01 09:11:03 +01:00
Krishna Acondy
5d77bbba8b fix(auth): use token functions from utils library 2021-07-01 09:10:32 +01:00
Yury Shkoda
eda021b6a5 Merge pull request #431 from sasjs/issue-409
fix: if response does not contain valid JSON throw error #409
2021-07-01 08:03:30 +03:00
Yury Shkoda
259c479ef0 Merge branch 'master' into issue-409 2021-07-01 07:58:23 +03:00
Krishna Acondy
a962b8e7cf Merge pull request #445 from sasjs/handle-304-status
fix(session): fixed polling session state, refresh token before server calls
2021-06-30 18:07:44 +01:00
Krishna Acondy
eb0e7247a6 fix(scripts): change git hook script to prepare 2021-06-30 18:05:52 +01:00
ccc77cb9d1 chore: remove console.log statements 2021-06-30 21:42:46 +05:00
Krishna Acondy
5cb5bbdb55 fix(execution): refresh tokens before fetching results 2021-06-30 15:19:12 +01:00
Yury Shkoda
ac6cd7be82 fix(session): fixed polling session state 2021-06-30 16:55:09 +03:00
Sabir Hassan
63f5f4d03d Merge branch 'master' into issue-409 2021-06-30 15:35:43 +05:00
Krishna Acondy
a164fb7df9 Merge pull request #432 from sasjs/job-refresh-tokens
fix(job-execution): refresh access token if it has expired during job status checks
2021-06-30 11:10:34 +01:00
Krishna Acondy
336ba207cf chore(deps): upgrade dependencies 2021-06-30 07:35:21 +01:00
Krishna Acondy
3cfd45cc62 chore(merge): pull in changes from master 2021-06-30 07:26:15 +01:00
Yury Shkoda
f7fb917282 Merge pull request #441 from sasjs/allanbowe-patch-1
Update README.md
2021-06-30 08:23:17 +03:00
Allan Bowe
a182037883 Update README.md 2021-06-29 15:36:37 +03:00
Krishna Acondy
f9e79fb756 chore(*): remove unused variables 2021-06-29 10:23:35 +01:00
Krishna Acondy
aaf0eef62b chore(tests): fix method arguments 2021-06-29 07:32:47 +01:00
Krishna Acondy
fafa0c3567 Merge branch 'master' into job-refresh-tokens 2021-06-28 08:50:46 +01:00
Allan Bowe
4a6845ad6a Merge pull request #437 from sasjs/issue-420
fix: on viya when calling api pass debug parameter to correct section
2021-06-27 13:19:33 +03:00
Sabir Hassan
61d66c6f82 Merge branch 'master' into issue-420 2021-06-25 14:09:34 +05:00
123fbc7235 fix: on viya when calling api pass debug parameter to correct section #420 2021-06-25 13:49:45 +05:00
Krishna Acondy
eae8694a29 Merge branch 'master' into job-refresh-tokens 2021-06-25 09:15:33 +01:00
Krishna Acondy
2b16be3aef chore(*): refactor to use logger if available 2021-06-25 09:14:29 +01:00
Sabir Hassan
d8d4da9c9a Merge branch 'master' into issue-409 2021-06-24 16:15:40 +05:00
VladislavParhomchik
0b755b7304 Merge pull request #436 from sasjs/allanbowe-patch-1
chore(*): Update PULL_REQUEST_TEMPLATE.md
2021-06-24 10:22:13 +03:00
Allan Bowe
0816b7b1f9 Update PULL_REQUEST_TEMPLATE.md 2021-06-24 10:04:29 +03:00
Krishna Acondy
97d45e87ec chore(merge): pull in changes from master 2021-06-24 07:21:12 +01:00
Krishna Acondy
57ef0647b5 fix(auth): refresh access tokens if expiring during job status check 2021-06-24 07:20:54 +01:00
Krishna Acondy
2ee6c45d16 Merge branch 'master' into job-refresh-tokens 2021-06-23 08:29:22 +01:00
Krishna Acondy
56b2ba026a chore(merge): pull in changes from master 2021-06-22 07:41:42 +01:00
Krishna Acondy
8beda1ad6c fix(*): pass in authConfig in place of accessToken 2021-06-22 07:38:12 +01:00
Krishna Acondy
b18b471549 fix(job-execution): refresh access token if it has expired during job status checks 2021-06-21 08:59:12 +01:00
93c9a34591 fix: if response is not valid json throw error #409 2021-06-21 00:45:37 +05:00
22 changed files with 38661 additions and 973 deletions

View File

@@ -6,7 +6,7 @@ GREEN="\033[1;32m"
# temporary file which holds the message). # temporary file which holds the message).
commit_message=$(cat "$1") 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" echo "${GREEN} ✔ Commit message meets Conventional Commit standards"
exit 0 exit 0
fi fi

View File

@@ -12,6 +12,8 @@ What code changes have been made to achieve the intent.
## Checks ## Checks
No PR (that involves a non-trivial code change) should be merged, unless all four of the items below are confirmed! If an urgent fix is needed - use a tar file.
- [ ] Code is formatted correctly (`npm run lint:fix`). - [ ] Code is formatted correctly (`npm run lint:fix`).
- [ ] All unit tests are passing (`npm test`). - [ ] All unit tests are passing (`npm test`).
- [ ] All `sasjs-cli` unit tests are passing (`npm test`). - [ ] All `sasjs-cli` unit tests are passing (`npm test`).

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 ### 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. 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.

17198
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@
"postpublish": "git clean -fd", "postpublish": "git clean -fd",
"semantic-release": "semantic-release", "semantic-release": "semantic-release",
"typedoc": "typedoc", "typedoc": "typedoc",
"postinstall": "[ -d .git ] && git config core.hooksPath ./.git-hooks || true" "prepare": "[ -d .git ] && git config core.hooksPath ./.git-hooks || true"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
@@ -43,28 +43,29 @@
"@types/tough-cookie": "^4.0.0", "@types/tough-cookie": "^4.0.0",
"cp": "^0.2.0", "cp": "^0.2.0",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"jest": "^27.0.4", "jest": "^27.0.6",
"jest-extended": "^0.11.5", "jest-extended": "^0.11.5",
"mime": "^2.5.2", "mime": "^2.5.2",
"node-polyfill-webpack-plugin": "^1.1.4",
"path": "^0.12.7", "path": "^0.12.7",
"process": "^0.11.10", "process": "^0.11.10",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semantic-release": "^17.4.4", "semantic-release": "^17.4.4",
"terser-webpack-plugin": "^5.1.3", "terser-webpack-plugin": "^5.1.4",
"ts-jest": "^27.0.3", "ts-jest": "^27.0.3",
"ts-loader": "^9.2.2", "ts-loader": "^9.2.2",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",
"typedoc": "^0.20.36", "typedoc": "^0.21.2",
"typedoc-neo-theme": "^1.1.1", "typedoc-neo-theme": "^1.1.1",
"typedoc-plugin-external-module-name": "^4.0.6", "typedoc-plugin-external-module-name": "^4.0.6",
"typescript": "^4.3.2", "typescript": "^4.3.4",
"webpack": "^5.38.1", "webpack": "^5.41.1",
"webpack-cli": "^4.7.2" "webpack-cli": "^4.7.2"
}, },
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@sasjs/utils": "^2.20.1", "@sasjs/utils": "^2.22.0",
"axios": "^0.21.1", "axios": "^0.21.1",
"axios-cookiejar-support": "^1.0.1", "axios-cookiejar-support": "^1.0.1",
"form-data": "^4.0.0", "form-data": "^4.0.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', '/Public/app/common/sendArr',
data, data,
{}, {},
'', undefined,
true true
) )
}, },

View File

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

View File

@@ -25,11 +25,18 @@ import {
import { formatDataForRequest } from './utils/formatDataForRequest' import { formatDataForRequest } from './utils/formatDataForRequest'
import { SessionManager } from './SessionManager' import { SessionManager } from './SessionManager'
import { ContextManager } from './ContextManager' import { ContextManager } from './ContextManager'
import { timestampToYYYYMMDDHHMMSS } from '@sasjs/utils/time' import {
import { Logger, LogLevel } from '@sasjs/utils/logger' timestampToYYYYMMDDHHMMSS,
isAccessTokenExpiring,
isRefreshTokenExpiring,
Logger,
LogLevel,
SasAuthResponse,
MacroVar,
AuthConfig
} from '@sasjs/utils'
import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired' import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired'
import { RequestClient } from './request/RequestClient' import { RequestClient } from './request/RequestClient'
import { SasAuthResponse, MacroVar } from '@sasjs/utils/types'
import { prefixMessage } from '@sasjs/utils/error' import { prefixMessage } from '@sasjs/utils/error'
import * as mime from 'mime' import * as mime from 'mime'
@@ -130,14 +137,14 @@ export class SASViyaApiClient {
/** /**
* Returns all compute contexts on this server that the user has access to. * 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) const bindedExecuteScript = this.executeScript.bind(this)
return await this.contextManager.getExecutableContexts( return await this.contextManager.getExecutableContexts(
bindedExecuteScript, bindedExecuteScript,
accessToken authConfig
) )
} }
@@ -266,7 +273,7 @@ export class SASViyaApiClient {
* @param jobPath - the path to the file being submitted for execution. * @param jobPath - the path to the file being submitted for execution.
* @param linesOfCode - an array of code lines to execute. * @param linesOfCode - an array of code lines to execute.
* @param contextName - the context to execute the code in. * @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 data - execution data.
* @param debug - when set to true, the log will be returned. * @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). * @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 +286,7 @@ export class SASViyaApiClient {
jobPath: string, jobPath: string,
linesOfCode: string[], linesOfCode: string[],
contextName: string, contextName: string,
accessToken?: string, authConfig?: AuthConfig,
data = null, data = null,
debug: boolean = false, debug: boolean = false,
expectWebout = false, expectWebout = false,
@@ -288,17 +295,18 @@ export class SASViyaApiClient {
printPid = false, printPid = false,
variables?: MacroVar variables?: MacroVar
): Promise<any> { ): Promise<any> {
let access_token = (authConfig || {}).access_token
if (authConfig) {
;({ access_token } = await this.getTokens(authConfig))
}
const logger = process.logger || console
try { try {
const headers: any = {
'Content-Type': 'application/json'
}
if (accessToken) headers.Authorization = `Bearer ${accessToken}`
let executionSessionId: string let executionSessionId: string
const session = await this.sessionManager const session = await this.sessionManager
.getSession(accessToken) .getSession(access_token)
.catch((err) => { .catch((err) => {
throw prefixMessage(err, 'Error while getting session. ') throw prefixMessage(err, 'Error while getting session. ')
}) })
@@ -307,7 +315,7 @@ export class SASViyaApiClient {
if (printPid) { if (printPid) {
const { result: jobIdVariable } = await this.sessionManager const { result: jobIdVariable } = await this.sessionManager
.getVariable(executionSessionId, 'SYSJOBID', accessToken) .getVariable(executionSessionId, 'SYSJOBID', access_token)
.catch((err) => { .catch((err) => {
throw prefixMessage(err, 'Error while getting session variable. ') throw prefixMessage(err, 'Error while getting session variable. ')
}) })
@@ -339,7 +347,6 @@ export class SASViyaApiClient {
if (debug) { if (debug) {
jobArguments['_OMITTEXTLOG'] = false jobArguments['_OMITTEXTLOG'] = false
jobArguments['_OMITSESSIONRESULTS'] = false jobArguments['_OMITSESSIONRESULTS'] = false
jobArguments['_DEBUG'] = 131
} }
let fileName let fileName
@@ -362,11 +369,13 @@ export class SASViyaApiClient {
if (variables) jobVariables = { ...jobVariables, ...variables } if (variables) jobVariables = { ...jobVariables, ...variables }
if (debug) jobVariables = { ...jobVariables, _DEBUG: 131 }
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).catch((err) => { files = await this.uploadTables(data, access_token).catch((err) => {
throw prefixMessage(err, 'Error while uploading tables. ') throw prefixMessage(err, 'Error while uploading tables. ')
}) })
@@ -396,7 +405,7 @@ export class SASViyaApiClient {
.post<Job>( .post<Job>(
`/compute/sessions/${executionSessionId}/jobs`, `/compute/sessions/${executionSessionId}/jobs`,
jobRequestBody, jobRequestBody,
accessToken access_token
) )
.catch((err) => { .catch((err) => {
throw prefixMessage(err, 'Error while posting job. ') throw prefixMessage(err, 'Error while posting job. ')
@@ -405,8 +414,8 @@ export class SASViyaApiClient {
if (!waitForResult) return session if (!waitForResult) return session
if (debug) { if (debug) {
console.log(`Job has been submitted for '${fileName}'.`) logger.info(`Job has been submitted for '${fileName}'.`)
console.log( logger.info(
`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
}'.` }'.`
@@ -416,7 +425,7 @@ export class SASViyaApiClient {
const jobStatus = await this.pollJobState( const jobStatus = await this.pollJobState(
postedJob, postedJob,
etag, etag,
accessToken, authConfig,
pollOptions pollOptions
).catch(async (err) => { ).catch(async (err) => {
const error = err?.response?.data const error = err?.response?.data
@@ -429,7 +438,7 @@ export class SASViyaApiClient {
const logCount = 1000000 const logCount = 1000000
err.log = await fetchLogByChunks( err.log = await fetchLogByChunks(
this.requestClient, this.requestClient,
accessToken!, access_token!,
sessionLogUrl, sessionLogUrl,
logCount logCount
) )
@@ -437,10 +446,14 @@ export class SASViyaApiClient {
throw prefixMessage(err, 'Error while polling job status. ') throw prefixMessage(err, 'Error while polling job status. ')
}) })
if (authConfig) {
;({ access_token } = await this.getTokens(authConfig))
}
const { result: currentJob } = await this.requestClient const { result: currentJob } = await this.requestClient
.get<Job>( .get<Job>(
`/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`, `/compute/sessions/${executionSessionId}/jobs/${postedJob.id}`,
accessToken access_token
) )
.catch((err) => { .catch((err) => {
throw prefixMessage(err, 'Error while getting job. ') throw prefixMessage(err, 'Error while getting job. ')
@@ -456,7 +469,7 @@ export class SASViyaApiClient {
const logCount = currentJob.logStatistics?.lineCount ?? 1000000 const logCount = currentJob.logStatistics?.lineCount ?? 1000000
log = await fetchLogByChunks( log = await fetchLogByChunks(
this.requestClient, this.requestClient,
accessToken!, access_token!,
logUrl, logUrl,
logCount logCount
) )
@@ -476,7 +489,7 @@ export class SASViyaApiClient {
if (resultLink) { if (resultLink) {
jobResult = await this.requestClient jobResult = await this.requestClient
.get<any>(resultLink, accessToken, 'text/plain') .get<any>(resultLink, access_token, 'text/plain')
.catch(async (e) => { .catch(async (e) => {
if (e instanceof NotFoundError) { if (e instanceof NotFoundError) {
if (logLink) { if (logLink) {
@@ -484,7 +497,7 @@ export class SASViyaApiClient {
const logCount = currentJob.logStatistics?.lineCount ?? 1000000 const logCount = currentJob.logStatistics?.lineCount ?? 1000000
log = await fetchLogByChunks( log = await fetchLogByChunks(
this.requestClient, this.requestClient,
accessToken!, access_token!,
logUrl, logUrl,
logCount logCount
) )
@@ -503,7 +516,7 @@ export class SASViyaApiClient {
} }
await this.sessionManager await this.sessionManager
.clearSession(executionSessionId, accessToken) .clearSession(executionSessionId, access_token)
.catch((err) => { .catch((err) => {
throw prefixMessage(err, 'Error while clearing session. ') throw prefixMessage(err, 'Error while clearing session. ')
}) })
@@ -515,7 +528,7 @@ export class SASViyaApiClient {
jobPath, jobPath,
linesOfCode, linesOfCode,
contextName, contextName,
accessToken, authConfig,
data, data,
debug, debug,
false, false,
@@ -602,6 +615,7 @@ export class SASViyaApiClient {
accessToken?: string, accessToken?: string,
isForced?: boolean isForced?: boolean
): Promise<Folder> { ): Promise<Folder> {
const logger = process.logger || console
if (!parentFolderPath && !parentFolderUri) { if (!parentFolderPath && !parentFolderUri) {
throw new Error('Path or URI of the parent folder is required.') throw new Error('Path or URI of the parent folder is required.')
} }
@@ -609,7 +623,7 @@ export class SASViyaApiClient {
if (!parentFolderUri && parentFolderPath) { if (!parentFolderUri && parentFolderPath) {
parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken) parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken)
if (!parentFolderUri) { if (!parentFolderUri) {
console.log( logger.info(
`Parent folder at path '${parentFolderPath}' is not present.` `Parent folder at path '${parentFolderPath}' is not present.`
) )
@@ -621,7 +635,7 @@ export class SASViyaApiClient {
if (newParentFolderPath === '') { if (newParentFolderPath === '') {
throw new Error('Root folder has to be present on the server.') throw new Error('Root folder has to be present on the server.')
} }
console.log( logger.info(
`Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'` `Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'`
) )
const parentFolder = await this.createFolder( const parentFolder = await this.createFolder(
@@ -630,7 +644,7 @@ export class SASViyaApiClient {
undefined, undefined,
accessToken accessToken
) )
console.log( logger.info(
`Parent folder '${newFolderName}' has been successfully created.` `Parent folder '${newFolderName}' has been successfully created.`
) )
parentFolderUri = `/folders/folders/${parentFolder.id}` parentFolderUri = `/folders/folders/${parentFolder.id}`
@@ -872,13 +886,18 @@ export class SASViyaApiClient {
contextName: string, contextName: string,
debug?: boolean, debug?: boolean,
data?: any, data?: any,
accessToken?: string, authConfig?: AuthConfig,
waitForResult = true, waitForResult = true,
expectWebout = false, expectWebout = false,
pollOptions?: PollOptions, pollOptions?: PollOptions,
printPid = false, printPid = false,
variables?: MacroVar variables?: MacroVar
) { ) {
let access_token = (authConfig || {}).access_token
if (authConfig) {
;({ access_token } = await this.getTokens(authConfig))
}
if (isRelativePath(sasJob) && !this.rootFolderName) { if (isRelativePath(sasJob) && !this.rootFolderName) {
throw new Error( throw new Error(
'Relative paths cannot be used without specifying a root folder name' 'Relative paths cannot be used without specifying a root folder name'
@@ -892,7 +911,7 @@ export class SASViyaApiClient {
? `${this.rootFolderName}/${folderPath}` ? `${this.rootFolderName}/${folderPath}`
: 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. ') throw prefixMessage(err, 'Error while populating folder map. ')
}) })
@@ -904,12 +923,6 @@ export class SASViyaApiClient {
) )
} }
const headers: any = { 'Content-Type': 'application/json' }
if (!!accessToken) {
headers.Authorization = `Bearer ${accessToken}`
}
const jobToExecute = jobFolder?.find((item) => item.name === jobName) const jobToExecute = jobFolder?.find((item) => item.name === jobName)
if (!jobToExecute) { if (!jobToExecute) {
@@ -930,7 +943,7 @@ export class SASViyaApiClient {
const { result: jobDefinition } = await this.requestClient const { result: jobDefinition } = await this.requestClient
.get<JobDefinition>( .get<JobDefinition>(
`${this.serverUrl}${jobDefinitionLink.href}`, `${this.serverUrl}${jobDefinitionLink.href}`,
accessToken access_token
) )
.catch((err) => { .catch((err) => {
throw prefixMessage(err, 'Error while getting job definition. ') throw prefixMessage(err, 'Error while getting job definition. ')
@@ -950,7 +963,7 @@ export class SASViyaApiClient {
sasJob, sasJob,
linesToExecute, linesToExecute,
contextName, contextName,
accessToken, authConfig,
data, data,
debug, debug,
expectWebout, expectWebout,
@@ -974,8 +987,12 @@ export class SASViyaApiClient {
contextName: string, contextName: string,
debug: boolean, debug: boolean,
data?: any, data?: any,
accessToken?: string authConfig?: AuthConfig
) { ) {
let access_token = (authConfig || {}).access_token
if (authConfig) {
;({ access_token } = await this.getTokens(authConfig))
}
if (isRelativePath(sasJob) && !this.rootFolderName) { if (isRelativePath(sasJob) && !this.rootFolderName) {
throw new Error( throw new Error(
'Relative paths cannot be used without specifying a root folder name.' 'Relative paths cannot be used without specifying a root folder name.'
@@ -988,7 +1005,7 @@ export class SASViyaApiClient {
const fullFolderPath = isRelativePath(sasJob) const fullFolderPath = isRelativePath(sasJob)
? `${this.rootFolderName}/${folderPath}` ? `${this.rootFolderName}/${folderPath}`
: folderPath : folderPath
await this.populateFolderMap(fullFolderPath, accessToken) await this.populateFolderMap(fullFolderPath, access_token)
const jobFolder = this.folderMap.get(fullFolderPath) const jobFolder = this.folderMap.get(fullFolderPath)
if (!jobFolder) { if (!jobFolder) {
@@ -1001,7 +1018,7 @@ export class SASViyaApiClient {
let files: any[] = [] let files: any[] = []
if (data && Object.keys(data).length) { if (data && Object.keys(data).length) {
files = await this.uploadTables(data, accessToken) files = await this.uploadTables(data, access_token)
} }
if (!jobToExecute) { if (!jobToExecute) {
@@ -1013,7 +1030,7 @@ export class SASViyaApiClient {
const { result: jobDefinition } = await this.requestClient.get<Job>( const { result: jobDefinition } = await this.requestClient.get<Job>(
`${this.serverUrl}${jobDefinitionLink}`, `${this.serverUrl}${jobDefinitionLink}`,
accessToken access_token
) )
const jobArguments: { [key: string]: any } = { const jobArguments: { [key: string]: any } = {
@@ -1049,18 +1066,18 @@ export class SASViyaApiClient {
const { result: postedJob, etag } = await this.requestClient.post<Job>( const { result: postedJob, etag } = await this.requestClient.post<Job>(
`${this.serverUrl}/jobExecution/jobs?_action=wait`, `${this.serverUrl}/jobExecution/jobs?_action=wait`,
postJobRequestBody, postJobRequestBody,
accessToken access_token
) )
const jobStatus = await this.pollJobState( const jobStatus = await this.pollJobState(
postedJob, postedJob,
etag, etag,
accessToken authConfig
).catch((err) => { ).catch((err) => {
throw prefixMessage(err, 'Error while polling job status. ') throw prefixMessage(err, 'Error while polling job status. ')
}) })
const { result: currentJob } = await this.requestClient.get<Job>( const { result: currentJob } = await this.requestClient.get<Job>(
`${this.serverUrl}/jobExecution/jobs/${postedJob.id}`, `${this.serverUrl}/jobExecution/jobs/${postedJob.id}`,
accessToken access_token
) )
let jobResult let jobResult
@@ -1071,13 +1088,13 @@ export class SASViyaApiClient {
if (resultLink) { if (resultLink) {
jobResult = await this.requestClient.get<any>( jobResult = await this.requestClient.get<any>(
`${this.serverUrl}${resultLink}/content`, `${this.serverUrl}${resultLink}/content`,
accessToken, access_token,
'text/plain' 'text/plain'
) )
} }
if (debug && logLink) { if (debug && logLink) {
log = await this.requestClient 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')) .then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
} }
if (jobStatus === 'failed') { if (jobStatus === 'failed') {
@@ -1127,12 +1144,18 @@ export class SASViyaApiClient {
private async pollJobState( private async pollJobState(
postedJob: any, postedJob: any,
etag: string | null, etag: string | null,
accessToken?: string, authConfig?: AuthConfig,
pollOptions?: PollOptions pollOptions?: PollOptions
) { ) {
const logger = process.logger || console
let POLL_INTERVAL = 300 let POLL_INTERVAL = 300
let MAX_POLL_COUNT = 1000 let MAX_POLL_COUNT = 1000
let MAX_ERROR_COUNT = 5 let MAX_ERROR_COUNT = 5
let access_token = (authConfig || {}).access_token
if (authConfig) {
;({ access_token } = await this.getTokens(authConfig))
}
if (pollOptions) { if (pollOptions) {
POLL_INTERVAL = pollOptions.POLL_INTERVAL || POLL_INTERVAL POLL_INTERVAL = pollOptions.POLL_INTERVAL || POLL_INTERVAL
@@ -1146,8 +1169,8 @@ export class SASViyaApiClient {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'If-None-Match': etag 'If-None-Match': etag
} }
if (accessToken) { if (access_token) {
headers.Authorization = `Bearer ${accessToken}` headers.Authorization = `Bearer ${access_token}`
} }
const stateLink = postedJob.links.find((l: any) => l.rel === 'state') const stateLink = postedJob.links.find((l: any) => l.rel === 'state')
if (!stateLink) { if (!stateLink) {
@@ -1157,7 +1180,7 @@ export class SASViyaApiClient {
const { result: state } = await this.requestClient const { result: state } = await this.requestClient
.get<string>( .get<string>(
`${this.serverUrl}${stateLink.href}?_action=wait&wait=300`, `${this.serverUrl}${stateLink.href}?_action=wait&wait=300`,
accessToken, access_token,
'text/plain', 'text/plain',
{}, {},
this.debug this.debug
@@ -1185,11 +1208,15 @@ export class SASViyaApiClient {
postedJobState === 'pending' || postedJobState === 'pending' ||
postedJobState === 'unavailable' postedJobState === 'unavailable'
) { ) {
if (authConfig) {
;({ access_token } = await this.getTokens(authConfig))
}
if (stateLink) { if (stateLink) {
const { result: jobState } = await this.requestClient const { result: jobState } = await this.requestClient
.get<string>( .get<string>(
`${this.serverUrl}${stateLink.href}?_action=wait&wait=300`, `${this.serverUrl}${stateLink.href}?_action=wait&wait=300`,
accessToken, access_token,
'text/plain', 'text/plain',
{}, {},
this.debug this.debug
@@ -1218,8 +1245,8 @@ export class SASViyaApiClient {
} }
if (this.debug && printedState !== postedJobState) { if (this.debug && printedState !== postedJobState) {
console.log('Polling job status...') logger.info('Polling job status...')
console.log(`Current job state: ${postedJobState}`) logger.info(`Current job state: ${postedJobState}`)
printedState = postedJobState printedState = postedJobState
} }
@@ -1409,6 +1436,9 @@ export class SASViyaApiClient {
accessToken accessToken
) )
if (!sourceFolderUri) {
return undefined
}
const sourceFolderId = sourceFolderUri?.split('/').pop() const sourceFolderId = sourceFolderUri?.split('/').pop()
const { result: folder } = await this.requestClient const { result: folder } = await this.requestClient
@@ -1463,4 +1493,21 @@ export class SASViyaApiClient {
return movedFolder return movedFolder
} }
private async getTokens(authConfig: AuthConfig): Promise<AuthConfig> {
const logger = process.logger || console
let { access_token, refresh_token, client, secret } = authConfig
if (
isAccessTokenExpiring(access_token) ||
isRefreshTokenExpiring(refresh_token)
) {
logger.info('Refreshing access and refresh tokens.')
;({ access_token, refresh_token } = await this.refreshTokens(
client,
secret,
refresh_token
))
}
return { access_token, refresh_token, client, secret }
}
} }

View File

@@ -4,7 +4,7 @@ import { SASViyaApiClient } from './SASViyaApiClient'
import { SAS9ApiClient } from './SAS9ApiClient' import { SAS9ApiClient } from './SAS9ApiClient'
import { FileUploader } from './FileUploader' import { FileUploader } from './FileUploader'
import { AuthManager } from './auth' 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 { RequestClient } from './request/RequestClient'
import { import {
JobExecutor, JobExecutor,
@@ -103,12 +103,12 @@ export default class SASjs {
/** /**
* Gets executable compute contexts. * 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) 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 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 linesOfCode - lines of sas code from the file to run.
* @param contextName - context name on which code will be run on the server. * @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 * @param debug - (optional) if true, global debug config will be overriden
*/ */
public async executeScriptSASViya( public async executeScriptSASViya(
fileName: string, fileName: string,
linesOfCode: string[], linesOfCode: string[],
contextName: string, contextName: string,
accessToken?: string, authConfig?: AuthConfig,
debug?: boolean debug?: boolean
) { ) {
this.isMethodSupported('executeScriptSASViya', ServerType.SasViya) this.isMethodSupported('executeScriptSASViya', ServerType.SasViya)
@@ -261,7 +261,7 @@ export default class SASjs {
fileName, fileName,
linesOfCode, linesOfCode,
contextName, contextName,
accessToken, authConfig,
null, null,
debug ? debug : this.sasjsConfig.debug debug ? debug : this.sasjsConfig.debug
) )
@@ -579,7 +579,7 @@ export default class SASjs {
data: { [key: string]: any } | null, data: { [key: string]: any } | null,
config: { [key: string]: any } = {}, config: { [key: string]: any } = {},
loginRequiredCallback?: () => any, loginRequiredCallback?: () => any,
accessToken?: string, authConfig?: AuthConfig,
extraResponseAttributes: ExtraResponseAttributes[] = [] extraResponseAttributes: ExtraResponseAttributes[] = []
) { ) {
config = { config = {
@@ -601,7 +601,7 @@ export default class SASjs {
data, data,
config, config,
loginRequiredCallback, loginRequiredCallback,
accessToken authConfig
) )
} else { } else {
return await this.jesJobExecutor!.execute( return await this.jesJobExecutor!.execute(
@@ -609,7 +609,7 @@ export default class SASjs {
data, data,
config, config,
loginRequiredCallback, loginRequiredCallback,
accessToken, authConfig,
extraResponseAttributes extraResponseAttributes
) )
} }
@@ -625,7 +625,7 @@ export default class SASjs {
data, data,
config, config,
loginRequiredCallback, loginRequiredCallback,
accessToken, authConfig,
extraResponseAttributes extraResponseAttributes
) )
} }
@@ -776,7 +776,7 @@ export default class SASjs {
* @param config - provide any changes to the config here, for instance to * @param config - provide any changes to the config here, for instance to
* enable/disable `debug`. Any change provided will override the global config, * enable/disable `debug`. Any change provided will override the global config,
* for that particular function call. * 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. * 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 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 }. * @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, sasJob: string,
data: any, data: any,
config: any = {}, config: any = {},
accessToken?: string, authConfig?: AuthConfig,
waitForResult?: boolean, waitForResult?: boolean,
pollOptions?: PollOptions, pollOptions?: PollOptions,
printPid = false, printPid = false,
@@ -810,7 +810,7 @@ export default class SASjs {
config.contextName, config.contextName,
config.debug, config.debug,
data, data,
accessToken, authConfig,
!!waitForResult, !!waitForResult,
false, false,
pollOptions, pollOptions,

View File

@@ -6,10 +6,6 @@ import { RequestClient } from './request/RequestClient'
const MAX_SESSION_COUNT = 1 const MAX_SESSION_COUNT = 1
const RETRY_LIMIT: number = 3 const RETRY_LIMIT: number = 3
let RETRY_COUNT: number = 0 let RETRY_COUNT: number = 0
const INTERNAL_SAS_ERROR = {
status: 304,
message: 'Not Modified'
}
export class SessionManager { export class SessionManager {
constructor( constructor(
@@ -158,11 +154,13 @@ export class SessionManager {
etag: string | null, etag: string | null,
accessToken?: string accessToken?: string
) { ) {
const logger = process.logger || console
let sessionState = session.state let sessionState = session.state
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, reject) => {
if ( if (
sessionState === 'pending' || sessionState === 'pending' ||
sessionState === 'running' || sessionState === 'running' ||
@@ -170,7 +168,7 @@ export class SessionManager {
) { ) {
if (stateLink) { if (stateLink) {
if (this.debug && !this.printedSessionState.printed) { if (this.debug && !this.printedSessionState.printed) {
console.log('Polling session status...') logger.info('Polling session status...')
this.printedSessionState.printed = true this.printedSessionState.printed = true
} }
@@ -180,13 +178,13 @@ export class SessionManager {
etag!, etag!,
accessToken accessToken
).catch((err) => { ).catch((err) => {
throw err throw prefixMessage(err, 'Error while getting session state.')
}) })
sessionState = state.trim() sessionState = state.trim()
if (this.debug && this.printedSessionState.state !== sessionState) { 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.state = sessionState
this.printedSessionState.printed = false this.printedSessionState.printed = false
@@ -194,13 +192,14 @@ export class SessionManager {
// There is an internal error present in SAS Viya 3.5 // There is an internal error present in SAS Viya 3.5
// Retry to wait for a session status in such case of SAS internal error // Retry to wait for a session status in such case of SAS internal error
if ( if (!sessionState) {
sessionState === INTERNAL_SAS_ERROR.message && if (RETRY_COUNT < RETRY_LIMIT) {
RETRY_COUNT < RETRY_LIMIT RETRY_COUNT++
) {
RETRY_COUNT++
resolve(this.waitForSession(session, etag, accessToken)) resolve(this.waitForSession(session, etag, accessToken))
} else {
reject('Could not get session state.')
}
} }
resolve(sessionState) resolve(sessionState)
@@ -220,9 +219,6 @@ export class SessionManager {
.get(url, accessToken, 'text/plain', { 'If-None-Match': etag }) .get(url, accessToken, 'text/plain', { 'If-None-Match': etag })
.then((res) => res.result as string) .then((res) => res.result as string)
.catch((err) => { .catch((err) => {
if (err.status === INTERNAL_SAS_ERROR.status)
return INTERNAL_SAS_ERROR.message
throw err throw err
}) })
} }

View File

@@ -1,5 +1,4 @@
import { ServerType } from '@sasjs/utils/types' import { ServerType } from '@sasjs/utils/types'
import { isAuthorizeFormRequired } from '.'
import { RequestClient } from '../request/RequestClient' import { RequestClient } from '../request/RequestClient'
import { serialize } from '../utils' 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 { SASViyaApiClient } from '../SASViyaApiClient'
import { import {
ErrorResponse, ErrorResponse,
@@ -17,7 +17,7 @@ export class ComputeJobExecutor extends BaseJobExecutor {
data: any, data: any,
config: any, config: any,
loginRequiredCallback?: any, loginRequiredCallback?: any,
accessToken?: string authConfig?: AuthConfig
) { ) {
const loginCallback = loginRequiredCallback || (() => Promise.resolve()) const loginCallback = loginRequiredCallback || (() => Promise.resolve())
const waitForResult = true const waitForResult = true
@@ -30,7 +30,7 @@ export class ComputeJobExecutor extends BaseJobExecutor {
config.contextName, config.contextName,
config.debug, config.debug,
data, data,
accessToken, authConfig,
waitForResult, waitForResult,
expectWebout 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 { SASViyaApiClient } from '../SASViyaApiClient'
import { import {
ErrorResponse, ErrorResponse,
@@ -18,20 +18,14 @@ export class JesJobExecutor extends BaseJobExecutor {
data: any, data: any,
config: any, config: any,
loginRequiredCallback?: any, loginRequiredCallback?: any,
accessToken?: string, authConfig?: AuthConfig,
extraResponseAttributes: ExtraResponseAttributes[] = [] extraResponseAttributes: ExtraResponseAttributes[] = []
) { ) {
const loginCallback = loginRequiredCallback || (() => Promise.resolve()) const loginCallback = loginRequiredCallback || (() => Promise.resolve())
const requestPromise = new Promise((resolve, reject) => { const requestPromise = new Promise((resolve, reject) => {
this.sasViyaApiClient this.sasViyaApiClient
?.executeJob( ?.executeJob(sasJob, config.contextName, config.debug, data, authConfig)
sasJob,
config.contextName,
config.debug,
data,
accessToken
)
.then((response: any) => { .then((response: any) => {
this.appendRequest(response, sasJob, config.debug) this.appendRequest(response, sasJob, config.debug)
@@ -69,7 +63,7 @@ export class JesJobExecutor extends BaseJobExecutor {
data, data,
config, config,
loginRequiredCallback, loginRequiredCallback,
accessToken, authConfig,
extraResponseAttributes extraResponseAttributes
).then( ).then(
(res: any) => { (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 { SASjsRequest } from '../types'
import { ExtraResponseAttributes } from '@sasjs/utils/types' import { ExtraResponseAttributes } from '@sasjs/utils/types'
import { asyncForEach, parseGeneratedCode, parseSourceCode } from '../utils' import { asyncForEach, parseGeneratedCode, parseSourceCode } from '../utils'
@@ -11,7 +11,7 @@ export interface JobExecutor {
data: any, data: any,
config: any, config: any,
loginRequiredCallback?: any, loginRequiredCallback?: any,
accessToken?: string, authConfig?: AuthConfig,
extraResponseAttributes?: ExtraResponseAttributes[] extraResponseAttributes?: ExtraResponseAttributes[]
) => Promise<any> ) => Promise<any>
resendWaitingRequests: () => Promise<void> resendWaitingRequests: () => Promise<void>
@@ -30,7 +30,7 @@ export abstract class BaseJobExecutor implements JobExecutor {
data: any, data: any,
config: any, config: any,
loginRequiredCallback?: any, loginRequiredCallback?: any,
accessToken?: string | undefined, authConfig?: AuthConfig | undefined,
extraResponseAttributes?: ExtraResponseAttributes[] extraResponseAttributes?: ExtraResponseAttributes[]
): Promise<any> ): Promise<any>

View File

@@ -8,8 +8,9 @@ import { generateFileUploadForm } from '../file/generateFileUploadForm'
import { generateTableUploadForm } from '../file/generateTableUploadForm' import { generateTableUploadForm } from '../file/generateTableUploadForm'
import { RequestClient } from '../request/RequestClient' import { RequestClient } from '../request/RequestClient'
import { SASViyaApiClient } from '../SASViyaApiClient' import { SASViyaApiClient } from '../SASViyaApiClient'
import { isRelativePath } from '../utils' import { isRelativePath, isValidJson } from '../utils'
import { BaseJobExecutor } from './JobExecutor' import { BaseJobExecutor } from './JobExecutor'
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
export interface WaitingRequstPromise { export interface WaitingRequstPromise {
promise: Promise<any> | null promise: Promise<any> | null
@@ -100,6 +101,19 @@ export class WebJobExecutor extends BaseJobExecutor {
this.appendRequest(res, sasJob, config.debug) this.appendRequest(res, sasJob, config.debug)
resolve(jsonResponse) resolve(jsonResponse)
} }
if (this.serverType === ServerType.Sas9 && config.debug) {
const jsonResponse = parseWeboutResponse(res.result as string)
if (jsonResponse === '') {
throw new Error(
'Valid JSON could not be extracted from response.'
)
}
isValidJson(jsonResponse)
this.appendRequest(res, sasJob, config.debug)
resolve(res.result)
}
isValidJson(res.result as string)
this.appendRequest(res, sasJob, config.debug) this.appendRequest(res, sasJob, config.debug)
resolve(res.result) resolve(res.result)
}) })

View File

@@ -11,6 +11,7 @@ import {
import { parseWeboutResponse } from '../utils/parseWeboutResponse' import { parseWeboutResponse } from '../utils/parseWeboutResponse'
import { prefixMessage } from '@sasjs/utils/error' import { prefixMessage } from '@sasjs/utils/error'
import { SAS9AuthError } from '../types/errors/SAS9AuthError' import { SAS9AuthError } from '../types/errors/SAS9AuthError'
import { isValidJson } from '../utils'
export interface HttpClient { export interface HttpClient {
get<T>( get<T>(
@@ -63,6 +64,9 @@ export class RequestClient implements HttpClient {
baseURL: baseUrl baseURL: baseUrl
}) })
} }
this.httpClient.defaults.validateStatus = (status) =>
status >= 200 && status < 305
} }
public getCsrfToken(type: 'general' | 'file' = 'general') { public getCsrfToken(type: 'general' | 'file' = 'general') {
@@ -287,7 +291,8 @@ export class RequestClient implements HttpClient {
}) })
.then((res) => res.data) .then((res) => res.data)
.catch((error) => { .catch((error) => {
console.log(error) const logger = process.logger || console
logger.error(error)
}) })
} }
@@ -419,7 +424,13 @@ export class RequestClient implements HttpClient {
} }
} catch { } catch {
try { try {
parsedResponse = JSON.parse(parseWeboutResponse(response.data)) const weboutResponse = parseWeboutResponse(response.data)
if (weboutResponse === '') {
throw new Error('Valid JSON could not be extracted from response.')
}
isValidJson(weboutResponse)
parsedResponse = JSON.parse(weboutResponse)
} catch { } catch {
parsedResponse = response.data parsedResponse = response.data
} }

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
}
}

View File

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

View File

@@ -12,3 +12,4 @@ export * from './serialize'
export * from './splitChunks' export * from './splitChunks'
export * from './parseWeboutResponse' export * from './parseWeboutResponse'
export * from './fetchLogByChunks' export * from './fetchLogByChunks'
export * from './isValidJson'

11
src/utils/isValidJson.ts Normal file
View File

@@ -0,0 +1,11 @@
/**
* Checks if string is in valid JSON format else throw error.
* @param str - string to check.
*/
export const isValidJson = (str: string) => {
try {
JSON.parse(str)
} catch (e) {
throw new Error('Invalid JSON response.')
}
}

View File

@@ -1,6 +1,7 @@
const path = require('path') const path = require('path')
const webpack = require('webpack') const webpack = require('webpack')
const terserPlugin = require('terser-webpack-plugin') const terserPlugin = require('terser-webpack-plugin')
const nodePolyfillPlugin = require('node-polyfill-webpack-plugin')
const defaultPlugins = [ const defaultPlugins = [
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/), new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
@@ -37,7 +38,7 @@ const browserConfig = {
}, },
resolve: { resolve: {
extensions: ['.ts', '.js'], extensions: ['.ts', '.js'],
fallback: { https: false } fallback: { https: false, fs: false, readline: false }
}, },
output: { output: {
filename: 'index.js', filename: 'index.js',
@@ -49,7 +50,8 @@ const browserConfig = {
...defaultPlugins, ...defaultPlugins,
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
process: 'process/browser' process: 'process/browser'
}) }),
new nodePolyfillPlugin()
] ]
} }