React-navigation:与身份验证的深度链接
Posted
技术标签:
【中文标题】React-navigation:与身份验证的深度链接【英文标题】:React-navigation: Deep linking with authentication 【发布时间】:2019-06-18 10:41:02 【问题描述】:我正在使用 react-native 和 react-navigation 库构建一个移动应用程序,用于管理我的应用程序中的导航。现在,我的应用看起来像这样:
App [SwitchNavigator]
Splash [Screen]
Auth [Screen]
MainApp [StackNavigator]
Home [Screen] (/home)
Profile [Screen] (/profile)
Notifications [Screen] (/notifications)
我已经为屏幕Home
、Profile
和Notifications
集成了深度链接与上述模式,并且它按预期工作。我面临的问题是如何在使用深层链接时管理用户的身份验证。现在,每当我打开深层链接(例如myapp://profile
)时,无论我是否经过身份验证,应用程序都会将我带到屏幕上。我想要它做的是在AsyncStorage
之前检查是否有userToken
,如果没有或它不再有效,那么只需在Auth
屏幕上重定向。
我以与here 描述的几乎完全相同的方式设置身份验证流程。因此,当我的应用程序启动 Splash
屏幕时,会检查用户的手机是否有有效令牌,并在 Auth
屏幕或 Home
屏幕上发送给他。
我现在想出的唯一解决方案是将每个深层链接指向Splash
,验证我的用户,然后解析链接以导航到正确的屏幕。
例如,当用户打开myapp://profile
时,我在Splash
上打开应用程序,验证令牌,然后解析url (/profile
),最后重定向到Auth
或Profile
。
这是这样做的好方法,还是 react-navigation 提供了更好的方法来做到这一点?他们网站上的Deep linking 页面有点亮。
感谢您的帮助!
【问题讨论】:
您是如何管理深度链接和身份验证流程的?每次我尝试打开深层链接(myApp://profile)时,它都会自动导航到主屏幕(因为我已通过身份验证) 【参考方案1】:我的设置与您的类似。我按照Authentication flows · React Navigation 和SplashScreen - Expo Documentation 设置了我的身份验证流程,所以我有点失望的是,让深层链接通过它也是一个挑战。我可以通过自定义我的主开关导航器来完成这项工作,该方法类似于您所说的您现在拥有的解决方案。我只是想分享我的解决方案,所以有一个具体的例子来说明如何开始工作。我的主开关导航器是这样设置的(我也在使用 TypeScript,所以如果不熟悉类型定义,请忽略它们):
const MainNavigation = createSwitchNavigator(
SplashLoading,
Onboarding: OnboardingStackNavigator,
App: AppNavigator,
,
initialRouteName: 'SplashLoading',
);
const previousGetActionForPathAndParams =
MainNavigation.router.getActionForPathAndParams;
Object.assign(MainNavigation.router,
getActionForPathAndParams(path: string, params: any)
const isAuthLink = path.startsWith('auth-link');
if (isAuthLink)
return NavigationActions.navigate(
routeName: 'SplashLoading',
params: ...params, path ,
);
return previousGetActionForPathAndParams(path, params);
,
);
export const AppNavigation = createAppContainer(MainNavigation);
您要通过身份验证流程路由的任何深层链接都需要以 auth-link
开头,或者您选择的任何前缀。这是SplashLoading
的样子:
export const SplashLoading = (props: NavigationScreenProps) =>
const [isSplashReady, setIsSplashReady] = useState(false);
const _cacheFonts: CacheFontsFn = fonts =>
fonts.map(font => Font.loadAsync(font as any));
const _cacheSplashAssets = () =>
const splashIcon = require(splashIconPath);
return Asset.fromModule(splashIcon).downloadAsync();
;
const _cacheAppAssets = async () =>
SplashScreen.hide();
const fontAssetPromises = _cacheFonts(fontMap);
return Promise.all([...fontAssetPromises]);
;
const _initializeApp = async () =>
// Cache assets
await _cacheAppAssets();
// Check if user is logged in
const sessionId = await SecureStore.getItemAsync(CCSID_KEY);
// Get deep linking params
const params = props.navigation.state.params;
let action: any;
if (params && params.routeName)
const routeName, ...routeParams = params;
action = NavigationActions.navigate( routeName, params: routeParams );
// If not logged in, navigate to Auth flow
if (!sessionId)
return props.navigation.dispatch(
NavigationActions.navigate(
routeName: 'Onboarding',
action,
)
);
// Otherwise, navigate to App flow
return props.navigation.navigate(
NavigationActions.navigate(
routeName: 'App',
action,
)
);
;
if (!isSplashReady)
return (
<AppLoading
startAsync=_cacheSplashAssets
onFinish=() => setIsSplashReady(true)
onError=console.warn
autoHideSplash=false
/>
);
return (
<View style= flex: 1 >
<Image source=require(splashIconPath) onLoad=_initializeApp />
</View>
);
;
我使用routeName
查询参数创建深层链接,这是执行身份验证检查后要导航到的屏幕的名称(您显然可以添加您需要的任何其他查询参数)。由于我的SplashLoading
屏幕处理加载所有字体/资产以及身份验证检查,因此我需要每个深层链接来路由它。我遇到的问题是,我会手动退出应用程序的多任务处理,点击深层链接 url,然后应用程序崩溃,因为深层链接绕过了 SplashLoading
,所以没有加载字体。
上面的方法声明了一个action
变量,如果没有设置它将什么都不做。如果routeName
查询参数不是undefined
,我设置action
变量。这使得一旦 Switch 路由器根据 auth(Onboarding
或 App
)决定采用哪条路径,该路由就会在退出 auth/splash 加载流程后获取子操作并导航到 routeName
。
这是我创建的一个示例链接,它在这个系统上运行良好:
exp://192.168.1.7:19000/--/auth-link?routeName=ForgotPasswordChange&cacheKey=a9b3ra50-5fc2-4er7-b4e7-0d6c0925c536
希望库的作者将来能将其作为本机支持的功能,这样黑客就没有必要了。我也很想看看你的想法!
【讨论】:
这很有帮助。帮助我解决了非常相似的情况。还想知道,一旦您导航到 Auth 流程,您如何获得嵌套操作。几行代码会很有帮助。 我遇到了和@VikalpJain 一样的问题。登录成功后如何执行子动作?【参考方案2】:我最终使用自定义 URI 来拦截深度链接启动,然后将这些参数传递给预期的路由。我的加载屏幕处理身份验证检查。
const previousGetActionForPathAndParams = AppContainer.router.getActionForPathAndParams
Object.assign(AppContainer.router,
getActionForPathAndParams (path, params)
if (path === 'auth' && params.routeName && params.userId )
// returns a profile navigate action for myApp://auth?routeName=chat&userId=1234
return NavigationActions.navigate(
routeName: 'Loading',
params: ...params, path ,
)
return previousGetActionForPathAndParams(path, params)
,
)
https://reactnavigation.org/docs/en/routers.html#handling-custom-uris
然后,在我的加载路线中,我像往常一样解析参数,然后路由到预期的位置,再次传递它们。
const userId = this.props.navigation.getParam('userId')
https://reactnavigation.org/docs/en/params.html
【讨论】:
【参考方案3】:在我这边,我实现了这一点,而无需手动解析路径以提取路径和参数。 步骤如下:
获取导航操作返回:getActionForPathAndParams 将导航操作作为参数传递给身份验证视图 然后,当身份验证成功或身份验证已经正常时,我会调度导航操作以继续预期的路线const previousGetActionForPathAndParams = AppContainer.router.getActionForPathAndParams
Object.assign(AppContainer.router,
getActionForPathAndParams(path: string, params: NavigationParams)
const navigationAction = previousGetActionForPathAndParams(path, params)
return NavigationActions.navigate(
routeName: 'Authentication',
params: navigationAction
)
)
然后在身份验证视图中:
const navigationAction = this.navigation.getParam('navigationAction')
if (navigationAction)
this.props.navigation.dispatch(navigationAction)
【讨论】:
【参考方案4】:我找到了一种更简单的方法,我将链接保存在一个单独的文件中,并将其导入主App.js
linking.js
const config =
screens:
Home:'home',
Profile:'profile,
,
;
const linking =
prefixes: ['demo://app'],
config,
;
export default linking;
App.js
& 在登录期间,我将令牌保存在异步存储中,当用户注销时,令牌被删除。根据令牌的可用性,我将链接附加到导航并使用状态将其分离,当它分离时它回退到 SplashScreen。
确保设置initialRouteName="SplashScreen"
import React, useState, useEffect from 'react';
import Linking from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import createStackNavigator from '@react-navigation/stack';
import NavigationContainer from '@react-navigation/native';
import linking from './utils/linking';
import Home, Profile, SplashScreen from './components';
const Stack = createStackNavigator();
// This will be used to retrieve the AsyncStorage String value
const getData = async (key) =>
try
const value = await AsyncStorage.getItem(key);
return value != null ? value : '';
catch (error)
console.error(`Error Caught while getting async storage data: $error`);
;
function _handleOpenUrl(event)
console.log('handleOpenUrl', event.url);
const App = () =>
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() =>
// Checks if the user is logged in or not, if not logged in then
// the app prevents the access to deep link & falls back to splash screen.
getData('studentToken').then((token) =>
if (token === '' || token === undefined) setIsLoggedIn(false);
else setIsLoggedIn(true);
);
Linking.addEventListener('url', _handleOpenUrl);
return () =>
Linking.removeEventListener('url', _handleOpenUrl);
;
, []);
return (
//linking is enabled only if the user is logged in
<NavigationContainer linking=isLoggedIn && linking>
<Stack.Navigator
initialRouteName="SplashScreen"
screenOptions=...TransitionPresets.SlideFromRightios>
<Stack.Screen
name="SplashScreen"
component=SplashScreen
options=headerShown: false
/>
<Stack.Screen
name="Home"
component=Home
options=headerShown: false, gestureEnabled: false
/>
<Stack.Screen
name="Profile"
component=Profile
options=headerShown: false, gestureEnabled: false
/>
</Stack.Navigator>
</NavigationContainer>
);
;
export default App;
当登录用户打开通知中的深层链接时,它会将他带到相应的深层链接屏幕,如果他未登录,则会从启动屏幕打开。
【讨论】:
以上是关于React-navigation:与身份验证的深度链接的主要内容,如果未能解决你的问题,请参考以下文章
无法通过 iOS 15 上的深度链接启动 Google 身份验证器应用程序
设置 apollo-client 标头时出现 NetworkError