Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: yat deep link handling #89

Merged
merged 15 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<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="shapeshift" />
</intent-filter>
</activity>
</application>
</manifest>
23 changes: 23 additions & 0 deletions android/app/src/main/java/com/shapeshift/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,34 @@
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;

import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.ReactContext;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends ReactActivity {

@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Uri url = intent.getData();
// only handle shapeshift:// urls
if (url != null && "shapeshift".equals(url.getScheme())) {
this.sendEvent(getReactInstanceManager().getCurrentReactContext(), "url", url.toString());
}
}

private void sendEvent(ReactContext reactContext, String eventName, String params) {
if (reactContext != null) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}

/**
* Returns the name of the main component registered from JavaScript. This is used to schedule
* rendering of the component.
Expand Down
8 changes: 8 additions & 0 deletions ios/shapeshift/AppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#import <React/RCTRootView.h>

#import <React/RCTAppSetupUtils.h>
#import <React/RCTLinkingManager.h>


#if RCT_NEW_ARCH_ENABLED
#import <React/CoreModulesPlugins.h>
Expand All @@ -29,6 +31,12 @@ @interface AppDelegate () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate>

@implementation AppDelegate

// https://reactnative.dev/docs/linking?syntax=ios#enabling-deep-links
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options
{
return [RCTLinkingManager application:application openURL:url options:options];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
RCTAppSetupPrepareApp(application);
Expand Down
11 changes: 11 additions & 0 deletions ios/shapeshift/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.shapeshift.shapeshift</string>
<key>CFBundleURLSchemes</key>
<array>
<string>shapeshift</string>
</array>
</dict>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"clean": "react-native-clean",
"clean:ios": "rm -rf ios/build && rm -rf ios/Pods && react-native-clean",
"android": "react-native run-android --appId 'com.shapeshift.droid_shapeshift' && yarn start",
"android": "react-native run-android --appId 'com.shapeshift.droid_shapeshift'",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
Expand Down
54 changes: 50 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react'
import { ActivityIndicator, BackHandler, View } from 'react-native'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { ActivityIndicator, BackHandler, Linking, View } from 'react-native'
import ErrorBoundary from 'react-native-error-boundary'
import RNShake from 'react-native-shake'
import { WebView } from 'react-native-webview'
Expand All @@ -12,8 +12,14 @@ import { getMessageManager } from './lib/getMessageManager'
import { shouldLoadFilter } from './lib/navigationFilter'
import { styles } from './styles'

import { LogBox } from 'react-native'

// disable bottom toast in app simulators - read the console instead
LogBox.ignoreAllLogs()

const App = () => {
const { settings, setSetting } = useSettings()
const [uri, setUri] = useState<string>()
const [loading, setLoading] = useState(true)
const [error, setError] = useState(false)
const [isDebugModalVisible, setIsDebugModalVisible] = useState(false)
Expand All @@ -38,6 +44,32 @@ const App = () => {
}
}, [messageManager])

// https://reactnative.dev/docs/linking?syntax=android#handling-deep-links
useEffect(() => {
if (!settings) return

// shared link handler
const deepLinkHandler = ({ url }: { url: string }) => {
// "shouldn't" happen, but did in testing
if (!url) return
// e.g. shapeshift://yat/🦊🚀🌈
// url escaped http://192.168.1.22:3000/#/yat/%F0%9F%A6%8A%F0%9F%9A%80%F0%9F%8C%88
// to test this, run:
// npx uri-scheme open "shapeshift://yat/%F0%9F%A6%8A%F0%9F%9A%80%F0%9F%8C%88" --ios
// npx uri-scheme open "shapeshift://yat/%F0%9F%A6%8A%F0%9F%9A%80%F0%9F%8C%88" --android
const URL_DELIMITER = 'shapeshift://'
const path = url.split(URL_DELIMITER)[1]
const newUri = `${settings?.SHAPESHIFT_URI}/#/${path}?${Date.now()}`
setUri(newUri)
}

// case where the app is backgrounded/not yet opened
Linking.getInitialURL().then(url => url && deepLinkHandler({ url }))

// case where the app is foregrounded/currently open
Linking.addEventListener('url', deepLinkHandler)
}, [settings, webviewRef])

useEffect(() => {
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
Expand All @@ -53,7 +85,18 @@ const App = () => {
}
}, [startImport, loading])

const defaultUrl = useMemo(() => {
if (!settings) return
return `${settings.SHAPESHIFT_URI}`
}, [settings])

useEffect(() => {
if (!defaultUrl) return
setUri(defaultUrl)
}, [defaultUrl])

if (!settings?.SHAPESHIFT_URI) return null
if (!uri) return null

return (
<View style={styles.container}>
Expand Down Expand Up @@ -93,9 +136,12 @@ const App = () => {
console.debug('\x1b[7m onNavigationStateChange', e, '\x1b[0m')
if (loading) setLoading(e.loading)
}}
onContentProcessDidTerminate={() => webviewRef.current?.reload()}
onContentProcessDidTerminate={() => {
setUri(defaultUrl)
webviewRef.current?.reload()
}}
onShouldStartLoadWithRequest={shouldLoadFilter}
source={{ uri: `${settings.SHAPESHIFT_URI}/#/dashboard` }}
source={{ uri }}
onError={syntheticEvent => {
const { nativeEvent } = syntheticEvent
console.error('WebView onError: ', nativeEvent)
Expand Down