diff options
Diffstat (limited to 'core/src/utils')
-rw-r--r-- | core/src/utils/ClipboardFallback.ts | 47 | ||||
-rw-r--r-- | core/src/utils/RedirectUnsupportedBrowsers.js | 40 | ||||
-rw-r--r-- | core/src/utils/xhr-request.js | 133 |
3 files changed, 220 insertions, 0 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/RedirectUnsupportedBrowsers.js b/core/src/utils/RedirectUnsupportedBrowsers.js new file mode 100644 index 00000000000..2880d051ca2 --- /dev/null +++ b/core/src/utils/RedirectUnsupportedBrowsers.js @@ -0,0 +1,40 @@ +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { generateUrl } from '@nextcloud/router' +import { supportedBrowsersRegExp } from '../services/BrowsersListService.js' +import browserStorage from '../services/BrowserStorageService.js' +import logger from '../logger.js' + +export const browserStorageKey = 'unsupported-browser-ignore' +const redirectPath = generateUrl('/unsupported') + +const isBrowserOverridden = browserStorage.getItem(browserStorageKey) === 'true' + +/** + * Test the current browser user agent against our official browserslist config + * and redirect if unsupported + */ +export const testSupportedBrowser = function() { + if (supportedBrowsersRegExp.test(navigator.userAgent)) { + logger.debug('this browser is officially supported ! 🚀') + return + } + + // If incompatible BUT ignored, let's keep going + if (isBrowserOverridden) { + logger.debug('this browser is NOT supported but has been manually overridden ! ⚠️') + return + } + + // If incompatible, NOT overridden AND NOT already on the warning page, + // redirect to the unsupported warning page + if (window.location.pathname.indexOf(redirectPath) === -1) { + const redirectUrl = window.location.href.replace(window.location.origin, '') + const base64Param = Buffer.from(redirectUrl).toString('base64') + history.pushState(null, null, `${redirectPath}?redirect_url=${base64Param}`) + window.location.reload() + } +} diff --git a/core/src/utils/xhr-request.js b/core/src/utils/xhr-request.js new file mode 100644 index 00000000000..7f074a857a6 --- /dev/null +++ b/core/src/utils/xhr-request.js @@ -0,0 +1,133 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { getCurrentUser } from '@nextcloud/auth' +import { generateUrl, getRootUrl } from '@nextcloud/router' +import logger from '../logger.js' + +/** + * + * @param {string} url the URL to check + * @return {boolean} + */ +const isRelativeUrl = (url) => { + return !url.startsWith('https://') && !url.startsWith('http://') +} + +/** + * @param {string} url The URL to check + * @return {boolean} true if the URL points to this nextcloud instance + */ +const isNextcloudUrl = (url) => { + const nextcloudBaseUrl = window.location.protocol + '//' + window.location.host + getRootUrl() + // if the URL is absolute and starts with the baseUrl+rootUrl + // OR if the URL is relative and starts with rootUrl + return url.startsWith(nextcloudBaseUrl) + || (isRelativeUrl(url) && url.startsWith(getRootUrl())) +} + +/** + * 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 + */ +export const interceptRequests = () => { + XMLHttpRequest.prototype.open = (function(open) { + return function(method, url, async) { + open.apply(this, arguments) + 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 async (resource, options) => { + // fetch allows the `input` to be either a Request object or any stringifyable value + if (!isNextcloudUrl(resource.url ?? resource.toString())) { + return await fetch(resource, options) + } + if (!options) { + options = {} + } + if (!options.headers) { + options.headers = new Headers() + } + + if (options.headers instanceof Headers && !options.headers.has('X-Requested-With')) { + options.headers.append('X-Requested-With', 'XMLHttpRequest') + } else if (options.headers instanceof Object && !options.headers['X-Requested-With']) { + options.headers['X-Requested-With'] = 'XMLHttpRequest' + } + + const response = await fetch(resource, options) + if (response.status === 401) { + checkLoginStatus() + } + return response + } + })(window.fetch) +} |