Browse Source

Cleanup ie and old edge properties

Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
John Molakvoæ (skjnldsv) 3 years ago
parent
commit
bd303388e3

+ 51 - 0
core/Controller/UnsupportedBrowserController.php

@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Core\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IRequest;
+use OCP\Util;
+
+class UnsupportedBrowserController extends Controller {
+	public function __construct(IRequest $request) {
+		parent::__construct('core', $request);
+	}
+
+	/**
+	 * @PublicPage
+	 * @NoCSRFRequired
+	 *
+	 * @return Response
+	 */
+	public function index(): Response {
+		Util::addScript('core', 'unsupported-browser');
+		Util::addStyle('core', 'icons');
+		return new TemplateResponse('core', 'unsupportedbrowser', [], TemplateResponse::RENDER_AS_ERROR);
+	}
+}

+ 0 - 10
core/css/apps.scss

@@ -970,16 +970,6 @@ $popoveritem-height: 44px;
 $popovericon-size: 16px;
 $outter-margin: math.div($popoveritem-height - $popovericon-size, 2);
 
-.ie,
-.edge {
-	.bubble, .bubble:after,
-	.popovermenu, .popovermenu:after,
-	#app-navigation .app-navigation-entry-menu,
-	#app-navigation .app-navigation-entry-menu:after {
-		border: 1px solid var(--color-border);
-	}
-}
-
 .contact .popovermenu ul,
 .popover__menu {
 	> li > a > img {

+ 0 - 6
core/css/public.scss

@@ -47,12 +47,6 @@ $footer-height: 65px;
 		padding-top: 0;
 	}
 
-	/* force layout to make sure the content element's height matches its contents' height */
-	.ie #content {
-		display: inline-block;
-	}
-
-
 	p.info {
 		margin: 20px auto;
 		text-shadow: 0 0 2px rgba(0, 0, 0, .4);

+ 5 - 0
core/routes.php

@@ -58,11 +58,13 @@ $application->registerRoutes($this, [
 		['name' => 'login#confirmPassword', 'url' => '/login/confirm', 'verb' => 'POST'],
 		['name' => 'login#showLoginForm', 'url' => '/login', 'verb' => 'GET'],
 		['name' => 'login#logout', 'url' => '/logout', 'verb' => 'GET'],
+
 		// Original login flow used by all clients
 		['name' => 'ClientFlowLogin#showAuthPickerPage', 'url' => '/login/flow', 'verb' => 'GET'],
 		['name' => 'ClientFlowLogin#generateAppPassword', 'url' => '/login/flow', 'verb' => 'POST'],
 		['name' => 'ClientFlowLogin#grantPage', 'url' => '/login/flow/grant', 'verb' => 'GET'],
 		['name' => 'ClientFlowLogin#apptokenRedirect', 'url' => '/login/flow/apptoken', 'verb' => 'POST'],
+
 		// NG login flow used by desktop client in case of Kerberos/fancy 2fa (smart cards for example)
 		['name' => 'ClientFlowLoginV2#poll', 'url' => '/login/v2/poll', 'verb' => 'POST'],
 		['name' => 'ClientFlowLoginV2#showAuthPickerPage', 'url' => '/login/v2/flow', 'verb' => 'GET'],
@@ -97,6 +99,9 @@ $application->registerRoutes($this, [
 
 		// Well known requests https://tools.ietf.org/html/rfc5785
 		['name' => 'WellKnown#handle', 'url' => '.well-known/{service}'],
+
+		// Unsupported browser
+		['name' => 'UnsupportedBrowser#index', 'url' => 'unsupported'],
 	],
 	'ocs' => [
 		['root' => '/cloud', 'name' => 'OCS#getCapabilities', 'url' => '/capabilities', 'verb' => 'GET'],

+ 0 - 41
core/src/Polyfill/closest.js

@@ -1,41 +0,0 @@
-/**
- * @copyright Copyright (c) 2016 John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @author John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @license AGPL-3.0-or-later
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
-
-if (!Element.prototype.matches) {
-	Element.prototype.matches
-		= Element.prototype.msMatchesSelector
-		|| Element.prototype.webkitMatchesSelector
-}
-
-if (!Element.prototype.closest) {
-	Element.prototype.closest = function(s) {
-		let el = this
-
-		do {
-			if (el.matches(s)) return el
-			el = el.parentElement || el.parentNode
-		} while (el !== null && el.nodeType === 1)
-		return null
-	}
-}

+ 0 - 3
core/src/Polyfill/index.js

@@ -21,7 +21,4 @@
  *
  */
 
-import './console'
-import './closest'
-import './windows-phone'
 import 'focus-visible'

+ 0 - 1
core/src/components/MainMenu.js

@@ -28,7 +28,6 @@ import Vue from 'vue'
 import AppMenu from './AppMenu.vue'
 
 export const setUp = () => {
-
 	Vue.mixin({
 		methods: {
 			t,

+ 6 - 6
core/src/init.js

@@ -29,12 +29,12 @@ import _ from 'underscore'
 import $ from 'jquery'
 import moment from 'moment'
 
-import { initSessionHeartBeat } from './session-heartbeat'
-import OC from './OC/index'
-import { setUp as setUpContactsMenu } from './components/ContactsMenu'
-import { setUp as setUpMainMenu } from './components/MainMenu'
-import { setUp as setUpUserMenu } from './components/UserMenu'
-import PasswordConfirmation from './OC/password-confirmation'
+import { initSessionHeartBeat } from './session-heartbeat.js'
+import OC from './OC/index.js'
+import { setUp as setUpContactsMenu } from './components/ContactsMenu.js'
+import { setUp as setUpMainMenu } from './components/MainMenu.js'
+import { setUp as setUpUserMenu } from './components/UserMenu.js'
+import PasswordConfirmation from './OC/password-confirmation.js'
 
 // keep in sync with core/css/variables.scss
 const breakpointMobileWidth = 1024

+ 11 - 6
core/src/main.js

@@ -26,16 +26,21 @@
 import $ from 'jquery'
 import 'core-js/stable'
 import 'regenerator-runtime/runtime'
-import './Polyfill/index'
+import './Polyfill/index.js'
 
 // If you remove the line below, tests won't pass
 // eslint-disable-next-line no-unused-vars
-import OC from './OC/index'
+import OC from './OC/index.js'
 
-import './globals'
-import './jquery/index'
-import { initCore } from './init'
-import { registerAppsSlideToggle } from './OC/apps'
+import './globals.js'
+import './jquery/index.js'
+import { initCore } from './init.js'
+import { registerAppsSlideToggle } from './OC/apps.js'
+import { testSupportedBrowser } from './utils/RedirectUnsupportedBrowsers.js'
+
+if (window.TESTING === undefined) {
+	testSupportedBrowser()
+}
 
 window.addEventListener('DOMContentLoaded', function() {
 	initCore()

+ 9 - 15
core/src/Polyfill/console.js → core/src/services/BrowserStorageService.js

@@ -1,10 +1,9 @@
 /**
- * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @copyright 2021 John Molakvoæ <skjnldsv@protonmail.com>
  *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  * @author John Molakvoæ <skjnldsv@protonmail.com>
  *
- * @license AGPL-3.0-or-later
+ * @license GNU AGPL version 3 or any later version
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -17,18 +16,13 @@
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  */
 
-/* eslint-disable no-console */
-if (typeof console === 'undefined' || typeof console.log === 'undefined') {
-	if (!window.console) {
-		window.console = {}
-	}
-	const noOp = () => {}
-	const methods = ['log', 'debug', 'warn', 'info', 'error', 'assert', 'time', 'timeEnd']
-	for (let i = 0; i < methods.length; i++) {
-		console[methods[i]] = noOp
-	}
-}
+import { getBuilder } from '@nextcloud/browser-storage'
+
+export default getBuilder('nextcloud')
+	.clearOnLogout()
+	.persist()
+	.build()

+ 30 - 0
core/src/services/BrowsersListService.js

@@ -0,0 +1,30 @@
+/**
+ * @copyright 2021 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import { getUserAgentRegExp } from 'browserslist-useragent-regexp'
+// eslint-disable-next-line node/no-extraneous-import
+import browserslist from 'browserslist'
+import browserslistConfig from '@nextcloud/browserslist-config'
+
+// Generate a regex that matches user agents to detect incompatible browsers
+export const supportedBrowsersRegExp = getUserAgentRegExp({ allowHigherVersions: true, browsers: browserslistConfig })
+export const supportedBrowsers = browserslist(browserslistConfig)

+ 9 - 12
core/src/Polyfill/windows-phone.js → core/src/services/LoggerService.js

@@ -1,10 +1,9 @@
 /**
- * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @copyright 2021 John Molakvoæ <skjnldsv@protonmail.com>
  *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  * @author John Molakvoæ <skjnldsv@protonmail.com>
  *
- * @license AGPL-3.0-or-later
+ * @license GNU AGPL version 3 or any later version
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -17,15 +16,13 @@
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
  */
 
-// fix device width on windows phone
-if ('-ms-user-select' in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/)) {
-	const msViewportStyle = document.createElement('style')
-	msViewportStyle.appendChild(
-		document.createTextNode('@-ms-viewport{width:auto!important}')
-	)
-	document.getElementsByTagName('head')[0].appendChild(msViewportStyle)
-}
+import { getLoggerBuilder } from '@nextcloud/logger'
+
+export default getLoggerBuilder()
+	.setApp('core')
+	.detectUser()
+	.build()

+ 39 - 0
core/src/unsupported-browser.js

@@ -0,0 +1,39 @@
+/**
+ * @copyright 2021 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import { generateUrl } from '@nextcloud/router'
+import Vue from 'vue'
+
+import { browserStorageKey } from './utils/RedirectUnsupportedBrowsers.js'
+import browserStorage from './services/BrowserStorageService.js'
+import UnsupportedBrowser from './views/UnsupportedBrowser.vue'
+
+// If the ignore token is set, redirect
+if (browserStorage.getItem(browserStorageKey) === 'true') {
+	window.location = generateUrl('/')
+}
+
+export default new Vue({
+	el: '#unsupported-browser',
+	// eslint-disable-next-line vue/match-component-file-name
+	name: 'UnsupportedBrowserRoot',
+	render: h => h(UnsupportedBrowser),
+})

+ 54 - 0
core/src/utils/RedirectUnsupportedBrowsers.js

@@ -0,0 +1,54 @@
+/**
+ * @copyright 2021 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import { generateUrl } from '@nextcloud/router'
+
+import { supportedBrowsersRegExp } from '../services/BrowsersListService.js'
+import browserStorage from '../services/BrowserStorageService.js'
+import logger from '../services/LoggerService.js'
+
+const redirectPath = '/unsupported'
+export const browserStorageKey = 'unsupported-browser-ignore'
+
+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) {
+		window.location = generateUrl(redirectPath)
+	}
+}

+ 191 - 0
core/src/views/UnsupportedBrowser.vue

@@ -0,0 +1,191 @@
+ <!--
+  - @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
+  -
+  - @author John Molakvoæ <skjnldsv@protonmail.com>
+  -
+  - @license GNU AGPL version 3 or any later version
+  -
+  - This program is free software: you can redistribute it and/or modify
+  - it under the terms of the GNU Affero General Public License as
+  - published by the Free Software Foundation, either version 3 of the
+  - License, or (at your option) any later version.
+  -
+  - This program is distributed in the hope that it will be useful,
+  - but WITHOUT ANY WARRANTY; without even the implied warranty of
+  - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  - GNU Affero General Public License for more details.
+  -
+  - You should have received a copy of the GNU Affero General Public License
+  - along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -
+  -->
+<template>
+	<div class="content-unsupported-browser guest-box">
+		<NcEmptyContent>
+			{{ t('core', 'This browser is not supported') }}
+			<template #icon>
+				<Web />
+			</template>
+			<template #action>
+				<div>
+					<h2>
+						{{ t('core', 'Please upgrade to a more recent browser') }}
+					</h2>
+					<NcButton class="content-unsupported-browser__continue" type="primary" @click="forceBrowsing">
+						{{ t('core', 'Continue with this outdated browser') }}
+					</NcButton>
+				</div>
+
+				<ul class="content-unsupported-browser__list">
+					<h3>{{ t('core', 'Supported versions') }}</h3>
+					<li v-for="browser in formattedBrowsersList" :key="browser">
+						{{ browser }}
+					</li>
+				</ul>
+			</template>
+		</NcEmptyContent>
+	</div>
+</template>
+
+<script>
+import { generateUrl } from '@nextcloud/router'
+import { translate as t, translatePlural as n } from '@nextcloud/l10n'
+import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
+import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent'
+import Web from 'vue-material-design-icons/Web'
+
+import { browserStorageKey } from '../utils/RedirectUnsupportedBrowsers.js'
+import { supportedBrowsers } from '../services/BrowsersListService.js'
+import browserStorage from '../services/BrowserStorageService.js'
+import logger from '../services/LoggerService.js'
+
+logger.debug('Supported browsers', { supportedBrowsers })
+
+export default {
+	name: 'UnsupportedBrowser',
+	components: {
+		Web,
+		NcButton,
+		NcEmptyContent,
+	},
+
+	data() {
+		return {
+			agents: {},
+		}
+	},
+
+	computed: {
+		isMobile() {
+			return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
+		},
+
+		/**
+		 * Filter out or include mobile/desktop browsers depending
+		 * on the current user platform/device
+		 */
+		filteredSupportedBrowsers() {
+			return supportedBrowsers.filter(browser => {
+				if (!browser) {
+					return false
+				}
+
+				if (this.isMobile) {
+					return this.isMobileBrowser(browser)
+				}
+				return !this.isMobileBrowser(browser)
+			})
+		},
+
+		formattedBrowsersList() {
+			const list = {}
+
+			// supportedBrowsers is generated by webpack at compilation time
+			this.filteredSupportedBrowsers.forEach(browser => {
+				const [id, version] = browser.split(' ')
+				if (!list[id] || list[id] < parseFloat(version, 10)) {
+					list[id] = parseFloat(version, 10)
+				}
+			})
+
+			return Object.keys(list).map(id => {
+				if (!this.agents[id]?.browser) {
+					return null
+				}
+
+				const version = list[id]
+				const name = this.agents[id]?.browser
+				return this.t('core', '{name} version {version} and above', {
+					name, version,
+				})
+			}).filter(entry => entry !== null)
+		},
+	},
+
+	async beforeMount() {
+		// Dynamic load big list of user agents
+		// eslint-disable-next-line node/no-extraneous-import
+		const { agents } = await import('caniuse-lite')
+		this.agents = agents
+	},
+
+	methods: {
+		t,
+		n,
+
+		// Set the flag allowing this browser and redirect to home
+		forceBrowsing() {
+			browserStorage.setItem(browserStorageKey, true)
+			window.location = generateUrl('/')
+		},
+
+		/**
+		 * Detect if the browserslist browser is a mobile one
+		 * https://github.com/browserslist/browserslist#query-composition
+		 *
+		 * @param {string} browser a valid browserlist browser. e.g `and_chr 90`
+		 */
+		isMobileBrowser(browser) {
+			browser = browser.toLowerCase()
+			return browser.includes('and_')
+				|| browser.includes('android')
+				|| browser.includes('ios_')
+				|| browser.includes('mobile')
+				|| browser.includes('_mob')
+				|| browser.includes('samsung')
+		},
+	},
+}
+</script>
+
+<style lang="scss" scoped>
+.content-unsupported-browser {
+	display: flex;
+	justify-content: center;
+	width: 400px;
+	max-width: 90vw;
+	margin: auto;
+	padding: 30px;
+
+	.empty-content {
+		margin: 0;
+		&::v-deep .empty-content__icon {
+			opacity: 1;
+		}
+	}
+
+	&__continue {
+		display: block;
+		margin: 20px auto;
+	}
+
+	&__list {
+		margin-top: 60px;
+		margin-bottom: 30px;
+		li {
+			text-align: left;
+		}
+	}
+}
+
+</style>

+ 1 - 0
core/templates/unsupportedbrowser.php

@@ -0,0 +1 @@
+<div id="unsupported-browser"></div>

File diff suppressed because it is too large
+ 1 - 0
dist/core-unsupported-browser.js


File diff suppressed because it is too large
+ 0 - 0
dist/core-unsupported-browser.js.map


+ 1 - 0
lib/composer/composer/autoload_classmap.php

@@ -994,6 +994,7 @@ return array(
     'OC\\Core\\Controller\\SetupController' => $baseDir . '/core/Controller/SetupController.php',
     'OC\\Core\\Controller\\TwoFactorChallengeController' => $baseDir . '/core/Controller/TwoFactorChallengeController.php',
     'OC\\Core\\Controller\\UnifiedSearchController' => $baseDir . '/core/Controller/UnifiedSearchController.php',
+    'OC\\Core\\Controller\\UnsupportedBrowserController' => $baseDir . '/core/Controller/UnsupportedBrowserController.php',
     'OC\\Core\\Controller\\UserController' => $baseDir . '/core/Controller/UserController.php',
     'OC\\Core\\Controller\\WalledGardenController' => $baseDir . '/core/Controller/WalledGardenController.php',
     'OC\\Core\\Controller\\WebAuthnController' => $baseDir . '/core/Controller/WebAuthnController.php',

+ 1 - 0
lib/composer/composer/autoload_static.php

@@ -1027,6 +1027,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
         'OC\\Core\\Controller\\SetupController' => __DIR__ . '/../../..' . '/core/Controller/SetupController.php',
         'OC\\Core\\Controller\\TwoFactorChallengeController' => __DIR__ . '/../../..' . '/core/Controller/TwoFactorChallengeController.php',
         'OC\\Core\\Controller\\UnifiedSearchController' => __DIR__ . '/../../..' . '/core/Controller/UnifiedSearchController.php',
+        'OC\\Core\\Controller\\UnsupportedBrowserController' => __DIR__ . '/../../..' . '/core/Controller/UnsupportedBrowserController.php',
         'OC\\Core\\Controller\\UserController' => __DIR__ . '/../../..' . '/core/Controller/UserController.php',
         'OC\\Core\\Controller\\WalledGardenController' => __DIR__ . '/../../..' . '/core/Controller/WalledGardenController.php',
         'OC\\Core\\Controller\\WebAuthnController' => __DIR__ . '/../../..' . '/core/Controller/WebAuthnController.php',

+ 433 - 92
package-lock.json

@@ -12,6 +12,8 @@
         "@chenfengyuan/vue-qrcode": "^1.0.2",
         "@nextcloud/auth": "^1.3.0",
         "@nextcloud/axios": "^1.10.0",
+        "@nextcloud/browser-storage": "^0.1.1",
+        "@nextcloud/browserslist-config": "^2.3.0",
         "@nextcloud/calendar-availability-vue": "^0.5.0-beta.2",
         "@nextcloud/capabilities": "^1.0.4",
         "@nextcloud/dialogs": "^3.1.4",
@@ -31,6 +33,7 @@
         "backbone": "^1.4.1",
         "blueimp-md5": "^2.19.0",
         "bootstrap": "^4.6.0",
+        "browserslist-useragent-regexp": "^3.0.2",
         "buffer": "^6.0.3",
         "camelcase": "^6.3.0",
         "clipboard": "^2.0.10",
@@ -83,7 +86,6 @@
       "devDependencies": {
         "@babel/node": "^7.17.10",
         "@nextcloud/babel-config": "^1.0.0",
-        "@nextcloud/browserslist-config": "^2.3.0",
         "@nextcloud/eslint-config": "^8.0.0",
         "@nextcloud/stylelint-config": "^2.1.2",
         "@testing-library/jest-dom": "^5.16.4",
@@ -3414,26 +3416,53 @@
         "node": ">=8"
       }
     },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
+      "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+      "dependencies": {
+        "@jridgewell/set-array": "^1.0.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
     "node_modules/@jridgewell/resolve-uri": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
       "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
-      "dev": true,
       "engines": {
         "node": ">=6.0.0"
       }
     },
+    "node_modules/@jridgewell/set-array": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/source-map": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
+      "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.0",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      }
+    },
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.4.14",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
-      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
-      "dev": true
+      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
     },
     "node_modules/@jridgewell/trace-mapping": {
       "version": "0.3.15",
       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
       "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
-      "dev": true,
       "dependencies": {
         "@jridgewell/resolve-uri": "^3.0.3",
         "@jridgewell/sourcemap-codec": "^1.4.10"
@@ -3523,7 +3552,6 @@
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/@nextcloud/browserslist-config/-/browserslist-config-2.3.0.tgz",
       "integrity": "sha512-1Tpkof2e9Q0UicHWahQnXXrubJoqyiaqsH9G52v3cjGeVeH3BCfa1FOa41eBwBSFe2/Jxj/wCH2YVLgIXpWbBg==",
-      "dev": true,
       "engines": {
         "node": "^16.0.0",
         "npm": "^7.0.0 || ^8.0.0"
@@ -5447,6 +5475,14 @@
         "sprintf-js": "~1.0.2"
       }
     },
+    "node_modules/argue-cli": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-1.2.1.tgz",
+      "integrity": "sha512-Em3HDMlqiVLNOgXUCYz5NG1mx/44aijsxUOO8elmfvAN4/3ar1S3WPTua7WGhsMbeQm8clOwpDZ09sN7C2FnOg==",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
     "node_modules/aria-query": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz",
@@ -6132,6 +6168,94 @@
         "url": "https://opencollective.com/browserslist"
       }
     },
+    "node_modules/browserslist-useragent-regexp": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/browserslist-useragent-regexp/-/browserslist-useragent-regexp-3.0.2.tgz",
+      "integrity": "sha512-hOvTo9ObY+2PvCLxydvam5WD9hlvWB4bFzRLxc/M5OdJfzjgfsQ9wEF7EpJJP7UJUAnKJdJK28XsSrl5d1DfoA==",
+      "dependencies": {
+        "@types/node": "^16.9.6",
+        "argue-cli": "^1.2.0",
+        "browserslist": "^4.16.3",
+        "chalk": "^4.0.0",
+        "easy-table": "^1.1.1",
+        "useragent": "^2.3.0"
+      },
+      "bin": {
+        "browserslist-useragent-regexp": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/browserslist-useragent-regexp/node_modules/@types/node": {
+      "version": "16.11.68",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.68.tgz",
+      "integrity": "sha512-JkRpuVz3xCNCWaeQ5EHLR/6woMbHZz/jZ7Kmc63AkU+1HxnoUugzSWMck7dsR4DvNYX8jp9wTi9K7WvnxOIQZQ=="
+    },
+    "node_modules/browserslist-useragent-regexp/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/browserslist-useragent-regexp/node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/browserslist-useragent-regexp/node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/browserslist-useragent-regexp/node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+    },
+    "node_modules/browserslist-useragent-regexp/node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/browserslist-useragent-regexp/node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/bser": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
@@ -6436,6 +6560,15 @@
         "wrap-ansi": "^6.2.0"
       }
     },
+    "node_modules/clone": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+      "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+      "optional": true,
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/clone-deep": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -7093,6 +7226,18 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/defaults": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+      "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+      "optional": true,
+      "dependencies": {
+        "clone": "^1.0.2"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/define-properties": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@@ -7313,6 +7458,17 @@
         "url": "https://github.com/fb55/domutils?sponsor=1"
       }
     },
