diff --git a/packages/nc-gui/composables/useApi/interceptors.ts b/packages/nc-gui/composables/useApi/interceptors.ts index 0fde440f8a..bdb365f640 100644 --- a/packages/nc-gui/composables/useApi/interceptors.ts +++ b/packages/nc-gui/composables/useApi/interceptors.ts @@ -1,6 +1,8 @@ import type { Api } from 'nocodb-sdk' const DbNotFoundMsg = 'Database config not found' +const TIMEOUT_RETRY_COUNT = 1 + export function addAxiosInterceptors(api: Api) { const state = useGlobal() const router = useRouter() @@ -61,37 +63,44 @@ export function addAxiosInterceptors(api: Api) { return Promise.reject(error) } - try { - const token = await state.refreshToken({ - axiosInstance, - skipLogout: true, - }) - - if (!token) { - await state.signOut({ - redirectToSignin: !isSharedPage, - skipApiCall: true, + let retry = 0 + do { + try { + const token = await state.refreshToken({ + axiosInstance, + skipLogout: true, }) - return Promise.reject(error) - } - - const config = error.config - config.headers['xc-auth'] = token - const response = await axiosInstance.request(config) - return response - } catch (refreshTokenError) { - if ((refreshTokenError as any)?.code === 'ERR_CANCELED') { - return Promise.reject(refreshTokenError) + if (!token) { + await state.signOut({ + redirectToSignin: !isSharedPage, + skipApiCall: true, + }) + return Promise.reject(error) + } + + const config = error.config + config.headers['xc-auth'] = token + + const response = await axiosInstance.request(config) + return response + } catch (refreshTokenError) { + if ((refreshTokenError as any)?.code === 'ERR_CANCELED') { + return Promise.reject(refreshTokenError) + } + + // if shared execution error, don't sign out + if (!(refreshTokenError instanceof SharedExecutionError)) { + await state.signOut({ + redirectToSignin: !isSharedPage, + skipApiCall: true, + }) + return Promise.reject(error) + } + + if (retry >= TIMEOUT_RETRY_COUNT) return Promise.reject(error) } - - await state.signOut({ - redirectToSignin: !isSharedPage, - skipApiCall: true, - }) - - return Promise.reject(error) - } + } while (retry++ < TIMEOUT_RETRY_COUNT) }, ) diff --git a/packages/nc-gui/composables/useSharedExecutionFn.ts b/packages/nc-gui/composables/useSharedExecutionFn.ts index 583bbfce2e..1b9dc6708b 100644 --- a/packages/nc-gui/composables/useSharedExecutionFn.ts +++ b/packages/nc-gui/composables/useSharedExecutionFn.ts @@ -1,5 +1,11 @@ import { useStorage, useTimeoutFn } from '@vueuse/core' +export class SharedExecutionError extends Error { + constructor(message?: string) { + super(message) + } +} + interface SharedExecutionOptions { timeout?: number // Maximum time a lock can be held before it's considered stale - default 5000ms storageDelay?: number // Delay before reading from storage to allow for changes to propagate - default 50ms @@ -102,7 +108,7 @@ export function useSharedExecutionFn(key: string, fn: () => Promise | T, o () => { timedOut = true localStorage.removeItem(storageLockKey) - reject(new Error(`Timeout waiting for result on key ${key}`)) + reject(new SharedExecutionError(`Timeout waiting for result on key ${key}`)) }, currentLock?.timestamp ? timeout - (Date.now() - currentLock.timestamp) : timeout, )