反应导航:带有身份验证的深层链接

时间:2019-01-24 16:15:46

标签: react-native react-navigation deep-linking

我正在构建一个带有react-native和react-navigation库的移动应用程序,用于管理我的应用程序中的导航。现在,我的应用看起来像这样:

App [SwitchNavigator]
    Splash [Screen]
    Auth [Screen]
    MainApp [StackNavigator]
        Home [Screen]            (/home)
        Profile [Screen]         (/profile)
        Notifications [Screen]   (/notifications)

我已经将“深层链接”与上述屏幕HomeProfileNotifications的模式集成在一起,并且可以正常工作。我面临的问题是使用深层链接时如何管理用户的身份验证。现在,无论何时打开深层链接(例如,myapp://profile),无论我是否通过身份验证,该应用程序都会将我带到屏幕上。我想要它做的是在AsyncStorage中检查是否有userToken,如果没有或不再有效,则只需在Auth屏幕上进行重定向。

我以与here几乎完全相同的方式设置了身份验证流程。因此,当我的应用程序启动时,Splash屏幕将检查用户电话中是否存在有效令牌,并在Auth屏幕或Home屏幕上将其发送。

我目前想出的唯一解决方案是将每个深层链接定向到Splash,对我的用户进行身份验证,然后解析该链接以导航至正确的屏幕。 因此,例如,当用户打开myapp://profile时,我在Splash上打开应用,验证令牌,然后解析url(/profile),最后将其中一个重定向到AuthProfile

这是这样做的好方法,还是反应导航提供了一种更好的方法?他们网站上的Deep linking页面有点亮。

感谢您的帮助!

3 个答案:

答案 0 :(得分:2)

我的设置类似于您的设置。我遵循了Authentication flows · React NavigationSplashScreen - Expo Documentation来设置我的Auth流,因此,让深层链接也流经它是一个挑战,对此我有些失望。我可以通过自定义主开关导航器来使此工作正常进行,该方法类似于您所说的当前解决方案。我只是想分享我的解决方案,因此有一个具体示例说明如何开始工作。我已经像这样设置了主开关导航器(也正在使用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查询参数创建了深层链接,查询参数是执行了auth检查之后可以导航到的屏幕的名称(显然,您可以添加所需的任何其他查询参数)。由于我的SplashLoading屏幕可处理所有字体/资产的加载以及身份验证,因此我需要每个深层链接来进行路由。我遇到的问题是,我将手动从多任务处理中退出应用程序,点击一个深层链接URL,并由于深层链接绕过SplashLoading而导致应用程序崩溃,因此字体没有被加载。

以上方法声明了一个action变量,如果未设置该变量将不起作用。如果routeName查询参数不是undefined,则设置action变量。这样一来,一旦Switch路由器根据auth(OnboardingApp决定采用哪个路径,该路由就会获得子操作并在退出auth /之后导航到routeName。飞溅加载流。

以下是我创建的一个示例链接,该链接在此系统上正常运行: exp://192.168.1.7:19000/--/auth-link?routeName=ForgotPasswordChange&cacheKey=a9b3ra50-5fc2-4er7-b4e7-0d6c0925c536

希望图书馆作者将来会将此功能作为本机支持的功能,因此无需进行黑客攻击。我也很想知道您的想法!

答案 1 :(得分:1)

我最终使用了一个自定义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

答案 2 :(得分:1)

在我这方面,我无需手动解析路径以提取路径和参数即可实现此目的。 步骤如下:

  • 获取以下操作返回的导航操作:getActionForPathAndParams
  • 将导航操作作为参数传递给Authentication视图
  • 然后,当身份验证成功或身份验证已经成功时,我将导航操作分派到预期的路线上

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)