Skip to content

Commit

Permalink
Handle walletconnect deeplinks (#3311)
Browse files Browse the repository at this point in the history
- Handle walletconnect deeplinks
- Refactor start intent handling
- Add start intent/deeplink handling unit tests
  • Loading branch information
JamesSmartCell authored Sep 25, 2023
1 parent 3e7ff79 commit 6db4ff3
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 114 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ dependencies {
analyticsImplementation 'com.google.android.play:core:1.10.3'
analyticsImplementation 'com.google.firebase:firebase-analytics:21.3.0'
analyticsImplementation 'com.mixpanel.android:mixpanel-android:5.8.4'
analyticsImplementation 'com.google.firebase:firebase-crashlytics:18.4.1'
analyticsImplementation 'com.google.firebase:firebase-crashlytics:18.4.3'

// Notifications: NB there appears to be an incompatibility in the newer builds of firebase-messaging.
// Update when resolved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,26 @@ public void parse(String urlString)
final HttpUrl url = HttpUrl.parse(urlString);
if (url != null)
{
for (Method method : ApiV1.VALID_METHODS)
try
{
if (method.getPath().equals(url.encodedPath()))
String encodedPath = url.encodedPath();
for (Method method : ApiV1.VALID_METHODS)
{
this.isValid = true;
this.requestUrl = url;
this.method = method;
this.redirectUrl = url.queryParameter(ApiV1.RequestParams.REDIRECT_URL);
final String metadataJson = url.queryParameter(ApiV1.RequestParams.METADATA);
this.metadata = new Gson().fromJson(metadataJson, Metadata.class);
if (method.getPath().equals(encodedPath))
{
this.isValid = true;
this.requestUrl = url;
this.method = method;
this.redirectUrl = url.queryParameter(ApiV1.RequestParams.REDIRECT_URL);
final String metadataJson = url.queryParameter(ApiV1.RequestParams.METADATA);
this.metadata = new Gson().fromJson(metadataJson, Metadata.class);
}
}
}
catch (Exception e)
{
//continue
}
}
else
{
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/com/alphawallet/app/entity/DeepLinkRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.alphawallet.app.entity;

public class DeepLinkRequest
{
public DeepLinkType type;
public final String data;

public DeepLinkRequest(DeepLinkType type, String data)
{
this.type = type;
this.data = data;
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/com/alphawallet/app/entity/DeepLinkType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.alphawallet.app.entity;

public enum DeepLinkType
{
WALLETCONNECT,
SMARTPASS,
URL_REDIRECT,
TOKEN_NOTIFICATION,
WALLET_API_DEEPLINK,
LEGACY_MAGICLINK, //ERC875 token import
IMPORT_SCRIPT,
INVALID_LINK

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.alphawallet.app.repository.KeyProviderFactory;
import com.alphawallet.app.repository.entity.RealmAttestation;
import com.alphawallet.app.service.AssetDefinitionService;
import com.alphawallet.app.service.DeepLinkService;
import com.alphawallet.app.service.RealmManager;
import com.alphawallet.app.service.TokensService;
import com.alphawallet.app.util.Utils;
Expand Down Expand Up @@ -79,9 +80,8 @@ public class ImportAttestation
private final RealmManager realmManager;
private final OkHttpClient client;
private final KeyProvider keyProvider = KeyProviderFactory.get();
public static final String SMART_PASS_URL = "https://aw.app/openurl?url=";
public static final String SMART_PASS_URL = DeepLinkService.AW_APP + "openurl?url=";
public static final String DELETE_KEY = "DELETE";

private static final String SMART_PASS_API = "https://backend.smartlayer.network/passes/pass-installed-in-aw";
private static final String SMART_PASS_API_DEV = "https://d2a5tt41o5qmyt.cloudfront.net/passes/pass-installed-in-aw";

Expand Down
138 changes: 138 additions & 0 deletions app/src/main/java/com/alphawallet/app/service/DeepLinkService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.alphawallet.app.service;

import static com.alphawallet.app.entity.WalletPage.DAPP_BROWSER;
import static com.alphawallet.app.entity.WalletPage.WALLET;
import static com.alphawallet.app.ui.HomeActivity.AW_MAGICLINK_DIRECT;

import android.content.Intent;
import android.text.TextUtils;

import com.alphawallet.app.analytics.Analytics;
import com.alphawallet.app.api.v1.entity.request.ApiV1Request;
import com.alphawallet.app.entity.CryptoFunctions;
import com.alphawallet.app.entity.DeepLinkRequest;
import com.alphawallet.app.entity.DeepLinkType;
import com.alphawallet.app.entity.EIP681Type;
import com.alphawallet.app.entity.QRResult;
import com.alphawallet.app.entity.attestation.ImportAttestation;
import com.alphawallet.app.repository.EthereumNetworkRepository;
import com.alphawallet.app.router.ImportTokenRouter;
import com.alphawallet.app.ui.DappBrowserFragment;
import com.alphawallet.app.util.Utils;
import com.alphawallet.token.entity.SalesOrderMalformed;
import com.alphawallet.token.tools.ParseMagicLink;

public class DeepLinkService
{
public static final String AW_APP = "https://aw.app/";
public static final String WC_PREFIX = "wc?uri=";
public static final String AW_PREFIX = "awallet://";
public static DeepLinkRequest parseIntent(String importData, Intent startIntent)
{
if (TextUtils.isEmpty(importData))
{
return checkIntents(startIntent);
}

importData = Utils.universalURLDecode(importData);

if (checkSmartPass(importData))
{
return new DeepLinkRequest(DeepLinkType.SMARTPASS, importData);
}

if (importData.startsWith(AW_APP + WC_PREFIX) || importData.startsWith(AW_PREFIX + WC_PREFIX))
{
int prefixIndex = importData.indexOf(WC_PREFIX) + WC_PREFIX.length();
return new DeepLinkRequest(DeepLinkType.WALLETCONNECT, importData.substring(prefixIndex));
}

if (importData.startsWith(NotificationService.AWSTARTUP))
{
return new DeepLinkRequest(DeepLinkType.TOKEN_NOTIFICATION, importData.substring(NotificationService.AWSTARTUP.length()));
}

if (importData.startsWith("wc:"))
{
return new DeepLinkRequest(DeepLinkType.WALLETCONNECT, importData);
}

if (new ApiV1Request(importData).isValid())
{
return new DeepLinkRequest(DeepLinkType.WALLET_API_DEEPLINK, importData);
}

int directLinkIndex = importData.indexOf(AW_MAGICLINK_DIRECT);
if (directLinkIndex > 0)
{
String link = importData.substring(directLinkIndex + AW_MAGICLINK_DIRECT.length());
if (Utils.isValidUrl(link))
{
//get link
return new DeepLinkRequest(DeepLinkType.URL_REDIRECT, link);
}
}

if (isLegacyMagiclink(importData))
{
return new DeepLinkRequest(DeepLinkType.LEGACY_MAGICLINK, importData);
}

if (startIntent != null && importData.startsWith("content://") && startIntent.getData() != null
&& !TextUtils.isEmpty(startIntent.getData().getPath()))
{
return new DeepLinkRequest(DeepLinkType.IMPORT_SCRIPT, null);
}


// finally see if there was a url in the intent (with non empty importData) or bail with invalid link
return checkIntents(startIntent);
}

// Check possibilities where importData is empty
private static DeepLinkRequest checkIntents(Intent startIntent)
{
String startIntentData = startIntent != null ? startIntent.getStringExtra("url") : null;
if (startIntentData != null)
{
return new DeepLinkRequest(DeepLinkType.URL_REDIRECT, startIntentData);
}
else
{
return new DeepLinkRequest(DeepLinkType.INVALID_LINK, null);
}
}

private static boolean isLegacyMagiclink(String importData)
{
try
{
ParseMagicLink parser = new ParseMagicLink(new CryptoFunctions(), EthereumNetworkRepository.extraChains());
if (parser.parseUniversalLink(importData).chainId > 0)
{
return true;
}
}
catch (SalesOrderMalformed e)
{
//
}

return false;
}

private static boolean checkSmartPass(String importData)
{
QRResult result = null;
if (importData != null && importData.startsWith(ImportAttestation.SMART_PASS_URL))
{
importData = importData.substring(ImportAttestation.SMART_PASS_URL.length()); //chop off leading URL
result = new QRResult(importData);
result.type = EIP681Type.EAS_ATTESTATION;
String taglessAttestation = Utils.parseEASAttestation(importData);
result.functionDetail = Utils.toAttestationJson(taglessAttestation);
}

return result != null && !TextUtils.isEmpty(result.functionDetail);
}
}
Loading

0 comments on commit 6db4ff3

Please sign in to comment.