+    "node_modules/easy-table": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz",
+      "integrity": "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "optionalDependencies": {
+        "wcwidth": "^1.0.1"
+      }
+    },
     "node_modules/ecc-jsbn": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -15512,6 +15668,14 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/p-limit": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
@@ -16110,8 +16274,7 @@
     "node_modules/pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
-      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
-      "dev": true
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
     },
     "node_modules/psl": {
       "version": "1.8.0",
@@ -17049,7 +17212,7 @@
     "node_modules/select2": {
       "version": "3.5.1",
       "resolved": "https://registry.npmjs.org/select2/-/select2-3.5.1.tgz",
-      "integrity": "sha1-8oGUibvGX9bTKL5yu+K5XdfofP4="
+      "integrity": "sha512-IFX3UFPpPyK1I1Kuw1R1x+upMyNAZbMlkFhiTnRCRR7ii0KU1brmJMLa3GZcrMWCHiQlm0eKqb6i4XO4pqOrGQ=="
     },
     "node_modules/semver": {
       "version": "6.3.0",
@@ -18066,6 +18229,23 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/terser": {
+      "version": "5.15.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz",
+      "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==",
+      "dependencies": {
+        "@jridgewell/source-map": "^0.3.2",
+        "acorn": "^8.5.0",
+        "commander": "^2.20.0",
+        "source-map-support": "~0.5.20"
+      },
+      "bin": {
+        "terser": "bin/terser"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/terser-webpack-plugin": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
@@ -18099,24 +18279,6 @@
         }
       }
     },
