From fc4aef3b92721e2f0b21c989fa7894cb51fd8c02 Mon Sep 17 00:00:00 2001 From: IZUMI-Zu <274620705z@gmail.com> Date: Fri, 27 Sep 2024 08:12:33 +0800 Subject: [PATCH] feat: improve login logic (#28) --- App.js | 15 ++- CasdoorLoginPage.js | 180 ++++++++++++++++----------- EnterCasdoorSdkConfig.js | 258 ++++++++++++--------------------------- Header.js | 25 +++- LoginMethodSelector.js | 62 ++++++++++ SettingPage.js | 110 ++++++++++------- package-lock.json | 109 ++++++++++------- package.json | 11 +- syncLogic.js | 74 ++++++----- 9 files changed, 466 insertions(+), 378 deletions(-) create mode 100644 LoginMethodSelector.js diff --git a/App.js b/App.js index d8cb384..6629a30 100644 --- a/App.js +++ b/App.js @@ -20,6 +20,7 @@ import ContentLoader, {Circle, Rect} from "react-content-loader/native"; import {ZoomInDownZoomOutUp, createNotifications} from "react-native-notificated"; import {GestureHandlerRootView} from "react-native-gesture-handler"; import {useMigrations} from "drizzle-orm/expo-sqlite/migrator"; +import {ActionSheetProvider} from "@expo/react-native-action-sheet"; import Header from "./Header"; import NavigationBar from "./NavigationBar"; @@ -77,12 +78,14 @@ const App = () => { return ( - - -
- - - + + + +
+ + + + ); diff --git a/CasdoorLoginPage.js b/CasdoorLoginPage.js index 8094681..22a5c6d 100644 --- a/CasdoorLoginPage.js +++ b/CasdoorLoginPage.js @@ -20,19 +20,21 @@ import {useNotifications} from "react-native-notificated"; import SDK from "casdoor-react-native-sdk"; import PropTypes from "prop-types"; import EnterCasdoorSdkConfig from "./EnterCasdoorSdkConfig"; +import ScanQRCodeForLogin from "./ScanLogin"; import useStore from "./useStorage"; -// import {LogBox} from "react-native"; -// LogBox.ignoreAllLogs(); +import DefaultCasdoorSdkConfig from "./DefaultCasdoorSdkConfig"; let sdk = null; -const CasdoorLoginPage = ({onWebviewClose}) => { + +function CasdoorLoginPage({onWebviewClose, initialMethod}) { CasdoorLoginPage.propTypes = { onWebviewClose: PropTypes.func.isRequired, + initialMethod: PropTypes.oneOf(["manual", "scan", "demo"]).isRequired, }; const {notify} = useNotifications(); const [casdoorLoginURL, setCasdoorLoginURL] = useState(""); - const [showConfigPage, setShowConfigPage] = useState(true); + const [currentView, setCurrentView] = useState(initialMethod === "scan" ? "scanner" : "config"); const { serverUrl, @@ -42,38 +44,55 @@ const CasdoorLoginPage = ({onWebviewClose}) => { organizationName, token, getCasdoorConfig, + setCasdoorConfig, + setServerUrl, + setClientId, + setAppName, + setOrganizationName, setUserInfo, setToken, } = useStore(); - const handleHideConfigPage = () => { - setShowConfigPage(false); - }; + useEffect(() => { + if (initialMethod === "demo") { + setCasdoorConfig(DefaultCasdoorSdkConfig); + } + }, [initialMethod, setCasdoorConfig]); - const handleShowConfigPage = () => { - setShowConfigPage(true); + const initSdk = () => { + const configs = { + demo: DefaultCasdoorSdkConfig, + scan: getCasdoorConfig(), + manual: serverUrl && clientId && redirectPath && appName && organizationName ? getCasdoorConfig() : null, + }; + sdk = configs[initialMethod] ? new SDK(configs[initialMethod]) : null; }; const getCasdoorSignInUrl = async() => { - const signinUrl = await sdk.getSigninUrl(); - setCasdoorLoginURL(signinUrl); + initSdk(); + if (sdk) { + const signinUrl = await sdk.getSigninUrl(); + setCasdoorLoginURL(signinUrl); + } }; - useEffect(() => { - if (serverUrl && clientId && redirectPath && appName && organizationName) { - sdk = new SDK(getCasdoorConfig()); - getCasdoorSignInUrl(); - } - }, [serverUrl, clientId, redirectPath, appName, organizationName]); + const handleLogin = (method) => { + const actions = { + manual: () => { + getCasdoorSignInUrl(); + setCurrentView("webview"); + }, + demo: () => { + getCasdoorSignInUrl(); + setCurrentView("webview"); + }, + scan: () => setCurrentView("scanner"), + }; - useEffect(() => { - if (token) { - onWebviewClose(); - } - }, [token]); + actions[method]?.(); + }; const onNavigationStateChange = async(navState) => { - const {redirectPath} = getCasdoorConfig(); if (navState.url.startsWith(redirectPath)) { onWebviewClose(); const token = await sdk.getAccessToken(navState.url); @@ -83,57 +102,70 @@ const CasdoorLoginPage = ({onWebviewClose}) => { } }; - const handleErrorResponse = (error) => { - notify("error", { - params: { - text1: "Error", - text2: error.description, - }, - }); - setShowConfigPage(true); + const handleQRLogin = (loginInfo) => { + setServerUrl(loginInfo.serverUrl); + setClientId(""); + setAppName(""); + setOrganizationName(""); + initSdk(); + try { + const accessToken = loginInfo.accessToken; + const userInfo = sdk.JwtDecode(accessToken); + setToken(accessToken); + setUserInfo(userInfo); + notify("success", {params: {title: "Success", description: "Logged in successfully!"}}); + setCurrentView("config"); + onWebviewClose(); + } catch (error) { + notify("error", {params: {title: "Error in login", description: error.message}}); + } }; - return ( - - - {showConfigPage && ( - { + const views = { + config: ( + handleLogin(initialMethod)} + onWebviewClose={onWebviewClose} + /> + ), + scanner: ( + { + setCurrentView("config"); + onWebviewClose(); + }} + onLogin={handleQRLogin} + /> + ), + webview: casdoorLoginURL && !token && ( + + setCurrentView("config")}> + Back to Config + + { + notify("error", {params: {title: "Error", description: nativeEvent.description}}); + setCurrentView("config"); + }} + style={styles.webview} + mixedContentMode="always" + javaScriptEnabled={true} /> - )} - {!showConfigPage && casdoorLoginURL !== "" && !token && ( - <> - - Back to Config - - { - const {nativeEvent} = syntheticEvent; - handleErrorResponse(nativeEvent); - }} - style={styles.webview} - mixedContentMode="always" - javaScriptEnabled={true} - /> - - )} - - - ); -}; + + ), + }; + + return views[currentView] || null; + }; + + return {renderContent()}; +} const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: "white", - paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0, - }, webview: { flex: 1, }, @@ -146,10 +178,18 @@ const styles = StyleSheet.create({ color: "white", fontWeight: "bold", }, + safeArea: { + flex: 1, + backgroundColor: "white", + paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0, + + }, }); export const CasdoorLogout = () => { - if (sdk) {sdk.clearState();} + if (sdk) { + sdk.clearState(); + } }; export default CasdoorLoginPage; diff --git a/EnterCasdoorSdkConfig.js b/EnterCasdoorSdkConfig.js index 6f52551..7458d10 100644 --- a/EnterCasdoorSdkConfig.js +++ b/EnterCasdoorSdkConfig.js @@ -12,22 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, {useState} from "react"; -import {ScrollView, Text, View} from "react-native"; -import {Button, IconButton, Portal, TextInput} from "react-native-paper"; +import React from "react"; +import {StyleSheet, Text, View} from "react-native"; +import {Button, Portal, TextInput} from "react-native-paper"; import {useNotifications} from "react-native-notificated"; -import SDK from "casdoor-react-native-sdk"; -import DefaultCasdoorSdkConfig from "./DefaultCasdoorSdkConfig"; import PropTypes from "prop-types"; -import ScanQRCodeForLogin from "./ScanLogin"; import useStore from "./useStorage"; -const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => { - EnterCasdoorSdkConfig.propTypes = { - onClose: PropTypes.func.isRequired, - onWebviewClose: PropTypes.func.isRequired, - }; - +function EnterCasdoorSdkConfig({onClose, onWebviewClose}) { const { serverUrl, clientId, @@ -38,16 +30,10 @@ const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => { setClientId, setAppName, setOrganizationName, - setCasdoorConfig, - getCasdoorConfig, - setToken, - setUserInfo, } = useStore(); const {notify} = useNotifications(); - const [showScanner, setShowScanner] = useState(false); - const closeConfigPage = () => { onClose(); onWebviewClose(); @@ -66,207 +52,121 @@ const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => { onClose(); }; - const handleScanToLogin = () => { - setShowScanner(true); - }; - - const handleLogin = (loginInfo) => { - setServerUrl(loginInfo.serverUrl); - setClientId(""); - setAppName(""); - setOrganizationName(""); - - const sdk = new SDK(getCasdoorConfig()); - - try { - const accessToken = loginInfo.accessToken; - const userInfo = sdk.JwtDecode(accessToken); - setToken(accessToken); - setUserInfo(userInfo); - - notify("success", { - params: { - title: "Success", - description: "Logged in successfully!", - }, - }); - - setShowScanner(false); - onClose(); - onWebviewClose(); - } catch (error) { - notify("error", { - params: { - title: "Error in login", - description: error, - }, - }); - } - }; - - const handleUseDefault = () => { - setCasdoorConfig(DefaultCasdoorSdkConfig); - onClose(); - }; - return ( - + - - Casdoor server - Casdoor Configuration + + + + + - - - - - + - - - {showScanner && ( - setShowScanner(false)} - onLogin={handleLogin} - /> - )} + ); +} + +EnterCasdoorSdkConfig.propTypes = { + onClose: PropTypes.func.isRequired, + onWebviewClose: PropTypes.func.isRequired, }; -const styles = { - scrollContainer: { - flexGrow: 1, - width: "100%", +const styles = StyleSheet.create({ + container: { + flex: 1, justifyContent: "center", alignItems: "center", - backgroundColor: "rgba(255, 255, 255, 0.5)", + backgroundColor: "rgba(0, 0, 0, 0.5)", }, content: { - width: "95%", - borderRadius: 10, - padding: 20, - backgroundColor: "#F5F5F5", + width: "90%", + maxWidth: 400, + borderRadius: 28, + padding: 24, + backgroundColor: "#FFFFFF", shadowColor: "#000", shadowOffset: {width: 0, height: 2}, shadowOpacity: 0.1, - shadowRadius: 10, + shadowRadius: 8, elevation: 5, }, - input: { - marginVertical: 10, - fontSize: 16, - backgroundColor: "white", + title: { + fontSize: 20, + fontWeight: "bold", + fontFamily: "Lato-Bold", + color: "#212121", + textAlign: "center", + marginBottom: 16, }, - buttonRow: { + buttonContainer: { flexDirection: "row", justifyContent: "space-between", - marginTop: 14, - marginBottom: 12, }, button: { - borderRadius: 5, - paddingVertical: 8, - }, - confirmButton: { - backgroundColor: "#6200EE", flex: 1, - marginRight: 5, - }, - scanButton: { - backgroundColor: "#03DAC6", - flex: 1, - marginLeft: 5, + marginHorizontal: 8, + borderRadius: 100, }, buttonLabel: { + paddingVertical: 4, fontSize: 16, - color: "white", - }, - outlinedButton: { - borderColor: "#6200EE", - borderWidth: 1, - width: "100%", - }, - outlinedButtonLabel: { - color: "#6200EE", - fontSize: 16, - textAlign: "center", - }, - header: { - position: "relative", - alignItems: "center", - marginBottom: 20, - }, - title: { - fontSize: 24, fontWeight: "bold", - color: "#333", - textAlign: "center", }, - closeButton: { - position: "absolute", - right: 0, - top: -8, + formContainer: { + marginBottom: 8, }, -}; + input: { + marginBottom: 16, + }, +}); export default EnterCasdoorSdkConfig; diff --git a/Header.js b/Header.js index d2300e8..bc94279 100644 --- a/Header.js +++ b/Header.js @@ -20,6 +20,7 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import CasdoorLoginPage, {CasdoorLogout} from "./CasdoorLoginPage"; import useStore from "./useStorage"; import {useAccountSync} from "./useAccountStore"; +import LoginMethodSelector from "./LoginMethodSelector"; const {width} = Dimensions.get("window"); @@ -28,6 +29,7 @@ const Header = () => { const {isSyncing, syncError, clearSyncError} = useAccountSync(); const [showLoginPage, setShowLoginPage] = React.useState(false); const [menuVisible, setMenuVisible] = React.useState(false); + const [loginMethod, setLoginMethod] = React.useState(null); const {notify} = useNotifications(); const openMenu = () => setMenuVisible(true); @@ -38,8 +40,20 @@ const Header = () => { closeMenu(); }; - const handleCasdoorLogin = () => setShowLoginPage(true); - const handleHideLoginPage = () => setShowLoginPage(false); + const {openActionSheet} = LoginMethodSelector({ + onSelectMethod: (method) => { + setLoginMethod(method); + setShowLoginPage(true); + }, + }); + + const handleCasdoorLogin = () => { + openActionSheet(); + }; + + const handleHideLoginPage = () => { + setShowLoginPage(false); + }; const handleCasdoorLogout = () => { CasdoorLogout(); @@ -114,7 +128,12 @@ const Header = () => { - {showLoginPage && } + {showLoginPage && ( + + )} ); }; diff --git a/LoginMethodSelector.js b/LoginMethodSelector.js new file mode 100644 index 0000000..4106362 --- /dev/null +++ b/LoginMethodSelector.js @@ -0,0 +1,62 @@ +// Copyright 2023 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {useActionSheet} from "@expo/react-native-action-sheet"; + +const LoginMethodSelector = ({onSelectMethod}) => { + const {showActionSheetWithOptions} = useActionSheet(); + + const openActionSheet = () => { + const options = [ + "Manual Server Setup", + "Login Using QR Code", + "Try Casdoor Demo Site", + "Cancel", + ]; + const cancelButtonIndex = 3; + + showActionSheetWithOptions( + { + title: "Select Login Method", + cancelButtonTintColor: "red", + options, + cancelButtonIndex, + }, + (buttonIndex) => { + handleSelection(buttonIndex); + } + ); + }; + + const handleSelection = (buttonIndex) => { + switch (buttonIndex) { + case 0: + onSelectMethod("manual"); + break; + case 1: + onSelectMethod("scan"); + break; + case 2: + onSelectMethod("demo"); + break; + default: + // Cancel was pressed + break; + } + }; + + return {openActionSheet}; +}; + +export default LoginMethodSelector; diff --git a/SettingPage.js b/SettingPage.js index 69503e8..f7f999f 100644 --- a/SettingPage.js +++ b/SettingPage.js @@ -12,64 +12,88 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as React from "react"; -import {StyleSheet, View, useWindowDimensions} from "react-native"; +import React, {useState} from "react"; +import {Dimensions, StyleSheet, View} from "react-native"; import {Button, Surface, Text} from "react-native-paper"; +import {ActionSheetProvider} from "@expo/react-native-action-sheet"; import CasdoorLoginPage, {CasdoorLogout} from "./CasdoorLoginPage"; +import LoginMethodSelector from "./LoginMethodSelector"; import useStore from "./useStorage"; +const {width} = Dimensions.get("window"); + const SettingPage = () => { - const [showLoginPage, setShowLoginPage] = React.useState(false); + const [showLoginPage, setShowLoginPage] = useState(false); + const [loginMethod, setLoginMethod] = useState(null); const {userInfo, clearAll} = useStore(); - const {width} = useWindowDimensions(); - const handleCasdoorLogin = () => setShowLoginPage(true); - const handleHideLoginPage = () => setShowLoginPage(false); + const {openActionSheet} = LoginMethodSelector({ + onSelectMethod: (method) => { + setLoginMethod(method); + setShowLoginPage(true); + }, + }); + + const handleCasdoorLogin = () => { + openActionSheet(); + }; + + const handleHideLoginPage = () => { + setShowLoginPage(false); + setLoginMethod(null); + }; const handleCasdoorLogout = () => { CasdoorLogout(); clearAll(); }; - const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: "center", - alignItems: "center", - padding: 16, - }, - surface: { - padding: 16, - width: width > 600 ? 400 : "100%", - maxWidth: 400, - alignItems: "center", - }, - title: { - fontSize: 24, - marginBottom: 24, - }, - button: { - marginTop: 16, - width: "100%", - }, - }); - return ( - - - Account Settings - - - {showLoginPage && } - + + + + Account Settings + + + {showLoginPage && ( + + )} + + ); }; +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + padding: 16, + }, + surface: { + padding: 16, + width: width > 600 ? 400 : "100%", + maxWidth: 400, + alignItems: "center", + }, + title: { + fontSize: 24, + marginBottom: 24, + }, + button: { + marginTop: 16, + width: "100%", + }, +}); + export default SettingPage; diff --git a/package-lock.json b/package-lock.json index 7aac8cd..bc6ba9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "casdoor-app", "version": "1.0.0", "dependencies": { + "@expo/react-native-action-sheet": "^4.1.0", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/masked-view": "^0.1.11", "@react-native-community/netinfo": "11.3.1", @@ -18,18 +19,18 @@ "casdoor-react-native-sdk": "1.1.0", "drizzle-orm": "^0.33.0", "eslint-plugin-import": "^2.28.1", - "expo": "~51.0.31", + "expo": "~51.0.34", "expo-asset": "~10.0.10", - "expo-camera": "~15.0.15", + "expo-camera": "~15.0.16", "expo-crypto": "~13.0.2", - "expo-dev-client": "~4.0.26", + "expo-dev-client": "~4.0.27", "expo-drizzle-studio-plugin": "^0.0.2", - "expo-image": "~1.12.15", + "expo-image": "~1.13.0", "expo-image-picker": "~15.0.7", "expo-sqlite": "^14.0.6", "expo-status-bar": "~1.12.1", "expo-system-ui": "~3.0.7", - "expo-updates": "~0.25.24", + "expo-updates": "~0.25.25", "hi-base32": "^0.5.1", "hotp-totp": "^1.0.6", "prop-types": "^15.8.1", @@ -3505,9 +3506,10 @@ } }, "node_modules/@expo/config-plugins": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-8.0.8.tgz", - "integrity": "sha512-Fvu6IO13EUw0R9WeqxUO37FkM62YJBNcZb9DyJAOgMz7Ez/vaKQGEjKt9cwT+Q6uirtCATMgaq6VWAW7YW8xXw==", + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-8.0.9.tgz", + "integrity": "sha512-dNCG45C7BbDPV9MdWvCbsFtJtVn4w/TJbb5b7Yr6FA8HYIlaaVM0wqUMzTPmGj54iYXw8X/Vge8uCPxg7RWgeA==", + "license": "MIT", "dependencies": { "@expo/config-types": "^51.0.0-unreleased", "@expo/json-file": "~8.3.0", @@ -4487,6 +4489,19 @@ "node": ">= 10.0.0" } }, + "node_modules/@expo/react-native-action-sheet": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@expo/react-native-action-sheet/-/react-native-action-sheet-4.1.0.tgz", + "integrity": "sha512-RILoWhREgjMdr1NUSmZa/cHg8onV2YPDAMOy0iIP1c3H7nT9QQZf5dQNHK8ehcLM82sarVxriBJyYSSHAx7j6w==", + "license": "MIT", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.1", + "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "react": ">=18.0.0" + } + }, "node_modules/@expo/rudder-sdk-node": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@expo/rudder-sdk-node/-/rudder-sdk-node-1.1.1.tgz", @@ -4522,9 +4537,10 @@ } }, "node_modules/@expo/vector-icons": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.0.2.tgz", - "integrity": "sha512-70LpmXQu4xa8cMxjp1fydgRPsalefnHaXLzIwaHMEzcZhnyjw2acZz8azRrZOslPVAWlxItOa2Dd7WtD/kI+CA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.0.3.tgz", + "integrity": "sha512-UJAKOXPPi6ez/1QZfoFVopCH3+c12Sw+T+IIVkvONCEN7zjN1fLxxWHkZ7Spz4WO5EH2ObtaJfCe/k4rw+ftxA==", + "license": "MIT", "dependencies": { "prop-types": "^15.8.1" } @@ -7281,6 +7297,16 @@ "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.42.tgz", "integrity": "sha512-Xxk14BrwHnGi0xlURPRb+Y0UNn2w3cTkeFm7pKMsYOaNgH/kabbJLhcBoNIodwsbTz7Z8KcWjtDvlGH0nc0U9w==" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "license": "MIT", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -7329,14 +7355,12 @@ "node_modules/@types/prop-types": { "version": "15.7.7", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.7.tgz", - "integrity": "sha512-FbtmBWCcSa2J4zL781Zf1p5YUBXQomPEcep9QZCfRfQgTxz3pJWiDFLebohZ9fFntX5ibzOkSsrJ0TEew8cAog==", - "devOptional": true + "integrity": "sha512-FbtmBWCcSa2J4zL781Zf1p5YUBXQomPEcep9QZCfRfQgTxz3pJWiDFLebohZ9fFntX5ibzOkSsrJ0TEew8cAog==" }, "node_modules/@types/react": { "version": "18.2.79", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz", "integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==", - "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -9056,8 +9080,7 @@ "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "devOptional": true + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/dag-map": { "version": "1.0.2", @@ -10420,24 +10443,24 @@ } }, "node_modules/expo": { - "version": "51.0.31", - "resolved": "https://registry.npmjs.org/expo/-/expo-51.0.31.tgz", - "integrity": "sha512-YiUNcxzSkQ0jlKW+e8F81KnZfAhCugEZI9VYmuIsFONHivtiYIADHdcFvUWnexUEdgPQDkgWw85XBnIbzIZ39Q==", + "version": "51.0.34", + "resolved": "https://registry.npmjs.org/expo/-/expo-51.0.34.tgz", + "integrity": "sha512-l2oi+hIj/ph3qGcvM54Nyd2uF3Zq5caVmSg7AXfBUgtvcdv5Pj1EI/2xCXP9tfMNQo351CWyOwBkTGjv+GdrLg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.18.29", "@expo/config": "9.0.3", - "@expo/config-plugins": "8.0.8", + "@expo/config-plugins": "8.0.9", "@expo/metro-config": "0.18.11", - "@expo/vector-icons": "^14.0.0", + "@expo/vector-icons": "^14.0.3", "babel-preset-expo": "~11.0.14", "expo-asset": "~10.0.10", "expo-file-system": "~17.0.1", - "expo-font": "~12.0.9", + "expo-font": "~12.0.10", "expo-keep-awake": "~13.0.2", "expo-modules-autolinking": "1.11.2", - "expo-modules-core": "1.12.23", + "expo-modules-core": "1.12.24", "fbemitter": "^3.0.0", "whatwg-url-without-unicode": "8.0.0-3" }, @@ -10459,9 +10482,9 @@ } }, "node_modules/expo-camera": { - "version": "15.0.15", - "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-15.0.15.tgz", - "integrity": "sha512-zJS0rfOwGfyDrccMsFaLH9s0mW0f6czw+W+RHvbyAdAmoAh6ZtzZRGbosKIYoRsn/8L7ajNp01seJUjsT0FtzA==", + "version": "15.0.16", + "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-15.0.16.tgz", + "integrity": "sha512-FLE02DMqkjwsb7IugKAqQvBe6s+TCQeb5LupO1+r//wAhBwmHncOrc6zV95ZEC2f9PTPK34nFH/s8CDGiVzIAA==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" @@ -10495,9 +10518,9 @@ } }, "node_modules/expo-dev-client": { - "version": "4.0.26", - "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-4.0.26.tgz", - "integrity": "sha512-GM+X7bngAK2vr0YMkPnQFUFVW22eG3CjoxTJ0yUwW3RgCqFdMkTeAIS/1sEXjyNYjGkigtgtch+bdYtJxfqpuw==", + "version": "4.0.27", + "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-4.0.27.tgz", + "integrity": "sha512-4f0eO7GTdGzYYg3qABR98Vc2iiCBA2HICh8namVAvqkcVCuh44I9lOctaAEe/932+lLugEW4+Mv29pdEHq3/FA==", "license": "MIT", "dependencies": { "expo-dev-launcher": "4.0.27", @@ -10620,9 +10643,10 @@ } }, "node_modules/expo-font": { - "version": "12.0.9", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-12.0.9.tgz", - "integrity": "sha512-seTCyf0tbgkAnp3ZI9ZfK9QVtURQUgFnuj+GuJ5TSnN0XsOtVe1s2RxTvmMgkfuvfkzcjJ69gyRpsZS1cC8hjw==", + "version": "12.0.10", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-12.0.10.tgz", + "integrity": "sha512-Q1i2NuYri3jy32zdnBaHHCya1wH1yMAsI+3CCmj9zlQzlhsS9Bdwcj2W3c5eU5FvH2hsNQy4O+O1NnM6o/pDaQ==", + "license": "MIT", "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -10631,9 +10655,9 @@ } }, "node_modules/expo-image": { - "version": "1.12.15", - "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-1.12.15.tgz", - "integrity": "sha512-rjvnNSaFnrmlugDESTaYJhgdqRLn+M5vu0lD5NGNd2LkxGG5HrRV3gSzeyQQ68XRhrDN8eJvkcKujPKJUTMraw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-1.13.0.tgz", + "integrity": "sha512-0NLDcFmEn4Nh1sXeRvNzDHT+Fl6FXtTol6ki6kYYH0/iDeSFWyIy/Fek6kzDDYAmhipSMR7buPf7VVoHseTbAA==", "license": "MIT", "peerDependencies": { "expo": "*" @@ -10810,9 +10834,9 @@ } }, "node_modules/expo-modules-core": { - "version": "1.12.23", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.12.23.tgz", - "integrity": "sha512-NYp/rWhKW6zlqNdC8/r+FckzlAGWX0IJEjOxwYHuYeRUn/vnKksb43G4E3jcaQEZgmWlKxK4LpxL3gr7m0RJFA==", + "version": "1.12.24", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.12.24.tgz", + "integrity": "sha512-3geIe2ecizlp7l26iY8Nmc59z2d1RUC5nQZtI9iJoi5uHEUV/zut8e4zRLFVnZb8KOcMcEDsrvaBL5DPnqdfpg==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" @@ -10852,9 +10876,9 @@ } }, "node_modules/expo-updates": { - "version": "0.25.24", - "resolved": "https://registry.npmjs.org/expo-updates/-/expo-updates-0.25.24.tgz", - "integrity": "sha512-juqdOUvaMfu6zeUg3fTk6ciLw4QK+0HXNR0+X41BVOFilNmlTFQZ6LyRGJAZJP7HQs2bHR5d/btAXkejtIqVXw==", + "version": "0.25.25", + "resolved": "https://registry.npmjs.org/expo-updates/-/expo-updates-0.25.25.tgz", + "integrity": "sha512-Z9sCf6w3876JLlj6DGRsXFI/NnRhXM0gfXT2dusniagt4qvwThGKxS/zEcpo9JUyO411yVL/XGv411Czeaw9xA==", "license": "MIT", "dependencies": { "@expo/code-signing-certificates": "0.0.5", @@ -11216,7 +11240,8 @@ "node_modules/fontfaceobserver": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", - "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==" + "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==", + "license": "BSD-2-Clause" }, "node_modules/for-each": { "version": "0.3.3", diff --git a/package.json b/package.json index 0fcc210..9bc59e9 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "release": "npx -p semantic-release-expo -p semantic-release -p @semantic-release/git -p @semantic-release/changelog -p @semantic-release/exec semantic-release" }, "dependencies": { + "@expo/react-native-action-sheet": "^4.1.0", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/masked-view": "^0.1.11", "@react-native-community/netinfo": "11.3.1", @@ -20,18 +21,18 @@ "casdoor-react-native-sdk": "1.1.0", "drizzle-orm": "^0.33.0", "eslint-plugin-import": "^2.28.1", - "expo": "~51.0.31", + "expo": "~51.0.34", "expo-asset": "~10.0.10", - "expo-camera": "~15.0.15", + "expo-camera": "~15.0.16", "expo-crypto": "~13.0.2", - "expo-dev-client": "~4.0.26", + "expo-dev-client": "~4.0.27", "expo-drizzle-studio-plugin": "^0.0.2", - "expo-image": "~1.12.15", + "expo-image": "~1.13.0", "expo-image-picker": "~15.0.7", "expo-sqlite": "^14.0.6", "expo-status-bar": "~1.12.1", "expo-system-ui": "~3.0.7", - "expo-updates": "~0.25.24", + "expo-updates": "~0.25.25", "hi-base32": "^0.5.1", "hotp-totp": "^1.0.6", "prop-types": "^15.8.1", diff --git a/syncLogic.js b/syncLogic.js index dfef243..a1df988 100644 --- a/syncLogic.js +++ b/syncLogic.js @@ -16,6 +16,12 @@ import {eq} from "drizzle-orm"; import * as schema from "./db/schema"; import * as api from "./api"; import {generateToken} from "./totpUtil"; +import useStore from "./useStorage"; + +function handleTokenExpiration() { + const {clearAll} = useStore.getState(); + clearAll(); +} function getLocalAccounts(db) { return db.select().from(schema.accounts).all(); @@ -134,48 +140,56 @@ function mergeAccounts(localAccounts, serverAccounts, serverTimestamp) { } export async function syncWithCloud(db, userInfo, serverUrl, token) { - // db.delete(schema.accounts).run(); - const localAccounts = await getLocalAccounts(db); + try { + const localAccounts = await getLocalAccounts(db); - const {updatedTime, mfaAccounts: serverAccounts} = await api.getMfaAccounts( - serverUrl, - userInfo.owner, - userInfo.name, - token - ); + const {updatedTime, mfaAccounts: serverAccounts} = await api.getMfaAccounts( + serverUrl, + userInfo.owner, + userInfo.name, + token + ); - const mergedAccounts = mergeAccounts(localAccounts, serverAccounts, updatedTime); + const mergedAccounts = mergeAccounts(localAccounts, serverAccounts, updatedTime); - await updateLocalDatabase(db, mergedAccounts); + await updateLocalDatabase(db, mergedAccounts); - const accountsToSync = mergedAccounts.filter(account => account.deletedAt === null || account.deletedAt === undefined) - .map(account => ({ + const accountsToSync = mergedAccounts.filter(account => account.deletedAt === null || account.deletedAt === undefined) + .map(account => ({ + issuer: account.issuer, + accountName: account.accountName, + secretKey: account.secretKey, + })); + + const serverAccountsStringified = serverAccounts.map(account => JSON.stringify({ issuer: account.issuer, accountName: account.accountName, secretKey: account.secretKey, })); - const serverAccountsStringified = serverAccounts.map(account => JSON.stringify({ - issuer: account.issuer, - accountName: account.accountName, - secretKey: account.secretKey, - })); + const accountsToSyncStringified = accountsToSync.map(account => JSON.stringify(account)); - const accountsToSyncStringified = accountsToSync.map(account => JSON.stringify(account)); + if (JSON.stringify(accountsToSyncStringified.sort()) !== JSON.stringify(serverAccountsStringified.sort())) { + const {status} = await api.updateMfaAccounts( + serverUrl, + userInfo.owner, + userInfo.name, + accountsToSync, + token + ); - if (JSON.stringify(accountsToSyncStringified.sort()) !== JSON.stringify(serverAccountsStringified.sort())) { - const {status} = await api.updateMfaAccounts( - serverUrl, - userInfo.owner, - userInfo.name, - accountsToSync, - token - ); + if (status !== "ok") { + throw new Error("Sync failed"); + } + } - if (status !== "ok") { - throw new Error("Sync failed"); + await db.update(schema.accounts).set({syncAt: new Date()}).run(); + + } catch (error) { + if (error.message.includes("Access token has expired")) { + handleTokenExpiration(); + throw new Error("Access token has expired, please login again."); } + throw error; } - - await db.update(schema.accounts).set({syncAt: new Date()}).run(); }