Resuming journeys using magic links in React Native
PingOne Advanced Identity Cloud PingAM React Native
You can configure your React Native app to capture the URI of the magic link the user visits. The key part of this deep-link URI is the suspendedId query parameter.
You can then use the Journey client’s resume() method, rather than start(), to continue the journey. You must pass in the URI that contains the suspendedId parameter.
Installing the SDK packages
To install the module into your React Native project, use yarn or npm as follows:
-
yarn
-
npm
yarn add @ping-identity/rn-journey
npm install @ping-identity/rn-journey
Because this package includes native iOS code, run pod install in the ios directory after installing it:
cd ios && pod install
Configuring a custom URI scheme for deep linking
Magic links encode the suspendedId as a query parameter in the URI that opens your app.
You must register a custom URI scheme in the Android and iOS apps so that the operating system routes the link to your application.
Registering a URI scheme on Android
Add an <intent-filter> to the activity that handles deep links (typically MainActivity) in android/app/src/main/AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
...>
<!-- Standard launch intent-filter -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Deep link intent-filter for magic links -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="example.com" />
</intent-filter>
</activity>
Replace myapp and example.com with the scheme and host that match the Magic Link Custom URL configured on your Email Suspend Node.
|
Using |
Registering a URI scheme on iOS
-
In Xcode, open your project’s Info tab and add a new URL type under URL Types.
-
Identifier: Your bundle ID, for example
com.example.myapp. -
URL Schemes: The scheme portion of your deep link, for example
myapp.
Alternatively, add the entry directly to
ios/<YourApp>/Info.plist:Registering a custom URI scheme in Info.plist<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>myapp</string> </array> <key>CFBundleURLName</key> <string>com.example.myapp</string> </dict> </array> -
-
Forward the URL to React Native’s
Linkingmodule inios/<YourApp>/AppDelegate.swift:
import React
// Inside AppDelegate class
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
return RCTLinkingManager.application(app, open: url, options: options)
}
// Handle universal links and cold-start URLs
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
return RCTLinkingManager.application(
application,
continue: userActivity,
restorationHandler: restorationHandler
)
}
Creating the Journey client
import { createJourneyClient } from '@ping-identity/rn-journey';
const journeyClient = createJourneyClient({
serverUrl: 'https://openam-forgerock-sdks.forgeblocks.com/am',
realm: 'alpha',
cookie: 'ch15fefc5407912',
timeout: 30000,
});
The client instance is typically created once, at module scope or inside an initialization hook. The same client instance can handle both start() and resume() calls.
Detecting the suspended journey
When the journey reaches an Email Suspend node, the SDK receives a ContinueNode containing a SuspendedTextOutputCallback.
Use this to show a "check your email" message and wait for the magic link to be tapped:
import type { JourneyNode } from '@ping-identity/rn-journey';
function handleNode(node: JourneyNode) {
if (node.type === 'ContinueNode') {
const suspended = node.callbacks?.find(
(cb) => cb.type === 'SuspendedTextOutputCallback',
);
if (suspended) {
// Journey is paused — show "check your email" UI
showMessage(suspended.message);
return;
}
// Otherwise render the callback inputs as normal
}
}
Handling the incoming deep link and resuming the journey
To handle the incoming URI and resume the journey, use React Native’s Linking API with actions.resume() from the useJourney hook.
The hook handles node state, loading, and error internally:
import React, { useEffect } from 'react';
import { Linking } from 'react-native';
import { useJourney } from '@ping-identity/rn-journey';
export function LoginScreen() {
const [node, actions] = useJourney(journeyClient);
useEffect(() => {
// App launched cold from the link — the URL won’t fire an event, so fetch it directly
Linking.getInitialURL().then((url) => { if (url) actions.resume(url); });
// App already running — the OS fires this event when the link is tapped
const sub = Linking.addEventListener('url', ({ url }) => actions.resume(url));
return () => sub.remove();
}, []);
// Render based on node ...
}
The resume() call accepts the complete URI string, including the suspendedId query parameter, and returns the next journey node, in the same manner as start() and next().
Handling errors
If resume() fails, for example, because the suspendedId has already been used or has expired, then useJourney sets actions.error to a JourneyError instance.
Read actions.error to surface the problem to the user:
export function LoginScreen() {
const [node, actions] = useJourney(journeyClient);
if (actions.error) {
return (
<View>
<Text>{actions.error.message}</Text>
<Button title="Start over" onPress={() => actions.start('Login')} />
</View>
);
}
// ...
}
|
A If the user visits the same link multiple times, |