-    "node_modules/terser-webpack-plugin/node_modules/acorn": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
-      "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
-      "optional": true,
-      "peer": true,
-      "bin": {
-        "acorn": "bin/acorn"
-      },
-      "engines": {
-        "node": ">=0.4.0"
-      }
-    },
-    "node_modules/terser-webpack-plugin/node_modules/commander": {
-      "version": "2.20.3",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
-    },
     "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
@@ -18142,37 +18304,21 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/terser-webpack-plugin/node_modules/terser": {
-      "version": "5.10.0",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
-      "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
-      "dependencies": {
-        "commander": "^2.20.0",
-        "source-map": "~0.7.2",
-        "source-map-support": "~0.5.20"
-      },
+    "node_modules/terser/node_modules/acorn": {
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
+      "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
       "bin": {
-        "terser": "bin/terser"
+        "acorn": "bin/acorn"
       },
       "engines": {
-        "node": ">=10"
-      },
-      "peerDependencies": {
-        "acorn": "^8.5.0"
-      },
-      "peerDependenciesMeta": {
-        "acorn": {
-          "optional": true
-        }
+        "node": ">=0.4.0"
       }
     },
-    "node_modules/terser-webpack-plugin/node_modules/terser/node_modules/source-map": {
-      "version": "0.7.3",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
-      "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
-      "engines": {
-        "node": ">= 8"
-      }
+    "node_modules/terser/node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
     },
     "node_modules/test-exclude": {
       "version": "6.0.0",
@@ -18661,6 +18807,40 @@
       "resolved": "https://registry.npmjs.org/url-search-params-polyfill/-/url-search-params-polyfill-8.1.1.tgz",
       "integrity": "sha512-KmkCs6SjE6t4ihrfW9JelAPQIIIFbJweaaSLTh/4AO+c58JlDcb+GbdPt8yr5lRcFg4rPswRFRRhBGpWwh0K/Q=="
     },
