mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-03 10:40:06 +00:00
244 lines
6.2 KiB
TypeScript
244 lines
6.2 KiB
TypeScript
import { Session, Context, CsrfToken, SessionVariable } from './types'
|
|
import { asyncForEach, isUrl } from './utils'
|
|
import { prefixMessage } from '@sasjs/utils/error'
|
|
import { RequestClient } from './request/RequestClient'
|
|
|
|
const MAX_SESSION_COUNT = 1
|
|
const RETRY_LIMIT: number = 3
|
|
let RETRY_COUNT: number = 0
|
|
const INTERNAL_SAS_ERROR = {
|
|
status: 304,
|
|
message: 'Not Modified'
|
|
}
|
|
|
|
export class SessionManager {
|
|
constructor(
|
|
private serverUrl: string,
|
|
private contextName: string,
|
|
private requestClient: RequestClient
|
|
) {
|
|
if (serverUrl) isUrl(serverUrl)
|
|
}
|
|
|
|
private sessions: Session[] = []
|
|
private currentContext: Context | null = null
|
|
private _debug: boolean = false
|
|
private printedSessionState = {
|
|
printed: false,
|
|
state: ''
|
|
}
|
|
|
|
public get debug() {
|
|
return this._debug
|
|
}
|
|
|
|
public set debug(value: boolean) {
|
|
this._debug = value
|
|
}
|
|
|
|
async getSession(accessToken?: string) {
|
|
await this.createSessions(accessToken)
|
|
await this.createAndWaitForSession(accessToken)
|
|
const session = this.sessions.pop()
|
|
const secondsSinceSessionCreation =
|
|
(new Date().getTime() - new Date(session!.creationTimeStamp).getTime()) /
|
|
1000
|
|
|
|
if (
|
|
!session!.attributes ||
|
|
secondsSinceSessionCreation >= session!.attributes.sessionInactiveTimeout
|
|
) {
|
|
await this.createSessions(accessToken)
|
|
const freshSession = this.sessions.pop()
|
|
|
|
return freshSession
|
|
}
|
|
|
|
return session
|
|
}
|
|
|
|
async clearSession(id: string, accessToken?: string) {
|
|
return await this.requestClient
|
|
.delete<Session>(`/compute/sessions/${id}`, accessToken)
|
|
.then(() => {
|
|
this.sessions = this.sessions.filter((s) => s.id !== id)
|
|
})
|
|
.catch((err) => {
|
|
throw prefixMessage(err, 'Error while deleting session. ')
|
|
})
|
|
}
|
|
|
|
private async createSessions(accessToken?: string) {
|
|
if (!this.sessions.length) {
|
|
if (!this.currentContext) {
|
|
await this.setCurrentContext(accessToken).catch((err) => {
|
|
throw err
|
|
})
|
|
}
|
|
|
|
await asyncForEach(new Array(MAX_SESSION_COUNT), async () => {
|
|
const createdSession = await this.createAndWaitForSession(
|
|
accessToken
|
|
).catch((err) => {
|
|
throw err
|
|
})
|
|
|
|
this.sessions.push(createdSession)
|
|
}).catch((err) => {
|
|
throw err
|
|
})
|
|
}
|
|
}
|
|
|
|
private async createAndWaitForSession(accessToken?: string) {
|
|
const { result: createdSession, etag } = await this.requestClient
|
|
.post<Session>(
|
|
`${this.serverUrl}/compute/contexts/${
|
|
this.currentContext!.id
|
|
}/sessions`,
|
|
{},
|
|
accessToken
|
|
)
|
|
.catch((err) => {
|
|
throw err
|
|
})
|
|
|
|
await this.waitForSession(createdSession, etag, accessToken)
|
|
|
|
this.sessions.push(createdSession)
|
|
|
|
return createdSession
|
|
}
|
|
|
|
private async setCurrentContext(accessToken?: string) {
|
|
if (!this.currentContext) {
|
|
const { result: contexts } = await this.requestClient
|
|
.get<{
|
|
items: Context[]
|
|
}>(`${this.serverUrl}/compute/contexts?limit=10000`, accessToken)
|
|
.catch((err) => {
|
|
throw err
|
|
})
|
|
|
|
const contextsList =
|
|
contexts && contexts.items && contexts.items.length
|
|
? contexts.items
|
|
: []
|
|
|
|
const currentContext = contextsList.find(
|
|
(c: any) => c.name === this.contextName
|
|
)
|
|
|
|
if (!currentContext) {
|
|
throw new Error(
|
|
`The context '${this.contextName}' was not found on the server ${this.serverUrl}.`
|
|
)
|
|
}
|
|
|
|
this.currentContext = currentContext
|
|
|
|
Promise.resolve()
|
|
}
|
|
}
|
|
|
|
private getHeaders(accessToken?: string) {
|
|
const headers: any = {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
if (accessToken) {
|
|
headers.Authorization = `Bearer ${accessToken}`
|
|
}
|
|
|
|
return headers
|
|
}
|
|
|
|
private async waitForSession(
|
|
session: Session,
|
|
etag: string | null,
|
|
accessToken?: string
|
|
) {
|
|
let sessionState = session.state
|
|
|
|
const stateLink = session.links.find((l: any) => l.rel === 'state')
|
|
|
|
return new Promise(async (resolve, _) => {
|
|
if (
|
|
sessionState === 'pending' ||
|
|
sessionState === 'running' ||
|
|
sessionState === ''
|
|
) {
|
|
if (stateLink) {
|
|
if (this.debug && !this.printedSessionState.printed) {
|
|
console.log('Polling session status...')
|
|
|
|
this.printedSessionState.printed = true
|
|
}
|
|
|
|
const state = await this.getSessionState(
|
|
`${this.serverUrl}${stateLink.href}?wait=30`,
|
|
etag!,
|
|
accessToken
|
|
).catch((err) => {
|
|
throw err
|
|
})
|
|
|
|
sessionState = state.trim()
|
|
|
|
if (this.debug && this.printedSessionState.state !== sessionState) {
|
|
console.log(`Current session state is '${sessionState}'`)
|
|
|
|
this.printedSessionState.state = sessionState
|
|
this.printedSessionState.printed = false
|
|
}
|
|
|
|
// 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
|
|
if (
|
|
sessionState === INTERNAL_SAS_ERROR.message &&
|
|
RETRY_COUNT < RETRY_LIMIT
|
|
) {
|
|
RETRY_COUNT++
|
|
|
|
resolve(this.waitForSession(session, etag, accessToken))
|
|
}
|
|
|
|
resolve(sessionState)
|
|
}
|
|
} else {
|
|
resolve(sessionState)
|
|
}
|
|
})
|
|
}
|
|
|
|
private async getSessionState(
|
|
url: string,
|
|
etag: string,
|
|
accessToken?: string
|
|
) {
|
|
return await this.requestClient
|
|
.get(url, accessToken, 'text/plain', { 'If-None-Match': etag })
|
|
.then((res) => res.result as string)
|
|
.catch((err) => {
|
|
if (err.status === INTERNAL_SAS_ERROR.status)
|
|
return INTERNAL_SAS_ERROR.message
|
|
|
|
throw err
|
|
})
|
|
}
|
|
|
|
async getVariable(sessionId: string, variable: string, accessToken?: string) {
|
|
return await this.requestClient
|
|
.get<SessionVariable>(
|
|
`${this.serverUrl}/compute/sessions/${sessionId}/variables/${variable}`,
|
|
accessToken
|
|
)
|
|
.catch((err) => {
|
|
throw prefixMessage(
|
|
err,
|
|
`Error while fetching session variable '${variable}'.`
|
|
)
|
|
})
|
|
}
|
|
}
|