Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/node-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
CYPRESS_INSTALL_BINARY: 0
run: |
npm ci
npx playwright install chromium
npm run build --if-present

- name: Test
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pids
lib-cov

# Coverage directory used by tools like istanbul
.vitest*
__screenshots__/
coverage

# nyc test coverage
Expand Down
8 changes: 1 addition & 7 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@ SPDX-PackageSupplier = "Nextcloud <info@nextcloud.com>"
SPDX-PackageDownloadLocation = "https://github.com/nextcloud-libraries/nextcloud-axios"

[[annotations]]
path = ["package-lock.json", "package.json", "tsconfig.json"]
path = ["package-lock.json", "package.json", "tsconfig.json", "tests/tsconfig.json"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2018-2024 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "GPL-3.0-or-later"

[[annotations]]
path = ".eslintrc.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "2023 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "GPL-3.0-or-later"
31 changes: 18 additions & 13 deletions lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,23 @@ export interface CancelableAxiosInstance extends AxiosInstance {
isCancel: typeof Axios.isCancel
}

const client = Axios.create({
headers: {
requesttoken: getRequestToken() ?? '',
'X-Requested-With': 'XMLHttpRequest',
},
})
/**
* Get an Axios instance with default Nextcloud headers and CSRF token handling.
*/
export function getCancelableClient(): CancelableAxiosInstance {
const client = Axios.create({
headers: {
requesttoken: getRequestToken() ?? '',
'X-Requested-With': 'XMLHttpRequest',
},
})

onRequestTokenUpdate((token: string) => {
client.defaults.headers.requesttoken = token
})
onRequestTokenUpdate((token: string) => {
client.defaults.headers.requesttoken = token
})

export const cancelableClient: CancelableAxiosInstance = Object.assign(client, {
CancelToken: Axios.CancelToken,
isCancel: Axios.isCancel,
})
return Object.assign(client, {
CancelToken: Axios.CancelToken,
isCancel: Axios.isCancel,
})
}
3 changes: 2 additions & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/

import { cancelableClient } from './client.ts'
import { getCancelableClient } from './client.ts'
import { onCsrfTokenError } from './interceptors/csrf-token.ts'
import { onMaintenanceModeError } from './interceptors/maintenance-mode.ts'
import { onNotLoggedInError } from './interceptors/not-logged-in.ts'

const cancelableClient = getCancelableClient()
cancelableClient.interceptors.response.use((r) => r, onCsrfTokenError(cancelableClient))
cancelableClient.interceptors.response.use((r) => r, onMaintenanceModeError(cancelableClient))
cancelableClient.interceptors.response.use((r) => r, onNotLoggedInError)
Expand Down
9 changes: 4 additions & 5 deletions lib/interceptors/csrf-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { InterceptorErrorHandler } from './index.ts'
import { generateUrl } from '@nextcloud/router'
import { isAxiosError } from 'axios'

const RETRY_KEY = Symbol('csrf-retry')
const RETRY_KEY = '_nextcloudCsrfTokenReloaded'

/**
* Handle CSRF token errors in Axios requests.
Expand All @@ -26,22 +26,21 @@ export function onCsrfTokenError(axios: CancelableAxiosInstance): InterceptorErr
const responseURL = request?.responseURL

if (config
&& !config[RETRY_KEY]
&& !(RETRY_KEY in config)
&& response?.status === 412
&& response?.data?.message === 'CSRF check failed') {
console.warn(`Request to ${responseURL} failed because of a CSRF mismatch. Fetching a new token`)
console.warn(`Request to ${responseURL} failed because of a CSRF mismatch. Fetching a new token.`)

const { data: { token } } = await axios.get(generateUrl('/csrftoken'))
console.debug(`New request token ${token} fetched`)
axios.defaults.headers.requesttoken = token

return axios({
...config,
[RETRY_KEY]: true,
headers: {
...config.headers,
requesttoken: token,
},
[RETRY_KEY]: true,
})
}

Expand Down
6 changes: 2 additions & 4 deletions lib/interceptors/maintenance-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { InterceptorErrorHandler } from './index.ts'

import { isAxiosError } from 'axios'

export const RETRY_DELAY_KEY = Symbol('retryDelay')
const RETRY_DELAY_KEY = '_nextcloudMaintenanceModeRetryDelay'

/**
* Handles Nextcloud maintenance mode errors in Axios requests.
Expand All @@ -25,9 +25,7 @@ export function onMaintenanceModeError(axios: CancelableAxiosInstance): Intercep
const responseURL = request?.responseURL
const status = response?.status
const headers = response?.headers
let retryDelay = typeof config?.[RETRY_DELAY_KEY] === 'number'
? config?.[RETRY_DELAY_KEY]
: 1
let retryDelay = config?.[RETRY_DELAY_KEY] ?? 1

/**
* Retry requests if they failed due to maintenance mode
Expand Down
9 changes: 6 additions & 3 deletions lib/interceptors/not-logged-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ export async function onNotLoggedInError(error: unknown) {
if (status === 401
&& response?.data?.message === 'Current user is not logged in'
&& config?.reloadExpiredSession
&& window?.location) {
&& globalThis.location?.reload) {
console.error(`Request to ${responseURL} failed because the user session expired. Reloading the page …`)

window.location.reload()
if (globalThis.OC?.reload) {
globalThis.OC.reload()
} else {
globalThis.location.reload()
}
}
}

Expand Down
10 changes: 9 additions & 1 deletion lib/axios.d.ts → lib/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,16 @@
declare module 'axios' {
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any -- needed as we extend the interface only.
interface AxiosRequestConfig<D = any> {
[key: symbol]: unknown
_nextcloudCsrfTokenReloaded?: true
_nextcloudMaintenanceModeRetryDelay?: number
}
}

declare global {
var OC: {
/** NC 32 and before */
reload?: () => void
} | undefined
}

export {}
Loading
Loading