+    "node_modules/useragent": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz",
+      "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==",
+      "dependencies": {
+        "lru-cache": "4.1.x",
+        "tmp": "0.0.x"
+      }
+    },
+    "node_modules/useragent/node_modules/lru-cache": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+      "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+      "dependencies": {
+        "pseudomap": "^1.0.2",
+        "yallist": "^2.1.2"
+      }
+    },
+    "node_modules/useragent/node_modules/tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dependencies": {
+        "os-tmpdir": "~1.0.2"
+      },
+      "engines": {
+        "node": ">=0.6.0"
+      }
+    },
+    "node_modules/useragent/node_modules/yallist": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+      "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
+    },
     "node_modules/util": {
       "version": "0.10.4",
       "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
@@ -19121,6 +19301,15 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/wcwidth": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+      "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+      "optional": true,
+      "dependencies": {
+        "defaults": "^1.0.3"
+      }
+    },
     "node_modules/webdav": {
       "version": "4.11.0",
       "resolved": "https://registry.npmjs.org/webdav/-/webdav-4.11.0.tgz",
@@ -22058,23 +22247,44 @@
         }
       }
     },
+    "@jridgewell/gen-mapping": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
+      "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+      "requires": {
+        "@jridgewell/set-array": "^1.0.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      }
+    },
     "@jridgewell/resolve-uri": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
