mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-05 11:40:06 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62deaf9f03 | ||
|
|
865bf71f7d | ||
|
|
734c5bccaa | ||
|
|
bc82cb5f5e | ||
|
|
e649b41e9e | ||
|
|
a962979765 | ||
|
|
01d76fa66f | ||
|
|
49cfde9f7d | ||
|
|
ce04ffea05 | ||
| 9dc0499f66 | |||
|
|
0457eb6663 | ||
|
|
519494718b | ||
|
|
0321f77451 | ||
|
|
6c5fdc01eb | ||
|
|
2aa0cd8d7a | ||
|
|
397bc4524f | ||
|
|
8dce9f3e48 | ||
| 8e9f1df1ce | |||
|
|
ff4915f7f3 | ||
|
|
6ff8eece7b | ||
|
|
2849e6ed07 | ||
|
|
90b11fe3fa | ||
|
|
147609842d | ||
|
|
dd6f9cd617 | ||
|
|
7f590c35da | ||
|
|
a38de108e3 | ||
|
|
e975e7de97 | ||
|
|
d418a7e971 | ||
|
|
a5b5052a5f | ||
|
|
7638595523 | ||
|
|
70d64f6eec | ||
|
|
f0ecfa57e5 | ||
|
|
5f3416ecd7 | ||
|
|
d8b1a72da2 | ||
|
|
7e64819eb2 | ||
|
|
2f1d403af4 | ||
|
|
075d410f7d | ||
| 085a3f84e9 |
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -13,14 +13,15 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [15.x]
|
node-version: [lts/*]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: npm
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Check code style
|
- name: Check code style
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,4 +5,4 @@ build
|
|||||||
|
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ docs/
|
|||||||
.github/
|
.github/
|
||||||
*.md
|
*.md
|
||||||
*.spec.ts
|
*.spec.ts
|
||||||
|
.all-contributorsrc
|
||||||
|
|||||||
20142
package-lock.json
generated
20142
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -41,17 +41,21 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/axios": "^0.14.0",
|
"@types/axios": "^0.14.0",
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
"@types/form-data": "^2.5.0",
|
"@types/form-data": "^2.5.0",
|
||||||
"@types/jest": "^27.0.1",
|
"@types/jest": "^27.0.1",
|
||||||
"@types/mime": "^2.0.3",
|
"@types/mime": "^2.0.3",
|
||||||
|
"@types/pem": "^1.9.6",
|
||||||
"@types/tough-cookie": "^4.0.1",
|
"@types/tough-cookie": "^4.0.1",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
"cp": "^0.2.0",
|
"cp": "^0.2.0",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
|
"express": "^4.17.1",
|
||||||
"jest": "^27.2.0",
|
"jest": "^27.2.0",
|
||||||
"jest-extended": "^0.11.5",
|
"jest-extended": "^0.11.5",
|
||||||
"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.4",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semantic-release": "^17.4.7",
|
"semantic-release": "^17.4.7",
|
||||||
@@ -69,7 +73,7 @@
|
|||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/utils": "^2.30.0",
|
"@sasjs/utils": "^2.32.0",
|
||||||
"axios": "^0.21.4",
|
"axios": "^0.21.4",
|
||||||
"axios-cookiejar-support": "^1.0.1",
|
"axios-cookiejar-support": "^1.0.1",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
|
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
|
||||||
"@sasjs/test-framework": "^1.4.0",
|
"@sasjs/test-framework": "^1.4.2",
|
||||||
"@types/jest": "^26.0.20",
|
"@types/jest": "^26.0.20",
|
||||||
"@types/node": "^14.14.41",
|
"@types/node": "^14.14.41",
|
||||||
"@types/react": "^17.0.1",
|
"@types/react": "^17.0.1",
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ const defaultConfig: SASjsConfig = {
|
|||||||
debug: false,
|
debug: false,
|
||||||
contextName: 'SAS Job Execution compute context',
|
contextName: 'SAS Job Execution compute context',
|
||||||
useComputeApi: false,
|
useComputeApi: false,
|
||||||
allowInsecureRequests: false,
|
|
||||||
loginMechanism: LoginMechanism.Default
|
loginMechanism: LoginMechanism.Default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import * as https from 'https'
|
||||||
import { generateTimestamp } from '@sasjs/utils/time'
|
import { generateTimestamp } from '@sasjs/utils/time'
|
||||||
import * as NodeFormData from 'form-data'
|
import * as NodeFormData from 'form-data'
|
||||||
import { Sas9RequestClient } from './request/Sas9RequestClient'
|
import { Sas9RequestClient } from './request/Sas9RequestClient'
|
||||||
@@ -13,10 +14,10 @@ export class SAS9ApiClient {
|
|||||||
constructor(
|
constructor(
|
||||||
private serverUrl: string,
|
private serverUrl: string,
|
||||||
private jobsPath: string,
|
private jobsPath: string,
|
||||||
allowInsecureRequests: boolean
|
httpsAgentOptions?: https.AgentOptions
|
||||||
) {
|
) {
|
||||||
if (serverUrl) isUrl(serverUrl)
|
if (serverUrl) isUrl(serverUrl)
|
||||||
this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests)
|
this.requestClient = new Sas9RequestClient(serverUrl, httpsAgentOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
50
src/SASjs.ts
50
src/SASjs.ts
@@ -4,10 +4,14 @@ import {
|
|||||||
UploadFile,
|
UploadFile,
|
||||||
EditContextInput,
|
EditContextInput,
|
||||||
PollOptions,
|
PollOptions,
|
||||||
LoginMechanism
|
LoginMechanism,
|
||||||
|
FolderMember,
|
||||||
|
ServiceMember,
|
||||||
|
ExecutionQuery
|
||||||
} from './types'
|
} from './types'
|
||||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||||
|
import { SASjsApiClient } from './SASjsApiClient'
|
||||||
import { AuthManager } from './auth'
|
import { AuthManager } from './auth'
|
||||||
import {
|
import {
|
||||||
ServerType,
|
ServerType,
|
||||||
@@ -36,7 +40,6 @@ const defaultConfig: SASjsConfig = {
|
|||||||
debug: false,
|
debug: false,
|
||||||
contextName: 'SAS Job Execution compute context',
|
contextName: 'SAS Job Execution compute context',
|
||||||
useComputeApi: null,
|
useComputeApi: null,
|
||||||
allowInsecureRequests: false,
|
|
||||||
loginMechanism: LoginMechanism.Default
|
loginMechanism: LoginMechanism.Default
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +52,7 @@ export default class SASjs {
|
|||||||
private jobsPath: string = ''
|
private jobsPath: string = ''
|
||||||
private sasViyaApiClient: SASViyaApiClient | null = null
|
private sasViyaApiClient: SASViyaApiClient | null = null
|
||||||
private sas9ApiClient: SAS9ApiClient | null = null
|
private sas9ApiClient: SAS9ApiClient | null = null
|
||||||
|
private SASjsApiClient: SASjsApiClient | null = null
|
||||||
private fileUploader: FileUploader | null = null
|
private fileUploader: FileUploader | null = null
|
||||||
private authManager: AuthManager | null = null
|
private authManager: AuthManager | null = null
|
||||||
private requestClient: RequestClient | null = null
|
private requestClient: RequestClient | null = null
|
||||||
@@ -57,7 +61,7 @@ export default class SASjs {
|
|||||||
private jesJobExecutor: JobExecutor | null = null
|
private jesJobExecutor: JobExecutor | null = null
|
||||||
private sas9JobExecutor: JobExecutor | null = null
|
private sas9JobExecutor: JobExecutor | null = null
|
||||||
|
|
||||||
constructor(config?: any) {
|
constructor(config?: Partial<SASjsConfig>) {
|
||||||
this.sasjsConfig = {
|
this.sasjsConfig = {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
...config
|
...config
|
||||||
@@ -792,7 +796,7 @@ export default class SASjs {
|
|||||||
sasApiClient = new SAS9ApiClient(
|
sasApiClient = new SAS9ApiClient(
|
||||||
serverUrl,
|
serverUrl,
|
||||||
this.jobsPath,
|
this.jobsPath,
|
||||||
this.sasjsConfig.allowInsecureRequests
|
this.sasjsConfig.httpsAgentOptions
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -824,6 +828,14 @@ export default class SASjs {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async deployToSASjs(members: [FolderMember, ServiceMember]) {
|
||||||
|
return await this.SASjsApiClient?.deploy(members, this.sasjsConfig.appLoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async executeJobSASjs(query: ExecutionQuery) {
|
||||||
|
return await this.SASjsApiClient?.executeJob(query)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kicks off execution of the given job via the compute API.
|
* Kicks off execution of the given job via the compute API.
|
||||||
* @returns an object representing the compute session created for the given job.
|
* @returns an object representing the compute session created for the given job.
|
||||||
@@ -951,12 +963,12 @@ export default class SASjs {
|
|||||||
if (!this.requestClient) {
|
if (!this.requestClient) {
|
||||||
this.requestClient = new RequestClient(
|
this.requestClient = new RequestClient(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.allowInsecureRequests
|
this.sasjsConfig.httpsAgentOptions
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
this.requestClient.setConfig(
|
this.requestClient.setConfig(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.allowInsecureRequests
|
this.sasjsConfig.httpsAgentOptions
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -973,30 +985,44 @@ export default class SASjs {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (this.sasjsConfig.serverType === ServerType.SasViya) {
|
if (this.sasjsConfig.serverType === ServerType.SasViya) {
|
||||||
if (this.sasViyaApiClient)
|
if (this.sasViyaApiClient) {
|
||||||
this.sasViyaApiClient!.setConfig(
|
this.sasViyaApiClient!.setConfig(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.appLoc
|
this.sasjsConfig.appLoc
|
||||||
)
|
)
|
||||||
else
|
} else {
|
||||||
this.sasViyaApiClient = new SASViyaApiClient(
|
this.sasViyaApiClient = new SASViyaApiClient(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.appLoc,
|
this.sasjsConfig.appLoc,
|
||||||
this.sasjsConfig.contextName,
|
this.sasjsConfig.contextName,
|
||||||
this.requestClient
|
this.requestClient
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
this.sasViyaApiClient.debug = this.sasjsConfig.debug
|
this.sasViyaApiClient.debug = this.sasjsConfig.debug
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.sasjsConfig.serverType === ServerType.Sas9) {
|
if (this.sasjsConfig.serverType === ServerType.Sas9) {
|
||||||
if (this.sas9ApiClient)
|
if (this.sas9ApiClient) {
|
||||||
this.sas9ApiClient!.setConfig(this.sasjsConfig.serverUrl)
|
this.sas9ApiClient!.setConfig(this.sasjsConfig.serverUrl)
|
||||||
else
|
} else {
|
||||||
this.sas9ApiClient = new SAS9ApiClient(
|
this.sas9ApiClient = new SAS9ApiClient(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.jobsPath,
|
this.jobsPath,
|
||||||
this.sasjsConfig.allowInsecureRequests
|
this.sasjsConfig.httpsAgentOptions
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sasjsConfig.serverType === ServerType.Sasjs) {
|
||||||
|
if (this.SASjsApiClient) {
|
||||||
|
this.SASjsApiClient.setConfig(this.sasjsConfig.serverUrl)
|
||||||
|
} else {
|
||||||
|
this.SASjsApiClient = new SASjsApiClient(
|
||||||
|
this.sasjsConfig.serverUrl,
|
||||||
|
this.requestClient
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fileUploader = new FileUploader(
|
this.fileUploader = new FileUploader(
|
||||||
@@ -1018,7 +1044,7 @@ export default class SASjs {
|
|||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.serverType!,
|
this.sasjsConfig.serverType!,
|
||||||
this.jobsPath,
|
this.jobsPath,
|
||||||
this.sasjsConfig.allowInsecureRequests
|
this.sasjsConfig.httpsAgentOptions
|
||||||
)
|
)
|
||||||
|
|
||||||
this.computeJobExecutor = new ComputeJobExecutor(
|
this.computeJobExecutor = new ComputeJobExecutor(
|
||||||
|
|||||||
39
src/SASjsApiClient.ts
Normal file
39
src/SASjsApiClient.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { FolderMember, ServiceMember, ExecutionQuery } from './types'
|
||||||
|
import { RequestClient } from './request/RequestClient'
|
||||||
|
|
||||||
|
export class SASjsApiClient {
|
||||||
|
constructor(
|
||||||
|
private serverUrl: string,
|
||||||
|
private requestClient: RequestClient
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public setConfig(serverUrl: string) {
|
||||||
|
if (serverUrl) this.serverUrl = serverUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deploy(members: [FolderMember, ServiceMember], appLoc: string) {
|
||||||
|
const { result } = await this.requestClient.post<{
|
||||||
|
status: string
|
||||||
|
message: string
|
||||||
|
example?: {}
|
||||||
|
}>(
|
||||||
|
'SASjsApi/drive/deploy',
|
||||||
|
{ fileTree: members, appLoc: appLoc },
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
|
||||||
|
return Promise.resolve(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async executeJob(query: ExecutionQuery) {
|
||||||
|
const { result } = await this.requestClient.post<{
|
||||||
|
status: string
|
||||||
|
message: string
|
||||||
|
log?: string
|
||||||
|
logPath?: string
|
||||||
|
error?: {}
|
||||||
|
}>('SASjsApi/stp/execute', query, undefined)
|
||||||
|
|
||||||
|
return Promise.resolve(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -256,6 +256,8 @@ export class AuthManager {
|
|||||||
.split(' ')
|
.split(' ')
|
||||||
.map((name: string) => name.slice(0, 3).toLowerCase())
|
.map((name: string) => name.slice(0, 3).toLowerCase())
|
||||||
.join('')
|
.join('')
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export async function getAccessToken(
|
|||||||
)
|
)
|
||||||
.then((res) => res.result as SasAuthResponse)
|
.then((res) => res.result as SasAuthResponse)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw prefixMessage(err, 'Error while getting access token')
|
throw prefixMessage(err, 'Error while getting access token. ')
|
||||||
})
|
})
|
||||||
|
|
||||||
return authResponse
|
return authResponse
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import * as https from 'https'
|
||||||
import { ServerType } from '@sasjs/utils/types'
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
import * as NodeFormData from 'form-data'
|
import * as NodeFormData from 'form-data'
|
||||||
import { ErrorResponse } from '../types/errors'
|
import { ErrorResponse } from '../types/errors'
|
||||||
@@ -17,10 +18,10 @@ export class Sas9JobExecutor extends BaseJobExecutor {
|
|||||||
serverUrl: string,
|
serverUrl: string,
|
||||||
serverType: ServerType,
|
serverType: ServerType,
|
||||||
private jobsPath: string,
|
private jobsPath: string,
|
||||||
allowInsecureRequests: boolean
|
httpsAgentOptions?: https.AgentOptions
|
||||||
) {
|
) {
|
||||||
super(serverUrl, serverType)
|
super(serverUrl, serverType)
|
||||||
this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests)
|
this.requestClient = new Sas9RequestClient(serverUrl, httpsAgentOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(sasJob: string, data: any, config: any) {
|
async execute(sasJob: string, data: any, config: any) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||||
|
import * as https from 'https'
|
||||||
import { CsrfToken } from '..'
|
import { CsrfToken } from '..'
|
||||||
import { isAuthorizeFormRequired, isLogInRequired } from '../auth'
|
import { isAuthorizeFormRequired, isLogInRequired } from '../auth'
|
||||||
import {
|
import {
|
||||||
@@ -12,7 +13,11 @@ import { SASjsRequest } from '../types'
|
|||||||
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 { parseGeneratedCode, parseSourceCode } from '../utils'
|
import {
|
||||||
|
parseGeneratedCode,
|
||||||
|
parseSourceCode,
|
||||||
|
createAxiosInstance
|
||||||
|
} from '../utils'
|
||||||
|
|
||||||
export interface HttpClient {
|
export interface HttpClient {
|
||||||
get<T>(
|
get<T>(
|
||||||
@@ -54,12 +59,15 @@ export class RequestClient implements HttpClient {
|
|||||||
protected fileUploadCsrfToken: CsrfToken | undefined
|
protected fileUploadCsrfToken: CsrfToken | undefined
|
||||||
protected httpClient!: AxiosInstance
|
protected httpClient!: AxiosInstance
|
||||||
|
|
||||||
constructor(protected baseUrl: string, allowInsecure = false) {
|
constructor(
|
||||||
this.createHttpClient(baseUrl, allowInsecure)
|
protected baseUrl: string,
|
||||||
|
httpsAgentOptions?: https.AgentOptions
|
||||||
|
) {
|
||||||
|
this.createHttpClient(baseUrl, httpsAgentOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
public setConfig(baseUrl: string, allowInsecure = false) {
|
public setConfig(baseUrl: string, httpsAgentOptions?: https.AgentOptions) {
|
||||||
this.createHttpClient(baseUrl, allowInsecure)
|
this.createHttpClient(baseUrl, httpsAgentOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCsrfToken(type: 'general' | 'file' = 'general') {
|
public getCsrfToken(type: 'general' | 'file' = 'general') {
|
||||||
@@ -511,23 +519,18 @@ export class RequestClient implements HttpClient {
|
|||||||
return responseToReturn
|
return responseToReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
private createHttpClient(baseUrl: string, allowInsecure = false) {
|
private createHttpClient(
|
||||||
const https = require('https')
|
baseUrl: string,
|
||||||
if (allowInsecure && https.Agent) {
|
httpsAgentOptions?: https.AgentOptions
|
||||||
this.httpClient = axios.create({
|
) {
|
||||||
baseURL: baseUrl,
|
const httpsAgent = httpsAgentOptions
|
||||||
httpsAgent: new https.Agent({
|
? new https.Agent(httpsAgentOptions)
|
||||||
rejectUnauthorized: !allowInsecure
|
: undefined
|
||||||
})
|
|
||||||
})
|
this.httpClient = createAxiosInstance(baseUrl, httpsAgent)
|
||||||
} else {
|
|
||||||
this.httpClient = axios.create({
|
|
||||||
baseURL: baseUrl
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.httpClient.defaults.validateStatus = (status) =>
|
this.httpClient.defaults.validateStatus = (status) =>
|
||||||
status >= 200 && status < 305
|
status >= 200 && status < 401
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import * as https from 'https'
|
||||||
import { AxiosRequestConfig } from 'axios'
|
import { AxiosRequestConfig } from 'axios'
|
||||||
import axiosCookieJarSupport from 'axios-cookiejar-support'
|
import axiosCookieJarSupport from 'axios-cookiejar-support'
|
||||||
import * as tough from 'tough-cookie'
|
import * as tough from 'tough-cookie'
|
||||||
@@ -9,8 +10,8 @@ import { RequestClient, throwIfError } from './RequestClient'
|
|||||||
* Handles redirects and cookie management.
|
* Handles redirects and cookie management.
|
||||||
*/
|
*/
|
||||||
export class Sas9RequestClient extends RequestClient {
|
export class Sas9RequestClient extends RequestClient {
|
||||||
constructor(baseUrl: string, allowInsecure = false) {
|
constructor(baseUrl: string, httpsAgentOptions?: https.AgentOptions) {
|
||||||
super(baseUrl, allowInsecure)
|
super(baseUrl, httpsAgentOptions)
|
||||||
this.httpClient.defaults.maxRedirects = 0
|
this.httpClient.defaults.maxRedirects = 0
|
||||||
this.httpClient.defaults.validateStatus = (status) =>
|
this.httpClient.defaults.validateStatus = (status) =>
|
||||||
status >= 200 && status < 303
|
status >= 200 && status < 303
|
||||||
|
|||||||
167
src/test/RequestClient.spec.ts
Normal file
167
src/test/RequestClient.spec.ts
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import * as pem from 'pem'
|
||||||
|
import * as http from 'http'
|
||||||
|
import * as https from 'https'
|
||||||
|
import { app, mockedAuthResponse } from './SAS_server_app'
|
||||||
|
import { ServerType } from '@sasjs/utils'
|
||||||
|
import SASjs from '../SASjs'
|
||||||
|
import * as axiosModules from '../utils/createAxiosInstance'
|
||||||
|
|
||||||
|
const axiosActual = jest.requireActual('axios')
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(axiosModules, 'createAxiosInstance')
|
||||||
|
.mockImplementation((baseURL: string, httpsAgent?: https.Agent) =>
|
||||||
|
axiosActual.create({ baseURL, httpsAgent })
|
||||||
|
)
|
||||||
|
|
||||||
|
const PORT = 8000
|
||||||
|
const SERVER_URL = `https://localhost:${PORT}/`
|
||||||
|
|
||||||
|
const ERROR_MESSAGES = {
|
||||||
|
selfSigned: 'self signed certificate',
|
||||||
|
CCA: 'unable to verify the first certificate'
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('RequestClient', () => {
|
||||||
|
let server: http.Server
|
||||||
|
|
||||||
|
const adapter = new SASjs({
|
||||||
|
serverUrl: `http://localhost:${PORT}/`,
|
||||||
|
serverType: ServerType.SasViya
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await new Promise((resolve: any, reject: any) => {
|
||||||
|
server = app
|
||||||
|
.listen(PORT, () => resolve())
|
||||||
|
.on('error', (err: any) => reject(err))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
server.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should response the POST method', async () => {
|
||||||
|
const authResponse = await adapter.getAccessToken(
|
||||||
|
'clientId',
|
||||||
|
'clientSecret',
|
||||||
|
'authCode'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(authResponse.access_token).toBe(mockedAuthResponse.access_token)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should response the POST method with Unauthorized', async () => {
|
||||||
|
await expect(
|
||||||
|
adapter.getAccessToken('clientId', 'clientSecret', 'incorrect')
|
||||||
|
).rejects.toThrow(
|
||||||
|
'Error while getting access token. Request failed with status code 401'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('RequestClient - Self Signed Server', () => {
|
||||||
|
let adapter: SASjs
|
||||||
|
|
||||||
|
let httpsServer: https.Server
|
||||||
|
let sslConfig: pem.CertificateCreationResult
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
;({ httpsServer, keys: sslConfig } = await setupSelfSignedServer())
|
||||||
|
await new Promise((resolve: any, reject: any) => {
|
||||||
|
httpsServer
|
||||||
|
.listen(PORT, () => resolve())
|
||||||
|
.on('error', (err: any) => reject(err))
|
||||||
|
})
|
||||||
|
|
||||||
|
adapter = new SASjs({
|
||||||
|
serverUrl: SERVER_URL,
|
||||||
|
serverType: ServerType.SasViya,
|
||||||
|
httpsAgentOptions: { ca: [sslConfig.certificate] }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
httpsServer.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error for not providing certificate', async () => {
|
||||||
|
const adapterWithoutCertificate = new SASjs({
|
||||||
|
serverUrl: SERVER_URL,
|
||||||
|
serverType: ServerType.SasViya
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
adapterWithoutCertificate.getAccessToken(
|
||||||
|
'clientId',
|
||||||
|
'clientSecret',
|
||||||
|
'authCode'
|
||||||
|
)
|
||||||
|
).rejects.toThrow(
|
||||||
|
`Error while getting access token. ${ERROR_MESSAGES.selfSigned}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should response the POST method using insecure flag', async () => {
|
||||||
|
const adapterAllowInsecure = new SASjs({
|
||||||
|
serverUrl: SERVER_URL,
|
||||||
|
serverType: ServerType.SasViya,
|
||||||
|
httpsAgentOptions: { rejectUnauthorized: false }
|
||||||
|
})
|
||||||
|
|
||||||
|
const authResponse = await adapterAllowInsecure.getAccessToken(
|
||||||
|
'clientId',
|
||||||
|
'clientSecret',
|
||||||
|
'authCode'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(authResponse.access_token).toBe(mockedAuthResponse.access_token)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should response the POST method', async () => {
|
||||||
|
const authResponse = await adapter.getAccessToken(
|
||||||
|
'clientId',
|
||||||
|
'clientSecret',
|
||||||
|
'authCode'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(authResponse.access_token).toBe(mockedAuthResponse.access_token)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should response the POST method with Unauthorized', async () => {
|
||||||
|
await expect(
|
||||||
|
adapter.getAccessToken('clientId', 'clientSecret', 'incorrect')
|
||||||
|
).rejects.toThrow(
|
||||||
|
'Error while getting access token. Request failed with status code 401'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const setupSelfSignedServer = async (): Promise<{
|
||||||
|
httpsServer: https.Server
|
||||||
|
keys: pem.CertificateCreationResult
|
||||||
|
}> => {
|
||||||
|
return await new Promise(async (resolve) => {
|
||||||
|
const keys = await createCertificate()
|
||||||
|
|
||||||
|
const httpsServer = https.createServer(
|
||||||
|
{ key: keys.clientKey, cert: keys.certificate },
|
||||||
|
app
|
||||||
|
)
|
||||||
|
|
||||||
|
resolve({ httpsServer, keys })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCertificate = async (): Promise<pem.CertificateCreationResult> => {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
pem.createCertificate(
|
||||||
|
{ days: 1, selfSigned: true },
|
||||||
|
(error: any, keys: pem.CertificateCreationResult) => {
|
||||||
|
if (error) reject(false)
|
||||||
|
resolve(keys)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
38
src/test/SAS_server_app.ts
Normal file
38
src/test/SAS_server_app.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import express = require('express')
|
||||||
|
|
||||||
|
export const app = express()
|
||||||
|
|
||||||
|
export const mockedAuthResponse = {
|
||||||
|
access_token: 'access_token',
|
||||||
|
token_type: 'bearer',
|
||||||
|
id_token: 'id_token',
|
||||||
|
refresh_token: 'refresh_token',
|
||||||
|
expires_in: 43199,
|
||||||
|
scope: 'openid',
|
||||||
|
jti: 'jti'
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/', function (req: any, res: any) {
|
||||||
|
res.send('Hello World')
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post('/SASLogon/oauth/token', function (req: any, res: any) {
|
||||||
|
let valid = true
|
||||||
|
// capture the encoded form data
|
||||||
|
req.on('data', (data: any) => {
|
||||||
|
const resData = data.toString()
|
||||||
|
|
||||||
|
if (resData.includes('incorrect')) valid = false
|
||||||
|
})
|
||||||
|
|
||||||
|
// send a response when finished reading
|
||||||
|
// the encoded form data
|
||||||
|
req.on('end', () => {
|
||||||
|
if (valid) res.status(200).send(mockedAuthResponse)
|
||||||
|
else
|
||||||
|
res.status(401).send({
|
||||||
|
error: 'unauthorized',
|
||||||
|
error_description: 'Bad credentials'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { SessionManager } from '../SessionManager'
|
import { SessionManager } from '../SessionManager'
|
||||||
import { RequestClient } from '../request/RequestClient'
|
import { RequestClient } from '../request/RequestClient'
|
||||||
import { NoSessionStateError } from '../types/errors'
|
|
||||||
import * as dotenv from 'dotenv'
|
import * as dotenv from 'dotenv'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { Logger, LogLevel } from '@sasjs/utils'
|
import { Logger, LogLevel } from '@sasjs/utils'
|
||||||
|
|||||||
4
src/types/ExecuteScript.ts
Normal file
4
src/types/ExecuteScript.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface ExecutionQuery {
|
||||||
|
_program: string
|
||||||
|
_debug?: number
|
||||||
|
}
|
||||||
47
src/types/FileTree.ts
Normal file
47
src/types/FileTree.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
export interface FileTree {
|
||||||
|
members: [FolderMember, ServiceMember]
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MemberType {
|
||||||
|
folder = 'folder',
|
||||||
|
service = 'service'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FolderMember {
|
||||||
|
name: string
|
||||||
|
type: MemberType.folder
|
||||||
|
members: [FolderMember, ServiceMember]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceMember {
|
||||||
|
name: string
|
||||||
|
type: MemberType.service
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isFileTree = (arg: any): arg is FileTree =>
|
||||||
|
arg &&
|
||||||
|
arg.members &&
|
||||||
|
Array.isArray(arg.members) &&
|
||||||
|
arg.members.filter(
|
||||||
|
(member: FolderMember | ServiceMember) =>
|
||||||
|
!isFolderMember(member) && !isServiceMember(member)
|
||||||
|
).length === 0
|
||||||
|
|
||||||
|
const isFolderMember = (arg: any): arg is FolderMember =>
|
||||||
|
arg &&
|
||||||
|
typeof arg.name === 'string' &&
|
||||||
|
arg.type === MemberType.folder &&
|
||||||
|
arg.members &&
|
||||||
|
Array.isArray(arg.members) &&
|
||||||
|
arg.members.filter(
|
||||||
|
(member: FolderMember | ServiceMember) =>
|
||||||
|
!isFolderMember(member) && !isServiceMember(member)
|
||||||
|
).length === 0
|
||||||
|
|
||||||
|
const isServiceMember = (arg: any): arg is ServiceMember =>
|
||||||
|
arg &&
|
||||||
|
typeof arg.name === 'string' &&
|
||||||
|
arg.type === MemberType.service &&
|
||||||
|
arg.code &&
|
||||||
|
typeof arg.code === 'string'
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import * as https from 'https'
|
||||||
import { ServerType } from '@sasjs/utils/types'
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,11 +55,11 @@ export class SASjsConfig {
|
|||||||
*/
|
*/
|
||||||
useComputeApi: boolean | null = null
|
useComputeApi: boolean | null = null
|
||||||
/**
|
/**
|
||||||
* Defaults to `false`.
|
* Optional settings to configure HTTPS Agent.
|
||||||
* When set to `true`, the adapter will allow requests to SAS servers that use a self-signed SSL certificate.
|
* By providing `key`, `cert`, `ca` to connect with server
|
||||||
* Changing this setting is not recommended.
|
* Other options can be set `rejectUnauthorized` and `requestCert`
|
||||||
*/
|
*/
|
||||||
allowInsecureRequests = false
|
httpsAgentOptions?: https.AgentOptions
|
||||||
/**
|
/**
|
||||||
* Supported login mechanisms are - Redirected and Default
|
* Supported login mechanisms are - Redirected and Default
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,3 +12,5 @@ export * from './Session'
|
|||||||
export * from './UploadFile'
|
export * from './UploadFile'
|
||||||
export * from './PollOptions'
|
export * from './PollOptions'
|
||||||
export * from './WriteStream'
|
export * from './WriteStream'
|
||||||
|
export * from './FileTree'
|
||||||
|
export * from './ExecuteScript'
|
||||||
|
|||||||
7
src/utils/createAxiosInstance.ts
Normal file
7
src/utils/createAxiosInstance.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import * as https from 'https'
|
||||||
|
|
||||||
|
export const createAxiosInstance = (
|
||||||
|
baseURL: string,
|
||||||
|
httpsAgent?: https.Agent
|
||||||
|
) => axios.create({ baseURL, httpsAgent })
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export * from './asyncForEach'
|
export * from './asyncForEach'
|
||||||
export * from './compareTimestamps'
|
export * from './compareTimestamps'
|
||||||
export * from './convertToCsv'
|
export * from './convertToCsv'
|
||||||
|
export * from './createAxiosInstance'
|
||||||
export * from './delay'
|
export * from './delay'
|
||||||
export * from './isNode'
|
export * from './isNode'
|
||||||
export * from './isRelativePath'
|
export * from './isRelativePath'
|
||||||
|
|||||||
Reference in New Issue
Block a user