diff options
Diffstat (limited to 'core/src/utils')
-rw-r--r-- | core/src/utils/ClipboardFallback.ts | 47 | ||||
-rw-r--r-- | core/src/utils/xhr-request.js | 81 |
2 files changed, 121 insertions, 7 deletions
diff --git a/core/src/utils/ClipboardFallback.ts b/core/src/utils/ClipboardFallback.ts new file mode 100644 index 00000000000..b374f9d0a44 --- /dev/null +++ b/core/src/utils/ClipboardFallback.ts @@ -0,0 +1,47 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { t } from '@nextcloud/l10n' + +/** + * + * @param text + */ +function unsecuredCopyToClipboard(text) { + const textArea = document.createElement('textarea') + const textAreaContent = document.createTextNode(text) + textArea.appendChild(textAreaContent) + document.body.appendChild(textArea) + + textArea.focus({ preventScroll: true }) + textArea.select() + + try { + // This is a fallback for browsers that do not support the Clipboard API + // execCommand is deprecated, but it is the only way to copy text to the clipboard in some browsers + document.execCommand('copy') + } catch (err) { + window.prompt(t('core', 'Clipboard not available, please copy manually'), text) + console.error('[ERROR] core: files Unable to copy to clipboard', err) + } + + document.body.removeChild(textArea) +} + +/** + * + */ +function initFallbackClipboardAPI() { + if (!window.navigator?.clipboard?.writeText) { + console.info('[INFO] core: Clipboard API not available, using fallback') + Object.defineProperty(window.navigator, 'clipboard', { + value: { + writeText: unsecuredCopyToClipboard, + }, + writable: false, + }) + } +} + +export { initFallbackClipboardAPI } diff --git a/core/src/utils/xhr-request.js b/core/src/utils/xhr-request.js index b991ae875cf..7f074a857a6 100644 --- a/core/src/utils/xhr-request.js +++ b/core/src/utils/xhr-request.js @@ -3,12 +3,14 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { getRootUrl } from '@nextcloud/router' +import { getCurrentUser } from '@nextcloud/auth' +import { generateUrl, getRootUrl } from '@nextcloud/router' +import logger from '../logger.js' /** * * @param {string} url the URL to check - * @returns {boolean} + * @return {boolean} */ const isRelativeUrl = (url) => { return !url.startsWith('https://') && !url.startsWith('http://') @@ -27,6 +29,60 @@ const isNextcloudUrl = (url) => { } /** + * Check if a user was logged in but is now logged-out. + * If this is the case then the user will be forwarded to the login page. + * @return {Promise<void>} + */ +async function checkLoginStatus() { + // skip if no logged in user + if (getCurrentUser() === null) { + return + } + + // skip if already running + if (checkLoginStatus.running === true) { + return + } + + // only run one request in parallel + checkLoginStatus.running = true + + try { + // We need to check this as a 401 in the first place could also come from other reasons + const { status } = await window.fetch(generateUrl('/apps/files')) + if (status === 401) { + console.warn('User session was terminated, forwarding to login page.') + await wipeBrowserStorages() + window.location = generateUrl('/login?redirect_url={url}', { + url: window.location.pathname + window.location.search + window.location.hash, + }) + } + } catch (error) { + console.warn('Could not check login-state') + } finally { + delete checkLoginStatus.running + } +} + +/** + * Clear all Browser storages connected to current origin. + * @return {Promise<void>} + */ +export async function wipeBrowserStorages() { + try { + window.localStorage.clear() + window.sessionStorage.clear() + const indexedDBList = await window.indexedDB.databases() + for (const indexedDB of indexedDBList) { + await window.indexedDB.deleteDatabase(indexedDB.name) + } + logger.debug('Browser storages cleared') + } catch (error) { + logger.error('Could not clear browser storages', { error }) + } +} + +/** * Intercept XMLHttpRequest and fetch API calls to add X-Requested-With header * * This is also done in @nextcloud/axios but not all requests pass through that @@ -35,17 +91,24 @@ export const interceptRequests = () => { XMLHttpRequest.prototype.open = (function(open) { return function(method, url, async) { open.apply(this, arguments) - if (isNextcloudUrl(url) && !this.getResponseHeader('X-Requested-With')) { - this.setRequestHeader('X-Requested-With', 'XMLHttpRequest') + if (isNextcloudUrl(url)) { + if (!this.getResponseHeader('X-Requested-With')) { + this.setRequestHeader('X-Requested-With', 'XMLHttpRequest') + } + this.addEventListener('loadend', function() { + if (this.status === 401) { + checkLoginStatus() + } + }) } } })(XMLHttpRequest.prototype.open) window.fetch = (function(fetch) { - return (resource, options) => { + return async (resource, options) => { // fetch allows the `input` to be either a Request object or any stringifyable value if (!isNextcloudUrl(resource.url ?? resource.toString())) { - return fetch(resource, options) + return await fetch(resource, options) } if (!options) { options = {} @@ -60,7 +123,11 @@ export const interceptRequests = () => { options.headers['X-Requested-With'] = 'XMLHttpRequest' } - return fetch(resource, options) + const response = await fetch(resource, options) + if (response.status === 401) { + checkLoginStatus() + } + return response } })(window.fetch) } |