-      "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
-      "dev": true
+      "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
+    },
+    "@jridgewell/set-array": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
+    },
+    "@jridgewell/source-map": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
+      "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
+      "requires": {
+        "@jridgewell/gen-mapping": "^0.3.0",
+        "@jridgewell/trace-mapping": "^0.3.9"
+      }
     },
     "@jridgewell/sourcemap-codec": {
       "version": "1.4.14",
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
-      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
-      "dev": true
+      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
     },
     "@jridgewell/trace-mapping": {
       "version": "0.3.15",
       "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
       "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
-      "dev": true,
       "requires": {
         "@jridgewell/resolve-uri": "^3.0.3",
         "@jridgewell/sourcemap-codec": "^1.4.10"
@@ -22145,8 +22355,7 @@
     "@nextcloud/browserslist-config": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/@nextcloud/browserslist-config/-/browserslist-config-2.3.0.tgz",
-      "integrity": "sha512-1Tpkof2e9Q0UicHWahQnXXrubJoqyiaqsH9G52v3cjGeVeH3BCfa1FOa41eBwBSFe2/Jxj/wCH2YVLgIXpWbBg==",
-      "dev": true
+      "integrity": "sha512-1Tpkof2e9Q0UicHWahQnXXrubJoqyiaqsH9G52v3cjGeVeH3BCfa1FOa41eBwBSFe2/Jxj/wCH2YVLgIXpWbBg=="
     },
     "@nextcloud/calendar-availability-vue": {
       "version": "0.5.0-beta.2",
@@ -23725,6 +23934,11 @@
         "sprintf-js": "~1.0.2"
       }
     },
