Deep Linking
Deep linking allows users to open specific StorifyMe stories directly from shared links, providing a seamless experience when content is shared across platforms.
Make sure you have configured your sharing domain in the StorifyMe Dashboard Settings. Set either Custom Domain OR Proxy - this domain will be used when links are copied/shared from your stories.
Installation
Install the required packages:
npm install @react-native-async-storage/async-storage react-native-bootsplash
# For React Navigation (if using)
npm install @react-navigation/native @react-navigation/native-stack react-native-screens
For linking functionality, you can use React Navigation's built-in linking or implement your own solution.
Platform Configuration
Android Configuration
Update AndroidManifest.xml
Add an intent filter to your Android manifest:
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop">
<!-- Your existing intent filters -->
<!-- Deep link intent filter -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="YOUR_PROXY_DOMAIN" />
</intent-filter>
</activity>
Add assetlinks.json
Create an assetlinks.json
file at https://YOUR_PROXY_DOMAIN/.well-known/assetlinks.json
:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.your.package",
"sha256_cert_fingerprints": [
"YOUR_PACKAGE_FINGERPRINT"
]
}
}
]
iOS Configuration
Add Associated Domain
In ios/YourApp/YourApp.entitlements
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:YOUR_PROXY_DOMAIN</string>
</array>
</dict>
</plist>
Add apple-app-site-association
Create an apple-app-site-association
file at https://YOUR_PROXY_DOMAIN/.well-known/apple-app-site-association
:
{
"applinks": {
"details": [
{
"appIDs": ["TEAMID.com.your.package"],
"paths": ["*"]
}
]
}
}
Implementation
Method 1: Direct Story Launch (Recommended)
For shared story links, use direct story opening without depending on widget loading:
import React, { useEffect } from 'react';
import { Linking } from 'react-native';
import StorifyMe from 'react-native-storifyme';
const App = () => {
useEffect(() => {
// Handle deep links when app is already running
const handleUrl = (event: { url: string }) => {
handleDeepLink(event.url);
};
const subscription = Linking.addEventListener('url', handleUrl);
// Handle deep links when app is launched from link
Linking.getInitialURL().then((url) => {
if (url) {
handleDeepLink(url);
}
});
return () => {
subscription?.remove();
};
}, []);
const handleDeepLink = (url: string) => {
try {
const parsedUrl = new URL(url);
if (parsedUrl.protocol === 'https:') {
const handle = extractStoryHandle(parsedUrl);
if (handle) {
// Launch story directly
StorifyMe.openStoryByHandle(handle);
}
}
} catch (error) {
console.warn('Invalid deep link URL:', url);
}
};
const extractStoryHandle = (url: URL): string | null => {
const pathSegments = url.pathname.split('/').filter(segment => segment);
switch (pathSegments.length) {
case 1:
// For URLs like: https://domain.com/story-handle
return pathSegments[0];
case 2:
// For URLs like: https://domain.com/story/story-handle
if (pathSegments[0] === 'story' || pathSegments[0] === 'stories') {
return pathSegments[1];
}
break;
default:
return null;
}
return null;
};
return (
<YourAppContent />
);
};
export default App;
Method 2: Widget-Based Launch
If you need to open a story within a loaded widget context:
import React, { useState, useEffect, useRef } from 'react';
import { View } from 'react-native';
import StorifyMe, { StorifyMeWidget } from 'react-native-storifyme';
interface StoriesScreenProps {
pendingHandle?: string;
}
const StoriesScreen: React.FC<StoriesScreenProps> = ({ pendingHandle }) => {
const [widgetLoaded, setWidgetLoaded] = useState(false);
const widgetRef = useRef<StorifyMeWidget>(null);
const pendingHandleRef = useRef<string | undefined>(pendingHandle);
useEffect(() => {
pendingHandleRef.current = pendingHandle;
}, [pendingHandle]);
const handleWidgetLoad = (stories: any[]) => {
setWidgetLoaded(true);
// If we have a pending handle, open it now
if (pendingHandleRef.current) {
openStoryInWidget(pendingHandleRef.current);
pendingHandleRef.current = undefined;
}
};
const handleWidgetFail = (error: string) => {
console.error('Widget failed to load:', error);
// If widget fails to load but we have a handle, try direct launch
if (pendingHandleRef.current) {
StorifyMe.openStoryByHandle(pendingHandleRef.current);
pendingHandleRef.current = undefined;
}
};
const openStoryInWidget = (handle: string) => {
widgetRef.current?.openStoryByHandle(handle, (result) => {
if (!result.success) {
// Story not found in widget, fall back to direct launch
StorifyMe.openStoryByHandle(handle);
}
});
};
const handleDeepLink = (handle: string) => {
if (widgetLoaded) {
// Widget is already loaded, open immediately
openStoryInWidget(handle);
} else {
// Store handle to open after widget loads
pendingHandleRef.current = handle;
}
};
return (
<View style={{ flex: 1 }}>
<StorifyMeWidget
ref={widgetRef}
widgetId="YOUR_WIDGET_ID"
onLoad={handleWidgetLoad}
onFail={handleWidgetFail}
style={{ flex: 1 }}
/>
</View>
);
};
export default StoriesScreen;
Using React Navigation
If you're using React Navigation, you can handle deep links through the navigation configuration:
import { LinkingOptions } from '@react-navigation/native';
import StorifyMe from 'react-native-storifyme';
const linking: LinkingOptions<RootStackParamList> = {
prefixes: ['https://YOUR_PROXY_DOMAIN'],
config: {
screens: {
Home: '',
Story: {
path: '/story/:handle',
parse: {
handle: (handle: string) => {
// Open story directly when navigating to story route
StorifyMe.openStoryByHandle(handle);
return handle;
},
},
},
// Handle direct story handle URLs
DirectStory: {
path: '/:handle',
parse: {
handle: (handle: string) => {
StorifyMe.openStoryByHandle(handle);
return handle;
},
},
},
},
},
};
export default linking;
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import linking from './navigation/LinkingConfiguration';
const Stack = createNativeStackNavigator();
const App = () => {
return (
<NavigationContainer linking={linking}>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Story" component={StoryScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
Using Redux for State Management
If you're using Redux, you can handle deep links through actions:
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface DeepLinkState {
pendingHandle?: string;
isProcessing: boolean;
}
const initialState: DeepLinkState = {
isProcessing: false,
};
const deepLinkSlice = createSlice({
name: 'deepLink',
initialState,
reducers: {
setPendingHandle: (state, action: PayloadAction<string>) => {
state.pendingHandle = action.payload;
},
clearPendingHandle: (state) => {
state.pendingHandle = undefined;
},
setProcessing: (state, action: PayloadAction<boolean>) => {
state.isProcessing = action.payload;
},
},
});
export const { setPendingHandle, clearPendingHandle, setProcessing } = deepLinkSlice.actions;
export default deepLinkSlice.reducer;
import { useEffect } from 'react';
import { Linking } from 'react-native';
import { useDispatch } from 'react-redux';
import StorifyMe from 'react-native-storifyme';
import { setPendingHandle } from '../store/deepLinkSlice';
export const useDeepLink = () => {
const dispatch = useDispatch();
useEffect(() => {
const handleUrl = (event: { url: string }) => {
processDeepLink(event.url);
};
const subscription = Linking.addEventListener('url', handleUrl);
Linking.getInitialURL().then((url) => {
if (url) {
processDeepLink(url);
}
});
return () => {
subscription?.remove();
};
}, []);
const processDeepLink = (url: string) => {
try {
const parsedUrl = new URL(url);
const handle = extractStoryHandle(parsedUrl);
if (handle) {
// Try direct launch first
StorifyMe.openStoryByHandle(handle);
// Also store in Redux for widget-based fallback
dispatch(setPendingHandle(handle));
}
} catch (error) {
console.warn('Invalid deep link URL:', url);
}
};
const extractStoryHandle = (url: URL): string | null => {
const pathSegments = url.pathname.split('/').filter(segment => segment);
switch (pathSegments.length) {
case 1:
return pathSegments[0];
case 2:
if (pathSegments[0] === 'story' || pathSegments[0] === 'stories') {
return pathSegments[1];
}
break;
default:
return null;
}
return null;
};
};
Testing Deep Links
Android Testing
Using ADB:
adb shell am start \
-W -a android.intent.action.VIEW \
-d "https://YOUR_PROXY_DOMAIN/your-story-handle" \
com.your.package
iOS Testing
- Use Safari in the iOS Simulator
- Navigate to your deep link URL
- Tap the link to trigger your app
Development Testing
You can simulate deep links during development:
// Add this to your development tools
const simulateDeepLink = (handle: string) => {
const testUrl = `https://YOUR_PROXY_DOMAIN/${handle}`;
Linking.openURL(testUrl);
};
URL Pattern Examples
Your implementation should handle various URL patterns:
URL Pattern | Handle Extraction |
---|---|
https://domain.com/story-handle | First path segment |
https://domain.com/story/story-handle | Second path segment |
https://domain.com/stories/story-handle | Second path segment |
Best Practices
- Use Direct Launch for Shared Content: Direct story opening is more reliable for shared links
- Implement Fallback Logic: If a story isn't found in a widget, fall back to direct launch
- Handle App States: Consider cold start vs. app already running scenarios
- Error Handling: Handle invalid or expired story handles gracefully
- Performance: Avoid blocking the main thread when processing deep links
- User Experience: Show loading states while processing deep links
TypeScript Types
export interface DeepLinkResult {
success: boolean;
handle?: string;
error?: string;
}
export interface StoryHandle {
handle: string;
widgetId?: string;
}
export type DeepLinkHandler = (url: string) => Promise<DeepLinkResult>;
Common Issues
Deep Link Not Working
- Verify platform-specific configuration files are properly set up
- Check that your proxy domain matches exactly in all configurations
- Test with different URL patterns
Story Not Opening
- Ensure the story handle exists and is published
- Check if direct launch method is available in your StorifyMe version
- Implement fallback to widget-based opening
Navigation Issues
- Consider how deep links affect your navigation stack
- Handle edge cases like multiple rapid deep link triggers
- Test with different app states (foreground, background, closed)