mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-05 19:50:06 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 435993e50e |
12
.github/issue_template.md
vendored
12
.github/issue_template.md
vendored
@@ -1,12 +0,0 @@
|
|||||||
## Expected behaviour
|
|
||||||
*Describe what should be happening*
|
|
||||||
|
|
||||||
## Current behaviour
|
|
||||||
*Describe what is actually happening*
|
|
||||||
|
|
||||||
## Environment info
|
|
||||||
**Client tech stack**: *Angular, React, Vue, VanillaJS, NodeJS etc.*
|
|
||||||
**Server type**: SASJS|SASVIYA|SAS9
|
|
||||||
**Login mechanism**: Default|Redirected
|
|
||||||
**Debug**: true|false
|
|
||||||
**Use Compute Api (relevant only on VIYA)**: true|false
|
|
||||||
4486
package-lock.json
generated
4486
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -59,12 +59,12 @@
|
|||||||
"jest-extended": "2.0.0",
|
"jest-extended": "2.0.0",
|
||||||
"node-polyfill-webpack-plugin": "1.1.4",
|
"node-polyfill-webpack-plugin": "1.1.4",
|
||||||
"path": "0.12.7",
|
"path": "0.12.7",
|
||||||
"pem": "1.14.5",
|
"pem": "1.14.6",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.7.1",
|
||||||
"process": "0.11.10",
|
"process": "0.11.10",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"semantic-release": "19.0.3",
|
"semantic-release": "19.0.3",
|
||||||
"terser-webpack-plugin": "5.3.6",
|
"terser-webpack-plugin": "5.3.1",
|
||||||
"ts-jest": "27.1.3",
|
"ts-jest": "27.1.3",
|
||||||
"ts-loader": "9.4.0",
|
"ts-loader": "9.4.0",
|
||||||
"tslint": "6.1.3",
|
"tslint": "6.1.3",
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"typedoc": "0.23.24",
|
"typedoc": "0.23.24",
|
||||||
"typedoc-plugin-rename-defaults": "0.6.4",
|
"typedoc-plugin-rename-defaults": "0.6.4",
|
||||||
"typescript": "4.8.3",
|
"typescript": "4.8.3",
|
||||||
"webpack": "5.76.2",
|
"webpack": "5.69.0",
|
||||||
"webpack-cli": "4.9.2"
|
"webpack-cli": "4.9.2"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ export const basicTests = (
|
|||||||
return await newAdapterIns.checkSession()
|
return await newAdapterIns.checkSession()
|
||||||
},
|
},
|
||||||
assertion: (response: any) =>
|
assertion: (response: any) =>
|
||||||
response?.isLoggedIn && response?.userName === userName
|
adapter.getSasjsConfig().serverType === ServerType.Sas9
|
||||||
|
? response?.isLoggedIn
|
||||||
|
: response?.isLoggedIn && response?.userName === userName
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Multiple Log in attempts',
|
title: 'Multiple Log in attempts',
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import {
|
|||||||
UploadFile,
|
UploadFile,
|
||||||
EditContextInput,
|
EditContextInput,
|
||||||
PollOptions,
|
PollOptions,
|
||||||
LoginMechanism
|
LoginMechanism,
|
||||||
|
ExecutionQuery
|
||||||
} from './types'
|
} from './types'
|
||||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||||
@@ -16,6 +17,7 @@ import {
|
|||||||
AuthConfig,
|
AuthConfig,
|
||||||
ExtraResponseAttributes,
|
ExtraResponseAttributes,
|
||||||
SasAuthResponse,
|
SasAuthResponse,
|
||||||
|
ServicePackSASjs,
|
||||||
AuthConfigSas9
|
AuthConfigSas9
|
||||||
} from '@sasjs/utils/types'
|
} from '@sasjs/utils/types'
|
||||||
import { RequestClient } from './request/RequestClient'
|
import { RequestClient } from './request/RequestClient'
|
||||||
@@ -893,7 +895,6 @@ export default class SASjs {
|
|||||||
await this.computeJobExecutor?.resendWaitingRequests()
|
await this.computeJobExecutor?.resendWaitingRequests()
|
||||||
await this.jesJobExecutor?.resendWaitingRequests()
|
await this.jesJobExecutor?.resendWaitingRequests()
|
||||||
await this.fileUploader?.resendWaitingRequests()
|
await this.fileUploader?.resendWaitingRequests()
|
||||||
await this.sasjsJobExecutor?.resendWaitingRequests()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ServerType } from '@sasjs/utils/types'
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
import { RequestClient } from '../request/RequestClient'
|
import { RequestClient } from '../request/RequestClient'
|
||||||
import { NotFoundError } from '../types/errors'
|
|
||||||
import { LoginOptions, LoginResult, LoginResultInternal } from '../types/Login'
|
import { LoginOptions, LoginResult, LoginResultInternal } from '../types/Login'
|
||||||
import { serialize } from '../utils'
|
import { serialize } from '../utils'
|
||||||
import { extractUserLongNameSas9 } from '../utils/sas9/extractUserLongNameSas9'
|
import { extractUserLongNameSas9 } from '../utils/sas9/extractUserLongNameSas9'
|
||||||
@@ -43,9 +42,6 @@ export class AuthManager {
|
|||||||
} = await this.fetchUserName()
|
} = await this.fetchUserName()
|
||||||
|
|
||||||
if (isLoggedInAlready) {
|
if (isLoggedInAlready) {
|
||||||
const logger = process.logger || console
|
|
||||||
logger.log('login was not attempted as a valid session already exists')
|
|
||||||
|
|
||||||
await this.loginCallback()
|
await this.loginCallback()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -113,9 +109,6 @@ export class AuthManager {
|
|||||||
} = await this.checkSession()
|
} = await this.checkSession()
|
||||||
|
|
||||||
if (isLoggedInAlready) {
|
if (isLoggedInAlready) {
|
||||||
const logger = process.logger || console
|
|
||||||
logger.log('login was not attempted as a valid session already exists')
|
|
||||||
|
|
||||||
await this.loginCallback()
|
await this.loginCallback()
|
||||||
|
|
||||||
this.userName = loginParams.username
|
this.userName = loginParams.username
|
||||||
@@ -148,7 +141,7 @@ export class AuthManager {
|
|||||||
await this.performCASSecurityCheck()
|
await this.performCASSecurityCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loginCallback()
|
await this.loginCallback()
|
||||||
this.userName = loginParams.username
|
this.userName = loginParams.username
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,12 +155,10 @@ export class AuthManager {
|
|||||||
private async performCASSecurityCheck() {
|
private async performCASSecurityCheck() {
|
||||||
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
||||||
|
|
||||||
await this.requestClient
|
await this.requestClient.get<string>(
|
||||||
.get<string>(`/SASLogon/login?service=${casAuthenticationUrl}`, undefined)
|
`/SASLogon/login?service=${casAuthenticationUrl}`,
|
||||||
.catch((err) => {
|
undefined
|
||||||
// ignore if resource not found error
|
)
|
||||||
if (!(err instanceof NotFoundError)) throw err
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendLoginRequest(
|
private async sendLoginRequest(
|
||||||
@@ -248,7 +239,7 @@ export class AuthManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { result: formResponse } = await this.requestClient.get<string>(
|
const { result: formResponse } = await this.requestClient.get<string>(
|
||||||
this.loginUrl.replace('/SASLogon/login.do', '/SASLogon/login'),
|
this.loginUrl.replace('.do', ''),
|
||||||
undefined,
|
undefined,
|
||||||
'text/plain'
|
'text/plain'
|
||||||
)
|
)
|
||||||
@@ -321,13 +312,12 @@ export class AuthManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getLoginForm(response: any) {
|
private getLoginForm(response: any) {
|
||||||
const pattern: RegExp = /<form.+action="(.*(Logon)|(login)[^"]*).*>/
|
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
|
||||||
const matches = pattern.exec(response)
|
const matches = pattern.exec(response)
|
||||||
const formInputs: any = {}
|
const formInputs: any = {}
|
||||||
|
|
||||||
if (matches && matches.length) {
|
if (matches && matches.length) {
|
||||||
this.setLoginUrl(matches)
|
this.setLoginUrl(matches)
|
||||||
response = response.replace(/<input/g, '\n<input')
|
|
||||||
const inputs = response.match(/<input.*"hidden"[^>]*>/g)
|
const inputs = response.match(/<input.*"hidden"[^>]*>/g)
|
||||||
|
|
||||||
if (inputs) {
|
if (inputs) {
|
||||||
@@ -358,7 +348,7 @@ export class AuthManager {
|
|||||||
this.loginUrl =
|
this.loginUrl =
|
||||||
this.serverType === ServerType.SasViya
|
this.serverType === ServerType.SasViya
|
||||||
? tempLoginLink
|
? tempLoginLink
|
||||||
: loginUrl.replace('/SASLogon/login.do', '/SASLogon/login')
|
: loginUrl.replace('.do', '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export const isLogInRequired = (response: string): boolean => {
|
export const isLogInRequired = (response: string): boolean => {
|
||||||
const pattern: RegExp = /<form.+action="(.*(Logon)|(login)[^"]*).*>/gm
|
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/gm
|
||||||
const matches = pattern.test(response)
|
const matches = pattern.test(response)
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import { SASViyaApiClient } from '../SASViyaApiClient'
|
|||||||
import {
|
import {
|
||||||
isRelativePath,
|
isRelativePath,
|
||||||
parseSasViyaDebugResponse,
|
parseSasViyaDebugResponse,
|
||||||
appendExtraResponseAttributes
|
appendExtraResponseAttributes,
|
||||||
|
getValidJson
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||||
@@ -183,6 +184,10 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof jsonResponse === 'string') {
|
||||||
|
jsonResponse = getValidJson(jsonResponse)
|
||||||
|
}
|
||||||
|
|
||||||
const responseObject = appendExtraResponseAttributes(
|
const responseObject = appendExtraResponseAttributes(
|
||||||
{ result: jsonResponse, log: res.log },
|
{ result: jsonResponse, log: res.log },
|
||||||
extraResponseAttributes
|
extraResponseAttributes
|
||||||
|
|||||||
@@ -1,273 +0,0 @@
|
|||||||
import { validateInput, compareTimestamps } from '../../utils'
|
|
||||||
import { SASjsConfig, UploadFile, LoginMechanism } from '../../types'
|
|
||||||
import { AuthManager } from '../../auth'
|
|
||||||
import {
|
|
||||||
ServerType,
|
|
||||||
AuthConfig,
|
|
||||||
ExtraResponseAttributes
|
|
||||||
} from '@sasjs/utils/types'
|
|
||||||
import { RequestClient } from '../../request/RequestClient'
|
|
||||||
import { FileUploader } from '../../job-execution/FileUploader'
|
|
||||||
import { WebJobExecutor } from './WebJobExecutor'
|
|
||||||
import { ErrorResponse } from '../../types/errors/ErrorResponse'
|
|
||||||
import { LoginOptions, LoginResult } from '../../types/Login'
|
|
||||||
|
|
||||||
const defaultConfig: SASjsConfig = {
|
|
||||||
serverUrl: '',
|
|
||||||
pathSASJS: '/SASjsApi/stp/execute',
|
|
||||||
pathSAS9: '/SASStoredProcess/do',
|
|
||||||
pathSASViya: '/SASJobExecution',
|
|
||||||
appLoc: '/Public/seedapp',
|
|
||||||
serverType: ServerType.Sas9,
|
|
||||||
debug: false,
|
|
||||||
contextName: 'SAS Job Execution compute context',
|
|
||||||
useComputeApi: null,
|
|
||||||
loginMechanism: LoginMechanism.Default
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SASjs is a JavaScript adapter for SAS.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export default class SASjs {
|
|
||||||
private sasjsConfig: SASjsConfig = new SASjsConfig()
|
|
||||||
private jobsPath: string = ''
|
|
||||||
private fileUploader: FileUploader | null = null
|
|
||||||
private authManager: AuthManager | null = null
|
|
||||||
private requestClient: RequestClient | null = null
|
|
||||||
private webJobExecutor: WebJobExecutor | null = null
|
|
||||||
|
|
||||||
constructor(config?: Partial<SASjsConfig>) {
|
|
||||||
this.sasjsConfig = {
|
|
||||||
...defaultConfig,
|
|
||||||
...config
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setupConfiguration()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs into the SAS server with the supplied credentials.
|
|
||||||
* @param username - a string representing the username.
|
|
||||||
* @param password - a string representing the password.
|
|
||||||
* @param clientId - a string representing the client ID.
|
|
||||||
*/
|
|
||||||
public async logIn(
|
|
||||||
username?: string,
|
|
||||||
password?: string,
|
|
||||||
clientId?: string,
|
|
||||||
options: LoginOptions = {}
|
|
||||||
): Promise<LoginResult> {
|
|
||||||
if (this.sasjsConfig.loginMechanism === LoginMechanism.Default) {
|
|
||||||
if (!username || !password)
|
|
||||||
throw new Error(
|
|
||||||
'A username and password are required when using the default login mechanism.'
|
|
||||||
)
|
|
||||||
|
|
||||||
return this.authManager!.logIn(username, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof window === typeof undefined) {
|
|
||||||
throw new Error(
|
|
||||||
'The redirected login mechanism is only available for use in the browser.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.authManager!.redirectedLogIn(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs out of the configured SAS server.
|
|
||||||
*/
|
|
||||||
public logOut() {
|
|
||||||
return this.authManager!.logOut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current SASjs configuration.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public getSasjsConfig() {
|
|
||||||
return this.sasjsConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this method returns an array of SASjsRequest
|
|
||||||
* @returns SASjsRequest[]
|
|
||||||
*/
|
|
||||||
public getSasRequests() {
|
|
||||||
const requests = [...this.requestClient!.getRequests()]
|
|
||||||
const sortedRequests = requests.sort(compareTimestamps)
|
|
||||||
return sortedRequests
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the debug state. Turning this on will enable additional logging in the adapter.
|
|
||||||
* @param value - boolean indicating debug state (on/off).
|
|
||||||
*/
|
|
||||||
public setDebugState(value: boolean) {
|
|
||||||
this.sasjsConfig.debug = value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads a file to the given service.
|
|
||||||
* @param sasJob - the path to the SAS program (ultimately resolves to
|
|
||||||
* the SAS `_program` parameter to run a Job Definition or SAS 9 Stored
|
|
||||||
* Process). Is prepended at runtime with the value of `appLoc`.
|
|
||||||
* @param files - array of files to be uploaded, including File object and file name.
|
|
||||||
* @param params - request URL parameters.
|
|
||||||
* @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 loginRequiredCallback - a function that is called if the
|
|
||||||
* user is not logged in (eg to display a login form). The request will be
|
|
||||||
* resubmitted after successful login.
|
|
||||||
*/
|
|
||||||
public async uploadFile(
|
|
||||||
sasJob: string,
|
|
||||||
files: UploadFile[],
|
|
||||||
params: { [key: string]: any } | null,
|
|
||||||
config: { [key: string]: any } = {},
|
|
||||||
loginRequiredCallback?: () => any
|
|
||||||
) {
|
|
||||||
config = {
|
|
||||||
...this.sasjsConfig,
|
|
||||||
...config
|
|
||||||
}
|
|
||||||
const data = { files, params }
|
|
||||||
|
|
||||||
return await this.fileUploader!.execute(
|
|
||||||
sasJob,
|
|
||||||
data,
|
|
||||||
config,
|
|
||||||
loginRequiredCallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes a request to program specified in `SASjob` (could be a Viya Job, a
|
|
||||||
* SAS 9 Stored Process, or a SASjs Server Stored Program). The response
|
|
||||||
* object will always contain table names in lowercase, and column names in
|
|
||||||
* uppercase. Values are returned formatted by default, unformatted
|
|
||||||
* values can be configured as an option in the `%webout` macro.
|
|
||||||
*
|
|
||||||
* @param sasJob - the path to the SAS program (ultimately resolves to
|
|
||||||
* the SAS `_program` parameter to run a Job Definition or SAS 9 Stored
|
|
||||||
* Process). Is prepended at runtime with the value of `appLoc`.
|
|
||||||
* @param data - a JSON object containing one or more tables to be sent to
|
|
||||||
* SAS. For an example of the table structure, see the project README. This
|
|
||||||
* value can be `null` if no inputs are required.
|
|
||||||
* @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 loginRequiredCallback - a function that is called if the
|
|
||||||
* user is not logged in (eg to display a login form). The request will be
|
|
||||||
* resubmitted after successful login.
|
|
||||||
* When using a `loginRequiredCallback`, the call to the request will look, for example, like so:
|
|
||||||
* `await request(sasJobPath, data, config, () => setIsLoggedIn(false))`
|
|
||||||
* If you are not passing in any data and configuration, it will look like so:
|
|
||||||
* `await request(sasJobPath, {}, {}, () => setIsLoggedIn(false))`
|
|
||||||
* @param extraResponseAttributes - a array of predefined values that are used
|
|
||||||
* to provide extra attributes (same names as those values) to be added in response
|
|
||||||
* Supported values are declared in ExtraResponseAttributes type.
|
|
||||||
*/
|
|
||||||
public async request(
|
|
||||||
sasJob: string,
|
|
||||||
data: { [key: string]: any } | null,
|
|
||||||
config: { [key: string]: any } = {},
|
|
||||||
loginRequiredCallback?: () => any,
|
|
||||||
authConfig?: AuthConfig,
|
|
||||||
extraResponseAttributes: ExtraResponseAttributes[] = []
|
|
||||||
) {
|
|
||||||
config = {
|
|
||||||
...this.sasjsConfig,
|
|
||||||
...config
|
|
||||||
}
|
|
||||||
|
|
||||||
const validationResult = validateInput(data)
|
|
||||||
|
|
||||||
// status is true if the data passes validation checks above
|
|
||||||
if (validationResult.status) {
|
|
||||||
return await this.webJobExecutor!.execute(
|
|
||||||
sasJob,
|
|
||||||
data,
|
|
||||||
config,
|
|
||||||
loginRequiredCallback,
|
|
||||||
authConfig,
|
|
||||||
extraResponseAttributes
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new ErrorResponse(validationResult.msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a session is active, or login is required.
|
|
||||||
* @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
|
|
||||||
*/
|
|
||||||
public async checkSession() {
|
|
||||||
return this.authManager!.checkSession()
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupConfiguration() {
|
|
||||||
if (
|
|
||||||
this.sasjsConfig.serverUrl === undefined ||
|
|
||||||
this.sasjsConfig.serverUrl === ''
|
|
||||||
) {
|
|
||||||
if (typeof location !== 'undefined') {
|
|
||||||
let url = `${location.protocol}//${location.hostname}`
|
|
||||||
|
|
||||||
if (location.port) url = `${url}:${location.port}`
|
|
||||||
|
|
||||||
this.sasjsConfig.serverUrl = url
|
|
||||||
} else {
|
|
||||||
this.sasjsConfig.serverUrl = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.sasjsConfig.serverUrl.slice(-1) === '/') {
|
|
||||||
this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.requestClient) {
|
|
||||||
this.requestClient = new RequestClient(
|
|
||||||
this.sasjsConfig.serverUrl,
|
|
||||||
this.sasjsConfig.httpsAgentOptions,
|
|
||||||
this.sasjsConfig.requestHistoryLimit
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.requestClient.setConfig(
|
|
||||||
this.sasjsConfig.serverUrl,
|
|
||||||
this.sasjsConfig.httpsAgentOptions
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.jobsPath = this.sasjsConfig.pathSAS9
|
|
||||||
|
|
||||||
this.authManager = new AuthManager(
|
|
||||||
this.sasjsConfig.serverUrl,
|
|
||||||
this.sasjsConfig.serverType!,
|
|
||||||
this.requestClient,
|
|
||||||
this.resendWaitingRequests
|
|
||||||
)
|
|
||||||
|
|
||||||
this.fileUploader = new FileUploader(
|
|
||||||
this.sasjsConfig.serverUrl,
|
|
||||||
this.sasjsConfig.serverType!,
|
|
||||||
this.jobsPath,
|
|
||||||
this.requestClient
|
|
||||||
)
|
|
||||||
|
|
||||||
this.webJobExecutor = new WebJobExecutor(
|
|
||||||
this.sasjsConfig.serverUrl,
|
|
||||||
this.sasjsConfig.serverType!,
|
|
||||||
this.jobsPath,
|
|
||||||
this.requestClient
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private resendWaitingRequests = async () => {
|
|
||||||
await this.webJobExecutor?.resendWaitingRequests()
|
|
||||||
await this.fileUploader?.resendWaitingRequests()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
import {
|
|
||||||
AuthConfig,
|
|
||||||
ExtraResponseAttributes,
|
|
||||||
ServerType
|
|
||||||
} from '@sasjs/utils/types'
|
|
||||||
import {
|
|
||||||
ErrorResponse,
|
|
||||||
JobExecutionError,
|
|
||||||
LoginRequiredError
|
|
||||||
} from '../../types/errors'
|
|
||||||
import { RequestClient } from '../../request/RequestClient'
|
|
||||||
import {
|
|
||||||
isRelativePath,
|
|
||||||
parseSasViyaDebugResponse,
|
|
||||||
appendExtraResponseAttributes,
|
|
||||||
convertToCSV
|
|
||||||
} from '../../utils'
|
|
||||||
import { BaseJobExecutor } from '../../job-execution/JobExecutor'
|
|
||||||
import { parseWeboutResponse } from '../../utils/parseWeboutResponse'
|
|
||||||
|
|
||||||
export interface WaitingRequstPromise {
|
|
||||||
promise: Promise<any> | null
|
|
||||||
resolve: any
|
|
||||||
reject: any
|
|
||||||
}
|
|
||||||
export class WebJobExecutor extends BaseJobExecutor {
|
|
||||||
constructor(
|
|
||||||
serverUrl: string,
|
|
||||||
serverType: ServerType,
|
|
||||||
private jobsPath: string,
|
|
||||||
private requestClient: RequestClient
|
|
||||||
) {
|
|
||||||
super(serverUrl, serverType)
|
|
||||||
}
|
|
||||||
|
|
||||||
async execute(
|
|
||||||
sasJob: string,
|
|
||||||
data: any,
|
|
||||||
config: any,
|
|
||||||
loginRequiredCallback?: any,
|
|
||||||
authConfig?: AuthConfig,
|
|
||||||
extraResponseAttributes: ExtraResponseAttributes[] = []
|
|
||||||
) {
|
|
||||||
const loginCallback = loginRequiredCallback
|
|
||||||
const program = isRelativePath(sasJob)
|
|
||||||
? config.appLoc
|
|
||||||
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
|
||||||
: sasJob
|
|
||||||
: sasJob
|
|
||||||
let apiUrl = `${config.serverUrl}${this.jobsPath}/?${'_program=' + program}`
|
|
||||||
|
|
||||||
let requestParams = {
|
|
||||||
...this.getRequestParams(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
let formData = new FormData()
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
try {
|
|
||||||
formData = generateFileUploadForm(formData, data)
|
|
||||||
} catch (e: any) {
|
|
||||||
return Promise.reject(new ErrorResponse(e?.message, e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in requestParams) {
|
|
||||||
if (requestParams.hasOwnProperty(key)) {
|
|
||||||
formData.append(key, requestParams[key])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestPromise = new Promise((resolve, reject) => {
|
|
||||||
this.requestClient!.post(apiUrl, formData, authConfig?.access_token)
|
|
||||||
.then(async (res: any) => {
|
|
||||||
this.requestClient!.appendRequest(res, sasJob, config.debug)
|
|
||||||
|
|
||||||
const jsonResponse =
|
|
||||||
config.debug && typeof res.result === 'string'
|
|
||||||
? parseWeboutResponse(res.result, apiUrl)
|
|
||||||
: res.result
|
|
||||||
|
|
||||||
const responseObject = appendExtraResponseAttributes(
|
|
||||||
{ result: jsonResponse, log: res.log },
|
|
||||||
extraResponseAttributes
|
|
||||||
)
|
|
||||||
resolve(responseObject)
|
|
||||||
})
|
|
||||||
.catch(async (e: Error) => {
|
|
||||||
if (e instanceof JobExecutionError) {
|
|
||||||
this.requestClient!.appendRequest(e, sasJob, config.debug)
|
|
||||||
reject(new ErrorResponse(e?.message, e))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e instanceof LoginRequiredError) {
|
|
||||||
if (!loginRequiredCallback) {
|
|
||||||
reject(
|
|
||||||
new ErrorResponse(
|
|
||||||
'Request is not authenticated. Make sure .env file exists with valid credentials.',
|
|
||||||
e
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.appendWaitingRequest(() => {
|
|
||||||
return this.execute(
|
|
||||||
sasJob,
|
|
||||||
data,
|
|
||||||
config,
|
|
||||||
loginRequiredCallback,
|
|
||||||
authConfig,
|
|
||||||
extraResponseAttributes
|
|
||||||
).then(
|
|
||||||
(res: any) => {
|
|
||||||
resolve(res)
|
|
||||||
},
|
|
||||||
(err: any) => {
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (loginCallback) await loginCallback()
|
|
||||||
} else reject(new ErrorResponse(e?.message, e))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return requestPromise
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* One of the approaches SASjs takes to send tables-formatted JSON (see README)
|
|
||||||
* to SAS is as multipart form data, where each table is provided as a specially
|
|
||||||
* formatted CSV file.
|
|
||||||
*/
|
|
||||||
const generateFileUploadForm = (formData: FormData, data: any): FormData => {
|
|
||||||
for (const tableName in data) {
|
|
||||||
if (!Array.isArray(data[tableName])) continue
|
|
||||||
|
|
||||||
const name = tableName
|
|
||||||
const csv = convertToCSV(data, tableName)
|
|
||||||
|
|
||||||
if (csv === 'ERROR: LARGE STRING LENGTH') {
|
|
||||||
throw new Error(
|
|
||||||
'The max length of a string value in SASjs is 32765 characters.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = new Blob([csv], {
|
|
||||||
type: 'application/csv'
|
|
||||||
})
|
|
||||||
|
|
||||||
formData.append(name, file, `${name}.csv`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return formData
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import SASjs from './SASjs'
|
|
||||||
export * from '../../types'
|
|
||||||
export default SASjs
|
|
||||||
@@ -4,7 +4,6 @@ import axiosCookieJarSupport from 'axios-cookiejar-support'
|
|||||||
import * as tough from 'tough-cookie'
|
import * as tough from 'tough-cookie'
|
||||||
import { prefixMessage } from '@sasjs/utils/error'
|
import { prefixMessage } from '@sasjs/utils/error'
|
||||||
import { RequestClient, throwIfError } from './RequestClient'
|
import { RequestClient, throwIfError } from './RequestClient'
|
||||||
import { JobExecutionError } from '../types/errors'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specific request client for SAS9 in Node.js environments.
|
* Specific request client for SAS9 in Node.js environments.
|
||||||
@@ -70,8 +69,6 @@ export class Sas9RequestClient extends RequestClient {
|
|||||||
return this.parseResponse<T>(response)
|
return this.parseResponse<T>(response)
|
||||||
})
|
})
|
||||||
.catch(async (e: any) => {
|
.catch(async (e: any) => {
|
||||||
if (e instanceof JobExecutionError) throw e
|
|
||||||
|
|
||||||
return await this.handleError(
|
return await this.handleError(
|
||||||
e,
|
e,
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -23,16 +23,8 @@ const optimization = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const browserConfig = {
|
const browserConfig = {
|
||||||
entry: {
|
entry: './src/index.ts',
|
||||||
index: './src/index.ts',
|
devtool: 'inline-source-map',
|
||||||
minified_sas9: './src/minified/sas9/index.ts'
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
filename: '[name].js',
|
|
||||||
path: path.resolve(__dirname, 'build'),
|
|
||||||
libraryTarget: 'umd',
|
|
||||||
library: 'SASjs'
|
|
||||||
},
|
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
optimization: optimization,
|
optimization: optimization,
|
||||||
module: {
|
module: {
|
||||||
@@ -48,6 +40,12 @@ const browserConfig = {
|
|||||||
extensions: ['.ts', '.js'],
|
extensions: ['.ts', '.js'],
|
||||||
fallback: { https: false, fs: false, readline: false }
|
fallback: { https: false, fs: false, readline: false }
|
||||||
},
|
},
|
||||||
|
output: {
|
||||||
|
filename: 'index.js',
|
||||||
|
path: path.resolve(__dirname, 'build'),
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
library: 'SASjs'
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
...defaultPlugins,
|
...defaultPlugins,
|
||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
@@ -57,18 +55,6 @@ const browserConfig = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const browserConfigWithDevTool = {
|
|
||||||
...browserConfig,
|
|
||||||
entry: './src/index.ts',
|
|
||||||
output: {
|
|
||||||
filename: 'index-dev.js',
|
|
||||||
path: path.resolve(__dirname, 'build'),
|
|
||||||
libraryTarget: 'umd',
|
|
||||||
library: 'SASjs'
|
|
||||||
},
|
|
||||||
devtool: 'inline-source-map'
|
|
||||||
}
|
|
||||||
|
|
||||||
const browserConfigWithoutProcessPlugin = {
|
const browserConfigWithoutProcessPlugin = {
|
||||||
entry: browserConfig.entry,
|
entry: browserConfig.entry,
|
||||||
devtool: browserConfig.devtool,
|
devtool: browserConfig.devtool,
|
||||||
@@ -90,4 +76,4 @@ const nodeConfig = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = [browserConfig, browserConfigWithDevTool, nodeConfig]
|
module.exports = [browserConfig, nodeConfig]
|
||||||
|
|||||||
Reference in New Issue
Block a user