+    "argue-cli": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-1.2.1.tgz",
+      "integrity": "sha512-Em3HDMlqiVLNOgXUCYz5NG1mx/44aijsxUOO8elmfvAN4/3ar1S3WPTua7WGhsMbeQm8clOwpDZ09sN7C2FnOg=="
+    },
     "aria-query": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz",
@@ -24242,6 +24456,69 @@
         "picocolors": "^1.0.0"
       }
     },
+    "browserslist-useragent-regexp": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/browserslist-useragent-regexp/-/browserslist-useragent-regexp-3.0.2.tgz",
+      "integrity": "sha512-hOvTo9ObY+2PvCLxydvam5WD9hlvWB4bFzRLxc/M5OdJfzjgfsQ9wEF7EpJJP7UJUAnKJdJK28XsSrl5d1DfoA==",
+      "requires": {
+        "@types/node": "^16.9.6",
+        "argue-cli": "^1.2.0",
+        "browserslist": "^4.16.3",
+        "chalk": "^4.0.0",
+        "easy-table": "^1.1.1",
+        "useragent": "^2.3.0"
+      },
+      "dependencies": {
+        "@types/node": {
+          "version": "16.11.68",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.68.tgz",
+          "integrity": "sha512-JkRpuVz3xCNCWaeQ5EHLR/6woMbHZz/jZ7Kmc63AkU+1HxnoUugzSWMck7dsR4DvNYX8jp9wTi9K7WvnxOIQZQ=="
+        },
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "chalk": {
+          "version": "4.1.2",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        }
+      }
+    },
     "bser": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
