aboutsummaryrefslogtreecommitdiffstats
path: root/core/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/utils')
-rw-r--r--core/src/utils/ClipboardFallback.ts47
-rw-r--r--core/src/utils/RedirectUnsupportedBrowsers.js40
-rw-r--r--core/src/utils/xhr-request.js133
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)
+}