From a110ec7897941bdc975eb13618d6f3f1954c4fd6 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Fri, 22 Nov 2024 12:19:53 +0000 Subject: [PATCH 1/2] fix: if shared execution error then don't signout --- packages/nc-gui/composables/useApi/interceptors.ts | 11 +++++++---- packages/nc-gui/composables/useSharedExecutionFn.ts | 8 +++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/nc-gui/composables/useApi/interceptors.ts b/packages/nc-gui/composables/useApi/interceptors.ts index 0fde440f8a..4f546984af 100644 --- a/packages/nc-gui/composables/useApi/interceptors.ts +++ b/packages/nc-gui/composables/useApi/interceptors.ts @@ -85,10 +85,13 @@ export function addAxiosInterceptors(api: Api) { return Promise.reject(refreshTokenError) } - await state.signOut({ - redirectToSignin: !isSharedPage, - skipApiCall: true, - }) + // if shared execution error, don't sign out + if (!(refreshTokenError instanceof SharedExecutionError)) { + await state.signOut({ + redirectToSignin: !isSharedPage, + skipApiCall: true, + }) + } return Promise.reject(error) } 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, ) From cbf80b0d32f8f37ebc3818f7c88a7458e30146f7 Mon Sep 17 00:00:00 2001 From: Pranav C Date: Fri, 22 Nov 2024 12:19:53 +0000 Subject: [PATCH 2/2] fix: add retry mechanism on failure --- .../nc-gui/composables/useApi/interceptors.ts | 68 ++++++++++--------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/packages/nc-gui/composables/useApi/interceptors.ts b/packages/nc-gui/composables/useApi/interceptors.ts index 4f546984af..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,40 +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) } - - // if shared execution error, don't sign out - if (!(refreshTokenError instanceof SharedExecutionError)) { - await state.signOut({ - redirectToSignin: !isSharedPage, - skipApiCall: true, - }) - } - - return Promise.reject(error) - } + } while (retry++ < TIMEOUT_RETRY_COUNT) }, )