mirror of
https://github.com/sasjs/adapter.git
synced 2026-04-21 05:01:31 +00:00
fix: re-establish session on ERR_NETWORK before retrying
This commit is contained in:
@@ -28,6 +28,9 @@ import {
|
|||||||
import { InvalidSASjsCsrfError } from '../types/errors/InvalidSASjsCsrfError'
|
import { InvalidSASjsCsrfError } from '../types/errors/InvalidSASjsCsrfError'
|
||||||
import { inspect } from 'util'
|
import { inspect } from 'util'
|
||||||
|
|
||||||
|
const getLogger = () =>
|
||||||
|
(typeof process !== 'undefined' && process.logger) || console
|
||||||
|
|
||||||
export class RequestClient implements HttpClient {
|
export class RequestClient implements HttpClient {
|
||||||
private requests: SASjsRequest[] = []
|
private requests: SASjsRequest[] = []
|
||||||
private requestsLimit: number = 10
|
private requestsLimit: number = 10
|
||||||
@@ -79,7 +82,7 @@ export class RequestClient implements HttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public resetInMemoryAuthState() {
|
public resetInMemoryAuthState() {
|
||||||
const logger = process.logger || console
|
const logger = getLogger()
|
||||||
const clearedCookies: string[] = []
|
const clearedCookies: string[] = []
|
||||||
|
|
||||||
this.clearCsrfTokens()
|
this.clearCsrfTokens()
|
||||||
@@ -385,7 +388,7 @@ export class RequestClient implements HttpClient {
|
|||||||
const csrfTokenKey = Object.keys(params).find((k) =>
|
const csrfTokenKey = Object.keys(params).find((k) =>
|
||||||
k?.toLowerCase().includes('csrf')
|
k?.toLowerCase().includes('csrf')
|
||||||
)
|
)
|
||||||
const logger = process.logger || console
|
const logger = getLogger()
|
||||||
|
|
||||||
if (csrfTokenKey) {
|
if (csrfTokenKey) {
|
||||||
this.csrfToken.value = params[csrfTokenKey]
|
this.csrfToken.value = params[csrfTokenKey]
|
||||||
@@ -622,7 +625,7 @@ ${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''}
|
|||||||
|
|
||||||
protected parseAndSetCsrfToken = (response: AxiosResponse) => {
|
protected parseAndSetCsrfToken = (response: AxiosResponse) => {
|
||||||
const token = this.parseCsrfToken(response)
|
const token = this.parseCsrfToken(response)
|
||||||
const logger = process.logger || console
|
const logger = getLogger()
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
this.csrfToken = token
|
this.csrfToken = token
|
||||||
@@ -652,7 +655,7 @@ ${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''}
|
|||||||
}
|
}
|
||||||
|
|
||||||
private logHandleError(step: string, details?: Record<string, any>) {
|
private logHandleError(step: string, details?: Record<string, any>) {
|
||||||
const logger = process.logger || console
|
const logger = getLogger()
|
||||||
logger.warn(`[handleError] ${step}`, details || '')
|
logger.warn(`[handleError] ${step}`, details || '')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -799,16 +802,43 @@ ${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''}
|
|||||||
!this.isRecoveringFromNetworkError
|
!this.isRecoveringFromNetworkError
|
||||||
) {
|
) {
|
||||||
// Opaque ERR_NETWORK usually means the server rejected stale credentials.
|
// Opaque ERR_NETWORK usually means the server rejected stale credentials.
|
||||||
// Wipe in-memory auth state so the retry either succeeds
|
// Wipe in-memory auth state, re-establish session via GET /,
|
||||||
// or surfaces a clean LoginRequiredError.
|
// then retry the original request.
|
||||||
this.logHandleError('ERR_NETWORK — clearing all auth state and retrying')
|
this.logHandleError('ERR_NETWORK — clearing all auth state')
|
||||||
this.resetInMemoryAuthState()
|
this.resetInMemoryAuthState()
|
||||||
this.isRecoveringFromNetworkError = true
|
this.isRecoveringFromNetworkError = true
|
||||||
try {
|
try {
|
||||||
|
// Re-establish session and CSRF cookie
|
||||||
|
this.logHandleError('ERR_NETWORK — re-establishing session via GET /')
|
||||||
|
const rootResponse = await this.httpClient
|
||||||
|
.get('/', { withXSRFToken: true })
|
||||||
|
.catch((err) => {
|
||||||
|
this.logHandleError('ERR_NETWORK — GET / failed', {
|
||||||
|
code: err?.code,
|
||||||
|
status: err?.response?.status,
|
||||||
|
message: err?.message
|
||||||
|
})
|
||||||
|
return err.response
|
||||||
|
})
|
||||||
|
|
||||||
|
if (rootResponse?.data) {
|
||||||
|
const cookie =
|
||||||
|
/<script>document.cookie = '(XSRF-TOKEN=.*; Max-Age=86400; SameSite=Strict; Path=\/;)'<\/script>/.exec(
|
||||||
|
rootResponse.data
|
||||||
|
)?.[1]
|
||||||
|
|
||||||
|
if (cookie && typeof document !== 'undefined') {
|
||||||
|
document.cookie = cookie
|
||||||
|
this.logHandleError('ERR_NETWORK — XSRF-TOKEN cookie restored')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.parseAndSetCsrfToken(rootResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logHandleError('ERR_NETWORK — retrying original request')
|
||||||
return await callback()
|
return await callback()
|
||||||
} catch (retryErr: any) {
|
} catch (retryErr: any) {
|
||||||
// Retry also failed — session is dead, surface LoginRequiredError
|
// Session could not be recovered — surface LoginRequiredError
|
||||||
// so the app can prompt re-authentication.
|
|
||||||
this.logHandleError(
|
this.logHandleError(
|
||||||
'ERR_NETWORK — retry failed, throwing LoginRequiredError',
|
'ERR_NETWORK — retry failed, throwing LoginRequiredError',
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user