@@ -24467,6 +24744,12 @@
         "wrap-ansi": "^6.2.0"
       }
     },
+    "clone": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+      "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+      "optional": true
+    },
     "clone-deep": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -24999,6 +25282,15 @@
       "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
       "dev": true
     },
+    "defaults": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+      "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+      "optional": true,
+      "requires": {
+        "clone": "^1.0.2"
+      }
+    },
     "define-properties": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@@ -25170,6 +25462,15 @@
         "domhandler": "^4.2.0"
       }
     },
+    "easy-table": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz",
+      "integrity": "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==",
+      "requires": {
+        "ansi-regex": "^5.0.1",
+        "wcwidth": "^1.0.1"
+      }
+    },
     "ecc-jsbn": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -31396,6 +31697,11 @@
         "word-wrap": "^1.2.3"
       }
     },
+    "os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="
+    },
     "p-limit": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
@@ -31818,8 +32124,7 @@
     "pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
-      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
-      "dev": true
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
     },
     "psl": {
       "version": "1.8.0",
@@ -32518,7 +32823,7 @@
     "select2": {
       "version": "3.5.1",
       "resolved": "https://registry.npmjs.org/select2/-/select2-3.5.1.tgz",
-      "integrity": "sha1-8oGUibvGX9bTKL5yu+K5XdfofP4="
+      "integrity": "sha512-IFX3UFPpPyK1I1Kuw1R1x+upMyNAZbMlkFhiTnRCRR7ii0KU1brmJMLa3GZcrMWCHiQlm0eKqb6i4XO4pqOrGQ=="
     },
     "semver": {
       "version": "6.3.0",
@@ -33326,6 +33631,29 @@
         "supports-hyperlinks": "^2.0.0"
       }
     },
