From c0660db06d2d3ac8ac45bcae0ab32cbf96cd2670 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:13:42 -0300 Subject: [PATCH] chore: migrate AuthenticationWebView to hooks (#5054) * chore: migrate AuthenticationWebView to hooks * minor tweak * remove navigation, tweak at useRoute --------- Co-authored-by: GleidsonDaniel --- app/lib/methods/helpers/debounce.ts | 6 +- app/stacks/OutsideStack.tsx | 6 +- app/views/AuthenticationWebView.tsx | 181 ++++++++++------------------ 3 files changed, 71 insertions(+), 122 deletions(-) diff --git a/app/lib/methods/helpers/debounce.ts b/app/lib/methods/helpers/debounce.ts index 5061e322b..4a58b6d76 100644 --- a/app/lib/methods/helpers/debounce.ts +++ b/app/lib/methods/helpers/debounce.ts @@ -1,4 +1,4 @@ -import { useDebouncedCallback } from 'use-debounce'; +import { useDebouncedCallback, Options } from 'use-debounce'; export function debounce(func: Function, wait?: number, immediate?: boolean) { let timeout: ReturnType | null; @@ -24,6 +24,6 @@ export function debounce(func: Function, wait?: number, immediate?: boolean) { return _debounce; } -export function useDebounce(func: (...args: any) => any, wait?: number): (...args: any[]) => void { - return useDebouncedCallback(func, wait || 1000); +export function useDebounce(func: (...args: any) => any, wait?: number, options?: Options): (...args: any[]) => void { + return useDebouncedCallback(func, wait || 1000, options); } diff --git a/app/stacks/OutsideStack.tsx b/app/stacks/OutsideStack.tsx index ec19c2319..40a3c9cb1 100644 --- a/app/stacks/OutsideStack.tsx +++ b/app/stacks/OutsideStack.tsx @@ -50,11 +50,7 @@ const OutsideStackModal = () => { screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...ModalAnimation, presentation: 'transparentModal' }} > - + ); }; diff --git a/app/views/AuthenticationWebView.tsx b/app/views/AuthenticationWebView.tsx index 894c524b9..4874f13a1 100644 --- a/app/views/AuthenticationWebView.tsx +++ b/app/views/AuthenticationWebView.tsx @@ -1,20 +1,20 @@ -import React from 'react'; -import { WebView, WebViewNavigation } from 'react-native-webview'; -import { connect } from 'react-redux'; -import parse from 'url-parse'; -import { StackNavigationProp } from '@react-navigation/stack'; -import { WebViewMessage } from 'react-native-webview/lib/WebViewTypes'; import { RouteProp } from '@react-navigation/core'; +import { useNavigation, useRoute } from '@react-navigation/native'; +import { StackNavigationProp } from '@react-navigation/stack'; +import React, { useLayoutEffect, useState } from 'react'; +import { WebView, WebViewNavigation } from 'react-native-webview'; +import { WebViewMessage } from 'react-native-webview/lib/WebViewTypes'; +import parse from 'url-parse'; -import { OutsideModalParamList } from '../stacks/types'; -import StatusBar from '../containers/StatusBar'; import ActivityIndicator from '../containers/ActivityIndicator'; -import { TSupportedThemes, withTheme } from '../theme'; -import { userAgent } from '../lib/constants'; -import { debounce } from '../lib/methods/helpers'; import * as HeaderButton from '../containers/HeaderButton'; +import StatusBar from '../containers/StatusBar'; +import { ICredentials } from '../definitions'; +import { userAgent } from '../lib/constants'; +import { useAppSelector } from '../lib/hooks'; +import { useDebounce } from '../lib/methods/helpers'; import { Services } from '../lib/services'; -import { IApplicationState, ICredentials } from '../definitions'; +import { OutsideModalParamList } from '../stacks/types'; // iframe uses a postMessage to send the token to the client // We'll handle this sending the token to the hash of the window.location @@ -40,95 +40,56 @@ window.addEventListener('popstate', function() { }); `; -interface INavigationOption { - navigation: StackNavigationProp; - route: RouteProp; -} +const AuthenticationWebView = () => { + const [logging, setLogging] = useState(false); + const [loading, setLoading] = useState(false); -interface IAuthenticationWebView extends INavigationOption { - server: string; - Accounts_Iframe_api_url: string; - Accounts_Iframe_api_method: string; - theme: TSupportedThemes; -} + const navigation = useNavigation>(); + const { + params: { authType, url, ssoToken } + } = useRoute>(); -interface IState { - logging: boolean; - loading: boolean; -} + const { Accounts_Iframe_api_method, Accounts_Iframe_api_url, server } = useAppSelector(state => ({ + server: state.server.server, + Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url as string, + Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method as string + })); -class AuthenticationWebView extends React.PureComponent { - private oauthRedirectRegex: RegExp; - private iframeRedirectRegex: RegExp; + const oauthRedirectRegex = new RegExp(`(?=.*(${server}))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g'); + const iframeRedirectRegex = new RegExp(`(?=.*(${server}))(?=.*(event|loginToken|token))`, 'g'); - static navigationOptions = ({ route, navigation }: INavigationOption) => { - const { authType } = route.params; - return { - headerLeft: () => , - title: ['saml', 'cas', 'iframe'].includes(authType) ? 'SSO' : 'OAuth' - }; - }; + // Force 3s delay so the server has time to evaluate the token + const debouncedLogin = useDebounce((params: ICredentials) => login(params), 3000); - constructor(props: IAuthenticationWebView) { - super(props); - this.state = { - logging: false, - loading: false - }; - this.oauthRedirectRegex = new RegExp(`(?=.*(${props.server}))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g'); - this.iframeRedirectRegex = new RegExp(`(?=.*(${props.server}))(?=.*(event|loginToken|token))`, 'g'); - } - - componentWillUnmount() { - if (this.debouncedLogin && this.debouncedLogin.stop) { - this.debouncedLogin.stop(); - } - } - - dismiss = () => { - const { navigation } = this.props; - navigation.pop(); - }; - - login = (params: ICredentials) => { - const { logging } = this.state; + const login = (params: ICredentials) => { if (logging) { return; } - - this.setState({ logging: true }); - + setLogging(true); try { Services.loginOAuthOrSso(params); } catch (e) { console.warn(e); } - this.setState({ logging: false }); - this.dismiss(); + setLogging(false); + navigation.pop(); }; - // Force 3s delay so the server has time to evaluate the token - debouncedLogin = debounce((params: ICredentials) => this.login(params), 3000); - - tryLogin = debounce( + const tryLogin = useDebounce( async () => { - const { Accounts_Iframe_api_url, Accounts_Iframe_api_method } = this.props; const data = await fetch(Accounts_Iframe_api_url, { method: Accounts_Iframe_api_method }).then(response => response.json()); const resume = data?.login || data?.loginToken; if (resume) { - this.login({ resume }); + login({ resume }); } }, 3000, - true + { leading: true } ); - onNavigationStateChange = (webViewState: WebViewNavigation | WebViewMessage) => { + const onNavigationStateChange = (webViewState: WebViewNavigation | WebViewMessage) => { const url = decodeURIComponent(webViewState.url); - const { route } = this.props; - const { authType } = route.params; if (authType === 'saml' || authType === 'cas') { - const { ssoToken } = route.params; const parsedUrl = parse(url, true); // ticket -> cas / validate & saml_idp_credentialToken -> saml if (parsedUrl.pathname?.includes('validate') || parsedUrl.query?.ticket || parsedUrl.query?.saml_idp_credentialToken) { @@ -140,28 +101,28 @@ class AuthenticationWebView extends React.PureComponent - - this.onNavigationStateChange(nativeEvent)} - onNavigationStateChange={this.onNavigationStateChange} - injectedJavaScript={isIframe ? injectedJavaScript : undefined} - onLoadStart={() => { - this.setState({ loading: true }); - }} - onLoadEnd={() => { - this.setState({ loading: false }); - }} - /> - {loading ? : null} - - ); - } -} + useLayoutEffect(() => { + navigation.setOptions({ + headerLeft: () => , + title: ['saml', 'cas', 'iframe'].includes(authType) ? 'SSO' : 'OAuth' + }); + }, [authType, navigation]); -const mapStateToProps = (state: IApplicationState) => ({ - server: state.server.server, - Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url as string, - Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method as string -}); + return ( + <> + + onNavigationStateChange(nativeEvent)} + onNavigationStateChange={onNavigationStateChange} + injectedJavaScript={isIframe ? injectedJavaScript : undefined} + onLoadStart={() => setLoading(true)} + onLoadEnd={() => setLoading(false)} + /> + {loading ? : null} + + ); +}; -export default connect(mapStateToProps)(withTheme(AuthenticationWebView)); +export default AuthenticationWebView;