mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-05 11:40:06 +00:00
Compare commits
24 Commits
v1.17.1
...
session-ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
232f4ec3fb | ||
|
|
e1f17ef47d | ||
|
|
8a40071c35 | ||
|
|
430957eb3d | ||
|
|
25874be679 | ||
|
|
ed8440434f | ||
|
|
0f9884c1b6 | ||
|
|
d126a05347 | ||
|
|
3e26bbbbba | ||
|
|
982cc8f7a0 | ||
|
|
d1770698e0 | ||
|
|
b78e8617c4 | ||
|
|
3ce9ca0986 | ||
|
|
04d17c3680 | ||
|
|
d26e15f91c | ||
|
|
83c46091b3 | ||
|
|
d640d7c040 | ||
|
|
c934eb2b08 | ||
|
|
24dd5e32ad | ||
|
|
a23103b2c3 | ||
|
|
35aa4235e4 | ||
|
|
e9be1cf99a | ||
|
|
c7b0821081 | ||
|
|
4a4618dd32 |
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -27,6 +27,16 @@ jobs:
|
|||||||
run: npm run lint
|
run: npm run lint
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: npm test
|
run: npm test
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
CLIENT: ${{secrets.CLIENT}}
|
||||||
|
SECRET: ${{secrets.SECRET}}
|
||||||
|
SAS_USERNAME: ${{secrets.SAS_USERNAME}}
|
||||||
|
SAS_PASSWORD: ${{secrets.SAS_PASSWORD}}
|
||||||
|
SERVER_URL: ${{secrets.SERVER_URL}}
|
||||||
|
SERVER_TYPE: ${{secrets.SERVER_TYPE}}
|
||||||
|
ACCESS_TOKEN: ${{secrets.ACCESS_TOKEN}}
|
||||||
|
REFRESH_TOKEN: ${{secrets.REFRESH_TOKEN}}
|
||||||
- name: Build Package
|
- name: Build Package
|
||||||
run: npm run package:lib
|
run: npm run package:lib
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ What code changes have been made to achieve the intent.
|
|||||||
|
|
||||||
- [ ] 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-tests` unit tests are passing (`npm test`).
|
||||||
- [ ] All `sasjs-tests` are passing (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)).
|
- [ ] All `sasjs-tests` are passing (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)).
|
||||||
|
|||||||
2585
package-lock.json
generated
2585
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -37,15 +37,16 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/isomorphic-fetch": "0.0.35",
|
"@types/isomorphic-fetch": "0.0.35",
|
||||||
"@types/jest": "^26.0.14",
|
"@types/jest": "^26.0.15",
|
||||||
"cp": "^0.2.0",
|
"cp": "^0.2.0",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
"jest": "^25.5.4",
|
"jest": "^25.5.4",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semantic-release": "^17.1.2",
|
"semantic-release": "^17.2.3",
|
||||||
"terser-webpack-plugin": "^4.2.2",
|
"terser-webpack-plugin": "^4.2.3",
|
||||||
"ts-jest": "^25.5.1",
|
"ts-jest": "^25.5.1",
|
||||||
"ts-loader": "^8.0.4",
|
"ts-loader": "^8.0.11",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.3",
|
||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"typedoc": "^0.17.8",
|
"typedoc": "^0.17.8",
|
||||||
@@ -53,7 +54,7 @@
|
|||||||
"typedoc-plugin-external-module-name": "^4.0.3",
|
"typedoc-plugin-external-module-name": "^4.0.3",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^3.9.7",
|
||||||
"webpack": "^4.44.2",
|
"webpack": "^4.44.2",
|
||||||
"webpack-cli": "^3.3.12"
|
"webpack-cli": "^4.2.0"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -37,6 +37,17 @@ export const basicTests = (
|
|||||||
assertion: (response: any) =>
|
assertion: (response: any) =>
|
||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Multiple Log in attempts",
|
||||||
|
description: "Should fail on first attempt and should log the user in on second attempt",
|
||||||
|
test: async () => {
|
||||||
|
await adapter.logOut()
|
||||||
|
await adapter.logIn('invalid', 'invalid')
|
||||||
|
return adapter.logIn(userName, password)
|
||||||
|
},
|
||||||
|
assertion: (response: any) =>
|
||||||
|
response && response.isLoggedIn && response.userName === userName
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Default config",
|
title: "Default config",
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -164,9 +164,9 @@ export class SASViyaApiClient {
|
|||||||
for (const promise of promises) results.push(await promise())
|
for (const promise of promises) results.push(await promise())
|
||||||
|
|
||||||
results.forEach((result: any, index: number) => {
|
results.forEach((result: any, index: number) => {
|
||||||
if (result && result.body && result.body.details) {
|
if (result && result.error && result.error.details) {
|
||||||
try {
|
try {
|
||||||
const resultParsed = JSON.parse(result.body.details)
|
const resultParsed = result.error.details
|
||||||
|
|
||||||
if (resultParsed && resultParsed.body) {
|
if (resultParsed && resultParsed.body) {
|
||||||
let sysUserId = ''
|
let sysUserId = ''
|
||||||
@@ -607,7 +607,7 @@ export class SASViyaApiClient {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new ErrorResponse('Job execution failed', {
|
new ErrorResponse('Job execution failed.', {
|
||||||
status: 500,
|
status: 500,
|
||||||
body: log
|
body: log
|
||||||
})
|
})
|
||||||
@@ -1114,7 +1114,7 @@ export class SASViyaApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!jobToExecute) {
|
if (!jobToExecute) {
|
||||||
throw new Error(`The job ${sasJob} was not found.`)
|
throw new Error(`Job was not found.`)
|
||||||
}
|
}
|
||||||
const jobDefinitionLink = jobToExecute?.links.find(
|
const jobDefinitionLink = jobToExecute?.links.find(
|
||||||
(l) => l.rel === 'getResource'
|
(l) => l.rel === 'getResource'
|
||||||
|
|||||||
118
src/SASjs.ts
118
src/SASjs.ts
@@ -411,6 +411,29 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getLoginForm(response: any) {
|
||||||
|
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
|
||||||
|
const matches = pattern.exec(response)
|
||||||
|
const formInputs: any = {}
|
||||||
|
|
||||||
|
if (matches && matches.length) {
|
||||||
|
this.setLoginUrl(matches)
|
||||||
|
const inputs = response.match(/<input.*"hidden"[^>]*>/g)
|
||||||
|
|
||||||
|
if (inputs) {
|
||||||
|
inputs.forEach((inputStr: string) => {
|
||||||
|
const valueMatch = inputStr.match(/name="([^"]*)"\svalue="([^"]*)/)
|
||||||
|
|
||||||
|
if (valueMatch && valueMatch.length) {
|
||||||
|
formInputs[valueMatch[1]] = valueMatch[2]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(formInputs).length ? formInputs : null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a session is active, or login is required.
|
* 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`.
|
* @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
|
||||||
@@ -419,10 +442,16 @@ export default class SASjs {
|
|||||||
const loginResponse = await fetch(this.loginUrl.replace('.do', ''))
|
const loginResponse = await fetch(this.loginUrl.replace('.do', ''))
|
||||||
const responseText = await loginResponse.text()
|
const responseText = await loginResponse.text()
|
||||||
const isLoggedIn = /<button.+onClick.+logout/gm.test(responseText)
|
const isLoggedIn = /<button.+onClick.+logout/gm.test(responseText)
|
||||||
|
let loginForm: any = null
|
||||||
|
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
loginForm = await this.getLoginForm(responseText)
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
userName: this.userName
|
userName: this.userName,
|
||||||
|
loginForm
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,7 +469,7 @@ export default class SASjs {
|
|||||||
|
|
||||||
this.userName = loginParams.username
|
this.userName = loginParams.username
|
||||||
|
|
||||||
const { isLoggedIn } = await this.checkSession()
|
const { isLoggedIn, loginForm } = await this.checkSession()
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
this.resendWaitingRequests()
|
this.resendWaitingRequests()
|
||||||
|
|
||||||
@@ -450,15 +479,13 @@ export default class SASjs {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginForm = await this.getLoginForm()
|
|
||||||
|
|
||||||
for (const key in loginForm) {
|
for (const key in loginForm) {
|
||||||
loginParams[key] = loginForm[key]
|
loginParams[key] = loginForm[key]
|
||||||
}
|
}
|
||||||
const loginParamsStr = serialize(loginParams)
|
const loginParamsStr = serialize(loginParams)
|
||||||
|
|
||||||
return fetch(this.loginUrl, {
|
return fetch(this.loginUrl, {
|
||||||
method: 'post',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
referrerPolicy: 'same-origin',
|
referrerPolicy: 'same-origin',
|
||||||
body: loginParamsStr,
|
body: loginParamsStr,
|
||||||
@@ -780,11 +807,23 @@ export default class SASjs {
|
|||||||
} else {
|
} else {
|
||||||
this.retryCountComputeApi = 0
|
this.retryCountComputeApi = 0
|
||||||
reject(
|
reject(
|
||||||
new ErrorResponse('Compute API retry requests limit reached')
|
new ErrorResponse('Compute API retry requests limit reached.')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response?.log) {
|
||||||
|
this.appendSasjsRequest(response.log, sasJob, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.toString().includes('Job was not found')) {
|
||||||
|
reject(
|
||||||
|
new ErrorResponse('Service not found on the server.', {
|
||||||
|
sasJob: sasJob
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (error && error.status === 401) {
|
if (error && error.status === 401) {
|
||||||
if (loginRequiredCallback) loginRequiredCallback(true)
|
if (loginRequiredCallback) loginRequiredCallback(true)
|
||||||
sasjsWaitingRequest.requestPromise.resolve = resolve
|
sasjsWaitingRequest.requestPromise.resolve = resolve
|
||||||
@@ -792,10 +831,8 @@ export default class SASjs {
|
|||||||
sasjsWaitingRequest.config = config
|
sasjsWaitingRequest.config = config
|
||||||
this.sasjsWaitingRequests.push(sasjsWaitingRequest)
|
this.sasjsWaitingRequests.push(sasjsWaitingRequest)
|
||||||
} else {
|
} else {
|
||||||
reject(new ErrorResponse('Job execution failed', error))
|
reject(new ErrorResponse('Job execution failed.', error))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.appendSasjsRequest(response.log, sasJob, null)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -875,12 +912,24 @@ export default class SASjs {
|
|||||||
} else {
|
} else {
|
||||||
this.retryCountJeseApi = 0
|
this.retryCountJeseApi = 0
|
||||||
reject(
|
reject(
|
||||||
new ErrorResponse('Jes API retry requests limit reached')
|
new ErrorResponse('Jes API retry requests limit reached.')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reject(new ErrorResponse('Job execution failed', e))
|
if (e?.log) {
|
||||||
|
this.appendSasjsRequest(e.log, sasJob, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.toString().includes('Job was not found')) {
|
||||||
|
reject(
|
||||||
|
new ErrorResponse('Service not found on the server.', {
|
||||||
|
sasJob: sasJob
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(new ErrorResponse('Job execution failed.', e))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1064,7 +1113,7 @@ export default class SASjs {
|
|||||||
} else {
|
} else {
|
||||||
reject(
|
reject(
|
||||||
new ErrorResponse(
|
new ErrorResponse(
|
||||||
'Job WEB execution failed',
|
'Job WEB execution failed.',
|
||||||
this.parseSAS9ErrorResponse(responseText)
|
this.parseSAS9ErrorResponse(responseText)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1082,7 +1131,7 @@ export default class SASjs {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(
|
reject(
|
||||||
new ErrorResponse(
|
new ErrorResponse(
|
||||||
'Job WEB debug response parsing failed',
|
'Job WEB debug response parsing failed.',
|
||||||
{ response: resText, exception: e }
|
{ response: resText, exception: e }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1091,7 +1140,7 @@ export default class SASjs {
|
|||||||
(err: any) => {
|
(err: any) => {
|
||||||
reject(
|
reject(
|
||||||
new ErrorResponse(
|
new ErrorResponse(
|
||||||
'Job WEB debug response parsing failed',
|
'Job WEB debug response parsing failed.',
|
||||||
err
|
err
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1100,19 +1149,34 @@ export default class SASjs {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(
|
reject(
|
||||||
new ErrorResponse(
|
new ErrorResponse(
|
||||||
'Job WEB debug response parsing failed',
|
'Job WEB debug response parsing failed.',
|
||||||
{ response: responseText, exception: e }
|
{ response: responseText, exception: e }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.updateUsername(responseText)
|
this.updateUsername(responseText)
|
||||||
|
if (
|
||||||
|
responseText.includes(
|
||||||
|
'The requested URL /SASStoredProcess/do/ was not found on this server.'
|
||||||
|
) ||
|
||||||
|
responseText.includes('Stored process not found')
|
||||||
|
) {
|
||||||
|
reject(
|
||||||
|
new ErrorResponse(
|
||||||
|
'Service not found on the server.',
|
||||||
|
{ service: sasJob },
|
||||||
|
responseText
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsedJson = JSON.parse(responseText)
|
const parsedJson = JSON.parse(responseText)
|
||||||
resolve(parsedJson)
|
resolve(parsedJson)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(
|
reject(
|
||||||
new ErrorResponse('Job WEB response parsing failed', {
|
new ErrorResponse('Job WEB response parsing failed.', {
|
||||||
response: responseText,
|
response: responseText,
|
||||||
exception: e
|
exception: e
|
||||||
})
|
})
|
||||||
@@ -1123,7 +1187,7 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e: Error) => {
|
.catch((e: Error) => {
|
||||||
reject(new ErrorResponse('Job WEB request failed', e))
|
reject(new ErrorResponse('Job WEB request failed.', e))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1448,26 +1512,6 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getLoginForm() {
|
|
||||||
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
|
|
||||||
const response = await fetch(this.loginUrl).then((r) => r.text())
|
|
||||||
const matches = pattern.exec(response)
|
|
||||||
const formInputs: any = {}
|
|
||||||
if (matches && matches.length) {
|
|
||||||
this.setLoginUrl(matches)
|
|
||||||
const inputs = response.match(/<input.*"hidden"[^>]*>/g)
|
|
||||||
if (inputs) {
|
|
||||||
inputs.forEach((inputStr: string) => {
|
|
||||||
const valueMatch = inputStr.match(/name="([^"]*)"\svalue="([^"]*)/)
|
|
||||||
if (valueMatch && valueMatch.length) {
|
|
||||||
formInputs[valueMatch[1]] = valueMatch[2]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Object.keys(formInputs).length ? formInputs : null
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createFoldersAndServices(
|
private async createFoldersAndServices(
|
||||||
parentFolder: string,
|
parentFolder: string,
|
||||||
membersJson: any[],
|
membersJson: any[],
|
||||||
|
|||||||
38
src/test/SessionManager.spec.ts
Normal file
38
src/test/SessionManager.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import dotenv from 'dotenv'
|
||||||
|
import { SessionManager } from '../SessionManager'
|
||||||
|
import { CsrfToken } from '../types'
|
||||||
|
|
||||||
|
describe('SessionManager', () => {
|
||||||
|
const setCsrfToken = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((csrfToken: CsrfToken) => console.log(csrfToken))
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
dotenv.config()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should instantiate', () => {
|
||||||
|
const sessionManager = new SessionManager(
|
||||||
|
'http://test-server.com',
|
||||||
|
'test context',
|
||||||
|
setCsrfToken
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(sessionManager).toBeInstanceOf(SessionManager)
|
||||||
|
expect(sessionManager.debug).toBeFalsy()
|
||||||
|
expect((sessionManager as any).serverUrl).toEqual('http://test-server.com')
|
||||||
|
expect((sessionManager as any).contextName).toEqual('test context')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should set the debug flag', () => {
|
||||||
|
const sessionManager = new SessionManager(
|
||||||
|
'http://test-server.com',
|
||||||
|
'test context',
|
||||||
|
setCsrfToken
|
||||||
|
)
|
||||||
|
|
||||||
|
sessionManager.debug = true
|
||||||
|
|
||||||
|
expect(sessionManager.debug).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
export class ErrorResponse {
|
export class ErrorResponse {
|
||||||
error: ErrorBody
|
error: ErrorBody
|
||||||
|
|
||||||
constructor(message: string, details?: any) {
|
constructor(message: string, details?: any, raw?: any) {
|
||||||
let detailsString = ''
|
let detailsString = details
|
||||||
let raw
|
|
||||||
|
|
||||||
try {
|
if (typeof details !== 'object') {
|
||||||
detailsString = JSON.stringify(details)
|
try {
|
||||||
} catch {
|
detailsString = JSON.parse(details)
|
||||||
raw = details
|
} catch {
|
||||||
|
raw = details
|
||||||
|
detailsString = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.error = {
|
this.error = {
|
||||||
|
|||||||
Reference in New Issue
Block a user