+    "terser": {
+      "version": "5.15.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz",
+      "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==",
+      "requires": {
+        "@jridgewell/source-map": "^0.3.2",
+        "acorn": "^8.5.0",
+        "commander": "^2.20.0",
+        "source-map-support": "~0.5.20"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "8.8.0",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
+          "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w=="
+        },
+        "commander": {
+          "version": "2.20.3",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+        }
+      }
+    },
     "terser-webpack-plugin": {
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz",
@@ -33338,18 +33666,6 @@
         "terser": "^5.7.2"
       },
       "dependencies": {
-        "acorn": {
-          "version": "8.7.0",
-          "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
-          "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
-          "optional": true,
-          "peer": true
-        },
-        "commander": {
-          "version": "2.20.3",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
-        },
         "schema-utils": {
           "version": "3.1.1",
           "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
@@ -33364,23 +33680,6 @@
           "version": "0.6.1",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
           "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
-        },
-        "terser": {
-          "version": "5.10.0",
-          "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
-          "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
-          "requires": {
-            "commander": "^2.20.0",
-            "source-map": "~0.7.2",
-            "source-map-support": "~0.5.20"
-          },
-          "dependencies": {
-            "source-map": {
-              "version": "0.7.3",
-              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
-              "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
-            }
-          }
         }
       }
     },
@@ -33766,6 +34065,39 @@
       "resolved": "https://registry.npmjs.org/url-search-params-polyfill/-/url-search-params-polyfill-8.1.1.tgz",
       "integrity": "sha512-KmkCs6SjE6t4ihrfW9JelAPQIIIFbJweaaSLTh/4AO+c58JlDcb+GbdPt8yr5lRcFg4rPswRFRRhBGpWwh0K/Q=="
     },
+    "useragent": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz",
+      "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==",
+      "requires": {
+        "lru-cache": "4.1.x",
+        "tmp": "0.0.x"
+      },
+      "dependencies": {
+        "lru-cache": {
+          "version": "4.1.5",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+          "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+          "requires": {
+            "pseudomap": "^1.0.2",
+            "yallist": "^2.1.2"
+          }
+        },
+        "tmp": {
+          "version": "0.0.33",
+          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+          "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+          "requires": {
+            "os-tmpdir": "~1.0.2"
+          }
+        },
+        "yallist": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+          "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
+        }
+      }
+    },
     "util": {
       "version": "0.10.4",
       "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
@@ -34146,6 +34478,15 @@
         "graceful-fs": "^4.1.2"
       }
     },
+    "wcwidth": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+      "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+      "optional": true,
+      "requires": {
+        "defaults": "^1.0.3"
+      }
+    },
     "webdav": {
       "version": "4.11.0",
       "resolved": "https://registry.npmjs.org/webdav/-/webdav-4.11.0.tgz",

+ 3 - 1
package.json

@@ -32,6 +32,8 @@
     "@chenfengyuan/vue-qrcode": "^1.0.2",
     "@nextcloud/auth": "^1.3.0",
     "@nextcloud/axios": "^1.10.0",
+    "@nextcloud/browser-storage": "^0.1.1",
+    "@nextcloud/browserslist-config": "^2.3.0",
     "@nextcloud/calendar-availability-vue": "^0.5.0-beta.2",
     "@nextcloud/capabilities": "^1.0.4",
     "@nextcloud/dialogs": "^3.1.4",
@@ -51,6 +53,7 @@
     "backbone": "^1.4.1",
     "blueimp-md5": "^2.19.0",
     "bootstrap": "^4.6.0",
+    "browserslist-useragent-regexp": "^3.0.2",
     "buffer": "^6.0.3",
     "camelcase": "^6.3.0",
     "clipboard": "^2.0.10",
@@ -103,7 +106,6 @@
   "devDependencies": {
     "@babel/node": "^7.17.10",
     "@nextcloud/babel-config": "^1.0.0",
-    "@nextcloud/browserslist-config": "^2.3.0",
     "@nextcloud/eslint-config": "^8.0.0",
     "@nextcloud/stylelint-config": "^2.1.2",
     "@testing-library/jest-dom": "^5.16.4",

+ 1 - 0
webpack.modules.js

@@ -38,6 +38,7 @@ module.exports = {
 		recommendedapps: path.join(__dirname, 'core/src', 'recommendedapps.js'),
 		systemtags: path.resolve(__dirname, 'core/src', 'systemtags/merged-systemtags.js'),
 		'unified-search': path.join(__dirname, 'core/src', 'unified-search.js'),
+		'unsupported-browser': path.join(__dirname, 'core/src', 'unsupported-browser.js'),
 	},
 	dashboard: {
 		main: path.join(__dirname, 'apps/dashboard/src', 'main.js'),

Some files were not shown because too many files changed in this diff