Skip to content

Commit c35a05c

Browse files
authored
Merge pull request #916 from nextcloud-libraries/fix/axios
fix: axios does not preserve symbols in its config
2 parents fe19736 + 6a4c5c5 commit c35a05c

21 files changed

Lines changed: 1165 additions & 383 deletions

.github/workflows/node-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ jobs:
5151
CYPRESS_INSTALL_BINARY: 0
5252
run: |
5353
npm ci
54+
npx playwright install chromium
5455
npm run build --if-present
5556
5657
- name: Test

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pids
1717
lib-cov
1818

1919
# Coverage directory used by tools like istanbul
20+
.vitest*
21+
__screenshots__/
2022
coverage
2123

2224
# nyc test coverage

REUSE.toml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@ SPDX-PackageSupplier = "Nextcloud <info@nextcloud.com>"
66
SPDX-PackageDownloadLocation = "https://github.com/nextcloud-libraries/nextcloud-axios"
77

88
[[annotations]]
9-
path = ["package-lock.json", "package.json", "tsconfig.json"]
9+
path = ["package-lock.json", "package.json", "tsconfig.json", "tests/tsconfig.json"]
1010
precedence = "aggregate"
1111
SPDX-FileCopyrightText = "2018-2024 Nextcloud GmbH and Nextcloud contributors"
1212
SPDX-License-Identifier = "GPL-3.0-or-later"
13-
14-
[[annotations]]
15-
path = ".eslintrc.json"
16-
precedence = "aggregate"
17-
SPDX-FileCopyrightText = "2023 Nextcloud GmbH and Nextcloud contributors"
18-
SPDX-License-Identifier = "GPL-3.0-or-later"

lib/client.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,23 @@ export interface CancelableAxiosInstance extends AxiosInstance {
1616
isCancel: typeof Axios.isCancel
1717
}
1818

19-
const client = Axios.create({
20-
headers: {
21-
requesttoken: getRequestToken() ?? '',
22-
'X-Requested-With': 'XMLHttpRequest',
23-
},
24-
})
19+
/**
20+
* Get an Axios instance with default Nextcloud headers and CSRF token handling.
21+
*/
22+
export function getCancelableClient(): CancelableAxiosInstance {
23+
const client = Axios.create({
24+
headers: {
25+
requesttoken: getRequestToken() ?? '',
26+
'X-Requested-With': 'XMLHttpRequest',
27+
},
28+
})
2529

26-
onRequestTokenUpdate((token: string) => {
27-
client.defaults.headers.requesttoken = token
28-
})
30+
onRequestTokenUpdate((token: string) => {
31+
client.defaults.headers.requesttoken = token
32+
})
2933

30-
export const cancelableClient: CancelableAxiosInstance = Object.assign(client, {
31-
CancelToken: Axios.CancelToken,
32-
isCancel: Axios.isCancel,
33-
})
34+
return Object.assign(client, {
35+
CancelToken: Axios.CancelToken,
36+
isCancel: Axios.isCancel,
37+
})
38+
}

lib/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
* SPDX-License-Identifier: GPL-3.0-or-later
44
*/
55

6-
import { cancelableClient } from './client.ts'
6+
import { getCancelableClient } from './client.ts'
77
import { onCsrfTokenError } from './interceptors/csrf-token.ts'
88
import { onMaintenanceModeError } from './interceptors/maintenance-mode.ts'
99
import { onNotLoggedInError } from './interceptors/not-logged-in.ts'
1010

11+
const cancelableClient = getCancelableClient()
1112
cancelableClient.interceptors.response.use((r) => r, onCsrfTokenError(cancelableClient))
1213
cancelableClient.interceptors.response.use((r) => r, onMaintenanceModeError(cancelableClient))
1314
cancelableClient.interceptors.response.use((r) => r, onNotLoggedInError)

lib/interceptors/csrf-token.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { InterceptorErrorHandler } from './index.ts'
99
import { generateUrl } from '@nextcloud/router'
1010
import { isAxiosError } from 'axios'
1111

12-
const RETRY_KEY = Symbol('csrf-retry')
12+
const RETRY_KEY = '_nextcloudCsrfTokenReloaded'
1313

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

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

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

3837
return axios({
3938
...config,
39+
[RETRY_KEY]: true,
4040
headers: {
4141
...config.headers,
4242
requesttoken: token,
4343
},
44-
[RETRY_KEY]: true,
4544
})
4645
}
4746

lib/interceptors/maintenance-mode.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { InterceptorErrorHandler } from './index.ts'
88

99
import { isAxiosError } from 'axios'
1010

11-
export const RETRY_DELAY_KEY = Symbol('retryDelay')
11+
const RETRY_DELAY_KEY = '_nextcloudMaintenanceModeRetryDelay'
1212

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

3230
/**
3331
* Retry requests if they failed due to maintenance mode

lib/interceptors/not-logged-in.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ export async function onNotLoggedInError(error: unknown) {
2121
if (status === 401
2222
&& response?.data?.message === 'Current user is not logged in'
2323
&& config?.reloadExpiredSession
24-
&& window?.location) {
24+
&& globalThis.location?.reload) {
2525
console.error(`Request to ${responseURL} failed because the user session expired. Reloading the page …`)
26-
27-
window.location.reload()
26+
if (globalThis.OC?.reload) {
27+
globalThis.OC.reload()
28+
} else {
29+
globalThis.location.reload()
30+
}
2831
}
2932
}
3033

lib/axios.d.ts renamed to lib/internal.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,16 @@
99
declare module 'axios' {
1010
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any -- needed as we extend the interface only.
1111
interface AxiosRequestConfig<D = any> {
12-
[key: symbol]: unknown
12+
_nextcloudCsrfTokenReloaded?: true
13+
_nextcloudMaintenanceModeRetryDelay?: number
1314
}
1415
}
1516

17+
declare global {
18+
var OC: {
19+
/** NC 32 and before */
20+
reload?: () => void
21+
} | undefined
22+
}
23+
1624
export {}

0 commit comments

Comments
 (0)